storage: fsync staging directory before atomic rename#922
Conversation
mtrmac
left a comment
There was a problem hiding this comment.
https://github.com/podman-container-tools/container-libs/blob/main/CONTRIBUTING.md#sign-your-prs please, we can’t really even look at contributions with unclear copyright status.
We now have sync=filesystem via #622 , isn’t that sufficient? What is missing?
e411848 to
986fac7
Compare
Thank you @mtrmac for your feedback. sync=filesystem helps, but doesn't fully close this gap: it's opt-in ( |
*shrug* so opt in.
That shouldn’t matter; the store is locked in the meantime, so there should be no concurrent accesses . And in case of power loss or the like, what governs is the layer database in |
Signed-off-by: Aditi Sahay <asahay@redhat.com> Co-authored-by: Cursor <cursoragent@cursor.com>
986fac7 to
1e62330
Compare
| // inode metadata, which is already covered once the parent | ||
| // directory is fsync'd below. Treating them like regular |
There was a problem hiding this comment.
What documents that fsync on a directory also syncs all nodes of its children?
PR Summary
Add
fsyncof overlay staging directory contents before the atomic rename thatcommits a new layer during image pull/unpack.
Replaces auto-closed containers/storage#2397
after the storage library moved into this monorepo.
Problem
This gap was reported against real customer deployments. Edge and
disconnected SNO customers (vehicle-mounted, tactical, and air-gapped
deployments with unstable power) hit
readlink: invalid argumenterrors andCRI-O crashes after hard resets, and requested a fix, since these nodes cannot
simply re-pull images from a registry that isn't reachable.
Root cause: when CRI-O (via
go.podman.io/storage) pulls an image, layer datais written into a staging directory and then committed with
os.Rename().Nothing forces that data to physical disk before the rename makes it visible
at its final path.
If power is lost in that window, the layer isn't just missing — it's present
but corrupted: binaries can end up truncated, and overlay
l/symlinks can beleft invalid, producing exactly the
readlink: invalid argumenterror reportedabove. On restart, CRI-O detects the unclean shutdown and
internal_repaircan't tell which layer is actually bad, so it falls back to a broad,
destructive repair — on a disconnected node, that means unrecoverable image
loss.
Solution
Guarantee durability before the rename ever exposes the new layer, so that
window can't exist regardless of
sync=filesystemconfiguration:fdatasync()every file under the staging directoryfsync()every directory under the staging tree, bottom-upos.Rename()This mirrors the pattern already used by
atomicFileWriter(
storage/pkg/ioutils/fswriters_linux.go) for single files, extended to awhole staging tree. It's scoped to just the layer being committed — much
cheaper than
Syncfs()'s whole-filesystem flushChanges
storage/pkg/ioutils/sync_directory_linux.goSyncDirectoryContents()helper (Linux)storage/pkg/ioutils/sync_directory_linux_test.gostorage/drivers/overlay/overlay.goApplyDiffFromStagingDirectory()Testing