Files
One-KVM/libs/ventoy-img-rs/docs/TECHNICAL.md
mofeng-git d143d158e4 init
2025-12-28 18:19:16 +08:00

511 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ventoy-img 技术文档
## 概述
ventoy-img 是一个纯 Rust 实现的 Ventoy 可启动镜像生成工具,无需 root 权限或 loop 设备即可创建完整可用的 Ventoy IMG 文件。
## 架构设计
```
┌─────────────────────────────────────────────────────────────┐
│ CLI (main.rs) │
├─────────────────────────────────────────────────────────────┤
│ VentoyImage (image.rs) │
├──────────────┬──────────────┬──────────────┬───────────────┤
│ Partition │ exFAT │ Resources │ Error │
│ (partition.rs)│ (exfat/) │(resources.rs)│ (error.rs) │
└──────────────┴──────────────┴──────────────┴───────────────┘
```
## 核心模块
### 1. 分区模块 (partition.rs)
负责 MBR 分区表的创建和管理。
#### 分区布局
```
┌────────────────────────────────────────────────────────────┐
│ Sector 0: MBR (Boot Code + Partition Table + Signature) │
├────────────────────────────────────────────────────────────┤
│ Sector 1-2047: GRUB core.img (引导代码) │
├────────────────────────────────────────────────────────────┤
│ Sector 2048 - N: 数据分区 (exFAT, 存放 ISO 文件) │
├────────────────────────────────────────────────────────────┤
│ Sector N+1 - End: EFI 分区 (FAT16, 32MB, UEFI 启动) │
└────────────────────────────────────────────────────────────┘
```
#### MBR 结构 (512 字节)
| 偏移 | 大小 | 内容 |
|------|------|------|
| 0x000 | 440 | Boot Code (来自 boot.img) |
| 0x1B8 | 4 | Disk Signature |
| 0x1BC | 2 | Reserved |
| 0x1BE | 16 | Partition Entry 1 (数据分区) |
| 0x1CE | 16 | Partition Entry 2 (EFI 分区) |
| 0x1DE | 16 | Partition Entry 3 (未使用) |
| 0x1EE | 16 | Partition Entry 4 (未使用) |
| 0x1FE | 2 | Boot Signature (0x55AA) |
#### Ventoy 签名
位于 MBR 偏移 0x19016 字节:
```
56 54 00 47 65 00 48 44 00 52 64 00 20 45 72 0D
```
### 2. exFAT 模块 (exfat/)
完整的 exFAT 文件系统实现,支持读写操作。
#### 2.1 格式化 (format.rs)
创建 exFAT 文件系统结构:
```
┌─────────────────────────────────────────────────────────────┐
│ Boot Region (Sector 0-11) │
│ - Boot Sector (512 bytes) │
│ - Extended Boot Sectors (8 sectors) │
│ - OEM Parameters (2 sectors) │
│ - Boot Checksum Sector │
├─────────────────────────────────────────────────────────────┤
│ Backup Boot Region (Sector 12-23) │
├─────────────────────────────────────────────────────────────┤
│ FAT Region (Sector 24+) │
│ - FAT Table (每个 cluster 4 字节) │
├─────────────────────────────────────────────────────────────┤
│ Cluster Heap │
│ - Cluster 2: Allocation Bitmap │
│ - Cluster 3..N: Upcase Table (128KB可能跨多个簇) │
│ - Cluster N+1: Root Directory │
│ - Cluster N+2+: 用户数据 │
└─────────────────────────────────────────────────────────────┘
```
##### 动态簇大小
根据卷大小自动选择最优簇大小:
| 卷大小 | 簇大小 | 说明 |
|--------|--------|------|
| < 256MB | 4KB | 适合小文件减少浪费 |
| 256MB - 8GB | 32KB | 平衡性能和空间 |
| > 8GB | 128KB | 优化大文件 (ISO) 性能 |
```rust
fn get_cluster_size(total_sectors: u64) -> u32 {
let volume_size = total_sectors * 512;
match volume_size {
n if n < 256 * 1024 * 1024 => 4096, // < 256MB
n if n < 8 * 1024 * 1024 * 1024 => 32768, // 256MB - 8GB
_ => 128 * 1024, // > 8GB
}
}
```
#### Boot Sector 关键字段
| 偏移 | 大小 | 字段 | 说明 |
|------|------|------|------|
| 0 | 3 | JumpBoot | 跳转指令 (0xEB 0x76 0x90) |
| 3 | 8 | FileSystemName | "EXFAT " |
| 64 | 8 | PartitionOffset | 分区偏移 |
| 72 | 8 | VolumeLength | 卷大小(扇区数) |
| 80 | 4 | FatOffset | FAT 起始扇区 |
| 84 | 4 | FatLength | FAT 长度(扇区数) |
| 88 | 4 | ClusterHeapOffset | Cluster Heap 起始扇区 |
| 92 | 4 | ClusterCount | Cluster 总数 |
| 96 | 4 | FirstClusterOfRootDirectory | 根目录起始 Cluster |
| 100 | 4 | VolumeSerialNumber | 卷序列号 |
| 108 | 1 | BytesPerSectorShift | 扇区大小位移 (9 = 512) |
| 109 | 1 | SectorsPerClusterShift | Cluster 大小位移 |
| 510 | 2 | BootSignature | 0xAA55 |
#### 2.2 文件操作 (ops.rs)
##### Cluster 编号规则
- Cluster 0, 1: 保留
- Cluster 2: Allocation Bitmap
- Cluster 3: Upcase Table
- Cluster 4: Root Directory
- Cluster 5+: 用户数据
##### FAT 表条目值
| 值 | 含义 |
|----|------|
| 0x00000000 | 空闲 |
| 0x00000002 - 0xFFFFFFF6 | 下一个 Cluster |
| 0xFFFFFFF7 | 坏 Cluster |
| 0xFFFFFFF8 - 0xFFFFFFFF | 链结束 |
##### 目录条目类型
| 类型 | 值 | 说明 |
|------|-----|------|
| Volume Label | 0x83 | 卷标 |
| Allocation Bitmap | 0x81 | 位图描述 |
| Upcase Table | 0x82 | 大写表描述 |
| File | 0x85 | 文件/目录 |
| Stream Extension | 0xC0 | 流扩展 |
| File Name | 0xC1 | 文件名 |
##### 文件条目集结构
创建一个文件需要 3+ 个目录条目:
```
┌─────────────────────────────────────────────────────────────┐
│ File Directory Entry (0x85) - 32 bytes │
│ - EntryType: 0x85 │
│ - SecondaryCount: 后续条目数 │
│ - SetChecksum: 校验和 │
│ - FileAttributes: 属性 │
│ - Timestamps: 创建/修改/访问时间 │
├─────────────────────────────────────────────────────────────┤
│ Stream Extension Entry (0xC0) - 32 bytes │
│ - EntryType: 0xC0 │
│ - GeneralSecondaryFlags: 标志 │
│ - NameLength: 文件名长度 (UTF-16 字符数) │
│ - NameHash: 文件名哈希 │
│ - FirstCluster: 数据起始 Cluster │
│ - DataLength: 文件大小 │
├─────────────────────────────────────────────────────────────┤
│ File Name Entry (0xC1) - 32 bytes × N │
│ - EntryType: 0xC1 │
│ - FileName: 15 个 UTF-16 字符 │
└─────────────────────────────────────────────────────────────┘
```
##### 校验和算法
```rust
// Entry Set Checksum
fn calculate_entry_set_checksum(entries: &[[u8; 32]]) -> u16 {
let mut checksum: u16 = 0;
for (entry_idx, entry) in entries.iter().enumerate() {
for (byte_idx, &byte) in entry.iter().enumerate() {
// 跳过第一个条目的校验和字段 (bytes 2-3)
if entry_idx == 0 && (byte_idx == 2 || byte_idx == 3) {
continue;
}
checksum = checksum.rotate_right(1).wrapping_add(byte as u16);
}
}
checksum
}
// Name Hash (使用 Unicode 大写转换)
fn calculate_name_hash(name: &str) -> u16 {
let mut hash: u16 = 0;
for ch in name.encode_utf16() {
let upper = unicode::to_uppercase_simple(ch); // 使用 Unicode 模块
let bytes = upper.to_le_bytes();
hash = hash.rotate_right(1).wrapping_add(bytes[0] as u16);
hash = hash.rotate_right(1).wrapping_add(bytes[1] as u16);
}
hash
}
```
#### 2.3 Unicode 模块 (unicode.rs)
提供 exFAT 文件名的 Unicode 支持:
##### UTF-16 大写转换
支持以下字符范围的大小写转换:
- ASCII (a-z)
- Latin-1 Supplement (à-ÿ)
- Latin Extended-A (ā-ž)
- Greek (α-ω)
- Cyrillic (а-я, ѐ-џ)
```rust
pub fn to_uppercase_simple(ch: u16) -> u16 {
match ch {
0x0061..=0x007A => ch - 32, // ASCII a-z
0x00E0..=0x00F6 | 0x00F8..=0x00FE => ch - 32, // Latin-1
0x03B1..=0x03C1 => ch - 32, // Greek α-ρ
0x03C3..=0x03C9 => ch - 32, // Greek σ
0x03C2 => 0x03A3, // ς -> Σ
0x0430..=0x044F => ch - 32, // Cyrillic а
0x0450..=0x045F => ch - 80, // Cyrillic ѐ-џ
// ... 更多 Latin Extended-A 映射
_ => ch,
}
}
```
##### Upcase Table
生成 128KB 的 Upcase 表,映射每个 UTF-16 代码单元到其大写形式:
```rust
pub fn generate_upcase_table() -> Vec<u8> {
let mut table = Vec::with_capacity(65536 * 2);
for i in 0u32..65536 {
let upper = to_uppercase_simple(i as u16);
table.extend_from_slice(&upper.to_le_bytes());
}
table // 128KB
}
```
##### UTF-16 编解码
支持 BMP 和补充平面字符(如 Emoji
```rust
// 编码
pub fn encode_utf16le(s: &str) -> Vec<u8>
// 解码(处理代理对)
pub fn decode_utf16le(bytes: &[u8]) -> String
```
### 3. 资源模块 (resources.rs)
内嵌 Ventoy 启动所需的二进制资源:
| 资源 | 大小 | 用途 |
|------|------|------|
| boot.img | 512 bytes | MBR 引导代码 |
| core.img.xz | ~448 KB | GRUB 核心镜像 (XZ 压缩) |
| ventoy.disk.img.xz | ~13 MB | EFI 分区镜像 (XZ 压缩) |
资源使用 `include_bytes!` 宏在编译时嵌入,运行时使用 `lzma-rs` 解压。
### 4. 错误处理 (error.rs)
使用 `thiserror` 定义错误类型:
```rust
#[derive(Debug, thiserror::Error)]
pub enum VentoyError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Invalid size format: {0}")]
InvalidSize(String),
#[error("Image error: {0}")]
ImageError(String),
#[error("Filesystem error: {0}")]
FilesystemError(String),
#[error("File not found: {0}")]
FileNotFound(String),
#[error("Decompression error: {0}")]
DecompressionError(String),
}
```
## 关键实现细节
### Cluster 大小选择
根据卷大小动态选择 cluster 大小:
- < 256MB: 4KB clusters (适合小文件)
- 256MB - 8GB: 32KB clusters (平衡)
- > 8GB: 128KB clusters (优化大文件性能)
Upcase Table 固定为 128KB (65536 × 2 bytes),可能跨多个 cluster。
### 流式读写
#### ExfatFileWriter
支持流式写入大文件:
```rust
pub struct ExfatFileWriter<'a> {
fs: &'a mut ExfatFs,
name: String,
total_size: u64, // 必须预先知道
allocated_clusters: Vec<u32>,
current_cluster_index: usize,
cluster_buffer: Vec<u8>, // 缓冲区(簇大小)
bytes_written: u64,
}
```
写入流程:
1. 预先分配所有需要的 clusters
2. 数据写入 cluster_buffer
3. 缓冲区满时写入当前 cluster
4. finish() 时创建目录条目
#### ExfatFileReader
支持流式读取大文件,实现 `std::io::Read``std::io::Seek`
```rust
pub struct ExfatFileReader<'a> {
fs: &'a mut ExfatFs,
cluster_chain: Vec<u32>, // 文件的 cluster 链
file_size: u64, // 文件总大小
position: u64, // 当前读取位置
cluster_cache: Option<(u32, Vec<u8>)>, // 当前 cluster 缓存
}
```
读取流程:
1. 根据 position 计算当前 cluster 索引和偏移
2. 如果 cluster 不在缓存中,读取并缓存
3. 从缓存中复制数据到用户缓冲区
4. 更新 position
Seek 支持:
- `SeekFrom::Start(n)` - 从文件开头偏移
- `SeekFrom::Current(n)` - 从当前位置偏移
- `SeekFrom::End(n)` - 从文件结尾偏移
### 文件名大小写
exFAT 文件名大小写不敏感但保留大小写:
- 查找时转换为小写比较
- 存储时保留原始大小写
- Name Hash 使用大写计算
## 性能考虑
1. **稀疏文件**: 使用 `file.set_len()` 创建稀疏文件,避免写入全零
2. **批量写入**: cluster 级别批量写入,减少 I/O 次数
3. **内存映射**: 未使用 mmap保持跨平台兼容性
4. **缓冲**: 流式写入使用 128KB 缓冲区
## 限制
1. ~~仅支持根目录文件操作~~ ✅ 已支持子目录
2. ~~不支持文件覆盖~~ ✅ 已支持文件覆盖
3. ~~目录条目限制在单个 cluster 内~~ ✅ 已支持目录扩展(多 cluster
4. ~~仅支持 ASCII 文件名~~ ✅ 已支持完整 Unicode中日韩、西里尔、希腊、Emoji
5. 不支持扩展属性和 ACL
## 新增功能
### 子目录支持
支持完整的目录操作:
- 路径解析:支持 `path/to/file` 格式
- 创建目录支持递归创建父目录mkdir -p
- 目录遍历:支持遍历多 cluster 的目录
- 递归列出:支持列出所有子目录中的文件
- 递归删除:支持删除目录及其所有内容
```rust
// 路径解析
fn parse_path(path: &str) -> Vec<&str> {
path.trim_matches('/')
.split('/')
.filter(|s| !s.is_empty())
.collect()
}
// 解析路径到目标目录
fn resolve_path(&mut self, path: &str, create_parents: bool) -> Result<ResolvedPath>
```
### 文件覆盖支持
所有写入方法都支持覆盖选项:
- `write_file_overwrite()` - 覆盖根目录文件
- `write_file_path()` - 支持 `overwrite` 参数
- `ExfatFileWriter::create_overwrite()` - 流式写入覆盖
- `ExfatFileWriter::create_at_path()` - 指定路径 + 覆盖
覆盖逻辑:
1. 检查目标文件是否存在
2. 如果存在且 `overwrite=true`,先删除旧文件
3. 创建新文件
### 目录扩展支持
当目录中的文件数量超过单个 cluster 容量时,自动扩展目录:
```rust
fn find_free_slot_in_directory(&mut self, dir_cluster: u32, entries_needed: usize) -> Result<(u32, u32)> {
// 1. 遍历目录链中的所有 cluster
// 2. 查找连续的空闲条目
// 3. 如果空间不足,调用 extend_cluster_chain() 分配新 cluster
// 4. 清除旧 cluster 中的 END 标记
// 5. 返回新 cluster 和偏移
}
fn extend_cluster_chain(&mut self, first_cluster: u32) -> Result<u32> {
// 1. 读取 cluster 链,找到最后一个 cluster
// 2. 分配一个新 cluster
// 3. 更新 FAT 表链接
// 4. 初始化新 cluster 为零
// 5. 返回新 cluster 编号
}
```
### 流式读取支持
`ExfatFileReader` 支持流式读取大文件:
```rust
use ventoy_img::exfat::ExfatFileReader;
use std::io::{Read, Seek, SeekFrom};
// 打开文件
let mut reader = ExfatFileReader::open(&mut fs, "large.iso")?;
// 获取文件信息
println!("Size: {}, Position: {}", reader.file_size(), reader.position());
// 读取数据
let mut buf = vec![0u8; 4096];
let n = reader.read(&mut buf)?;
// Seek 操作
reader.seek(SeekFrom::Start(1024))?;
reader.seek(SeekFrom::Current(100))?;
reader.seek(SeekFrom::End(-100))?;
```
特性:
- 实现 `std::io::Read``std::io::Seek` 特征
- Cluster 级别缓存,减少 I/O
- 支持任意位置 seek
- 内存占用低(只缓存当前 cluster
### Unicode 支持
完整的 Unicode 文件名支持:
支持的字符范围:
- ASCII (a-z, A-Z)
- Latin-1 Supplement (à-ÿ, À-Þ)
- Latin Extended-A (ā-ž)
- Greek (α-ω, Α-Ω)
- Cyrillic (а-я, А-Я, ѐ-џ, Ѐ-Џ)
- CJK 字符(中日韩)
- Emoji通过 UTF-16 代理对)
```rust
// 支持 Unicode 文件名
fs.write_file("中文文件.txt", b"content")?;
fs.write_file("Файл.txt", b"content")?; // 俄语
fs.write_file("αβγ.txt", b"content")?; // 希腊语
fs.write_file("😀🎉.txt", b"content")?; // Emoji
// 大小写不敏感查找
let data = fs.read_file("ФАЙЛ.TXT")?; // 找到 Файл.txt
```
## 参考资料
- [exFAT File System Specification](https://docs.microsoft.com/en-us/windows/win32/fileio/exfat-specification)
- [Ventoy Official Documentation](https://www.ventoy.net/en/doc_start.html)
- [GRUB Manual](https://www.gnu.org/software/grub/manual/grub/)