Merge branch 'feat/parity-b3' into feat/parity-next
# Conflicts: # RSYNC_COMPAT.md
This commit is contained in:
@@ -381,21 +381,28 @@ bool files_from_list_check(const Config* config, ArrayList* missing_dest, int* s
|
||||
paths, loading and sending nothing. --delete-before/--delete-during need the
|
||||
complete keep-set manifest before the first data byte, so it is built by a
|
||||
dedicated pre-scan pass and transmitted early; the data pass then re-scans
|
||||
with a fresh scanner. A source I/O error is fatal unless the options carry
|
||||
--ignore-errors, in which case the scan continues past the unreadable
|
||||
directory and *io_error_out reports it (the caller still performs the
|
||||
deletion but reports the run as errored). */
|
||||
with a fresh scanner. --delete-before additionally replays this very scan as
|
||||
its data pass (rsync's single file list), so `chunks_out` (optional) retains
|
||||
the scanned Chunk objects for the caller to send instead of destroying them;
|
||||
the caller owns the list and must give it a chunk_destroy destructor. A
|
||||
source I/O error is fatal unless the options carry --ignore-errors, in which
|
||||
case the scan continues past the unreadable directory and *io_error_out
|
||||
reports it (the caller still performs the deletion but reports the run as
|
||||
errored). */
|
||||
bool scan_paths_only(const Config* config, const ScannerOptions* options, ArrayList* manifest,
|
||||
DeletePlanSender* plans, bool* io_error_out,
|
||||
unsigned long long* non_dir_count_out) {
|
||||
unsigned long long* non_dir_count_out, ArrayList* chunks_out,
|
||||
bool emit_nonreg) {
|
||||
if (io_error_out)
|
||||
*io_error_out = false;
|
||||
if (non_dir_count_out)
|
||||
*non_dir_count_out = 0;
|
||||
ScannerOptions local = *options;
|
||||
/* The pre-scan is a paths-only pass with no client output; it must not emit
|
||||
--info=nonreg lines (the data pass does that once). */
|
||||
local.note_nonreg = false;
|
||||
/* The pre-scan is normally a paths-only pass with no client output: it must
|
||||
not emit --info=nonreg lines because the data pass re-scans and emits them
|
||||
once. When the caller replays this scan as the data pass (--delete-before)
|
||||
there is no later scan, so it opts in and the lines are emitted here. */
|
||||
local.note_nonreg = emit_nonreg && options->note_nonreg;
|
||||
DirectoryScanner* scanner = directory_scanner_create_with_options(config->send_directory, &local);
|
||||
if (!scanner)
|
||||
return false;
|
||||
@@ -430,7 +437,16 @@ bool scan_paths_only(const Config* config, const ScannerOptions* options, ArrayL
|
||||
break;
|
||||
}
|
||||
}
|
||||
chunk_destroy(chunk);
|
||||
if (chunks_out) {
|
||||
/* Retain the chunk for the caller's data pass; ownership moves with it. */
|
||||
if (!array_list_add(chunks_out, chunk)) {
|
||||
ok = false;
|
||||
chunk_destroy(chunk);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
chunk_destroy(chunk);
|
||||
}
|
||||
}
|
||||
if (ok) {
|
||||
/* Keep every traversed source directory, including empty ones, so a plan
|
||||
|
||||
+66
-21
@@ -1394,6 +1394,10 @@ typedef struct {
|
||||
Client* client;
|
||||
DirectoryScanner* scanner;
|
||||
ArrayList* manifest;
|
||||
/* --delete-before: the pre-scan that built the keep-set, retained as the data
|
||||
pass's file list (owning Chunk*; consumed chunks are NULLed as they are
|
||||
sent). NULL in every other mode, where the data pass scans normally. */
|
||||
ArrayList* prescan_chunks;
|
||||
DeletePlanSender* plan_sender;
|
||||
ArrayList* remove_sources;
|
||||
ArrayList* dir_entries;
|
||||
@@ -1478,12 +1482,23 @@ static bool send_files_prepare_delete(Config* config, SendFilesState* state) {
|
||||
if (state->delete_early) {
|
||||
/* Pass 1: collect the complete keep-set (paths only, no data loaded) and
|
||||
transmit it now, before any file data. The receiver removes extras and
|
||||
acks; the transfer aborts here if the deletion could not commit. */
|
||||
acks; the transfer aborts here if the deletion could not commit. The
|
||||
scanned chunks are retained so the data pass can replay this exact list
|
||||
instead of re-reading the source (rsync builds one file list and never
|
||||
transfers a file created after it). */
|
||||
ArrayList* early_manifest = array_list_create(free);
|
||||
if (!early_manifest)
|
||||
ArrayList* prescan_chunks = array_list_create(chunk_destroy);
|
||||
if (!early_manifest || !prescan_chunks) {
|
||||
array_list_delete(early_manifest);
|
||||
array_list_delete(prescan_chunks);
|
||||
return false;
|
||||
}
|
||||
/* No later scan runs for --delete-before, so this pass must also capture the
|
||||
deferred directory times and the --stats directory count. */
|
||||
state->prepared.options.dir_entries = state->dir_entries;
|
||||
state->prepared.options.dir_count = config->stats ? &state->dir_count : NULL;
|
||||
bool prescan_ok = scan_paths_only(config, &state->prepared.options, early_manifest, NULL,
|
||||
&state->had_scan_io, NULL);
|
||||
&state->had_scan_io, NULL, prescan_chunks, true);
|
||||
bool early_ok = false;
|
||||
bool skip_delete = false;
|
||||
if (prescan_ok) {
|
||||
@@ -1514,8 +1529,13 @@ static bool send_files_prepare_delete(Config* config, SendFilesState* state) {
|
||||
state->prepared.options.excluded_paths = NULL;
|
||||
state->prepared.options.size_skipped_paths = NULL;
|
||||
state->prepared.options.synced_dirs = NULL;
|
||||
if (!prescan_ok || (!early_ok && !skip_delete))
|
||||
if (!prescan_ok || (!early_ok && !skip_delete)) {
|
||||
array_list_delete(prescan_chunks);
|
||||
return false;
|
||||
}
|
||||
/* Adopt the captured scan as the data pass's file list (including when an
|
||||
I/O error suppressed only the deletion: the list is still complete). */
|
||||
state->prescan_chunks = prescan_chunks;
|
||||
} else if (state->delete_per_dir) {
|
||||
/* --delete-during/--delete-delay: build one plan per source directory from a
|
||||
path-only pre-scan and transmit the COMPLETE plan set now, before any data,
|
||||
@@ -1527,8 +1547,9 @@ static bool send_files_prepare_delete(Config* config, SendFilesState* state) {
|
||||
if (!state->plan_sender || !state->plan_dirs)
|
||||
return false;
|
||||
state->prepared.options.plan_dirs = state->plan_dirs;
|
||||
bool prescan_ok = scan_paths_only(config, &state->prepared.options, NULL, state->plan_sender,
|
||||
&state->had_scan_io, &state->per_dir_non_dir_count);
|
||||
bool prescan_ok =
|
||||
scan_paths_only(config, &state->prepared.options, NULL, state->plan_sender,
|
||||
&state->had_scan_io, &state->per_dir_non_dir_count, NULL, false);
|
||||
bool plans_ok = false;
|
||||
bool skip_delete = false;
|
||||
if (prescan_ok) {
|
||||
@@ -1592,24 +1613,42 @@ static bool send_files_run(Config* config, SendFilesState* state) {
|
||||
state->stop = stop_condition_make(config->stop_after_mins > 0, config->stop_after_mins,
|
||||
config->cli.stop_at_set, config->stop_at, now_mono);
|
||||
state->prepared.options.stop_condition = &state->stop;
|
||||
/* The early-delete pre-scan above already ran; only the data pass should feed
|
||||
the directory-time list (otherwise every directory would be captured
|
||||
twice). */
|
||||
state->prepared.options.dir_entries = state->dir_entries;
|
||||
state->prepared.options.dir_count = config->stats ? &state->dir_count : NULL;
|
||||
state->scanner =
|
||||
directory_scanner_create_with_options(config->send_directory, &state->prepared.options);
|
||||
if (!state->scanner)
|
||||
return false;
|
||||
/* --delete-before reuses the pre-scan that built the keep-set as the data
|
||||
pass's file list, so a source file created after that scan is neither
|
||||
transferred nor kept (rsync builds one file list). That pre-scan captured
|
||||
the deferred directory times and the --stats directory count because no
|
||||
later scan runs; every other mode opens a fresh data scanner here. */
|
||||
if (state->prescan_chunks == NULL) {
|
||||
state->prepared.options.dir_entries = state->dir_entries;
|
||||
state->prepared.options.dir_count = config->stats ? &state->dir_count : NULL;
|
||||
state->scanner =
|
||||
directory_scanner_create_with_options(config->send_directory, &state->prepared.options);
|
||||
if (!state->scanner)
|
||||
return false;
|
||||
}
|
||||
|
||||
Chunk* current_chunk;
|
||||
int prescan_index = 0;
|
||||
memset(&state->transfer_stats, 0, sizeof(state->transfer_stats));
|
||||
state->start = time(NULL);
|
||||
client_progress_begin(config);
|
||||
/* True when the stop deadline cut the scan short so the keep-set manifest is
|
||||
only a prefix of the source. */
|
||||
bool send_failed = false;
|
||||
while ((current_chunk = directory_scanner_next(state->scanner)) != NULL) {
|
||||
while (true) {
|
||||
if (state->prescan_chunks != NULL) {
|
||||
if (prescan_index >= state->prescan_chunks->size)
|
||||
break;
|
||||
/* Move ownership out of the retained list so chunk_destroy below (and the
|
||||
cleanup tail for an early exit) never double-frees it. */
|
||||
current_chunk = (Chunk*)state->prescan_chunks->items[prescan_index];
|
||||
state->prescan_chunks->items[prescan_index] = NULL;
|
||||
prescan_index++;
|
||||
} else {
|
||||
current_chunk = directory_scanner_next(state->scanner);
|
||||
if (current_chunk == NULL)
|
||||
break;
|
||||
}
|
||||
/* Graceful abort (Ctrl-C/SIGTERM): notify the receiver and clean up. The
|
||||
session is active (config_send already succeeded); a send failure here is
|
||||
fine because the client is exiting anyway. */
|
||||
@@ -1667,10 +1706,14 @@ static bool send_files_run(Config* config, SendFilesState* state) {
|
||||
* Returns the rsync-compatible exit code. */
|
||||
static int send_files_finalize(const Config* config, SendFilesState* state) {
|
||||
Client* client = state->client;
|
||||
if (directory_scanner_failed(state->scanner))
|
||||
return 1;
|
||||
if (directory_scanner_had_io_error(state->scanner))
|
||||
state->had_scan_io = true;
|
||||
/* A --delete-before run replays the pre-scan and owns no data scanner; its
|
||||
I/O-error verdict was already recorded by that pre-scan. */
|
||||
if (state->scanner != NULL) {
|
||||
if (directory_scanner_failed(state->scanner))
|
||||
return 1;
|
||||
if (directory_scanner_had_io_error(state->scanner))
|
||||
state->had_scan_io = true;
|
||||
}
|
||||
/* An abort that arrived after the last chunk must still stop the completion
|
||||
tail (manifest/finalize) rather than let it run to success. */
|
||||
if (client_abort_pending()) {
|
||||
@@ -1774,6 +1817,8 @@ static int send_files_finalize(const Config* config, SendFilesState* state) {
|
||||
static void send_files_cleanup(SendFilesState* state) {
|
||||
if (state->manifest)
|
||||
array_list_delete(state->manifest);
|
||||
if (state->prescan_chunks)
|
||||
array_list_delete(state->prescan_chunks);
|
||||
if (state->plan_sender)
|
||||
delete_plan_sender_destroy(state->plan_sender);
|
||||
if (state->excluded)
|
||||
@@ -1993,7 +2038,7 @@ int send_files_multithreaded(Config* config) {
|
||||
bool prebuilt =
|
||||
prepared_ok &&
|
||||
scan_paths_only(config, &prepared.options, context->manifest, context->delete_plans,
|
||||
&context->scan_had_io_error, &pre_scan_non_dir);
|
||||
&context->scan_had_io_error, &pre_scan_non_dir, NULL, false);
|
||||
prepared_scanner_destroy(&prepared);
|
||||
if (per_dir && prebuilt) {
|
||||
const char* walk_root = delete_plan_walk_root(config, context->synced_dirs);
|
||||
|
||||
@@ -44,7 +44,8 @@ const char* delete_plan_walk_root(const Config* config, const ArrayList* synced_
|
||||
bool files_from_list_check(const Config* config, ArrayList* missing_dest, int* skipped_out);
|
||||
bool scan_paths_only(const Config* config, const ScannerOptions* options, ArrayList* manifest,
|
||||
DeletePlanSender* plans, bool* io_error_out,
|
||||
unsigned long long* non_dir_count_out);
|
||||
unsigned long long* non_dir_count_out, ArrayList* chunks_out,
|
||||
bool emit_nonreg);
|
||||
|
||||
/* client_report.c */
|
||||
void log_server_rejection(const char* context);
|
||||
|
||||
Reference in New Issue
Block a user