Master the fundamental concepts of file systems through this focused micro-challenge.
You have read the whole brief, and the concepts above stay free on every task. Writing and running the code needs a plan.
Three hints are available for this task, revealed one at a time inside the code workspace so you can struggle productively before seeing them.
Every task includes starter code, theory, and hidden tests so you can implement and verify locally in the browser.
How it worksfsync and fdatasync push buffered writes to stable storage. I/O barriers constrain reordering so journal records hit disk before metadata they protect. Databases depend on these ordering guarantees.
Stack layers:
For example, SQLite relies on fsync after journal write before committing the main db page; skipping it risks corruption on power loss.
SQLite and Postgres both rely on fsync() plus write ordering to guarantee that a committed transaction survives a power loss, and both batch writes into a single fsync (group commit) because one fsync on spinning disk can cost 5-20 milliseconds. Skipping the barrier between two dependent writes is exactly how the classic 'transfer $100, crash mid-way, money disappears' bug happens in real financial software.
Before you call the implementation done, walk failure modes on purpose. Test empty structures, single-element edge cases, maximum concurrency, and errno paths that must not crash the program. OS code usually fails in production when happy-path tests pass but invariants break under contention or memory pressure.
Keep structures small and name fields after kernel counterparts when possible. That lets you read man pages and kernel source side by side while you work. Print observable events during development; remove noisy logs once tests pass reliably.
You will trace a write with and without fsync using strace or your block cache, and explain which crash windows remain. The task asks you to pair journal commits with barrier semantics from the journaling task.
Model what survives a power failure. Writes land in the page cache, and only fsync makes a file's data durable. Creating or renaming a file changes its directory, and that change is only durable after the directory itself is fsynced. Simulate the page cache, the durable disk and a crash, and show why the classic safe-save sequence is write temp → fsync temp → rename → fsync dir. Also count fsync latency, to show why databases batch commits (group commit).
Files have an identity (#1, #2, … in creation order) separate from their names, like inodes.
| Command | Effect on the cache view | Output |
|---|---|---|
create NAME | new empty file #k named NAME (replacing whatever NAME pointed to) | create NAME -> #k |
write NAME TEXT... | append TEXT (rest of the line after one space) to the file | write NAME: N bytes cached |
fsync NAME | make the file's current data durable (not its name) | fsync NAME: data durable (C us) |
rename OLD NEW | NEW now names OLD's file; OLD disappears (atomic replace) | rename OLD -> NEW |
fsyncdir | make the current directory mapping durable | fsyncdir: N entries durable (C us) |
show | print the cache view | view: a.txt=#1 "abc", ... |
crash | discard everything not durable, then print what the disk holds | after crash: a.txt=#1 "ab", ... |
Every fsync/fsyncdir costs LAT microseconds, set by a first line latency LAT. C is the running total. Files and entries print in name order. A file that is named durably but whose data was never fsynced survives a crash empty, and an unnamed file is lost. view: empty / after crash: empty when there are no names.
Errors: write/fsync/rename of a missing name → COMMAND NAME: ENOENT.
The final line is total: F fsyncs, T us.
Input:
cLoading…
Output:
cLoading…
The rename was never made durable, so the old version survives, and survives intact. Hidden tests show the other orders: rename without fsyncing the new file's data (the name points at an empty file after the crash), and the fully safe sequence.
crash replaces the view with the disk state.fsync copies a file's data only; fsyncdir copies the name mapping only.Hidden tests cover the complete safe-save sequence, a rename whose data was never fsynced, a new file that was fsynced but whose directory entry wasn't, many small writes committed with one fsync compared with one fsync per write, and ENOENT errors.