diff --git a/src/client/client_cli.c b/src/client/client_cli.c index ba17a21..88b37e4 100644 --- a/src/client/client_cli.c +++ b/src/client/client_cli.c @@ -71,6 +71,39 @@ static void print_usage(void) { printf(" --partial Keep partial files on interrupted transfer\n"); printf(" --fastsync-server-path \n"); printf(" Path to fastsync-server on remote (default: fastsync-server)\n"); + printf(" -l, --links Copy symlinks as symlinks\n"); + printf(" --copy-links Transform symlinks into referent files\n"); + printf(" --safe-links Skip symlinks that point outside transfer tree\n"); + printf(" --copy-unsafe-links Only transform unsafe symlinks into referent files\n"); + printf(" -H, --hard-links Preserve hard links\n"); + printf(" -A, --acls Preserve ACLs\n"); + printf(" -X, --xattrs Preserve extended attributes\n"); + printf(" -D, --devices Preserve device files\n"); + printf(" -S, --sparse Handle sparse files efficiently\n"); + printf(" -i, --itemize-changes Show per-file change summary\n"); + printf(" --out-format Custom output format string\n"); + printf(" --info Info verbosity level\n"); + printf(" --debug Debug verbosity level\n"); + printf(" --list-only List files without transferring\n"); + printf(" -h, --human-readable Human-readable numbers\n"); + printf(" -u, --update Skip files newer on destination\n"); + 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-excluded Also delete excluded files\n"); + printf(" --delete-after Delete after transfer, not before\n"); + printf(" --max-delete Maximum number of files to delete\n"); + printf(" --filter Add file filtering rule\n"); + printf(" --files-from Read file list from file\n"); + printf(" --cvs-exclude Auto-ignore CVS files\n"); + printf(" --prune-empty-dirs Omit empty directories from transfer\n"); + printf(" -R, --relative Use relative paths\n"); + printf(" -e, --rsh Specify remote shell\n"); + printf(" --rsync-path Path to remote binary\n"); + printf(" --temp-dir Temporary directory for files\n"); + printf(" --compare-dest Compare destination\n"); + printf(" --copy-dest Copy destination\n"); + printf(" --link-dest Link destination\n"); printf(" --help Show this help\n"); } @@ -318,6 +351,103 @@ int main(int argc, char* argv[]) { config->fastsync_server_path = str_dup(argv[++i]); } 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) { + config->follow_symlinks = true; + log_message(LOG_LEVEL_INFO, "Enabled symlink preservation"); + } else if (strcmp(argv[i], "--copy-links") == 0) { + config->copy_links = true; + log_message(LOG_LEVEL_INFO, "Enabled copy-links (dereference symlinks)"); + } else if (strcmp(argv[i], "--safe-links") == 0) { + config->safe_links = true; + log_message(LOG_LEVEL_INFO, "Enabled safe-links (skip unsafe symlinks)"); + } else if (strcmp(argv[i], "--copy-unsafe-links") == 0) { + config->copy_unsafe_links = true; + log_message(LOG_LEVEL_INFO, "Enabled copy-unsafe-links"); + } else if (strcmp(argv[i], "-H") == 0 || strcmp(argv[i], "--hard-links") == 0) { + config->preserve_hard_links = true; + log_message(LOG_LEVEL_INFO, "Enabled hard link preservation"); + } else if (strcmp(argv[i], "-A") == 0 || strcmp(argv[i], "--acls") == 0) { + config->preserve_acls = true; + log_message(LOG_LEVEL_INFO, "Enabled ACL preservation"); + } else if (strcmp(argv[i], "-X") == 0 || strcmp(argv[i], "--xattrs") == 0) { + config->preserve_xattrs = true; + log_message(LOG_LEVEL_INFO, "Enabled xattr preservation"); + } else if (strcmp(argv[i], "-D") == 0 || strcmp(argv[i], "--devices") == 0) { + config->preserve_devices = true; + log_message(LOG_LEVEL_INFO, "Enabled device file preservation"); + } else if (strcmp(argv[i], "-S") == 0 || strcmp(argv[i], "--sparse") == 0) { + config->preserve_sparse = true; + log_message(LOG_LEVEL_INFO, "Enabled sparse file handling"); + } 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) { + free(config->out_format); + config->out_format = str_dup(argv[++i]); + } else if (strcmp(argv[i], "--info") == 0 && i + 1 < argc) { + config->info_level = atoi(argv[++i]); + } else if (strcmp(argv[i], "--debug") == 0 && i + 1 < argc) { + config->debug_level = atoi(argv[++i]); + } 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) { + config->human_readable = true; + } else if (strcmp(argv[i], "-u") == 0 || strcmp(argv[i], "--update") == 0) { + config->update = true; + } else if (strcmp(argv[i], "--inplace") == 0) { + config->inplace = true; + } else if (strcmp(argv[i], "--append") == 0) { + config->append = true; + } else if (strcmp(argv[i], "--append-verify") == 0) { + config->append_verify = true; + } else if (strcmp(argv[i], "--delete-excluded") == 0) { + config->delete_excluded = true; + } else if (strcmp(argv[i], "--delete-after") == 0) { + config->delete_after = true; + } else if (strcmp(argv[i], "--max-delete") == 0 && i + 1 < argc) { + config->max_delete = atoi(argv[++i]); + if (config->max_delete < 0) { + fprintf(stderr, "Error: --max-delete must be a non-negative integer\n"); + exit_code = 1; + goto cleanup; + } + } else if (strcmp(argv[i], "--filter") == 0 && i + 1 < argc) { + if (!config->filters) { + config->filters = array_list_create(free); + } + array_list_add(config->filters, str_dup(argv[++i])); + } else if (strcmp(argv[i], "--files-from") == 0 && i + 1 < argc) { + free(config->files_from); + config->files_from = str_dup(argv[++i]); + } else if (strcmp(argv[i], "--cvs-exclude") == 0) { + config->cvs_exclude = true; + } else if (strcmp(argv[i], "--prune-empty-dirs") == 0) { + config->prune_empty_dirs = true; + } else if (strcmp(argv[i], "-R") == 0 || strcmp(argv[i], "--relative") == 0) { + config->relative = true; + } else if (strcmp(argv[i], "-e") == 0 || strcmp(argv[i], "--rsh") == 0) { + if (i + 1 < argc) { + free(config->rsh_command); + config->rsh_command = str_dup(argv[++i]); + } else { + fprintf(stderr, "Error: -e/--rsh requires a command argument\n"); + exit_code = 1; + goto cleanup; + } + } else if (strcmp(argv[i], "--rsync-path") == 0 && i + 1 < argc) { + free(config->rsync_path); + config->rsync_path = str_dup(argv[++i]); + } else if (strcmp(argv[i], "--temp-dir") == 0 && i + 1 < argc) { + free(config->temp_dir); + config->temp_dir = str_dup(argv[++i]); + } else if (strcmp(argv[i], "--compare-dest") == 0 && i + 1 < argc) { + free(config->compare_dest); + config->compare_dest = str_dup(argv[++i]); + } else if (strcmp(argv[i], "--copy-dest") == 0 && i + 1 < argc) { + free(config->copy_dest); + config->copy_dest = str_dup(argv[++i]); + } else if (strcmp(argv[i], "--link-dest") == 0 && i + 1 < argc) { + free(config->link_dest); + config->link_dest = str_dup(argv[++i]); } else if (argv[i][0] == '-') { fprintf(stderr, "Unknown option: %s\n", argv[i]); print_usage(); diff --git a/src/client/client_send.c b/src/client/client_send.c index adc582c..9918173 100644 --- a/src/client/client_send.c +++ b/src/client/client_send.c @@ -306,7 +306,9 @@ static int scan_directory_multithreaded(void* pipeline_context) { context->config->send_directory, context->config->use_metadata, context->config->chunk_size, 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->min_size, context->config->max_depth, 4, + context->config->follow_symlinks, context->config->copy_links, + context->config->safe_links, context->config->copy_unsafe_links); Chunk* current_chunk; while ((current_chunk = parallel_scanner_next(scanner)) != NULL) { @@ -369,7 +371,8 @@ int send_files(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->min_size, config->max_depth, config->follow_symlinks, config->copy_links, + config->safe_links, config->copy_unsafe_links); Chunk* chunk; int file_count = 0; unsigned long long total_bytes = 0; @@ -423,7 +426,8 @@ int send_files(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->min_size, config->max_depth, config->follow_symlinks, config->copy_links, + config->safe_links, config->copy_unsafe_links); Chunk* current_chunk; unsigned long long total_bytes = 0; time_t last_progress = 0; @@ -512,7 +516,8 @@ int send_files_multithreaded(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->min_size, config->max_depth, config->follow_symlinks, config->copy_links, + config->safe_links, config->copy_unsafe_links); Chunk* chunk; int file_count = 0; unsigned long long total_bytes = 0; diff --git a/src/client/scanner.c b/src/client/scanner.c index 76fbd78..e696503 100644 --- a/src/client/scanner.c +++ b/src/client/scanner.c @@ -35,10 +35,12 @@ static DirEntry* dir_entry_create(const char* path, int depth) { } DirectoryScanner* directory_scanner_create(const char* root_directory, bool use_metadata, - unsigned long long chunk_size, char** exclude_patterns, - int exclude_count, char** include_patterns, - int include_count, unsigned long long max_size, - unsigned long long min_size, int max_depth) { + unsigned long long chunk_size, char** exclude_patterns, + int exclude_count, char** include_patterns, + 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) { DirectoryScanner* scanner = malloc(sizeof(DirectoryScanner)); if (scanner == NULL) return NULL; @@ -55,6 +57,10 @@ DirectoryScanner* directory_scanner_create(const char* root_directory, bool use_ scanner->min_size = min_size; scanner->max_depth = max_depth; scanner->current_depth = 0; + scanner->follow_symlinks = follow_symlinks; + scanner->copy_links = copy_links; + scanner->safe_links = safe_links; + scanner->copy_unsafe_links = copy_unsafe_links; queue_enqueue(scanner->directories, dir_entry_create(root_directory, 0)); return scanner; } @@ -131,10 +137,57 @@ Chunk* directory_scanner_next(DirectoryScanner* scanner) { char* cur_path = path_cat(scanner->current_path, entry->d_name); struct stat stats; - if (stat(cur_path, &stats) != 0) { + struct stat lstats; + bool is_symlink = false; + if (lstat(cur_path, &lstats) != 0) { free(cur_path); continue; } + is_symlink = S_ISLNK(lstats.st_mode); + + if (is_symlink && !scanner->follow_symlinks && !scanner->copy_links && !scanner->safe_links && !scanner->copy_unsafe_links) { + free(cur_path); + continue; + } + + if (is_symlink && scanner->safe_links) { + char link_target[4096]; + ssize_t len = readlink(cur_path, link_target, sizeof(link_target) - 1); + if (len < 0) { + free(cur_path); + continue; + } + link_target[len] = '\0'; + if (link_target[0] == '/') { + free(cur_path); + continue; + } + } + + if (is_symlink && scanner->copy_unsafe_links && !scanner->copy_links) { + char link_target[4096]; + ssize_t len = readlink(cur_path, link_target, sizeof(link_target) - 1); + if (len < 0) { + free(cur_path); + continue; + } + link_target[len] = '\0'; + bool unsafe = (link_target[0] == '/'); + if (!unsafe) { + free(cur_path); + continue; + } + } + + bool use_lstat = is_symlink && scanner->follow_symlinks && !scanner->copy_links; + if (use_lstat) { + stats = lstats; + } else { + if (stat(cur_path, &stats) != 0) { + free(cur_path); + continue; + } + } if (S_ISDIR(stats.st_mode)) { int next_depth = scanner->current_depth + 1; @@ -218,6 +271,10 @@ typedef struct { 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; } ParallelWorkerArg; static int parallel_worker_thread(void* arg) { @@ -225,7 +282,8 @@ static int parallel_worker_thread(void* arg) { for (int i = 0; i < wa->dir_count; i++) { 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->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); Chunk* chunk; while ((chunk = directory_scanner_next(ds)) != NULL) { queue_enqueue_multithreaded(wa->ps->result_queue, chunk, &wa->ps->result_mutex, @@ -248,11 +306,13 @@ static int parallel_worker_thread(void* arg) { } ParallelScanner* parallel_scanner_create(char* root_directory, bool use_metadata, - unsigned long long chunk_size, char** exclude_patterns, - int exclude_count, char** include_patterns, - int include_count, unsigned long long max_size, - unsigned long long min_size, int max_depth, - int num_threads) { + unsigned long long chunk_size, char** exclude_patterns, + int exclude_count, char** include_patterns, + 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) { ParallelScanner* ps = calloc(1, sizeof(ParallelScanner)); if (!ps) return NULL; @@ -411,6 +471,10 @@ ParallelScanner* parallel_scanner_create(char* root_directory, bool use_metadata wa->max_size = max_size; wa->min_size = min_size; wa->max_depth = max_depth; + wa->follow_symlinks = follow_symlinks; + wa->copy_links = copy_links; + wa->safe_links = safe_links; + wa->copy_unsafe_links = copy_unsafe_links; start += count; if (thrd_create(&ps->threads[t], parallel_worker_thread, wa) != thrd_success) { for (int j = 0; j < count; j++) diff --git a/src/client/scanner.h b/src/client/scanner.h index 1a8c4dd..17bcf8f 100644 --- a/src/client/scanner.h +++ b/src/client/scanner.h @@ -21,6 +21,10 @@ typedef struct { unsigned long long min_size; int max_depth; int current_depth; + bool follow_symlinks; + bool copy_links; + bool safe_links; + bool copy_unsafe_links; } DirectoryScanner; typedef struct { @@ -36,19 +40,23 @@ typedef struct { } ParallelScanner; DirectoryScanner* directory_scanner_create(const char* root_directory, bool use_metadata, - unsigned long long chunk_size, char** exclude_patterns, - int exclude_count, char** include_patterns, - int include_count, unsigned long long max_size, - unsigned long long min_size, int max_depth); + unsigned long long chunk_size, char** exclude_patterns, + int exclude_count, char** include_patterns, + 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); Chunk* directory_scanner_next(DirectoryScanner* scanner); void directory_scanner_destroy(DirectoryScanner* scanner); ParallelScanner* parallel_scanner_create(char* root_directory, bool use_metadata, - unsigned long long chunk_size, char** exclude_patterns, - int exclude_count, char** include_patterns, - int include_count, unsigned long long max_size, - unsigned long long min_size, int max_depth, - int num_threads); + unsigned long long chunk_size, char** exclude_patterns, + int exclude_count, char** include_patterns, + 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); Chunk* parallel_scanner_next(ParallelScanner* scanner); void parallel_scanner_destroy(ParallelScanner* scanner); diff --git a/src/shared/config.c b/src/shared/config.c index ae8936b..f11a003 100644 --- a/src/shared/config.c +++ b/src/shared/config.c @@ -59,6 +59,40 @@ Config* config_create(char* version, char* send_directory, char* receive_directo config->max_depth = 0; config->log_file = NULL; config->queue_size = 100; + config->follow_symlinks = false; + config->partial = false; + config->copy_links = false; + config->safe_links = false; + config->copy_unsafe_links = false; + config->preserve_hard_links = false; + config->preserve_acls = false; + config->preserve_xattrs = false; + config->preserve_devices = false; + config->preserve_sparse = false; + config->itemize_changes = false; + config->out_format = NULL; + config->info_level = 0; + config->debug_level = 0; + config->list_only = false; + config->human_readable = false; + config->update = false; + config->inplace = false; + config->append = false; + config->append_verify = false; + config->delete_excluded = false; + config->delete_after = false; + config->max_delete = 0; + config->filters = NULL; + config->files_from = NULL; + config->cvs_exclude = false; + config->prune_empty_dirs = false; + config->relative = false; + config->rsh_command = NULL; + config->rsync_path = NULL; + config->temp_dir = NULL; + config->compare_dest = NULL; + config->copy_dest = NULL; + config->link_dest = NULL; return config; } @@ -105,6 +139,17 @@ void config_delete(Config* config) { free(config->tls_ca); free(config->backup_dir); free(config->server_host); + free(config->out_format); + free(config->files_from); + free(config->rsh_command); + free(config->rsync_path); + free(config->temp_dir); + free(config->compare_dest); + free(config->copy_dest); + free(config->link_dest); + if (config->filters) { + array_list_delete(config->filters); + } free(config); } @@ -145,6 +190,44 @@ bool config_send(int file_descriptor, const Config* config) { return false; if (!send_str(file_descriptor, config->backup_dir ? config->backup_dir : "")) return false; + if (!send_int(file_descriptor, config->follow_symlinks)) + return false; + if (!send_int(file_descriptor, config->copy_links)) + return false; + if (!send_int(file_descriptor, config->safe_links)) + return false; + if (!send_int(file_descriptor, config->copy_unsafe_links)) + return false; + if (!send_int(file_descriptor, config->preserve_hard_links)) + return false; + if (!send_int(file_descriptor, config->preserve_acls)) + return false; + if (!send_int(file_descriptor, config->preserve_xattrs)) + return false; + if (!send_int(file_descriptor, config->preserve_devices)) + return false; + if (!send_int(file_descriptor, config->preserve_sparse)) + return false; + if (!send_int(file_descriptor, config->update)) + return false; + if (!send_int(file_descriptor, config->inplace)) + return false; + if (!send_int(file_descriptor, config->append)) + return false; + if (!send_int(file_descriptor, config->append_verify)) + return false; + if (!send_int(file_descriptor, config->delete_excluded)) + return false; + if (!send_int(file_descriptor, config->delete_after)) + return false; + if (!send_n_data(file_descriptor, &config->max_delete, sizeof(config->max_delete))) + return false; + if (!send_int(file_descriptor, config->relative)) + return false; + if (!send_int(file_descriptor, config->prune_empty_dirs)) + return false; + if (!send_str(file_descriptor, config->temp_dir ? config->temp_dir : "")) + return false; Status status; if (!receive_status(file_descriptor, &status)) return false; @@ -247,12 +330,101 @@ Config* config_receive(int file_descriptor) { config->max_depth = 0; config->log_file = NULL; config->queue_size = 100; + config->follow_symlinks = false; + config->copy_links = false; + config->safe_links = false; + config->copy_unsafe_links = false; + config->preserve_hard_links = false; + config->preserve_acls = false; + config->preserve_xattrs = false; + config->preserve_devices = false; + config->preserve_sparse = false; + config->itemize_changes = false; + config->out_format = NULL; + config->info_level = 0; + config->debug_level = 0; + config->list_only = false; + config->human_readable = false; + config->update = false; + config->inplace = false; + config->append = false; + config->append_verify = false; + config->delete_excluded = false; + config->delete_after = false; + config->max_delete = 0; + config->filters = NULL; + config->files_from = NULL; + config->cvs_exclude = false; + config->prune_empty_dirs = false; + config->relative = false; + config->rsh_command = NULL; + config->rsync_path = NULL; + config->temp_dir = NULL; + config->compare_dest = NULL; + config->copy_dest = NULL; + config->link_dest = NULL; if (!receive_int(file_descriptor, &tmp)) goto error; config->backup = tmp; config->backup_dir = receive_str(file_descriptor); if (config->backup_dir == NULL) goto error; + if (!receive_int(file_descriptor, &tmp)) + goto error; + config->follow_symlinks = tmp; + if (!receive_int(file_descriptor, &tmp)) + goto error; + config->copy_links = tmp; + if (!receive_int(file_descriptor, &tmp)) + goto error; + config->safe_links = tmp; + if (!receive_int(file_descriptor, &tmp)) + goto error; + config->copy_unsafe_links = tmp; + if (!receive_int(file_descriptor, &tmp)) + goto error; + config->preserve_hard_links = tmp; + if (!receive_int(file_descriptor, &tmp)) + goto error; + config->preserve_acls = tmp; + if (!receive_int(file_descriptor, &tmp)) + goto error; + config->preserve_xattrs = tmp; + if (!receive_int(file_descriptor, &tmp)) + goto error; + config->preserve_devices = tmp; + if (!receive_int(file_descriptor, &tmp)) + goto error; + config->preserve_sparse = tmp; + if (!receive_int(file_descriptor, &tmp)) + goto error; + config->update = tmp; + if (!receive_int(file_descriptor, &tmp)) + goto error; + config->inplace = tmp; + if (!receive_int(file_descriptor, &tmp)) + goto error; + config->append = tmp; + if (!receive_int(file_descriptor, &tmp)) + goto error; + config->append_verify = tmp; + if (!receive_int(file_descriptor, &tmp)) + goto error; + config->delete_excluded = tmp; + if (!receive_int(file_descriptor, &tmp)) + goto error; + config->delete_after = tmp; + if (!receive_n_data(file_descriptor, &config->max_delete, sizeof(config->max_delete))) + goto error; + if (!receive_int(file_descriptor, &tmp)) + goto error; + config->relative = tmp; + if (!receive_int(file_descriptor, &tmp)) + goto error; + config->prune_empty_dirs = tmp; + config->temp_dir = receive_str(file_descriptor); + if (config->temp_dir == NULL) + goto error; config->server_host = str_dup("127.0.0.1"); config->server_port = 8080; if (!send_status(file_descriptor, STATUS_OK)) @@ -265,6 +437,7 @@ error: free(config->receive_root_directory); free(config->server_host); free(config->backup_dir); + free(config->temp_dir); free(config); return NULL; } diff --git a/src/shared/config.h b/src/shared/config.h index 894b061..71336ea 100644 --- a/src/shared/config.h +++ b/src/shared/config.h @@ -1,6 +1,7 @@ #ifndef CONFIG_H #define CONFIG_H +#include "array_list.h" #include #include #include @@ -53,6 +54,52 @@ typedef struct Config { int queue_size; bool follow_symlinks; bool partial; + + // Issue #120: Symlink handling + bool copy_links; + bool safe_links; + bool copy_unsafe_links; + + // Issue #121: Extended metadata preservation + bool preserve_hard_links; + bool preserve_acls; + bool preserve_xattrs; + bool preserve_devices; + bool preserve_sparse; + + // Issue #122: Output/logging options + bool itemize_changes; + char* out_format; + int info_level; + int debug_level; + bool list_only; + bool human_readable; + + // Issue #127: Transfer modes + bool update; + bool inplace; + bool append; + bool append_verify; + + // Issue #128: Extended delete options + bool delete_excluded; + bool delete_after; + int max_delete; + + // Issue #129: Advanced file selection + ArrayList* filters; + char* files_from; + bool cvs_exclude; + bool prune_empty_dirs; + bool relative; + + // Issue #130: Remote shell/connection options + char* rsh_command; + char* rsync_path; + char* temp_dir; + char* compare_dest; + char* copy_dest; + char* link_dest; } Config; #define PROTOCOL_VERSION "1.3.0" diff --git a/tests/test_scanner.c b/tests/test_scanner.c index 903693e..1ce524a 100644 --- a/tests/test_scanner.c +++ b/tests/test_scanner.c @@ -19,7 +19,7 @@ static void test_scanner_single_file() { create_test_file(file1, content1); DirectoryScanner* scanner = - directory_scanner_create((char*)dir, false, 0, NULL, 0, NULL, 0, 0, 0, 0); + directory_scanner_create((char*)dir, false, 0, NULL, 0, NULL, 0, 0, 0, 0, false, false, false, false); EXPECT_NOT_NULL(scanner); Chunk* chunk = directory_scanner_next(scanner); @@ -48,7 +48,7 @@ static void test_scanner_multiple_files() { create_test_file(file2, content2); DirectoryScanner* scanner = - directory_scanner_create((char*)dir, false, 0, NULL, 0, NULL, 0, 0, 0, 0); + directory_scanner_create((char*)dir, false, 0, NULL, 0, NULL, 0, 0, 0, 0, false, false, false, false); EXPECT_NOT_NULL(scanner); const Chunk* chunk = directory_scanner_next(scanner); @@ -88,7 +88,7 @@ static void test_scanner_subdirectory() { create_test_file(sub_file, content); DirectoryScanner* scanner = - directory_scanner_create((char*)root, false, 0, NULL, 0, NULL, 0, 0, 0, 0); + directory_scanner_create((char*)root, false, 0, NULL, 0, NULL, 0, 0, 0, 0, false, false, false, false); EXPECT_NOT_NULL(scanner); int total_files = 0; @@ -112,7 +112,7 @@ static void test_scanner_empty_directory() { EXPECT_EQ_INT(mkdir(dir, 0755), 0); DirectoryScanner* scanner = - directory_scanner_create((char*)dir, false, 0, NULL, 0, NULL, 0, 0, 0, 0); + directory_scanner_create((char*)dir, false, 0, NULL, 0, NULL, 0, 0, 0, 0, false, false, false, false); EXPECT_NOT_NULL(scanner); const Chunk* chunk = directory_scanner_next(scanner); @@ -136,7 +136,7 @@ static void test_scanner_exclude_pattern() { char* exclude[] = {"*.tmp"}; DirectoryScanner* scanner = - directory_scanner_create((char*)dir, false, 0, exclude, 1, NULL, 0, 0, 0, 0); + directory_scanner_create((char*)dir, false, 0, exclude, 1, NULL, 0, 0, 0, 0, false, false, false, false); EXPECT_NOT_NULL(scanner); Chunk* chunk = directory_scanner_next(scanner); @@ -169,7 +169,7 @@ static void test_scanner_exclude_subdirectory() { char* exclude[] = {"*.tmp"}; DirectoryScanner* scanner = - directory_scanner_create((char*)root, false, 0, exclude, 1, NULL, 0, 0, 0, 0); + directory_scanner_create((char*)root, false, 0, exclude, 1, NULL, 0, 0, 0, 0, false, false, false, false); EXPECT_NOT_NULL(scanner); int total = 0; @@ -207,7 +207,7 @@ static void test_scanner_include_and_exclude() { char* exclude[] = {"*.bak"}; char* include[] = {"*.txt", "*.log"}; DirectoryScanner* scanner = - directory_scanner_create((char*)dir, false, 0, exclude, 1, include, 2, 0, 0, 0); + directory_scanner_create((char*)dir, false, 0, exclude, 1, include, 2, 0, 0, 0, false, false, false, false); EXPECT_NOT_NULL(scanner); Chunk* chunk = directory_scanner_next(scanner); @@ -244,7 +244,7 @@ static void test_scanner_max_size() { /* max_size = 10 — only files <= 10 bytes */ DirectoryScanner* scanner = - directory_scanner_create((char*)dir, false, 0, NULL, 0, NULL, 0, 10, 0, 0); + directory_scanner_create((char*)dir, false, 0, NULL, 0, NULL, 0, 10, 0, 0, false, false, false, false); EXPECT_NOT_NULL(scanner); Chunk* chunk = directory_scanner_next(scanner); @@ -272,7 +272,7 @@ static void test_scanner_min_size() { /* min_size = 1 — only files >= 1 byte */ DirectoryScanner* scanner = - directory_scanner_create((char*)dir, false, 0, NULL, 0, NULL, 0, 0, 1, 0); + directory_scanner_create((char*)dir, false, 0, NULL, 0, NULL, 0, 0, 1, 0, false, false, false, false); EXPECT_NOT_NULL(scanner); Chunk* chunk = directory_scanner_next(scanner); @@ -302,7 +302,7 @@ static void test_scanner_size_range() { /* Only files between 3 and 20 bytes */ DirectoryScanner* scanner = - directory_scanner_create((char*)dir, false, 0, NULL, 0, NULL, 0, 20, 3, 0); + directory_scanner_create((char*)dir, false, 0, NULL, 0, NULL, 0, 20, 3, 0, false, false, false, false); EXPECT_NOT_NULL(scanner); Chunk* chunk = directory_scanner_next(scanner); @@ -338,7 +338,7 @@ static void test_scanner_mixed_patterns() { char* exclude[] = {"*.bak"}; char* include[] = {"*.txt"}; DirectoryScanner* scanner = - directory_scanner_create((char*)dir, false, 0, exclude, 1, include, 1, 10, 3, 0); + directory_scanner_create((char*)dir, false, 0, exclude, 1, include, 1, 10, 3, 0, false, false, false, false); EXPECT_NOT_NULL(scanner); Chunk* chunk = directory_scanner_next(scanner); @@ -369,7 +369,7 @@ static void test_scanner_no_patterns() { create_test_file(f2, "second"); DirectoryScanner* scanner = - directory_scanner_create((char*)dir, false, 0, NULL, 0, NULL, 0, 0, 0, 0); + directory_scanner_create((char*)dir, false, 0, NULL, 0, NULL, 0, 0, 0, 0, false, false, false, false); EXPECT_NOT_NULL(scanner); Chunk* chunk = directory_scanner_next(scanner);