Documentation
¶
Overview ¶
Package iox provides atomic file IO and file-locking primitives used by agentsync's apply pipeline.
Index ¶
Constants ¶
const AllowSymlinkDestEnv = "AGENTSYNC_ALLOW_SYMLINK_DEST"
AllowSymlinkDestEnv lets callers opt in to AtomicWrite following an existing symlink rather than refusing. Default behaviour refuses so a chezmoi-style dotfile symlink at the destination is not silently replaced with a regular file.
Variables ¶
var ErrSymlinkDest = errors.New("destination is a symlink")
ErrSymlinkDest is returned when dest is an existing symlink and the caller has not set AGENTSYNC_ALLOW_SYMLINK_DEST=1.
Functions ¶
func AtomicWrite ¶
AtomicWrite writes data to dest using a three-phase approach: write to a sibling .agentsync.tmp file (always created mode 0o600 so cleartext payloads — secrets, age TOML — never sit world-readable in the destination directory between create and rename), fsync it, rename(2) into place, chmod to the caller-requested mode, then fsync the parent directory. If the process crashes between phases, the destination is either the old content (rename did not run) or the new content (rename ran). Never partial.
If dest is an existing symlink, AtomicWrite refuses unless AGENTSYNC_ALLOW_SYMLINK_DEST=1. Replacing the symlink with a regular file via rename would silently break a chezmoi/Stow setup where the user's real source lives behind the link. With the env set, dest is resolved through the link first so the underlying file is updated in place and the symlink itself is preserved.
The parent-directory fsync ensures that on filesystems where the directory entry update is asynchronous (ext4 without data=journal, btrfs, and historically some NFS configurations), a power loss immediately after the rename does not revert the entry to point at the old inode. On platforms where opening a directory for fsync is not supported (notably Windows) the directory fsync is best-effort and silently skipped.
Parent directory is created if missing (with mode 0o755).
Types ¶
type Lock ¶
type Lock struct {
// contains filtered or unexported fields
}
Lock represents an acquired exclusive file lock. Release() drops it.
func AcquireLock ¶
AcquireLock takes an exclusive lock on path, blocking forever until it succeeds. The parent directory is created if missing.
func AcquireLockTimeout ¶
AcquireLockTimeout takes an exclusive lock on path, returning an error if the lock cannot be acquired within timeout.