fix: features and enhancements — issues #70, #36, #34, #33, #32, #57, #40, #37
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
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
This commit is contained in:
@@ -42,6 +42,8 @@ Config* config_create(char* version, char* send_directory, char* receive_directo
|
||||
config->delta_block_size = DELTA_BLOCK_SIZE_DEFAULT;
|
||||
config->delta_max_file_size = DELTA_MAX_FILE_SIZE;
|
||||
config->use_tls = false;
|
||||
config->partial = false;
|
||||
config->follow_symlinks = false;
|
||||
config->tls_cert = NULL;
|
||||
config->tls_key = NULL;
|
||||
config->tls_ca = NULL;
|
||||
@@ -218,6 +220,8 @@ Config* config_receive(int file_descriptor) {
|
||||
config->max_size = 0;
|
||||
config->min_size = 0;
|
||||
config->use_tls = false;
|
||||
config->partial = false;
|
||||
config->follow_symlinks = false;
|
||||
config->tls_cert = NULL;
|
||||
config->tls_key = NULL;
|
||||
config->tls_ca = NULL;
|
||||
|
||||
@@ -35,6 +35,8 @@ typedef struct Config {
|
||||
uint32_t delta_block_size;
|
||||
unsigned long long delta_max_file_size;
|
||||
bool use_tls;
|
||||
bool partial;
|
||||
bool follow_symlinks;
|
||||
char* server_host;
|
||||
int server_port;
|
||||
char* tls_cert;
|
||||
|
||||
+45
-73
@@ -155,6 +155,49 @@ static void* old_data_from_path(const char* full_path, unsigned long long old_si
|
||||
return data;
|
||||
}
|
||||
|
||||
/* Helper: receive data + optionally decompress + store in a new File.
|
||||
* On success returns the File (caller owns it). On failure sends
|
||||
* STATUS_ERROR on fd and returns NULL. */
|
||||
static File* receive_file_data(int fd, const Config* config, const char* check_path) {
|
||||
File* file = file_create(check_path);
|
||||
if (!file) {
|
||||
send_status(fd, STATUS_ERROR);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (config->use_metadata) {
|
||||
int meta_ok = 1;
|
||||
file->metadata = metadata_receive(fd, &meta_ok);
|
||||
if (!meta_ok) {
|
||||
file_destroy(file);
|
||||
send_status(fd, STATUS_ERROR);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
Data* file_data = receive_data(fd);
|
||||
if (!file_data) {
|
||||
file_destroy(file);
|
||||
send_status(fd, STATUS_ERROR);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (config->use_compression) {
|
||||
Data* uncompressed = data_decompress(file_data);
|
||||
data_destroy(file_data);
|
||||
if (!uncompressed) {
|
||||
file_destroy(file);
|
||||
send_status(fd, STATUS_ERROR);
|
||||
return NULL;
|
||||
}
|
||||
file_data = uncompressed;
|
||||
}
|
||||
|
||||
data_destroy(file->data);
|
||||
file->data = file_data;
|
||||
return file;
|
||||
}
|
||||
|
||||
static File* receive_delta_file(int fd, const Config* config, const char* check_path,
|
||||
void* old_data, unsigned long long old_size) {
|
||||
if (!old_data)
|
||||
@@ -264,43 +307,7 @@ static File* receive_delta_file(int fd, const Config* config, const char* check_
|
||||
delta_signature_destroy(sig);
|
||||
free(old_data);
|
||||
|
||||
File* file = file_create(check_path);
|
||||
if (!file) {
|
||||
send_status(fd, STATUS_ERROR);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (config->use_metadata) {
|
||||
int meta_ok = 1;
|
||||
file->metadata = metadata_receive(fd, &meta_ok);
|
||||
if (!meta_ok) {
|
||||
file_destroy(file);
|
||||
send_status(fd, STATUS_ERROR);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
Data* file_data = receive_data(fd);
|
||||
if (file_data == NULL) {
|
||||
file_destroy(file);
|
||||
send_status(fd, STATUS_ERROR);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (config->use_compression) {
|
||||
Data* uncompressed = data_decompress(file_data);
|
||||
data_destroy(file_data);
|
||||
if (uncompressed == NULL) {
|
||||
file_destroy(file);
|
||||
send_status(fd, STATUS_ERROR);
|
||||
return NULL;
|
||||
}
|
||||
file_data = uncompressed;
|
||||
}
|
||||
|
||||
data_destroy(file->data);
|
||||
file->data = file_data;
|
||||
return file;
|
||||
return receive_file_data(fd, config, check_path);
|
||||
}
|
||||
|
||||
delta_signature_destroy(sig);
|
||||
@@ -367,44 +374,9 @@ File* receive_incremental_check(int fd, const Config* config, bool* skipped) {
|
||||
}
|
||||
}
|
||||
|
||||
File* file = file_create(check_path);
|
||||
File* file = receive_file_data(fd, config, check_path);
|
||||
free(check_path);
|
||||
free(full_path);
|
||||
if (file == NULL) {
|
||||
send_status(fd, STATUS_ERROR);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (config->use_metadata) {
|
||||
int meta_ok = 1;
|
||||
file->metadata = metadata_receive(fd, &meta_ok);
|
||||
if (!meta_ok) {
|
||||
file_destroy(file);
|
||||
send_status(fd, STATUS_ERROR);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
Data* file_data = receive_data(fd);
|
||||
if (file_data == NULL) {
|
||||
file_destroy(file);
|
||||
send_status(fd, STATUS_ERROR);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (config->use_compression) {
|
||||
Data* uncompressed = data_decompress(file_data);
|
||||
data_destroy(file_data);
|
||||
if (uncompressed == NULL) {
|
||||
file_destroy(file);
|
||||
send_status(fd, STATUS_ERROR);
|
||||
return NULL;
|
||||
}
|
||||
file_data = uncompressed;
|
||||
}
|
||||
|
||||
data_destroy(file->data);
|
||||
file->data = file_data;
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,10 +14,18 @@ typedef struct {
|
||||
long mtime_nsec;
|
||||
} FileMetadata;
|
||||
|
||||
typedef enum {
|
||||
FILE_TYPE_REGULAR,
|
||||
FILE_TYPE_SYMLINK,
|
||||
FILE_TYPE_DIRECTORY
|
||||
} FileType;
|
||||
|
||||
typedef struct {
|
||||
char* path;
|
||||
Data* data;
|
||||
FileMetadata* metadata;
|
||||
FileType type;
|
||||
char* link_target;
|
||||
} File;
|
||||
|
||||
File* file_create(const char* path);
|
||||
|
||||
+1
-1
@@ -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
@@ -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
|
||||
|
||||
@@ -13,6 +13,26 @@
|
||||
#include <string.h>
|
||||
#include <threads.h>
|
||||
|
||||
static const char* filename_from_path(const char* path) {
|
||||
const char* slash = strrchr(path, '/');
|
||||
return slash ? slash + 1 : path;
|
||||
}
|
||||
|
||||
static bool should_exclude_file(const Config* config, const char* filename) {
|
||||
for (int i = 0; i < config->exclude_count; i++) {
|
||||
if (glob_match(config->exclude_patterns[i], filename))
|
||||
return true;
|
||||
}
|
||||
if (config->include_count > 0) {
|
||||
for (int i = 0; i < config->include_count; i++) {
|
||||
if (glob_match(config->include_patterns[i], filename))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
PipelineContextSender* pipeline_context_sender_create(Config* config, Queue* queue_scanner,
|
||||
Queue* queue_loader) {
|
||||
PipelineContextSender* context = malloc(sizeof(PipelineContextSender));
|
||||
@@ -155,8 +175,10 @@ int write_thread(void* pipeline_context) {
|
||||
free(root_directory);
|
||||
return thrd_success;
|
||||
}
|
||||
if (save_to_disk)
|
||||
file_save_to_disk(root_directory, file);
|
||||
if (save_to_disk) {
|
||||
if (!should_exclude_file(context->config, filename_from_path(file->path)))
|
||||
file_save_to_disk(root_directory, file);
|
||||
}
|
||||
file_destroy(file);
|
||||
}
|
||||
}
|
||||
|
||||
+49
-16
@@ -34,11 +34,17 @@ static void bw_throttle(size_t bytes_written) {
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
|
||||
long long elapsed_ns =
|
||||
(now.tv_sec - bw_last_refill.tv_sec) * 1000000000LL + (now.tv_nsec - bw_last_refill.tv_nsec);
|
||||
/* Use unsigned long long for intermediate computation to avoid overflow.
|
||||
* sec_diff * 1000000000ULL could overflow signed 64-bit for large deltas;
|
||||
* clamp to a safe maximum. */
|
||||
unsigned long long sec_diff = (unsigned long long)(now.tv_sec - bw_last_refill.tv_sec);
|
||||
if (sec_diff > 9223372036ULL)
|
||||
sec_diff = 9223372036ULL;
|
||||
unsigned long long elapsed_ns =
|
||||
sec_diff * 1000000000ULL + (unsigned long long)(now.tv_nsec - bw_last_refill.tv_nsec);
|
||||
bw_last_refill = now;
|
||||
|
||||
long long tokens_to_add = (long long)((double)io_bwlimit * elapsed_ns / 1000000000.0);
|
||||
long long tokens_to_add = (long long)((double)io_bwlimit * (double)elapsed_ns / 1000000000.0);
|
||||
bw_tokens += tokens_to_add;
|
||||
if (bw_tokens > (long long)io_bwlimit)
|
||||
bw_tokens = (long long)io_bwlimit;
|
||||
@@ -74,13 +80,23 @@ bool send_n_data(int file_descriptor, const void* data, size_t data_size) {
|
||||
if (io_bwlimit > 0 && chunk > 65536)
|
||||
chunk = 65536;
|
||||
ssize_t bytes_send;
|
||||
if (io_ssl)
|
||||
if (io_ssl) {
|
||||
bytes_send = SSL_write(io_ssl, (const char*)data + total_bytes_send, chunk);
|
||||
else
|
||||
if (bytes_send <= 0) {
|
||||
int err = SSL_get_error(io_ssl, (int)bytes_send);
|
||||
if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {
|
||||
/* Non-fatal: retry without counting progress */
|
||||
continue;
|
||||
}
|
||||
log_message(LOG_LEVEL_ERROR, "Could not send data (SSL error: %d)", err);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
bytes_send = write(fd, (const char*)data + total_bytes_send, chunk);
|
||||
if (bytes_send <= 0) {
|
||||
log_message(LOG_LEVEL_ERROR, "Could not send data");
|
||||
return false;
|
||||
if (bytes_send <= 0) {
|
||||
log_message(LOG_LEVEL_ERROR, "Could not send data");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bw_throttle((size_t)bytes_send);
|
||||
total_bytes_send += bytes_send;
|
||||
@@ -95,18 +111,31 @@ bool receive_n_data(int file_descriptor, void* data, size_t data_size) {
|
||||
size_t total_bytes_received = 0;
|
||||
while (total_bytes_received < data_size) {
|
||||
ssize_t bytes_received;
|
||||
if (io_ssl)
|
||||
if (io_ssl) {
|
||||
bytes_received =
|
||||
SSL_read(io_ssl, (char*)data + total_bytes_received, data_size - total_bytes_received);
|
||||
else
|
||||
if (bytes_received <= 0) {
|
||||
int err = SSL_get_error(io_ssl, (int)bytes_received);
|
||||
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
|
||||
/* Non-fatal: retry without counting progress */
|
||||
continue;
|
||||
}
|
||||
if (bytes_received == 0)
|
||||
log_message(LOG_LEVEL_ERROR, "Connection closed while receiving data");
|
||||
else
|
||||
log_message(LOG_LEVEL_ERROR, "Could not receive bytes (SSL error: %d)", err);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
bytes_received =
|
||||
read(fd, (char*)data + total_bytes_received, data_size - total_bytes_received);
|
||||
if (bytes_received <= 0) {
|
||||
if (bytes_received == 0)
|
||||
log_message(LOG_LEVEL_ERROR, "Connection closed while receiving data");
|
||||
else
|
||||
log_message(LOG_LEVEL_ERROR, "Could not receive bytes");
|
||||
return false;
|
||||
if (bytes_received <= 0) {
|
||||
if (bytes_received == 0)
|
||||
log_message(LOG_LEVEL_ERROR, "Connection closed while receiving data");
|
||||
else
|
||||
log_message(LOG_LEVEL_ERROR, "Could not receive bytes");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
total_bytes_received += bytes_received;
|
||||
}
|
||||
@@ -151,6 +180,10 @@ char* receive_str(int file_descriptor) {
|
||||
size_t size;
|
||||
if (!receive_n_data(file_descriptor, &size, sizeof(size_t)))
|
||||
return NULL;
|
||||
if (size > MAX_STRING_SIZE) {
|
||||
log_message(LOG_LEVEL_ERROR, "String size %zu exceeds maximum %d", size, MAX_STRING_SIZE);
|
||||
return NULL;
|
||||
}
|
||||
char* data = (char*)malloc(size + 1);
|
||||
if (data == NULL)
|
||||
return NULL;
|
||||
|
||||
@@ -61,6 +61,24 @@ char* str_dup(const char* string) {
|
||||
bool glob_match(const char* pattern, const char* str) {
|
||||
while (*pattern) {
|
||||
if (*pattern == '*') {
|
||||
if (*(pattern + 1) == '*') {
|
||||
/* ** pattern: matches zero or more characters including '/' */
|
||||
pattern += 2; /* skip both stars */
|
||||
/* If ** is immediately followed by '/', consume it too so that
|
||||
* **/foo behaves intuitively (matches foo at any depth). */
|
||||
if (*pattern == '/')
|
||||
pattern++;
|
||||
/* Try matching the remainder of pattern at every position in str,
|
||||
* including across '/' boundaries and at the very end. */
|
||||
while (1) {
|
||||
if (glob_match(pattern, str))
|
||||
return true;
|
||||
if (*str == '\0')
|
||||
return false;
|
||||
str++;
|
||||
}
|
||||
}
|
||||
/* Single *: matches any characters except '/' */
|
||||
pattern++;
|
||||
while (*str && *str != '/') {
|
||||
if (glob_match(pattern, str))
|
||||
|
||||
Reference in New Issue
Block a user