Merge remote-tracking branch 'origin/fix/features-v2' into integration/all-fixes-v2
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user