# 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 偏移 0x190,16 字节: ``` 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 { 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 // 解码(处理代理对) 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, current_cluster_index: usize, cluster_buffer: Vec, // 缓冲区(簇大小) 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, // 文件的 cluster 链 file_size: u64, // 文件总大小 position: u64, // 当前读取位置 cluster_cache: Option<(u32, Vec)>, // 当前 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 ``` ### 文件覆盖支持 所有写入方法都支持覆盖选项: - `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 { // 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/)