Prevent stale, truncated, or changing-file copies (#10)
* Prevent stale, truncated, or changing-file copies - copy_file() now loops until every byte is actually written (os.write may write fewer bytes than given) and compares fstat() on the source's own open descriptor before/after the read loop - this reflects the underlying inode directly, catching a source file truncated, grown, or overwritten in place while being read, and isn't fooled by the source path being renamed away mid-copy. Raises BackupCreationError (already a fatal, staging-discarding condition in initiate_backup) on a mismatch. - copy_file() returns a sha256 hex digest of the copied content; copy_direntry()/update_direntry() propagate it; rsync() carries it through the previously-unused msg field for CREATE/REWRITE actions. - initiate_backup() collects those digests into a checksums dict keyed by path relative to the snapshot root, recorded in the completion manifest (already JSON since the topology-validation work). - Added verify_snapshot(): re-hashes every file with a recorded checksum and reports any mismatch, satisfying the release gate's "completed snapshot passes manifest verification" requirement. - Switched every mtime comparison and os.utime() restore (rsync's file/directory sync, hardlink_dir's directory metadata, the hardlink-breaking copy-on-write step) from float seconds to nanoseconds, so a same-second modification can't be missed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Address Copilot review: hash checksums for external rsync too, fix log/error text - initiate_backup() now hashes CREATE/REWRITE files itself when msg is empty (fs.rsync_ext(), the external-rsync path, never yields a digest). Without this, verify_snapshot() silently became a no-op for every snapshot created with external_rsync=True. - Fixed a pre-existing log message that said "different inodes" for the branch whose actual condition is src/dst sharing an inode. - verify_snapshot()'s error message now also mentions a corrupt manifest, not just missing/pre-migration, matching what _read_manifest() actually distinguishes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Maxim Shulga <maxim.shulga@alludo.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
5 files changed
+326
-46
No files matched your search
+5
-5
@@ -74,11 +74,11 @@ Acceptance criteria:
|
||||
|
||||
### Prevent stale, truncated, or changing-file copies
|
||||
|
||||
- [ ] Handle partial writes correctly or use a proven copy primitive.
|
||||
- [ ] Use nanosecond timestamps and a documented integrity strategy.
|
||||
- [ ] Add checksum verification, either always or through an explicit mode.
|
||||
- [ ] Stat files before and after copying and retry or fail if they changed.
|
||||
- [ ] Record integrity information in the completion manifest.
|
||||
- [x] Handle partial writes correctly or use a proven copy primitive.
|
||||
- [x] Use nanosecond timestamps and a documented integrity strategy.
|
||||
- [x] Add checksum verification, either always or through an explicit mode.
|
||||
- [x] Stat files before and after copying and retry or fail if they changed.
|
||||
- [x] Record integrity information in the completion manifest.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
|
||||
Reference in new issue
Block a user