durablefile

package
v0.10.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 11, 2026 License: Apache-2.0 Imports: 6 Imported by: 0

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

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

func (i Installer) MkdirAll(dir string, mode fs.FileMode) error

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

func (i Installer) SetModeOn(f *os.File, mode fs.FileMode) error

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

func (i Installer) SyncDirectory(dir string) error

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.

func (Installer) SyncFileHandle

func (i Installer) SyncFileHandle(f *os.File) error

SyncFileHandle flushes a file's contents and metadata.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL