1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
[]
= "fstool"
= "0.4.30"
= "2024"
# Floor is the edition-2024 minimum (1.85) bumped to 1.88 by the
# `purecrypto` dependency (encrypted-DMG crypto), whose MSRV is 1.88 as of
# 0.6.14.
= "1.88"
= "Build disk images and filesystems (ext2/3/4, MBR, GPT) from a directory tree and TOML spec, in the spirit of genext2fs."
= "MIT"
= "https://github.com/KarpelesLab/fstool"
= "README.md"
= ["filesystem", "ext4", "ext2", "gpt", "image"]
= ["filesystem", "command-line-utilities"]
# Keep the published crate to the library + CLI source. The browser UI and CI
# config are repo-only and would just bloat the crates.io tarball, and the
# bare-metal example is its own crate. (The wasm bindings live in-crate at
# src/wasm.rs behind the `wasm` feature.)
= ["/web", "/.github", "/examples/embedded-cortex-m"]
[]
= "fstool"
# `rlib` only. wasm-bindgen needs a `cdylib`, but declaring one here makes
# *every* build produce one, and a `cdylib` is a linked artifact: in a
# `no_std` configuration it demands a `#[global_allocator]` and a
# `#[panic_handler]` the library has no business providing, so
# `cargo build --no-default-features --features fat` would fail for a
# reason that has nothing to do with the code. (On a firmware target cargo
# drops the crate type with a warning instead, which is no better — a
# warning on every build.) The browser bundle asks for the cdylib on the
# command line instead; see the `wasm` job in .github/workflows/pages.yml:
#
# cargo rustc --release --lib --crate-type cdylib \
# --target wasm32-unknown-unknown --features wasm,…
= ["rlib"]
= "src/lib.rs"
[[]]
= "fstool"
= "src/bin/fstool/main.rs"
# Gate the binary on the feature carrying its dependencies, so a library
# consumer that turns `cli` off doesn't build (or resolve) any of them.
= ["cli"]
[[]]
= "format_empty_ext2"
= ["ext"]
[[]]
= "inspect_gpt"
= ["std"]
[[]]
= "memconv_smoke"
= ["std"]
[]
# Compression codecs for SquashFS reads and `.tar.<algo>` streaming I/O.
# Enabled by default; disable with `default-features = false` and pick
# a subset if you want to slim the binary or avoid a C-bundled build.
#
# Archive backends (zip / cpio / ar) are always compiled — they add no new
# dependencies (zip's DEFLATE rides the `gzip` feature; cpio/ar need no
# codec). Every other archive format has a read-only reader behind its own
# per-format feature (`cab`, `amiga-lzx`, `lha`, `arc`, `sit`, `sevenz`,
# `rar`); each enables only the compcol codecs it can use today and returns a
# clean `Unsupported` for methods whose codec hasn't landed in compcol yet.
= [
"std",
# Implied by `std`, but named here because it is a deliberate part of
# the default rather than an accident of it: the floor below is a
# build with `alloc` off (see the feature's own note).
"alloc",
"filesystems",
"containers",
"codecs",
"spec",
"json",
"log",
"unix-host",
"cli",
"readline",
]
# The Rust standard library. Off, the crate is `#![no_std]` (with `alloc`)
# and what remains is the embedded core: the `BlockDevice` trait with its
# in-memory and sliced backends, MBR / GPT / APM partition tables, the
# `Filesystem` trait, and the filesystems that carry no `std` requirement
# of their own (`fat`, `exfat`, `littlefs`). Everything that talks to a
# host — files, the image containers, the TOML spec, `inspect` / `repack`
# / the CLI — needs `std`, and the features below that cover such code
# turn it on for you. An embedded build is therefore spelled
#
# fstool = { version = "0.4", default-features = false,
# features = ["fat"] }
#
# and reads an SD card through the `BlockDevice` you implement over your
# driver. `uuid` is the one dependency left; its random-GUID generator
# (GPT formatting) is `std`-only, and `Gpt::build_with_guids` takes the
# GUIDs from you instead.
= ["alloc", "uuid/std", "uuid/v4"]
# The `alloc` crate — a heap. On by default (through `std`), and what
# most backends are built on: the hosted API hands back `Vec`s and
# `String`s throughout, and a driver that holds its allocation tables in
# memory needs somewhere to put them.
#
# The floor beneath it is a build with `alloc` off, where nothing that
# can allocate is compiled and the crate links on a target with no
# `#[global_allocator]` at all. `alloc` is additive from there: turning
# it on adds the hosted layers and the backends that require them, and
# never takes anything away. A backend that works either way — today
# `fat` — gives you its allocation-free driver in both configurations,
# so the heapless build of a FAT reader is spelled
#
# fstool = { version = "0.4", default-features = false,
# features = ["fat"] }
#
# and reads and writes an SD card through the `SectorDriver` you
# implement over your card driver. See `fs::fat`.
= []
# Everything the library reads and writes, with no command-line surface.
# This is the set a library consumer wants:
#
# fstool = { version = "0.4", default-features = false,
# features = ["filesystems", "containers", "codecs"] }
#
# which drops `clap` and `rustyline` (and their ~30 transitive crates)
# while keeping every format.
= ["gzip", "xz", "lzma", "lz4", "zstd", "lzo", "cab", "amiga-lzx", "lha", "arc", "sit", "sevenz", "rar", "dmg-bzip2", "dmg-lzfse"]
# ---------------------------------------------------------------------
# Filesystems. One feature per backend, every one on by default through
# `filesystems`. Each pulls exactly what it needs: the three flash / SD
# card formats (`fat`, `exfat`, `littlefs`) are `no_std`-clean and stand
# alone; the rest require `std`, and the two that decode legacy or
# Unicode names take their table crate with them.
# ---------------------------------------------------------------------
= [
"affs",
"apfs",
"archive",
"exfat",
"ext",
"f2fs",
"fat",
"grf",
"hfs",
"hfs-plus",
"iso9660",
"littlefs",
"ntfs",
"ramfs",
"squashfs",
"tar",
"xfs",
]
# FAT12 / FAT16 / FAT32, in both configurations — the one backend that
# needs neither `std` nor `alloc`.
#
# On its own it compiles the allocation-free driver in `fs::fat`: a
# `SectorDriver` you implement, one sector of scratch RAM, and the
# allocation table read from the card rather than held in memory. Reads
# and writes, long names, subdirectories, MBR partitions.
#
# `alloc` (so, any default or `std` build) only adds to that module: the
# hosted `Fat32`, which speaks the crate's `Filesystem` trait and formats
# volumes, and an in-memory allocation table that makes the driver above
# faster through exactly the same API.
= []
# exFAT. Shares the allocation-table code with the hosted `fat` driver,
# so unlike `fat` it needs a heap.
= ["fat", "alloc"]
# littlefs (the embedded-flash filesystem, `lfs2` disk versions 2.0 + 2.1).
= ["alloc"]
# ext2 / ext3 / ext4.
= ["std"]
# XFS.
= ["std"]
# NTFS.
= ["std"]
# F2FS.
= ["std"]
# APFS. `intl` supplies the NFD normalisation + case folding the drec
# hash needs (see the dependency note below).
= ["std", "dep:intl"]
# Classic HFS (Mac OS ≤ 8) — also the resource-fork + MacRoman helpers.
= ["std"]
# HFS+ / HFSX.
= ["std"]
# Amiga OFS / FFS.
= ["std"]
# ISO 9660 (+ Joliet, Rock Ridge, El Torito).
= ["std"]
# SquashFS (reads decode through the codec features).
= ["std"]
# GRF (Gravity Ragnarok Online archive). `charcode` decodes its CP949 names;
# every member is zlib-compressed, so the writer needs the `gzip` codec.
= ["std", "dep:charcode", "gzip"]
# tar — the streaming reader / writer and the tar-as-filesystem view.
= ["std"]
# The archive core: zip / cpio / ar, plus the per-format readers below
# when their feature is on. `charcode` decodes non-UTF-8 zip names.
= ["std", "dep:charcode"]
# In-memory filesystem — the scratch tree `repack` / `merge` and the FUSE
# adapter build on.
= ["std"]
# ---------------------------------------------------------------------
# Disk-image containers — the `BlockDevice` layers between a host file
# and the filesystem inside it. All `std`.
# ---------------------------------------------------------------------
= ["qcow2", "dmg", "diskcopy", "dmg-encrypted", "luks", "qcow2-crypto"]
# qcow2 (with backing files; encryption via `qcow2-crypto`).
= ["std"]
# Apple UDIF `.dmg` (read-only; zero / raw / zlib / ADC always, the other
# chunk codecs behind `dmg-bzip2` / `dmg-lzfse`).
= ["std"]
# DiskCopy 4.2 images (the classic-Mac floppy container).
= ["std"]
# The TOML spec engine: `spec::Spec`, `spec::build`, and
# `OptionMap::merge_toml` — everything that turns a `.toml` file into an
# image. Pulls `toml` (and `serde`, which it is built on). Off, the
# library is driven through its Rust API instead; `spec::parse_size` and
# the other pure helpers stay either way.
= ["std", "dep:tomlproc", "dep:serde"]
# Speaking JSON: `Serialize` on the report types, the `--json` output of
# `analyze`, the wasm bridge's return values — and LUKS2, whose on-disk
# metadata *is* a JSON document, which is why `luks` requires this.
= ["std", "dep:serde", "dep:serde_json"]
# The `log` facade. Four call sites, all of them reporting something
# recovered from rather than failed on. Off, they compile to nothing.
= ["dep:log"]
# Unix host integration: querying a real block device's capacity
# (`BLKGETSIZE64` / `DKIOCGETBLOCK*`), opening one `O_EXCL` so the kernel
# refuses a mounted disk, terminal width for the progress line, and the
# CLI's Ctrl-C handler. Off, these degrade the way they already do on
# non-Unix: a block device reports no size and is refused, the progress
# line assumes 80 columns, and Ctrl-C is the default kill. Image *files*
# are untouched either way.
= ["std", "dep:libc"]
# The `fstool` binary's argument parsing. On by default so `cargo install
# fstool` produces a working command; a library consumer turns it off with
# `default-features = false`. Deliberately does NOT imply `readline`: the
# static libc-free release build wants the CLI without rustyline's
# unix-only terminal layer. Nor does it imply any filesystem — the
# subcommands that need one are compiled in with that filesystem's
# feature, so a slim `fstool` can be built for exactly the formats it
# has to handle.
= ["dep:clap", "std", "spec", "json", "log", "unix-host"]
# Line editing + command history for the interactive `fstool shell` (↑/↓
# history, Ctrl-A/E, etc.) via `rustyline`. Implied by `cli`; drop it with
# `--features cli` and no `readline` to lose the dependency — the shell
# then falls back to a plain line-buffered reader. No effect on
# piped/non-TTY input, and nothing in the library uses it.
= ["dep:rustyline"]
# WebAssembly bindings (src/wasm.rs) for the browser UI. Pulls in
# `wasm-bindgen`; build with `--no-default-features --features
# wasm,<codecs…> --target wasm32-unknown-unknown`, then run `wasm-bindgen`.
# Off by default so native library/CLI builds don't carry the dependency.
= ["dep:wasm-bindgen", "dep:console_error_panic_hook", "json"]
# Every codec is served by `compcol` (one uniform crate): gzip/zlib/deflate/
# xz/lzma/zstd/lz4/lzo, plus the CAB + Amiga-LZX archive codecs and the DMG
# bzip2/lzfse decoders. The `gzip` feature also covers zip DEFLATE, DMG zlib,
# and HFS+ decmpfs; lz4 uses compcol's raw block (SquashFS) + canonical frame
# (tar); lzo uses compcol's raw LZO1X block; lzma is the `.lzma` alone codec.
# The codecs are consumed by `std`-only code (the compression module, the
# archive readers, SquashFS, DMG) and so imply `std`.
= ["std", "dep:compcol", "compcol/gzip", "compcol/zlib", "compcol/deflate"]
= ["std", "dep:compcol", "compcol/xz"]
= ["std", "dep:compcol", "compcol/lzma"]
= ["std", "dep:compcol", "compcol/lz4"]
# Microsoft Cabinet (.cab) reader: Store/MSZIP/LZX/Quantum folders decode
# via compcol. Read-only.
= ["archive", "dep:compcol", "compcol/deflate", "compcol/lzx", "compcol/quantum"]
# Amiga LZX (.lzx) reader: Store + LZX (compcol amiga_lzx) groups. Read-only.
= ["archive", "dep:compcol", "compcol/amiga_lzx"]
# LHA / LZH (.lzh) reader (read-only): walks level-0/1/2 headers. `-lh0-`
# store decodes today; the lh1/4/5/6/7 LZSS+Huffman methods list but read as
# Unsupported pending an `lha` codec in compcol (will add `compcol/lha`).
= ["archive"]
# SEA ARC (.arc) reader (read-only): walks the flat header chain. Stored
# methods 1/2 decode today; the compressed methods (RLE90 / squeeze / crunch /
# squash) list but read as Unsupported pending ARC codecs in compcol.
= ["archive"]
# StuffIt (.sit) reader (read-only): classic `SIT!` container — data-fork
# method 0 (store) decodes; compressed methods + StuffIt 5 list/detect but
# read as Unsupported pending StuffIt codecs in compcol.
= ["archive"]
# 7-Zip (.7z) reader (read-only): parses the container + single-coder folders
# (Copy / LZMA / BZip2 / Deflate via compcol; solid folders sliced per
# substream). LZMA2 / BCJ filters / PPMd / multi-coder / encryption list but
# read as Unsupported pending raw-LZMA2 + branch-filter codecs in compcol.
= ["archive", "dep:compcol", "compcol/lzma", "compcol/bzip2", "compcol/deflate"]
# RAR reader (read-only): RAR5 store + compressed via compcol's rar5 decoder,
# including solid groups (decoded as one continuous stream; a sequential walk
# such as repack decompresses the group once). (RAR4 would add compcol/rar1+
# rar2+rar3 later.)
= ["archive", "dep:compcol", "compcol/rar5"]
= ["std", "dep:compcol", "compcol/zstd"]
= ["std", "dep:compcol", "compcol/lzo"]
# DMG chunk codecs beyond the always-available zero / raw / zlib / ADC set,
# decoded via compcol (bzip2 + lzfse decoders). Gated so a slim build can
# drop them. (`lzfse_rust` is a dev-dependency used only to generate the
# lzfse test vector — compcol's LZFSE is decode-only.)
= ["dmg", "dep:compcol", "compcol/bzip2"]
= ["dmg", "dep:compcol", "compcol/lzfse"]
# Password-protected DMG read support (`encrcdsa` v2). Served by the
# `purecrypto` crate (AES-CBC, 3DES-EDE3-CBC via Cbc64, HMAC-SHA1, SHA-1,
# PBKDF2) — pure-Rust, no foreign code. Gated so a slim build can drop it.
= ["dmg", "dep:purecrypto"]
# LUKS1 / LUKS2 containers: unlock an existing volume with a passphrase,
# read and write it in place, and format a fresh one. Served by
# `purecrypto` (AES/Camellia/ARIA/SM4 in XTS/CBC/CTR/ECB, PBKDF2,
# Argon2i/id, SHA-1/2, RIPEMD-160, Whirlpool) — pure-Rust, no foreign
# code. Gated so a slim build can drop it.
= ["std", "dep:purecrypto", "json"]
# qcow2 encryption — both `crypt_method` values: 1 (the legacy AES-CBC
# scheme qemu now reads but no longer creates) and 2 (LUKS, whose header
# is embedded in the image via the crypto-header extension). Builds on
# the `luks` engine for the sector cipher and keyslot unwrap.
= ["qcow2", "luks"]
# FUSE adapter: enables the `fstool mount` subcommand which exposes an
# ext{2,3,4} image as a userspace filesystem via libfuse (Linux) or
# macFUSE (macOS). Off by default so the core build doesn't need a C
# FUSE library on the host; opt in with `--features fuse`.
= ["dep:fuser", "unix-host"]
[]
= { = "0.4", = true }
= { = "1", = false }
# Argument parsing for the `fstool` binary — see the `cli` feature. The
# library itself needs clap only to derive `ValueEnum` on `PathStyle`,
# which is `cfg_attr`-gated on the same feature.
= { = "4", = ["derive"], = true }
# Serialization, behind the `json` and `spec` features.
= { = "1.0.228", = ["derive"], = true }
= { = "1", = true }
# TOML for the spec engine, via `tomlproc` (KarpelesLab) — a
# self-contained TOML 1.0.0 parser whose only dependency is the `serde`
# we already take. The `toml` crate it replaces brought five more
# (toml_datetime, toml_parser, toml_writer, serde_spanned, winnow).
= { = "0.1.1", = true, = false, = ["serde"] }
# Unicode NFD + full case folding for the APFS drec hash function.
# Volumes formatted with APFS_INCOMPAT_NORMALIZATION_INSENSITIVE (the
# macOS default for user data volumes) store drec keys with a 22-bit
# CRC32C of the NFD-normalised (optionally case-folded) name; our writer
# computes it via `intl`.
#
# `intl` (KarpelesLab) is a pure-Rust, no_std ICU analog with zero
# mandatory dependencies. We take the two modules that hash needs —
# UAX #15 normalization and UTS-conformant case folding — over the whole
# codepoint range (`full`), and none of its CLDR / collation / datetime /
# timezone surface.
#
# Folding is not `str::to_lowercase`: full folding maps ß to "ss" and fi
# to "fi" where lowercasing leaves them alone, and the macOS kernel
# folds, so getting it wrong files a drec in a bucket the kernel never
# looks up (see the `apfs_drec_hash_known_vectors` test, which pins the
# exact hashes).
= { = "0.6.1", = true, = false, = ["case", "full"] }
# Legacy character encodings, via `charcode` (KarpelesLab) — the WHATWG
# Encoding Standard with no dependencies of its own. Four of its tables
# are enough for us: `euc-kr` (which the standard defines as CP949, the
# Microsoft superset, and which GRF filenames use), `shift-jis` and
# `euc-jp` (the Japanese legacy encodings a ZIP filename may be in when
# it carries no UTF-8 flag), and `single-byte` for the ISO-8859-15
# fallback that maps every byte.
= { = "0.1.3", = true, = false, = ["std", "euc-kr", "shift-jis", "euc-jp", "single-byte"] }
# Compression codecs — all optional and feature-gated. Pure-rust where
# possible (flate2 via miniz_oxide, lz4_flex, lzma-rs); zstd and lzo
# pull in bundled C source.
# compcol: uniform no_std codec collection serving gzip/zlib/deflate/xz/zstd
# /lz4/lzo plus the CAB codecs. `std` is always on (for the `io` Read/Write
# adapters); per-algorithm features are added by fstool's feature flags.
= { = "0.6.11", = true, = false, = ["std", "checksum"] }
# Line editing + persistent history for the interactive shell. Optional, behind
# the `readline` feature; the bin enables it by default. Pure-Rust line editor.
= { = "15", = true }
# Crypto for encrypted DMG (encrcdsa v2), LUKS1/LUKS2 and qcow2
# encryption — `purecrypto`, a pure-Rust no_std toolkit (KarpelesLab,
# MIT). Opt-in via the `dmg-encrypted` / `luks` / `qcow2-crypto`
# features; we pull only the symmetric/hash/kdf/rng modules (`kdf`
# re-enables `hash` + `cipher`), not its TLS/PQC/RSA surface.
# LUKS formatting draws its salts and master key from `rng` (OsRng).
= { = "0.6.29", = true, = false, = ["std", "cipher", "hash", "kdf", "rng"] }
# FUSE adapter — Linux uses libfuse, macOS uses macFUSE. Off by default
# (see the `fuse` feature). `fuser` is the maintained successor to
# `fuse-rs` and tracks the libfuse 3.x ABI. We enable the default
# `libfuse` feature so the build links against the system FUSE
# library; running `cargo build --features fuse` therefore requires
# libfuse-dev (Linux) or macFUSE (macOS) installed on the host.
= { = "0.16", = true, = ["libfuse"] }
# Needed on Unix for the BLKGETSIZE64 / DKIOCGETBLOCKCOUNT ioctls used to
# query the size of a block device, plus the O_EXCL open flag that refuses
# devices with a mounted partition.
[]
= { = "0.2", = true }
[]
= "3"
= "0.11"
# Test-only: an independent LZFSE *encoder* to generate fixtures that the
# production `compcol` LZFSE decoder must round-trip (compcol's LZFSE is
# decode-only). Not linked into the shipped binary.
= "0.2"
# wasm32 needs a browser randomness source for uuid v4 generation, plus the
# wasm-bindgen glue when the `wasm` feature is on.
[]
= { = "1", = ["v4", "js"] }
# Pinned to the wasm-bindgen CLI version CI installs; the crate and the CLI
# that post-processes the .wasm must match exactly.
= { = "=0.2.121", = true }
= { = "0.1", = true }