From c1e1db8f5e3436981f37e2638d0abf21e07873d8 Mon Sep 17 00:00:00 2001 From: TapTap Date: Mon, 20 Jul 2026 18:01:23 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20refactoring=20and=20portability=20?= =?UTF-8?q?=E2=80=94=20issues=20#61,=20#51,=20#52?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/client_cli.c | 4 + src/client/scanner.c | 59 ++++++++- src/server/server.c | 4 + src/shared/compression.c | 12 +- src/shared/data.c | 2 + src/shared/data.h | 2 +- src/shared/log.c | 2 +- src/shared/log.h | 2 +- src/shared/metadata.c | 85 ++++++++----- src/shared/metadata.h | 4 +- src/shared/protocol.c | 70 ++++++++--- src/shared/protocol.h | 3 + src/shared/transport_ssh.c | 15 ++- src/shared/transport_tcp.h | 11 +- src/shared/utils.c | 30 +++-- tests/test_data.c | 9 ++ tests/test_file_sendfile.c | 235 +++++++++++++++++++++++++++++++++++ tests/test_file_sendfile.h | 6 + tests/test_log.c | 78 ++++++++++++ tests/test_log.h | 6 + tests/test_multiprocessing.c | 91 ++++++++++++++ tests/test_multiprocessing.h | 6 + tests/test_transport_ssh.c | 34 +++++ tests/test_transport_ssh.h | 6 + tests/test_transport_tcp.c | 90 ++++++++++++++ tests/test_transport_tcp.h | 6 + tests/test_transport_tls.c | 69 ++++++++++ tests/test_transport_tls.h | 6 + 28 files changed, 877 insertions(+), 70 deletions(-) create mode 100644 tests/test_file_sendfile.c create mode 100644 tests/test_file_sendfile.h create mode 100644 tests/test_log.c create mode 100644 tests/test_log.h create mode 100644 tests/test_multiprocessing.c create mode 100644 tests/test_multiprocessing.h create mode 100644 tests/test_transport_ssh.c create mode 100644 tests/test_transport_ssh.h create mode 100644 tests/test_transport_tcp.c create mode 100644 tests/test_transport_tcp.h create mode 100644 tests/test_transport_tls.c create mode 100644 tests/test_transport_tls.h diff --git a/src/client/client_cli.c b/src/client/client_cli.c index d7cff62..7fd2e57 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(" -V, --version Show version information\n"); printf(" --help Show this help\n"); } @@ -80,6 +81,9 @@ int main(int argc, char* argv[]) { if (strcmp(argv[i], "--help") == 0) { print_usage(); goto cleanup; + } else if (strcmp(argv[i], "--version") == 0 || strcmp(argv[i], "-V") == 0) { + printf("FastSync version %s\n", PROTOCOL_VERSION); + goto cleanup; } else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--archive") == 0) { config->use_compression = true; config->use_multithreading = true; 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..ed3617d 100644 --- a/src/server/server.c +++ b/src/server/server.c @@ -134,6 +134,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 +150,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/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/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/protocol.c b/src/shared/protocol.c index 4d91ce6..6408906 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 * 1000000000LL could overflow a signed 64-bit if the elapsed + * time is very large; 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; } @@ -138,6 +167,10 @@ static const char* status_to_string(Status status) { } bool send_str(int file_descriptor, const char* data) { + if (data == NULL) { + log_message(LOG_LEVEL_ERROR, "send_str called with NULL data"); + return false; + } size_t size = strlen(data); if (!send_n_data(file_descriptor, &size, sizeof(size_t))) return false; @@ -151,6 +184,11 @@ 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, "receive_str: size %zu exceeds maximum %zu", size, + (size_t)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..bf8c214 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) { 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_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