Guide

A file system turns a disk into named files and directories. Each file has an inode, a record holding its size, permissions, timestamps and pointers to its data blocks. The name is separate. A directory entry is just a mapping from a name to an inode number.

That separation explains a lot. Renaming a file doesn't move its data. Deleting a name doesn't free the data until no names point to the inode. Two names can share one inode.

A hard link is a second name for the same inode. The file's link count tracks how many names exist. Here two names point at one inode:

Python
import os, tempfile
d = tempfile.mkdtemp()
p = os.path.join(d, "a.txt"); q = os.path.join(d, "b.txt")
with open(p, "w") as f: f.write("hello\n")
os.link(p, q)
sa, sb = os.stat(p), os.stat(q)
print(sa.st_ino == sb.st_ino, sa.st_nlink)

Output:

True 2

Both names report the same inode, and the link count is 2. Deleting one name leaves the data in place, because the other name still refers to the inode. A symbolic link, by contrast, stores a path, so it can dangle if the target is moved or deleted. Hard links can't cross file systems or point at directories, which is why they're rarely used for application data.

The crash problem

Creating a file changes several structures: the directory entry, the inode, a bitmap of free blocks, and the data. A crash between two of those changes leaves the file system inconsistent. For example, a block is marked used but no file points to it, or a directory points to an inode that was never written.

Old file systems fixed this with a slow fsck pass after every crash, which scans the whole disk. On large volumes, that takes too long.

Journaling

A journal solves it, the same way a database's write-ahead log does. Before changing the real metadata, the file system writes the intended change to the journal. After a crash, it replays the committed entries in the journal and discards the rest. Recovery takes seconds, not hours.

Modes differ in what they journal:

  • Metadata-only journaling (ext4's default data=ordered) journals names and inodes and writes data first. The file system stays consistent, but file contents can be stale after a crash.
  • Full data journaling logs the data too, which is safer and slower.

Journaling keeps the file system's structure consistent. It does not by itself guarantee that your application's data reached the disk. That is the job of fsync, covered in page cache and fsync.

Note

Copy-on-write file systems, such as ZFS and Btrfs, take a different route. They never overwrite live blocks, so a crash leaves either the old tree or the new one, and they checksum data as well.

Common mistakes

  • Assuming that a journaled file system protects your file contents. It protects structure by default, not your bytes.
  • Using hard links across volumes or for directories, which the system won't allow.
  • Treating a rename as a safe update without checking what happens to the old file's data.

Check yourself

  • Why does a hard link keep a file's data after the original name is deleted? Answer: both names point to the same inode, and the data is freed only when the link count reaches zero.
  • Why does a journal make recovery fast? Answer: it replays only the recent committed changes, instead of scanning the whole disk for inconsistencies.

Key takeaways

  • A name is a pointer to an inode. Hard links share an inode, and deleting a name only drops one link.
  • Journaling logs metadata changes before applying them, so recovery is quick and structure stays consistent.
  • Journaling doesn't guarantee your data is on disk. Use fsync for that.

Further learning

Next, see page cache and fsync to learn when data is truly durable.