fix(protocol): atomic client-msg flag, sanitize peer messages, flush before terminal, serialization probe
This commit is contained in:
@@ -495,6 +495,7 @@ int send_dry_run_remote(Config* config) {
|
||||
protocol_session_bind(&session);
|
||||
|
||||
int ret = 1;
|
||||
bool partial = false;
|
||||
time_t dry_start = time(NULL);
|
||||
ReceiverStats dry_stats;
|
||||
memset(&dry_stats, 0, sizeof(dry_stats));
|
||||
@@ -653,8 +654,14 @@ int send_dry_run_remote(Config* config) {
|
||||
if (!receive_status(client->file_descriptor, &status))
|
||||
goto dry_fail;
|
||||
}
|
||||
if (status != STATUS_OK)
|
||||
/* A per-entry receiver failure is rsync's PARTIAL transfer (exit 23), not a
|
||||
hard failure: a dry run transfers nothing, but keep the verdict consistent
|
||||
with the normal path instead of treating it as a protocol error. */
|
||||
if (status == STATUS_PARTIAL) {
|
||||
partial = true;
|
||||
} else if (status != STATUS_OK) {
|
||||
goto dry_fail;
|
||||
}
|
||||
if (!config->quiet) {
|
||||
if (config->human_readable)
|
||||
printf("Total: %d files, %s\n", file_count,
|
||||
@@ -672,7 +679,7 @@ int send_dry_run_remote(Config* config) {
|
||||
dry_transfer.literal_data = total_bytes;
|
||||
report_transfer_stats(config, &dry_transfer, dry_start, &dry_stats);
|
||||
}
|
||||
ret = io_error ? 1 : 0;
|
||||
ret = io_error ? 1 : (partial ? 23 : 0);
|
||||
|
||||
dry_fail:
|
||||
if (dry_manifest)
|
||||
|
||||
@@ -1213,8 +1213,11 @@ static ArrayList* client_msg_queue = NULL; /* owns char* */
|
||||
static size_t client_msg_bytes = 0;
|
||||
/* True only while a live transfer session exists: before the connection is up
|
||||
(or after it drops) the sink declines so log_message falls back to local
|
||||
output, matching rsync's documented fallback. */
|
||||
static bool client_msg_active = false;
|
||||
output, matching rsync's documented fallback. Written by the sender thread
|
||||
(client_messages_activate) and read by scanner worker threads in
|
||||
client_msg_enqueue, so it must be atomic: the queue itself stays guarded by
|
||||
client_msg_mutex, but the flag is polled before taking that lock. */
|
||||
static _Atomic bool client_msg_active = false;
|
||||
|
||||
static void client_msg_mutex_init(void) {
|
||||
mtx_init(&client_msg_mutex, mtx_plain);
|
||||
@@ -1229,20 +1232,26 @@ void client_messages_install(void) {
|
||||
mtx_lock(&client_msg_mutex);
|
||||
if (!client_msg_queue)
|
||||
client_msg_queue = array_list_create(free);
|
||||
bool ready = client_msg_queue != NULL;
|
||||
mtx_unlock(&client_msg_mutex);
|
||||
log_set_client_msg_sink(client_msg_enqueue);
|
||||
/* Only arm the sink once the queue exists; on allocation failure leave the
|
||||
sink uninstalled so log_message keeps writing locally instead of handing
|
||||
messages to a sink that would silently drop them. */
|
||||
if (ready)
|
||||
log_set_client_msg_sink(client_msg_enqueue);
|
||||
}
|
||||
|
||||
void client_messages_activate(bool active) {
|
||||
client_msg_active = active;
|
||||
atomic_store(&client_msg_active, active);
|
||||
}
|
||||
|
||||
/* log_message sink: takes ownership (queues) the message when a session is
|
||||
* live; returns false otherwise so the caller writes it locally. */
|
||||
static bool client_msg_enqueue(const char* message) {
|
||||
bool active = atomic_load(&client_msg_active);
|
||||
if (!message || message[0] == '\0')
|
||||
return client_msg_active;
|
||||
if (!client_msg_active)
|
||||
return active;
|
||||
if (!active)
|
||||
return false;
|
||||
size_t len = strlen(message);
|
||||
call_once(&client_msg_mutex_once, client_msg_mutex_init);
|
||||
@@ -1273,8 +1282,19 @@ void client_flush_client_messages(int fd) {
|
||||
call_once(&client_msg_mutex_once, client_msg_mutex_init);
|
||||
mtx_lock(&client_msg_mutex);
|
||||
ArrayList* pending = client_msg_queue;
|
||||
client_msg_queue = array_list_create(free);
|
||||
client_msg_bytes = 0;
|
||||
if (pending) {
|
||||
ArrayList* fresh = array_list_create(free);
|
||||
if (fresh) {
|
||||
client_msg_queue = fresh;
|
||||
} else {
|
||||
/* No memory for a replacement queue: stop queuing new diagnostics (they
|
||||
fall back to local output) and drain this batch below so nothing is
|
||||
silently dropped. */
|
||||
client_msg_queue = NULL;
|
||||
log_set_client_msg_sink(NULL);
|
||||
}
|
||||
client_msg_bytes = 0;
|
||||
}
|
||||
mtx_unlock(&client_msg_mutex);
|
||||
if (!pending)
|
||||
return;
|
||||
@@ -1289,7 +1309,7 @@ void client_flush_client_messages(int fd) {
|
||||
/* Tear down the sink after a transfer and free anything still queued. */
|
||||
void client_messages_end(void) {
|
||||
log_set_client_msg_sink(NULL);
|
||||
client_msg_active = false;
|
||||
atomic_store(&client_msg_active, false);
|
||||
call_once(&client_msg_mutex_once, client_msg_mutex_init);
|
||||
mtx_lock(&client_msg_mutex);
|
||||
ArrayList* pending = client_msg_queue;
|
||||
|
||||
@@ -255,8 +255,18 @@ static bool finalize_transfer(Client* client, const Config* config, ArrayList* r
|
||||
*delete_limit_out = false;
|
||||
if (partial_out)
|
||||
*partial_out = false;
|
||||
/* --stderr=client: the receiver consumes frames until it reads
|
||||
STATUS_FINISHED, after which it no longer reads. Flush every diagnostic
|
||||
queued during the transfer here -- the last frame boundary at which the
|
||||
peer is still reading -- so nothing is stranded in the queue. */
|
||||
client_flush_client_messages(client->file_descriptor);
|
||||
if (!send_status(client->file_descriptor, STATUS_FINISHED))
|
||||
return false;
|
||||
/* Past STATUS_FINISHED the receiver has stopped reading, so any diagnostic
|
||||
logged from here on (notably the STATUS_PARTIAL warning below) can no
|
||||
longer be forwarded. Deactivate the channel so those messages fall back
|
||||
to local output instead of being queued for a closed peer and lost. */
|
||||
client_messages_activate(false);
|
||||
/* The receiver emits its optional wire-stats frame (protocol 2.25.0) FIRST,
|
||||
then any per-file --remove-source-files acks, then the terminal status. */
|
||||
Status status;
|
||||
@@ -899,6 +909,13 @@ static int send_chunk_with_removal(Client* client, Chunk* chunk, Config* config,
|
||||
if (chunk->items[i] == NULL)
|
||||
continue;
|
||||
transfer_stats_note_entry(stats, chunk->items[i]);
|
||||
/* Output parity: probe each entry's ancestor directories' destination
|
||||
state before emitting its itemize line, exactly as the non-serialized
|
||||
loop does. Without this, dest_state.known stays false and -i/-P
|
||||
renders an existing dir/symlink as created instead of `.d..t...` (or
|
||||
suppressing it). */
|
||||
if (!client_change_probe_ancestors(config, chunk->items[i], client->file_descriptor))
|
||||
return -1;
|
||||
/* The chunk-serialization path emits no --progress name lines, so only
|
||||
feed -i/--out-format its ancestor directory lines here. */
|
||||
if (config->itemize_changes || config->out_format != NULL)
|
||||
|
||||
Reference in New Issue
Block a user