feat(p7-times): real directory/symlink time preservation; -O/-J meaningful
Wave D of Phase 7. Make -O/--omit-dir-times and -J/--omit-link-times real by preserving directory and symlink times, and mark --secluded-args as an explicit Impossible/Divergence no-op. Wire: PROTOCOL_VERSION 2.16.0 -> 2.17.0. Adds a terminal STATUS_DIR_TIMES frame (int count + (wire path, metadata) pairs) sent after all file data and the optional delete manifest. STATUS_MKDIR also carries metadata for --dirs entries. Config-frame layout is unchanged. Sender: the recursive scanner captures every traversed source directory (both DirectoryScanner and the parallel scanner root + workers, appends mutex-guarded) into a shared list; the single-threaded and -m paths transmit it last. Receiver: a DirTimeList accumulates received directory metadata and applies it with fd-relative no-follow utimensat only at the very end -- after all children, after the commit-style --delete, and after --delay-updates publication -- in the single-threaded success frame and in server.c after the -m threads join. -O skips the application. Symlink metadata is applied at link creation with utimensat/fchownat/fchmodat AT_SYMLINK_NOFOLLOW; -J suppresses only link times. identity_apply_ownership_link shares the identity resolver with the fd path. Docs: -O/-J rows -> Implemented; --secluded-args -> Impossible/Divergence; --protocol accepted/rejected values and Phase-6/7 notes updated. Tests: unit (scanner dir capture, DirTimeList apply, symlink metadata, protocol version values) and integration (dir mtime round-trip + -O, symlink mtime round-trip + -J, independent suppression), parameterized over single/multithread.
This commit is contained in:
@@ -302,6 +302,39 @@ static void test_chmod_changes() {
|
||||
EXPECT_FALSE(chmod_apply(0777, "a+r,", &result));
|
||||
}
|
||||
|
||||
/* P7 Wave D: symlink metadata is applied with no-follow primitives, and -J
|
||||
* (omit_link_times) suppresses the timestamp. The test is robust to
|
||||
* filesystems that silently ignore symlink timestamps: it mainly proves the
|
||||
* omit path never touches the stored time. */
|
||||
static void test_file_restore_symlink_metadata() {
|
||||
const char* dir = "temp_symlink_md_test";
|
||||
const char* target = "temp_symlink_md_test/target";
|
||||
const char* link = "temp_symlink_md_test/link";
|
||||
EXPECT_EQ_INT(mkdir(dir, 0755), 0);
|
||||
FILE* f = fopen(target, "w");
|
||||
EXPECT_NOT_NULL(f);
|
||||
fputs("t", f);
|
||||
fclose(f);
|
||||
EXPECT_EQ_INT(symlink("target", link), 0);
|
||||
|
||||
FileMetadata applied = {.mtime_sec = 1000000000, .mtime_nsec = 0};
|
||||
file_restore_symlink_metadata(link, &applied, false);
|
||||
struct stat st;
|
||||
EXPECT_EQ_INT(lstat(link, &st), 0);
|
||||
EXPECT_TRUE(S_ISLNK(st.st_mode));
|
||||
time_t t1 = st.st_mtime;
|
||||
|
||||
/* -J: a different time must be left untouched. */
|
||||
FileMetadata newer = {.mtime_sec = 1234567890, .mtime_nsec = 0};
|
||||
file_restore_symlink_metadata(link, &newer, true);
|
||||
EXPECT_EQ_INT(lstat(link, &st), 0);
|
||||
EXPECT_EQ_INT((int)st.st_mtime, (int)t1);
|
||||
|
||||
unlink(link);
|
||||
unlink(target);
|
||||
rmdir(dir);
|
||||
}
|
||||
|
||||
void test_metadata() {
|
||||
test_metadata_to_from_buf_roundtrip();
|
||||
test_metadata_to_buf_null();
|
||||
@@ -315,5 +348,6 @@ void test_metadata() {
|
||||
test_file_restore_metadata_applies_atime();
|
||||
test_file_restore_executability_only();
|
||||
test_directory_restore_executability_only();
|
||||
test_file_restore_symlink_metadata();
|
||||
test_chmod_changes();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user