vfs

package
v0.6.1 Latest Latest
Warning

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

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

Documentation

Overview

Package vfs defines the virtual filesystem contract that the shell, its commands, and all backends share. It depends on nothing but the standard library: it is the seam between the shell (which operates on a filesystem) and the backends (which implement one).

Index

Constants

View Source
const DefaultWriteConflictPolicy = PolicyHash

DefaultWriteConflictPolicy is the policy applied when none is configured.

Variables

View Source
var ErrReadOnly = fmt.Errorf("read-only filesystem")

ErrReadOnly is returned by mutating operations when the substrate is in read-only mode.

Functions

func CleanPath

func CleanPath(p string) string

CleanPath normalises a path to always start with / and removes . and .. segments.

func ErrIsDirectory

func ErrIsDirectory(p string) error

ErrIsDirectory is returned when a file operation is attempted on a directory.

func ErrNotDirectory

func ErrNotDirectory(p string) error

ErrNotDirectory is returned when a directory operation is attempted on a file.

func ErrNotFound

func ErrNotFound(p string) error

ErrNotFound is returned when a path does not exist.

func ValidateChangeSet added in v0.4.0

func ValidateChangeSet(cs ChangeSet) error

ValidateChangeSet rejects empty, mixed, and malformed singleton/batch values.

func WalkDir

func WalkDir(fsys FileSystem, root string, fn func(path string, info *FileInfo, err error) error) error

WalkDir walks the filesystem tree rooted at root, calling fn for each file or directory.

Types

type Change added in v0.4.0

type Change struct {
	Target         string             `json:"target"`
	Action         ChangeAction       `json:"action"`
	Write          *WriteChange       `json:"write,omitempty"`
	RemoveAll      *RemoveAllChange   `json:"remove_all,omitempty"`
	Xattr          *XattrChange       `json:"xattr,omitempty"`
	XattrRepair    *XattrRepairChange `json:"xattr_repair,omitempty"`
	XattrMigration *XattrMigration    `json:"xattr_migration,omitempty"`
}

Change is a non-recursive leaf in a batch ChangeSet.

type ChangeAction

type ChangeAction string

ChangeAction discriminates the kind of mutation a ChangeSet describes. Every mutating WritableFS method has a corresponding action, so every namespace mutation — file writes, directory creation, and removals — flows through the ordered log as a ChangeSet. Directories are first-class namespace entries (like a special filetype), so their creation and removal are logged operations too: this is what prevents a write from racing ahead of a remove (or landing on an already-removed path) — the single serialized applier orders them.

const (
	// ChangeActionWrite is a single-file whole-object write (WriteFileAtomic).
	ChangeActionWrite ChangeAction = "write"
	// ChangeActionMkdir creates a single directory (Mkdir).
	ChangeActionMkdir ChangeAction = "mkdir"
	// ChangeActionMkdirAll creates a directory and any missing parents
	// (MkdirAll).
	ChangeActionMkdirAll ChangeAction = "mkdir_all"
	// ChangeActionRemove removes a single file or empty directory (Remove).
	ChangeActionRemove ChangeAction = "remove"
	// ChangeActionRemoveAll is an atomic whole-tree removal (RemoveAll).
	ChangeActionRemoveAll                 ChangeAction = "remove_all"
	ChangeActionSetXattr                  ChangeAction = "set_xattr"
	ChangeActionRemoveXattr               ChangeAction = "remove_xattr"
	ChangeActionPreserveAndRecreateXattrs ChangeAction = "preserve_and_recreate_xattrs"
	ChangeActionMigrateXattrs             ChangeAction = "migrate_xattrs"
)

type ChangePreflighter added in v0.4.0

type ChangePreflighter interface {
	PreflightChange(Change) error
}

ChangePreflighter optionally rejects deterministic substrate-policy failures before an ordered batch applies its first leaf. It must not mutate state.

type ChangeSet

