GitHub - antimatter-studios/ext4-win-driver: Browse and mount ext4 filesystems on Windows via WinFsp · GitHub
Skip to content

About

Browse and mount ext4 filesystems on Windows via WinFsp

Topics

Resources

Stars

23 stars

Watchers

1 watching

Forks

Repository files navigation

ext4-win-driver

Overview

This project provides Windows-first userspace tooling for ext4 volumes, built on the fs-ext4 Rust library.

Raspberry Pi SD card auto-mounted as drive F: on Windows 11 ARM64

A Raspberry Pi root-filesystem SD card plugged into a Windows 11 ARM64 host, auto-mounted on F: by the ExtFsWatcher service and browsable in Explorer the moment it appeared.

Scope:

  1. Auto-mount service -- plug an ext4 SD card or USB stick into Windows and the ExtFsWatcher service mounts it on a free drive letter in the active console session. Detach and it unmounts cleanly.
  2. Right-click verb -- "Mount as ext4" on .img files in Explorer for offline disk images.
  3. CLI browser -- open an ext4 image or raw device; list/stat/read files without mounting. Cross-platform (macOS/Linux work too -- handy for testing).
  4. Setup.exe -- bundles WinFsp via a Burn bootstrapper, so end users only run one installer.

The fs-ext4 library lives at ../rust-fs-ext4/ (a sibling checkout of antimatter-studios/rust-fs-ext4, pinned in chores.yml) and is path-depended; this crate is the distribution unit.

The Windows-driver scaffolding (SCM service, disk-arrival watcher, WinFsp.Launcher integration, partition-table walker, raw-device I/O, installer + CI templates) lives in winfsp-fs-skeleton, checked out as a sibling at ../winfsp-fs-skeleton/. ext4-win-driver is the first consumer of that skeleton -- it was extracted from this project once the auto-mount path was working end-to-end. See The skeleton split below for the boundary.

Status

