mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-01-28 16:41:52 +08:00
18 KiB
18 KiB
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) 性能 |
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 字符 │
└─────────────────────────────────────────────────────────────┘
校验和算法
// 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 (а-я, ѐ-џ)
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 代码单元到其大写形式:
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):
// 编码
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 定义错误类型:
#[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
支持流式写入大文件:
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,
}
写入流程:
- 预先分配所有需要的 clusters
- 数据写入 cluster_buffer
- 缓冲区满时写入当前 cluster
- finish() 时创建目录条目
ExfatFileReader
支持流式读取大文件,实现 std::io::Read 和 std::io::Seek:
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 缓存
}
读取流程:
- 根据 position 计算当前 cluster 索引和偏移
- 如果 cluster 不在缓存中,读取并缓存
- 从缓存中复制数据到用户缓冲区
- 更新 position
Seek 支持:
SeekFrom::Start(n)- 从文件开头偏移SeekFrom::Current(n)- 从当前位置偏移SeekFrom::End(n)- 从文件结尾偏移
文件名大小写
exFAT 文件名大小写不敏感但保留大小写:
- 查找时转换为小写比较
- 存储时保留原始大小写
- Name Hash 使用大写计算
性能考虑
- 稀疏文件: 使用
file.set_len()创建稀疏文件,避免写入全零 - 批量写入: cluster 级别批量写入,减少 I/O 次数
- 内存映射: 未使用 mmap,保持跨平台兼容性
- 缓冲: 流式写入使用 128KB 缓冲区
限制
仅支持根目录文件操作✅ 已支持子目录不支持文件覆盖✅ 已支持文件覆盖目录条目限制在单个 cluster 内✅ 已支持目录扩展(多 cluster)仅支持 ASCII 文件名✅ 已支持完整 Unicode(中日韩、西里尔、希腊、Emoji)- 不支持扩展属性和 ACL
新增功能
子目录支持
支持完整的目录操作:
- 路径解析:支持
path/to/file格式 - 创建目录:支持递归创建父目录(mkdir -p)
- 目录遍历:支持遍历多 cluster 的目录
- 递归列出:支持列出所有子目录中的文件
- 递归删除:支持删除目录及其所有内容
// 路径解析
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()- 指定路径 + 覆盖
覆盖逻辑:
- 检查目标文件是否存在
- 如果存在且
overwrite=true,先删除旧文件 - 创建新文件
目录扩展支持
当目录中的文件数量超过单个 cluster 容量时,自动扩展目录:
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 支持流式读取大文件:
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 代理对)
// 支持 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