pub enum FileOp {
Write {
path: PathBuf,
bytes: Vec<u8>,
},
Rename {
from: PathBuf,
to: PathBuf,
},
Remove {
path: PathBuf,
},
CopyFrom {
path: PathBuf,
source: PathBuf,
},
SetExecutable {
path: PathBuf,
executable: bool,
},
SetLink {
path: PathBuf,
target: PathBuf,
},
}Expand description
One staged filesystem operation. Paths are root-relative — the root
is joined on at apply time, so a set is portable
between trees and prints readably in a dry run.
Variants§
Write
Write bytes to path, creating it (and any missing parent directory)
or replacing it wholesale.
Rename
Move from to to, creating any missing parent directory of to.
Remove
Remove the file at path. It must exist.
CopyFrom
Copy the bytes already on disk at source to path, verbatim.
Write with the payload left where it lies. The journal
records the source path instead of the bytes, so a set that writes a
large payload costs O(path) of journal rather than a second copy of every
byte — which is what makes putting a whole captured tree back
tractable, where Write would duplicate every byte of it into the
journal at the commit point.
The source must be immutable for the lifetime of the change, because
that is the entire correctness argument. A Write journals the exact bytes
it intends, so replay after a crash is deterministic by construction; a
CopyFrom journals a reference, and replay is deterministic only if the
referent cannot have changed underneath it. A content-addressed blob
satisfies this by definition — its path is the digest of its
contents, so bytes found there are the bytes intended, or the file is gone
and replay fails loudly. Do not point this at a mutable file, at a
path some other op in the same set writes, or at anything outside the
root.
This bounds journal growth, not peak memory: rollback still buffers the
bytes it overwrites, exactly as Write does.
Fields
SetExecutable
Make the file at path runnable, or not — one bit, not a mode.
Lands through Storage::set_executable, whose default is the honest
no-op for a backend with no such bit: over such a backend the op
“applies” as nothing, which is the same nothing the bit’s absence
already means there. Undo is captured through
ReadStorage::executable, so a
bit that was already in the requested state rolls back to itself rather
than to its opposite.
A path holding a symbolic link is refused
(InvalidInput), whoever made the
link: mode writes follow links, so the bit would land on the link’s
referent — wherever it points, the lexical root guard notwithstanding.
Fields
SetLink
Place a symbolic link at path pointing at target, replacing
whatever is there.
The target is recorded, never resolved: it may point outside the
root, at nothing, or at another link, and staging it writes nothing
through it — the same terms as Storage::set_link. What is not
permitted is another op in the same set addressing a path that
traverses this link: the root guard is lexical, and a set that writes
through its own fresh link is writing wherever the link points.
Over a backend that models no links the op is refused
(Unsupported) and the set unwinds:
unlike an execute bit, a link has no honest substitute.