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:
@@ -1,5 +1,6 @@
|
||||
#include "test_file.h"
|
||||
#include "file.h"
|
||||
#include "file_receive.h"
|
||||
#include "data.h"
|
||||
#include "config.h"
|
||||
#include "utils.h"
|
||||
@@ -1216,6 +1217,39 @@ void test_trust_sender() {
|
||||
file_set_authorized_root(-1, NULL);
|
||||
}
|
||||
|
||||
/* P7 Wave D: the deferred directory-time list deep-copies entries and applies
|
||||
* them (fd-relative, no-follow) to an existing directory, then frees cleanly. */
|
||||
static void test_dir_time_list() {
|
||||
const char* root = "test_dir_time_root";
|
||||
const char* sub = "test_dir_time_root/sub";
|
||||
file_set_authorized_root(-1, NULL);
|
||||
rmdir(sub);
|
||||
rmdir(root);
|
||||
EXPECT_EQ_INT(mkdir(root, 0755), 0);
|
||||
EXPECT_EQ_INT(mkdir(sub, 0755), 0);
|
||||
|
||||
DirTimeList list;
|
||||
dir_time_list_init(&list);
|
||||
EXPECT_EQ_INT((int)list.count, 0);
|
||||
FileMetadata metadata = {.mtime_sec = 1000000000, .mtime_nsec = 0};
|
||||
EXPECT_TRUE(dir_time_list_add(&list, "sub", &metadata));
|
||||
EXPECT_TRUE(dir_time_list_add(&list, "sub", &metadata));
|
||||
EXPECT_EQ_INT((int)list.count, 2);
|
||||
|
||||
dir_time_list_apply(&list, root);
|
||||
struct stat st;
|
||||
EXPECT_EQ_INT(stat(sub, &st), 0);
|
||||
EXPECT_EQ_INT((int)st.st_mtime, 1000000000);
|
||||
|
||||
dir_time_list_free(&list);
|
||||
EXPECT_EQ_INT((int)list.count, 0);
|
||||
EXPECT_NULL(list.paths);
|
||||
EXPECT_NULL(list.entries);
|
||||
|
||||
rmdir(sub);
|
||||
rmdir(root);
|
||||
}
|
||||
|
||||
void test_file() {
|
||||
test_file_create();
|
||||
test_file_special_rdev_valid();
|
||||
@@ -1254,6 +1288,7 @@ void test_file() {
|
||||
test_file_send_single_calls_metadata_and_path();
|
||||
}
|
||||
test_file_metadata_create();
|
||||
test_dir_time_list();
|
||||
test_inplace_overwrite_clears_special_mode_bits();
|
||||
test_inplace_overwrite_metadata_strips_special_bits();
|
||||
test_inplace_overwrite_truncates_shorter_payload();
|
||||
|
||||
Reference in New Issue
Block a user