Skip to content

Analyses / Filesystem recovery

Btrfs tree logs: why a read-only mount can still replay writes

Published 15 October 2026 / Version-pinned source review

Read-only is not a complete description of Btrfs recovery behavior. Mount-time tree-log replay can write even when the requested mount is read-only. Skipping replay changes the recovered view; clearing the log changes the evidence. These are different operations, not interchangeable repair steps.

Copy-on-write still has a recovery log

Btrfs uses copy-on-write metadata, but an fsync need not commit the whole filesystem transaction. The Linux v6.16 design comment describes a tree log per subvolume root: changed items for a file or directory can be logged separately. Recovery later incorporates those items into the subvolume tree and accounts for their data extents. [1]

Bacik's 2012 architectural explanation describes why limiting the metadata involved in fsync can avoid unrelated work. That is historical design context, not a latency guarantee for today's systems. [8] The v6.16 fsync implementation still includes a full transaction-commit fallback. Neither "fsync always commits everything" nor "fsync only ever writes the log" is a sound rule. [2]

Our interpretation: a crash-recoverable file update and a fully committed filesystem transaction are different boundaries. Treating them as one event can make a recovery procedure discard information the application expected to survive. The analogous distinction in ZFS intent logging does not imply identical on-disk formats or recovery algorithms.

The mount itself is part of the write boundary

Btrfs documents replay on read-only mounts. Its rescue option rescue=nologreplay suppresses dirty-log replay and requires a read-only mount. Files or directories represented only by the pending log may consequently be absent from that view. A readable older view does not prove that acknowledged updates never existed. [3]

This also qualifies read-only scrub advice: the scrub operation and the earlier mount are separate stages. "The scrub did not repair anything" cannot establish that mounting left the original image untouched. For an investigation, preserve the source and work from a controlled copy; record the recovery policy before interpreting missing content.

Which layer is read-only?

The same label can describe three different controls. Record the object and layer before treating a read-only observation as evidence about storage preservation.

Documented controls, not a tested recovery procedure.
ControlScopeWhat it does not establish
Btrfs subvolume roA property of one subvolume's contents and hierarchy. [10]Neither a device-wide lock nor a change to the mount flags.
Read-only mountLinux distinguishes the VFS mount node from the filesystem superblock. A requested ro setting applies to both layers by default for standard mounts. [12]A read-only bind mount can coexist with a writable original path.
Block-device read-only flagSoftware-controlled device state, exposed through ioctls. [13]The util-linux v2.41.1 manual warns that existing active access may be unaffected, including a filesystem already in read-write use.

A read-only access path does not turn a writable Btrfs subvolume into a read-only subvolume. Conversely, a read-only subvolume does not freeze the entire filesystem. Its property and the mount state answer different questions. [11] The tree-log replay boundary above remains a separate part of recovery.

The optional spellings have a compatibility boundary. The v2.41.1 manual dates ro=vfs and ro=fs to util-linux 2.41, labels these arguments experimental, and requires the file-descriptor-based kernel mount API. With the old mount(2) interface, the argument is silently ignored. [12, ro option] Do not infer the resulting layer from the requested spelling alone. The non-mutating query findmnt -o TARGET,VFS-OPTIONS,FS-OPTIONS separates the reported flags; it does not prove an absence of device writes.

There is also a coverage trap: Btrfs snapshots are non-recursive. Nested subvolume contents are omitted from the parent snapshot, leaving stubs. [11] For example, a parent containing a separately created database subvolume does not capture that database merely because its pathname is beneath the parent. This is an illustrative consequence, not an experiment. Compare snapshot and backup failure boundaries.

Our preservation conclusion is deliberately narrower than "read-only means safe": none of these flags alone validates an entire forensic write-blocking workflow. Software is not inherently disqualified; NIST maintains software write-blocker testing resources. [14] Identify the exact configuration, prior access and recovery policy, and preserve the source before drawing conclusions from a working copy.

The old option spelling stopped working upstream

The Btrfs feature history places rescue= in Linux 5.9 and removal of standalone nologreplay in 6.16. [4] We compared the option-registration tables in the two adjacent upstream tags. This resolves an important trap: rolling documentation can still mention a legacy spelling even though a newer parser no longer registers it. [9] [5]

Source registrations, not executed mount tests. Distribution backports may differ.
Upstream tagStandalone nologreplayrescue=nologreplaynorecovery alias
v6.15Registered, deprecatedRegisteredRegistered for compatibility
v6.16Not registeredRegisteredRegistered for compatibility

notreelog controls tree logging; it is not the switch for skipping an existing recovery log. In v6.16, it sets NOTREELOG, while the rescue replay option sets NOLOGREPLAY. The broader rescue=all also enables options that ignore checksum checks: it is not an equivalent shorthand. [5]

Download the source-audit JSON with SHA-256 hashes and audit script. Running node audit-btrfs-options.mjs fetches the pinned source files and prints the comparison. The script checks exact table boundaries and fails if its expected structure changes. It does not compile a kernel, mount a volume, or certify runtime behavior.

Skipping replay is not clearing the log

btrfs rescue zero-log clears the on-disk log tree. The tool documentation warns that changes since the last transaction commit may be lost. That is a destructive recovery action, not merely another way to inspect a filesystem without replay. [6]

Consider a file whose update was made recoverable through the tree log before a crash. If replay is deliberately suppressed, failure to see that update is not by itself an fsync failure. If the log is then cleared, the recovery input is removed. This is a reasoning example derived from the documented boundaries, not an experiment we ran.

We provide no repair command sequence for an unknown damaged volume. Establish whether the objective is preserving evidence, recovering the newest valid state, or inspecting an earlier state. A successful mount alone cannot decide which objective was met.

What crash-testing research adds

Mohan and colleagues' OSDI 2018 study used CrashMonkey and Ace to generate bounded workloads and simulate crashes around persistence points. New-bug testing used Linux 4.16; shorter sequences covered ext4, XFS, F2FS and Btrfs, while the larger three-operation search focused on F2FS and Btrfs. Those populations were not identical. [7, section 6.2]

One reported Btrfs rename-atomicity failure required an fsync on a sibling file before the crash, illustrating a non-obvious interaction. It is a historical counterexample, not evidence that Linux 6.16 has that bug. Nor does finding no bug within a bounded search prove correctness outside it. [7, section 6.2]

For a future disposable experiment, our proposed oracle would distinguish normal replay from intentionally skipped replay and compare application-visible names, contents and link relationships. Record return values, kernel build, filesystem features and the exact crash point. Do not label deliberately omitted recovery as a filesystem durability defect. See crash-test oracles and bounds.

Method and limits

This is a documentation and code review, with an executable source-registration audit. No filesystem was mounted, repaired or power-cut. We measured no latency, write amplification or failure rate. Version-pinned implementation details do not promise identical behavior in vendor kernels, and a rescue option is not a full forensic write-blocking strategy.

Related mechanisms: ext4 revocation and interrupted replay, failed fsync and retries, and checksums versus freshness.

Primary sources

  1. Linux v6.16: Btrfs tree-log.c, design comment and replay implementation
  2. Linux v6.16: Btrfs file.c, btrfs_sync_file and transaction-commit fallback
  3. Btrfs documentation: btrfs(5), tree logging and rescue mount options
  4. Btrfs feature-by-version: rescue in 5.9 and standalone nologreplay removal in 6.16
  5. Linux v6.16: Btrfs super.c, option registration and read-only checks
  6. Btrfs documentation: btrfs-rescue(8), zero-log and possible loss of changes
  7. Mohan, Martinez, Ponnapalli, Raju and Chidambaram (2018). Finding Crash-Consistency Bugs with Bounded Black-Box Crash Testing. OSDI 18, pp. 33-50, sections 5-6.
  8. Josef Bacik (2012). Btrfs: The Swiss Army Knife of Storage. USENIX ;login:, volume 37, number 1, Filesystem Consistency section.
  9. Linux v6.15: Btrfs super.c, deprecated standalone nologreplay registration
  10. Btrfs documentation: subvolume ro property
  11. Btrfs documentation: subvolume flags and non-recursive snapshots
  12. util-linux v2.41.1: mount(8), Read-only Setting Notes
  13. util-linux v2.41.1: blockdev(8), read-only flag and active-access caveat
  14. NIST CFTT: Software Write Block testing resources

Reviewed 15 October 2026. Rolling manuals are distinguished from pinned source tags.