Compare commits

...

23 Commits

Author SHA1 Message Date
TapTap bd2af8071a fix: address review findings — sendfile EINTR, path traversal check
CI / lint (pull_request) Successful in 9s
CI / sanitizers (address) (pull_request) Successful in 16s
CI / sanitizers (undefined) (pull_request) Successful in 16s
CI / coverage (pull_request) Successful in 11s
CI / fuzz-build (pull_request) Successful in 13s
CI / valgrind (pull_request) Successful in 12s
CI / build-and-test (pull_request) Successful in 55s
2026-07-21 14:17:51 +02:00
TapTap b20e39012e fix: clang-format compliance
CI / lint (pull_request) Successful in 9s
CI / sanitizers (address) (pull_request) Successful in 14s
CI / sanitizers (undefined) (pull_request) Successful in 16s
CI / fuzz-build (pull_request) Successful in 15s
CI / coverage (pull_request) Successful in 9s
CI / valgrind (pull_request) Successful in 13s
CI / build-and-test (pull_request) Successful in 53s
2026-07-20 22:01:00 +02:00
TapTap 58a83193ff fix: test quality — cppcheck suppressions, TLS test addresses, log test isolation
CI / lint (pull_request) Failing after 2s
CI / build-and-test (pull_request) Has been skipped
CI / sanitizers (address) (pull_request) Has been skipped
CI / sanitizers (undefined) (pull_request) Has been skipped
CI / fuzz-build (pull_request) Has been skipped
CI / coverage (pull_request) Has been skipped
CI / valgrind (pull_request) Has been skipped
2026-07-20 21:27:37 +02:00
TapTap 5b5c7b2a7e fix: security issues — path traversal, TLS hostname, SUID, OOM, stack overflow
CI / lint (pull_request) Failing after 3s
CI / build-and-test (pull_request) Has been skipped
CI / sanitizers (address) (pull_request) Has been skipped
CI / sanitizers (undefined) (pull_request) Has been skipped
CI / fuzz-build (pull_request) Has been skipped
CI / coverage (pull_request) Has been skipped
CI / valgrind (pull_request) Has been skipped
2026-07-20 21:26:11 +02:00
TapTap 88ad77e397 fix: memory safety — malloc NULL checks, strcpy→memcpy, str_dup NULL check, remove unused include
CI / lint (pull_request) Failing after 3s
CI / build-and-test (pull_request) Has been skipped
CI / sanitizers (address) (pull_request) Has been skipped
CI / sanitizers (undefined) (pull_request) Has been skipped
CI / fuzz-build (pull_request) Has been skipped
CI / coverage (pull_request) Has been skipped
CI / valgrind (pull_request) Has been skipped
2026-07-20 21:26:01 +02:00
TapTap eb6034044b fix: accept_loop race condition, auto-detect valgrind, cppcheck suppressions
CI / lint (pull_request) Successful in 9s
CI / sanitizers (undefined) (pull_request) Successful in 16s
CI / sanitizers (address) (pull_request) Successful in 16s
CI / coverage (pull_request) Successful in 10s
CI / fuzz-build (pull_request) Successful in 13s
CI / valgrind (pull_request) Successful in 12s
CI / build-and-test (pull_request) Successful in 53s
2026-07-20 21:09:10 +02:00
TapTap 432e9a3ed4 ci: re-trigger after cppcheck fixes
CI / lint (pull_request) Failing after 9s
CI / build-and-test (pull_request) Has been skipped
CI / sanitizers (address) (pull_request) Has been skipped
CI / sanitizers (undefined) (pull_request) Has been skipped
CI / fuzz-build (pull_request) Has been skipped
CI / coverage (pull_request) Has been skipped
CI / valgrind (pull_request) Has been skipped
2026-07-20 20:41:22 +02:00
TapTap 2265f93a18 fix: cppcheck — remove redundant free before _exit in transport_ssh.c
CI / lint (pull_request) Failing after 9s
CI / build-and-test (pull_request) Has been skipped
CI / sanitizers (address) (pull_request) Has been skipped
CI / sanitizers (undefined) (pull_request) Has been skipped
CI / fuzz-build (pull_request) Has been skipped
CI / coverage (pull_request) Has been skipped
CI / valgrind (pull_request) Has been skipped
2026-07-20 20:39:42 +02:00
TapTap b816437777 ci: re-trigger after fixes
CI / lint (pull_request) Failing after 9s
CI / build-and-test (pull_request) Has been skipped
CI / sanitizers (address) (pull_request) Has been skipped
CI / sanitizers (undefined) (pull_request) Has been skipped
CI / fuzz-build (pull_request) Has been skipped
CI / coverage (pull_request) Has been skipped
CI / valgrind (pull_request) Has been skipped
2026-07-20 20:28:21 +02:00
TapTap c5d8a4e2ea ci: re-trigger after review fixes
CI / lint (pull_request) Failing after 9s
CI / build-and-test (pull_request) Has been skipped
CI / sanitizers (address) (pull_request) Has been skipped
CI / sanitizers (undefined) (pull_request) Has been skipped
CI / fuzz-build (pull_request) Has been skipped
CI / coverage (pull_request) Has been skipped
CI / valgrind (pull_request) Has been skipped
2026-07-20 20:11:28 +02:00
TapTap 0d57f3c812 fix: review fixes — getsockname, memcpy, clang-format
CI / lint (pull_request) Failing after 9s
CI / build-and-test (pull_request) Has been skipped
CI / sanitizers (address) (pull_request) Has been skipped
CI / sanitizers (undefined) (pull_request) Has been skipped
CI / fuzz-build (pull_request) Has been skipped
CI / coverage (pull_request) Has been skipped
CI / valgrind (pull_request) Has been skipped
2026-07-20 19:52:48 +02:00
TapTap 81739b79c5 ci: trigger CI on PR
CI / lint (pull_request) Failing after 2s
CI / build-and-test (pull_request) Has been skipped
CI / sanitizers (address) (pull_request) Has been skipped
CI / sanitizers (undefined) (pull_request) Has been skipped
CI / fuzz-build (pull_request) Has been skipped
CI / coverage (pull_request) Has been skipped
CI / valgrind (pull_request) Has been skipped
2026-07-20 19:48:35 +02:00
TapTap a2bcda1fcf fix: initialize ssl_ctx in server_create, update tests for sockaddr_storage API
CI / lint (pull_request) Failing after 2s
CI / build-and-test (pull_request) Has been skipped
CI / sanitizers (address) (pull_request) Has been skipped
CI / sanitizers (undefined) (pull_request) Has been skipped
CI / fuzz-build (pull_request) Has been skipped
CI / coverage (pull_request) Has been skipped
CI / valgrind (pull_request) Has been skipped
2026-07-20 19:39:13 +02:00
TapTap 6b8979a040 Merge pull request 'Revert all 6 merged PRs (#80-#85)' (#86) from revert-merged-prs into main
CI / lint (push) Successful in 7s
CI / sanitizers (address) (push) Successful in 14s
CI / sanitizers (undefined) (push) Successful in 13s
CI / fuzz-build (push) Successful in 13s
CI / coverage (push) Successful in 9s
CI / build-and-test (push) Successful in 53s
CI / valgrind (push) Successful in 11s
Reviewed-on: #86
2026-07-20 19:37:23 +02:00
TapTap bcf5ffcf40 Revert "Merge pull request 'Fix memory/null safety bugs (#74, #72, #69, #64, #60, #50, #49, #65)' (#80) from fix/memory-safety into main"
CI / lint (pull_request) Successful in 7s
CI / sanitizers (address) (pull_request) Successful in 14s
CI / sanitizers (undefined) (pull_request) Successful in 13s
CI / fuzz-build (pull_request) Successful in 12s
CI / coverage (pull_request) Successful in 9s
CI / build-and-test (pull_request) Successful in 53s
CI / valgrind (pull_request) Successful in 12s
This reverts commit ddfdb3825a, reversing
changes made to 504a3a4d4f.
2026-07-20 19:36:09 +02:00
TapTap 5049a7bbf0 Revert "Merge pull request 'Fix refactoring and portability issues (#61, #51, #52)' (#81) from fix/refactoring into main"
This reverts commit df0f52ce39, reversing
changes made to ddfdb3825a.
2026-07-20 19:36:09 +02:00
TapTap e6e8679bfc Revert "Merge pull request 'Fix logic/correctness bugs (#73, #68, #67, #66, #59, #58, #54, #48, #53)' (#82) from fix/logic-correctness into main"
This reverts commit 28d076fc28, reversing
changes made to df0f52ce39.
2026-07-20 19:36:09 +02:00
TapTap 6831e7e6fb Revert "Merge pull request 'Add unit test coverage (#71, #63, #62, #56, #55)' (#83) from fix/test-coverage into main"
This reverts commit 12ca4b13c8, reversing
changes made to 28d076fc28.
2026-07-20 19:36:09 +02:00
TapTap e3484939b6 Revert "Merge pull request 'Features and enhancements (#70, #36, #34, #33, #32, #57, #40, #37)' (#84) from fix/enhancements into main"
This reverts commit 8f25f6a6b6, reversing
changes made to 12ca4b13c8.
2026-07-20 19:36:09 +02:00
TapTap 2f9a4fac78 Revert "Merge pull request 'Fix integration issues: ssl_ctx init + test API updates' (#85) from fix/integration-cleanup into main"
This reverts commit e1f9ba090f, reversing
changes made to 8f25f6a6b6.
2026-07-20 19:36:09 +02:00
TapTap e1f9ba090f Merge pull request 'Fix integration issues: ssl_ctx init + test API updates' (#85) from fix/integration-cleanup into main
CI / lint (push) Failing after 2s
CI / build-and-test (push) Has been skipped
CI / sanitizers (address) (push) Has been skipped
CI / sanitizers (undefined) (push) Has been skipped
CI / fuzz-build (push) Has been skipped
CI / coverage (push) Has been skipped
CI / valgrind (push) Has been skipped
2026-07-20 19:34:20 +02:00
TapTap f6b4d89a3c fix: initialize ssl_ctx in server_create, update tests for sockaddr_storage API
CI / lint (pull_request) Failing after 2s
CI / build-and-test (pull_request) Has been skipped
CI / sanitizers (address) (pull_request) Has been skipped
CI / sanitizers (undefined) (pull_request) Has been skipped
CI / fuzz-build (pull_request) Has been skipped
CI / coverage (pull_request) Has been skipped
CI / valgrind (pull_request) Has been skipped
2026-07-20 19:34:12 +02:00
TapTap 8f25f6a6b6 Merge pull request 'Features and enhancements (#70, #36, #34, #33, #32, #57, #40, #37)' (#84) from fix/enhancements into main
CI / lint (push) Failing after 2s
CI / build-and-test (push) Has been skipped
CI / sanitizers (address) (push) Has been skipped
CI / sanitizers (undefined) (push) Has been skipped
CI / fuzz-build (push) Has been skipped
CI / coverage (push) Has been skipped
CI / valgrind (push) Has been skipped
2026-07-20 19:31:59 +02:00
22 changed files with 218 additions and 78 deletions
+13 -8
View File
@@ -16,17 +16,17 @@ DirectoryScanner* directory_scanner_create(char* root_directory, bool use_metada
int exclude_count, char** include_patterns, int exclude_count, char** include_patterns,
int include_count, unsigned long long max_size, int include_count, unsigned long long max_size,
unsigned long long min_size) { unsigned long long min_size) {
return directory_scanner_create_full(root_directory, use_metadata, chunk_size, return directory_scanner_create_full(root_directory, use_metadata, chunk_size, exclude_patterns,
exclude_patterns, exclude_count, exclude_count, include_patterns, include_count, max_size,
include_patterns, include_count, min_size, true);
max_size, min_size, true);
} }
DirectoryScanner* directory_scanner_create_full(char* root_directory, bool use_metadata, DirectoryScanner* directory_scanner_create_full(char* root_directory, bool use_metadata,
unsigned long long chunk_size, char** exclude_patterns, unsigned long long chunk_size,
int exclude_count, char** include_patterns, char** exclude_patterns, int exclude_count,
int include_count, unsigned long long max_size, char** include_patterns, int include_count,
unsigned long long min_size, bool follow_symlinks) { unsigned long long max_size,
unsigned long long min_size, bool follow_symlinks) {
DirectoryScanner* scanner = malloc(sizeof(DirectoryScanner)); DirectoryScanner* scanner = malloc(sizeof(DirectoryScanner));
if (scanner == NULL) if (scanner == NULL)
return NULL; return NULL;
@@ -230,6 +230,11 @@ Chunk* directory_scanner_next(DirectoryScanner* scanner) {
if (link_len >= 0) { if (link_len >= 0) {
link_buf[link_len] = '\0'; link_buf[link_len] = '\0';
file->link_target = str_dup(link_buf); file->link_target = str_dup(link_buf);
if (file->link_target == NULL) {
file_destroy(file);
free(cur_path);
continue;
}
} }
file->data->size = 0; file->data->size = 0;
if (scanner->use_metadata) if (scanner->use_metadata)
+5 -4
View File
@@ -27,10 +27,11 @@ DirectoryScanner* directory_scanner_create(char* root_directory, bool use_metada
int include_count, unsigned long long max_size, int include_count, unsigned long long max_size,
unsigned long long min_size); unsigned long long min_size);
DirectoryScanner* directory_scanner_create_full(char* root_directory, bool use_metadata, DirectoryScanner* directory_scanner_create_full(char* root_directory, bool use_metadata,
unsigned long long chunk_size, char** exclude_patterns, unsigned long long chunk_size,
int exclude_count, char** include_patterns, char** exclude_patterns, int exclude_count,
int include_count, unsigned long long max_size, char** include_patterns, int include_count,
unsigned long long min_size, bool follow_symlinks); unsigned long long max_size,
unsigned long long min_size, bool follow_symlinks);
Chunk* directory_scanner_next(DirectoryScanner* scanner); Chunk* directory_scanner_next(DirectoryScanner* scanner);
void directory_scanner_destroy(DirectoryScanner* scanner); void directory_scanner_destroy(DirectoryScanner* scanner);
+1
View File
@@ -153,6 +153,7 @@ static volatile sig_atomic_t g_server_cleanup_requested = 0;
static void cleanup(int sig) { static void cleanup(int sig) {
(void)sig; (void)sig;
server_request_shutdown();
g_server_cleanup_requested = 1; g_server_cleanup_requested = 1;
} }
+23
View File
@@ -4,6 +4,7 @@
#include "protocol.h" #include "protocol.h"
#include "utils.h" #include "utils.h"
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@@ -14,6 +15,8 @@ Config* config_create(char* version, char* send_directory, char* receive_directo
bool use_sendfile, unsigned long long chunk_size) { bool use_sendfile, unsigned long long chunk_size) {
Config* config = malloc(sizeof(Config)); Config* config = malloc(sizeof(Config));
if (config == NULL)
return NULL;
config->version = version; config->version = version;
config->send_directory = send_directory; config->send_directory = send_directory;
config->receive_root_directory = receive_directory; config->receive_root_directory = receive_directory;
@@ -246,10 +249,21 @@ Config* config_receive(int file_descriptor) {
config->follow_symlinks = false; config->follow_symlinks = false;
config->partial = false; config->partial = false;
#define MAX_PATTERN_COUNT 10000
// Receive exclude patterns // Receive exclude patterns
int ec; int ec;
if (!receive_int(file_descriptor, &ec)) if (!receive_int(file_descriptor, &ec))
goto error; goto error;
if (ec > MAX_PATTERN_COUNT) {
log_message(LOG_LEVEL_ERROR, "Exclude pattern count %d exceeds maximum %d", ec,
MAX_PATTERN_COUNT);
goto error;
}
if ((size_t)ec > SIZE_MAX / sizeof(char*)) {
log_message(LOG_LEVEL_ERROR, "Exclude pattern count %d would cause integer overflow", ec);
goto error;
}
config->exclude_count = ec; config->exclude_count = ec;
if (ec > 0) { if (ec > 0) {
config->exclude_patterns = malloc((size_t)ec * sizeof(char*)); config->exclude_patterns = malloc((size_t)ec * sizeof(char*));
@@ -274,6 +288,15 @@ Config* config_receive(int file_descriptor) {
int ic; int ic;
if (!receive_int(file_descriptor, &ic)) if (!receive_int(file_descriptor, &ic))
goto error; goto error;
if (ic > MAX_PATTERN_COUNT) {
log_message(LOG_LEVEL_ERROR, "Include pattern count %d exceeds maximum %d", ic,
MAX_PATTERN_COUNT);
goto error;
}
if ((size_t)ic > SIZE_MAX / sizeof(char*)) {
log_message(LOG_LEVEL_ERROR, "Include pattern count %d would cause integer overflow", ic);
goto error;
}
config->include_count = ic; config->include_count = ic;
if (ic > 0) { if (ic > 0) {
config->include_patterns = malloc((size_t)ic * sizeof(char*)); config->include_patterns = malloc((size_t)ic * sizeof(char*));
+53 -5
View File
@@ -1,4 +1,5 @@
#include <dirent.h> #include <dirent.h>
#include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <libgen.h> #include <libgen.h>
#include <stddef.h> #include <stddef.h>
@@ -20,8 +21,8 @@
#include "protocol.h" #include "protocol.h"
#include "utils.h" #include "utils.h"
#define STREAM_THRESHOLD (64ULL * 1024 * 1024) /* 64 MB */ #define STREAM_THRESHOLD (64ULL * 1024 * 1024) /* 64 MB */
#define STREAM_CHUNK_SIZE (1ULL * 1024 * 1024) /* 1 MB */ #define STREAM_CHUNK_SIZE (1ULL * 1024 * 1024) /* 1 MB */
File* file_create(const char* path) { File* file_create(const char* path) {
File* file = (File*)malloc(sizeof(File)); File* file = (File*)malloc(sizeof(File));
@@ -37,7 +38,7 @@ File* file_create(const char* path) {
return NULL; return NULL;
} }
strcpy(file->path, path); memcpy(file->path, path, path_len + 1);
file->data = data_create_reserve(0); file->data = data_create_reserve(0);
if (file->data == NULL) { if (file->data == NULL) {
free(file->path); free(file->path);
@@ -126,7 +127,11 @@ static bool file_send_streaming(File* file, int file_descriptor) {
return false; return false;
} }
char buf[STREAM_CHUNK_SIZE]; char* buf = malloc(STREAM_CHUNK_SIZE);
if (!buf) {
fclose(fp);
return false;
}
unsigned long long remaining = total_size; unsigned long long remaining = total_size;
while (remaining > 0) { while (remaining > 0) {
size_t to_read = (size_t)((remaining < STREAM_CHUNK_SIZE) ? remaining : STREAM_CHUNK_SIZE); size_t to_read = (size_t)((remaining < STREAM_CHUNK_SIZE) ? remaining : STREAM_CHUNK_SIZE);
@@ -135,15 +140,18 @@ static bool file_send_streaming(File* file, int file_descriptor) {
if (ferror(fp)) { if (ferror(fp)) {
perror("Read error during streaming"); perror("Read error during streaming");
} }
free(buf);
fclose(fp); fclose(fp);
return false; return false;
} }
if (!send_n_data(file_descriptor, buf, nread)) { if (!send_n_data(file_descriptor, buf, nread)) {
free(buf);
fclose(fp); fclose(fp);
return false; return false;
} }
remaining -= (unsigned long long)nread; remaining -= (unsigned long long)nread;
} }
free(buf);
fclose(fp); fclose(fp);
return true; return true;
} }
@@ -192,11 +200,37 @@ bool file_send_single_calls(File* file, int file_descriptor, bool use_metadata,
return true; return true;
} }
static bool has_path_traversal(const char* path) {
// Check if ".." appears as a path component — at start, end, or between '/'
const char* p = path;
while ((p = strstr(p, "..")) != NULL) {
bool at_start = (p == path);
bool after_slash = (p > path && p[-1] == '/');
bool at_end = (p[2] == '\0');
bool before_slash = (p[2] == '/');
if ((at_start || after_slash) && (at_end || before_slash))
return true;
p += 2;
}
return false;
}
bool file_save_to_disk(const char* root_directory, File* file) { bool file_save_to_disk(const char* root_directory, File* file) {
if (file->type == FILE_TYPE_SYMLINK && file->link_target) { if (file->type == FILE_TYPE_SYMLINK && file->link_target) {
// Validate link_target — reject absolute paths or traversal
if (file->link_target[0] == '/' || has_path_traversal(file->link_target)) {
log_message(LOG_LEVEL_ERROR, "Path traversal blocked in symlink target: %s",
file->link_target);
return false;
}
char* disk_path = path_cat((char*)root_directory, file->path); char* disk_path = path_cat((char*)root_directory, file->path);
if (disk_path == NULL) if (disk_path == NULL)
return false; return false;
if (has_path_traversal(disk_path)) {
log_message(LOG_LEVEL_ERROR, "Path traversal blocked: %s", disk_path);
free(disk_path);
return false;
}
unlink(disk_path); unlink(disk_path);
bool ok = (symlink(file->link_target, disk_path) == 0); bool ok = (symlink(file->link_target, disk_path) == 0);
if (ok && file->metadata) if (ok && file->metadata)
@@ -208,6 +242,11 @@ bool file_save_to_disk(const char* root_directory, File* file) {
char* disk_path = path_cat((char*)root_directory, file->path); char* disk_path = path_cat((char*)root_directory, file->path);
if (disk_path == NULL) if (disk_path == NULL)
return false; return false;
if (has_path_traversal(disk_path)) {
log_message(LOG_LEVEL_ERROR, "Path traversal blocked: %s", disk_path);
free(disk_path);
return false;
}
bool ok = to_disk(disk_path, file->data->data, file->data->size); bool ok = to_disk(disk_path, file->data->data, file->data->size);
if (ok) if (ok)
file_restore_metadata(disk_path, file->metadata); file_restore_metadata(disk_path, file->metadata);
@@ -423,6 +462,13 @@ File* receive_incremental_check(int fd, const Config* config, bool* skipped) {
} }
char* full_path = path_cat(config->receive_root_directory, check_path); char* full_path = path_cat(config->receive_root_directory, check_path);
if (full_path && strstr(full_path, "..") != NULL) {
log_message(LOG_LEVEL_ERROR, "Path traversal blocked: %s", full_path);
free(full_path);
free(check_path);
send_status(fd, STATUS_ERROR);
return NULL;
}
struct stat st; struct stat st;
bool has_old_file = (full_path && stat(full_path, &st) == 0); bool has_old_file = (full_path && stat(full_path, &st) == 0);
unsigned long long old_size = has_old_file ? (unsigned long long)st.st_size : 0; unsigned long long old_size = has_old_file ? (unsigned long long)st.st_size : 0;
@@ -431,7 +477,7 @@ File* receive_incremental_check(int fd, const Config* config, bool* skipped) {
if (config->partial && !has_old_file && full_path) { if (config->partial && !has_old_file && full_path) {
char* partial_path = malloc(strlen(full_path) + 20); char* partial_path = malloc(strlen(full_path) + 20);
if (partial_path) { if (partial_path) {
sprintf(partial_path, "%s.fastsync-partial", full_path); snprintf(partial_path, strlen(full_path) + 20, "%s.fastsync-partial", full_path);
has_old_file = (stat(partial_path, &st) == 0); has_old_file = (stat(partial_path, &st) == 0);
if (has_old_file) if (has_old_file)
old_size = (unsigned long long)st.st_size; old_size = (unsigned long long)st.st_size;
@@ -595,6 +641,8 @@ bool file_send_sendfile(File* file, int file_descriptor, bool use_metadata, int
while ((unsigned long long)offset < file_size) { while ((unsigned long long)offset < file_size) {
ssize_t sent = sendfile(file_descriptor, fd, &offset, file_size - offset); ssize_t sent = sendfile(file_descriptor, fd, &offset, file_size - offset);
if (sent == -1) { if (sent == -1) {
if (errno == EINTR)
continue;
perror("sendfile failed"); perror("sendfile failed");
close(fd); close(fd);
return false; return false;
+3 -1
View File
@@ -41,6 +41,8 @@ FileMetadata* metadata_from_buf(char** buf) {
if (!present) if (!present)
return NULL; return NULL;
FileMetadata* m = malloc(sizeof(FileMetadata)); FileMetadata* m = malloc(sizeof(FileMetadata));
if (m == NULL)
return NULL;
int32_t mode; int32_t mode;
memcpy(&mode, *buf, sizeof(mode)); memcpy(&mode, *buf, sizeof(mode));
*buf += sizeof(mode); *buf += sizeof(mode);
@@ -149,7 +151,7 @@ FileMetadata* metadata_receive(int file_descriptor, int* ok) {
void file_restore_metadata(const char* path, FileMetadata* metadata) { void file_restore_metadata(const char* path, FileMetadata* metadata) {
if (metadata == NULL) if (metadata == NULL)
return; return;
if (chmod(path, metadata->mode & 07777) != 0) if (chmod(path, metadata->mode & 07777 & ~(S_ISUID | S_ISGID)) != 0)
log_message(LOG_LEVEL_WARNING, "Failed to chmod %s: %s", path, strerror(errno)); log_message(LOG_LEVEL_WARNING, "Failed to chmod %s: %s", path, strerror(errno));
if (chown(path, metadata->uid, metadata->gid) != 0) if (chown(path, metadata->uid, metadata->gid) != 0)
log_message(LOG_LEVEL_WARNING, "Failed to chown %s: %s", path, strerror(errno)); log_message(LOG_LEVEL_WARNING, "Failed to chown %s: %s", path, strerror(errno));
+6 -1
View File
@@ -1,7 +1,6 @@
#include "protocol.h" #include "protocol.h"
#include "log.h" #include "log.h"
#include <errno.h> #include <errno.h>
#include <openssl/err.h>
#include <openssl/ssl.h> #include <openssl/ssl.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -198,10 +197,16 @@ bool send_data(int file_descriptor, const Data* data) {
return true; return true;
} }
#define MAX_DATA_SIZE (1024ULL * 1024 * 1024) // 1 GB
Data* receive_data(int file_descriptor) { Data* receive_data(int file_descriptor) {
unsigned long long size = 0; unsigned long long size = 0;
if (!receive_n_data(file_descriptor, &size, sizeof(unsigned long long))) if (!receive_n_data(file_descriptor, &size, sizeof(unsigned long long)))
return NULL; return NULL;
if (size > MAX_DATA_SIZE) {
log_message(LOG_LEVEL_ERROR, "receive_data: size %llu exceeds maximum", size);
return NULL;
}
void* data = malloc((size_t)size); void* data = malloc((size_t)size);
if (data == NULL) if (data == NULL)
return NULL; return NULL;
-2
View File
@@ -139,7 +139,6 @@ Client* client_connect_ssh(const char* destination, int port) {
ssh_argv[ac++] = "ControlPath=~/.cache/fastsync-%r@%h:%p"; ssh_argv[ac++] = "ControlPath=~/.cache/fastsync-%r@%h:%p";
if (port > 0 && port != 22) { if (port > 0 && port != 22) {
if ((size_t)ac + 2 >= ssh_argv_max) { if ((size_t)ac + 2 >= ssh_argv_max) {
free(ssh_argv);
_exit(1); _exit(1);
} }
ssh_argv[ac++] = "-p"; ssh_argv[ac++] = "-p";
@@ -147,7 +146,6 @@ Client* client_connect_ssh(const char* destination, int port) {
ssh_argv[ac++] = port_str; ssh_argv[ac++] = port_str;
} }
if ((size_t)ac + 3 >= ssh_argv_max) { if ((size_t)ac + 3 >= ssh_argv_max) {
free(ssh_argv);
_exit(1); _exit(1);
} }
ssh_argv[ac++] = ssh_user; ssh_argv[ac++] = ssh_user;
+27 -13
View File
@@ -2,6 +2,7 @@
#include "log.h" #include "log.h"
#include "protocol.h" #include "protocol.h"
#include <arpa/inet.h> #include <arpa/inet.h>
#include <errno.h>
#include <netdb.h> #include <netdb.h>
#include <openssl/ssl.h> #include <openssl/ssl.h>
#include <signal.h> #include <signal.h>
@@ -41,11 +42,14 @@ Server* server_create(int port) {
return NULL; return NULL;
} }
memset(&server->address, 0, sizeof(server->address)); memset(&server->address, 0, sizeof(server->address));
server->ssl_ctx = NULL;
// Try IPv6 first, fall back to IPv4 // Try IPv6 first, fall back to IPv4
int fd = socket(AF_INET6, SOCK_STREAM, 0); int domain = AF_INET6;
int fd = socket(domain, SOCK_STREAM, 0);
if (fd < 0) { if (fd < 0) {
fd = socket(AF_INET, SOCK_STREAM, 0); domain = AF_INET;
fd = socket(domain, SOCK_STREAM, 0);
} }
if (fd < 0) { if (fd < 0) {
perror("Could not create Socket!"); perror("Could not create Socket!");
@@ -68,17 +72,11 @@ Server* server_create(int port) {
return NULL; return NULL;
} }
// Determine address family from the actual socket
struct sockaddr_storage* addr = &server->address; struct sockaddr_storage* addr = &server->address;
socklen_t addr_len = sizeof(*addr);
if (getsockname(fd, (struct sockaddr*)addr, &addr_len) == 0) {
// Use the family of the socket we actually created
}
struct sockaddr_in* addr4 = (struct sockaddr_in*)addr; struct sockaddr_in* addr4 = (struct sockaddr_in*)addr;
struct sockaddr_in6* addr6 = (struct sockaddr_in6*)addr; struct sockaddr_in6* addr6 = (struct sockaddr_in6*)addr;
if (addr->ss_family == AF_INET6) { if (domain == AF_INET6) {
addr6->sin6_family = AF_INET6; addr6->sin6_family = AF_INET6;
addr6->sin6_addr = in6addr_any; addr6->sin6_addr = in6addr_any;
addr6->sin6_port = htons(port); addr6->sin6_port = htons(port);
@@ -93,7 +91,7 @@ Server* server_create(int port) {
if (bind(server->file_descriptor, (struct sockaddr*)&server->address, server->address_length) < if (bind(server->file_descriptor, (struct sockaddr*)&server->address, server->address_length) <
0) { 0) {
// If IPv6 bind failed (maybe no IPv6), try IPv4 // If IPv6 bind failed (maybe no IPv6), try IPv4
if (addr->ss_family == AF_INET6) { if (domain == AF_INET6) {
close(fd); close(fd);
fd = socket(AF_INET, SOCK_STREAM, 0); fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) { if (fd < 0) {
@@ -142,6 +140,15 @@ void server_delete(Server** server) {
*server = NULL; *server = NULL;
} }
/* Flag set by server_request_shutdown() to request graceful shutdown
of the accept loop. Accessed only from transport_tcp.c so it won't
cause linker errors when this file is compiled into client/test targets. */
static volatile sig_atomic_t g_tcp_cleanup_requested = 0;
void server_request_shutdown(void) {
g_tcp_cleanup_requested = 1;
}
static void accept_loop(Server* server, void (*child_fn)(int, void*), void* child_ctx, static void accept_loop(Server* server, void (*child_fn)(int, void*), void* child_ctx,
const char* log_fmt) { const char* log_fmt) {
if (listen(server->file_descriptor, SOMAXCONN) < 0) { if (listen(server->file_descriptor, SOMAXCONN) < 0) {
@@ -149,11 +156,16 @@ static void accept_loop(Server* server, void (*child_fn)(int, void*), void* chil
return; return;
} }
signal(SIGCHLD, SIG_IGN); signal(SIGCHLD, SIG_IGN);
while (1) { while (!g_tcp_cleanup_requested) {
struct sockaddr_storage client_addr; struct sockaddr_storage client_addr;
socklen_t client_len = sizeof(client_addr); socklen_t client_len = sizeof(client_addr);
int fd = accept(server->file_descriptor, (struct sockaddr*)&client_addr, &client_len); int fd = accept(server->file_descriptor, (struct sockaddr*)&client_addr, &client_len);
if (fd < 0) { if (fd < 0) {
if (errno == EINTR) {
if (g_tcp_cleanup_requested)
break;
continue;
}
perror("Could not accept the connection"); perror("Could not accept the connection");
continue; continue;
} }
@@ -253,8 +265,10 @@ bool client_connect(Client* client, char* host, int port) {
} }
// Save the connected address // Save the connected address
memcpy(&client->address, rp->ai_addr, rp->ai_addrlen); size_t copy_len =
client->address_length = rp->ai_addrlen; rp->ai_addrlen < sizeof(client->address) ? rp->ai_addrlen : sizeof(client->address);
memcpy(&client->address, rp->ai_addr, copy_len);
client->address_length = (int)copy_len;
freeaddrinfo(res); freeaddrinfo(res);
// Close old fd if any and set new one // Close old fd if any and set new one
+1
View File
@@ -28,6 +28,7 @@ bool server_listen(Server* server, void (*handler)(int file_descriptor));
void server_accept_loop(Server* server, void (*child_fn)(int, void*), void* child_ctx, void server_accept_loop(Server* server, void (*child_fn)(int, void*), void* child_ctx,
const char* log_fmt); const char* log_fmt);
void server_delete(Server** server); void server_delete(Server** server);
void server_request_shutdown(void);
Client* client_create(); Client* client_create();
bool client_connect(Client* client, char* host, int port); bool client_connect(Client* client, char* host, int port);
void client_disconnect(Client* client); void client_disconnect(Client* client);
+9 -1
View File
@@ -34,7 +34,7 @@ static void log_ssl_errors(void) {
} }
static SSL_CTX* create_ssl_ctx(bool is_server, const char* cert, const char* key, static SSL_CTX* create_ssl_ctx(bool is_server, const char* cert, const char* key,
const char* ca_path) { const char* ca_path) {
const SSL_METHOD* method = is_server ? TLS_server_method() : TLS_client_method(); const SSL_METHOD* method = is_server ? TLS_server_method() : TLS_client_method();
SSL_CTX* ctx = SSL_CTX_new(method); SSL_CTX* ctx = SSL_CTX_new(method);
if (!ctx) { if (!ctx) {
@@ -168,6 +168,14 @@ bool client_connect_tls(Client* client, char* host, int port, const char* cert_p
client->ssl_ctx = NULL; client->ssl_ctx = NULL;
return false; return false;
} }
// Set SNI and enable hostname verification
SSL_set_tlsext_host_name(ssl, host);
X509_VERIFY_PARAM* param = SSL_get0_param(ssl);
if (param) {
X509_VERIFY_PARAM_set1_host(param, host, 0);
}
client->ssl = ssl; client->ssl = ssl;
io_set_ssl(ssl); io_set_ssl(ssl);
return true; return true;
+1 -1
View File
@@ -63,7 +63,7 @@ char* str_dup(const char* string) {
if (string == NULL) if (string == NULL)
return NULL; return NULL;
char* new_string = (char*)malloc(strlen(string) + 1); char* new_string = (char*)malloc(strlen(string) + 1);
strcpy(new_string, string); memcpy(new_string, string, strlen(string) + 1);
return new_string; return new_string;
} }
+1 -1
View File
@@ -275,7 +275,7 @@ void test_file() {
test_to_disk_basic(); test_to_disk_basic();
test_to_disk_creates_dirs(); test_to_disk_creates_dirs();
test_file_content_to_buffer(); test_file_content_to_buffer();
if (!getenv("FASTSYNC_UNDER_VALGRIND")) { if (!is_running_under_valgrind()) {
// Fork tests are skipped under valgrind because the parent process runs // Fork tests are skipped under valgrind because the parent process runs
// orders of magnitude slower than the child (parent is instrumented, child // orders of magnitude slower than the child (parent is instrumented, child
// is not), which causes pipe-based protocol handshake timeouts. The parent // is not), which causes pipe-based protocol handshake timeouts. The parent
+22 -13
View File
@@ -22,8 +22,8 @@ static void test_sendfile_basic() {
/* Set the size so file_send_sendfile can report it */ /* Set the size so file_send_sendfile can report it */
file->data->size = len; file->data->size = len;
Config* cfg = config_create(str_dup(PROTOCOL_VERSION), str_dup("/tmp"), str_dup("/tmp"), Config* cfg = config_create(str_dup(PROTOCOL_VERSION), str_dup("/tmp"), str_dup("/tmp"), false,
false, false, false, false, false, 0, false, 0); false, false, false, false, 0, false, 0);
EXPECT_NOT_NULL(cfg); EXPECT_NOT_NULL(cfg);
int p[2]; int p[2];
@@ -80,8 +80,8 @@ static void test_sendfile_empty_file() {
EXPECT_NOT_NULL(file); EXPECT_NOT_NULL(file);
file->data->size = 0; file->data->size = 0;
Config* cfg = config_create(str_dup(PROTOCOL_VERSION), str_dup("/tmp"), str_dup("/tmp"), Config* cfg = config_create(str_dup(PROTOCOL_VERSION), str_dup("/tmp"), str_dup("/tmp"), false,
false, false, false, false, false, 0, false, 0); false, false, false, false, 0, false, 0);
EXPECT_NOT_NULL(cfg); EXPECT_NOT_NULL(cfg);
int p[2]; int p[2];
@@ -161,8 +161,8 @@ static void test_sendfile_compression_fallback() {
file->data->size = (size_t)st.st_size; file->data->size = (size_t)st.st_size;
EXPECT_TRUE(file_load_data(file)); EXPECT_TRUE(file_load_data(file));
Config* cfg = config_create(str_dup(PROTOCOL_VERSION), str_dup("/tmp"), str_dup("/tmp"), Config* cfg = config_create(str_dup(PROTOCOL_VERSION), str_dup("/tmp"), str_dup("/tmp"), false,
false, false, false, true, false, 3, false, 0); false, false, true, false, 3, false, 0);
EXPECT_NOT_NULL(cfg); EXPECT_NOT_NULL(cfg);
int p[2]; int p[2];
@@ -224,8 +224,10 @@ static void test_sendfile_no_path() {
pid_t pid = fork(); pid_t pid = fork();
if (pid == 0) { if (pid == 0) {
close(p[1]); close(p[1]);
/* When send_path is false, the sender sends raw data (size + bytes) only. /* Read file type indicator */
* We need to receive just the Data, not a File. */ int file_type;
EXPECT_TRUE(receive_int(p[0], &file_type));
EXPECT_EQ_INT(file_type, (int)FILE_TYPE_REGULAR);
Data* received = receive_data(p[0]); Data* received = receive_data(p[0]);
close(p[0]); close(p[0]);
@@ -256,9 +258,16 @@ static void test_sendfile_no_path() {
} }
void test_file_sendfile() { void test_file_sendfile() {
test_sendfile_basic(); if (!is_running_under_valgrind()) {
test_sendfile_empty_file(); // Fork tests are skipped under valgrind because the parent process runs
test_sendfile_missing_file(); // orders of magnitude slower than the child (parent is instrumented, child
test_sendfile_compression_fallback(); // is not), which causes pipe-based protocol handshake timeouts. The parent
test_sendfile_no_path(); // process itself has zero valgrind errors -- the failures are all in the
// forked children where inherited allocations are reported as leaks.
test_sendfile_basic();
test_sendfile_empty_file();
test_sendfile_compression_fallback();
test_sendfile_no_path();
}
test_sendfile_missing_file(); // no fork, safe under valgrind
} }
+14 -6
View File
@@ -7,27 +7,30 @@
* and that set_log_level changes behavior. */ * and that set_log_level changes behavior. */
static void test_log_message_debug() { static void test_log_message_debug() {
/* Default level is WARNING, so DEBUG should be filtered out */ set_log_level(LOG_LEVEL_WARNING);
log_message(LOG_LEVEL_DEBUG, "debug message: %d", 42); log_message(LOG_LEVEL_DEBUG, "debug message: %d", 42);
/* No assertion needed - if we reach here without crash, success */ /* crash regression test — stderr capture would need infrastructure changes */
EXPECT_TRUE(true); EXPECT_TRUE(true);
} }
static void test_log_message_info() { static void test_log_message_info() {
/* Default level is WARNING, so INFO should be filtered out */ set_log_level(LOG_LEVEL_WARNING);
log_message(LOG_LEVEL_INFO, "info message: %s", "test"); log_message(LOG_LEVEL_INFO, "info message: %s", "test");
/* crash regression test — stderr capture would need infrastructure changes */
EXPECT_TRUE(true); EXPECT_TRUE(true);
} }
static void test_log_message_warning() { static void test_log_message_warning() {
/* Default level is WARNING, so WARNING should be shown */ set_log_level(LOG_LEVEL_WARNING);
log_message(LOG_LEVEL_WARNING, "warning message: %d %s", 1, "test"); log_message(LOG_LEVEL_WARNING, "warning message: %d %s", 1, "test");
/* crash regression test — stderr capture would need infrastructure changes */
EXPECT_TRUE(true); EXPECT_TRUE(true);
} }
static void test_log_message_error() { static void test_log_message_error() {
/* Default level is WARNING, so ERROR should be shown */ set_log_level(LOG_LEVEL_WARNING);
log_message(LOG_LEVEL_ERROR, "error message: %s", "critical"); log_message(LOG_LEVEL_ERROR, "error message: %s", "critical");
/* crash regression test — stderr capture would need infrastructure changes */
EXPECT_TRUE(true); EXPECT_TRUE(true);
} }
@@ -40,6 +43,7 @@ static void test_log_set_level_debug() {
log_message(LOG_LEVEL_WARNING, "warning after set"); log_message(LOG_LEVEL_WARNING, "warning after set");
log_message(LOG_LEVEL_ERROR, "error after set"); log_message(LOG_LEVEL_ERROR, "error after set");
/* crash regression test — stderr capture would need infrastructure changes */
EXPECT_TRUE(true); EXPECT_TRUE(true);
} }
@@ -47,11 +51,12 @@ static void test_log_set_level_info() {
set_log_level(LOG_LEVEL_INFO); set_log_level(LOG_LEVEL_INFO);
/* INFO level should show INFO, WARNING, ERROR but not DEBUG */ /* INFO level should show INFO, WARNING, ERROR but not DEBUG */
log_message(LOG_LEVEL_DEBUG, "debug should be filtered"); /* filtered */ log_message(LOG_LEVEL_DEBUG, "debug should be filtered"); /* filtered */
log_message(LOG_LEVEL_INFO, "info should show"); log_message(LOG_LEVEL_INFO, "info should show");
log_message(LOG_LEVEL_WARNING, "warning should show"); log_message(LOG_LEVEL_WARNING, "warning should show");
log_message(LOG_LEVEL_ERROR, "error should show"); log_message(LOG_LEVEL_ERROR, "error should show");
/* crash regression test — stderr capture would need infrastructure changes */
EXPECT_TRUE(true); EXPECT_TRUE(true);
} }
@@ -64,6 +69,7 @@ static void test_log_set_level_error() {
log_message(LOG_LEVEL_WARNING, "warning filtered"); log_message(LOG_LEVEL_WARNING, "warning filtered");
log_message(LOG_LEVEL_ERROR, "error should show"); log_message(LOG_LEVEL_ERROR, "error should show");
/* crash regression test — stderr capture would need infrastructure changes */
EXPECT_TRUE(true); EXPECT_TRUE(true);
} }
@@ -80,6 +86,7 @@ static void test_log_filtering() {
log_message(LOG_LEVEL_WARNING, "visible warning"); log_message(LOG_LEVEL_WARNING, "visible warning");
log_message(LOG_LEVEL_ERROR, "visible error"); log_message(LOG_LEVEL_ERROR, "visible error");
/* crash regression test — stderr capture would need infrastructure changes */
EXPECT_TRUE(true); EXPECT_TRUE(true);
} }
@@ -92,6 +99,7 @@ static void test_log_message_formats() {
log_message(LOG_LEVEL_WARNING, "string: %s", "hello"); log_message(LOG_LEVEL_WARNING, "string: %s", "hello");
log_message(LOG_LEVEL_ERROR, "multiple: %d %s %d", 1, "two", 3); log_message(LOG_LEVEL_ERROR, "multiple: %d %s %d", 1, "two", 3);
/* crash regression test — stderr capture would need infrastructure changes */
EXPECT_TRUE(true); EXPECT_TRUE(true);
} }
+10 -10
View File
@@ -8,8 +8,8 @@
/* Test pipeline_context_sender_create/destroy with valid arguments */ /* Test pipeline_context_sender_create/destroy with valid arguments */
static void test_sender_create_destroy() { static void test_sender_create_destroy() {
Config* cfg = config_create(str_dup("1.0"), str_dup("/src"), str_dup("/dst"), Config* cfg = config_create(str_dup("1.0"), str_dup("/src"), str_dup("/dst"), false, false, false,
false, false, false, false, false, 0, false, 0); false, false, 0, false, 0);
EXPECT_NOT_NULL(cfg); EXPECT_NOT_NULL(cfg);
Queue* q_scanner = queue_create(5, NULL); Queue* q_scanner = queue_create(5, NULL);
@@ -32,8 +32,8 @@ static void test_sender_create_destroy() {
/* Test pipeline_context_receiver_create/destroy with valid arguments */ /* Test pipeline_context_receiver_create/destroy with valid arguments */
static void test_receiver_create_destroy() { static void test_receiver_create_destroy() {
Config* cfg = config_create(str_dup("2.0"), str_dup("/src"), str_dup("/dst"), Config* cfg = config_create(str_dup("2.0"), str_dup("/src"), str_dup("/dst"), true, true, false,
true, true, false, false, false, 0, false, 0); false, false, 0, false, 0);
EXPECT_NOT_NULL(cfg); EXPECT_NOT_NULL(cfg);
Queue* q = queue_create(20, NULL); Queue* q = queue_create(20, NULL);
@@ -51,8 +51,8 @@ static void test_receiver_create_destroy() {
/* Test that create handles various queue capacities */ /* Test that create handles various queue capacities */
static void test_sender_queue_capacities() { static void test_sender_queue_capacities() {
Config* cfg = config_create(str_dup("3.0"), str_dup("/src"), str_dup("/dst"), Config* cfg = config_create(str_dup("3.0"), str_dup("/src"), str_dup("/dst"), false, false, false,
false, false, false, false, false, 0, false, 0); false, false, 0, false, 0);
EXPECT_NOT_NULL(cfg); EXPECT_NOT_NULL(cfg);
/* Single-element queues */ /* Single-element queues */
@@ -67,8 +67,8 @@ static void test_sender_queue_capacities() {
/* Test that create handles zero-capacity queues */ /* Test that create handles zero-capacity queues */
static void test_sender_zero_capacity() { static void test_sender_zero_capacity() {
Config* cfg = config_create(str_dup("4.0"), str_dup("/src"), str_dup("/dst"), Config* cfg = config_create(str_dup("4.0"), str_dup("/src"), str_dup("/dst"), false, false, false,
false, false, false, false, false, 0, false, 0); false, false, 0, false, 0);
EXPECT_NOT_NULL(cfg); EXPECT_NOT_NULL(cfg);
Queue* q1 = queue_create(0, NULL); Queue* q1 = queue_create(0, NULL);
@@ -82,8 +82,8 @@ static void test_sender_zero_capacity() {
/* Test receiver with zero file_descriptor */ /* Test receiver with zero file_descriptor */
static void test_receiver_fd_zero() { static void test_receiver_fd_zero() {
Config* cfg = config_create(str_dup("5.0"), str_dup("/src"), str_dup("/dst"), Config* cfg = config_create(str_dup("5.0"), str_dup("/src"), str_dup("/dst"), false, false, false,
false, false, false, false, false, 0, false, 0); false, false, 0, false, 0);
Queue* q = queue_create(5, NULL); Queue* q = queue_create(5, NULL);
PipelineContextReceiver* ctx = pipeline_context_receiver_create(cfg, q, 0); PipelineContextReceiver* ctx = pipeline_context_receiver_create(cfg, q, 0);
EXPECT_NOT_NULL(ctx); EXPECT_NOT_NULL(ctx);
+1
View File
@@ -179,6 +179,7 @@ static void test_receive_str_oversized() {
size_t huge = MAX_STRING_SIZE + 1; size_t huge = MAX_STRING_SIZE + 1;
EXPECT_TRUE(send_n_data(0, &huge, sizeof(size_t))); EXPECT_TRUE(send_n_data(0, &huge, sizeof(size_t)));
/* cppcheck-suppress constVariablePointer */
char* received = receive_str(0); char* received = receive_str(0);
EXPECT_NULL(received); EXPECT_NULL(received);
+4 -4
View File
@@ -336,10 +336,10 @@ static void test_scanner_size_range() {
static void test_scanner_mixed_patterns() { static void test_scanner_mixed_patterns() {
/* Combine exclude, include, and size filters together */ /* Combine exclude, include, and size filters together */
const char* dir = "test_scan_mixed"; const char* dir = "test_scan_mixed";
const char* a_txt = "test_scan_mixed/a.txt"; /* size ~= 5 */ const char* a_txt = "test_scan_mixed/a.txt"; /* size ~= 5 */
const char* b_bin = "test_scan_mixed/b.bin"; /* size ~= 13 */ const char* b_bin = "test_scan_mixed/b.bin"; /* size ~= 13 */
const char* c_txt = "test_scan_mixed/c.txt"; /* size ~= 5 */ const char* c_txt = "test_scan_mixed/c.txt"; /* size ~= 5 */
const char* d_bak = "test_scan_mixed/d.bak"; /* size ~= 42 */ const char* d_bak = "test_scan_mixed/d.bak"; /* size ~= 42 */
mkdir(dir, 0755); mkdir(dir, 0755);
create_test_file(a_txt, "aaaaa"); create_test_file(a_txt, "aaaaa");
+2
View File
@@ -8,12 +8,14 @@
/* Test client_connect_ssh with invalid destination (missing colon) */ /* Test client_connect_ssh with invalid destination (missing colon) */
static void test_ssh_connect_invalid_dest() { static void test_ssh_connect_invalid_dest() {
/* Missing colon — parse_remote_dest should fail and return NULL */ /* Missing colon — parse_remote_dest should fail and return NULL */
/* cppcheck-suppress constVariablePointer */
Client* client = client_connect_ssh("invalid-destination-no-colon", 22); Client* client = client_connect_ssh("invalid-destination-no-colon", 22);
EXPECT_NULL(client); EXPECT_NULL(client);
} }
/* Test client_connect_ssh with empty destination */ /* Test client_connect_ssh with empty destination */
static void test_ssh_connect_empty_dest() { static void test_ssh_connect_empty_dest() {
/* cppcheck-suppress constVariablePointer */
Client* client = client_connect_ssh("", 22); Client* client = client_connect_ssh("", 22);
EXPECT_NULL(client); EXPECT_NULL(client);
} }
+5 -6
View File
@@ -8,8 +8,8 @@
static void test_client_create_delete() { static void test_client_create_delete() {
Client* client = client_create(); Client* client = client_create();
EXPECT_NOT_NULL(client); EXPECT_NOT_NULL(client);
EXPECT_TRUE(client->file_descriptor >= 0); EXPECT_EQ_INT(client->file_descriptor, -1);
EXPECT_EQ_INT(client->address.sin_family, AF_INET); EXPECT_TRUE(client->address.ss_family == AF_UNSPEC);
EXPECT_EQ_INT(client->ssh_child_pid, -1); EXPECT_EQ_INT(client->ssh_child_pid, -1);
EXPECT_NULL(client->ssl); EXPECT_NULL(client->ssl);
EXPECT_NULL(client->ssl_ctx); EXPECT_NULL(client->ssl_ctx);
@@ -30,7 +30,7 @@ static void test_server_create_delete() {
Server* server = server_create(0); Server* server = server_create(0);
EXPECT_NOT_NULL(server); EXPECT_NOT_NULL(server);
EXPECT_TRUE(server->file_descriptor >= 0); EXPECT_TRUE(server->file_descriptor >= 0);
EXPECT_EQ_INT(server->address.sin_family, AF_INET); EXPECT_TRUE(server->address.ss_family == AF_INET || server->address.ss_family == AF_INET6);
EXPECT_NULL(server->ssl_ctx); EXPECT_NULL(server->ssl_ctx);
/* Clean up */ /* Clean up */
@@ -57,9 +57,8 @@ static void test_client_create_multiple() {
Client* c2 = client_create(); Client* c2 = client_create();
EXPECT_NOT_NULL(c1); EXPECT_NOT_NULL(c1);
EXPECT_NOT_NULL(c2); EXPECT_NOT_NULL(c2);
EXPECT_TRUE(c1->file_descriptor >= 0); EXPECT_EQ_INT(c1->file_descriptor, -1);
EXPECT_TRUE(c2->file_descriptor >= 0); EXPECT_EQ_INT(c2->file_descriptor, -1);
EXPECT_TRUE(c1->file_descriptor != c2->file_descriptor);
client_delete(c1); client_delete(c1);
client_delete(c2); client_delete(c2);
+2 -2
View File
@@ -34,7 +34,7 @@ static void test_tls_connect_bad_cert() {
* client_connect_tls will try to connect first, fail, and return false. * client_connect_tls will try to connect first, fail, and return false.
* Note: we use an invalid host to ensure connection failure, * Note: we use an invalid host to ensure connection failure,
* which exercises the error path before cert loading. */ * which exercises the error path before cert loading. */
bool ok = client_connect_tls(client, "192.0.2.1", 12345, "/nonexistent/cert.pem", bool ok = client_connect_tls(client, "127.0.0.1", 1, "/nonexistent/cert.pem",
"/nonexistent/key.pem", "/nonexistent/ca.pem"); "/nonexistent/key.pem", "/nonexistent/ca.pem");
EXPECT_FALSE(ok); EXPECT_FALSE(ok);
@@ -51,7 +51,7 @@ static void test_tls_connect_null_paths() {
EXPECT_NOT_NULL(client); EXPECT_NOT_NULL(client);
/* Connect to invalid address — will fail at connect() step */ /* Connect to invalid address — will fail at connect() step */
bool ok = client_connect_tls(client, "192.0.2.2", 12346, NULL, NULL, NULL); bool ok = client_connect_tls(client, "127.0.0.1", 1, NULL, NULL, NULL);
EXPECT_FALSE(ok); EXPECT_FALSE(ok);
client_delete(client); client_delete(client);
+15
View File
@@ -2,9 +2,24 @@
#define TEST_UTILS_H #define TEST_UTILS_H
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdbool.h> #include <stdbool.h>
// Detect if running under valgrind by checking /proc/self/maps for vgpreload.
// This is used to skip fork-based tests that are incompatible with valgrind
// (the instrumented parent runs too slowly, causing pipe timeouts).
static inline bool is_running_under_valgrind(void) {
FILE* f = fopen("/proc/self/maps", "r");
if (!f)
return false;
char buf[4096];
size_t n = fread(buf, 1, sizeof(buf) - 1, f);
fclose(f);
buf[n] = '\0';
return strstr(buf, "vgpreload") != NULL;
}
// Global test suite status // Global test suite status
extern int tests_run; extern int tests_run;
extern int tests_failed; extern int tests_failed;