Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bf91c5b588 | |||
| 5eaea9d92e | |||
| 9d67fcbf92 | |||
| 261683aaff | |||
| 15a861747a | |||
| 5f1b27c8ff | |||
| 732cd67735 | |||
| 50fb7185a8 | |||
| 3062927e07 | |||
| 5d74c7cb74 | |||
| efc7e062e3 | |||
| 88746c3396 |
+131
-110
@@ -49,6 +49,37 @@ static bool parse_nonneg_int(const char* s, int* out_val) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Duplicate a string argument into *dest, freeing the old value. Returns 0 on success, -1 on
|
||||
* failure. */
|
||||
static int set_string_option(char** dest, const char* value, const char* option_name) {
|
||||
char* dup = str_dup(value);
|
||||
if (!dup) {
|
||||
fprintf(stderr, "Error: memory allocation failed for %s\n", option_name);
|
||||
return -1;
|
||||
}
|
||||
free(*dest);
|
||||
*dest = dup;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Parse a string as a positive integer into *dest. Returns 0 on success, -1 on error. */
|
||||
static int set_positive_int_option(int* dest, const char* value, const char* option_name) {
|
||||
if (!parse_positive_int(value, dest)) {
|
||||
fprintf(stderr, "Error: %s must be a positive integer\n", option_name);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Parse a string as a non-negative integer into *dest. Returns 0 on success, -1 on error. */
|
||||
static int set_nonneg_int_option(int* dest, const char* value, const char* option_name) {
|
||||
if (!parse_nonneg_int(value, dest)) {
|
||||
fprintf(stderr, "Error: %s must be a non-negative integer\n", option_name);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void print_usage(void);
|
||||
static int read_patterns_from_file(const char* filepath, char*** patterns, int* count);
|
||||
|
||||
@@ -100,9 +131,23 @@ static int parse_args(Config* config, int argc, char* argv[], int* positional_ar
|
||||
}
|
||||
config->include_patterns[config->include_count++] = dup;
|
||||
} else if (strcmp(argv[i], "--max-size") == 0 && i + 1 < argc) {
|
||||
config->max_size = strtoull(argv[++i], NULL, 10);
|
||||
char* end;
|
||||
errno = 0;
|
||||
unsigned long long val = strtoull(argv[++i], &end, 10);
|
||||
if (errno != 0 || *end != '\0') {
|
||||
fprintf(stderr, "Error: --max-size must be a non-negative integer\n");
|
||||
return -1;
|
||||
}
|
||||
config->max_size = val;
|
||||
} else if (strcmp(argv[i], "--min-size") == 0 && i + 1 < argc) {
|
||||
config->min_size = strtoull(argv[++i], NULL, 10);
|
||||
char* end;
|
||||
errno = 0;
|
||||
unsigned long long val = strtoull(argv[++i], &end, 10);
|
||||
if (errno != 0 || *end != '\0') {
|
||||
fprintf(stderr, "Error: --min-size must be a non-negative integer\n");
|
||||
return -1;
|
||||
}
|
||||
config->min_size = val;
|
||||
} else if (strcmp(argv[i], "--incremental") == 0) {
|
||||
config->use_incremental = true;
|
||||
} else if (strcmp(argv[i], "--delta") == 0) {
|
||||
@@ -132,21 +177,11 @@ static int parse_args(Config* config, int argc, char* argv[], int* positional_ar
|
||||
}
|
||||
}
|
||||
} else if (strcmp(argv[i], "--source-dir") == 0 && i + 1 < argc) {
|
||||
char* dup = str_dup(argv[++i]);
|
||||
if (!dup) {
|
||||
fprintf(stderr, "Error: memory allocation failed for --source-dir\n");
|
||||
if (set_string_option(&config->send_directory, argv[++i], "--source-dir") != 0)
|
||||
return -1;
|
||||
}
|
||||
free(config->send_directory);
|
||||
config->send_directory = dup;
|
||||
} else if (strcmp(argv[i], "--dest-dir") == 0 && i + 1 < argc) {
|
||||
char* dup = str_dup(argv[++i]);
|
||||
if (!dup) {
|
||||
fprintf(stderr, "Error: memory allocation failed for --dest-dir\n");
|
||||
if (set_string_option(&config->receive_root_directory, argv[++i], "--dest-dir") != 0)
|
||||
return -1;
|
||||
}
|
||||
free(config->receive_root_directory);
|
||||
config->receive_root_directory = dup;
|
||||
} else if (strcmp(argv[i], "--save-to-disk") == 0) {
|
||||
config->save_to_disk = true;
|
||||
} else if (strcmp(argv[i], "-M") == 0 || strcmp(argv[i], "--preserve") == 0) {
|
||||
@@ -162,13 +197,8 @@ static int parse_args(Config* config, int argc, char* argv[], int* positional_ar
|
||||
config->use_chunk_serialization = true;
|
||||
log_message(LOG_LEVEL_INFO, "Enabled Chunk Serialization");
|
||||
} else if (strcmp(argv[i], "--server-host") == 0 && i + 1 < argc) {
|
||||
char* dup = str_dup(argv[++i]);
|
||||
if (!dup) {
|
||||
fprintf(stderr, "Error: memory allocation failed for --server-host\n");
|
||||
if (set_string_option(&config->server_host, argv[++i], "--server-host") != 0)
|
||||
return -1;
|
||||
}
|
||||
free(config->server_host);
|
||||
config->server_host = dup;
|
||||
} else if (strcmp(argv[i], "--server-port") == 0 && i + 1 < argc) {
|
||||
if (!parse_positive_int(argv[++i], &config->server_port)) {
|
||||
fprintf(stderr, "Error: invalid --server-port value: %s\n", argv[i]);
|
||||
@@ -191,69 +221,44 @@ static int parse_args(Config* config, int argc, char* argv[], int* positional_ar
|
||||
} else if (strcmp(argv[i], "--progress") == 0) {
|
||||
config->show_progress = true;
|
||||
} else if (strcmp(argv[i], "--chunk-size") == 0 && i + 1 < argc) {
|
||||
unsigned long long val = strtoull(argv[++i], NULL, 10);
|
||||
if (val > 0)
|
||||
config->chunk_size = val;
|
||||
char* end;
|
||||
errno = 0;
|
||||
unsigned long long val = strtoull(argv[++i], &end, 10);
|
||||
if (errno != 0 || *end != '\0' || val == 0) {
|
||||
fprintf(stderr, "Error: --chunk-size must be a positive integer\n");
|
||||
return -1;
|
||||
}
|
||||
config->chunk_size = val;
|
||||
} else if (strcmp(argv[i], "--tls") == 0) {
|
||||
config->use_tls = true;
|
||||
} else if (strcmp(argv[i], "--cert") == 0 && i + 1 < argc) {
|
||||
char* dup = str_dup(argv[++i]);
|
||||
if (!dup) {
|
||||
fprintf(stderr, "Error: memory allocation failed for --cert\n");
|
||||
if (set_string_option(&config->tls_cert, argv[++i], "--cert") != 0)
|
||||
return -1;
|
||||
}
|
||||
free(config->tls_cert);
|
||||
config->tls_cert = dup;
|
||||
} else if (strcmp(argv[i], "--key") == 0 && i + 1 < argc) {
|
||||
char* dup = str_dup(argv[++i]);
|
||||
if (!dup) {
|
||||
fprintf(stderr, "Error: memory allocation failed for --key\n");
|
||||
if (set_string_option(&config->tls_key, argv[++i], "--key") != 0)
|
||||
return -1;
|
||||
}
|
||||
free(config->tls_key);
|
||||
config->tls_key = dup;
|
||||
} else if (strcmp(argv[i], "--ca") == 0 && i + 1 < argc) {
|
||||
char* dup = str_dup(argv[++i]);
|
||||
if (!dup) {
|
||||
fprintf(stderr, "Error: memory allocation failed for --ca\n");
|
||||
if (set_string_option(&config->tls_ca, argv[++i], "--ca") != 0)
|
||||
return -1;
|
||||
}
|
||||
free(config->tls_ca);
|
||||
config->tls_ca = dup;
|
||||
} else if (strcmp(argv[i], "--timeout") == 0 && i + 1 < argc) {
|
||||
int val;
|
||||
if (!parse_positive_int(argv[++i], &val)) {
|
||||
fprintf(stderr, "Error: --timeout must be a positive integer\n");
|
||||
if (set_positive_int_option(&config->timeout, argv[++i], "--timeout") != 0)
|
||||
return -1;
|
||||
}
|
||||
config->timeout = val;
|
||||
} else if (strcmp(argv[i], "--contimeout") == 0 && i + 1 < argc) {
|
||||
int val;
|
||||
if (!parse_positive_int(argv[++i], &val)) {
|
||||
fprintf(stderr, "Error: --contimeout must be a positive integer\n");
|
||||
if (set_positive_int_option(&config->contimeout, argv[++i], "--contimeout") != 0)
|
||||
return -1;
|
||||
}
|
||||
config->contimeout = val;
|
||||
} else if (strcmp(argv[i], "-q") == 0 || strcmp(argv[i], "--quiet") == 0 ||
|
||||
strcmp(argv[i], "--silent") == 0) {
|
||||
config->quiet = true;
|
||||
} else if (strcmp(argv[i], "--backup") == 0) {
|
||||
config->backup = true;
|
||||
} else if (strcmp(argv[i], "--backup-dir") == 0 && i + 1 < argc) {
|
||||
char* dup = str_dup(argv[++i]);
|
||||
if (!dup) {
|
||||
fprintf(stderr, "Error: memory allocation failed for --backup-dir\n");
|
||||
if (set_string_option(&config->backup_dir, argv[++i], "--backup-dir") != 0)
|
||||
return -1;
|
||||
}
|
||||
free(config->backup_dir);
|
||||
config->backup_dir = dup;
|
||||
} else if (strcmp(argv[i], "--stats") == 0) {
|
||||
config->stats = true;
|
||||
} else if (strcmp(argv[i], "--max-depth") == 0 && i + 1 < argc) {
|
||||
if (!parse_nonneg_int(argv[++i], &config->max_depth)) {
|
||||
fprintf(stderr, "Error: --max-depth must be a non-negative integer\n");
|
||||
if (set_nonneg_int_option(&config->max_depth, argv[++i], "--max-depth") != 0)
|
||||
return -1;
|
||||
}
|
||||
} else if (strcmp(argv[i], "--log-file") == 0 && i + 1 < argc) {
|
||||
FILE* lf = fopen(argv[++i], "a");
|
||||
if (!lf) {
|
||||
@@ -263,12 +268,8 @@ static int parse_args(Config* config, int argc, char* argv[], int* positional_ar
|
||||
config->log_file = lf;
|
||||
log_set_file(lf);
|
||||
} else if (strcmp(argv[i], "--queue-size") == 0 && i + 1 < argc) {
|
||||
int val;
|
||||
if (!parse_positive_int(argv[++i], &val)) {
|
||||
fprintf(stderr, "Error: --queue-size must be a positive integer\n");
|
||||
if (set_positive_int_option(&config->queue_size, argv[++i], "--queue-size") != 0)
|
||||
return -1;
|
||||
}
|
||||
config->queue_size = val;
|
||||
} else if (strcmp(argv[i], "--exclude-from") == 0 && i + 1 < argc) {
|
||||
if (read_patterns_from_file(argv[++i], &config->exclude_patterns, &config->exclude_count) !=
|
||||
0)
|
||||
@@ -280,13 +281,9 @@ static int parse_args(Config* config, int argc, char* argv[], int* positional_ar
|
||||
} else if (strcmp(argv[i], "--partial") == 0) {
|
||||
config->partial = true;
|
||||
} else if (strcmp(argv[i], "--fastsync-server-path") == 0 && i + 1 < argc) {
|
||||
char* dup = str_dup(argv[++i]);
|
||||
if (!dup) {
|
||||
fprintf(stderr, "Error: memory allocation failed for --fastsync-server-path\n");
|
||||
if (set_string_option(&config->fastsync_server_path, argv[++i], "--fastsync-server-path") !=
|
||||
0)
|
||||
return -1;
|
||||
}
|
||||
free(config->fastsync_server_path);
|
||||
config->fastsync_server_path = dup;
|
||||
} else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
|
||||
set_log_level(LOG_LEVEL_DEBUG);
|
||||
} else if (strcmp(argv[i], "-l") == 0 || strcmp(argv[i], "--links") == 0) {
|
||||
@@ -310,15 +307,14 @@ static int parse_args(Config* config, int argc, char* argv[], int* positional_ar
|
||||
} else if (strcmp(argv[i], "-i") == 0 || strcmp(argv[i], "--itemize-changes") == 0) {
|
||||
config->itemize_changes = true;
|
||||
} else if (strcmp(argv[i], "--out-format") == 0 && i + 1 < argc) {
|
||||
char* dup = str_dup(argv[++i]);
|
||||
if (!dup)
|
||||
if (set_string_option(&config->out_format, argv[++i], "--out-format") != 0)
|
||||
return -1;
|
||||
free(config->out_format);
|
||||
config->out_format = dup;
|
||||
} else if (strcmp(argv[i], "--info") == 0 && i + 1 < argc) {
|
||||
config->info_level = atoi(argv[++i]);
|
||||
if (set_nonneg_int_option(&config->info_level, argv[++i], "--info") != 0)
|
||||
return -1;
|
||||
} else if (strcmp(argv[i], "--debug") == 0 && i + 1 < argc) {
|
||||
config->debug_level = atoi(argv[++i]);
|
||||
if (set_nonneg_int_option(&config->debug_level, argv[++i], "--debug") != 0)
|
||||
return -1;
|
||||
} else if (strcmp(argv[i], "--list-only") == 0) {
|
||||
config->list_only = true;
|
||||
} else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--human-readable") == 0) {
|
||||
@@ -336,12 +332,8 @@ static int parse_args(Config* config, int argc, char* argv[], int* positional_ar
|
||||
} else if (strcmp(argv[i], "--delete-after") == 0) {
|
||||
config->delete_after = true;
|
||||
} else if (strcmp(argv[i], "--max-delete") == 0 && i + 1 < argc) {
|
||||
int val;
|
||||
if (!parse_nonneg_int(argv[++i], &val)) {
|
||||
fprintf(stderr, "Error: --max-delete must be a non-negative integer\n");
|
||||
if (set_nonneg_int_option(&config->max_delete, argv[++i], "--max-delete") != 0)
|
||||
return -1;
|
||||
}
|
||||
config->max_delete = val;
|
||||
} else if (strcmp(argv[i], "--filter") == 0 && i + 1 < argc) {
|
||||
if (!config->filters)
|
||||
config->filters = array_list_create(free);
|
||||
@@ -350,11 +342,8 @@ static int parse_args(Config* config, int argc, char* argv[], int* positional_ar
|
||||
return -1;
|
||||
array_list_add(config->filters, dup);
|
||||
} else if (strcmp(argv[i], "--files-from") == 0 && i + 1 < argc) {
|
||||
char* dup = str_dup(argv[++i]);
|
||||
if (!dup)
|
||||
if (set_string_option(&config->files_from, argv[++i], "--files-from") != 0)
|
||||
return -1;
|
||||
free(config->files_from);
|
||||
config->files_from = dup;
|
||||
} else if (strcmp(argv[i], "--cvs-exclude") == 0) {
|
||||
config->cvs_exclude = true;
|
||||
} else if (strcmp(argv[i], "--prune-empty-dirs") == 0) {
|
||||
@@ -363,45 +352,63 @@ static int parse_args(Config* config, int argc, char* argv[], int* positional_ar
|
||||
config->relative = true;
|
||||
} else if (strcmp(argv[i], "-e") == 0 || strcmp(argv[i], "--rsh") == 0) {
|
||||
if (i + 1 < argc) {
|
||||
char* dup = str_dup(argv[++i]);
|
||||
if (!dup)
|
||||
if (set_string_option(&config->rsh_command, argv[++i], "-e/--rsh") != 0)
|
||||
return -1;
|
||||
free(config->rsh_command);
|
||||
config->rsh_command = dup;
|
||||
} else {
|
||||
fprintf(stderr, "Error: -e/--rsh requires a command argument\n");
|
||||
return -1;
|
||||
}
|
||||
} else if (strcmp(argv[i], "--rsync-path") == 0 && i + 1 < argc) {
|
||||
char* dup = str_dup(argv[++i]);
|
||||
if (!dup)
|
||||
if (set_string_option(&config->rsync_path, argv[++i], "--rsync-path") != 0)
|
||||
return -1;
|
||||
free(config->rsync_path);
|
||||
config->rsync_path = dup;
|
||||
} else if (strcmp(argv[i], "--temp-dir") == 0 && i + 1 < argc) {
|
||||
char* dup = str_dup(argv[++i]);
|
||||
if (!dup)
|
||||
if (set_string_option(&config->temp_dir, argv[++i], "--temp-dir") != 0)
|
||||
return -1;
|
||||
free(config->temp_dir);
|
||||
config->temp_dir = dup;
|
||||
} else if (strcmp(argv[i], "--compare-dest") == 0 && i + 1 < argc) {
|
||||
char* dup = str_dup(argv[++i]);
|
||||
if (!dup)
|
||||
if (set_string_option(&config->compare_dest, argv[++i], "--compare-dest") != 0)
|
||||
return -1;
|
||||
free(config->compare_dest);
|
||||
config->compare_dest = dup;
|
||||
} else if (strcmp(argv[i], "--copy-dest") == 0 && i + 1 < argc) {
|
||||
char* dup = str_dup(argv[++i]);
|
||||
if (!dup)
|
||||
if (set_string_option(&config->copy_dest, argv[++i], "--copy-dest") != 0)
|
||||
return -1;
|
||||
free(config->copy_dest);
|
||||
config->copy_dest = dup;
|
||||
} else if (strcmp(argv[i], "--link-dest") == 0 && i + 1 < argc) {
|
||||
char* dup = str_dup(argv[++i]);
|
||||
if (!dup)
|
||||
if (set_string_option(&config->link_dest, argv[++i], "--link-dest") != 0)
|
||||
return -1;
|
||||
} else if (strcmp(argv[i], "--partial-dir") == 0 && i + 1 < argc) {
|
||||
if (set_string_option(&config->partial_dir, argv[++i], "--partial-dir") != 0)
|
||||
return -1;
|
||||
} else if (strcmp(argv[i], "--suffix") == 0 && i + 1 < argc) {
|
||||
if (set_string_option(&config->suffix, argv[++i], "--suffix") != 0)
|
||||
return -1;
|
||||
} else if (strcmp(argv[i], "--delete-before") == 0) {
|
||||
config->delete_before = true;
|
||||
} else if (strcmp(argv[i], "-T") == 0 && i + 1 < argc) {
|
||||
if (set_positive_int_option(&config->timeout, argv[++i], "-T") != 0)
|
||||
return -1;
|
||||
} else if (strcmp(argv[i], "--address") == 0 && i + 1 < argc) {
|
||||
if (set_string_option(&config->address, argv[++i], "--address") != 0)
|
||||
return -1;
|
||||
} else if (strcmp(argv[i], "--bind-address") == 0 && i + 1 < argc) {
|
||||
if (set_string_option(&config->bind_address, argv[++i], "--bind-address") != 0)
|
||||
return -1;
|
||||
} else if (strcmp(argv[i], "--ipv6") == 0) {
|
||||
config->ipv6 = true;
|
||||
} else if (strcmp(argv[i], "--ipv4") == 0) {
|
||||
config->ipv4 = true;
|
||||
} else if (strcmp(argv[i], "--daemon") == 0) {
|
||||
config->daemon = true;
|
||||
} else if (strcmp(argv[i], "--config") == 0 && i + 1 < argc) {
|
||||
if (set_string_option(&config->daemon_config, argv[++i], "--config") != 0)
|
||||
return -1;
|
||||
} else if (strcmp(argv[i], "--server") == 0) {
|
||||
config->server_mode = true;
|
||||
} else if (strcmp(argv[i], "--checksum") == 0) {
|
||||
config->checksum = true;
|
||||
} else if (strcmp(argv[i], "--compress-choice") == 0 && i + 1 < argc) {
|
||||
if (set_string_option(&config->compress_choice, argv[++i], "--compress-choice") != 0)
|
||||
return -1;
|
||||
} else if (strcmp(argv[i], "--compress-level") == 0 && i + 1 < argc) {
|
||||
if (set_positive_int_option(&config->compression_level, argv[++i], "--compress-level") != 0)
|
||||
return -1;
|
||||
free(config->link_dest);
|
||||
config->link_dest = dup;
|
||||
} else if (argv[i][0] == '-') {
|
||||
fprintf(stderr, "Unknown option: %s\n", argv[i]);
|
||||
print_usage();
|
||||
@@ -507,16 +514,23 @@ static void print_usage(void) {
|
||||
printf(" --key <path> TLS private key file (PEM)\n");
|
||||
printf(" --ca <path> TLS CA certificate file (PEM)\n");
|
||||
printf(" --timeout <sec> I/O timeout in seconds (default: 30)\n");
|
||||
printf(" -T <sec> Alias for --timeout\n");
|
||||
printf(" --contimeout <sec> Connection timeout in seconds (default: 10)\n");
|
||||
printf(" --address <host> Server hostname/IP to connect to\n");
|
||||
printf(" --bind-address <ip> Bind to specific local address\n");
|
||||
printf(" --ipv6 Prefer IPv6 connections\n");
|
||||
printf(" --ipv4 Prefer IPv4 connections\n");
|
||||
printf(" -q, --quiet Suppress non-error output\n");
|
||||
printf(" --silent Alias for --quiet\n");
|
||||
printf(" --backup Backup existing files before overwriting\n");
|
||||
printf(" --backup-dir <dir> Directory for backups (requires --backup)\n");
|
||||
printf(" --suffix <str> Backup suffix (default: ~)\n");
|
||||
printf(" --stats Print transfer statistics at end\n");
|
||||
printf(" --max-depth <n> Maximum directory depth (0=unlimited)\n");
|
||||
printf(" --log-file <path> Write log messages to file\n");
|
||||
printf(" --queue-size <n> Queue capacity for multithreaded mode (default: 100)\n");
|
||||
printf(" --partial Keep partial files on interrupted transfer\n");
|
||||
printf(" --partial-dir <dir> Directory for partial files\n");
|
||||
printf(" --fastsync-server-path <path>\n");
|
||||
printf(" Path to fastsync-server on remote (default: fastsync-server)\n");
|
||||
printf(" -l, --links Copy symlinks as symlinks\n");
|
||||
@@ -538,6 +552,7 @@ static void print_usage(void) {
|
||||
printf(" --inplace Update files in-place (no temp+rename)\n");
|
||||
printf(" --append Append data to shorter files\n");
|
||||
printf(" --append-verify Append with verify\n");
|
||||
printf(" --delete-before Delete before transfer\n");
|
||||
printf(" --delete-excluded Also delete excluded files\n");
|
||||
printf(" --delete-after Delete after transfer, not before\n");
|
||||
printf(" --max-delete <n> Maximum number of files to delete\n");
|
||||
@@ -548,6 +563,12 @@ static void print_usage(void) {
|
||||
printf(" -R, --relative Use relative paths\n");
|
||||
printf(" -e, --rsh <cmd> Specify remote shell\n");
|
||||
printf(" --rsync-path <path> Path to remote binary\n");
|
||||
printf(" --daemon Run in daemon mode\n");
|
||||
printf(" --config <path> Path to configuration file\n");
|
||||
printf(" --server Run in server mode\n");
|
||||
printf(" --checksum Skip files based on checksum, not mod-time/size\n");
|
||||
printf(" --compress-choice <alg> Compression algorithm (default: zstd)\n");
|
||||
printf(" --compress-level <n> Compression level (default: 5)\n");
|
||||
printf(" --temp-dir <dir> Temporary directory for files\n");
|
||||
printf(" --compare-dest <dir> Compare destination\n");
|
||||
printf(" --copy-dest <dir> Copy destination\n");
|
||||
|
||||
@@ -25,13 +25,16 @@
|
||||
|
||||
#define STREAM_THRESHOLD (64ULL * 1024 * 1024)
|
||||
|
||||
/* Forward declaration for progress-reporting thread used in multithreaded send. */
|
||||
static int progress_thread_fn(void* arg);
|
||||
|
||||
/* Print dry-run manifest showing files that would be transferred. Returns 0 on success. */
|
||||
static int send_dry_run_manifest(Config* config) {
|
||||
DirectoryScanner* scanner = directory_scanner_create(
|
||||
config->send_directory, config->use_metadata, config->chunk_size, config->exclude_patterns,
|
||||
config->exclude_count, config->include_patterns, config->include_count, config->max_size,
|
||||
config->min_size, config->max_depth, config->follow_symlinks, config->copy_links,
|
||||
config->safe_links, config->copy_unsafe_links);
|
||||
config->safe_links, config->copy_unsafe_links, config->checksum);
|
||||
if (!scanner)
|
||||
return -1;
|
||||
Chunk* chunk;
|
||||
@@ -271,6 +274,9 @@ static int send_chunks_multithreaded(void* pipeline_context) {
|
||||
if (context->config->transport == TRANSPORT_SSH) {
|
||||
if (context->config->use_sendfile) {
|
||||
fprintf(stderr, "Error: -f/--sendfile is not supported with SSH transport\n");
|
||||
mtx_lock(&context->mutex_progress);
|
||||
context->sender_done = true;
|
||||
mtx_unlock(&context->mutex_progress);
|
||||
return 1;
|
||||
}
|
||||
client = client_connect_ssh(context->config->ssh_destination, context->config->ssh_port,
|
||||
@@ -283,6 +289,9 @@ static int send_chunks_multithreaded(void* pipeline_context) {
|
||||
if (client)
|
||||
client_delete(client);
|
||||
fprintf(stderr, "Error: could not connect to server via TLS\n");
|
||||
mtx_lock(&context->mutex_progress);
|
||||
context->sender_done = true;
|
||||
mtx_unlock(&context->mutex_progress);
|
||||
return thrd_error;
|
||||
}
|
||||
} else {
|
||||
@@ -292,12 +301,18 @@ static int send_chunks_multithreaded(void* pipeline_context) {
|
||||
if (client)
|
||||
client_delete(client);
|
||||
fprintf(stderr, "Error: could not connect to server\n");
|
||||
mtx_lock(&context->mutex_progress);
|
||||
context->sender_done = true;
|
||||
mtx_unlock(&context->mutex_progress);
|
||||
return thrd_error;
|
||||
}
|
||||
}
|
||||
if (!config_send(client->file_descriptor, context->config)) {
|
||||
client_disconnect(client);
|
||||
client_delete(client);
|
||||
mtx_lock(&context->mutex_progress);
|
||||
context->sender_done = true;
|
||||
mtx_unlock(&context->mutex_progress);
|
||||
return thrd_error;
|
||||
}
|
||||
|
||||
@@ -316,19 +331,38 @@ static int send_chunks_multithreaded(void* pipeline_context) {
|
||||
int ok = receive_status(client->file_descriptor, &s) && s == STATUS_OK;
|
||||
client_disconnect(client);
|
||||
client_delete(client);
|
||||
mtx_lock(&context->mutex_progress);
|
||||
context->sender_done = true;
|
||||
mtx_unlock(&context->mutex_progress);
|
||||
return ok ? thrd_success : thrd_error;
|
||||
|
||||
send_fail:
|
||||
client_disconnect(client);
|
||||
client_delete(client);
|
||||
mtx_lock(&context->mutex_progress);
|
||||
context->sender_done = true;
|
||||
mtx_unlock(&context->mutex_progress);
|
||||
return thrd_error;
|
||||
}
|
||||
if (send_chunk(client, current_chunk, context->config) != 0) {
|
||||
fprintf(stderr, "Error: unexpected error while sending chunk\n");
|
||||
client_disconnect(client);
|
||||
client_delete(client);
|
||||
mtx_lock(&context->mutex_progress);
|
||||
context->sender_done = true;
|
||||
mtx_unlock(&context->mutex_progress);
|
||||
return thrd_error;
|
||||
}
|
||||
if (context->config->show_progress) {
|
||||
unsigned long long chunk_bytes = 0;
|
||||
for (int i = 0; i < current_chunk->element_count; i++) {
|
||||
if (current_chunk->items[i] && current_chunk->items[i]->data)
|
||||
chunk_bytes += current_chunk->items[i]->data->size;
|
||||
}
|
||||
mtx_lock(&context->mutex_progress);
|
||||
context->progress_bytes += chunk_bytes;
|
||||
mtx_unlock(&context->mutex_progress);
|
||||
}
|
||||
chunk_destroy(current_chunk);
|
||||
}
|
||||
}
|
||||
@@ -340,7 +374,8 @@ static int scan_directory_multithreaded(void* pipeline_context) {
|
||||
context->config->exclude_patterns, context->config->exclude_count,
|
||||
context->config->include_patterns, context->config->include_count, context->config->max_size,
|
||||
context->config->min_size, context->config->max_depth, 4, context->config->follow_symlinks,
|
||||
context->config->copy_links, context->config->safe_links, context->config->copy_unsafe_links);
|
||||
context->config->copy_links, context->config->safe_links, context->config->copy_unsafe_links,
|
||||
context->config->checksum);
|
||||
|
||||
Chunk* current_chunk;
|
||||
while ((current_chunk = parallel_scanner_next(scanner)) != NULL) {
|
||||
@@ -398,6 +433,42 @@ static int load_files_multithreaded(void* pipeline_context) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Progress-reporting thread for multithreaded send. Runs in parallel with
|
||||
the scanner/loader/sender threads and prints periodic progress to stderr. */
|
||||
static int progress_thread_fn(void* arg) {
|
||||
PipelineContextSender* context = (PipelineContextSender*)arg;
|
||||
time_t last_progress = 0;
|
||||
time_t start = time(NULL);
|
||||
|
||||
while (true) {
|
||||
mtx_lock(&context->mutex_progress);
|
||||
bool done = context->sender_done;
|
||||
unsigned long long total = context->progress_bytes;
|
||||
mtx_unlock(&context->mutex_progress);
|
||||
|
||||
if (done) {
|
||||
time_t now = time(NULL);
|
||||
double elapsed = difftime(now, start);
|
||||
double rate = elapsed > 0.0 ? total / (1048576.0 * elapsed) : 0.0;
|
||||
fprintf(stderr, "\rSent %.1f MB (%.1f MB/s) Done.\n", total / 1048576.0, rate);
|
||||
break;
|
||||
}
|
||||
|
||||
time_t now = time(NULL);
|
||||
if (now - last_progress >= 1) {
|
||||
last_progress = now;
|
||||
double elapsed = difftime(now, start);
|
||||
double rate = elapsed > 0.0 ? total / (1048576.0 * elapsed) : 0.0;
|
||||
fprintf(stderr, "\rSent %.1f MB (%.1f MB/s) ", total / 1048576.0, rate);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
struct timespec ts = {0, 100 * 1000000L}; /* 100 ms */
|
||||
thrd_sleep(&ts, NULL);
|
||||
}
|
||||
return thrd_success;
|
||||
}
|
||||
|
||||
int send_files(Config* config) {
|
||||
if (config->dry_run)
|
||||
return send_dry_run_manifest(config);
|
||||
@@ -439,9 +510,10 @@ int send_files(Config* config) {
|
||||
config->send_directory, config->use_metadata, config->chunk_size, config->exclude_patterns,
|
||||
config->exclude_count, config->include_patterns, config->include_count, config->max_size,
|
||||
config->min_size, config->max_depth, config->follow_symlinks, config->copy_links,
|
||||
config->safe_links, config->copy_unsafe_links);
|
||||
config->safe_links, config->copy_unsafe_links, config->checksum);
|
||||
Chunk* current_chunk;
|
||||
unsigned long long total_bytes = 0;
|
||||
int total_files = 0;
|
||||
time_t last_progress = 0;
|
||||
time_t start = time(NULL);
|
||||
ArrayList* manifest = config->use_delete ? array_list_create(free) : NULL;
|
||||
@@ -449,6 +521,7 @@ int send_files(Config* config) {
|
||||
unsigned long long chunk_bytes = 0;
|
||||
for (int i = 0; i < current_chunk->element_count; i++) {
|
||||
chunk_bytes += current_chunk->items[i]->data->size;
|
||||
total_files++;
|
||||
if (manifest) {
|
||||
const char* p = current_chunk->items[i]->path;
|
||||
if (*p == '/')
|
||||
@@ -496,11 +569,16 @@ int send_files(Config* config) {
|
||||
goto send_fail;
|
||||
Status s;
|
||||
int ok = receive_status(client->file_descriptor, &s) && s == STATUS_OK;
|
||||
double elapsed_total = difftime(time(NULL), start);
|
||||
if (config->show_progress) {
|
||||
double elapsed = difftime(time(NULL), start);
|
||||
double rate = elapsed > 0 ? total_bytes / (1048576.0 * elapsed) : 0;
|
||||
double rate = elapsed_total > 0 ? total_bytes / (1048576.0 * elapsed_total) : 0;
|
||||
fprintf(stderr, "\rSent %.1f MB (%.1f MB/s) Done.\n", total_bytes / 1048576.0, rate);
|
||||
}
|
||||
if (config->stats) {
|
||||
double rate = elapsed_total > 0 ? total_bytes / (1048576.0 * elapsed_total) : 0;
|
||||
fprintf(stderr, "Stats: %d files, %.1f MB, %.1f MB/s\n", total_files, total_bytes / 1048576.0,
|
||||
rate);
|
||||
}
|
||||
directory_scanner_destroy(scanner);
|
||||
client_disconnect(client);
|
||||
client_delete(client);
|
||||
@@ -547,7 +625,7 @@ int send_files_multithreaded(Config* config) {
|
||||
if (config->use_delete)
|
||||
context->manifest = array_list_create(free);
|
||||
|
||||
thrd_t scanner, loader, sender;
|
||||
thrd_t scanner, loader, sender, progress;
|
||||
if (thrd_create(&scanner, scan_directory_multithreaded, context) != thrd_success ||
|
||||
thrd_create(&loader, load_files_multithreaded, context) != thrd_success ||
|
||||
thrd_create(&sender, send_chunks_multithreaded, context) != thrd_success) {
|
||||
@@ -556,11 +634,26 @@ int send_files_multithreaded(Config* config) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (config->show_progress) {
|
||||
if (thrd_create(&progress, progress_thread_fn, context) != thrd_success) {
|
||||
perror("Error creating progress thread.\n");
|
||||
/* Non-fatal; continue without progress reporting */
|
||||
}
|
||||
}
|
||||
|
||||
int sender_result;
|
||||
thrd_join(scanner, NULL);
|
||||
thrd_join(loader, NULL);
|
||||
thrd_join(sender, &sender_result);
|
||||
|
||||
if (config->show_progress) {
|
||||
/* Signal progress thread to exit if it hasn't already */
|
||||
mtx_lock(&context->mutex_progress);
|
||||
context->sender_done = true;
|
||||
mtx_unlock(&context->mutex_progress);
|
||||
thrd_join(progress, NULL);
|
||||
}
|
||||
|
||||
pipeline_context_sender_destroy(context);
|
||||
return sender_result == thrd_success ? 0 : 1;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ DirectoryScanner* directory_scanner_create(const char* root_directory, bool use_
|
||||
int include_count, unsigned long long max_size,
|
||||
unsigned long long min_size, int max_depth,
|
||||
bool follow_symlinks, bool copy_links, bool safe_links,
|
||||
bool copy_unsafe_links) {
|
||||
bool copy_unsafe_links, bool checksum) {
|
||||
DirectoryScanner* scanner = malloc(sizeof(DirectoryScanner));
|
||||
if (scanner == NULL)
|
||||
return NULL;
|
||||
@@ -61,6 +61,7 @@ DirectoryScanner* directory_scanner_create(const char* root_directory, bool use_
|
||||
scanner->copy_links = copy_links;
|
||||
scanner->safe_links = safe_links;
|
||||
scanner->copy_unsafe_links = copy_unsafe_links;
|
||||
scanner->checksum = checksum;
|
||||
queue_enqueue(scanner->directories, dir_entry_create(root_directory, 0));
|
||||
return scanner;
|
||||
}
|
||||
@@ -276,6 +277,7 @@ typedef struct {
|
||||
bool copy_links;
|
||||
bool safe_links;
|
||||
bool copy_unsafe_links;
|
||||
bool checksum;
|
||||
} ParallelWorkerArg;
|
||||
|
||||
static int parallel_worker_thread(void* arg) {
|
||||
@@ -284,7 +286,7 @@ static int parallel_worker_thread(void* arg) {
|
||||
DirectoryScanner* ds = directory_scanner_create(
|
||||
wa->dirs[i], wa->use_metadata, wa->chunk_size, wa->exclude_patterns, wa->exclude_count,
|
||||
wa->include_patterns, wa->include_count, wa->max_size, wa->min_size, wa->max_depth,
|
||||
wa->follow_symlinks, wa->copy_links, wa->safe_links, wa->copy_unsafe_links);
|
||||
wa->follow_symlinks, wa->copy_links, wa->safe_links, wa->copy_unsafe_links, wa->checksum);
|
||||
Chunk* chunk;
|
||||
while ((chunk = directory_scanner_next(ds)) != NULL) {
|
||||
queue_enqueue_multithreaded(wa->ps->result_queue, chunk, &wa->ps->result_mutex,
|
||||
@@ -312,7 +314,7 @@ ParallelScanner* parallel_scanner_create(char* root_directory, bool use_metadata
|
||||
int include_count, unsigned long long max_size,
|
||||
unsigned long long min_size, int max_depth,
|
||||
int num_threads, bool follow_symlinks, bool copy_links,
|
||||
bool safe_links, bool copy_unsafe_links) {
|
||||
bool safe_links, bool copy_unsafe_links, bool checksum) {
|
||||
ParallelScanner* ps = calloc(1, sizeof(ParallelScanner));
|
||||
if (!ps)
|
||||
return NULL;
|
||||
@@ -353,8 +355,7 @@ ParallelScanner* parallel_scanner_create(char* root_directory, bool use_metadata
|
||||
bool is_symlink = S_ISLNK(lstats.st_mode);
|
||||
|
||||
// Skip symlinks unless the user explicitly enabled following/copying them.
|
||||
if (is_symlink && !follow_symlinks && !copy_links && !safe_links &&
|
||||
!copy_unsafe_links) {
|
||||
if (is_symlink && !follow_symlinks && !copy_links && !safe_links && !copy_unsafe_links) {
|
||||
free(cur_path);
|
||||
continue;
|
||||
}
|
||||
@@ -367,7 +368,7 @@ ParallelScanner* parallel_scanner_create(char* root_directory, bool use_metadata
|
||||
free(cur_path);
|
||||
continue;
|
||||
}
|
||||
link_target[len] = ' | ||||