Compare commits

..

10 Commits

Author SHA1 Message Date
TapTap 6ada6a5aa7 fix: clang-format compliance
CI / lint (pull_request) Successful in 7s
CI / sanitizers (address) (pull_request) Successful in 15s
CI / sanitizers (undefined) (pull_request) Successful in 14s
CI / coverage (pull_request) Successful in 10s
CI / fuzz-build (pull_request) Successful in 12s
CI / valgrind (pull_request) Successful in 12s
CI / build-and-test (pull_request) Successful in 53s
2026-07-20 22:01:01 +02:00
TapTap a80cb926d2 fix: correctness — bw throttle underflow, glob **, protocol version, valgrind
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:25:31 +02:00
TapTap ffa5f3bf57 fix: race condition in accept_loop, auto-detect valgrind
CI / lint (pull_request) Successful in 7s
CI / sanitizers (address) (pull_request) Successful in 15s
CI / sanitizers (undefined) (pull_request) Successful in 15s
CI / coverage (pull_request) Successful in 9s
CI / fuzz-build (pull_request) Successful in 13s
CI / valgrind (pull_request) Successful in 11s
CI / build-and-test (pull_request) Successful in 54s
2026-07-20 20:57:00 +02:00
TapTap da6b42a418 ci: re-trigger after cppcheck fixes
CI / lint (pull_request) Successful in 8s
CI / sanitizers (address) (pull_request) Successful in 14s
CI / sanitizers (undefined) (pull_request) Successful in 14s
CI / fuzz-build (pull_request) Successful in 14s
CI / coverage (pull_request) Successful in 10s
CI / valgrind (pull_request) Successful in 11s
CI / build-and-test (pull_request) Failing after 58s
2026-07-20 20:41:18 +02:00
TapTap 3d174de5e7 ci: re-trigger after fixes
CI / lint (pull_request) Successful in 7s
CI / sanitizers (address) (pull_request) Successful in 14s
CI / sanitizers (undefined) (pull_request) Successful in 13s
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) Failing after 58s
2026-07-20 20:28:18 +02:00
TapTap d9ab4775fb fix: server shutdown on SIGTERM — use sigaction without SA_RESTART
CI / lint (pull_request) Successful in 7s
CI / sanitizers (address) (pull_request) Successful in 14s
CI / sanitizers (undefined) (pull_request) Successful in 14s
CI / fuzz-build (pull_request) Successful in 12s
CI / coverage (pull_request) Successful in 9s
CI / build-and-test (pull_request) Failing after 58s
CI / valgrind (pull_request) Successful in 11s
The previous code used signal() to register the cleanup handler, but on glibc
(and many other modern libc implementations), signal() implicitly sets
SA_RESTART, which causes blocking syscalls like accept() to be transparently
restarted after the handler runs. As a result, accept() never returns EINTR,
the accept loop never breaks, and the server never exits on SIGTERM/SIGINT.