type ChangeSet struct {
	Target         string             `json:"target"`
	Action         ChangeAction       `json:"action"`
	Write          *WriteChange       `json:"write,omitempty"`
	RemoveAll      *RemoveAllChange   `json:"remove_all,omitempty"`
	Xattr          *XattrChange       `json:"xattr,omitempty"`
	XattrRepair    *XattrRepairChange `json:"xattr_repair,omitempty"`
	XattrMigration *XattrMigration    `json:"xattr_migration,omitempty"`
	Changes        []Change           `json:"changes,omitempty"`
	Moves          []Move             `json:"moves,omitempty"`
}

ChangeSet is an immutable, serializable description of either one mutation or an ordered, rollback-protected batch. A batch's aggregate committed hash is empty.

It is the single primitive a consumer persists (e.g. while a write awaits human approval) and later replays with CommitChangeSet. It deliberately carries no approver / status / capability / proposer fields: those are the consumer's policy concern, not part of the content-addressed change.

Action selects the payload: a write carries the exact proposed bytes plus its precondition contract; a remove_all carries the delete precondition. mkdir / mkdir_all / remove need only Target.

func (ChangeSet) Leaves added in v0.4.0

func (cs ChangeSet) Leaves() []Change

Leaves returns the changes in execution order, presenting a singleton as a one-element slice so authorization and plugin middleware can inspect both forms uniformly. Callers must treat the returned values as immutable.

type ChangeSetAdmitter added in v0.4.0

type ChangeSetAdmitter interface {
	AdmitChangeSet(ChangeSet) error
}

ChangeSetAdmitter is an optional session-filesystem capability for submitting one ordered batch through the same scope and admission pipeline as writes.

type CommitResult added in v0.4.0

type CommitResult struct {
	Hash      string
	Committed ChangeSet
}

CommitChangeSet replays cs against fs under the exact precondition contract it carries. A write commits its proposed bytes under its WriteOpts (unconditional / IfMatch / IfNoneMatch); a remove_all replays its whole-tree removal under its RemoveOpts; mkdir / mkdir_all / remove replay their namespace mutation. Compare-and-swap drift fails with *PreconditionError (write) or *TreeStaleError (remove_all). Before applying a batch, it snapshots every affected namespace root. If any leaf fails, prior leaves are rolled back before the error is returned.

It performs no authorization and captures no approver — the caller is responsible for deciding a ChangeSet may commit before calling this. For a singleton write it returns the hex SHA-256 of the committed bytes; every other action and every batch returns an empty hash.

func CommitChangeSet

func CommitChangeSet(fs WritableFS, cs ChangeSet) (result CommitResult, err error)

func (CommitResult) HasCommitted added in v0.4.0

func (r CommitResult) HasCommitted() bool

HasCommitted reports whether at least one mutation was durably applied.

type FileInfo

type FileInfo struct {
	FileName    string
	FilePath    string
	Content     []byte
	Dir         bool
	FileModTime time.Time
	FileSize    int64
}

FileInfo represents a file or directory in the virtual filesystem.

func Dir

func Dir(p string, modTime time.Time) *FileInfo

Dir is a convenience constructor for a directory FileInfo.

func File

func File(name, filePath string, content []byte, modTime time.Time) *FileInfo

File is a convenience constructor for a file FileInfo.

func (*FileInfo) IsDir

func (fi *FileInfo) IsDir() bool

func (*FileInfo) ModTime

func (fi *FileInfo) ModTime() time.Time

func (*FileInfo) Mode

func (fi *FileInfo) Mode() fs.FileMode

func (*FileInfo) Name

func (fi *FileInfo) Name() string

func (*FileInfo) Size

func (fi *FileInfo) Size() int64

func (*FileInfo) Sys

func (fi *FileInfo) Sys() interface{}

type FileSystem

type FileSystem interface {
	Stat(path string) (*FileInfo, error)
	ReadDir(path string) ([]FileInfo, error)
	ReadFile(path string) ([]byte, error)
}

FileSystem is the read-only filesystem interface that all commands operate on.

type Move added in v0.6.0

type Move struct {
	From int `json:"from"`
	To   int `json:"to"`
}

Move explicitly pairs a write leaf with the remove leaf that supplies its prior path. Indices refer to Changes and remove ambiguity when equal-content files are moved in the same batch.

