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/server/server.c b/src/server/server.c index d3bbc74..5dbf4e5 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); } 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/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/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/utils.c b/src/shared/utils.c index ad1e533..ada186a 100644 --- a/src/shared/utils.c +++ b/src/shared/utils.c @@ -61,6 +61,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/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..d5be290 --- /dev/null +++ b/tests/test_file_sendfile.c @@ -0,0 +1,38 @@ +#include "test_file_sendfile.h" +#include "file.h" +#include "data.h" +#include "protocol.h" +#include "utils.h" +#include "test_utils.h" +#include +#include +#include +#include +static void t1() { + const char*c="Hi";size_t l=2; + EXPECT_TRUE(to_disk("tsf1",c,l)); + File*f=file_create("tsf1");EXPECT_NOT_NULL(f); + f->data->size=l;f->data->data=malloc(l);memcpy(f->data->data,c,l); + 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]);bool ok=1;unsigned long long rs; + ok=ok&&receive_n_data(p[0],&rs,sizeof(rs));ok=ok&&((int)rs==(int)l); + if(ok&&l>0){char*b=malloc(rs+1);if(b){ok=ok&&receive_n_data(p[0],b,rs);free(b);}} + close(p[0]);_exit(ok?0:1); + }else{close(p[0]);bool s=file_send_sendfile(f,p[1],0,0,0);close(p[1]);int st;waitpid(pid,&st,0); + file_destroy(f);unlink("tsf1");EXPECT_TRUE(s);EXPECT_TRUE(WIFEXITED(st)&&WEXITSTATUS(st)==0);} +} +static void t2() { + EXPECT_TRUE(to_disk("tsf2","",0)); + File*f=file_create("tsf2");f->data->size=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]);bool ok=1;unsigned long long rs;ok=ok&&receive_n_data(p[0],&rs,sizeof(rs));ok=ok&&((int)rs==0);close(p[0]);_exit(ok?0:1);} + else{close(p[0]);bool s=file_send_sendfile(f,p[1],0,0,0);close(p[1]);int st;waitpid(pid,&st,0);file_destroy(f);unlink("tsf2");EXPECT_TRUE(s);EXPECT_TRUE(WIFEXITED(st)&&WEXITSTATUS(st)==0);} +} +static void t3() { + File*f=file_create("tsf3");f->data->size=100;f->data->data=malloc(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 s=file_send_sendfile(f,p[1],0,0,0);close(p[1]);file_destroy(f);EXPECT_FALSE(s); +} +void test_file_sendfile(){t1();t2();t3();} diff --git a/tests/test_file_sendfile.h b/tests/test_file_sendfile.h new file mode 100644 index 0000000..d0a7c00 --- /dev/null +++ b/tests/test_file_sendfile.h @@ -0,0 +1,4 @@ +#ifndef TEST_FILE_SENDFILE_H +#define TEST_FILE_SENDFILE_H +void test_file_sendfile(); +#endif diff --git a/tests/test_glob.c b/tests/test_glob.c index 5658d06..7f2208f 100644 --- a/tests/test_glob.c +++ b/tests/test_glob.c @@ -49,6 +49,33 @@ static void test_glob_question_star() { EXPECT_TRUE(glob_match("?*.txt", "a.txt")); } +static void test_glob_doublestar_all() { + EXPECT_TRUE(glob_match("**", "anything")); + EXPECT_TRUE(glob_match("**", "path/to/file.txt")); + EXPECT_TRUE(glob_match("**", "")); +} + +static void test_glob_doublestar_prefix() { + EXPECT_TRUE(glob_match("a/**/b", "a/b")); + EXPECT_TRUE(glob_match("a/**/b", "a/x/b")); + EXPECT_TRUE(glob_match("a/**/b", "a/x/y/b")); + EXPECT_FALSE(glob_match("a/**/b", "a/x/y/c")); +} + +static void test_glob_doublestar_suffix() { + EXPECT_TRUE(glob_match("a/**", "a/b")); + EXPECT_TRUE(glob_match("a/**", "a/b/c/d")); + EXPECT_TRUE(glob_match("a/**", "a/")); + EXPECT_FALSE(glob_match("a/**", "b/a")); +} + +static void test_glob_doublestar_subdir() { + EXPECT_TRUE(glob_match("**/foo", "foo")); + EXPECT_TRUE(glob_match("**/foo", "bar/foo")); + EXPECT_TRUE(glob_match("**/foo", "a/b/c/foo")); + EXPECT_FALSE(glob_match("**/foo", "foobar")); +} + void test_glob() { test_glob_exact_match(); test_glob_question_mark(); @@ -60,4 +87,8 @@ void test_glob() { test_glob_slash_not_matched(); test_glob_complex(); test_glob_question_star(); + test_glob_doublestar_all(); + test_glob_doublestar_prefix(); + test_glob_doublestar_suffix(); + test_glob_doublestar_subdir(); } diff --git a/tests/test_log.c b/tests/test_log.c new file mode 100644 index 0000000..ad0bf00 --- /dev/null +++ b/tests/test_log.c @@ -0,0 +1,8 @@ +#include "test_log.h" +#include "log.h" +#include "test_utils.h" +void test_log(){ + set_log_level(LOG_LEVEL_DEBUG);log_message(LOG_LEVEL_DEBUG,"d");log_message(LOG_LEVEL_INFO,"i");log_message(LOG_LEVEL_WARNING,"w");log_message(LOG_LEVEL_ERROR,"e"); + set_log_level(LOG_LEVEL_WARNING);log_message(LOG_LEVEL_DEBUG,"h");log_message(LOG_LEVEL_WARNING,"s"); + set_log_level(LOG_LEVEL_DEBUG);log_message(LOG_LEVEL_DEBUG,"d2");set_log_level(LOG_LEVEL_WARNING);log_message(LOG_LEVEL_DEBUG,"h2");log_message(LOG_LEVEL_WARNING,"w2"); +} diff --git a/tests/test_log.h b/tests/test_log.h new file mode 100644 index 0000000..198d107 --- /dev/null +++ b/tests/test_log.h @@ -0,0 +1,4 @@ +#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..6419c2e --- /dev/null +++ b/tests/test_multiprocessing.c @@ -0,0 +1,16 @@ +#include "test_multiprocessing.h" +#include "multiprocessing.h" +#include "config.h" +#include "queue.h" +#include "utils.h" +#include "test_utils.h" +void test_multiprocessing(){ + Config*c=config_create(str_dup("1"),str_dup("/t"),str_dup("/t"),0,0,0,0,0,0,0,0);EXPECT_NOT_NULL(c); + Queue*q1=queue_create(10,free),*q2=queue_create(10,free); + PipelineContextSender*ps=pipeline_context_sender_create(c,q1,q2);EXPECT_NOT_NULL(ps);EXPECT_NULL(ps->manifest);pipeline_context_sender_destroy(ps); + pipeline_context_sender_destroy(NULL); + Config*c2=config_create(str_dup("1"),str_dup("/t"),str_dup("/t"),0,0,0,0,0,0,0,0); + Queue*q=queue_create(10,free); + PipelineContextReceiver*pr=pipeline_context_receiver_create(c2,q,42);EXPECT_NOT_NULL(pr);EXPECT_EQ_INT(pr->file_descriptor,42);pipeline_context_receiver_destroy(pr); + pipeline_context_receiver_destroy(NULL); +} diff --git a/tests/test_multiprocessing.h b/tests/test_multiprocessing.h new file mode 100644 index 0000000..a44eaaa --- /dev/null +++ b/tests/test_multiprocessing.h @@ -0,0 +1,4 @@ +#ifndef TEST_MULTIPROCESSING_H +#define TEST_MULTIPROCESSING_H +void test_multiprocessing(); +#endif diff --git a/tests/test_protocol.c b/tests/test_protocol.c index d0070fe..09c2a46 100644 --- a/tests/test_protocol.c +++ b/tests/test_protocol.c @@ -169,6 +169,23 @@ static void test_receive_str_truncated() { close(p[0]); } +static void test_receive_str_oversized() { + int p[2]; + EXPECT_EQ_INT(pipe(p), 0); + io_set_fds(p[0], p[1]); + io_set_bwlimit(0); + + /* Send a size exceeding MAX_STRING_SIZE */ + size_t huge = MAX_STRING_SIZE + 1; + EXPECT_TRUE(send_n_data(0, &huge, sizeof(size_t))); + + char* received = receive_str(0); + EXPECT_NULL(received); + + close(p[0]); + close(p[1]); +} + void test_protocol() { test_send_receive_n_data(); test_send_receive_n_data_zero(); @@ -179,4 +196,5 @@ void test_protocol() { test_send_receive_status(); test_receive_n_data_truncated(); test_receive_str_truncated(); + test_receive_str_oversized(); } diff --git a/tests/test_transport_ssh.c b/tests/test_transport_ssh.c new file mode 100644 index 0000000..9c0482e --- /dev/null +++ b/tests/test_transport_ssh.c @@ -0,0 +1,9 @@ +#include "test_transport_ssh.h" +#include "transport_ssh.h" +#include "transport_tcp.h" +#include "test_utils.h" +void test_transport_ssh(){ + EXPECT_NULL(client_connect_ssh("nohost.x:22",22)); + EXPECT_NULL(client_connect_ssh(NULL,22)); + EXPECT_NULL(client_connect_ssh("bad",22)); +} diff --git a/tests/test_transport_ssh.h b/tests/test_transport_ssh.h new file mode 100644 index 0000000..1d48a1d --- /dev/null +++ b/tests/test_transport_ssh.h @@ -0,0 +1,4 @@ +#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..cf27708 --- /dev/null +++ b/tests/test_transport_tcp.c @@ -0,0 +1,11 @@ +#include "test_transport_tcp.h" +#include "transport_tcp.h" +#include "test_utils.h" +void test_transport_tcp(){ + Client*c=client_create();EXPECT_NOT_NULL(c);client_delete(c); + client_delete(NULL);client_disconnect(NULL); + c=client_create();EXPECT_FALSE(client_connect(c,"9.9.9.9",1));client_disconnect(c);client_delete(c); + c=client_create();EXPECT_FALSE(client_connect(c,"1.1.1.1",0));client_disconnect(c);client_delete(c); + c=client_create();EXPECT_FALSE(client_connect(c,NULL,1));client_disconnect(c);client_delete(c); + Server*s=server_create(0);if(s){server_delete(&s);}Server*x=NULL;server_delete(&x); +} diff --git a/tests/test_transport_tcp.h b/tests/test_transport_tcp.h new file mode 100644 index 0000000..52fdc6e --- /dev/null +++ b/tests/test_transport_tcp.h @@ -0,0 +1,4 @@ +#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..cb885b4 --- /dev/null +++ b/tests/test_transport_tls.c @@ -0,0 +1,10 @@ +#include "test_transport_tls.h" +#include "transport_tls.h" +#include "transport_tcp.h" +#include "test_utils.h" +void test_transport_tls(){ + tls_global_init(); + Client*c=client_create();EXPECT_FALSE(client_connect_tls(c,"1.2.3.4",443,NULL,NULL,NULL));client_disconnect(c);client_delete(c); + c=client_create();EXPECT_FALSE(client_connect_tls(c,NULL,0,NULL,NULL,NULL));client_disconnect(c);client_delete(c); + Server*s=server_create(0);if(s){EXPECT_FALSE(server_create_tls(s,"/a.pem","/b.pem","/c.pem"));server_delete(&s);} +} diff --git a/tests/test_transport_tls.h b/tests/test_transport_tls.h new file mode 100644 index 0000000..5ffbacb --- /dev/null +++ b/tests/test_transport_tls.h @@ -0,0 +1,4 @@ +#ifndef TEST_TRANSPORT_TLS_H +#define TEST_TRANSPORT_TLS_H +void test_transport_tls(); +#endif