vfs

package
v1.9.0 Latest Latest
Warning

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

Go to latest
Published: Oct 21, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package vfs is graphdb's filesystem driver interface.

Every file operation graphdb performs goes through a FileSystem. The default is OS(), which calls the os package directly and is what ships. A caller may install a different one — to inject I/O errors, to simulate a power cut, to serve a store from memory — and graphdb's real code paths then run against it.

Why this exists in production code

graphdb's earlier fault-injection seams lived in _test.go files: an unexported interface that only a test ever substituted. That tests a specially-constructed object rather than the artifact that ships, which is the distinction SQLite draws between its TCL suite ("a source code test, not an object code test, because it's not testing exactly the same code that you're delivering") and TH3. SQLite's answer is sqlite3_vfs_register: the OS interface is replaceable through published API present in every production build, so its I/O-error, crash and power-loss tests drive the shipped library. This package is the same idea.

A second consequence matters as much: a downstream consumer can inject faults into ITS use of graphdb, which a test-only seam can never offer.

Stability

FileSystem and File are a published interface. Adding a method to either is a breaking change for any implementation outside this repository, so treat them with the same care as the on-disk formats described in CLAUDE.md.

Index

Constants

View Source
const DirSyncSupported = dirSyncSupported

DirSyncSupported reports whether SyncParentDir does real work on this platform. It is false where a directory handle cannot be synced.

A test that asserts a directory sync happened must skip when this is false. Without the constant such a test fails on that platform, and the failure says the code is wrong when the platform is simply different.

Variables

View Source
var ErrMapUnsupported = errors.New("vfs: driver cannot map this file")

ErrMapUnsupported is returned by a Mapper that cannot map this file, or cannot map at all on this platform. MapFile treats it as "use the Open path instead", so it is a routing signal and not a failure.

Functions

func MapFile

func MapFile(fs FileSystem, name string) ([]byte, func() error, error)

MapFile returns the whole contents of name.

It uses fs's Mapper when it has one, and reads the file through Open when it does not, so a driver written before Mapper existed keeps working. The returned release must be called exactly once.

func Register

func Register(name string, fs FileSystem) error

Register installs a driver under a name. Registering a name twice replaces the previous driver, which is what a test that installs a fresh fault driver per case wants.

Registering over "os" is refused: it is the default every caller falls back to, and a test that replaced it globally would change the behaviour of every other test in the same binary.

func SyncParentDir

func SyncParentDir(fsys FileSystem, path string) error

SyncParentDir makes the directory entry for path durable.

graphdb publishes durable state with write-temp, sync-file, rename. POSIX makes the rename atomic, so a reader sees the old name or the new one and never a half-written file. Atomic is not the same as durable. The new name is an entry in the parent directory, and syncing the FILE writes the file's data and inode, not the directory block that holds the name. A power cut after the rename can therefore lose the entry: the bytes are on the disk and nothing points at them.

fsync on the parent directory is what publishes the name. Call this after every rename that publishes state, and after creating a file whose existence must survive a crash.

Report the error. A caller that ignores it has the durability gap it had before the call, and no way to know.

func Unregister

func Unregister(name string)

Unregister removes a driver. It is a no-op for a name that is not registered, and it refuses to remove "os".

Types

type File

type File interface {
	io.Reader
	io.Writer
	io.Seeker
	io.Closer

	ReadAt(p []byte, off int64) (n int, err error)
	WriteAt(p []byte, off int64) (n int, err error)

	// Sync commits the file's contents to stable storage. A driver that
	// simulates a power cut discards everything not yet passed to Sync.
	Sync() error

	// Truncate changes the file's size.
	Truncate(size int64) error

	// Name returns the name the file was opened with.
	Name() string
}

File is an open file.

The method set is the intersection of what pkg/wal, pkg/lsm, pkg/btree and pkg/storage actually use, deliberately: a wider interface is harder to implement correctly in a fault driver, and every method here is one a wonky implementation has to get right.

