This commit is contained in:
mofeng-git
2025-12-28 18:19:16 +08:00
commit d143d158e4
771 changed files with 220548 additions and 0 deletions

View File

@@ -0,0 +1,510 @@
# 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/)