Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bd2af8071a | |||
| b20e39012e | |||
| 58a83193ff | |||
| 5b5c7b2a7e | |||
| 88ad77e397 | |||
| eb6034044b | |||
| 432e9a3ed4 | |||
| 2265f93a18 | |||
| b816437777 | |||
| c5d8a4e2ea | |||
| 0d57f3c812 | |||
| 81739b79c5 | |||
| a2bcda1fcf | |||
| 6b8979a040 | |||
| bcf5ffcf40 | |||
| 5049a7bbf0 | |||
| e6e8679bfc | |||
| 6831e7e6fb | |||
| e3484939b6 | |||
| 2f9a4fac78 | |||
| e1f9ba090f | |||
| f6b4d89a3c | |||
| 8f25f6a6b6 |
+13
-8
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user