type FileSystem

type FileSystem interface {
	// Open opens a file with the semantics of os.OpenFile.
	Open(name string, flag int, perm os.FileMode) (File, error)

	// Remove deletes a file, with the semantics of os.Remove.
	Remove(name string) error

	// Rename moves a file, with the semantics of os.Rename. graphdb relies on
	// this being atomic on POSIX for snapshot and WAL rotation, so an
	// implementation that cannot offer atomicity must say so in its docs.
	Rename(oldpath, newpath string) error

	// Stat returns file metadata, with the semantics of os.Stat.
	Stat(name string) (os.FileInfo, error)

	// MkdirAll creates a directory tree, with the semantics of os.MkdirAll.
	MkdirAll(path string, perm os.FileMode) error

	// ReadDir lists a directory, with the semantics of os.ReadDir.
	//
	// Added when pkg/lsm became the second caller: it enumerates its SSTables,
	// and a driver that cannot fail a directory listing cannot test what
	// happens when that listing fails. The method set is the intersection of
	// what callers actually use, so the intersection grew.
	ReadDir(name string) ([]os.DirEntry, error)

	// Name identifies the driver, for diagnostics and for tests that assert
	// which driver is installed. "os" is the default.
	Name() string
}

FileSystem is the set of filesystem operations graphdb performs.

An implementation must be safe for concurrent use: graphdb opens and syncs files from several goroutines.

func Default

func Default() FileSystem

Default returns the driver to use when a caller names none. It is always the OS driver: a store that did not ask for a fault driver must never get one.

func Get

func Get(name string) (FileSystem, bool)

Get returns the driver registered under a name.

func OS

func OS() FileSystem

OS returns the filesystem driver graphdb uses unless told otherwise.

func Resolve

func Resolve(name string) (FileSystem, error)

Resolve turns a configured name into a driver. An empty name yields the default. An unknown name is an error rather than a silent fallback, because falling back would run a test against the real filesystem while the test believed it had installed a fault driver — a pass that means nothing.

type Mapper

type Mapper interface {
	// Map returns the file's entire contents and a function that releases them.
	//
	// The bytes stay valid until release is called, and the caller must not
	// retain them afterwards. Callers must call release exactly once, even
	// when they only read part of the data.
	//
	// A driver that cannot map returns ErrMapUnsupported, and the caller falls
	// back to reading the file through Open.
	Map(name string) (data []byte, release func() error, err error)
}

Mapper is an OPTIONAL capability. A FileSystem may implement it to expose a file's whole contents as one byte slice.

It is deliberately not a method on FileSystem. FileSystem is a published interface, and adding a method to it breaks every implementation outside this repository (see the Stability note on this package). An optional interface costs a driver that does not want it nothing.

Why graphdb needs it

The mmap snapshot reader calls syscall.Mmap on a file descriptor. File has no Fd method and should not grow one: a fault driver has no descriptor to give, so an Fd-shaped seam would let the OS driver through and lock every other driver out — which is the same test-only seam this package exists to remove.

Mapping is the operation the reader actually needs, so that is what the interface exposes. The OS driver satisfies it with mmap. A fault driver satisfies it by returning a buffer it chose, which is how a truncated or corrupt snapshot reaches the production read path without a corrupt file ever existing on disk. SQLite makes the same move with xFetch on sqlite3_io_methods.

Directories

Path Synopsis
Package extfs adapts an EXTERNAL filesystem — one graphdb does not own and does not import — onto pkg/vfs.FileSystem, so a third-party fault-injection or crash-simulation library can drive graphdb's real write paths.
Package extfs adapts an EXTERNAL filesystem — one graphdb does not own and does not import — onto pkg/vfs.FileSystem, so a third-party fault-injection or crash-simulation library can drive graphdb's real write paths.
Package vfstest provides filesystem drivers that misbehave on purpose.
Package vfstest provides filesystem drivers that misbehave on purpose.

Jump to

Keyboard shortcuts

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