Documentation
¶
Overview ¶
Package durablefile installs a file so that a power loss cannot leave anything else pointing at a name that is not there.
ONE ORDERING, IN ONE PLACE: metadata change, file sync, atomic rename, containing directory sync. Each step is obvious and the sequence is not, which is why it is a primitive rather than four lines every caller writes again.
THE STEP EVERYBODY FORGETS IS THE LAST ONE. fsync(2) says plainly that flushing a file does not flush the DIRECTORY ENTRY that names it, and that an explicit fsync on the containing directory is required. `billet images pull` installed a kernel with a sync, a chmod after that sync and a rename — and then published Ceph metadata naming the file. A crash in between leaves a complete, remotely visible generation whose paired kernel disappeared in recovery: nodes resolve a verified generation and cannot boot the exact kernel it was verified against, which is the matched-pair invariant the firecracker backend rests on.
THE MODE CHANGE COMES FIRST, before the sync rather than after it, because a sync flushes the inode as it is at that moment. A chmod behind it is a metadata change nothing has committed, so the file can come back with the mode the temporary file was created with — 0600, which is not readable by the account that boots a guest.
Index ¶
- type Installer
- func (i Installer) Install(dir, name string, mode fs.FileMode, write func(w io.Writer) error) (string, error)
- func (i Installer) MkdirAll(dir string, mode fs.FileMode) error
- func (i Installer) SetModeOn(f *os.File, mode fs.FileMode) error
- func (i Installer) SyncDirectory(dir string) error
- func (i Installer) SyncFileHandle(f *os.File) error
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Installer ¶
type Installer struct {
SetMode func(f *os.File, mode fs.FileMode) error
SyncFile func(f *os.File) error
Rename func(from, to string) error
SyncDir func(dir string) error
}
Installer performs the durable install, with a seam at each step.
THE ZERO VALUE IS THE REAL THING. A caller cannot accidentally construct one that does nothing, and a test overrides exactly the step it wants to fail. The hooks exist because the property that matters here — that a caller does not go on to publish something REMOTE when a local step failed — is only observable if a local step can be made to fail, and none of these can be provoked by ordinary means.
func (Installer) Install ¶
func (i Installer) Install( dir, name string, mode fs.FileMode, write func(w io.Writer) error, ) (string, error)
Install writes name into dir durably and returns the path it landed at.
The write callback receives the staged file and may refuse: an error from it aborts before anything is renamed and is returned unchanged, which is what lets a caller verify a digest as it copies rather than after the file has a real name.
WHAT IS LEFT BEHIND ON FAILURE, and it differs by step. Anything up to and including the rename leaves nothing under the final name — the staged file is removed, and no reader ever saw it. A failure of the DIRECTORY SYNC leaves the file where it is: the entry may already be durable, an unlink is not itself durable either, and the caller's retry re-checks the content and flushes the directory again. What must not happen — and is the whole point — is the caller treating that failure as success.
func (Installer) MkdirAll ¶
MkdirAll creates a directory and commits the entries that NAME it.
BECAUSE FLUSHING A DIRECTORY DOES NOT COMMIT THE DIRECTORY. Install flushes the entries INSIDE dir, which is what makes the file it just renamed durable — and says nothing about the entry for dir itself in its parent. On a fresh host the kernel directory does not exist until the first pull creates it, so without this a power loss can take the whole directory away and leave exactly the failure Install exists to prevent: a published generation naming a kernel that is gone.
EVERY ANCESTOR, EVERY TIME, INCLUDING WHEN THE DIRECTORY ALREADY EXISTS. A previous run may have created it and died before its parent was flushed, and a retry that skipped the flush would certify that state instead of repairing it — the same rule the "already installed" branch of an install follows. It is a handful of fsyncs on directories, once per operation.
func (Installer) SetModeOn ¶
SetModeOn and SyncFileHandle are the two file steps, exposed for a caller that is REPAIRING an artifact rather than installing one.
THE SAME SEAMS, SO THE REPAIR IS THE SAME OPERATION. A caller that reached for f.Chmod and f.Sync directly would be a second implementation of the ordering this package exists to hold, and a test could not fail it where it fails Install.
func (Installer) SyncDirectory ¶
SyncDirectory flushes a directory's entries, so a rename into it survives a crash.
EXPORTED BECAUSE THE REPAIR PATH NEEDS IT ALONE. A caller that finds the file already present — an interrupted run that renamed and then died — has nothing to install and everything still to commit, and returning success there would leave exactly the state Install exists to prevent, reachable by retrying.