feat(protocol): add optional STATUS_ERROR_DETAIL rejection reason (2.21.0)
Today a server rejection sends a bare STATUS_ERROR and the reason only
reaches the server log, so the client cannot say why a transfer was
refused. Add an optional, bounded server->client error-detail frame:
- Status gains STATUS_ERROR_DETAIL appended LAST so existing wire
values are unchanged.
- send_error_detail(fd, msg) sends STATUS_ERROR_DETAIL followed by the
existing length-prefixed string primitive, slicing over-long messages
to MAX_ERROR_DETAIL_BYTES (4096).
- receive_status() (and the timed/keepalive status readers) always
consume the detail body and map the status back to STATUS_ERROR,
capturing the text into a thread-local buffer exposed by
protocol_last_error(); a bare STATUS_ERROR leaves it cleared. Every
existing call site keeps working and the stream cannot desync.
- Upgrade the daemon module gate / config validation (config.c), the
final transfer failure (server.c) and receiver-side path/node
validation (file_receive.c) to send a concrete reason; surface it on
the client in client_send.c/config.c.
- Bump PROTOCOL_VERSION to 2.21.0 (CMake VERSION, CHANGELOG, docs) and
update the pinned config wire golden hash / CLI-version tests.
- Add tests/test_protocol_error.c covering mapping+capture, the
over-long bound, bare-error clearing, and thread-locality.
This commit is contained in:
@@ -48,6 +48,17 @@
|
||||
Always cast to double when dividing so the output stays fractional. */
|
||||
#define BYTES_PER_MIB (1024ULL * 1024ULL)
|
||||
|
||||
/* Surface a server rejection to the user. When the last status exchange
|
||||
carried a STATUS_ERROR_DETAIL reason (protocol 2.21.0) it is appended to the
|
||||
client-side context; a bare STATUS_ERROR still logs the context alone. */
|
||||
static void log_server_rejection(const char* context) {
|
||||
const char* detail = protocol_last_error();
|
||||
if (detail && detail[0] != '\0')
|
||||
log_message(LOG_LEVEL_ERROR, "%s: %s", context, detail);
|
||||
else
|
||||
log_message(LOG_LEVEL_ERROR, "%s", context);
|
||||
}
|
||||
|
||||
/* Forward declaration for progress-reporting thread used in multithreaded send. */
|
||||
static int progress_thread_fn(void* arg);
|
||||
|
||||
@@ -608,8 +619,10 @@ static bool finalize_transfer(Client* client, const Config* config, ArrayList* r
|
||||
Status per_file;
|
||||
if (!receive_status(client->file_descriptor, &per_file))
|
||||
return false;
|
||||
if (per_file == STATUS_ERROR)
|
||||
if (per_file == STATUS_ERROR) {
|
||||
log_server_rejection("Receiver reported a per-file error");
|
||||
return false;
|
||||
}
|
||||
if (per_file == STATUS_OK) {
|
||||
((SourceFile*)remove_sources->items[i])->skipped = true;
|
||||
} else if (per_file != STATUS_NEXT) {
|
||||
@@ -619,7 +632,13 @@ static bool finalize_transfer(Client* client, const Config* config, ArrayList* r
|
||||
}
|
||||
}
|
||||
Status status;
|
||||
return receive_status(client->file_descriptor, &status) && status == STATUS_OK;
|
||||
if (!receive_status(client->file_descriptor, &status))
|
||||
return false;
|
||||
if (status != STATUS_OK) {
|
||||
log_server_rejection("Receiver reported transfer failure");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void pipeline_cancel(PipelineContextSender* context) {
|
||||
@@ -914,7 +933,7 @@ static bool send_delete_manifest_early(Client* client, ArrayList* manifest,
|
||||
return false;
|
||||
}
|
||||
if (ack != STATUS_OK) {
|
||||
log_message(LOG_LEVEL_ERROR, "Server failed to delete files before the transfer");
|
||||
log_server_rejection("Server failed to delete files before the transfer");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -991,7 +1010,7 @@ static int incremental_check(Client* client, File* file, const Config* config,
|
||||
if (!receive_status(client->file_descriptor, &s))
|
||||
return -1;
|
||||
if (s == STATUS_ERROR) {
|
||||
log_message(LOG_LEVEL_ERROR, "Server reported error for file");
|
||||
log_server_rejection("Server reported error for file");
|
||||
return -1;
|
||||
}
|
||||
if (s == STATUS_OK)
|
||||
@@ -1025,7 +1044,7 @@ static int incremental_check(Client* client, File* file, const Config* config,
|
||||
return 3;
|
||||
}
|
||||
if (s != STATUS_NEXT) {
|
||||
log_message(LOG_LEVEL_ERROR, "Unexpected server status");
|
||||
log_server_rejection("Unexpected server status");
|
||||
send_status(client->file_descriptor, STATUS_ERROR);
|
||||
return -1;
|
||||
}
|
||||
@@ -1119,6 +1138,7 @@ static int send_append(const Client* client, File* file, Config* config,
|
||||
return rc;
|
||||
}
|
||||
if (resp != STATUS_APPEND_OK) {
|
||||
log_server_rejection("Unexpected append-verify response");
|
||||
send_status(fd, STATUS_ERROR);
|
||||
return -1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user