diff --git a/src/client/client_cli.c b/src/client/client_cli.c index 711a449..3181675 100644 --- a/src/client/client_cli.c +++ b/src/client/client_cli.c @@ -8,9 +8,11 @@ #include "utils.h" #include #include +#include #include #include #include +#include "usage.c" #ifndef FASTSYNC_TEST_BUILD /* Parse environment variables for source/destination directories and save-to-disk flag. */ @@ -25,19 +27,6 @@ static void parse_environment(const char** out_env_source, const char** out_env_ } #endif -/* Parse a string as a positive integer, returning true on success. */ -static bool parse_positive_int(const char* s, int* out_val) { - if (!s || *s == '\0') - return false; - char* endptr; - errno = 0; - long val = strtol(s, &endptr, 10); - if (errno != 0 || *endptr != '\0' || val <= 0 || val > INT_MAX) - return false; - *out_val = (int)val; - return true; -} - /* Parse a string as a non-negative integer, returning true on success. */ static bool parse_nonneg_int(const char* s, int* out_val) { if (!s || *s == '\0') @@ -51,9 +40,22 @@ 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 +/* Parse a string as a positive integer, returning true on success. */ +static bool parse_positive_int(const char* s, int* out_val) { + int temp; + if (!parse_nonneg_int(s, &temp)) { + return false; + } + if (temp == 0) { + return false; + } + *out_val = temp; + return true; +} + +/* Duplicate a string argument into *dest, freeing the old value. Returns true on success, false on * failure. */ -static int set_string_option(char** dest, const char* value, const char* option_name) { +static bool 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); @@ -64,8 +66,8 @@ static int set_string_option(char** dest, const char* value, const char* option_ 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) { +/* Parse a string as a positive integer into *dest. Returns true on success, false on error. */ +static bool 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; @@ -73,8 +75,8 @@ static int set_positive_int_option(int* dest, const char* value, const char* opt 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) { +/* Parse a string as a non-negative integer into *dest. Returns true on success, false on error. */ +static bool 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; @@ -82,7 +84,6 @@ static int set_nonneg_int_option(int* dest, const char* value, const char* optio return 0; } -static void print_usage(void); static int read_patterns_from_file(const char* filepath, char*** patterns, int* count); /* Parse CLI arguments into config. Returns 0 on success, -1 on error, 1 for help/clean-exit. */ @@ -103,12 +104,10 @@ int parse_args(Config* config, int argc, char* argv[], int* positional_args, } else if (strcmp(argv[i], "-n") == 0 || strcmp(argv[i], "--dry-run") == 0) { config->dry_run = true; } else if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) { - if (!parse_positive_int(argv[++i], &config->ssh_port)) { - fprintf(stderr, "Error: invalid --port/-p value: %s\n", argv[i]); + if (!set_positive_int_option(&config->ssh_port, argv[++i], "-p")) return -1; - } if (config->ssh_port > 65535) { - fprintf(stderr, "Error: SSH port must be 1-65535\n"); + log_message(LOG_LEVEL_ERROR, "SSH port must be 1-65535\n"); return -1; } } else if (strcmp(argv[i], "--delete") == 0) { @@ -140,14 +139,8 @@ int parse_args(Config* config, int argc, char* argv[], int* positional_args, } config->include_patterns[config->include_count++] = dup; } else if (strcmp(argv[i], "--max-size") == 0 && i + 1 < argc) { - 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"); + if (!set_nonneg_int_option(&config->max_size, argv[++i], "Max Size")) return -1; - } - config->max_size = val; } else if (strcmp(argv[i], "--min-size") == 0 && i + 1 < argc) { char* end; errno = 0; @@ -507,116 +500,6 @@ static bool validate_config(const Config* config) { } #endif /* FASTSYNC_TEST_BUILD */ -static void print_usage(void) { - printf("Usage:\n"); - printf(" fastsync [options] \n"); - printf(" fastsync [options] --source-dir --dest-dir \n"); - printf("\n"); - printf("Destination formats:\n"); - printf(" user@host:/path SSH transport (rsync-style)\n"); - printf(" host:/path SSH transport (current user)\n"); - printf(" /local/path TCP transport (requires server on localhost:8080)\n"); - printf("\n"); - printf("Options:\n"); - printf(" -c [level] Enable compression (level 1-22, default 5)\n"); - printf(" -z [level] Alias for -c\n"); - printf(" -a, --archive Archive mode (-c -m -M)\n"); - printf(" -n, --dry-run Show what would be transferred\n"); - printf(" -p SSH port (default: 22)\n"); - printf(" --progress Show transfer progress\n"); - printf(" --delete Delete files on receiver not in source\n"); - printf(" --exclude Exclude files matching pattern\n"); - printf(" --include Only include files matching pattern\n"); - printf(" --exclude-from Read exclude patterns from file\n"); - printf(" --include-from Read include patterns from file\n"); - printf(" --max-size Skip files larger than n bytes\n"); - printf(" --min-size Skip files smaller than n bytes\n"); - printf(" --incremental Skip files unchanged since last transfer\n"); - printf(" --delta Delta transfer for changed files (requires --incremental)\n"); - printf(" --delta-block Delta block size in bytes (default: %d)\n", - DELTA_BLOCK_SIZE_DEFAULT); - printf(" --delta-max Max file size for delta transfer (default: %llu)\n", - DELTA_MAX_FILE_SIZE); - printf(" -m Enable multithreading\n"); - printf(" -s Enable chunk serialization\n"); - printf(" -f Enable sendfile (TCP only, not with -c or -s)\n"); - printf(" -v, --verbose Enable debug logging\n"); - printf(" -M, --preserve Preserve file metadata\n"); - printf(" --chunk-size Chunk size in bytes (default: %d)\n", DEFAULT_CHUNK_SIZE); - printf(" --source-dir Source directory\n"); - printf(" --dest-dir Destination directory\n"); - printf(" --save-to-disk Write received files to disk\n"); - printf(" --server-host Server IP address (default: 127.0.0.1)\n"); - printf(" --server-port Server port (default: 8080)\n"); - printf(" --bwlimit Bandwidth limit in kilobytes per second\n"); - printf(" --tls Enable TLS encryption\n"); - printf(" --cert TLS certificate file (PEM)\n"); - printf(" --key TLS private key file (PEM)\n"); - printf(" --ca TLS CA certificate file (PEM)\n"); - printf(" --timeout I/O timeout in seconds (default: 30)\n"); - printf(" -T Alias for --timeout\n"); - printf(" --contimeout Connection timeout in seconds (default: 10)\n"); - printf(" --address Server hostname/IP to connect to\n"); - printf(" --bind-address 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 Directory for backups (requires --backup)\n"); - printf(" --suffix Backup suffix (default: ~)\n"); - printf(" --stats Print transfer statistics at end\n"); - printf(" --max-depth Maximum directory depth (0=unlimited)\n"); - printf(" --log-file Write log messages to file\n"); - printf(" --queue-size Queue capacity for multithreaded mode (default: 100)\n"); - printf(" --partial Keep partial files on interrupted transfer\n"); - printf(" --partial-dir Directory for partial files\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-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 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(" --daemon Run in daemon mode\n"); - printf(" --config 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 Compression algorithm (default: zstd)\n"); - printf(" --compress-level Compression level (default: 5)\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"); - printf(" -V, --version Show version\n"); -} - static int read_patterns_from_file(const char* filepath, char*** patterns, int* count) { FILE* fp = fopen(filepath, "r"); if (!fp) { diff --git a/src/client/usage.c b/src/client/usage.c new file mode 100644 index 0000000..2c477ea --- /dev/null +++ b/src/client/usage.c @@ -0,0 +1,113 @@ +#include "stdio.h" +#include +#include + +static __attribute__((unused)) void print_usage() { + printf("Usage:\n"); + printf(" fastsync [options] \n"); + printf(" fastsync [options] --source-dir --dest-dir \n"); + printf("\n"); + printf("Destination formats:\n"); + printf(" user@host:/path SSH transport (rsync-style)\n"); + printf(" host:/path SSH transport (current user)\n"); + printf(" /local/path TCP transport (requires server on localhost:8080)\n"); + printf("\n"); + printf("Options:\n"); + printf(" -c [level] Enable compression (level 1-22, default 5)\n"); + printf(" -z [level] Alias for -c\n"); + printf(" -a, --archive Archive mode (-c -m -M)\n"); + printf(" -n, --dry-run Show what would be transferred\n"); + printf(" -p SSH port (default: 22)\n"); + printf(" --progress Show transfer progress\n"); + printf(" --delete Delete files on receiver not in source\n"); + printf(" --exclude Exclude files matching pattern\n"); + printf(" --include Only include files matching pattern\n"); + printf(" --exclude-from Read exclude patterns from file\n"); + printf(" --include-from Read include patterns from file\n"); + printf(" --max-size Skip files larger than n bytes\n"); + printf(" --min-size Skip files smaller than n bytes\n"); + printf(" --incremental Skip files unchanged since last transfer\n"); + printf(" --delta Delta transfer for changed files (requires --incremental)\n"); + printf(" --delta-block Delta block size in bytes (default: %d)\n", + DELTA_BLOCK_SIZE_DEFAULT); + printf(" --delta-max Max file size for delta transfer (default: %llu)\n", + DELTA_MAX_FILE_SIZE); + printf(" -m Enable multithreading\n"); + printf(" -s Enable chunk serialization\n"); + printf(" -f Enable sendfile (TCP only, not with -c or -s)\n"); + printf(" -v, --verbose Enable debug logging\n"); + printf(" -M, --preserve Preserve file metadata\n"); + printf(" --chunk-size Chunk size in bytes (default: %d)\n", DEFAULT_CHUNK_SIZE); + printf(" --source-dir Source directory\n"); + printf(" --dest-dir Destination directory\n"); + printf(" --save-to-disk Write received files to disk\n"); + printf(" --server-host Server IP address (default: 127.0.0.1)\n"); + printf(" --server-port Server port (default: 8080)\n"); + printf(" --bwlimit Bandwidth limit in kilobytes per second\n"); + printf(" --tls Enable TLS encryption\n"); + printf(" --cert TLS certificate file (PEM)\n"); + printf(" --key TLS private key file (PEM)\n"); + printf(" --ca TLS CA certificate file (PEM)\n"); + printf(" --timeout I/O timeout in seconds (default: 30)\n"); + printf(" -T Alias for --timeout\n"); + printf(" --contimeout Connection timeout in seconds (default: 10)\n"); + printf(" --address Server hostname/IP to connect to\n"); + printf(" --bind-address 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 Directory for backups (requires --backup)\n"); + printf(" --suffix Backup suffix (default: ~)\n"); + printf(" --stats Print transfer statistics at end\n"); + printf(" --max-depth Maximum directory depth (0=unlimited)\n"); + printf(" --log-file Write log messages to file\n"); + printf(" --queue-size Queue capacity for multithreaded mode (default: 100)\n"); + printf(" --partial Keep partial files on interrupted transfer\n"); + printf(" --partial-dir Directory for partial files\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-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 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(" --daemon Run in daemon mode\n"); + printf(" --config 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 Compression algorithm (default: zstd)\n"); + printf(" --compress-level Compression level (default: 5)\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"); + printf(" -V, --version Show version\n"); +} diff --git a/src/shared/log.c b/src/shared/log.c index fbd9400..c22a853 100644 --- a/src/shared/log.c +++ b/src/shared/log.c @@ -15,6 +15,15 @@ void log_set_file(FILE* fp) { log_fp = fp; } +static inline void write_message(FILE* dest_io, LogLevel log_level, struct tm t, const char* format, + va_list args) { + fprintf(dest_io, "%04d-%02d-%02d %02d:%02d:%02d [%s]: ", t.tm_year + 1900, t.tm_mon + 1, + t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, log_level_strings[log_level]); + + vfprintf(dest_io, format, args); + fprintf(dest_io, "\n"); +} + void log_message(LogLevel log_level, const char* format, ...) { if (log_level < current_log_level) return; @@ -25,22 +34,16 @@ void log_message(LogLevel log_level, const char* format, ...) { if (!localtime_r(&now, &t)) return; - fprintf(stderr, "%04d-%02d-%02d %02d:%02d:%02d [%s]: ", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, - t.tm_hour, t.tm_min, t.tm_sec, log_level_strings[log_level]); + FILE* dest_io = stdout; + if (log_level == LOG_LEVEL_ERROR) { + dest_io = stderr; + } va_list args; va_start(args, format); - vfprintf(stderr, format, args); - va_end(args); - fprintf(stderr, "\n"); + write_message(dest_io, log_level, t, format, args); if (log_fp) { - fprintf(log_fp, "%04d-%02d-%02d %02d:%02d:%02d [%s]: ", t.tm_year + 1900, t.tm_mon + 1, - t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, log_level_strings[log_level]); - va_start(args, format); - vfprintf(log_fp, format, args); - va_end(args); - fprintf(log_fp, "\n"); - fflush(log_fp); + write_message(log_fp, log_level, t, format, args); } }