KarpelesLab/fstool

GitHub: KarpelesLab/fstool

一个 Rust 实现的多格式磁盘映像与文件系统构建、检查、转换和重新打包工具,同时提供 CLI 与可嵌入的库。

Stars: 1 | Forks: 0

# fstool [![CI](https://static.pigsec.cn/wp-content/uploads/repos/cas/ad/ad5834178f7599af9fdda11629d49cae07f2997beec49821b2920eff5bfd50e7.svg)](https://github.com/KarpelesLab/fstool/actions/workflows/ci.yml) [![Crates.io](https://img.shields.io/crates/v/fstool.svg)](https://crates.io/crates/fstool) [![docs.rs](https://docs.rs/fstool/badge.svg)](https://docs.rs/fstool) 构建、检查、修改和重新打包磁盘映像与文件系统映像。 秉承 `genext2fs` 的理念,但支持整个磁盘、多种文件系统, 以及格式间的往返转换——所有操作均可通过 TOML 规范或直接从 命令行完成。 fstool 以 Rust 库(`fstool`)加上一个轻量级 CLI 二进制程序(`fstool`)的形式发布。 在 v0.5 版本之前,公共 API 是**不稳定**的。 ``` cargo install fstool fstool create -t ext4 ./src -o out.img # build an ext4 image from a dir fstool create -t squashfs ./src -o out.sqsh \ -O compression=zstd,block_size=128KiB # FS-specific knobs via -O fstool info out.img # what's inside fstool ls out.img / # walk it fstool repack out.img out.tar # convert ext4 → tar (and back) fstool repack base.tar patch.tar flat.tar # OCI-style layer merge with .wh.* whiteouts ``` ## 文件系统支持 | 文件系统 | 读取 | 写入 | 原地编辑 | 备注 | |------------|------|-------|----------------|--------------------------------------------------------------------------------------------------------------------| | ext2 | ✅ | ✅ | ✅ | 在相同输入下与 `genext2fs` 字节完全一致 | | ext3 | ✅ | ✅ | ✅ | + JBD2 日志 — 在 `open_file_rw`(Path A)上进行真实事务 | | ext4 | ✅ | ✅ | ✅ | extents(读取 + 写入:任意深度),FILETYPE,`metadata_csum`,xattrs,JBD2 | | FAT32 | ✅ | ✅ | ✅ | VFAT LFN 条目,8.3 短文件名别名 | | exFAT | ✅ | ✅ | ✅ | 格式化 + 创建 + 移除 + flush + `open_file_rw` | | tar | ✅ | ✅ | — | ustar + PAX,用于 xattrs 的 `SCHILY.xattr.*`;仅限流式处理 | | XFS | ✅ | ✅ | ✅ | shortform + block / leaf / node + 多级 B-tree 目录 + BMBT;leaf-form xattrs;真实 XLOG 事务 (Path A);通过 `xfs_repair -n` 单一及多 AG 测试 | | HFS+/HFSX | ✅ | ✅ | ✅ | inline + extents-overflow,符号链接,硬链接;decmpfs 读取(zlib 类型 3 + 4);**资源分叉**(`cat --rsrc`,`resources`,`com.apple.ResourceFork` xattr);真实日志 (Path A);通过 `fsck.hfsplus` 测试 | | HFS | ✅ | ✅ | ✅ | 经典 HFS (Mac OS ≤ 8):MDB + catalog/extents B-trees,MacRoman 名称,数据 + **资源**分叉读取;透明解包 **DiskCopy 4.2** 映像。**写入**:`create -t hfs` / `build` / `repack` 生成全新卷,而 `add` / `rm` / shell `put`/`mkdir` 会原地修改现有映像(在 flush 时重建 catalog) | | AFFS | ✅ | ✅ | ✅ | Amiga OFS/FFS(`.adf`):启动块变体检测(`DOS\0`..`DOS\7`),哈希表目录,文件头 + 扩展块,OFS(24 字节数据头)+ FFS 原始数据,BCPL/Latin-1 名称,Amiga 1978 纪元日期;读取已针对真实 OFS/FFS Workbench 卷进行验证。**写入**:`create -t affs` / `-t ofs` / `build` / `repack` 生成全新的 OFS 或 FFS 卷(默认为 DOS\3 FFS+INTL;`-O fstype=ofs,intl=false`),而 `add` / `rm` / shell `put`/`mkdir` 会在磁盘上**增量**修改现有映像 — 仅触及受影响的块(卷位图、父目录的哈希链以及新增/移除文件的文件头 + 数据 + 扩展块);未被触及的文件保留其原始块,且内存使用量由位图决定,而非文件内容。符合规范(块校验和 + 名称哈希放置 + 位图,即 Linux 内核 `affs` 驱动程序强制执行的不变量) | | APFS | ✅ | ✅ | 🚧 | **读取**:多级 omap + fs-tree,目录列表 + 文件 extents,内嵌 xattrs,快照(只读,单叶 snap-meta)。**写入**:格式化 + `create_dir`/`create_file`/`create_symlink` + `chmod`/`chown`/`set_times`/`rename`/`unlink`/`link`,通过全新的 COW 检查点(带有 IP 环 + SFQ 空闲队列的 spaceman),往返于真实的 macOS 挂载。**缺口**:原地编辑为整文件覆盖(无部分 extent COW);`UF_COMPRESSED`/decmpfs 文件读取为空;加密、sealed-volume 完整性、Fusion 分层以及基于 dstream 的 xattrs 被拒绝;尚未达到 `fsck_apfs` 清洁标准 | | NTFS | ✅ | ✅ | ✅ | MFT,属性,$DATA + ADS,索引;xattr 映射;多类 `$Secure`($SDS/$SDH/$SII);真实的 `$LogFile` LFS 记录 (Path A) | | F2FS | ✅ | ✅ | — | CP / NAT / dnodes / inline data + dentries;写入器通过 `fsck.f2fs` 测试;**一次构建** — 写入器在 flush 时从内存序列化整个 FS,因此重新打开的映像是只读的(报告为 `Immutable`) | | SquashFS | ✅ | ✅ | — | 通过 Cargo 特性支持 gzip / xz / lz4 / zstd / lzo / lzma;写入器通过 `unsquashfs` 进行往返转换;仅限重新打包 | | ISO 9660 | ✅ | ✅ | — | PVD + Joliet (UCS-2) + Rock Ridge (PX/NM/SL/TF) + El Torito 启动目录;仅限重新打包 | | GRF | ✅ | ✅ | ✅ | Gravity Ragnarok Online 归档 — v0x102 / v0x103 / v0x200;置换密码(`MIXCRYPT` / `DES`);CP949 文件名 | | zip | ✅ | ✅ | — | central-directory 索引,ZIP64,Stored + Deflate,Unix mode/symlinks,UTF-8/Shift-JIS/EUC-JP 文件名检测;仅限重新打包写入器 | | cpio | ✅ | ✅ | — | 读取 newc / newc-crc / odc;写入 newc;仅限重新打包 | | ar | ✅ | ✅ | — | 读取 GNU + BSD 长文件名,写入 GNU;扁平结构(无目录);仅限重新打包 | | cab | ✅ | — | — | 只读 Microsoft Cabinet:Store / MSZIP / LZX / Quantum 文件夹通过 `compcol` 解码(与 `cabextract` 交叉验证)。不支持 Spanned cabinet 和创建 | | lzx | ✅ | — | — | 只读 Amiga LZX:通过 `compcol::amiga_lzx` 处理 Store + LZX(mode 2)合并组;容器已与 `unlzx` 交叉验证。不支持创建 | | rar | ✅ | — | — | 只读 RAR5,包含 **solid** 归档(顺序遍历 / `repack` 仅解码该组一次):通过 `compcol::rar5` 处理 Store + 压缩(无过滤器 / x86 E8E9);已与 `unrar` 交叉验证。不支持 RAR4、加密、stored-in-solid、其他过滤器及创建 | | lha | ✅ | — | — | 只读 LHA / LZH:遍历 level-0/1/2 头(长文件名 + 目录)。`-lh0-` store 解码并已与 `lha` 交叉验证;lh1/4/5/6/7 LZSS+Huffman 方法已列出,但在 `compcol` 中等待 `lha` 编解码器支持,读取时返回 `Unsupported`。不支持创建 | | arc | ✅ | — | — | 只读 SEA ARC:遍历扁平头链。Stored 方法(1 old / 2)解码;压缩方法(RLE90 / squeeze / crunch / squash)已列出,但在 `compcol` 中等待 ARC 编解码器支持,读取时返回 `Unsupported`。不支持创建 | | sit | ✅ | — | — | 只读 StuffIt:经典 `SIT!` 容器(数据分叉索引,文件夹标记)。Method 0(store)解码;压缩方法 + StuffIt 5 可列出/检测,但在 `compcol` 中等待 StuffIt 编解码器支持,读取时返回 `Unsupported`。不支持创建 | | 7z | ✅ | — | — | 只读 7-Zip:解析容器(包含 LZMA 打包头 + 按子流切分的 solid 文件夹);单编码器 **Copy / LZMA / BZip2 / Deflate** 文件夹解码(已与 `7z` 交叉验证)。**LZMA2**(默认)、BCJ 过滤器、PPMd、加密及多编码器流水线已列出,但在 `compcol` 中等待原生 LZMA2 + 分支过滤器编解码器支持,读取时返回 `Unsupported`。不支持创建 | `🚧` 标记了存在已知缺陷的写入器/修改路径(参见限制)。 所有可写的文件系统 — ext2/3/4, FAT32, exFAT, XFS, HFS+, NTFS, APFS, F2FS, SquashFS, ISO 9660, GRF — 均实现了统一的 `Filesystem` trait,因此 CLI(`build`, `repack`, `add`, `rm`)和 TOML `[filesystem] type = "…"` 规范会通过同一个 代码路径分发;可通过在 `repack` 上设置 `--fs-type` 或在 TOML 规范中设置 `type = "hfsplus"`(等等)来选择目标 FS。“原地编辑” 意味着一个已经 flush 过的映像可以被重新打开以进行 `add` / `rm` / `open_file_rw` — 对于带有日志的文件系统,该路径会通过 真实的事务提交,因此写入中途崩溃也会留下一个可被 宿主机 `fsck` 重放的映像。 `qcow2` 和 `dmg` **未**包含在上表中:它们不是 文件系统,而是*磁盘映像容器*。它们位于下一层,作为 `BlockDevice` 后端(参见架构图和“分区、块设备、qcow2”),呈现为一个扁平的可按字节寻址的设备, 随后上述任何文件系统都可以被布局到其*内部* — fstool 会透明地通过它们进行读写。qcow2 支持读/写(v2 + v3, allocate-on-write),包括**压缩的** cluster — 透明读取 zlib 和 zstd (写入压缩 cluster 时会将其拷贝出并转换为普通 cluster); dmg 是只读的(UDIF v4 mish 块:zero / raw / zlib / ADC / bzip2 / LZFSE / LZMA,以及加密的 v2 `encrcdsa`)。 每个 FS 的读取器都是流式的:无论文件多大,其内容都绝不会完全驻留在 内存中。写入器也是如此,采用两遍处理:扫描以测量 几何结构,然后将字节从每个源文件流式传输到映像中。 没有 POSIX 类比的 NTFS 元数据(DOS 属性、ADS、安全 描述符、NT-FILETIME 时间戳、短名称、重解析数据)通过 `user.ntfs.*` 和 `system.ntfs_security` 下的 xattrs 进行往返转换。 ## CLI 命令 | 命令 | 功能 | |---------------|-------------------------------------------------------------------------| | `create` | 从宿主机目录树构建任意受支持的 FS(`-t ext4` / `fat32` / `xfs` / `hfs+` / `fs` / `f2fs` / `squashfs` / `iso` / `apfs` / `exfat` / `grf` / `zip` / `cpio` / `ar`)的裸映像。特定于 FS 的调节参数通过 `-O key=val,key=val` 传递。 | | `build` | 从 TOML 规范构建 — 裸 FS 或分区磁盘映像。 | | `info` | 打印分区表(整盘)或 FS 摘要 + 根目录列表。 | | `ls` | 列出映像内的目录;`-R` 递归遍历子目录。 | | `cat` | 将映像中文件的字节流式输出到标准输出。`--rsrc` 流式传输资源分叉 (HFS / HFS+)。 | | `resources` | 清点 HFS / HFS+ 文件的资源分叉(ResEdit 风格:`vers`/`ICN#`/`DITL`/… 及解码摘要);`--extract TYPE:ID` 导出单个资源。 | | `add` | 将宿主机文件 / 目录树复制到现有映像中(任何可变的 FS)。 | | `rm` | 取消链接文件、符号链接、设备或空目录。 | | `shell` | SFTP 风格的 REPL — `ls cd pwd cat put get rm mkdir info`(`get` 将文件/目录从映像复制到宿主机 — 与 `put` 相反),外加 `find`(name/type/mtime 过滤器,`-sort`/`-limit` 用于例如查找最新的 N 个文件)和 `grep`(`-i`/`-n`/`-r`/`-v`/`-l`/`-c`;二进制匹配打印为 `hexdump -C`)。Ctrl-C 可取消正在运行的 `find`/`grep` 而不会退出 shell。`--with-cache` 将所有 inode 预加载到 RAM 中,使 `find`/`ls` 瞬间完成;`--ro` 以只读方式浏览(包括 tar/ISO/SquashFS)。在 TTY 上具有行编辑 + ↑/↓ 命令历史功能。 | | `convert` | 字节级别的 raw ↔ qcow2 转换,可选扩容。 | | `repack` | 遍历一个或多个源 FS,使用 whiteouts 自底向上合并,并重建为全新的映像。 | | `dd` | 弹性原始块拷贝(文件/设备 → 文件/设备),`ddrescue` 风格:以 1 MiB 块为单位读取,出错时将块大小减半直至源扇区大小,并跳过无法读取的点。带有多线程 读/写器流水线及实时进度条(%,ETA,独立的读/写速度,缓冲区占用率,当前块,跳过的字节数)。Ctrl-C 可干净地取消。 | 所有命令均接受支持分区的 `disk.img:N` 目标(从 1 开始索引) — 参见 下文的“分区、块设备、qcow2”。 所有检查/修改命令均接受 `disk.img:N`(从 1 开始索引) 目标,以遍历进入 GPT、MBR 或 Apple Partition Map 磁盘映像的分区 。不带后缀的 `fstool info disk.img` 会打印分区表 本身。 ### 路径风格 (`--path-style`) 经典 Mac 文件系统使用 `:` 分隔路径组件,因此 `/` 是一个合法的 *文件名* 字符(真实的目录可以命名为 `A/ROSE Includes`)。全局 `--path-style` 标志用于选择路径的拼写方式: - **`unix`**(默认) — 到处使用 `/` 分隔;HFS/HFS+ 名称中 的字面量 `/` 显示为 `:`(macOS 自身使用的约定)。因此 `fstool ls disk.toast:2 …` 列出 `A:ROSE Includes`,并且**重新打包到 tar/zip 时也会以相同方式呈现名称**(`A:ROSE Includes`) — 如果不希望字面量 `/` 被读取为目录分隔符,它就不能 放入归档成员名中。 - **`native`** — 文件系统自身的分隔符(HFS/HFS+ 使用 `:`,FAT/exFAT/NTFS 使用 `\`,其他地方使用 `/`);保留真实文件名。使用 原生分隔符导航,例如 `fstool ls --path-style native disk.toast:2 ':Apple Software Library:…:A/ROSE Includes'`。 `native` 仅更改 CLI 和 shell *显示和接受* 路径的方式;磁盘上的 格式(以及 `repack`/`add` 使用的规范名称)不受影响。 ### 特定于 FS 的选项 (`-O`) 大多数文件系统通过通用的 `-O key=value,key=value` 标志公开可调参数(块大小、标签、压缩编解码器、 卷名、日志开关等),该标志可重复使用,模仿自 `mke2fs -O`: ``` # 4 KiB blocks + ext4 上的自定义标签 fstool create -t ext4 ./rootfs -o out.img -O block_size=4096,volume_label=ROOT # 选择一个 SquashFS codec 并收紧 block size fstool create -t squashfs ./rootfs -o out.sqsh \ -O compression=zstd,block_size=128KiB # 强制使用 deflate level 9 的 v0x103 GRF fstool create -t grf ./rootfs -o out.grf -O version=0x103,compression_level=9 ``` 每个后端的 `apply_options` 都会验证键;未知的键会被拒绝, 并返回一条引用该 FS 类型的明确错误。相同的选项可通过 TOML 规范使用 — 参见下文的“[filesystem.options]”。 ## 分区、块设备、qcow2 - **分区表** — MBR(4 个主分区)和 GPT(128 条目,在 表头 + 条目数组上进行 CRC32 校验,主 + 备,保护性 MBR)。已与 `sgdisk -v` 和 `fdisk -l` 交叉验证。**Apple Partition Map**(经典 Mac / `.toast` 方案)是只读的:`info` 列出 `Apple_HFS` / `Apple_Free` / `Apple_partition_map` 条目,`disk.toast:N` 可对其进行切片。 - **块设备** — 在 Unix 上,fstool 可以格式化和修改真实的块 设备(`/dev/sdX`, `/dev/nvme0n1`, loop 设备)。容量通过 内核 ioctl 查询(Linux 上的 `BLKGETSIZE64`,macOS 上的 `DKIOCGETBLOCK*`), 并且 open 使用 `O_EXCL`,因此如果有任何分区被挂载,内核将拒绝操作。 当输出是块设备时,Build 命令需要 `--force`。 - **qcow2** — `Qcow2Backend` 读取 QEMU v2 / v3 映像并写入全新的 v3 映像(支持 allocate-on-write)。**压缩的 cluster** 被透明读取 (zlib/deflate 和 zstd,使用 4 KiB 窗口解码以匹配 qemu 并限制 RAM);对压缩 cluster 的写入会将其拷贝出并转换为普通 cluster。要 *生成*压缩映像,请向 `create` / `build` / `repack` / `convert` 传递 `--compress`(例如 `--compress`、`--compress=9`、`--compress=zstd`、 `--compress=zstd:9`);结果可通过 `qemu-img check` 检查。基于路径的 工厂(`block::open_image`, `block::create_image`)通过 qcow2 魔数或文件扩展名自动分发,因此 `fstool create -t ext4 src -o out.qcow2` 即可正常工作。 ## TOML 规范 声明式映像描述 — 要么是裸文件系统(`[filesystem]`) 要么是分区磁盘(`[image]` + `[[partitions]]`): ``` [image] size = "64MiB" partition_table = "gpt" [[partitions]] name = "EFI" type = "esp" size = "16MiB" [[partitions]] name = "root" type = "linux" size = "remaining" [partitions.filesystem] type = "ext4" source = "./rootfs" ``` ``` fstool build disk.toml -o disk.img sgdisk -v disk.img # "No problems found." ``` ### `source` — 用于填充 FS 的内容 `source` 接受三种形式,根据字符串指向的内容自动检测: ``` [partitions.filesystem] type = "ext4" source = "./rootfs" # a host directory — walk it recursively ``` ``` [partitions.filesystem] type = "ext4" source = "./rootfs.tar.gz" # a tar archive — repack entries into the FS ``` ``` [partitions.filesystem] type = "ext4" source = "./old-disk.img:2" # an existing image, optional :N partition # — walks the source FS, copies every # entry into the new partition ``` 识别的 tar 扩展名:`.tar`, `.tar.gz`, `.tgz`, `.tar.xz`, `.txz`, `.tar.zst`, `.tar.lz4`, `.tar.lzma`, `.tar.lzo`(编解码器受限于 匹配的 Cargo 特性)。对于映像,`:N` 后缀用于选择分区 *N*(从 1 开始索引);不带此后缀,则将源作为裸文件系统打开。 源 FS 可以是任何可读类型 — `ext{2,3,4}`, FAT32, exFAT, XFS, HFS+, APFS, NTFS, F2FS, SquashFS, ISO 9660, tar, 或 GRF — 并且 除非显式设置了 `size`,否则目标大小会自动调整以适应 内容。 ### `[filesystem.options]` — 特定于 FS 的可调参数 CLI 公开的相同 `-O key=val` 旋钮可通过 自由格式的 `[filesystem.options]` 表在 TOML 中使用: ``` [filesystem] type = "squashfs" source = "./rootfs" [filesystem.options] compression = "zstd" block_size = 131072 [partitions.filesystem] type = "ext4" source = "./rootfs" [partitions.filesystem.options] block_size = 4096 volume_label = "ROOT" ``` 识别的键记录在每个后端的 `FormatOpts::apply_options` 旁边;未知的键会在规范解析 阶段被拒绝,并返回一条引用该 FS 类型的明确错误。现有的扁平字段 (`block_size`, `volume_label`, `mtime`, …)继续工作以保持向后 兼容。 ## 架构 ``` ┌────────────────────────────────────────────┐ │ CLI (clap) — bin/fstool │ └────────────────────────────────────────────┘ │ ┌────────────────────────────────────────────┐ │ Spec layer (TOML → ImageSpec / FsSpec) │ └────────────────────────────────────────────┘ │ ┌────────────────────────────────────────────┐ │ Filesystem trait → ext, fat, xfs, ntfs, … │ └────────────────────────────────────────────┘ │ ┌────────────────────────────────────────────┐ │ PartitionTable trait → Mbr, Gpt │ └────────────────────────────────────────────┘ │ ┌────────────────────────────────────────────┐ │ BlockDevice trait → File, Mem, Sliced, │ │ Qcow2, Dmg │ └────────────────────────────────────────────┘ ``` 每一层都是可替换的。文件系统实现只与 `BlockDevice` 通信;它不知道也不关心设备是真实的文件、 测试中的内存缓冲区、由分区表切割出的更大磁盘的切片, 还是 qcow2 支持的稀疏容器。DMG(`.dmg`)也以 相同方式处理:打开映像,遍历 mish 表以获取 块布局,然后堆栈的其余部分将其作为扁平的 块设备读取 — 包括加密(`encrcdsa` v2)变体 (前提是提供了解锁密码)。 ## ext 特定的细节 - `BuildPlan` 自动将文件系统调整为恰好适合源目录树的大小 (genext2fs 风格的“自适应大小”)。 - `Ext::populate_rootdevs` 放入一个 `Minimal` 或 `Standard` 的 `/dev/*` 目录树 (console, null, zero, ptmx, tty, fuse, random, urandom — 外加 tty0..15, ttyS0..3, kmsg, mem, port, hda..hdd, sda..sdd 及适用于 `Standard` 的分区),因此非 root 用户构建 Linux 根 FS 时不需要 `CAP_MKNOD`。 - xattrs 通过重新打包进行往返转换:inline(扩展 inode body)和 外部 `file_acl` 块源均会被读取;当开启 `metadata_csum` 时,目的地会 写入带有正确计算的 CRC32C 的外部块。 `debugfs ea_get` 确认重新打包后的值完全相同。 ## 跨 FS 重新打包 `fstool repack` 遍历源文件系统并将目录树重建为 全新的映像。使用 `--fs-type` 可动态更改文件系统;`--shrink` 会将输出自动调整为恰好容纳内容的最小尺寸。 该流水线是**一个通用的遍历器馈送到两个 sink 之一** — 一个 流式 tar sink(tar / `.tar.`)或一个块设备 `Filesystem` sink — 没有 针对每个 `(source,dest)` 类型的特殊情况。因此 **任何可读的源均可通过单一路径重新打包为任何可写的目的地** (`fstool repack app.zip out.tar`、`fstool repack disk.xfs out.iso` 等)。 遍历器通过源的 trait `getattr` / `list_xattrs` / `read_symlink` 读取每个条目的元数据,因此 mode、uid/gid、mtime、 符号链接、设备号、xattrs 和硬链接在两端都能表示的情况下均可实现往返转换。文件主体直接从源流式传输到 目的地(`create_file_streaming`,无每个文件的临时文件)。硬链接 在目的地支持时进行去重,在其他情况下 则表现为拷贝;不支持符号链接/设备/xattr 的目的地会丢弃它们并发出警告。 每个读取器都会呈现其格式实际存储的元数据: ext、tar、归档格式、F2FS、XFS、SquashFS、APFS 和 HFS+ 带有 完整的 POSIX mode/uid/gid + 时间戳(HFS+ 转换其 1904 纪元); 当存在 Rock Ridge 时,ISO 9660 也会如此(纯/Joliet 没有); NTFS — 没有 POSIX 所有权 — 呈现真实的时间戳 + 从其 DOS 属性合成的 mode,并在重新打包时携带其原生元数据 (DOS 属性、ADS、安全描述符、重解析数据等),作为 `user.ntfs.*` / `system.ntfs_security` xattrs。 `fstool repack` 会写入任何实现了 `Filesystem` trait 的目的地 — `ext2/3/4`、FAT32、exFAT、tar、XFS、HFS+、FS、NTFS、F2FS、 SquashFS、ISO 9660、GRF。`add` / `rm` 通过相同的 trait, 这意味着它们适用于任何其写入器能重新打开现有 映像的 FS;目前这包括所有可变的后端 — ext、FAT32、exFAT、 F2FS、XFS、HFS+、NTFS、APFS 和 GRF。SquashFS、ISO 9660 和 tar 仅限重新打包(它们的 `MutationCapability` 为 `Immutable` 或 `Streaming`,因此 `add` / `rm` 会快速失败并返回可操作的错误, 引导用户使用 `repack`)。 ## 带 whiteouts 的分层合并 `repack` 接受一个或多个源位置参数,后面跟着 目的地。如果是单个源,则行为与之前相同;如果有两个或更多 源,它会在写入前自底向上合并这些源 — 后面的层 会覆盖相同路径的文件,上层中的 tombstones 会移除下层的 路径。有两种 tombstone 约定会被自动检测: | 约定 | 标记 | 效果 | |------------|--------|--------| | tar-OCI | 目录 D 中的 `.wh.` | 删除 `D/` | | tar-OCI | 目录 D 中的 `.wh..wh..opq` | 在当前层自身内容落下之前,丢弃 D 的所有下层子项 | | OverlayFS | major=0, minor=0 的字符设备 | 删除此路径 | | OverlayFS | 目录上的 xattr `trusted.overlay.opaque = "y"` | 该目录的 opaque-dir 语义 | Tombstones 本身永远不会出现在输出中。源可以是 宿主机目录、tar 归档(压缩或普通)或文件系统 映像 — 任何混合均可使用。 ``` # OCI 风格:将一堆 layers 重建为一个扁平的 tar fstool repack base.tar layer1.tar layer2.tar flat.tar # 使用包含替换文件的 tar 来 patch 一个 ISO fstool repack disc.iso patch.tar updated.iso --fs-type iso # Shell globs 有效 — 最后一个位置参数是目标 fstool repack layer*.tar merged.tar ``` 在内部,合并会将所有层折叠到保存在临时文件中的单个未压缩 tar 中, 然后驱动现有的单源重新打包 流水线;目标 FS 并不知道它来自多个 源。 ## ISO 9660 ISO 9660 读取涵盖裸 ECMA-119 布局以及四种 常见扩展中的三种: - **Joliet** (Microsoft) — 通过补充卷描述符使用 UCS-2 BE 长名称。 - **Rock Ridge** (IEEE P1282) — 通过 `PX` 实现 POSIX mode + uid + gid,通过 `NM` 实现长名称,通过 `SL` 实现符号链接,通过 `TF` 实现 时间戳。跨扇区边界跟随继续区域(`CE`)。 - **El Torito** — 启动目录:验证条目、默认条目和 段头(`0x90` / `0x91`);解析后的目录会呈现在 `fstool info` 中。 写入器仅限重新打包 — ISO 是顺序格式的,单个 `flush()` 即可写入整个映像。它会生成一个 PVD 以及可选的 Joliet SVD, 同时生成 L 型和 M 型路径表、双重目录记录树(一个 用于 PVD,一个用于 Joliet),以及附加在 PVD 记录上的 Rock Ridge System Use Areas(`NM` / `PX` / `SL`)。输出可以通过 `isoinfo -lR` 进行往返转换 并重新通过 fstool 自身的读取器读回。 ``` # 从 host 目录构建一个 ISO fstool repack ./rootfs disc.iso --fs-type iso # 遍历现有的 ISO fstool ls disc.iso / fstool cat disc.iso /README.TXT # ISO → tar → ISO 往返转换 fstool repack disc.iso plain.tar fstool repack plain.tar disc2.iso --fs-type iso ``` ## 归档格式 归档通过与 tar 和 GRF 相同的 `Filesystem` trait 被视为文件系统,因此 `info` / `ls` / `cat` / `repack` 可以在它们上面统一运作。它们 共享一个公共核心(`src/fs/archive/`):每种格式提供一个*扫描器*,它 将归档索引到内存中的树状结构,并且 — 如果可写 — 提供一个*构建器*; 该核心提供通用的读取路径,并通过现有的压缩编解码器解码每个条目的字节范围。 ``` fstool create -t zip ./rootfs -o out.zip # build a zip from a dir fstool create -t zip ./rootfs -o out.zip -O compression=stored fstool ls app.zip / # walk any zip/cpio/ar fstool cat app.zip /etc/config fstool repack app.zip out.cpio --fs-type cpio # convert between archives ``` | 格式 | 读取 | 写入 | 备注 | |--------|------|-------|-------| | zip | ✅ | ✅ | ZIP64, Stored + Deflate, Unix mode + symlinks;读取任何工具生成的归档;文件名解码为 UTF-8(有标记),否则自动检测。写入时,UTF-8 标记仅对非 ASCII 名称设置。 | | cpio | ✅ | ✅ | 读取 newc / newc-crc / odc;写入 newc。 | | ar | ✅ | ✅ | 读取 GNU + BSD 长文件名,写入 GNU。扁平结构 — 嵌套的源目录树会被拒绝,并提示使用 tar/zip/cpio。 | 写入器仅限重新打包(`MutationCapability::Streaming`,类似于 tar):现有归档无法进行原地编辑 — `add` / `rm` 会引导您使用 `repack`,它会进行重建。`cab`(Store/MSZIP/LZX/Quantum)、`lzx`(Amiga LZX)和 `rar`(RAR5 Store/压缩,包含 **solid** 组)是位于 `cab` / `amiga-lzx` / `rar` 特性之后的通过 `compcol` 实现的只读 读取器。solid RAR 组被解码为一个连续的流;像 `repack` 这样的顺序遍历 恰好只解压一次(对早期成员的后向/随机读取会从组开头重新解码,有内存限制)。`lha` (LHA/LZH,在 `lha` 特性之后)遍历 level-0/1/2 表头并读取 `-lh0-` store 成员;其 LZSS+Huffman 方法已列出,但在 `compcol` 中等待 `lha` 编解码器支持,读取时返回 `Unsupported`。`arc`(SEA ARC,在 `arc` 特性之后)遍历扁平头链并读取 store 成员;其 压缩方法已列出,但在 `compcol` 中等待 ARC 编解码器支持,读取时返回 `Unsupported`。`sit`(StuffIt,在 `sit` 特性之后)解析经典 `SIT!` 容器并读取 store 成员;其压缩方法和整个 StuffIt 5 格式可列出/检测,但在 `compcol` 中等待 StuffIt 编解码器支持,读取时返回 `Unsupported`。`7z`(在 `sevenz` 特性之后)解析整个 容器(LZMA 打包头,按子流切分的 solid 文件夹)并读取 单编码器 Copy / LZMA / BZip2 / Deflate 文件夹;LZMA2(默认)、BCJ 过滤器、PPMd、加密和多编码器流水线已列出,但在 `compcol` 中等待原生 LZMA2 + 分支过滤器编解码器支持,读取时返回 `Unsupported`。每种 归档格式现在都有读取器 — 不再有仅用于检测的脚手架代码。 (`rar` 和 `sit` 充其量是只读的 — 它们的创建是专有的;RAR4、 加密、stored-in-solid 以及已过滤但不受支持的 RAR5 流仍保持 `Unsupported`。) zip 的 Deflate 支持依托于现有的 `gzip` Cargo 特性(通过 `compcol` 进行原生 DEFLATE);不带该特性的构建会回退到 Stored。`cpio` 和 `ar` 不需要 编解码器。归档到 `ext`/`fat`/`tar` 的重新打包使用专用的 FS 到 FS 拷贝器,目前尚未连接(与 XFS/HFS+ 源的限制相同) — 请通过通用 trait 路径在归档之间转换,或转换为 `iso`/`grf`。 ## 压缩 `fstool` 默认启用了六种压缩编解码器。每种都有 自己的 Cargo 特性标志,因此您可以精简二进制文件: | 编解码器 | 特性 | 用于 | |-------|---------|----------| | gzip | `gzip` | SquashFS, `.tar.gz` / `.tgz` | | xz | `xz` | SquashFS, `.tar.xz` / `.txz` | | lzma | `lzma` | SquashFS, `.tar.lzma` | | lz4 | `lz4` | SquashFS, `.tar.lz4` | | zstd | `zstd` | SquashFS, `.tar.zst` | | lzo | `lzo` | SquashFS, `.tar.lzo` | 压缩的 tar 输入 / 输出通过文件扩展名检测(如果输入没有可识别的扩展名,则通过魔数检测):`fstool ls disk.tar.zst /` 和 `fstool repack ext.img out.tar.gz` 即可正常工作。 在内部,编解码器通过临时文件流式传输,因此整个 归档永远不会驻留在 RAM 中。 要在构建时禁用编解码器,例如为了避免在受限系统上捆绑 C 语言编写的 `zstd` 构建版本: ``` cargo install fstool --no-default-features --features gzip,lz4,xz,lzma ``` ## 限制 目前明确不在范围内的内容,按可能发生更改的概率粗略排序: - **ext4 写入路径**:写入路径上的 `flex_bg`(读取器正常)。 - **APFS 原地编辑**:`open_file_rw` 会在整个文件内容之上重建一个新的 COW 检查点,因此它是整文件 粒度的 — 部分 extent COW 尚未实现, 并且 rw 路径上的 `create_file` / `remove` 搭载在同一个 检查点上。多次连续提交受限于 `xp_desc` 环(读取器尚未对其进行轮转)。 - **APFS 读取器**:快照是只读的,且仅支持单叶 snap-meta (多级 snap 树返回 `Unsupported`)。`UF_COMPRESSED`/decmpfs 文件 内容读取为空(数据尚未解码,尽管可以重用 HFS+ decmpfs 解码器)。加密、sealed-volume 完整性(hash/integrity 树)、Fusion 分层以及基于 dstream 的(`XATTR_DATA_STREAM`)xattrs 不在 范围内。 - **APFS / NTFS 严格检查器测试**:spaceman + `$Secure` / `$LogFile` 结构现已填充数据,但 `fsck_apfs` 和 `ntfs-3g` 挂载仍然可能因为一些细节问题而标记映像 (空闲队列 B-trees、日志元数据布局)。读取 + 写入可端到端 工作;宿主机工具门槛是剩下的完善工作。 - **NTFS 读取器**:压缩和加密的 `$DATA`,`$ATTRIBUTE_LIST` 溢出,以及通过 `$Secure` 进行的超出驻留路径处理范围的 安全描述符间接寻址均返回 `Unsupported`。 - **XFS 读取器**:深于叶节点上一级的 B-tree 格式(`di_format=BTREE`)目录 返回 `Error::Unsupported` (shortform / block / leaf / node 和单级 B-tree 目录受 支持);写入器假定是 shortform / extent 目录。Node 格式 (多叶 dabtree)的 xattrs 是只读的。 - **HFS+ decmpfs**:支持 type 3(zlib inline)+ type 4(zlib resource fork)。LZVN(类型 7/8)和 LZFSE(类型 11/12)返回 `Unsupported`。 - **DMG**:只读 — 没有 DMG 写入器 / `convert` 路径。加密的 v1 (`cdsaencr` 遗留 3DES)块返回 `Unsupported`;v2 受 支持。 - **Trait 接口上的部分文件重写** — `open_file_rw` 在所有安全的地方都存在,但在句柄上的 `Read + Write + Seek` 之外,尚未公开带有类型的“在已知大文件上修补此字节范围 ”的 API。 ## 试一试 ``` cargo install fstool # or: cargo install --path . mkdir -p /tmp/src/etc && echo hi > /tmp/src/greeting.txt fstool create -t ext4 /tmp/src -o /tmp/out.img fstool info /tmp/out.img fstool ls /tmp/out.img / fstool cat /tmp/out.img /greeting.txt e2fsck -fn /tmp/out.img # must report clean ``` 运行测试套件: ``` cargo test # unit tests + external cross-checks if tools present ``` CI 会在 Linux 上运行完整的套件(安装了 `apt` 包管理器提供的 `e2fsprogs`、 `dosfstools`、`mtools`、`gdisk`、`qemu-utils` 用于交叉验证),外加在 macOS(Homebrew `qemu`)和 Windows 上的构建 + 测试通过。 ## 许可证 MIT。版权所有 © 2026 Karpelès Lab Inc. 参见 [LICENSE](LICENSE)。
标签:Python安全, Rust, 可视化界面, 文件系统, 文档结构分析, 格式转换, 磁盘镜像, 网络流量审计, 通知系统