type PathCanonicalizer added in v0.3.0

type PathCanonicalizer interface {
	CanonicalPath(path string) string
}

PathCanonicalizer is optionally implemented by filesystems that expose more than one virtual path for the same file. Commands use it when path identity matters, such as avoiding a move from an alias onto its canonical path.

type PendingChangeError

type PendingChangeError struct {
	ChangeSet ChangeSet
	Ref       string
}

PendingChangeError is returned by a mutating operation that an admission middleware intercepted and handed off instead of committing — for example, parked to await human approval. It is NOT a failure: the change was accepted as pending, so callers should report it informationally, not as an error.

ChangeSet is the captured (immutable) mutation; Ref is the consumer-owned handle for the pending change (e.g. a held-changeset id) that callers surface so it can be resolved later.

func (*PendingChangeError) Error

func (e *PendingChangeError) Error() string

type PreconditionError

type PreconditionError struct {
	Path    string
	Current string
}

PreconditionError is returned when a WriteOpts precondition (IfMatch / IfNoneMatch) is not satisfied. Current is the hash of the existing bytes (empty if the target does not exist).

func (*PreconditionError) Error

func (e *PreconditionError) Error() string

type ReadTracker

type ReadTracker interface {
	// LastReadHash returns the hex SHA-256 recorded when path was last read
	// (or written) in this session, and whether such a record exists.
	LastReadHash(path string) (hash string, seen bool)
}

ReadTracker is optionally implemented by a session filesystem that remembers the content hash of every file read during the session. It lets the write seam compare-and-swap a whole-file overwrite against the version the caller last saw — without the caller naming a hash — so an overwrite fails if the file changed since it was last read. A successful write updates the tracked hash so repeated writes in one session chain correctly.

type RemoveAllChange

type RemoveAllChange struct {
	Opts RemoveOpts `json:"opts"`
}

RemoveAllChange is the whole-tree removal payload of a ChangeSet: the precondition contract the removal commits under, carried verbatim as the caller's own RemoveOpts. Its zero value (Expected nil) is an unconditional removal; a non-nil Expected snapshot pins the compare-and-swap base captured at proposal time.

type RemoveOpts

type RemoveOpts struct {
	// Expected, when non-nil, requires the current visible subtree at the
	// target to match this snapshot exactly before the delete commits. Any
	// drift — a changed file, an added or removed descendant, a target that
	// vanished or changed kind — fails with a *TreeStaleError and deletes
	// nothing.
	Expected *TreeSnapshot
}

RemoveOpts carries the precondition contract for an atomic whole-tree delete. The zero value (Expected nil) means an unconditional delete.

type RollbackError added in v0.4.0

type RollbackError struct {
	CommitErr   error
	RollbackErr error
}

RollbackError reports both the leaf failure and a failure restoring the pre-batch snapshot. Callers can still use errors.Is/As for the commit error.

func (*RollbackError) Error added in v0.4.0

func (e *RollbackError) Error() string

func (*RollbackError) Unwrap added in v0.4.0

func (e *RollbackError) Unwrap() error

type TreeOp

type TreeOp struct {
	RelPath string
	Kind    string // "file" | "dir"
	Hash    string
	Size    int64
}

TreeOp describes one descendant within a TreeSnapshot. RelPath is relative to the snapshot Root (the root itself is RelPath "."). Kind is "file" or "dir". Hash is the hex SHA-256 of a file's content (empty for a directory); Size is the file's byte length (0 for a directory).

type TreeSnapshot

type TreeSnapshot struct {
	Root string
	Ops  []TreeOp
}

TreeSnapshot is an exact, order-independent description of a subtree, used as the compare-and-swap base for an atomic delete. Root is the cleaned target path the snapshot was taken at; Ops lists every descendant (files and directories) with paths relative to Root.

type TreeStaleError

type TreeStaleError struct {
	Path   string
	Detail string
}

TreeStaleError is returned by RemoveAll when the current subtree no longer matches opts.Expected — the tree drifted since it was snapshotted, so the delete was refused. Detail names the first divergence found.