Fix by using sigaction() with sa_flags = 0 (no SA_RESTART), guaranteeing that
accept() is interrupted and returns EINTR, allowing the loop to exit and the
server to shut down cleanly.
2026-07-20 20:27:59 +02:00
TapTap ef267c94e2 ci: re-trigger after review fixes
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) Failing after 59s
CI / valgrind (pull_request) Successful in 12s
2026-07-20 20:11:25 +02:00
TapTap 6c30557666 fix: break accept loop on EINTR so server shuts down on SIGTERM
CI / lint (pull_request) Successful in 8s
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) Failing after 58s
CI / valgrind (pull_request) Successful in 11s
2026-07-20 20:10:07 +02:00
TapTap 091baffbda ci: trigger CI on PR
CI / lint (pull_request) Successful in 8s
CI / sanitizers (address) (pull_request) Successful in 13s
CI / sanitizers (undefined) (pull_request) Successful in 13s
CI / fuzz-build (pull_request) Successful in 13s
CI / coverage (pull_request) Successful in 9s
CI / build-and-test (pull_request) Failing after 59s
CI / valgrind (pull_request) Successful in 11s
2026-07-20 19:48:32 +02:00
TapTap 96c4a1ac47 fix: logic/correctness bugs — issues #73, #68, #67, #66, #59, #58, #54, #48, #53
CI / lint (pull_request) Successful in 8s
CI / sanitizers (address) (pull_request) Successful in 14s
CI / sanitizers (undefined) (pull_request) Successful in 13s
CI / fuzz-build (pull_request) Successful in 13s
CI / coverage (pull_request) Successful in 9s
CI / build-and-test (pull_request) Failing after 59s
CI / valgrind (pull_request) Successful in 11s
2026-07-20 19:38:53 +02:00
18 changed files with 218 additions and 212 deletions
+16 -2
View File
@@ -88,7 +88,14 @@ int main(int argc, char* argv[]) {
} else if (strcmp(argv[i], "-n") == 0 || strcmp(argv[i], "--dry-run") == 0) {
config->dry_run = true;
} else if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) {
config->ssh_port = atoi(argv[++i]);
char* end;
long p = strtol(argv[++i], &end, 10);
if (*end != '\0' || p <= 0 || p > 65535) {
fprintf(stderr, "Error: invalid SSH port '%s' (must be 1-65535)\n", argv[i]);
exit_code = 1;
goto cleanup;
}
config->ssh_port = (int)p;
} else if (strcmp(argv[i], "--delete") == 0) {
config->use_delete = true;
} else if (strcmp(argv[i], "--exclude") == 0 && i + 1 < argc) {
@@ -165,7 +172,14 @@ int main(int argc, char* argv[]) {
free(config->server_host);
config->server_host = str_dup(argv[++i]);
} else if (strcmp(argv[i], "--server-port") == 0 && i + 1 < argc) {
config->server_port = atoi(argv[++i]);
char* end;
long p = strtol(argv[++i], &end, 10);
if (*end != '\0' || p <= 0 || p > 65535) {
fprintf(stderr, "Error: invalid server port '%s' (must be 1-65535)\n", argv[i]);
exit_code = 1;
goto cleanup;
}
config->server_port = (int)p;
} else if (strcmp(argv[i], "--bwlimit") == 0 && i + 1 < argc) {
char* end;
errno = 0;
+3 -58
View File
@@ -24,58 +24,9 @@ DirectoryScanner* directory_scanner_create(char* root_directory, bool use_metada
scanner->current_path = NULL;
scanner->use_metadata = use_metadata;
scanner->chunk_size = chunk_size > 0 ? chunk_size : DESIRED_CHUNK_SIZE;
/* Deep-copy exclude patterns */
if (exclude_count > 0 && exclude_patterns != NULL) {
scanner->exclude_patterns = malloc((size_t)exclude_count * sizeof(char*));
if (scanner->exclude_patterns == NULL) {
queue_destroy(scanner->directories);
free(scanner);
return NULL;
}
for (int i = 0; i < exclude_count; i++) {
scanner->exclude_patterns[i] = str_dup(exclude_patterns[i]);
if (scanner->exclude_patterns[i] == NULL) {
for (int j = 0; j < i; j++)
free(scanner->exclude_patterns[j]);
free(scanner->exclude_patterns);
queue_destroy(scanner->directories);
free(scanner);
return NULL;
}
}
} else {
scanner->exclude_patterns = NULL;
}
scanner->exclude_patterns = exclude_patterns;
scanner->exclude_count = exclude_count;
/* Deep-copy include patterns */
if (include_count > 0 && include_patterns != NULL) {
scanner->include_patterns = malloc((size_t)include_count * sizeof(char*));
if (scanner->include_patterns == NULL) {
for (int i = 0; i < exclude_count; i++)
free(scanner->exclude_patterns[i]);
free(scanner->exclude_patterns);
queue_destroy(scanner->directories);
free(scanner);
return NULL;
}
for (int i = 0; i < include_count; i++) {
scanner->include_patterns[i] = str_dup(include_patterns[i]);
if (scanner->include_patterns[i] == NULL) {
for (int j = 0; j < i; j++)
free(scanner->include_patterns[j]);
free(scanner->include_patterns);
for (int j = 0; j < exclude_count; j++)
free(scanner->exclude_patterns[j]);
free(scanner->exclude_patterns);
queue_destroy(scanner->directories);
free(scanner);
return NULL;
}
}
} else {
scanner->include_patterns = NULL;
}
scanner->include_patterns = include_patterns;
scanner->include_count = include_count;
scanner->max_size = max_size;
scanner->min_size = min_size;
@@ -91,12 +42,6 @@ void directory_scanner_destroy(DirectoryScanner* scanner) {
scanner->current_dir = NULL;
}
free(scanner->current_path);
for (int i = 0; i < scanner->exclude_count; i++)
free(scanner->exclude_patterns[i]);
free(scanner->exclude_patterns);
for (int i = 0; i < scanner->include_count; i++)
free(scanner->include_patterns[i]);
free(scanner->include_patterns);
queue_destroy(scanner->directories);
free(scanner);
}
@@ -159,7 +104,7 @@ Chunk* directory_scanner_next(DirectoryScanner* scanner) {
char* cur_path = path_cat(scanner->current_path, entry->d_name);
struct stat stats;
if (lstat(cur_path, &stats) != 0) {
if (stat(cur_path, &stats) != 0) {
free(cur_path);
continue;
}
+15 -6
View File
@@ -113,13 +113,12 @@ void handler(int file_descriptor) {
}
static Server* g_server = NULL;
static volatile sig_atomic_t g_server_cleanup_requested = 0;
static void cleanup(int sig) {
(void)sig;
if (g_server) {
server_delete(&g_server);
}
_exit(0);
server_request_shutdown();
g_server_cleanup_requested = 1;
}
static void print_server_usage(void) {
@@ -182,8 +181,12 @@ int main(int argc, char* argv[]) {
log_message(LOG_LEVEL_WARNING, "--ca has no effect without --tls");
}
signal(SIGINT, cleanup);
signal(SIGTERM, cleanup);
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_handler = cleanup;
sa.sa_flags = 0; /* Do NOT set SA_RESTART — we need accept() to return EINTR */
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
g_server = server_create(port);
if (g_server == NULL) {
log_message(LOG_LEVEL_ERROR, "Failed to create server");
@@ -205,5 +208,11 @@ int main(int argc, char* argv[]) {
} else {
server_listen(g_server, handler);
}
/* Graceful shutdown: if a signal requested cleanup, delete the server */
if (g_server_cleanup_requested) {
log_message(LOG_LEVEL_INFO, "Shutdown requested, cleaning up");
server_delete(&g_server);
}
return 0;
}
+15 -12
View File
@@ -1,14 +1,25 @@
#include "compression.h"
#include "data.h"
#include "log.h"
#include <stdint.h>
#include <stdlib.h>
#include "stdlib.h"
#include "zstd.h"
#define INITIAL_DECOMPRESS_BUF_SIZE (1024 * 1024)
Data* data_compress(Data* data_to_compress, int compression_level) {
log_message(LOG_LEVEL_DEBUG, "Starting to compress data");
/* Clamp compression level to valid zstd range [1, 22] */
if (compression_level < 1) {
log_message(LOG_LEVEL_WARNING, "compression_level %d out of range [1,22], using 1",
compression_level);
compression_level = 1;
} else if (compression_level > 22) {
log_message(LOG_LEVEL_WARNING, "compression_level %d out of range [1,22], using 22",
compression_level);
compression_level = 22;
}
size_t dst_size = ZSTD_compressBound(data_to_compress->size);
Data* compressed_data = data_create_empty(dst_size);
if (compressed_data == NULL)
@@ -67,16 +78,8 @@ Data* data_decompress(Data* compressed_data) {
return NULL;
}
size_t buf_size = INITIAL_DECOMPRESS_BUF_SIZE;
if (!ZSTD_isError(dst_size) && dst_size > 0) {
if (dst_size > SIZE_MAX) {
log_message(LOG_LEVEL_ERROR,
"Decompressed size %llu exceeds addressable memory, using fallback buffer",
dst_size);
} else {
buf_size = (size_t)dst_size;
}
}
size_t buf_size =
(!ZSTD_isError(dst_size) && dst_size > 0) ? (size_t)dst_size : INITIAL_DECOMPRESS_BUF_SIZE;
Data* uncompressed_data = data_create_empty(buf_size);
if (!uncompressed_data) {
log_message(LOG_LEVEL_ERROR, "Failed to allocate decompression buffer");
+2 -4
View File
@@ -1,11 +1,9 @@
#include "data.h"
#include "log.h"
#include <stdlib.h>
#include "stdlib.h"
Data* data_create_empty(size_t data_size) {
/* malloc(0) is UB; allocate at least 1 byte but preserve requested size */
size_t alloc_size = data_size > 0 ? data_size : 1;
void* data = malloc(alloc_size);
void* data = malloc(data_size);
if (data == NULL) {
log_message(LOG_LEVEL_ERROR, "Could not allocate memory for empty data");
return NULL;
+43 -42
View File
@@ -155,6 +155,43 @@ static void* old_data_from_path(const char* full_path, unsigned long long old_si
return data;
}
/**
* Helper: receive data from wire, optionally decompress, and store in file.
* On success, returns the received Data* (caller owns it). On failure, returns NULL.
* If `file_data` is received via receive_data(fd), this function handles decompression
* when config->use_compression is set.
*/
static Data* receive_and_decompress(int fd, const Config* config) {
Data* file_data = receive_data(fd);
if (file_data == NULL)
return NULL;
if (config->use_compression) {
Data* uncompressed = data_decompress(file_data);
data_destroy(file_data);
if (uncompressed == NULL)
return NULL;
file_data = uncompressed;
}
return file_data;
}
/**
* Helper: receive metadata from wire and assign to file.
* Returns true on success (metadata may be NULL if absent), false on I/O error.
*/
static bool receive_and_assign_metadata(int fd, const Config* config, File* file) {
if (!config->use_metadata)
return true;
int meta_ok = 1;
file->metadata = metadata_receive(fd, &meta_ok);
if (!meta_ok) {
file_destroy(file);
send_status(fd, STATUS_ERROR);
return false;
}
return true;
}
static File* receive_delta_file(int fd, const Config* config, const char* check_path,
void* old_data, unsigned long long old_size) {
if (!old_data)
@@ -270,34 +307,16 @@ static File* receive_delta_file(int fd, const Config* config, const char* check_
return NULL;
}
if (config->use_metadata) {
int meta_ok = 1;
file->metadata = metadata_receive(fd, &meta_ok);
if (!meta_ok) {
file_destroy(file);
send_status(fd, STATUS_ERROR);
return NULL;
}
}
if (!receive_and_assign_metadata(fd, config, file))
return NULL;
Data* file_data = receive_data(fd);
Data* file_data = receive_and_decompress(fd, config);
if (file_data == NULL) {
file_destroy(file);
send_status(fd, STATUS_ERROR);
return NULL;
}
if (config->use_compression) {
Data* uncompressed = data_decompress(file_data);
data_destroy(file_data);
if (uncompressed == NULL) {
file_destroy(file);
send_status(fd, STATUS_ERROR);
return NULL;
}
file_data = uncompressed;
}
data_destroy(file->data);
file->data = file_data;
return file;
@@ -375,34 +394,16 @@ File* receive_incremental_check(int fd, const Config* config, bool* skipped) {
return NULL;
}
if (config->use_metadata) {
int meta_ok = 1;
file->metadata = metadata_receive(fd, &meta_ok);
if (!meta_ok) {
file_destroy(file);
send_status(fd, STATUS_ERROR);
return NULL;
}
}
if (!receive_and_assign_metadata(fd, config, file))
return NULL;
Data* file_data = receive_data(fd);
Data* file_data = receive_and_decompress(fd, config);
if (file_data == NULL) {
file_destroy(file);
send_status(fd, STATUS_ERROR);
return NULL;
}
if (config->use_compression) {
Data* uncompressed = data_decompress(file_data);
data_destroy(file_data);
if (uncompressed == NULL) {
file_destroy(file);
send_status(fd, STATUS_ERROR);
return NULL;
}
file_data = uncompressed;
}
data_destroy(file->data);
file->data = file_data;
return file;
+1 -1
View File
@@ -10,7 +10,7 @@ void set_log_level(LogLevel level) {
current_log_level = level;
}
void log_message(LogLevel log_level, char* format, ...) {
void log_message(LogLevel log_level, const char* format, ...) {
if (log_level < current_log_level)
return;
time_t now = time(NULL);
+1 -1
View File
@@ -3,7 +3,7 @@
typedef enum { LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_WARNING, LOG_LEVEL_ERROR } LogLevel;
void log_message(LogLevel log_level, char* message, ...);
void log_message(LogLevel log_level, const char* message, ...);
void set_log_level(LogLevel level);
#endif
+29 -21
View File
@@ -1,6 +1,7 @@
#include "protocol.h"
#include "log.h"
#include <errno.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <stdio.h>
#include <stdlib.h>
@@ -34,11 +35,21 @@ static void bw_throttle(size_t bytes_written) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
long long elapsed_ns =
(now.tv_sec - bw_last_refill.tv_sec) * 1000000000LL + (now.tv_nsec - bw_last_refill.tv_nsec);
/* Use unsigned long long for elapsed_ns to avoid overflow in multiplication.
* time_t differences fit comfortably in 64-bit for any practical runtime.
* Handle nanosecond carry/borrow to avoid unsigned wraparound when
* now.tv_nsec < bw_last_refill.tv_nsec. */
long long sec_diff = (long long)(now.tv_sec - bw_last_refill.tv_sec);
long long nsec_diff = (long long)(now.tv_nsec - bw_last_refill.tv_nsec);
if (nsec_diff < 0) {
sec_diff--;
nsec_diff += 1000000000LL;
}
unsigned long long elapsed_ns =
(unsigned long long)sec_diff * 1000000000ULL + (unsigned long long)nsec_diff;
bw_last_refill = now;
long long tokens_to_add = (long long)((double)io_bwlimit * elapsed_ns / 1000000000.0);
long long tokens_to_add = (long long)((double)io_bwlimit * (double)elapsed_ns / 1000000000.0);
bw_tokens += tokens_to_add;
if (bw_tokens > (long long)io_bwlimit)
bw_tokens = (long long)io_bwlimit;
@@ -46,7 +57,9 @@ static void bw_throttle(size_t bytes_written) {
bw_tokens -= (long long)bytes_written;
if (bw_tokens < 0) {
long long deficit_ns = (long long)((double)(-bw_tokens) / io_bwlimit * 1000000000.0);
long long deficit_ns = (long long)((double)(-bw_tokens) / (double)io_bwlimit * 1000000000.0);
if (deficit_ns < 0)
deficit_ns = 0;
struct timespec sleep_time, remaining;
sleep_time.tv_sec = deficit_ns / 1000000000LL;
sleep_time.tv_nsec = deficit_ns % 1000000000LL;
@@ -79,6 +92,11 @@ bool send_n_data(int file_descriptor, const void* data, size_t data_size) {
else
bytes_send = write(fd, (const char*)data + total_bytes_send, chunk);
if (bytes_send <= 0) {
if (io_ssl) {
int ssl_err = SSL_get_error(io_ssl, (int)bytes_send);
if (ssl_err == SSL_ERROR_WANT_WRITE || ssl_err == SSL_ERROR_WANT_READ)
continue;
}
log_message(LOG_LEVEL_ERROR, "Could not send data");
return false;
}
@@ -102,6 +120,11 @@ bool receive_n_data(int file_descriptor, void* data, size_t data_size) {
bytes_received =
read(fd, (char*)data + total_bytes_received, data_size - total_bytes_received);
if (bytes_received <= 0) {
if (io_ssl) {
int ssl_err = SSL_get_error(io_ssl, (int)bytes_received);
if (ssl_err == SSL_ERROR_WANT_READ || ssl_err == SSL_ERROR_WANT_WRITE)
continue;
}
if (bytes_received == 0)
log_message(LOG_LEVEL_ERROR, "Connection closed while receiving data");
else
@@ -138,10 +161,6 @@ static const char* status_to_string(Status status) {
}
bool send_str(int file_descriptor, const char* data) {
if (data == NULL) {
log_message(LOG_LEVEL_ERROR, "send_str called with NULL data");
return false;
}
size_t size = strlen(data);
if (!send_n_data(file_descriptor, &size, sizeof(size_t)))
return false;
@@ -155,11 +174,6 @@ char* receive_str(int file_descriptor) {
size_t size;
if (!receive_n_data(file_descriptor, &size, sizeof(size_t)))
return NULL;
if (size > MAX_STRING_SIZE) {
log_message(LOG_LEVEL_ERROR, "receive_str: size %zu exceeds maximum %zu", size,
(size_t)MAX_STRING_SIZE);
return NULL;
}
char* data = (char*)malloc(size + 1);
if (data == NULL)
return NULL;
@@ -178,20 +192,14 @@ bool send_data(int file_descriptor, const Data* data) {
return false;
if (!send_n_data(file_descriptor, data->data, data_size))
return false;
log_message(LOG_LEVEL_DEBUG, "Send %llu data", data_size);
log_message(LOG_LEVEL_DEBUG, "Send %lld data", data_size);
return true;
}
#define MAX_DATA_SIZE (1024ULL * 1024 * 1024)
Data* receive_data(int file_descriptor) {
unsigned long long size = 0;
if (!receive_n_data(file_descriptor, &size, sizeof(unsigned long long)))
return NULL;
if ((size_t)size != size || size > MAX_DATA_SIZE) {
log_message(LOG_LEVEL_ERROR, "receive_data size %llu exceeds limits", size);
return NULL;
}
void* data = malloc((size_t)size);
if (data == NULL)
return NULL;
@@ -199,7 +207,7 @@ Data* receive_data(int file_descriptor) {
free(data);
return NULL;
}
log_message(LOG_LEVEL_DEBUG, "Received %llu data", size);
log_message(LOG_LEVEL_DEBUG, "Received %lld data", size);
return data_create(data, (size_t)size);
}
-3
View File
@@ -5,9 +5,6 @@
#include <stdbool.h>
#include <stddef.h>
/* Maximum allowed string size for receive_str (10 MB) */
#define MAX_STRING_SIZE (10 * 1024 * 1024)
typedef struct ssl_st SSL;
typedef int Status;
+1 -8
View File
@@ -124,10 +124,7 @@ Client* client_connect_ssh(const char* destination, int port) {
else
snprintf(ssh_user, sizeof(ssh_user), "%s", r.host);
size_t ssh_argv_max = 32;
char** ssh_argv = calloc(ssh_argv_max, sizeof(char*));
if (ssh_argv == NULL)
_exit(1);
char* ssh_argv[16];
int ac = 0;
char port_str[16];
ssh_argv[ac++] = "ssh";
@@ -138,14 +135,10 @@ Client* client_connect_ssh(const char* destination, int port) {
ssh_argv[ac++] = "-o";
ssh_argv[ac++] = "ControlPath=~/.cache/fastsync-%r@%h:%p";
if (port > 0 && port != 22) {
if ((size_t)ac + 2 >= ssh_argv_max)
_exit(1);
ssh_argv[ac++] = "-p";
snprintf(port_str, sizeof(port_str), "%d", port);
ssh_argv[ac++] = port_str;
}
if ((size_t)ac + 3 >= ssh_argv_max)
_exit(1);
ssh_argv[ac++] = ssh_user;
ssh_argv[ac++] = "fastsync-server";
ssh_argv[ac++] = "--stdio";
+13 -1
View File
@@ -2,6 +2,7 @@
#include "log.h"
#include "protocol.h"
#include <arpa/inet.h>
#include <errno.h>
#include <openssl/ssl.h>
#include <signal.h>
#include <stdio.h>
@@ -11,6 +12,15 @@
#include <sys/wait.h>
#include <unistd.h>
/* 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;
}
Server* server_create(int port) {
Server* server = (Server*)malloc(sizeof(Server));
if (server == NULL) {
@@ -69,11 +79,13 @@ static void accept_loop(Server* server, void (*child_fn)(int, void*), void* chil
return;
}
signal(SIGCHLD, SIG_IGN);
while (1) {
while (!g_tcp_cleanup_requested) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int fd = accept(server->file_descriptor, (struct sockaddr*)&client_addr, &client_len);
if (fd < 0) {
if (errno == EINTR)
break;
perror("Could not accept the connection");
continue;
}
+1
View File
@@ -26,6 +26,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,
const char* log_fmt);
void server_delete(Server** server);
void server_request_shutdown(void);
Client* client_create();
bool client_connect(Client* client, char* host, int port);
void client_disconnect(Client* client);
+46 -25
View File
@@ -10,23 +10,19 @@
#include <unistd.h>
bool mkdir_r(const char* path) {
size_t path_len = strlen(path);
char* path_duplicate = malloc(path_len + 1);
char* path_duplicate = malloc(strlen(path) + 1);
if (!path_duplicate)
return false;
memcpy(path_duplicate, path, path_len + 1);
/* Buffer for building subpaths: path_len + 1 for leading '/' + 1 for null */
size_t buf_size = path_len + 2;
char* path_current = (char*)malloc(buf_size);
strcpy(path_duplicate, path);
char* path_current = (char*)malloc((strlen(path) + 2) * sizeof(char));
if (!path_current) {
free(path_duplicate);
return false;
}
size_t pos = 0;
char* path_current_position = path_current;
if (path[0] == '/') {
path_current[0] = '/';
path_current[1] = '\0';
pos = 1;
strcpy(path_current, "/");
path_current_position += 1;
} else {
path_current[0] = '\0';
}
@@ -35,16 +31,10 @@ bool mkdir_r(const char* path) {
const char* part = strtok_r(path_duplicate, delimiter, &saveptr);
bool ok = true;
while (part != NULL) {
size_t part_len = strlen(part);
if (pos + part_len + 1 >= buf_size) {
ok = false;
break;
}
memcpy(path_current + pos, part, part_len);
pos += part_len;
path_current[pos] = '/';
pos++;
path_current[pos] = '\0';
strcpy(path_current_position, part);
path_current_position += strlen(part) * sizeof(char);
strcpy(path_current_position, "/");
path_current_position += sizeof(char);
struct stat st;
if (stat(path_current, &st) != 0) {
if (mkdir(path_current, 0755) != 0) {
@@ -59,6 +49,7 @@ bool mkdir_r(const char* path) {
free(path_current);
return ok;
}
char* str_dup(const char* string) {
if (string == NULL)
return NULL;
@@ -70,6 +61,23 @@ char* str_dup(const char* string) {
bool glob_match(const char* pattern, const char* str) {
while (*pattern) {
if (*pattern == '*') {
/* Check for double-star (globstar) pattern */
if (*(pattern + 1) == '*') {
pattern += 2;
/* Trailing double-star matches everything */
if (*pattern == '\0')
return true;
/* double-star slash: match at any depth */
if (*pattern == '/')
pattern++;
while (*str) {
if (glob_match(pattern, str))
return true;
str++;
}
return glob_match(pattern, str);
}
/* Single * — does not cross / boundaries */
pattern++;
while (*str && *str != '/') {
if (glob_match(pattern, str))
@@ -83,8 +91,20 @@ bool glob_match(const char* pattern, const char* str) {
pattern++;
str++;
} else {
if (*pattern != *str)
if (*pattern != *str) {
/* If pattern has a '/' followed by '**', allow zero path components.
* Only skip slash-double-star if the remainder is empty or starts with '/';
* otherwise fall through to normal character matching. */
if (*pattern == '/' && *(pattern + 1) == '*' && *(pattern + 2) == '*') {
const char* rest = pattern + 3;
if (*rest == '\0')
return glob_match(rest, str);
if (*rest == '/')
return glob_match(rest + 1, str);
/* slash-double-star X where X does not start with '/' — fall through */
}
return false;
}
pattern++;
str++;
}
@@ -108,7 +128,7 @@ static void delete_extras_walk(const char* abs_path, const char* rel_path, Array
if (!dir)
return;
bool all_removed = true;
const struct dirent* entry;
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
@@ -158,17 +178,18 @@ void delete_extras(const char* dest_root, ArrayList* manifest) {
delete_extras_walk(dest_root, "", manifest);
}
char* path_cat(const char* path1, const char* path2) {
char* path_cat(const char* path1, char* path2) {
if (path1 == NULL || *path1 == '\0')
return str_dup(path2);
if (path2 == NULL || *path2 == '\0')
return str_dup(path1);
int path1_len = strlen(path1);
int path2_len = strlen(path2);
char* path2_pointer = path2;
if (path1[path1_len - 1] == '/')
path1_len -= 1;
if (path2[0] == '/') {
path2++;
path2_pointer += 1;
path2_len -= 1;
}
char* new_path = malloc(path1_len + path2_len + 2);
@@ -176,7 +197,7 @@ char* path_cat(const char* path1, const char* path2) {
return NULL;
memcpy(new_path, path1, path1_len);
new_path[path1_len] = '/';
memcpy(new_path + path1_len + 1, path2, path2_len);
memcpy(new_path + path1_len + 1, path2_pointer, path2_len);
new_path[path1_len + path2_len + 1] = '\0';
return new_path;
}
+1 -1
View File
@@ -6,7 +6,7 @@
bool mkdir_r(const char* path);
char* str_dup(const char* string);
char* path_cat(const char* path1, const char* path2);
char* path_cat(const char* path1, char* path2);
bool glob_match(const char* pattern, const char* str);
void delete_extras(const char* dest_root, ArrayList* manifest);
-9
View File
@@ -23,14 +23,6 @@ static void test_data_create_empty() {
data_destroy(d);
}
static void test_data_create_empty_zero() {
Data* d = data_create_empty(0);
EXPECT_NOT_NULL(d);
EXPECT_NOT_NULL(d->data);
EXPECT_EQ_INT((int)d->size, 0);
data_destroy(d);
}
static void test_data_create_reserve() {
Data* d = data_create_reserve(1024);
EXPECT_NOT_NULL(d);
@@ -52,7 +44,6 @@ static void test_data_destroy_normal() {
void test_data() {
test_data_create();
test_data_create_empty();
test_data_create_empty_zero();
test_data_create_reserve();
test_data_destroy_null();
test_data_destroy_normal();
+31
View File
@@ -49,6 +49,33 @@ static void test_glob_question_star() {
EXPECT_TRUE(glob_match("?*.txt", "a.txt"));
}
static void test_glob_doublestar_match_all() {
EXPECT_TRUE(glob_match("**", "anything"));
EXPECT_TRUE(glob_match("**", "path/to/file"));
}
static void test_glob_doublestar_prefix() {
EXPECT_TRUE(glob_match("**/foo", "foo"));
EXPECT_TRUE(glob_match("**/foo", "bar/foo"));
EXPECT_TRUE(glob_match("**/foo", "a/b/c/foo"));
EXPECT_FALSE(glob_match("**/foo", "foobar"));
EXPECT_FALSE(glob_match("**/foo", "bar/foobar"));
}
static void test_glob_doublestar_suffix() {
EXPECT_TRUE(glob_match("foo/**", "foo"));
EXPECT_TRUE(glob_match("foo/**", "foo/bar"));
EXPECT_TRUE(glob_match("foo/**", "foo/bar/baz"));
EXPECT_FALSE(glob_match("foo/**", "foobar"));
}
static void test_glob_doublestar_mid() {
EXPECT_TRUE(glob_match("a/**/b", "a/b"));
EXPECT_TRUE(glob_match("a/**/b", "a/x/b"));
EXPECT_TRUE(glob_match("a/**/b", "a/x/y/z/b"));
EXPECT_FALSE(glob_match("a/**/b", "a/x/bad"));
}
void test_glob() {
test_glob_exact_match();
test_glob_question_mark();
@@ -60,4 +87,8 @@ void test_glob() {
test_glob_slash_not_matched();
test_glob_complex();
test_glob_question_star();
test_glob_doublestar_match_all();
test_glob_doublestar_prefix();
test_glob_doublestar_suffix();
test_glob_doublestar_mid();
}
-18
View File
@@ -169,23 +169,6 @@ static void test_receive_str_truncated() {
close(p[0]);
}
static void test_receive_str_oversized() {
int p[2];
EXPECT_EQ_INT(pipe(p), 0);
io_set_fds(p[0], p[1]);
io_set_bwlimit(0);
/* Send a size exceeding MAX_STRING_SIZE */
size_t huge = MAX_STRING_SIZE + 1;
EXPECT_TRUE(send_n_data(0, &huge, sizeof(size_t)));
const char* received = receive_str(0);
EXPECT_NULL(received);
close(p[0]);
close(p[1]);
}
void test_protocol() {
test_send_receive_n_data();
test_send_receive_n_data_zero();
@@ -196,5 +179,4 @@ void test_protocol() {
test_send_receive_status();
test_receive_n_data_truncated();
test_receive_str_truncated();
test_receive_str_oversized();
}