Merge remote-tracking branch 'origin/fix/features-v2' into integration/all-fixes-v2

This commit is contained in:
2026-07-30 19:04:12 +02:00
12 changed files with 138 additions and 41 deletions
+80
View File
@@ -402,6 +402,72 @@ static int parse_args(Config* config, int argc, char* argv[], int* positional_ar
return -1;
free(config->link_dest);
config->link_dest = dup;
} else if (strcmp(argv[i], "--partial-dir") == 0 && i + 1 < argc) {
char* dup = str_dup(argv[++i]);
if (!dup) {
fprintf(stderr, "Error: memory allocation failed for --partial-dir\n");
return -1;
}
free(config->partial_dir);
config->partial_dir = dup;
} else if (strcmp(argv[i], "--suffix") == 0 && i + 1 < argc) {
char* dup = str_dup(argv[++i]);
if (!dup) {
fprintf(stderr, "Error: memory allocation failed for --suffix\n");
return -1;
}
free(config->suffix);
config->suffix = dup;
} else if (strcmp(argv[i], "--delete-before") == 0) {
config->delete_before = true;
} else if (strcmp(argv[i], "-T") == 0 && i + 1 < argc) {
int val;
if (!parse_positive_int(argv[++i], &val)) {
fprintf(stderr, "Error: -T must be a positive integer\n");
return -1;
}
config->timeout = val;
} else if (strcmp(argv[i], "--address") == 0 && i + 1 < argc) {
char* dup = str_dup(argv[++i]);
if (!dup)
return -1;
free(config->address);
config->address = dup;
} else if (strcmp(argv[i], "--bind-address") == 0 && i + 1 < argc) {
char* dup = str_dup(argv[++i]);
if (!dup)
return -1;
free(config->bind_address);
config->bind_address = dup;
} 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) {
char* dup = str_dup(argv[++i]);
if (!dup)
return -1;
free(config->daemon_config);
config->daemon_config = dup;
} 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) {
char* dup = str_dup(argv[++i]);
if (!dup)
return -1;
free(config->compress_choice);
config->compress_choice = dup;
} else if (strcmp(argv[i], "--compress-level") == 0 && i + 1 < argc) {
int val;
if (!parse_positive_int(argv[++i], &val)) {
fprintf(stderr, "Error: --compress-level must be a positive integer\n");
return -1;
}
config->compression_level = val;
} else if (argv[i][0] == '-') {
fprintf(stderr, "Unknown option: %s\n", argv[i]);
print_usage();
@@ -507,16 +573,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 +611,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 +622,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");
+12 -5
View File
@@ -34,7 +34,7 @@ static int send_dry_run_manifest(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, false);
config->safe_links, config->copy_unsafe_links, config->checksum);
if (!scanner)
return -1;
Chunk* chunk;
@@ -374,7 +374,7 @@ 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, false);
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) {
@@ -509,9 +509,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, false);
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;
@@ -519,6 +520,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 == '/')
@@ -566,11 +568,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);
+26 -16
View File
@@ -152,9 +152,6 @@ void config_delete(Config* config) {
free(config->rsh_command);
free(config->rsync_path);
free(config->temp_dir);
free(config->partial_dir);
free(config->suffix);
free(config->compress_choice);
free(config->compare_dest);
free(config->copy_dest);
free(config->link_dest);
@@ -245,7 +242,6 @@ bool config_send(int file_descriptor, const Config* config) {
return false;
if (!send_str(file_descriptor, config->temp_dir ? config->temp_dir : ""))
return false;
Status status;
if (!send_int(file_descriptor, config->partial))
return false;
if (!send_str(file_descriptor, config->partial_dir ? config->partial_dir : ""))
@@ -258,6 +254,7 @@ bool config_send(int file_descriptor, const Config* config) {
return false;
if (!send_str(file_descriptor, config->compress_choice ? config->compress_choice : ""))
return false;
Status status;
if (!receive_status(file_descriptor, &status))
return false;
if (status != STATUS_OK) {
@@ -392,18 +389,6 @@ Config* config_receive(int file_descriptor) {
config->compare_dest = NULL;
config->copy_dest = NULL;
config->link_dest = NULL;
config->partial_dir = NULL;
config->suffix = NULL;
config->delete_before = false;
config->address = NULL;
config->bind_address = NULL;
config->ipv6 = false;
config->ipv4 = false;
config->daemon = false;
config->daemon_config = NULL;
config->server_mode = false;
config->checksum = false;
config->compress_choice = NULL;
if (!receive_int(file_descriptor, &tmp))
goto error;
config->backup = tmp;
@@ -466,6 +451,31 @@ Config* config_receive(int file_descriptor) {
config->temp_dir = receive_str(file_descriptor);
if (config->temp_dir == NULL)
goto error;
if (!receive_int(file_descriptor, &tmp))
goto error;
config->partial = tmp;
config->partial_dir = receive_str(file_descriptor);
if (config->partial_dir == NULL)
goto error;
config->suffix = receive_str(file_descriptor);
if (config->suffix == NULL)
goto error;
if (!receive_int(file_descriptor, &tmp))
goto error;
config->delete_before = tmp;
if (!receive_int(file_descriptor, &tmp))
goto error;
config->checksum = tmp;
config->compress_choice = receive_str(file_descriptor);
if (config->compress_choice == NULL)
goto error;
config->address = NULL;
config->bind_address = NULL;
config->ipv6 = false;
config->ipv4 = false;
config->daemon = false;
config->daemon_config = NULL;
config->server_mode = false;
config->server_host = str_dup("127.0.0.1");
config->server_port = 8080;
if (!send_status(file_descriptor, STATUS_OK))
+3 -3
View File
@@ -11,7 +11,7 @@ static void test_file_operations() {
char* test_content = "Hello, Chunk System!";
unsigned long long test_len = strlen(test_content);
to_disk(test_path, test_content, test_len);
to_disk(test_path, test_content, test_len, false, false);
File* f = file_create(test_path);
EXPECT_NOT_NULL(f);
@@ -43,8 +43,8 @@ static void test_chunk_operations() {
char* content2 = "chunk item number 2";
unsigned long long len2 = strlen(content2);
to_disk(path1, content1, len1);
to_disk(path2, content2, len2);
to_disk(path1, content1, len1, false, false);
to_disk(path2, content2, len2, false, false);
struct stat st1, st2;
stat(path1, &st1);
+2 -2
View File
@@ -62,8 +62,8 @@ static void test_chunk_compress_decompress_roundtrip() {
char* content2 = "chunk compression test file 2 with more data";
unsigned long long len2 = strlen(content2);
to_disk(path1, content1, len1);
to_disk(path2, content2, len2);
to_disk(path1, content1, len1, false, false);
to_disk(path2, content2, len2, false, false);
struct stat st1, st2;
EXPECT_EQ_INT(stat(path1, &st1), 0);
+6 -6
View File
@@ -34,7 +34,7 @@ static void test_file_destroy_normal() {
static void test_file_load_data() {
const char* content = "Hello Load Test";
EXPECT_TRUE(to_disk("test_file_load_data.txt", content, strlen(content)));
EXPECT_TRUE(to_disk("test_file_load_data.txt", content, strlen(content), false, false));
struct stat st;
EXPECT_EQ_INT(stat("test_file_load_data.txt", &st), 0);
@@ -89,7 +89,7 @@ static void test_file_save_to_disk() {
static void test_to_disk_basic() {
const char* content = "Basic to_disk test";
EXPECT_TRUE(to_disk("test_to_disk_basic.txt", content, strlen(content)));
EXPECT_TRUE(to_disk("test_to_disk_basic.txt", content, strlen(content), false, false));
struct stat st;
EXPECT_EQ_INT(stat("test_to_disk_basic.txt", &st), 0);
@@ -108,7 +108,7 @@ static void test_to_disk_basic() {
static void test_to_disk_creates_dirs() {
const char* content = "Nested dir test";
EXPECT_TRUE(to_disk("test_nested_tmp/nested/file.txt", content, strlen(content)));
EXPECT_TRUE(to_disk("test_nested_tmp/nested/file.txt", content, strlen(content), false, false));
struct stat st;
EXPECT_EQ_INT(stat("test_nested_tmp/nested/file.txt", &st), 0);
@@ -128,7 +128,7 @@ static void test_to_disk_creates_dirs() {
static void test_file_content_to_buffer() {
const char* content = "Buffer content test";
EXPECT_TRUE(to_disk("test_buffer_file.txt", content, strlen(content)));
EXPECT_TRUE(to_disk("test_buffer_file.txt", content, strlen(content), false, false));
File* f = file_create("test_buffer_file.txt");
EXPECT_NOT_NULL(f);
@@ -250,7 +250,7 @@ static void test_file_send_no_path() {
}
static void test_file_metadata_create() {
EXPECT_TRUE(to_disk("test_meta_file.txt", "metadata test", 13));
EXPECT_TRUE(to_disk("test_meta_file.txt", "metadata test", 13, false, false));
struct stat st;
EXPECT_EQ_INT(stat("test_meta_file.txt", &st), 0);
@@ -359,7 +359,7 @@ static void test_file_send_single_calls_metadata_and_path() {
/* Create a real file on disk so we can have metadata */
const char* content = "File with metadata";
size_t len = strlen(content);
EXPECT_TRUE(to_disk("test_meta_send.txt", content, len));
EXPECT_TRUE(to_disk("test_meta_send.txt", content, len, false, false));
struct stat st;
EXPECT_EQ_INT(stat("test_meta_send.txt", &st), 0);
+4 -4
View File
@@ -15,7 +15,7 @@
static void test_sendfile_basic() {
const char* content = "Hello from sendfile test!";
size_t len = strlen(content);
EXPECT_TRUE(to_disk("test_sendfile_basic.txt", content, len));
EXPECT_TRUE(to_disk("test_sendfile_basic.txt", content, len, false, false));
File* file = file_create("test_sendfile_basic.txt");
EXPECT_NOT_NULL(file);
@@ -77,7 +77,7 @@ static void test_sendfile_basic() {
static void test_sendfile_empty_file() {
const char* content = "";
size_t len = 0;
EXPECT_TRUE(to_disk("test_sendfile_empty.txt", content, len));
EXPECT_TRUE(to_disk("test_sendfile_empty.txt", content, len, false, false));
File* file = file_create("test_sendfile_empty.txt");
EXPECT_NOT_NULL(file);
@@ -156,7 +156,7 @@ static void test_sendfile_missing_file() {
static void test_sendfile_compression_fallback() {
const char* content = "Compression fallback content";
size_t len = strlen(content);
EXPECT_TRUE(to_disk("test_sendfile_comp.txt", content, len));
EXPECT_TRUE(to_disk("test_sendfile_comp.txt", content, len, false, false));
struct stat st;
EXPECT_EQ_INT(stat("test_sendfile_comp.txt", &st), 0);
@@ -222,7 +222,7 @@ static void test_sendfile_compression_fallback() {
static void test_sendfile_no_path() {
const char* content = "No path sendfile test";
size_t len = strlen(content);
EXPECT_TRUE(to_disk("test_sendfile_nopath.txt", content, len));
EXPECT_TRUE(to_disk("test_sendfile_nopath.txt", content, len, false, false));
File* file = file_create("test_sendfile_nopath.txt");
EXPECT_NOT_NULL(file);
+1 -1
View File
@@ -101,7 +101,7 @@ static void test_fuzz_delta_deserialize() {
/* Smoke test for metadata_from_buf fuzz target */
static void test_fuzz_metadata_from_buf() {
/* Create a real file to get metadata from */
EXPECT_TRUE(to_disk("fuzz_meta_test.txt", "metadata test", 13));
EXPECT_TRUE(to_disk("fuzz_meta_test.txt", "metadata test", 13, false, false));
struct stat st;
EXPECT_EQ_INT(stat("fuzz_meta_test.txt", &st), 0);
+1 -1
View File
@@ -112,7 +112,7 @@ static void test_metadata_send_null() {
static void test_file_restore_metadata() {
const char* path = "temp_meta_restore_test.txt";
const char* content = "test content";
EXPECT_TRUE(to_disk(path, content, strlen(content)));
EXPECT_TRUE(to_disk(path, content, strlen(content), false, false));
FileMetadata m;
m.mode = 0644;
+1 -1
View File
@@ -84,7 +84,7 @@ static void test_property_chunk_roundtrip() {
for (int i = 0; i < content_len; i++)
content[i] = (char)(rand() % 256);
to_disk(path, content, content_len);
to_disk(path, content, content_len, false, false);
struct stat st;
stat(path, &st);
+1 -1
View File
@@ -13,7 +13,7 @@
static void test_chunk_deserialize_truncated() {
char* path = "test_rob_trunc.txt";
char* content = "hello";
to_disk(path, content, strlen(content));
to_disk(path, content, strlen(content), false, false);
struct stat st;
stat(path, &st);
+1 -1
View File
@@ -7,7 +7,7 @@
#include <unistd.h>
static void create_test_file(const char* path, const char* content) {
(void)to_disk(path, content, strlen(content));
(void)to_disk(path, content, strlen(content), false, false);
}
static void test_scanner_single_file() {