func (*TreeStaleError) Error

func (e *TreeStaleError) Error() string

type WritableFS

type WritableFS interface {
	FileSystem

	// SetWriteable transitions the backend to writable. It returns an error
	// if the backend can't support writes at all (not if it is already
	// writable — that is an idempotent no-op success).
	SetWriteable() error

	// SetReadonly transitions the backend back to read-only. It is idempotent
	// and drains in-flight writes: a write that already passed its precondition
	// check is allowed to complete, and SetReadonly blocks until the substrate
	// is quiescent before returning.
	SetReadonly() error

	// WriteFileAtomic commits data to name as a single atomic whole-object
	// operation, honoring the WriteOpts precondition. It returns the hex
	// SHA-256 of the committed bytes. It errors when the backend is currently
	// read-only or when the precondition fails.
	WriteFileAtomic(name string, data []byte, opts WriteOpts) (newHash string, err error)

	// Mkdir creates a folder. It uses plain mkdir semantics (the parent must
	// exist) but errors if name is not strictly inside a docset — you cannot
	// create a docset, nor anything at or above a docset root, through the FS.
	Mkdir(name string) error

	// MkdirAll creates a folder and any missing ancestors (mkdir -p). Like
	// Mkdir it errors if name is not strictly inside a docset: the enclosing
	// docset root must already exist, and every folder it creates must sit
	// strictly below that root. An already-existing directory is not an error.
	MkdirAll(name string) error

	// Remove deletes a single file or empty directory at name. It errors if
	// name is a docset root or anything at or above one, and refuses a
	// non-empty directory (use RemoveAll). It participates in the readonly
	// lock like every mutation.
	Remove(name string) error

	// RemoveAll deletes name and everything under it as a single atomic
	// whole-tree operation, honoring opts.Expected as a compare-and-swap
	// precondition on the exact subtree. It errors if name is a docset root or
	// anything at or above one, and refuses the delete if the physical subtree
	// contains any descendant hidden or denied by file policy (so the atomic
	// rename cannot destroy invisible bytes). A mismatch against opts.Expected
	// returns a *TreeStaleError and removes nothing.
	RemoveAll(name string, opts RemoveOpts) error
}

WritableFS is the read+write filesystem interface. A backend only satisfies it if it can physically persist writes; embedded/read-only backends (embed.FS, plain fs.FS adapters) deliberately do not implement it.

Writes are whole-object and atomic by construction — there is no streaming, offset, or partial-write surface. The substrate's write capability is a stateful flag toggled by SetWriteable/SetReadonly; while read-only every mutating call returns an error.

type WriteChange

type WriteChange struct {
	Bytes []byte    `json:"bytes"`
	Opts  WriteOpts `json:"opts"`
}

WriteChange is the write payload of a ChangeSet: the exact proposed bytes plus the precondition contract they commit under. Opts is the caller's own WriteOpts, carried verbatim so a replay is byte-for-byte the write the caller requested — its zero value is an unconditional (last-write-wins) overwrite, an IfMatch pins a compare-and-swap base, and IfNoneMatch is create-only. This preserves every write mode through the log and through an approval hold.

type WriteConflictPolicy

type WriteConflictPolicy string

WriteConflictPolicy selects how a whole-file overwrite (the `>`, tee, sed -i and publish verbs) defends against concurrent writers. It does not affect append (`>>`) or patch, which are always compare-and-swap by construction.

const (
	// PolicyHash (the default) makes overwrites compare-and-swap: the write
	// carries an IfMatch precondition on the base the caller read, so a
	// concurrent change is rejected with a PreconditionError instead of being
	// silently clobbered. For a read-modify-write verb (sed -i) the base is the
	// content it transformed, giving true optimistic concurrency; for a blind
	// redirect the base is read at command time, so the guarantee narrows to
	// the command's own read→write window.
	PolicyHash WriteConflictPolicy = "hash"

	// PolicyLastWriteWins makes overwrites unconditional (the zero WriteOpts):
	// atomic, but the last writer silently wins. Opt-in per docset or globally.
	PolicyLastWriteWins WriteConflictPolicy = "last_write_wins"
)

