feat(p6-iconv): --iconv charset conversion + PROTOCOL 2.16.0

This commit is contained in:
2026-09-10 17:13:01 +02:00
parent 6bb63c6f21
commit 6e02a24232
15 changed files with 585 additions and 32 deletions
+18
View File
@@ -1,5 +1,6 @@
#include "client_send.h"
#include "client_validation.h"
#include "charset.h"
#include "chmod.h"
#include "compression.h"
#include "config.h"
@@ -590,6 +591,11 @@ static const OptionEntry OPTION_TABLE[] = {
* path; main() reads it (after the destination form is known) and derives
* the wire credentials. Never crosses the wire. */
{"--password-file", NULL, OPT_STRING, offsetof(Config, password_file)},
/* --iconv (protocol 2.16.0): convert file-NAME charsets at the wire
* boundary. The CONVERT_SPEC (LOCAL[,REMOTE]) is validated for real iconv
* charsets at startup (client_validation.c) and the full spec rides the
* config frame so the receiver derives the wire charset symmetrically. */
{"--iconv", NULL, OPT_STRING, offsetof(Config, iconv_spec)},
{"--delete-before", NULL, OPT_FLAG, offsetof(Config, delete_before)},
{"--delete-during", "--del", OPT_FLAG, offsetof(Config, delete_during)},
{"--delete-delay", NULL, OPT_FLAG, offsetof(Config, delete_delay)},
@@ -1593,6 +1599,17 @@ int main(int argc, char* argv[]) {
goto cleanup;
}
/* --iconv: install the sender-side local->wire conversion before any path is
scanned or serialized (the scanner and the chunk/data path read windows are
all driven from this process, so one global initialization covers every
send site). */
if (!charset_wire_init_sender(config->iconv_spec)) {
log_message(LOG_LEVEL_ERROR,
"--iconv has an invalid CONVERT_SPEC or an unsupported charset name");
exit_code = 1;
goto cleanup;
}
/* Apply the requested --outbuf style now that the mode is parsed. */
apply_output_buffering(config);
@@ -1614,6 +1631,7 @@ int main(int argc, char* argv[]) {
}
cleanup:
charset_wire_free();
if (config) {
config_delete(config);
}
+10 -9
View File
@@ -1,6 +1,7 @@
#include "client_send.h"
#include "array_list.h"
#include "change_list.h"
#include "charset.h"
#include "chunk.h"
#include "compression.h"
#include "config.h"
@@ -810,21 +811,21 @@ static int send_delete_manifest(int fd, ArrayList* manifest, ArrayList* protecte
if (!send_int(fd, keep_count))
return -1;
for (int i = 0; i < keep_count; i++) {
if (!send_str(fd, (char*)manifest->items[i]))
if (!send_wire_str(fd, (char*)manifest->items[i]))
return -1;
}
int protected_count = protected_prefixes ? protected_prefixes->size : 0;
if (!send_int(fd, protected_count))
return -1;
for (int i = 0; i < protected_count; i++) {
if (!send_str(fd, (char*)protected_prefixes->items[i]))
if (!send_wire_str(fd, (char*)protected_prefixes->items[i]))
return -1;
}
int missing_count = missing_args ? missing_args->size : 0;
if (!send_int(fd, missing_count))
return -1;
for (int i = 0; i < missing_count; i++) {
if (!send_str(fd, (char*)missing_args->items[i]))
if (!send_wire_str(fd, (char*)missing_args->items[i]))
return -1;
}
return 0;
@@ -899,7 +900,7 @@ static int incremental_check(Client* client, File* file, const Config* config,
*resume_offset = 0;
if (!send_status(client->file_descriptor, STATUS_CHECK))
return -1;
if (!send_str(client->file_descriptor, file_wire_path(file)))
if (!send_wire_str(client->file_descriptor, file_wire_path(file)))
return -1;
unsigned long long fsize = file->data->size;
long long mtime = file->metadata ? file->metadata->mtime_sec : 0;
@@ -1119,7 +1120,7 @@ static bool send_directory_entry(Client* client, File* file) {
return false;
if (!send_status(client->file_descriptor, STATUS_MKDIR))
return false;
return send_str(client->file_descriptor, file_wire_path(file));
return send_wire_str(client->file_descriptor, file_wire_path(file));
}
/* Transmit one symlink entry: a STATUS_SYMLINK frame carrying the destination
@@ -1130,8 +1131,8 @@ static bool send_symlink_entry(const Client* client, File* file, const Config* c
if (!file || !file_wire_path(file) || !file->symlink_target)
return false;
int fd = client->file_descriptor;
if (!send_status(fd, STATUS_SYMLINK) || !send_str(fd, file_wire_path(file)) ||
!send_str(fd, file->symlink_target))
if (!send_status(fd, STATUS_SYMLINK) || !send_wire_str(fd, file_wire_path(file)) ||
!send_wire_str(fd, file->symlink_target))
return false;
return !config->use_metadata || metadata_send(fd, file->metadata);
}
@@ -1311,9 +1312,9 @@ static int send_chunk_with_removal(Client* client, Chunk* chunk, Config* config,
wire path so the receiver links this entry to that installed file. */
if (f->link_group != 0 && !f->link_first && f->hardlink_target != NULL) {
if (!send_status(client->file_descriptor, STATUS_HARDLINK) ||
!send_str(client->file_descriptor, file_wire_path(f)) ||
!send_wire_str(client->file_descriptor, file_wire_path(f)) ||
!send_int(client->file_descriptor, f->link_group) ||
!send_str(client->file_descriptor, f->hardlink_target))
!send_wire_str(client->file_descriptor, f->hardlink_target))
return -1;
change_emit_file_sent(config, f);
continue;
+9
View File
@@ -1,4 +1,5 @@
#include "client_validation.h"
#include "charset.h"
#include "delay_updates.h"
#include "log.h"
#include "usage.h"
@@ -123,5 +124,13 @@ bool validate_config(const Config* config) {
"timing; at most one may be given and each implies --delete");
return false;
}
/* --iconv: reject a malformed CONVERT_SPEC or an unsupported charset name at
startup (a probe iconv_open is attempted), so a typo'd charset never fails
the run mid-transfer with per-file errors. */
if (!charset_spec_valid(config->iconv_spec)) {
log_message(LOG_LEVEL_ERROR,
"--iconv requires LOCAL[,REMOTE] charset names supported by iconv");
return false;
}
return true;
}
+6
View File
@@ -35,6 +35,12 @@ void print_usage(void) {
printf(" --progress Show transfer progress\n");
printf(" -P Partial mode with progress (retention incomplete)\n");
printf(" -8, --8-bit-output Leave high-bit characters unescaped in output\n");
printf(" --iconv=LOCAL[,REMOTE] Convert file-NAME charsets at the wire boundary:\n");
printf(" LOCAL is the charset of our file names, REMOTE is the\n");
printf(" remote side's charset (defaults to LOCAL). Names are\n");
printf(" converted before transmission and back on receipt; a\n");
printf(" name that cannot be represented in the target charset\n");
printf(" fails that transfer cleanly (rsync-compatible)\n");
printf(" --delete Delete files on receiver not in source\n");
printf(" (default timing: delete only after the whole\n");
printf(" transfer has succeeded)\n");