A successful fsync() on a regular file does not, by itself, guarantee that the directory entry naming that file has reached persistent storage. Linux documents this boundary explicitly: file synchronization covers the file’s data and associated metadata, while persistence of the containing directory entry requires an fsync() on a file descriptor for that directory.
This distinction matters when software creates a new file or atomically replaces an existing pathname. File contents and pathname metadata are separate pieces of filesystem state, and a crash can test the durability boundary between them.
File state and directory state are separate
A regular file is represented by an inode plus its data blocks and filesystem-specific metadata. A directory contains mappings from names to filesystem objects.
Writing bytes changes file state. Creating, unlinking, or renaming a pathname changes directory state. The operations are related, but they are not the same persistence unit at the system-call interface.
fsync(file_fd) requests synchronization of the file referred to by file_fd. Linux also flushes metadata associated with that file. The call does not necessarily force the directory block containing the file’s name to stable storage.
For a newly created file, this means durable contents do not automatically imply a durable name.
Atomic rename and crash durability are different properties
rename() provides an atomic namespace transition when its filesystem and path constraints permit the operation. Other processes do not observe a state in which an existing destination pathname simply vanishes between removal and replacement.
That atomicity describes visibility while the system is running. It does not state that the namespace transition has been forced to persistent media before rename() returns.
A common replacement sequence writes a temporary file, synchronizes that file, and renames it over the destination. The file synchronization protects the new file contents against pending writeback. The rename changes the directory entry atomically. Synchronizing the containing directory addresses persistence of that namespace change.
These are distinct guarantees:
write temporary file
|
v
fsync temporary file
|
v
rename temporary -> target
|
v
fsync containing directoryThe first synchronization concerns the replacement object’s data and file metadata. The final synchronization concerns the directory mutation that makes the replacement reachable through the target name.
A successful write is weaker than a successful fsync
Buffered writes can complete after data has been copied into kernel-managed cache without waiting for the storage device to persist it. Dirty pages can remain pending for later writeback.
fsync() closes part of that gap by requesting that modified file data and relevant metadata be transferred through the filesystem and storage stack according to the platform’s synchronization semantics.
The distinction is visible in error handling as well. Linux can report delayed writeback errors through a later fsync() call. Treating a successful write() as a durability acknowledgment skips that error boundary.
Storage hardware, device caches, filesystem behavior, and mount configuration still affect the final persistence model. fsync() is the application-facing primitive for requesting this synchronization; it cannot repair hardware that falsely reports completed persistence.
fdatasync has a narrower metadata contract
fdatasync() resembles fsync() but may omit metadata that is not required for subsequent data retrieval. A file-size update, for example, is relevant because the stored size affects which bytes can later be read. Some timestamp updates need not receive the same treatment.
This narrower contract can reduce metadata traffic for workloads that only require durable file contents.
It does not collapse file state and directory state into one operation. A directory entry created or changed around the file remains namespace metadata owned by the directory.
Replacement across directories expands the boundary
A rename can involve two parent directories. The source name is removed from one directory and the destination name is created or replaced in another.
That operation modifies both namespace containers. Software with strict crash-consistency requirements must account for every directory whose persistent state matters to the intended result.
Cross-filesystem moves are a different case. The rename() system call reports EXDEV when the source and destination are on different mounted filesystems and the operation cannot be represented as a rename. User-space tools may then implement a copy-and-delete sequence, which has different atomicity and durability properties.
The apparent operation called a “move” therefore does not imply one universal persistence mechanism.
Journaling can strengthen behavior without changing the API contract
Journaled filesystems often group related metadata updates into transactions. Specific filesystems can provide behavior stronger than the minimum application-visible contract in particular configurations.
Relying on incidental transaction coupling makes durability dependent on filesystem implementation details, mount options, kernel behavior, and storage topology. Code intended to express a persistence requirement directly should use the synchronization operations corresponding to the state it changed.
This also keeps the boundary visible during review: file synchronization protects the file object, while directory synchronization protects the namespace mutation.
Directory synchronization completes the namespace persistence request
A directory can be opened and its file descriptor passed to fsync() on Linux. After creating, unlinking, or renaming entries, that call requests synchronization of the directory changes.
The resulting sequence is not merely a defensive pattern around rename(). It mirrors the filesystem objects being modified. File data belongs to the file. The mapping from a pathname component to that file belongs to a directory.
Crash consistency depends on both when an application requires durable contents under a durable name. An atomic namespace operation can prevent observers from seeing an intermediate replacement state, while explicit synchronization establishes the separate persistence boundaries for the object and its name.