Documentation
¶
Index ¶
- func CopyFile(ctx context.Context, src, dst string) error
- func CopyWithContext(ctx context.Context, dst io.Writer, src io.Reader, buf []byte) (int64, error)
- func Exists(path string) (bool, error)
- func HashFile(ctx context.Context, path string) (string, error)
- func IsUnderDir(path, dir string) (bool, error)
- func SyncDir(dir string) error
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CopyFile ¶
CopyFile copies the file at src to dst, replacing dst if it exists.
The copy is atomic: the content is written to a temporary file in the directory of dst, synced, and then renamed into place. So dst is never seen half-written, and if the copy fails or ctx is canceled dst is left as it was and nothing is left behind. Because dst is replaced and not written to, it is also fine for src and dst to be the same file, and a dst that is a symlink is replaced by the copy and not followed.
The new file has mode 0644, whatever the mode of src and the umask. Once it is in place the directory is synced too, so that the new name survives a crash and not only the content (best effort, see SyncDir).
func CopyWithContext ¶
CopyWithContext copies bytes from src to dst using the provided buffer, periodically checking ctx for cancellation.
It behaves similarly to io.CopyBuffer, but allows the caller to cancel long-running copy operations (e.g., very large archives) via context.
The function:
- Reads into the provided reusable buffer (no allocations inside the loop)
- Writes each chunk fully before proceeding
- Returns the total number of bytes successfully written
- Stops early if ctx is canceled
This is useful when ingesting large blobs where we want the CLI to remain interruptible (Ctrl+C, timeouts, etc.) without relying on OS-level signals to interrupt a blocking read.
func Exists ¶
Exists reports whether there is a file (or directory, or anything else) at path, following symlinks: a symlink whose target is missing doesn't exist.
Only a path that isn't there is reported as not existing. Failing to find out for any other reason (a parent directory that can't be searched, or that is a file, an I/O error) is returned as an error: not knowing is not the same as the answer being no, and treating it that way would let callers go on to, for instance, overwrite a file without backing it up.
func HashFile ¶
HashFile computes the sha256 digest of the file at path and returns it as a lowercase hex string. It stops early if ctx is canceled, which matters when hashing very large files (such as archives) in a command that has to stay interruptible.
HashFile is safe to call from several goroutines at once: every call reads through a buffer of its own, sized to the file, and shares no state with any other call. Please keep it that way (for instance, don't cache a buffer between calls to save allocations).
func IsUnderDir ¶
isUnderDir reports whether the given path resides within the directory dir.
Both path and dir are first converted to absolute paths to avoid surprises with relative paths. The function then computes the relative path from dir to path using filepath.Rel.
If the resulting relative path starts with "..", the path lies outside dir. Otherwise, it is considered inside dir (including the case where path == dir).
This avoids unsafe string-prefix checks such as:
strings.HasPrefix(path, dir)
which can produce false positives (e.g. "/foo/bar-baz" vs "/foo/bar") and does not correctly handle ".." traversal.
The function does not resolve symlinks. If symlink-aware containment checks are required, both paths should be resolved via filepath.EvalSymlinks first.
func SyncDir ¶
SyncDir calls fsync(2) on a directory to ensure that metadata changes within that directory are durably persisted to disk.
Why this is needed: After renaming a file into its final location (os.Rename), the file’s contents are durable (because we fsync’d the temp file), but the directory entry itself may still be sitting in the kernel’s metadata buffers. If the system crashes at that exact moment, the file could theoretically disappear after reboot even though the rename returned successfully.
By opening the directory and calling Sync() on it, we force the directory metadata (including the new filename entry) to be flushed to stable storage.
This is best-effort: some filesystems may ignore directory fsync or relax guarantees, but on modern Linux filesystems (ext4, xfs, btrfs) this provides proper crash-consistency for atomic rename patterns.
It is intentionally non-fatal in callers because durability is strongly desired but not worth aborting the operation if unsupported.
Types ¶
This section is empty.