diff --git a/src/client/client_cli.c b/src/client/client_cli.c index d7cff62..3eb8554 100644 --- a/src/client/client_cli.c +++ b/src/client/client_cli.c @@ -55,6 +55,7 @@ static void print_usage(void) { printf(" --cert TLS certificate file (PEM)\n"); printf(" --key TLS private key file (PEM)\n"); printf(" --ca TLS CA certificate file (PEM)\n"); + printf(" --partial Keep partially transferred files on interruption\n"); printf(" --help Show this help\n"); } @@ -88,7 +89,14 @@ int main(int argc, char* argv[]) { } 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) { - config->ssh_port = atoi(argv[++i]); + char* end; + long p = strtol(argv[++i], &end, 10); + if (*end || p <= 0 || p > 65535) { + fprintf(stderr, "Error: invalid port '%s' (must be 1-65535)\n", argv[i]); + exit_code = 1; + goto cleanup; + } + config->ssh_port = (int)p; } else if (strcmp(argv[i], "--delete") == 0) { config->use_delete = true; } else if (strcmp(argv[i], "--exclude") == 0 && i + 1 < argc) { @@ -165,7 +173,14 @@ int main(int argc, char* argv[]) { free(config->server_host); config->server_host = str_dup(argv[++i]); } else if (strcmp(argv[i], "--server-port") == 0 && i + 1 < argc) { - config->server_port = atoi(argv[++i]); + char* end; + long p = strtol(argv[++i], &end, 10); + if (*end || p <= 0 || p > 65535) { + fprintf(stderr, "Error: invalid port '%s' (must be 1-65535)\n", argv[i]); + exit_code = 1; + goto cleanup; + } + config->server_port = (int)p; } else if (strcmp(argv[i], "--bwlimit") == 0 && i + 1 < argc) { char* end; errno = 0; @@ -199,6 +214,8 @@ int main(int argc, char* argv[]) { } else if (strcmp(argv[i], "--ca") == 0 && i + 1 < argc) { free(config->tls_ca); config->tls_ca = str_dup(argv[++i]); + } else if (strcmp(argv[i], "--partial") == 0) { + config->partial = true; } else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) { set_log_level(LOG_LEVEL_DEBUG); } else if (argv[i][0] == '-') { diff --git a/src/client/client_send.c b/src/client/client_send.c index 256d8e4..f1c25ce 100644 --- a/src/client/client_send.c +++ b/src/client/client_send.c @@ -86,7 +86,9 @@ static int send_delta(Client* client, File* file, DeltaSignature* sig, Config* c return -1; } + int file_type = (int)file->type; bool ok = send_status(client->file_descriptor, STATUS_DELTA_DATA) && + send_int(client->file_descriptor, file_type) && send_data(client->file_descriptor, to_send); if (ok && config->use_metadata) @@ -500,7 +502,7 @@ 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->min_size, config->follow_symlinks); 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 ad56fb2..109ffa0 100644 --- a/src/client/scanner.c +++ b/src/client/scanner.c @@ -24,9 +24,58 @@ DirectoryScanner* directory_scanner_create(char* root_directory, bool use_metada scanner->current_path = NULL; scanner->use_metadata = use_metadata; scanner->chunk_size = chunk_size > 0 ? chunk_size : DESIRED_CHUNK_SIZE; - scanner->exclude_patterns = exclude_patterns; + /* Deep-copy exclude patterns */ + if (exclude_count > 0 && exclude_patterns != NULL) { + scanner->exclude_patterns = malloc((size_t)exclude_count * sizeof(char*)); + if (scanner->exclude_patterns == NULL) { + queue_destroy(scanner->directories); + free(scanner); + return NULL; + } + for (int i = 0; i < exclude_count; i++) { + scanner->exclude_patterns[i] = str_dup(exclude_patterns[i]); + if (scanner->exclude_patterns[i] == NULL) { + for (int j = 0; j < i; j++) + free(scanner->exclude_patterns[j]); + free(scanner->exclude_patterns); + queue_destroy(scanner->directories); + free(scanner); + return NULL; + } + } + } else { + scanner->exclude_patterns = NULL; + } scanner->exclude_count = exclude_count; - scanner->include_patterns = include_patterns; + + /* Deep-copy include patterns */ + if (include_count > 0 && include_patterns != NULL) { + scanner->include_patterns = malloc((size_t)include_count * sizeof(char*)); + if (scanner->include_patterns == NULL) { + for (int i = 0; i < exclude_count; i++) + free(scanner->exclude_patterns[i]); + free(scanner->exclude_patterns); + queue_destroy(scanner->directories); + free(scanner); + return NULL; + } + for (int i = 0; i < include_count; i++) { + scanner->include_patterns[i] = str_dup(include_patterns[i]); + if (scanner->include_patterns[i] == NULL) { + for (int j = 0; j < i; j++) + free(scanner->include_patterns[j]); + free(scanner->include_patterns); + for (int j = 0; j < exclude_count; j++) + free(scanner->exclude_patterns[j]); + free(scanner->exclude_patterns); + queue_destroy(scanner->directories); + free(scanner); + return NULL; + } + } + } else { + scanner->include_patterns = NULL; + } scanner->include_count = include_count; scanner->max_size = max_size; scanner->min_size = min_size; @@ -42,6 +91,12 @@ void directory_scanner_destroy(DirectoryScanner* scanner) { scanner->current_dir = NULL; } free(scanner->current_path); + for (int i = 0; i < scanner->exclude_count; i++) + free(scanner->exclude_patterns[i]); + free(scanner->exclude_patterns); + for (int i = 0; i < scanner->include_count; i++) + free(scanner->include_patterns[i]); + free(scanner->include_patterns); queue_destroy(scanner->directories); free(scanner); } diff --git a/src/server/server.c b/src/server/server.c index d3bbc74..b3ef5ff 100644 --- a/src/server/server.c +++ b/src/server/server.c @@ -16,6 +16,26 @@ #include #include +static const char* filename_from_path(const char* path) { + const char* slash = strrchr(path, '/'); + return slash ? slash + 1 : path; +} + +static bool should_exclude_file(const Config* config, const char* filename) { + for (int i = 0; i < config->exclude_count; i++) { + if (glob_match(config->exclude_patterns[i], filename)) + return true; + } + if (config->include_count > 0) { + for (int i = 0; i < config->include_count; i++) { + if (glob_match(config->include_patterns[i], filename)) + return true; + } + return false; + } + return false; +} + int receive_files(Config* config, int fd) { Status status; if (!receive_status(fd, &status)) @@ -29,8 +49,10 @@ int receive_files(Config* config, int fd) { goto next; if (file == NULL && !skipped) return -1; - if (config->save_to_disk) - file_save_to_disk(config->receive_root_directory, file); + if (config->save_to_disk) { + if (!should_exclude_file(config, filename_from_path(file->path))) + file_save_to_disk(config->receive_root_directory, file); + } file_destroy(file); } else if (status == STATUS_CHUNK) { Chunk* chunk = receive_chunk_data(fd, config); @@ -39,8 +61,10 @@ int receive_files(Config* config, int fd) { return -1; } for (int i = 0; i < chunk->element_count; i++) { - if (config->save_to_disk) - file_save_to_disk(config->receive_root_directory, chunk->items[i]); + if (config->save_to_disk) { + if (!should_exclude_file(config, filename_from_path(chunk->items[i]->path))) + file_save_to_disk(config->receive_root_directory, chunk->items[i]); + } } chunk_destroy(chunk); } else { @@ -50,8 +74,10 @@ int receive_files(Config* config, int fd) { send_status(fd, STATUS_ERROR); return -1; } - if (config->save_to_disk) - file_save_to_disk(config->receive_root_directory, file); + if (config->save_to_disk) { + if (!should_exclude_file(config, filename_from_path(file->path))) + file_save_to_disk(config->receive_root_directory, file); + } file_destroy(file); } next: @@ -112,13 +138,21 @@ void handler(int file_descriptor) { close(file_descriptor); } +/* Signal-safe flag: set by the signal handler, checked in main loop. + * We cannot safely access g_server from the signal handler because it's + * not sig_atomic_t. Instead, the handler sets this flag and calls _exit + * (which is async-signal-safe). The server is fork-based (not threaded), + * so g_server is only accessed from the main thread and cleanup() only + * runs in the parent process — no concurrent access from children. */ +static volatile sig_atomic_t g_server_cleanup_requested = 0; static Server* g_server = NULL; static void cleanup(int sig) { (void)sig; - if (g_server) { - server_delete(&g_server); - } + g_server_cleanup_requested = 1; + /* _exit is async-signal-safe; we must not call server_delete() from a + * signal handler (it may call non-async-signal-safe functions). The OS + * will reclaim resources on exit. */ _exit(0); } @@ -134,6 +168,7 @@ static void print_server_usage(void) { printf(" --key TLS private key file (PEM)\n"); printf(" --ca TLS CA certificate file (PEM)\n"); printf(" -v, --verbose Enable debug logging\n"); + printf(" -V, --version Show version information\n"); printf(" --help Show this help\n"); } @@ -149,6 +184,9 @@ int main(int argc, char* argv[]) { if (strcmp(argv[i], "--help") == 0) { print_server_usage(); return 0; + } else if (strcmp(argv[i], "--version") == 0 || strcmp(argv[i], "-V") == 0) { + printf("FastSync Server version %s\n", PROTOCOL_VERSION); + return 0; } else if (strcmp(argv[i], "--stdio") == 0) { io_set_fds(STDIN_FILENO, STDOUT_FILENO); handler(STDIN_FILENO); diff --git a/src/shared/compression.c b/src/shared/compression.c index 3641859..88a4f31 100644 --- a/src/shared/compression.c +++ b/src/shared/compression.c @@ -66,8 +66,16 @@ Data* data_decompress(Data* compressed_data) { return NULL; } - size_t buf_size = - (!ZSTD_isError(dst_size) && dst_size > 0) ? (size_t)dst_size : INITIAL_DECOMPRESS_BUF_SIZE; + size_t buf_size = INITIAL_DECOMPRESS_BUF_SIZE; + if (!ZSTD_isError(dst_size) && dst_size > 0) { + if (dst_size > SIZE_MAX) { + log_message(LOG_LEVEL_ERROR, + "Decompressed size %llu exceeds addressable memory, using fallback buffer", + dst_size); + } else { + buf_size = (size_t)dst_size; + } + } Data* uncompressed_data = data_create_empty(buf_size); if (!uncompressed_data) { log_message(LOG_LEVEL_ERROR, "Failed to allocate decompression buffer"); diff --git a/src/shared/config.c b/src/shared/config.c index 8ae4c5e..259dd7d 100644 --- a/src/shared/config.c +++ b/src/shared/config.c @@ -42,6 +42,8 @@ Config* config_create(char* version, char* send_directory, char* receive_directo config->delta_block_size = DELTA_BLOCK_SIZE_DEFAULT; config->delta_max_file_size = DELTA_MAX_FILE_SIZE; config->use_tls = false; + config->partial = false; + config->follow_symlinks = false; config->tls_cert = NULL; config->tls_key = NULL; config->tls_ca = NULL; @@ -218,6 +220,8 @@ Config* config_receive(int file_descriptor) { config->max_size = 0; config->min_size = 0; config->use_tls = false; + config->partial = false; + config->follow_symlinks = false; config->tls_cert = NULL; config->tls_key = NULL; config->tls_ca = NULL; diff --git a/src/shared/config.h b/src/shared/config.h index 183f60c..431444b 100644 --- a/src/shared/config.h +++ b/src/shared/config.h @@ -35,6 +35,8 @@ typedef struct Config { uint32_t delta_block_size; unsigned long long delta_max_file_size; bool use_tls; + bool partial; + bool follow_symlinks; char* server_host; int server_port; char* tls_cert; diff --git a/src/shared/data.c b/src/shared/data.c index 5b3dcba..581eb79 100644 --- a/src/shared/data.c +++ b/src/shared/data.c @@ -3,6 +3,8 @@ #include "stdlib.h" Data* data_create_empty(size_t data_size) { + if (data_size == 0) + data_size = 1; void* data = malloc(data_size); if (data == NULL) { log_message(LOG_LEVEL_ERROR, "Could not allocate memory for empty data"); diff --git a/src/shared/data.h b/src/shared/data.h index 5246afa..f03fabf 100644 --- a/src/shared/data.h +++ b/src/shared/data.h @@ -1,7 +1,7 @@ #ifndef DATA_H #define DATA_H -#include "stdlib.h" +#include typedef struct { void* data; diff --git a/src/shared/file.c b/src/shared/file.c index 58b0455..3f84bdb 100644 --- a/src/shared/file.c +++ b/src/shared/file.c @@ -155,6 +155,49 @@ static void* old_data_from_path(const char* full_path, unsigned long long old_si return data; } +/* Helper: receive data + optionally decompress + store in a new File. + * On success returns the File (caller owns it). On failure sends + * STATUS_ERROR on fd and returns NULL. */ +static File* receive_file_data(int fd, const Config* config, const char* check_path) { + File* file = file_create(check_path); + if (!file) { + send_status(fd, STATUS_ERROR); + return NULL; + } + + if (config->use_metadata) { + int meta_ok = 1; + file->metadata = metadata_receive(fd, &meta_ok); + if (!meta_ok) { + file_destroy(file); + send_status(fd, STATUS_ERROR); + return NULL; + } + } + + Data* file_data = receive_data(fd); + if (!file_data) { + file_destroy(file); + send_status(fd, STATUS_ERROR); + return NULL; + } + + if (config->use_compression) { + Data* uncompressed = data_decompress(file_data); + data_destroy(file_data); + if (!uncompressed) { + file_destroy(file); + send_status(fd, STATUS_ERROR); + return NULL; + } + file_data = uncompressed; + } + + data_destroy(file->data); + file->data = file_data; + return file; +} + static File* receive_delta_file(int fd, const Config* config, const char* check_path, void* old_data, unsigned long long old_size) { if (!old_data) @@ -264,43 +307,7 @@ static File* receive_delta_file(int fd, const Config* config, const char* check_ delta_signature_destroy(sig); free(old_data); - File* file = file_create(check_path); - if (!file) { - send_status(fd, STATUS_ERROR); - return NULL; - } - - if (config->use_metadata) { - int meta_ok = 1; - file->metadata = metadata_receive(fd, &meta_ok); - if (!meta_ok) { - file_destroy(file); - send_status(fd, STATUS_ERROR); - return NULL; - } - } - - Data* file_data = receive_data(fd); - if (file_data == NULL) { - file_destroy(file); - send_status(fd, STATUS_ERROR); - return NULL; - } - - if (config->use_compression) { - Data* uncompressed = data_decompress(file_data); - data_destroy(file_data); - if (uncompressed == NULL) { - file_destroy(file); - send_status(fd, STATUS_ERROR); - return NULL; - } - file_data = uncompressed; - } - - data_destroy(file->data); - file->data = file_data; - return file; + return receive_file_data(fd, config, check_path); } delta_signature_destroy(sig); @@ -367,44 +374,9 @@ File* receive_incremental_check(int fd, const Config* config, bool* skipped) { } } - File* file = file_create(check_path); + File* file = receive_file_data(fd, config, check_path); free(check_path); free(full_path); - if (file == NULL) { - send_status(fd, STATUS_ERROR); - return NULL; - } - - if (config->use_metadata) { - int meta_ok = 1; - file->metadata = metadata_receive(fd, &meta_ok); - if (!meta_ok) { - file_destroy(file); - send_status(fd, STATUS_ERROR); - return NULL; - } - } - - Data* file_data = receive_data(fd); - if (file_data == NULL) { - file_destroy(file); - send_status(fd, STATUS_ERROR); - return NULL; - } - - if (config->use_compression) { - Data* uncompressed = data_decompress(file_data); - data_destroy(file_data); - if (uncompressed == NULL) { - file_destroy(file); - send_status(fd, STATUS_ERROR); - return NULL; - } - file_data = uncompressed; - } - - data_destroy(file->data); - file->data = file_data; return file; } diff --git a/src/shared/file.h b/src/shared/file.h index f6acac2..bbba691 100644 --- a/src/shared/file.h +++ b/src/shared/file.h @@ -14,10 +14,18 @@ typedef struct { long mtime_nsec; } FileMetadata; +typedef enum { + FILE_TYPE_REGULAR, + FILE_TYPE_SYMLINK, + FILE_TYPE_DIRECTORY +} FileType; + typedef struct { char* path; Data* data; FileMetadata* metadata; + FileType type; + char* link_target; } File; File* file_create(const char* path); diff --git a/src/shared/log.c b/src/shared/log.c index bcf91bf..0479812 100644 --- a/src/shared/log.c +++ b/src/shared/log.c @@ -10,7 +10,7 @@ void set_log_level(LogLevel level) { current_log_level = level; } -void log_message(LogLevel log_level, char* format, ...) { +void log_message(LogLevel log_level, const char* format, ...) { if (log_level < current_log_level) return; time_t now = time(NULL); diff --git a/src/shared/log.h b/src/shared/log.h index ccff5dc..3c37ced 100644 --- a/src/shared/log.h +++ b/src/shared/log.h @@ -3,7 +3,7 @@ typedef enum { LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_WARNING, LOG_LEVEL_ERROR } LogLevel; -void log_message(LogLevel log_level, char* message, ...); +void log_message(LogLevel log_level, const char* message, ...); void set_log_level(LogLevel level); #endif diff --git a/src/shared/metadata.c b/src/shared/metadata.c index f36e2d6..04ec2e7 100644 --- a/src/shared/metadata.c +++ b/src/shared/metadata.c @@ -4,6 +4,7 @@ #include "protocol.h" #include #include +#include #include #include #include @@ -16,16 +17,21 @@ void metadata_to_buf(char** buf, const FileMetadata* m) { *buf += sizeof(int); if (m == NULL) return; - memcpy(*buf, &m->mode, sizeof(mode_t)); - *buf += sizeof(mode_t); - memcpy(*buf, &m->uid, sizeof(uid_t)); - *buf += sizeof(uid_t); - memcpy(*buf, &m->gid, sizeof(gid_t)); - *buf += sizeof(gid_t); - memcpy(*buf, &m->mtime_sec, sizeof(time_t)); - *buf += sizeof(time_t); - memcpy(*buf, &m->mtime_nsec, sizeof(long)); - *buf += sizeof(long); + int32_t tmp32 = (int32_t)m->mode; + memcpy(*buf, &tmp32, sizeof(int32_t)); + *buf += sizeof(int32_t); + tmp32 = (int32_t)m->uid; + memcpy(*buf, &tmp32, sizeof(int32_t)); + *buf += sizeof(int32_t); + tmp32 = (int32_t)m->gid; + memcpy(*buf, &tmp32, sizeof(int32_t)); + *buf += sizeof(int32_t); + int64_t tmp64 = (int64_t)m->mtime_sec; + memcpy(*buf, &tmp64, sizeof(int64_t)); + *buf += sizeof(int64_t); + tmp64 = (int64_t)m->mtime_nsec; + memcpy(*buf, &tmp64, sizeof(int64_t)); + *buf += sizeof(int64_t); } FileMetadata* metadata_from_buf(char** buf) { @@ -35,16 +41,23 @@ FileMetadata* metadata_from_buf(char** buf) { if (!present) return NULL; FileMetadata* m = malloc(sizeof(FileMetadata)); - memcpy(&m->mode, *buf, sizeof(mode_t)); - *buf += sizeof(mode_t); - memcpy(&m->uid, *buf, sizeof(uid_t)); - *buf += sizeof(uid_t); - memcpy(&m->gid, *buf, sizeof(gid_t)); - *buf += sizeof(gid_t); - memcpy(&m->mtime_sec, *buf, sizeof(time_t)); - *buf += sizeof(time_t); - memcpy(&m->mtime_nsec, *buf, sizeof(long)); - *buf += sizeof(long); + int32_t tmp32; + int64_t tmp64; + memcpy(&tmp32, *buf, sizeof(int32_t)); + *buf += sizeof(int32_t); + m->mode = (mode_t)tmp32; + memcpy(&tmp32, *buf, sizeof(int32_t)); + *buf += sizeof(int32_t); + m->uid = (uid_t)tmp32; + memcpy(&tmp32, *buf, sizeof(int32_t)); + *buf += sizeof(int32_t); + m->gid = (gid_t)tmp32; + memcpy(&tmp64, *buf, sizeof(int64_t)); + *buf += sizeof(int64_t); + m->mtime_sec = (time_t)tmp64; + memcpy(&tmp64, *buf, sizeof(int64_t)); + *buf += sizeof(int64_t); + m->mtime_nsec = (long)tmp64; return m; } @@ -54,12 +67,17 @@ bool metadata_send(int file_descriptor, FileMetadata* m) { return send_n_data(file_descriptor, &zero, sizeof(int)); } int present = 1; + int32_t mode_i32 = (int32_t)m->mode; + int32_t uid_i32 = (int32_t)m->uid; + int32_t gid_i32 = (int32_t)m->gid; + int64_t mtime_sec_i64 = (int64_t)m->mtime_sec; + int64_t mtime_nsec_i64 = (int64_t)m->mtime_nsec; return send_n_data(file_descriptor, &present, sizeof(int)) && - send_n_data(file_descriptor, &m->mode, sizeof(mode_t)) && - send_n_data(file_descriptor, &m->uid, sizeof(uid_t)) && - send_n_data(file_descriptor, &m->gid, sizeof(gid_t)) && - send_n_data(file_descriptor, &m->mtime_sec, sizeof(time_t)) && - send_n_data(file_descriptor, &m->mtime_nsec, sizeof(long)); + send_n_data(file_descriptor, &mode_i32, sizeof(int32_t)) && + send_n_data(file_descriptor, &uid_i32, sizeof(int32_t)) && + send_n_data(file_descriptor, &gid_i32, sizeof(int32_t)) && + send_n_data(file_descriptor, &mtime_sec_i64, sizeof(int64_t)) && + send_n_data(file_descriptor, &mtime_nsec_i64, sizeof(int64_t)); } FileMetadata* metadata_receive(int file_descriptor, int* ok) { @@ -80,16 +98,23 @@ FileMetadata* metadata_receive(int file_descriptor, int* ok) { *ok = 0; return NULL; } - if (!receive_n_data(file_descriptor, &m->mode, sizeof(mode_t)) || - !receive_n_data(file_descriptor, &m->uid, sizeof(uid_t)) || - !receive_n_data(file_descriptor, &m->gid, sizeof(gid_t)) || - !receive_n_data(file_descriptor, &m->mtime_sec, sizeof(time_t)) || - !receive_n_data(file_descriptor, &m->mtime_nsec, sizeof(long))) { + int32_t mode_i32, uid_i32, gid_i32; + int64_t mtime_sec_i64, mtime_nsec_i64; + if (!receive_n_data(file_descriptor, &mode_i32, sizeof(int32_t)) || + !receive_n_data(file_descriptor, &uid_i32, sizeof(int32_t)) || + !receive_n_data(file_descriptor, &gid_i32, sizeof(int32_t)) || + !receive_n_data(file_descriptor, &mtime_sec_i64, sizeof(int64_t)) || + !receive_n_data(file_descriptor, &mtime_nsec_i64, sizeof(int64_t))) { free(m); if (ok) *ok = 0; return NULL; } + m->mode = (mode_t)mode_i32; + m->uid = (uid_t)uid_i32; + m->gid = (gid_t)gid_i32; + m->mtime_sec = (time_t)mtime_sec_i64; + m->mtime_nsec = (long)mtime_nsec_i64; if (ok) *ok = 1; return m; diff --git a/src/shared/metadata.h b/src/shared/metadata.h index 2ad8b3d..518077b 100644 --- a/src/shared/metadata.h +++ b/src/shared/metadata.h @@ -3,10 +3,10 @@ #include "file.h" #include +#include #include -#define FILE_METADATA_WIRE_SIZE \ - (sizeof(mode_t) + sizeof(uid_t) + sizeof(gid_t) + sizeof(time_t) + sizeof(long)) +#define FILE_METADATA_WIRE_SIZE (sizeof(int32_t) * 3 + sizeof(int64_t) * 2) void metadata_to_buf(char** buf, const FileMetadata* m); FileMetadata* metadata_from_buf(char** buf); diff --git a/src/shared/multiprocessing.c b/src/shared/multiprocessing.c index f6ce08f..473ba70 100644 --- a/src/shared/multiprocessing.c +++ b/src/shared/multiprocessing.c @@ -13,6 +13,26 @@ #include #include +static const char* filename_from_path(const char* path) { + const char* slash = strrchr(path, '/'); + return slash ? slash + 1 : path; +} + +static bool should_exclude_file(const Config* config, const char* filename) { + for (int i = 0; i < config->exclude_count; i++) { + if (glob_match(config->exclude_patterns[i], filename)) + return true; + } + if (config->include_count > 0) { + for (int i = 0; i < config->include_count; i++) { + if (glob_match(config->include_patterns[i], filename)) + return true; + } + return false; + } + return false; +} + PipelineContextSender* pipeline_context_sender_create(Config* config, Queue* queue_scanner, Queue* queue_loader) { PipelineContextSender* context = malloc(sizeof(PipelineContextSender)); @@ -155,8 +175,10 @@ int write_thread(void* pipeline_context) { free(root_directory); return thrd_success; } - if (save_to_disk) - file_save_to_disk(root_directory, file); + if (save_to_disk) { + if (!should_exclude_file(context->config, filename_from_path(file->path))) + file_save_to_disk(root_directory, file); + } file_destroy(file); } } diff --git a/src/shared/protocol.c b/src/shared/protocol.c index 4d91ce6..383093c 100644 --- a/src/shared/protocol.c +++ b/src/shared/protocol.c @@ -34,11 +34,17 @@ static void bw_throttle(size_t bytes_written) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); - long long elapsed_ns = - (now.tv_sec - bw_last_refill.tv_sec) * 1000000000LL + (now.tv_nsec - bw_last_refill.tv_nsec); + /* Use unsigned long long for intermediate computation to avoid overflow. + * sec_diff * 1000000000ULL could overflow signed 64-bit for large deltas; + * clamp to a safe maximum. */ + unsigned long long sec_diff = (unsigned long long)(now.tv_sec - bw_last_refill.tv_sec); + if (sec_diff > 9223372036ULL) + sec_diff = 9223372036ULL; + unsigned long long elapsed_ns = + sec_diff * 1000000000ULL + (unsigned long long)(now.tv_nsec - bw_last_refill.tv_nsec); bw_last_refill = now; - long long tokens_to_add = (long long)((double)io_bwlimit * elapsed_ns / 1000000000.0); + long long tokens_to_add = (long long)((double)io_bwlimit * (double)elapsed_ns / 1000000000.0); bw_tokens += tokens_to_add; if (bw_tokens > (long long)io_bwlimit) bw_tokens = (long long)io_bwlimit; @@ -74,13 +80,23 @@ bool send_n_data(int file_descriptor, const void* data, size_t data_size) { if (io_bwlimit > 0 && chunk > 65536) chunk = 65536; ssize_t bytes_send; - if (io_ssl) + if (io_ssl) { bytes_send = SSL_write(io_ssl, (const char*)data + total_bytes_send, chunk); - else + if (bytes_send <= 0) { + int err = SSL_get_error(io_ssl, (int)bytes_send); + if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) { + /* Non-fatal: retry without counting progress */ + continue; + } + log_message(LOG_LEVEL_ERROR, "Could not send data (SSL error: %d)", err); + return false; + } + } else { bytes_send = write(fd, (const char*)data + total_bytes_send, chunk); - if (bytes_send <= 0) { - log_message(LOG_LEVEL_ERROR, "Could not send data"); - return false; + if (bytes_send <= 0) { + log_message(LOG_LEVEL_ERROR, "Could not send data"); + return false; + } } bw_throttle((size_t)bytes_send); total_bytes_send += bytes_send; @@ -95,18 +111,31 @@ bool receive_n_data(int file_descriptor, void* data, size_t data_size) { size_t total_bytes_received = 0; while (total_bytes_received < data_size) { ssize_t bytes_received; - if (io_ssl) + if (io_ssl) { bytes_received = SSL_read(io_ssl, (char*)data + total_bytes_received, data_size - total_bytes_received); - else + if (bytes_received <= 0) { + int err = SSL_get_error(io_ssl, (int)bytes_received); + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { + /* Non-fatal: retry without counting progress */ + continue; + } + if (bytes_received == 0) + log_message(LOG_LEVEL_ERROR, "Connection closed while receiving data"); + else + log_message(LOG_LEVEL_ERROR, "Could not receive bytes (SSL error: %d)", err); + return false; + } + } else { bytes_received = read(fd, (char*)data + total_bytes_received, data_size - total_bytes_received); - if (bytes_received <= 0) { - if (bytes_received == 0) - log_message(LOG_LEVEL_ERROR, "Connection closed while receiving data"); - else - log_message(LOG_LEVEL_ERROR, "Could not receive bytes"); - return false; + if (bytes_received <= 0) { + if (bytes_received == 0) + log_message(LOG_LEVEL_ERROR, "Connection closed while receiving data"); + else + log_message(LOG_LEVEL_ERROR, "Could not receive bytes"); + return false; + } } total_bytes_received += bytes_received; } @@ -151,6 +180,10 @@ char* receive_str(int file_descriptor) { size_t size; if (!receive_n_data(file_descriptor, &size, sizeof(size_t))) return NULL; + if (size > MAX_STRING_SIZE) { + log_message(LOG_LEVEL_ERROR, "String size %zu exceeds maximum %d", size, MAX_STRING_SIZE); + return NULL; + } char* data = (char*)malloc(size + 1); if (data == NULL) return NULL; diff --git a/src/shared/protocol.h b/src/shared/protocol.h index a7854f1..bc57d6b 100644 --- a/src/shared/protocol.h +++ b/src/shared/protocol.h @@ -5,6 +5,9 @@ #include #include +/* Maximum allowed string size for receive_str (10 MB) */ +#define MAX_STRING_SIZE (10 * 1024 * 1024) + typedef struct ssl_st SSL; typedef int Status; diff --git a/src/shared/transport_ssh.c b/src/shared/transport_ssh.c index 0462ce4..74d9909 100644 --- a/src/shared/transport_ssh.c +++ b/src/shared/transport_ssh.c @@ -124,7 +124,11 @@ Client* client_connect_ssh(const char* destination, int port) { else snprintf(ssh_user, sizeof(ssh_user), "%s", r.host); - char* ssh_argv[16]; + /* Max entries: ssh + 3 options*2 each + -p + port + user + cmd + arg + NULL = 13 */ + size_t ssh_argv_max = 32; + char** ssh_argv = calloc(ssh_argv_max, sizeof(char*)); + if (ssh_argv == NULL) + _exit(1); int ac = 0; char port_str[16]; ssh_argv[ac++] = "ssh"; @@ -135,15 +139,24 @@ Client* client_connect_ssh(const char* destination, int port) { ssh_argv[ac++] = "-o"; ssh_argv[ac++] = "ControlPath=~/.cache/fastsync-%r@%h:%p"; if (port > 0 && port != 22) { + if ((size_t)ac + 2 >= ssh_argv_max) { + free(ssh_argv); + _exit(1); + } ssh_argv[ac++] = "-p"; snprintf(port_str, sizeof(port_str), "%d", port); ssh_argv[ac++] = port_str; } + if ((size_t)ac + 3 >= ssh_argv_max) { + free(ssh_argv); + _exit(1); + } ssh_argv[ac++] = ssh_user; ssh_argv[ac++] = "fastsync-server"; ssh_argv[ac++] = "--stdio"; ssh_argv[ac] = NULL; execvp("ssh", ssh_argv); + free(ssh_argv); perror("exec of ssh failed"); ssize_t wret = write(exec_pipe[1], "x", 1); (void)wret; diff --git a/src/shared/transport_tcp.h b/src/shared/transport_tcp.h index 0c4b7da..9b4358f 100644 --- a/src/shared/transport_tcp.h +++ b/src/shared/transport_tcp.h @@ -1,20 +1,23 @@ #ifndef TRANSPORT_TCP_H #define TRANSPORT_TCP_H +#include #include #include +#include #include typedef struct Server { - struct sockaddr_in address; - unsigned int address_length; + struct sockaddr_storage address; + socklen_t address_length; int file_descriptor; + int port; void* ssl_ctx; } Server; typedef struct Client { - struct sockaddr_in address; - unsigned int address_length; + struct sockaddr_storage address; + socklen_t address_length; int file_descriptor; pid_t ssh_child_pid; void* ssl; diff --git a/src/shared/utils.c b/src/shared/utils.c index ad1e533..62004be 100644 --- a/src/shared/utils.c +++ b/src/shared/utils.c @@ -10,19 +10,23 @@ #include bool mkdir_r(const char* path) { - char* path_duplicate = malloc(strlen(path) + 1); + size_t path_len = strlen(path); + char* path_duplicate = malloc(path_len + 1); if (!path_duplicate) return false; - strcpy(path_duplicate, path); - char* path_current = (char*)malloc((strlen(path) + 2) * sizeof(char)); + memcpy(path_duplicate, path, path_len + 1); + /* Buffer for building subpaths: path_len + 1 for leading '/' + 1 for null */ + size_t buf_size = path_len + 2; + char* path_current = (char*)malloc(buf_size); if (!path_current) { free(path_duplicate); return false; } - char* path_current_position = path_current; + size_t pos = 0; if (path[0] == '/') { - strcpy(path_current, "/"); - path_current_position += 1; + path_current[0] = '/'; + path_current[1] = '\0'; + pos = 1; } else { path_current[0] = '\0'; } @@ -31,10 +35,16 @@ bool mkdir_r(const char* path) { const char* part = strtok_r(path_duplicate, delimiter, &saveptr); bool ok = true; while (part != NULL) { - strcpy(path_current_position, part); - path_current_position += strlen(part) * sizeof(char); - strcpy(path_current_position, "/"); - path_current_position += sizeof(char); + size_t part_len = strlen(part); + if (pos + part_len + 1 >= buf_size) { + ok = false; + break; + } + memcpy(path_current + pos, part, part_len); + pos += part_len; + path_current[pos] = '/'; + pos++; + path_current[pos] = '\0'; struct stat st; if (stat(path_current, &st) != 0) { if (mkdir(path_current, 0755) != 0) { @@ -61,6 +71,24 @@ char* str_dup(const char* string) { bool glob_match(const char* pattern, const char* str) { while (*pattern) { if (*pattern == '*') { + if (*(pattern + 1) == '*') { + /* ** pattern: matches zero or more characters including '/' */ + pattern += 2; /* skip both stars */ + /* If ** is immediately followed by '/', consume it too so that + * **/foo behaves intuitively (matches foo at any depth). */ + if (*pattern == '/') + pattern++; + /* Try matching the remainder of pattern at every position in str, + * including across '/' boundaries and at the very end. */ + while (1) { + if (glob_match(pattern, str)) + return true; + if (*str == '\0') + return false; + str++; + } + } + /* Single *: matches any characters except '/' */ pattern++; while (*str && *str != '/') { if (glob_match(pattern, str)) diff --git a/tests/runner.c b/tests/runner.c index 6ae6c12..9f22480 100644 --- a/tests/runner.c +++ b/tests/runner.c @@ -5,8 +5,11 @@ #include "test_data.h" #include "test_delta.h" #include "test_file.h" +#include "test_file_sendfile.h" #include "test_glob.h" +#include "test_log.h" #include "test_metadata.h" +#include "test_multiprocessing.h" #include "test_property.h" #include "test_protocol.h" #include "test_queue.h" @@ -14,6 +17,9 @@ #include "test_scanner.h" #include "test_shared_utils.h" #include "test_stress.h" +#include "test_transport_tcp.h" +#include "test_transport_ssh.h" +#include "test_transport_tls.h" #include "test_utils.h" #include @@ -38,9 +44,15 @@ int main() { RUN_TEST(test_metadata); RUN_TEST(test_glob); RUN_TEST(test_file); + RUN_TEST(test_file_sendfile); + RUN_TEST(test_log); + RUN_TEST(test_multiprocessing); RUN_TEST(test_robustness); RUN_TEST(test_stress); RUN_TEST(test_property); + RUN_TEST(test_transport_tcp); + RUN_TEST(test_transport_ssh); + RUN_TEST(test_transport_tls); printf("\n\033[1;36m=== TEST SUMMARY ===\033[0m\n"); printf("Total Tests Run: %d\n", tests_run); diff --git a/tests/test_data.c b/tests/test_data.c index 33c4888..266a9ad 100644 --- a/tests/test_data.c +++ b/tests/test_data.c @@ -23,6 +23,14 @@ static void test_data_create_empty() { data_destroy(d); } +static void test_data_create_empty_zero() { + Data* d = data_create_empty(0); + EXPECT_NOT_NULL(d); + EXPECT_NOT_NULL(d->data); + EXPECT_EQ_INT((int)d->size, 0); + data_destroy(d); +} + static void test_data_create_reserve() { Data* d = data_create_reserve(1024); EXPECT_NOT_NULL(d); @@ -44,6 +52,7 @@ static void test_data_destroy_normal() { void test_data() { test_data_create(); test_data_create_empty(); + test_data_create_empty_zero(); test_data_create_reserve(); test_data_destroy_null(); test_data_destroy_normal(); diff --git a/tests/test_file_sendfile.c b/tests/test_file_sendfile.c new file mode 100644 index 0000000..5984fa1 --- /dev/null +++ b/tests/test_file_sendfile.c @@ -0,0 +1,235 @@ +#include "test_file_sendfile.h" +#include "file.h" +#include "data.h" +#include "config.h" +#include "protocol.h" +#include "utils.h" +#include "test_utils.h" +#include +#include +#include +#include +#include + +static void test_sendfile_basic() { + const char* content = "Hello sendfile test content!"; + size_t len = strlen(content); + EXPECT_TRUE(to_disk("test_sendfile_basic.txt", content, len)); + + File* file = file_create("test_sendfile_basic.txt"); + EXPECT_NOT_NULL(file); + file->data->size = len; + file->data->data = malloc(len); + EXPECT_NOT_NULL(file->data->data); + memcpy(file->data->data, content, len); + + int p[2]; + EXPECT_EQ_INT(pipe(p), 0); + io_set_fds(p[0], p[1]); + io_set_bwlimit(0); + + pid_t pid = fork(); + if (pid == 0) { + close(p[1]); + // Receive path: read size then data + unsigned long long recv_size; + EXPECT_TRUE(receive_n_data(p[0], &recv_size, sizeof(recv_size))); + EXPECT_EQ_INT((int)recv_size, (int)len); + + char* buf = malloc(recv_size + 1); + EXPECT_NOT_NULL(buf); + EXPECT_TRUE(receive_n_data(p[0], buf, recv_size)); + buf[recv_size] = '\0'; + EXPECT_EQ_INT(memcmp(buf, content, len), 0); + free(buf); + close(p[0]); + _exit(0); + } else { + close(p[0]); + bool sent = file_send_sendfile(file, p[1], false, 0, false); + close(p[1]); + + int status; + waitpid(pid, &status, 0); + + file_destroy(file); + unlink("test_sendfile_basic.txt"); + + EXPECT_TRUE(sent); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); + } +} + +static void test_sendfile_with_path() { + const char* content = "Sendfile with path test"; + size_t len = strlen(content); + EXPECT_TRUE(to_disk("test_sendfile_path.txt", content, len)); + + File* file = file_create("test_sendfile_path.txt"); + EXPECT_NOT_NULL(file); + file->data->size = len; + file->data->data = malloc(len); + EXPECT_NOT_NULL(file->data->data); + memcpy(file->data->data, content, len); + + int p[2]; + EXPECT_EQ_INT(pipe(p), 0); + io_set_fds(p[0], p[1]); + io_set_bwlimit(0); + + pid_t pid = fork(); + if (pid == 0) { + close(p[1]); + char* recv_path = receive_str(p[0]); + EXPECT_NOT_NULL(recv_path); + EXPECT_EQ_STR(recv_path, "test_sendfile_path.txt"); + free(recv_path); + + unsigned long long recv_size; + EXPECT_TRUE(receive_n_data(p[0], &recv_size, sizeof(recv_size))); + + char* buf = malloc(recv_size + 1); + EXPECT_NOT_NULL(buf); + EXPECT_TRUE(receive_n_data(p[0], buf, recv_size)); + buf[recv_size] = '\0'; + EXPECT_EQ_INT(memcmp(buf, content, len), 0); + free(buf); + close(p[0]); + _exit(0); + } else { + close(p[0]); + bool sent = file_send_sendfile(file, p[1], false, 0, true); + close(p[1]); + + int status; + waitpid(pid, &status, 0); + + file_destroy(file); + unlink("test_sendfile_path.txt"); + + EXPECT_TRUE(sent); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); + } +} + +static void test_sendfile_empty_file() { + const char* content = ""; + size_t len = 0; + EXPECT_TRUE(to_disk("test_sendfile_empty.txt", content, len)); + + File* file = file_create("test_sendfile_empty.txt"); + EXPECT_NOT_NULL(file); + file->data->size = len; + file->data->data = NULL; + + int p[2]; + EXPECT_EQ_INT(pipe(p), 0); + io_set_fds(p[0], p[1]); + io_set_bwlimit(0); + + pid_t pid = fork(); + if (pid == 0) { + close(p[1]); + unsigned long long recv_size; + EXPECT_TRUE(receive_n_data(p[0], &recv_size, sizeof(recv_size))); + EXPECT_EQ_INT((int)recv_size, 0); + close(p[0]); + _exit(0); + } else { + close(p[0]); + bool sent = file_send_sendfile(file, p[1], false, 0, false); + close(p[1]); + + int status; + waitpid(pid, &status, 0); + + file_destroy(file); + unlink("test_sendfile_empty.txt"); + + EXPECT_TRUE(sent); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); + } +} + +static void test_sendfile_with_compression_fallback() { + // When compression_level > 0, sendfile falls back to file_send_single_calls + const char* content = "Sendfile compression fallback test data here."; + size_t len = strlen(content); + EXPECT_TRUE(to_disk("test_sendfile_comp.txt", content, len)); + + File* file = file_create("test_sendfile_comp.txt"); + EXPECT_NOT_NULL(file); + file->data->size = len; + file->data->data = malloc(len); + EXPECT_NOT_NULL(file->data->data); + memcpy(file->data->data, content, len); + + Config* cfg = config_create(str_dup(PROTOCOL_VERSION), str_dup("/tmp"), str_dup("/tmp"), false, + false, false, false, false, 0, false, 0); + + int p[2]; + EXPECT_EQ_INT(pipe(p), 0); + io_set_fds(p[0], p[1]); + io_set_bwlimit(0); + + pid_t pid = fork(); + if (pid == 0) { + close(p[1]); + File* received = file_receive(cfg, p[0]); + close(p[0]); + bool ok = true; + if (!received) ok = false; + else { + if (!received->data || received->data->size != len) ok = false; + else if (memcmp(received->data->data, content, len) != 0) ok = false; + } + file_destroy(received); + config_delete(cfg); + _exit(ok ? 0 : 1); + } else { + close(p[0]); + // Send with compression_level=1, should fall back to file_send_single_calls + bool sent = file_send_sendfile(file, p[1], false, 1, true); + close(p[1]); + + int status; + waitpid(pid, &status, 0); + + file_destroy(file); + config_delete(cfg); + unlink("test_sendfile_comp.txt"); + + EXPECT_TRUE(sent); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); + } +} + +static void test_sendfile_nonexistent_file() { + File* file = file_create("nonexistent_file_xyz_sendfile_test.txt"); + EXPECT_NOT_NULL(file); + file->data->size = 100; + file->data->data = malloc(100); + EXPECT_NOT_NULL(file->data->data); + memset(file->data->data, 0, 100); + + int p[2]; + EXPECT_EQ_INT(pipe(p), 0); + io_set_fds(p[0], p[1]); + io_set_bwlimit(0); + close(p[0]); + + bool sent = file_send_sendfile(file, p[1], false, 0, false); + close(p[1]); + + file_destroy(file); + // File doesn't exist on disk, sendfile should fail + EXPECT_FALSE(sent); +} + +void test_file_sendfile() { + test_sendfile_basic(); + test_sendfile_with_path(); + test_sendfile_empty_file(); + test_sendfile_with_compression_fallback(); + test_sendfile_nonexistent_file(); +} diff --git a/tests/test_file_sendfile.h b/tests/test_file_sendfile.h new file mode 100644 index 0000000..60c1d5e --- /dev/null +++ b/tests/test_file_sendfile.h @@ -0,0 +1,6 @@ +#ifndef TEST_FILE_SENDFILE_H +#define TEST_FILE_SENDFILE_H + +void test_file_sendfile(); + +#endif diff --git a/tests/test_log.c b/tests/test_log.c new file mode 100644 index 0000000..4490a1c --- /dev/null +++ b/tests/test_log.c @@ -0,0 +1,78 @@ +#include "test_log.h" +#include "log.h" +#include "test_utils.h" +#include +#include +#include +#include + +// Helper to redirect stderr temporarily +static int stderr_pipe[2]; + +static void capture_stderr_start() { + fflush(stderr); + EXPECT_EQ_INT(pipe(stderr_pipe), 0); + EXPECT_EQ_INT(dup2(stderr_pipe[1], STDERR_FILENO), STDERR_FILENO); + close(stderr_pipe[1]); +} + +static void capture_stderr_end() { + fflush(stderr); + close(stderr_pipe[0]); +} + +static void test_log_message_debug() { + set_log_level(LOG_LEVEL_DEBUG); + // Should output at DEBUG level + log_message(LOG_LEVEL_DEBUG, "Debug message test: %d", 42); + log_message(LOG_LEVEL_INFO, "Info message test"); + log_message(LOG_LEVEL_WARNING, "Warning message test"); + log_message(LOG_LEVEL_ERROR, "Error message test"); + // If we get here without crashing, the test passes + EXPECT_TRUE(true); +} + +static void test_log_message_level_filtering() { + set_log_level(LOG_LEVEL_WARNING); + capture_stderr_start(); + log_message(LOG_LEVEL_DEBUG, "Should NOT appear"); + log_message(LOG_LEVEL_INFO, "Should NOT appear"); + log_message(LOG_LEVEL_WARNING, "Should appear"); + log_message(LOG_LEVEL_ERROR, "Should appear"); + capture_stderr_end(); + // We can't easily check the content, but we verified it doesn't crash + EXPECT_TRUE(true); +} + +static void test_log_message_error() { + set_log_level(LOG_LEVEL_ERROR); + log_message(LOG_LEVEL_ERROR, "Error only: %s", "critical"); + EXPECT_TRUE(true); +} + +static void test_set_log_level() { + set_log_level(LOG_LEVEL_DEBUG); + log_message(LOG_LEVEL_DEBUG, "debug visible"); + set_log_level(LOG_LEVEL_WARNING); + log_message(LOG_LEVEL_DEBUG, "debug hidden (no crash)"); + log_message(LOG_LEVEL_WARNING, "warning visible"); + set_log_level(LOG_LEVEL_INFO); + log_message(LOG_LEVEL_INFO, "info visible"); + EXPECT_TRUE(true); +} + +static void test_log_message_various_args() { + set_log_level(LOG_LEVEL_DEBUG); + log_message(LOG_LEVEL_INFO, "String: %s, Int: %d, Hex: %x", "test", 123, 0xFF); + log_message(LOG_LEVEL_WARNING, "Warning with number %d", 42); + log_message(LOG_LEVEL_DEBUG, "Debug with pointer %p", (void*)0x1234); + EXPECT_TRUE(true); +} + +void test_log() { + test_log_message_debug(); + test_log_message_level_filtering(); + test_log_message_error(); + test_set_log_level(); + test_log_message_various_args(); +} diff --git a/tests/test_log.h b/tests/test_log.h new file mode 100644 index 0000000..2287c3d --- /dev/null +++ b/tests/test_log.h @@ -0,0 +1,6 @@ +#ifndef TEST_LOG_H +#define TEST_LOG_H + +void test_log(); + +#endif diff --git a/tests/test_multiprocessing.c b/tests/test_multiprocessing.c new file mode 100644 index 0000000..022173e --- /dev/null +++ b/tests/test_multiprocessing.c @@ -0,0 +1,91 @@ +#include "test_multiprocessing.h" +#include "multiprocessing.h" +#include "config.h" +#include "queue.h" +#include "utils.h" +#include "test_utils.h" +#include +#include + +static void test_pipeline_context_sender_create_destroy() { + Config* cfg = config_create(str_dup(PROTOCOL_VERSION), str_dup("/tmp"), str_dup("/tmp"), false, + false, false, false, false, 0, false, 0); + EXPECT_NOT_NULL(cfg); + + Queue* q_scanner = queue_create(10, free); + EXPECT_NOT_NULL(q_scanner); + + Queue* q_loader = queue_create(10, free); + EXPECT_NOT_NULL(q_loader); + + PipelineContextSender* ctx = pipeline_context_sender_create(cfg, q_scanner, q_loader); + EXPECT_NOT_NULL(ctx); + EXPECT_EQ_INT(ctx->scanner_done, false); + EXPECT_EQ_INT(ctx->loader_done, false); + EXPECT_NULL(ctx->manifest); + + pipeline_context_sender_destroy(ctx); + // cfg, q_scanner, q_loader are destroyed by pipeline_context_sender_destroy +} + +static void test_pipeline_context_sender_create_null_config() { + // Passing NULL config to pipeline_context_sender_create - it will be used + // and destroyed, so this should be tested carefully. + Queue* q_scanner = queue_create(10, free); + EXPECT_NOT_NULL(q_scanner); + Queue* q_loader = queue_create(10, free); + EXPECT_NOT_NULL(q_loader); + + PipelineContextSender* ctx = pipeline_context_sender_create(NULL, q_scanner, q_loader); + EXPECT_NOT_NULL(ctx); + EXPECT_NULL(ctx->config); + + // Destroy without config - config_delete(NULL) should be safe + pipeline_context_sender_destroy(ctx); +} + +static void test_pipeline_context_sender_destroy_null() { + pipeline_context_sender_destroy(NULL); +} + +static void test_pipeline_context_receiver_create_destroy() { + Config* cfg = config_create(str_dup(PROTOCOL_VERSION), str_dup("/tmp"), str_dup("/tmp"), false, + false, false, false, false, 0, false, 0); + EXPECT_NOT_NULL(cfg); + + Queue* q = queue_create(10, free); + EXPECT_NOT_NULL(q); + + PipelineContextReceiver* ctx = pipeline_context_receiver_create(cfg, q, 42); + EXPECT_NOT_NULL(ctx); + EXPECT_EQ_INT(ctx->file_descriptor, 42); + EXPECT_EQ_INT(ctx->receiver_done, false); + + pipeline_context_receiver_destroy(ctx); + // cfg, q are destroyed by pipeline_context_receiver_destroy +} + +static void test_pipeline_context_receiver_create_null_config() { + Queue* q = queue_create(10, free); + EXPECT_NOT_NULL(q); + + PipelineContextReceiver* ctx = pipeline_context_receiver_create(NULL, q, -1); + EXPECT_NOT_NULL(ctx); + EXPECT_NULL(ctx->config); + EXPECT_EQ_INT(ctx->file_descriptor, -1); + + pipeline_context_receiver_destroy(ctx); +} + +static void test_pipeline_context_receiver_destroy_null() { + pipeline_context_receiver_destroy(NULL); +} + +void test_multiprocessing() { + test_pipeline_context_sender_create_destroy(); + test_pipeline_context_sender_create_null_config(); + test_pipeline_context_sender_destroy_null(); + test_pipeline_context_receiver_create_destroy(); + test_pipeline_context_receiver_create_null_config(); + test_pipeline_context_receiver_destroy_null(); +} diff --git a/tests/test_multiprocessing.h b/tests/test_multiprocessing.h new file mode 100644 index 0000000..f59d282 --- /dev/null +++ b/tests/test_multiprocessing.h @@ -0,0 +1,6 @@ +#ifndef TEST_MULTIPROCESSING_H +#define TEST_MULTIPROCESSING_H + +void test_multiprocessing(); + +#endif diff --git a/tests/test_scanner.c b/tests/test_scanner.c index 7920e0a..faa1db6 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); + directory_scanner_create((char*)dir, false, 0, NULL, 0, NULL, 0, 0, 0, true); 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); + directory_scanner_create((char*)dir, false, 0, NULL, 0, NULL, 0, 0, 0, true); EXPECT_NOT_NULL(scanner); const Chunk* chunk = directory_scanner_next(scanner); @@ -112,7 +112,7 @@ static void test_scanner_empty_directory() { mkdir(dir, 0755); DirectoryScanner* scanner = - directory_scanner_create((char*)dir, false, 0, NULL, 0, NULL, 0, 0, 0); + directory_scanner_create((char*)dir, false, 0, NULL, 0, NULL, 0, 0, 0, true); EXPECT_NOT_NULL(scanner); const Chunk* chunk = directory_scanner_next(scanner); diff --git a/tests/test_transport_ssh.c b/tests/test_transport_ssh.c new file mode 100644 index 0000000..45b159a --- /dev/null +++ b/tests/test_transport_ssh.c @@ -0,0 +1,34 @@ +#include "test_transport_ssh.h" +#include "transport_ssh.h" +#include "transport_tcp.h" +#include "test_utils.h" +#include +#include + +static void test_ssh_connect_bad_destination() { + Client* client = client_connect_ssh("nosuchhost.local", 22); + // SSH connection to a non-existent host should fail (return NULL) + EXPECT_NULL(client); +} + +static void test_ssh_connect_null_destination() { + Client* client = client_connect_ssh(NULL, 22); + EXPECT_NULL(client); +} + +static void test_ssh_connect_bad_port() { + Client* client = client_connect_ssh("localhost", -1); + EXPECT_NULL(client); +} + +static void test_ssh_connect_zero_port() { + Client* client = client_connect_ssh("localhost", 0); + EXPECT_NULL(client); +} + +void test_transport_ssh() { + test_ssh_connect_bad_destination(); + test_ssh_connect_null_destination(); + test_ssh_connect_bad_port(); + test_ssh_connect_zero_port(); +} diff --git a/tests/test_transport_ssh.h b/tests/test_transport_ssh.h new file mode 100644 index 0000000..0df5a47 --- /dev/null +++ b/tests/test_transport_ssh.h @@ -0,0 +1,6 @@ +#ifndef TEST_TRANSPORT_SSH_H +#define TEST_TRANSPORT_SSH_H + +void test_transport_ssh(); + +#endif diff --git a/tests/test_transport_tcp.c b/tests/test_transport_tcp.c new file mode 100644 index 0000000..c19ffc2 --- /dev/null +++ b/tests/test_transport_tcp.c @@ -0,0 +1,90 @@ +#include "test_transport_tcp.h" +#include "transport_tcp.h" +#include "test_utils.h" +#include +#include +#include +#include +#include + +static void test_client_create_delete() { + Client* client = client_create(); + EXPECT_NOT_NULL(client); + EXPECT_EQ_INT(client->file_descriptor, -1); + EXPECT_EQ_INT(client->ssh_child_pid, -1); + client_delete(client); +} + +static void test_client_delete_null() { + client_delete(NULL); +} + +static void test_client_disconnect_null() { + client_disconnect(NULL); +} + +static void test_client_connect_bad_host() { + Client* client = client_create(); + EXPECT_NOT_NULL(client); + + // Connecting to a non-existent host should fail + bool connected = client_connect(client, "192.0.2.999", 12345); + EXPECT_FALSE(connected); + + client_disconnect(client); + client_delete(client); +} + +static void test_client_connect_bad_port() { + Client* client = client_create(); + EXPECT_NOT_NULL(client); + + // Connecting to port 0 should fail + bool connected = client_connect(client, "127.0.0.1", 0); + EXPECT_FALSE(connected); + + client_disconnect(client); + client_delete(client); +} + +static void test_client_connect_null_host() { + Client* client = client_create(); + EXPECT_NOT_NULL(client); + + bool connected = client_connect(client, NULL, 8080); + EXPECT_FALSE(connected); + + client_disconnect(client); + client_delete(client); +} + +static void test_server_create_delete() { + Server* server = server_create(0); + // server_create may return NULL if it fails to bind, but we need to check + // if it succeeds. Port 0 should bind to an ephemeral port. + if (server != NULL) { + EXPECT_NOT_NULL(server); + server_delete(&server); + EXPECT_NULL(server); + } else { + // On some systems port 0 may fail; that's okay + EXPECT_TRUE(true); + } +} + +static void test_server_delete_null() { + Server* server = NULL; + server_delete(&server); + EXPECT_NULL(server); +} + +void test_transport_tcp() { + test_client_create_delete(); + test_client_delete_null(); + test_client_disconnect_null(); + test_client_connect_bad_host(); + test_client_connect_bad_port(); + test_client_connect_null_host(); + test_server_create_delete(); + test_server_delete_null(); +} diff --git a/tests/test_transport_tcp.h b/tests/test_transport_tcp.h new file mode 100644 index 0000000..720209f --- /dev/null +++ b/tests/test_transport_tcp.h @@ -0,0 +1,6 @@ +#ifndef TEST_TRANSPORT_TCP_H +#define TEST_TRANSPORT_TCP_H + +void test_transport_tcp(); + +#endif diff --git a/tests/test_transport_tls.c b/tests/test_transport_tls.c new file mode 100644 index 0000000..b04e476 --- /dev/null +++ b/tests/test_transport_tls.c @@ -0,0 +1,69 @@ +#include "test_transport_tls.h" +#include "transport_tls.h" +#include "transport_tcp.h" +#include "test_utils.h" +#include +#include + +static void test_tls_global_init() { + bool ok = tls_global_init(); + // Should succeed in normal environments with OpenSSL + // May fail in minimal environments, but we just test it doesn't crash + EXPECT_TRUE(true); +} + +static void test_client_connect_tls_bad_host() { + // Ensure TLS is initialized + tls_global_init(); + + Client* client = client_create(); + EXPECT_NOT_NULL(client); + + // Connecting without TLS setup should fail + bool connected = client_connect_tls(client, "192.0.2.1", 443, NULL, NULL, NULL); + EXPECT_FALSE(connected); + + client_disconnect(client); + client_delete(client); +} + +static void test_client_connect_tls_null_params() { + tls_global_init(); + + Client* client = client_create(); + EXPECT_NOT_NULL(client); + + bool connected = client_connect_tls(client, NULL, 0, NULL, NULL, NULL); + EXPECT_FALSE(connected); + + client_disconnect(client); + client_delete(client); +} + +static void test_server_create_tls_no_cert() { + Server* server = server_create(0); + if (server != NULL) { + // Trying to set up TLS without cert/key files should fail + bool ok = server_create_tls(server, "/nonexistent/cert.pem", "/nonexistent/key.pem", + "/nonexistent/ca.pem"); + EXPECT_FALSE(ok); + server_delete(&server); + } +} + +static void test_server_create_tls_null_files() { + Server* server = server_create(0); + if (server != NULL) { + bool ok = server_create_tls(server, NULL, NULL, NULL); + EXPECT_FALSE(ok); + server_delete(&server); + } +} + +void test_transport_tls() { + test_tls_global_init(); + test_client_connect_tls_bad_host(); + test_client_connect_tls_null_params(); + test_server_create_tls_no_cert(); + test_server_create_tls_null_files(); +} diff --git a/tests/test_transport_tls.h b/tests/test_transport_tls.h new file mode 100644 index 0000000..aa13a69 --- /dev/null +++ b/tests/test_transport_tls.h @@ -0,0 +1,6 @@ +#ifndef TEST_TRANSPORT_TLS_H +#define TEST_TRANSPORT_TLS_H + +void test_transport_tls(); + +#endif