fix(p7-times): dir-time entries only record (never create dirs); bound/chunk dir-time frames; harden list add; docs+tests
Review fixes for Phase 7 Wave D. #1 (HIGH): STATUS_DIR_TIMES entries no longer create directories. A new receiver-only File.dir_time_only flag marks dir-time entries; file_save_to_disk_full short-circuits them as FILE_SAVE_SKIPPED before any device/dir branch, so the sink still accumulates metadata into the deferred DirTimeList but creates nothing. Empty source dirs stay untransferred (-a), -m/--prune-empty-dirs semantics are preserved, and a pre-existing regular file/symlink at an empty-dir mirror path no longer aborts the transfer. dir_time_list_apply fstatat()s the leaf (AT_SYMLINK_NOFOLLOW) and skips absent/non-directory paths QUIETLY; only a real existing directory is stamped. Also initialize File.dir_time_only in file_create() (uninitialised garbage otherwise). #2 (MED): send_dir_times() chunks entries into repeated STATUS_DIR_TIMES frames of at most MAX_MANIFEST_ENTRIES, matching the receiver's per-frame bound; the tautological > INT_MAX check is gone. #3 (LOW): dir_time_list_add() assigns each grown array right after its realloc (no dangling) and advances capacity only after both succeed. #4 (LOW): RSYNC_COMPAT.md -- STATUS_MKDIR carries metadata, dir times are transmitted via STATUS_DIR_TIMES and applied at the end, empty dirs are still never created; -m rationale, -O row and Wave D notes updated. Summary counts untouched. #5 (LOW): integration tests for the three #1 scenarios (empty-dir non-creation under -a and -a -m, collision non-abort), scanner test now covers empty-dir capture, and test_file_restore_symlink_metadata asserts the positive apply path when supported. PROTOCOL_VERSION stays 2.17.0; config-frame layout unchanged.
This commit is contained in:
+26
-16
@@ -1133,26 +1133,36 @@ static bool send_directory_entry(const Client* client, File* file, const Config*
|
||||
return !config->use_metadata || metadata_send(client->file_descriptor, file->metadata);
|
||||
}
|
||||
|
||||
/* P7 Wave D: transmit every captured source directory's metadata in one
|
||||
terminal STATUS_DIR_TIMES frame (count, then (path, metadata) pairs) after all
|
||||
file data and the optional delete manifest. The receiver applies them at the
|
||||
END of its own transfer (after deletion and --delay-updates publication) so a
|
||||
/* P7 Wave D: transmit every captured source directory's metadata in terminal
|
||||
STATUS_DIR_TIMES frames (count, then (path, metadata) pairs) after all file
|
||||
data and the optional delete manifest. The receiver applies them at the END
|
||||
of its own transfer (after deletion and --delay-updates publication) so a
|
||||
directory's mtime is not clobbered by writing its children. A non-metadata
|
||||
transfer (or an empty set) sends nothing, keeping the stream byte-identical. */
|
||||
transfer (or an empty set) sends nothing, keeping the stream byte-identical.
|
||||
|
||||
The receiver rejects a frame whose count exceeds MAX_MANIFEST_ENTRIES, so a
|
||||
huge tree is CHUNKED into repeated frames of at most that many entries each
|
||||
(the receiver's loop handles repeated STATUS_DIR_TIMES frames). Every frame
|
||||
stays within the receiver's bound, and a frame that would exceed it is never
|
||||
emitted. */
|
||||
static bool send_dir_times(const Client* client, const Config* config, ArrayList* dir_entries) {
|
||||
if (!client || !config || !config->use_metadata || !dir_entries || dir_entries->size == 0)
|
||||
return true;
|
||||
if (dir_entries->size > INT_MAX)
|
||||
return false;
|
||||
int fd = client->file_descriptor;
|
||||
if (!send_status(fd, STATUS_DIR_TIMES) || !send_int(fd, dir_entries->size))
|
||||
return false;
|
||||
for (int i = 0; i < dir_entries->size; i++) {
|
||||
File* file = (File*)dir_entries->items[i];
|
||||
if (!file || !file_wire_path(file))
|
||||
return false;
|
||||
if (!send_wire_str(fd, file_wire_path(file)) || !metadata_send(fd, file->metadata))
|
||||
int index = 0;
|
||||
while (index < dir_entries->size) {
|
||||
int remaining = dir_entries->size - index;
|
||||
int chunk = remaining > MAX_MANIFEST_ENTRIES ? MAX_MANIFEST_ENTRIES : remaining;
|
||||
if (!send_status(fd, STATUS_DIR_TIMES) || !send_int(fd, chunk))
|
||||
return false;
|
||||
for (int i = 0; i < chunk; i++) {
|
||||
File* file = (File*)dir_entries->items[index + i];
|
||||
if (!file || !file_wire_path(file))
|
||||
return false;
|
||||
if (!send_wire_str(fd, file_wire_path(file)) || !metadata_send(fd, file->metadata))
|
||||
return false;
|
||||
}
|
||||
index += chunk;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1872,8 +1882,8 @@ int send_files(Config* config) {
|
||||
DirectoryScanner* scanner = NULL;
|
||||
ArrayList* manifest = NULL;
|
||||
ArrayList* remove_sources = NULL;
|
||||
/* P7 Wave D: captured source directory times, transmitted in one terminal
|
||||
STATUS_DIR_TIMES frame (only when metadata rides the wire). */
|
||||
/* P7 Wave D: captured source directory times, transmitted in trailing
|
||||
STATUS_DIR_TIMES frame(s) (only when metadata rides the wire). */
|
||||
ArrayList* dir_entries = NULL;
|
||||
/* Protected excluded prefixes (delete-excluded default protection). */
|
||||
ArrayList* excluded = NULL;
|
||||
|
||||
Reference in New Issue
Block a user