File Systems
A file system organizes a storage device into named files and directories.
Concept
Files, Directories and Inodes A file system organizes a storage device into named files and directories. In Unix file systems (ext4, XFS, Btrfs):
An inode stores a file's metadata — type, permissions, owner, size, timestamps, link count — and the location of its data blocks. It does not store the name.
A directory is a table of *name → inode number* entries.
A hard link is another directory entry for the same inode; the data is freed only when the link count and the number of open descriptors both reach zero.
A symbolic link is a small file containing a path; it breaks if the target is removed.
On-Disk Layout and Allocation Each file in consecutive blocks
ISO 9660 (CD-ROM); fast, external fragmentation Linked / FAT
Each block (or a table entry) points to the next FAT32, exFAT (USB sticks, SD cards, many embedded devices)
The inode lists block addresses (direct, indirect, double indirect)
Ranges (start, length) of contiguous blocks ext4, XFS, NTFS Free space is tracked with bitmaps or free lists/trees. Sparse files have holes that occupy no blocks.
Consistency: Journaling and Copy-on-Write A crash in the middle of an update (e.g. while allocating a block and updating the inode) can corrupt the file system. Solutions:
Journaling (ext4, XFS, NTFS): write the intended changes to a log first, then apply them; after a crash, replay or discard the log. ext4 journals metadata by default (data=ordered).
Copy-on-write (Btrfs, ZFS, APFS): never overwrite live data; write new blocks and switch a root pointer atomically; enables cheap snapshots.
fsync forces a file's data and metadata to stable storage — the basis of database durability.
The VFS Layer and Caching Linux's Virtual File System gives every file system (ext4, FAT, NFS, /proc, /sys) the same interface: open, read, write, stat. The page cache keeps file data in RAM; writes are usually buffered and written back later.
Why It Matters
Log files that fill the disk but "can't be found", data loss after power failures, slow ls in directories with millions of files, flash wear in embedded devices and the durability guarantees of databases all come down to file system design.
Syntax
ls -i, stat Inode number and metadata Hard and symbolic links File system type, space used
open, read, write, lseek, fsync, unlink File system calls
Example
22.1 Inodes, hard links, symbolic links, sparse files 22.2 A deleted file that is still open
$ echo "12.6 V" > battery.txt
$ stat -c 'inode %i, size %s bytes, %b blocks of %B bytes, links %h, type: %F' battery.txt
inode 102649, size 7 bytes, 8 blocks of 512 bytes, links 1, type: regular file
$ ln battery.txt hardlink.txt && ln -s battery.txt symlink.txt
$ stat -c '%i %A links=%h %N' battery.txt hardlink.txt symlink.txt
102649 -rw-r--r-- links=2 'battery.txt'
102649 -rw-r--r-- links=2 'hardlink.txt'
102650 lrwxrwxrwx links=1 'symlink.txt' -> 'battery.txt'
$ rm battery.txt && cat hardlink.txt && cat symlink.txt
12.6 V
cat: symlink.txt: No such file or directory
$ dd if=/dev/zero of=sparse.img bs=1 count=0 seek=1G 2>/dev/null && ls -lh sparse.img | awk '{print "apparent size", $5}' && du -h sparse.img | awk '{print "blocks used ", $1}'
apparent size 1.0G
blocks used 0
$ df -T . | awk 'NR==2 {print "file system type:", $2}'
file system type: ext4
$ ls -ld /proc /sys /dev /tmp | awk '{print $1, $NF}'
drwxr-xr-x /dev
dr-xr-xr-x /proc
dr-xr-xr-x /sys
drwxrwxrwt /tmp#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
int main(void)
{
int fd = open("/tmp/eh_log.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
write(fd, "flash log\n", 10);
unlink("/tmp/eh_log.txt"); /* remove the NAME: the inode lives while fd is open */
struct stat st;
fstat(fd, &st);
printf("after unlink: name exists: %s, inode %lu still open with %ld link(s)\n",
access("/tmp/eh_log.txt", F_OK) == 0 ? "yes" : "no", (unsigned long)st.st_ino, (long)st.st_nlink);
char buf[32] = {0};
pread(fd, buf, sizeof buf - 1, 0);
printf("data still readable through the descriptor: %s", buf);
fsync(fd); /* force data to stable storage (what databases do on commit) */
close(fd); /* now the kernel frees the inode and its blocks */
return 0;
}Expected Result
The transcript shows:
battery.txt and hardlink.txt share the same inode and the link count is 2; the symbolic link has its own inode and just stores the path.
After rm battery.txt the data is still reachable through the hard link, but the symbolic link is dangling.
The 1 GB sparse file uses no blocks at all.
The small file uses 8 blocks of 512 bytes (one 4 KB file system block).
/proc and /sys are virtual file systems served by the kernel.
(The inode numbers depend on the file system.) After unlink the name was gone and the link count was 0, but the inode and its data lived on while the descriptor was open — the classic "disk full but I deleted the log" situation (the process still holds the file open).
after unlink: name exists: no, inode 102650 still open with 0 link(s)
data still readable through the descriptor: flash logCommon Mistakes
Deleting a large log file that a process still writes to, expecting the space to be freed.
Assuming write means the data is on disk (it's in the page cache until written back or fsynced).
Using symbolic links where hard links (or vice versa) are needed; hard links can't cross file systems or point to directories.
Storing millions of files in one directory on file systems without hashed directories.
Best Practices
For durable updates: write to a temporary file, fsync it, rename over the old file, fsync the directory.
Use lsof +L1 to find deleted-but-open files.
Choose FAT/exFAT for removable media interoperability, ext4/XFS for Linux systems, and flash-aware file systems (F2FS, LittleFS on microcontrollers) for raw flash.
Interview Relevance
High: "what is an inode?", "hard link vs soft link?", "what does a directory contain?", "how does journaling work?", "what happens when you delete an open file?", "FAT vs inode-based file systems?".
Practice Questions
How many blocks can an ext2 inode address with 12 direct pointers, one indirect and one double-indirect pointer, 4 KB blocks and 4-byte block addresses?
Implement "atomic save" (write temp, fsync, rename) in C.
Create a directory with 100 000 files and time ls and find | wc -l.
Use debugfs or stat to inspect an ext4 file's extents (on a system where you have access).
Quick Quiz
Q1. (Multiple choice) Where is a file's name stored in a Unix file system? A) In the inode B) In the directory entry C) In the superblock D) In the first data block Answer: B.
Q2. (Predict the output) After rm battery.txt, what does cat hardlink.txt print, and what does cat symlink.txt do?
Answer: The hard link prints 12.6 V; the symbolic link fails with "No such file or directory" (verified).
Q3. (True/False) A 1 GB sparse file always occupies 1 GB of disk space.
Answer: False. It used 0 blocks (verified).
Q4. (Find the bug) A program writes a config file with fopen("w"), fwrite, fclose; after a power cut the file is empty.
Answer: The data was still in the page cache; and truncating then rewriting in place isn't atomic. Write a temp file, fflush+fsync, then rename.
Coding Challenge
Implement atomic_write(path, text) that writes to path.tmp, calls fsync, and renames it over path; use it twice on the same file and print the final contents.
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
static int atomic_write(const char *path, const char *text)
{
char tmp[256];
snprintf(tmp, sizeof tmp, "%s.tmp", path);
int fd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) return -1;
if (write(fd, text, strlen(text)) != (ssize_t)strlen(text) || fsync(fd) != 0) { close(fd); return -1; }
close(fd);
return rename(tmp, path); /* atomic replacement of the directory entry */
}
int main(void)
{
atomic_write("/tmp/eh_config.txt", "bitrate=250000\n");
atomic_write("/tmp/eh_config.txt", "bitrate=500000\n");
char buf[64] = {0};
FILE *f = fopen("/tmp/eh_config.txt", "r");
size_t n = fread(buf, 1, sizeof buf - 1, f);
fclose(f);
printf("final contents (%zu bytes): %s", n, buf);
return 0;
}