72 lines
3.5 KiB
Markdown
72 lines
3.5 KiB
Markdown
# Internal: File Operations
|
|
|
|
`internal/fileops` owns the narrow mechanics for durable replacement of one
|
|
byte file. Callers keep ownership of serialization, validation, cancellation,
|
|
and destination-directory policy.
|
|
|
|
## Destination Confinement
|
|
|
|
Before it creates, replaces, or installs a destination file, `fileops` opens
|
|
each ancestor from the filesystem root and rejects symbolic links or components
|
|
that change during traversal. The resulting parent-directory handle is retained
|
|
for sibling temporary-file creation and rename, so a later pathname swap cannot
|
|
redirect the replacement. Existing destination symlinks are replaced as leaf
|
|
entries; their targets are never followed.
|
|
|
|
Remote object acquisition uses a writer supplied by the storage owner. The
|
|
writer receives a `fileops`-owned, already-open sibling temporary file rather
|
|
than a mutable destination path. Callers still own remote object selection,
|
|
validation, conflict handling, and final mode.
|
|
|
|
Directory promotion keeps the verified destination parent open while it creates
|
|
the temporary tree, copies regular source entries, and performs the platform
|
|
no-replace rename. Platforms without a verified handle-relative atomic
|
|
no-replace primitive reject promotion before writing a temporary tree.
|
|
|
|
## Cleanup Contract
|
|
|
|
`RemoveAllUnderRoot` accepts an explicit root and a proper descendant. It opens
|
|
the root and each target ancestor without following symlinks, then removes the
|
|
tree through those directory handles. It rejects root deletion and any symlink
|
|
encountered in the target path or tree; repeated removal of a missing target is
|
|
successful. Command and post-publish policy remains owned by `internal/app`.
|
|
|
|
## Confined Reads
|
|
|
|
`ReadRegularFileUnderRoot` is the no-follow, bounded read primitive for a
|
|
caller-selected root and relative file path; `ReadRegularFile` is its
|
|
path-based convenience wrapper. They verify every ancestor through directory
|
|
handles and admit only a stable regular-file handle. Callers enforce their own
|
|
byte limits and access policy. Credential mode policy and environment
|
|
precedence remain owned by `internal/app`.
|
|
|
|
## Replacement Contract
|
|
|
|
`ReplaceFileAtomic` requires an existing destination directory. It creates a
|
|
sibling temporary file, writes the complete byte sequence, applies the
|
|
caller-supplied mode, syncs and closes the file, runs an optional pre-rename
|
|
check, replaces the destination with a rename, then syncs the containing
|
|
directory.
|
|
|
|
The pre-rename check is the last point at which a caller can cancel without
|
|
installing a new destination. A failure before the rename leaves the old
|
|
destination unchanged and removes the temporary file; any cleanup failure is
|
|
returned alongside the primary failure. A failure after the rename may leave
|
|
the new file visible, but it is not reported as crash-durable.
|
|
|
|
Replacement follows the operating system's same-filesystem rename semantics.
|
|
If a platform cannot replace an existing destination, the operation returns an
|
|
error and never removes the old file as an emulation step.
|
|
|
|
## Directory-Sync Support
|
|
|
|
Linux and macOS attempt to sync the destination directory. Windows opens the
|
|
directory with backup semantics and flushes its buffers. If either operation
|
|
is unavailable for the platform, directory handle, or filesystem,
|
|
`ErrDirectorySyncUnsupported` is returned. Narratio does not treat that result
|
|
as successful crash-durable replacement.
|
|
|
|
`WriteFileAtomic`, copy helpers, and downloaded temporary-file installation
|
|
retain their compatibility behavior of creating the destination parent with
|
|
the repository's workspace permissions before using this contract.
|