Skip to main content

Module commit

Module commit 

Source
Expand description

Atomic on-disk commit for a staged save token.

crate::save::resolve_commit_with is filesystem-free; this module supplies the real source re-read, the file-identity comparison that decides whether that re-read applies, and a per-destination lock. The replacement itself is not done here: it is rightkit_fs::AtomicWriter (temp sibling in the destination’s directory, flush, rename, directory sync), which this crate does not reimplement. Path authorization is the caller’s job.

§What this crate owns, and what it does not

Owned here: the destination lock, the source re-validation, resolving a symlink destination once, invalidating the other staged tokens, and the check described under “What a replacement keeps” below. Owned by rightkit-fs: the temp file, the flush, the rename (on Windows the POSIX-semantics std::fs::rename, never ReplaceFileW), the directory sync, and cleanup. A write that fails before the rename leaves the original file as it was and removes the temp file; there is no state in which neither version exists.

§Concurrency and aliases

A commit is “check the source, replace the destination, invalidate the siblings”. Two commits staged against the same source must not interleave those steps - each would read the original bytes, both would pass the check, and the later rename would silently discard the earlier edit. So every commit takes a lock on its destination’s identity first and holds it across all three steps; the second commit then finds its token invalidated (or the source changed) and fails with PdfEditError::WriteConflict, the same error a source changed by someone else gives.

Identity is not the path string. /tmp/./memo.pdf, a hard link, a symlink, and (on a case-insensitive volume) MEMO.pdf are the same file, and the source recheck must run for all of them. [FileIdentity] is the volume and file id read from the file itself (dev/ino on Unix, FILE_ID_INFO from an opened handle on Windows) together with the canonical path; a destination that does not exist yet is its canonical parent directory plus its file name. Where the platform cannot give an existing file an id, the commit is refused (fail closed) rather than compared by path alone.

A symlink destination is resolved once: the lock, the source recheck and the replacement all use the resolved path, so the rename replaces the file the link points at and never the link itself. The destination is resolved again after the lock is obtained; if it now resolves elsewhere (the link was retargeted while this commit waited) the commit fails with PdfEditError::WriteConflict.

The lock is in-process. Another program replacing the file between the recheck and the rename is narrowed by the recheck, not excluded.

§What a replacement keeps

A replacement is a new file in the same directory, so it carries what a new file there would carry, not what the old file had. Before any byte is written this module compares the new temp file with the destination and refuses the save (CommitError::Refused) when a difference it can see would weaken or change the file’s protection; the original is untouched. It sees only what std and rightkit-fs already expose, no platform call of its own:

PlatformRefused whenNot examined, and not kept
Unixowner, group or permission bits (0o7777) of the new file differ from the destination’sPOSIX ACLs, SELinux labels, extended attributes, file flags, hard links (siblings keep the old bytes), creation time
macOSas Unix; and a destination whose mode is owner-only (& 0o077 == 0) when it or the new file is not provably owner-only, which is how rightkit-fs reports an extended ACLthe ACL of a destination whose mode is wider than owner-only; extended attributes; file flags
Windowsthe read-only attribute or the EFS-encrypted attribute is setthe destination’s DACL (a protected or explicit ACL, its owner), alternate data streams, the hidden/system/compressed attributes, creation time: the new file takes the parent directory’s inherited ACL

“Not examined” means exactly that: such a save succeeds and the property is lost.

Enums§

CommitError
Unpreserved
What a replacement file would not keep of the file it replaces, found before anything was written. See “What a replacement keeps” in the module docs for what is, and is not, examined on each platform.

Functions§

commit_pdf_save
Consumes token, re-checks the on-disk source for same-file saves, and atomically writes the verified candidate to target (or back to the source path when None).
write_atomic
Writes bytes to target atomically through rightkit_fs::AtomicWriter: a temp file in the same directory is flushed and then renamed over the target, and the directory is synced, so readers observe either the old or the new file, never a mix, and a failure before the rename leaves the old file in place.