func ParseWriteConflictPolicy

func ParseWriteConflictPolicy(s string) (WriteConflictPolicy, error)

ParseWriteConflictPolicy validates and normalizes a policy string. An empty string resolves to the default (hash). Unknown values are rejected.

type WriteOpts

type WriteOpts struct {
	// IfMatch, when non-nil, requires the current content hash of the target
	// to equal *IfMatch before the write commits. A nonexistent target fails
	// (you cannot match a hash that has no bytes). This is the hex SHA-256 of
	// the exact bytes ReadFile returns.
	IfMatch *string

	// IfNoneMatch, when true, requires the target to not already exist
	// (create-only). Fails with a conflict if it does.
	IfNoneMatch bool

	// ContentPolicyValidated is set only by trusted, narrowly scoped ingress
	// adapters after they enforce their own extension/MIME and request limits.
	// Substrates bypass their ordinary extension and per-write size policies for
	// this leaf; admission and precondition checks still apply.
	ContentPolicyValidated bool
}

WriteOpts carries the precondition contract for an atomic write. The zero value means "unconditional overwrite" (last-write-wins, but still atomic).

Precedence: IfNoneMatch (create-only) is mutually exclusive with IfMatch.

type WriteScopeFS

type WriteScopeFS interface {
	CanWrite(path string) bool
}

WriteScopeFS is optionally implemented by a session filesystem that confines writes to a subset of paths. CanWrite reports whether a write to path would be permitted by the session's scope, without performing one — used for fail-fast checks (e.g. `spawn` validating its target before scheduling a job). It says nothing about content preconditions or approval gating, only scope.

type XattrChange added in v0.4.0

type XattrChange struct {
	Name  string     `json:"name"`
	Value []byte     `json:"value,omitempty"`
	Flags XattrFlags `json:"flags,omitempty"`
}

type XattrEdit added in v0.4.0

type XattrEdit struct {
	Name   string `json:"name"`
	Value  []byte `json:"value,omitempty"`
	Remove bool   `json:"remove,omitempty"`
}

type XattrFlags added in v0.4.0

type XattrFlags uint32

XattrFlags controls creation/replacement of an extended attribute.

const (
	XattrCreate XattrFlags = 1 << iota
	XattrReplace
)

func (XattrFlags) Valid added in v0.4.0

func (f XattrFlags) Valid() bool

type XattrMaintenance added in v0.4.0

type XattrMaintenance interface {
	PreserveAndRecreateXattrs(path string, attrs map[string][]byte) error
	MigrateXattrs(path string, migration XattrMigration) error
}

XattrMaintenance is the focused internal storage capability used by queued recovery and plugin-schema migrations. It is intentionally separate from the public set/remove surface.

type XattrMigration added in v0.4.0

type XattrMigration struct {
	NamespacePrefix        string      `json:"namespace_prefix"`
	ExpectedEnvelopeSHA256 []byte      `json:"expected_envelope_sha256"`
	Edits                  []XattrEdit `json:"edits"`
}

type XattrReader added in v0.4.0

type XattrReader interface {
	GetXattr(path, name string) ([]byte, error)
	ListXattrs(path string) ([]string, error)
}

XattrReader and XattrWriter are optional capabilities. Implementations which cannot store attributes should return syscall.ENOTSUP.

type XattrRepairChange added in v0.4.0

type XattrRepairChange struct {
	Attributes map[string][]byte `json:"attributes"`
}

type XattrStaleError added in v0.4.0

type XattrStaleError struct{}

XattrStaleError indicates that a migration's exact-envelope precondition no longer matches. Callers may use errors.As to rediscover and retry.

func (*XattrStaleError) Error added in v0.4.0

func (*XattrStaleError) Error() string

type XattrWriter added in v0.4.0

type XattrWriter interface {
	SetXattr(path, name string, value []byte, flags XattrFlags) error
	RemoveXattr(path, name string) error
}

Jump to

Keyboard shortcuts

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