ntfs on linux
NTFS support on Linux has a deserved reputation for sharp edges. wootc's entire design lives next to NTFS β the Linux system is a file on the Windows C: drive β so "are we subject to those issues?" deserves a precise answer, not reassurance.
The honest answer: we are exposed to NTFS-on-Linux at four narrow choke points, and the design is built so that none of them can decide the integrity of the Linux system or of Windows. One of these hazards has already bitten a real run; the fix and its regression test are part of the story below. Every claim here is tied to the code that enforces it.
The problems, as they actually existβ
- NTFS cannot host a Linux system. No POSIX permissions (uid/gid are faked at mount time), no security xattrs (so no SELinux labels), no case sensitivity, symlinks only as reparse points. A Linux root placed directly on NTFS is broken from first boot.
- Two drivers, different failure modes. The kernel
ntfs3driver (Paragon, merged in 5.15) is fast but young β it has shipped corruption and sparse-file bugs, went through unmaintained stretches, and refuses volumes the older driver accepts. The FUSEntfs-3gdriver is slow but battle-tested since 2007. Distributions disagree about which one you get, and some images ship neither. - Dirty volumes, hibernation, and Fast Startup. Windows Fast Startup
is hibernation in disguise: "shut down" leaves the filesystem
journaled-open with metadata cached inside
hiberfil.sys. Mounting that volume read-write from Linux is the classic dual-boot data-loss story, andntfs3refuses dirty volumes even read-only. - Valid Data Length (VDL). NTFS tracks how much of a file's
allocation has actually been written. The Linux
ntfs3driver EIOs on writes past VDL β a Windows-side subtlety invisible until a loop device dies mid-write. - Only Windows can repair NTFS. Journal replay and
chkdskare Windows facilities;ntfsfixclears flags, it does not fix filesystems. Anything Linux corrupts, only Windows can heal. - Unmount discipline is on you. A Linux mount that is not cleanly released leaves the volume dirty for the next Windows boot.
The design answer, hazard by hazardβ
Linux never lives on NTFS β NTFS only stores a container's bytesβ
The installed system's root is inside root.disk, whose contents
are a real Linux filesystem (xfs, ext4, or btrfs) with full POSIX
permissions, security xattrs, and SELinux labels. NTFS's job is reduced to
storing one large file's bytes; none of its semantic gaps (problem 1) can
reach the OS. This is the load-bearing decision β everything else follows
from it. (app/disk_windows.go, docs/philosophy.md on the Wubi
heritage.)
Windows allocates; Linux writes inside the linesβ
root.disk is created by Windows, with Windows' own NTFS driver:
sparse allocation via SetLength, then the Valid Data Length is extended
with fsutil file setvaliddata β precisely because the Linux ntfs3
driver EIOs on loop-device writes past VDL (problem 4; the comment above
createRootDisk in app/disk_windows.go records this). Linux never asks
an NTFS driver to make allocation decisions; it performs bulk I/O within a
file whose NTFS metadata Windows already settled. Host-profile bind mounts
use ntfs3's prealloc option for the same reason.
Fast Startup is removed at the source, and put back on the way outβ
The install preflight detects both halves separately β the
HiberbootEnabled registry flag and a hibernation image actually on
disk (app/sysprobe_windows.go; the distinction mattered, see #63). The
install then runs powercfg /h off and clears HiberbootEnabled, so
every "shut down" from that point is a full shutdown and the volume Linux
mounts is journaled-closed (problem 3). Uninstall restores the user's
prior power state (restorePriorPowerState) β "Windows is unchanged"
stays true after leaving.
Defense in depth: the deployer still treats dirty volumes as possible.
Its read-only scan mounts try ntfs3 -o ro, then ro,force, then
ntfs-3g (payload/deployer/deploy.sh), and the Phase-2 attach hook's
failure message names the fix a user can actually perform: "Boot Windows
once and full-shutdown."
No single driver is trusted β a probe chain decides at bootβ
The Phase-2 attach hook (platform/dracut/99wootc-boot/wootc-attach-loop.sh)
probes in order: kernel ntfs3 (which may be built in, with no .ko to
find), then the ntfs-3g/lowntfs-3g FUSE drivers, then the legacy
ntfs type β because image support genuinely varies (problem 2). The
deployer ships its own self-contained ntfs-3g closure
(stage_ntfs3g_closure: binary, full library closure, and loader, proven
by execution) so an image that ships no NTFS driver at all still attaches.
Which driver won is printed to the serial console and asserted by the E2E
per image β the proven combinations are in docs/status.md, not in a
promise.
One writer at a time, by constructionβ
Windows and Linux never touch the volume concurrently: Phase 2 boots only after Windows has fully shut down, and Windows returns only after Phase 2 has rebooted. Mutual exclusion is enforced by the machine itself β there is no file-locking protocol to get wrong.
The volume is handed back closed β the hazard that actually bitβ
Problem 6 is not hypothetical here. Phase 2's root is a loop device backed
by a file on the rw NTFS mount, so at shutdown the stack pins itself:
systemd could not remount the NTFS read-only, Windows booted into a
half-open volume, looped the boot manager, and died in Startup Repair
(run 30704513401 β notably, only the kernel-ntfs3 cells failed;
FUSE cells survived, which made a structural bug look image-specific).
The fix pivots PID 1 back into the initramfs at shutdown
(/run/initramfs/shutdown), where the root is no longer the loop device,
so the loop detaches and the NTFS unmounts cleanly β including on ostree
and composefs images where dracut's normal restore path no-ops. The full
story is docs/phase2-attach-postmortem.md; the contract is pinned by
tests/unit/phase2-clean-ntfs-umount.bats, and the E2E asserts Windows
actually comes back after every Phase-2 run.
Blast radius: a container file, never the C: volumeβ
The worst realistic outcome of an NTFS-driver bug on our write path is
damage inside root.disk β the user's Linux, recoverable by
reinstalling into the same file, while Windows and the user's files are
untouched. The reverse worry (Linux damaging C: metadata) is bounded by
the facts above: Windows performed the allocation, writes are
within-file, mounts are exclusive and cleanly released, and chkdsk
retains a healthy volume it fully owns. And the exit is always one
deletion away: remove root.disk, and NTFS never knew we were there
(docs/user-guide.md Β§9 β the uninstall's cleanup claims are themselves
E2E-verified).
What we deliberately do not doβ
- No Linux root directly on NTFS. Ever. That design fails problem 1 on day one.
- No writing to a hibernated volume. Fast Startup is disabled before the first byte is staged, not worked around afterward.
- No betting on one driver. Both
ntfs3andntfs-3gare first-class paths, per image, chosen by a runtime probe and proven by the E2E. - No trusting a write that landed. Every NTFS-adjacent claim above is
asserted by a test that fails when the claim is false β the harness's
founding lesson (
docs/agent-lessons.md).
Residual risk, stated plainlyβ
The youngest component we rely on is kernel ntfs3 in read-write mode
for the user-data bridge (host profile folders bound into $HOME). A
future ntfs3 regression could corrupt files it writes through those
binds β that is a real exposure, shared with every dual-boot setup on the
same kernel. Its bounds: the FUSE fallback exists on every image, the
bridge binds live data rather than migrating copies (so nothing is
deleted from the Windows side by design), the Linux OS itself is not
reachable through that driver, and every supported image's bridge is
exercised nightly by the E2E before users see it.