Shipped in v0.1.0:

  • CLI: info, ls, stat, cat, tree, parts, audit
  • MBR/GPT partition parsing (5 unit tests)
  • --part N mounts a partition slice via the C ABI's callback mount
  • Win32 raw-device support (\\.\X:, \\.\PhysicalDriveN, \\?\STORAGE#Disk#...) with sector-aligned reads for 4Kn / advanced-format drives
  • WinFsp read-only mount -- verified on Windows 11 ARM64 with both VHD smoke tests and a real Raspberry Pi SD card
  • WinFsp read-write mount (default; --ro for read-only) -- create/pwrite/truncate/rename/unlink/rmdir/mkdir/utimens wired through the C ABI. Streaming positional writes land in fs-ext4 via pwrite, so large-file workloads no longer round-trip the whole file.
  • ExtFsWatcher SCM service -- subscribes to disk-class arrivals (GUID_DEVINTERFACE_DISK), walks the partition table directly, probes each partition for the ext4 superblock magic, and asks WinFsp.Launcher to spawn a per-partition mount in the active console session
  • Right-click .img -> Mount as ext4 verb registered under HKCR\SystemFileAssociations\.img
  • MSI + Burn bundle that auto-installs WinFsp if missing (chained via the official WinFsp redistributable MSI)
  • x64 + arm64 Setup.exe built per release tag by GH Actions (.github/workflows/release.yml) and attached to the release.

Not yet shipped:

Out of scope (intentionally):

  • Code-signing certificate -- the per-year cost isn't justified for an unsponsored side project. Setup.exe will continue to trip SmartScreen on first download until that changes.

Install (end users)

Download ext4-win-driver-<ver>-<arch>-Setup.exe (arch is x64 or arm64) from Releases, run it, accept the GPL-3 prompt. The installer:

  • chains the WinFsp MSI if WinFsp isn't already installed,
  • drops ext4.exe and Mount-Ext4.ps1 into C:\Program Files\ext4-win-driver\,
  • registers ExtFsWatcher as an automatic Windows service and starts it,
  • registers the ext4-mount service-class with WinFsp.Launcher,
  • registers the "Mount as ext4" right-click verb on .img files.

Setup.exe /quiet works for unattended installs. SCCM/Intune deploys should push the bare MSI separately, after pushing WinFsp.

Usage

Plug in an ext4 SD card or USB stick: nothing else needed. The service picks it up, finds the ext4 partition(s), and mounts each on a free drive letter (E: upward).

CLI (works on any host):

ext4 info   <image>                        # volume label, sizes, features
ext4 ls     <image> [path]                 # directory listing
ext4 stat   <image> <path>
ext4 cat    <image> <path>
ext4 tree   <image> [--canonical]            # --canonical: no inode numbers, sorted
ext4 parts  <image>                        # MBR/GPT partition table
ext4 audit  <image>                        # link-count + dirent integrity scan
ext4 ls     <whole-disk.img> --part 1 /    # browse partition 1

Manual WinFsp mount (Windows + mount feature):

ext4 mount <image> --drive X:                       # read-write (default)
ext4 mount <image> --drive X: --ro                  # read-only
ext4 mount <whole-disk.img> --drive X: --part 1     # mount partition 1

Then browse X: in Explorer, or Get-ChildItem X:\, etc. Ctrl-C to unmount.

Toggling read-only / read-write

The default for every mount path is read-write. Pass --ro per-mount, or use one of the system-wide knobs below:

  • Auto-mount service. The WinFsp.Launcher CommandLine for the ext4-mount service-class lives at HKLM\SOFTWARE\WOW6432Node\WinFsp\Services\ext4-mount\CommandLine (32-bit registry view). Default: mount %2 --drive %1 --part %3. Append --ro there to force every auto-mount read-only.
  • Right-click verb. The shortcut runs Mount-Ext4.ps1 which takes a -ReadOnly switch. Edit HKCR\SystemFileAssociations\.img\shell\MountAsExt4\command to add -ReadOnly to the script invocation if you want the verb itself to default to RO.
  • Per-mount via PowerShell. Run Mount-Ext4.ps1 -ImagePath disk.img -ReadOnly for one-off RO mounts.

The legacy --rw flag is still accepted (silently) so existing scripts and harness.toml configs that pre-date the flip don't break.

Watch mode (foreground variant of the service, useful for dev / debugging):

ext4 watch                                  # foreground; logs each arrival

The skeleton split

After v0.1.0 was working end-to-end, the platform plumbing was extracted into its own crate (winfsp-fs-skeleton) so the same scaffolding can host future filesystem drivers (qcow2, ntfs, ...) without copy-paste. The boundary:

Lives in skeleton (reusable) Lives here (ext4-specific)
service::run<B> -- SCM dispatcher + WinFsp.Launcher src/main.rs -- Ext4Backend impl (4 const + 1 fn), CLI dispatch
watch::run<B> -- foreground variant src/cmd.rs -- info/ls/stat/cat/tree/parts/audit subcommands
partition -- MBR/GPT parsing src/mount.rs -- WinFsp FileSystemContext impl, fs-ext4 callbacks
device -- BlockSource + sector-aligned FileSource src/probe.rs -- is_ext4 magic-byte predicate
probe -- drive-letter selection, GUID_DEVINTERFACE_DISK, DEV_BROADCAST_DEVICEINTERFACE_W parsing
templates/installer/ -- WiX MSI + Burn shapes installer/ -- ext4-customised copies of the templates
templates/release.yml -- GH Actions x64 + arm64 build matrix .github/workflows/release.yml
templates/winget/ -- manifest skeleton winget/v0.1.0/ -- the actual submission

The public seam is one trait + four constants:

use winfsp_fs_skeleton::FsBackend;

struct Ext4Backend;
impl FsBackend for Ext4Backend {
    const FS_NAME: &'static str = "ext4";
    const SERVICE_NAME: &'static str = "ExtFsWatcher";
    const LAUNCHER_SERVICE_CLASS: &'static str = "ext4-mount";
    const FILE_EXTENSION: &'static str = "img";
    fn detect(bytes: &[u8]) -> bool { probe::is_ext4(bytes) }
}

Cmd::Watch and Cmd::Service in src/main.rs just hand off:

Cmd::Watch   => winfsp_fs_skeleton::watch::run::<Ext4Backend>(),
Cmd::Service => winfsp_fs_skeleton::service::run::<Ext4Backend>(),

Build

CLI only (any platform):

cargo build --release

WinFsp mount + auto-mount service (Windows only):

cargo build --release --features mount,service

The release profile statically links the C runtime via .cargo/config.toml so the binary has no libunwind.dll import -- required for the SCM session-0 launch path where the LocalSystem PATH doesn't reach the LLVM-MinGW runtime dir. panic = "abort" is set in Cargo.toml for the same release profile.

WinFsp build prerequisites

  • WinFsp 2.1+ installed on the build/run machine (winfsp.dev -> MSI, or winget install WinFsp.WinFsp).
  • A forked winfsp-rs is checked out as a sibling at ../winfsp-rs/, pinned to a commit from the fix/guard-strategy-constant-type branch (path-depended; the upstream PR is pending). The fork also requires:
    • LLVM for libclang.dll (winget install LLVM.LLVM)
    • LLVM-MinGW (winget install MartinStorsjo.LLVM-MinGW.UCRT)
  • LIBCLANG_PATH=C:\Program Files\LLVM\bin so bindgen can find libclang.dll.
  • winfsp-fs-skeleton is checked out as a sibling at ../winfsp-fs-skeleton/; pure Rust, no extra toolchain requirements.

Building the installer

After cargo build --release --features mount,service finishes:

installer\build.ps1 -ExePath target\release\ext4.exe -Arch arm64

Produces dist\ext4-win-driver-<ver>-arm64.msi and dist\ext4-win-driver-<ver>-arm64-Setup.exe. The script PE-sniffs -ExePath to catch arch mismatches before WiX does. See installer/README.md for the longer walkthrough (WiX 7 install, WinFsp redist pin, etc).

Testing

Scenarios live in test-matrix.json and the per-project adapter config in fs-windows-test-harness.toml. Both are consumed by the shared fs-windows-test-harness, checked out as a sibling repo at ../fs-windows-test-harness/ -- not a submodule. Its URL and pinned tag (HARNESS_URL / HARNESS_REF) live in chores.yml, next to the other family pins.

After cloning, check out the siblings at their pinned refs:

chore siblings          # clone or move each sibling to its pin; refuses to touch a dirty one
chore siblings:check    # report where each sibling is, changing nothing

CI does not run chore (there is no Windows build of it); the workflows read the same *_URL / *_REF values out of chores.yml and clone the siblings directly.

Run the test matrix (Mac -> SSH -> Windows VM -> diag pull):

chore matrix                          # full matrix, quietly
chore matrix -- basic-ro-list         # one scenario (substring filter)
chore matrix -- rw- --verbose         # stream the run as well

chore matrix builds the host CLI the verifiers use, then runs scripts/run-matrix.sh, which starts the Alpine VM that builds each scenario's ext4 image and hands off to the harness's run-tests.sh. It is quiet: the whole run (every SSH session, echoed PowerShell command line and verifier) goes to tmp/logs/matrix.log, and what is printed is one verdict line and the executed-scenario count against its floor. A run that passed but printed more than the tier's measured budget fails with status 65; the budgets are the table in scripts/tier-budgets.txt, and the runner and wrapper are rust-fs-core's scripts/tier.sh and scripts/output-budget.sh, run in place from ../rust-fs-core, not copies.

CI runs the same matrix on every pull request, in the matrix job on windows-latest: the runner is its own Windows VM over SSH to localhost, and the images are built by the same _vm-builder.sh on an Ubuntu runner first (EXT4_PREBUILT_IMAGES), because a Windows runner cannot start the builder VM.

The first run needs the VM's details. A quiet run has no terminal for the harness to prompt on, so pass them once as flags and the harness writes .test-env (gitignored) for every later run:

chore matrix -- --vm-host=USER@IP --ssh-key=/path/to/key --vm-workdir=C:/path/on/vm

After a VM IP change or key rotation, pass the new value the same way; any flag given is written back to .test-env.

Diagnostics land under test-diagnostics/run-<UTC>/. See the harness's docs/triage-protocol.md for how to read a failure, and docs/multi-agent-protocol.md for running multiple agents against the same matrix.

To move to a new harness release, edit HARNESS_REF in chores.yml to the new tag, then re-run chore siblings to check it out locally. That one line is the whole bump: CI picks the new ref up from the same file.

License

GPL-3.0-or-later -- inherited from the WinFsp Rust bindings. The CLI alone (without the mount feature) doesn't link winfsp and could be relicensed if split out, but the single-license declaration keeps the distribution unit simple.

About

Browse and mount ext4 filesystems on Windows via WinFsp

Topics

Resources

Stars

23 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages