The one rule: never write where the old data lives
Guide 3 left you with a hard-won truth: a write that updates data in place is dangerous, because a crash can catch it half done. The fix there was the journal — write your intent to a log first, then go back and modify the real blocks, so a crash can be cleaned up by replaying or discarding the log. A copy-on-write filesystem reaches the same safety from the opposite direction. It makes one stubborn rule: when you change a block, you never overwrite the old one. You write a brand-new copy somewhere free, and leave the old block exactly as it was until the change is fully committed.
You have already met this idea outside the disk. In the virtual-memory rung, copy-on-write let a freshly forked process share its parent's pages read-only, and only duplicate a page the instant someone wrote to it. The disk version is the same trick aimed at storage instead of RAM: don't touch the original until you must, then make your change in a fresh location. The name is honest — the copy happens on write, and only on write. A block nobody modifies is never copied at all.
The filesystem is a tree, and you re-grow a branch
To see why never-overwrite is powerful and not just wasteful, picture the on-disk layout as a tree of pointers. At the leaves are your data blocks. Above them sit metadata blocks — in the extent and indirect-block world of guide 2, the structures that say "this file's bytes live at these block numbers." Above those sit more metadata, and at the very top sits a single root block that ultimately points, through all the layers, to every byte on the volume. Find the root and you can reach anything; change the root and you change which version of everything is live.
Now follow one small write. You change four bytes in the middle of a file. Copy-on-write does not edit the leaf in place. It writes a new leaf block holding the updated bytes, somewhere free. But the metadata block above used to point at the old leaf, so that pointer is now wrong — and you cannot edit it in place either, by the same rule. So you write a new metadata block that points at the new leaf. That block's parent is now stale too. You keep climbing, writing a fresh copy of each node on the path, all the way up to a new root. This walk is the heart of the design, and it has a name worth keeping: re-growing the branch from leaf to root.
before the write: after a copy-on-write update:
ROOT -----------+ ROOT' (new) ------+
| | | (old ROOT |
meta ... meta' still on ...
| | | disk, |
leaf meta leaf ... leaf' meta leaf ...
(data) ... (new) (shared, untouched)
only the leaf->...->root PATH is rewritten; every
block NOT on that path is shared, not copied.Here is the quiet brilliance: until the very last step, the old root still points at a perfectly intact old tree. Your new blocks exist but nothing references them yet. The commit is one tiny act — atomically swing the on-disk pointer from the old root to the new root. Before that swing, a crash leaves the old version whole. After it, the new version is whole. There is no in-between state where the tree is half-updated, which is exactly the crash consistency guarantee guide 3 fought for, achieved here without a separate log at all.
Snapshots fall out for free
Once you see the tree-and-root picture, snapshots stop being a feature you bolt on and become something you almost cannot avoid. A snapshot is a frozen, read-only view of the whole filesystem at one instant. To make one, you do not copy any data. You simply keep a copy of the current root pointer and promise not to free the blocks it reaches. That is the entire operation: remember one root. It takes a few bytes and happens in a moment, no matter whether the volume holds one file or ten million.
Why does the snapshot stay correct as the live filesystem keeps changing? Because of the one rule. New writes never overwrite the blocks the snapshot's old root still points at — they always go to fresh locations and build a new root. So the snapshot's tree is automatically preserved, untouched, simply by not deleting its blocks. The live tree and the snapshot tree quietly share every block that has not changed since the snapshot, and diverge only on the blocks that have. A snapshot therefore costs almost nothing at creation and grows only as the live data drifts away from it — you pay storage exactly for what changed, never for what stayed the same.
Checksums, self-healing, and an honest cost
The tree buys one more thing that older filesystems struggle to offer: end-to-end integrity. Because every parent block already has to be rewritten whenever a child changes, it is cheap to store, inside the parent, a checksum of the child it points to. ZFS and Btrfs do exactly this: each pointer carries a checksum of the block it targets, all the way up to the root. When the filesystem reads a block, it recomputes the checksum and compares — so silent disk corruption, a bit flipped by a failing drive or a cosmic ray, is detected rather than quietly handed to your program as if it were good data. That is a genuinely different guarantee from a filesystem that trusts the disk to return what it stored.
When the volume keeps redundant copies — a mirror, or RAID-style parity — detection becomes repair. A checksum mismatch tells the filesystem which copy is bad, so it reads a good copy, returns the correct bytes to your program, and rewrites the bad block, all transparently. This is the self-healing that makes these filesystems beloved for long-term storage. Be precise about the limit, though: a checksum on a single un-mirrored disk can tell you a block is corrupt, but it cannot conjure back the lost data. Integrity checking and redundancy are two different jobs, and you need both for the disk to actually heal.
ZFS and Btrfs: two takes on the same idea
Both ZFS and Btrfs are built on the copy-on-write tree, but they grew from different goals and it shows. ZFS came out of Sun Microsystems in the mid-2000s, and its big idea is to swallow the whole storage stack — it is filesystem and volume manager fused into one, pooling many disks together and managing redundancy itself, so that integrity and RAID are decided by the layer that holds the checksums. It is famous for being conservative and rock-solid, at the price of being hungry for RAM and, for licensing reasons, never merged into the mainline Linux kernel.
Btrfs (the "B-tree filesystem") was designed natively for Linux and lives in the mainline kernel. Its structures lean on B-trees — the same balanced-tree shape behind the extent and directory indexing you met in guide 2 — to organize that copy-on-write metadata. It offers the same headline features, snapshots and checksums and pooling, with a different feel: more flexible to reshape a live array, and historically rougher around some edges, particularly the parity-RAID modes, which is exactly the kind of honest caveat worth knowing before you trust a filesystem with data you cannot lose.
It is worth seeing how this rhymes with hardware you already met. An SSD's flash translation layer also refuses to overwrite in place — flash must be erased in big units before rewriting — so it, too, writes new data to fresh pages and remaps a pointer, then garbage-collects the stale ones later. Copy-on-write at the filesystem level and remapping at the flash level are the same instinct at two layers: never overwrite live data, redirect a pointer instead. Seeing the pattern twice is the point of climbing this ladder — the same idea keeps reappearing wherever something must change safely while something else might be reading it.
Where this leaves you
Step back and the whole design folds into one rule and its consequences. Never overwrite live data: write new blocks, re-grow the path to a new root, and commit by swinging one pointer atomically. From that single discipline you get crash consistency without a journal, near-instant snapshots that cost only what diverges, and a natural home for checksums that turn silent corruption into something detected and, with redundancy, repaired. The price, paid honestly, is fragmentation and write amplification — real, measurable, and worth the trade for many workloads.
You have now seen two answers to the same crash-safety question: guide 3's journal writes twice and overwrites in place, while copy-on-write writes once and never overwrites at all. Neither is strictly better; they make different bargains, and knowing both lets you read a filesystem's design and predict its behavior. One thread still runs through both, though, and it is the last piece of this rung: all of this safety reasoning lives above a cache. Your writes do not hit the disk when write() returns — they sit in the page cache until something forces them down. Guide 5 follows that final mile, and pins down exactly what promise fsync does and does not make about your data surviving a power cut.