Preserve symlinks and old-snapshot immutability (#9)
* Preserve symlinks and old-snapshot immutability Two real bugs found while implementing this item, both in fs.py: - copy_direntry() checked entry.is_dir() (default follow_symlinks=True) before entry.is_symlink(). A symlink pointing at a directory was misclassified as a directory and an empty real directory was created in its place, silently destroying the symlink and losing its target entirely. Fixed by checking is_symlink() first, and is_dir() with follow_symlinks=False. - rsync()'s permission/ownership-sync branch called os.chmod/os.chown on dst_entry.path unconditionally. Two problems: (1) for a symlink whose target already matched, this had no follow_symlinks=False guard and could rewrite an external symlink target's permissions; (2) for a regular file, that path may still be hardlinked to the same file in the previous, completed snapshot (hardlink_dir() links unchanged files in at the start of every backup) - mutating it in place silently corrupted every older snapshot sharing the inode. Fixed by skipping symlinks entirely (nothing to sync once their target matches) and adding _break_shared_hardlink(), a copy-on-write step that gives a file its own inode before any in-place metadata change. Also fixed the dir-mtime-restore loop's is_dir() call (same default-follow_symlinks issue, lower severity since its own operations were already lstat-based/no-follow). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Address Copilot review: preserve ownership/timestamps when breaking hardlinks, fix test fd leak - _break_shared_hardlink() now explicitly restores the original file's uid/gid and atime/mtime on the new private copy before it replaces the shared one. copy_file() only carries over mode at create time, so without this a caller that only meant to update permissions would silently also reset ownership/mtime as a side effect of the copy-on-write break itself. Also cleans up the temp file on failure and uses a less collision-prone temp name. - Fixed a file descriptor leak in the new hardlink-immutability test (bare open(...).read() without a context manager) - Strengthened the same test to assert mtime survives the break 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:
4 files changed
+173
-9
No files matched your search
+4
-4
@@ -43,10 +43,10 @@ Acceptance criteria:
|
||||
|
||||
### Preserve symlinks and old-snapshot immutability
|
||||
|
||||
- [ ] Classify entries with `lstat()` and process symlinks before directories.
|
||||
- [ ] Never follow symlinks while applying symlink metadata.
|
||||
- [ ] Break shared hardlinks before changing mode, ownership, flags, or times.
|
||||
- [ ] Define copy-on-write invariants for every mutation.
|
||||
- [x] Classify entries with `lstat()` and process symlinks before directories.
|
||||
- [x] Never follow symlinks while applying symlink metadata.
|
||||
- [x] Break shared hardlinks before changing mode, ownership, flags, or times.
|
||||
- [x] Define copy-on-write invariants for every mutation.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
|
||||
Reference in new issue
Block a user