+19
-2
@@ -55,6 +55,7 @@ static void print_usage(void) {
|
|||||||
printf(" --cert <path> TLS certificate file (PEM)\n");
|
printf(" --cert <path> TLS certificate file (PEM)\n");
|
||||||
printf(" --key <path> TLS private key file (PEM)\n");
|
printf(" --key <path> TLS private key file (PEM)\n");
|
||||||
printf(" --ca <path> TLS CA certificate file (PEM)\n");
|
printf(" --ca <path> TLS CA certificate file (PEM)\n");
|
||||||
|
printf(" --partial Keep partially transferred files on interruption\n");
|
||||||
printf(" --help Show this help\n");
|
printf(" --help Show this help\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +89,14 @@ int main(int argc, char* argv[]) {
|
|||||||
} else if (strcmp(argv[i], "-n") == 0 || strcmp(argv[i], "--dry-run") == 0) {
|
} else if (strcmp(argv[i], "-n") == 0 || strcmp(argv[i], "--dry-run") == 0) {
|
||||||
config->dry_run = true;
|
config->dry_run = true;
|
||||||
} else if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) {
|
} else if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) {
|
||||||
config->ssh_port = atoi(argv[++i]);
|
char* end;
|
||||||
|
long p = strtol(argv[++i], &end, 10);
|
||||||
|
if (*end || p <= 0 || p > 65535) {
|
||||||
|
fprintf(stderr, "Error: invalid port '%s' (must be 1-65535)\n", argv[i]);
|
||||||
|
exit_code = 1;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
config->ssh_port = (int)p;
|
||||||
} else if (strcmp(argv[i], "--delete") == 0) {
|
} else if (strcmp(argv[i], "--delete") == 0) {
|
||||||
config->use_delete = true;
|
config->use_delete = true;
|
||||||
} else if (strcmp(argv[i], "--exclude") == 0 && i + 1 < argc) {
|
} else if (strcmp(argv[i], "--exclude") == 0 && i + 1 < argc) {
|
||||||
@@ -165,7 +173,14 @@ int main(int argc, char* argv[]) {
|
|||||||
free(config->server_host);
|
free(config->server_host);
|
||||||
config->server_host = str_dup(argv[++i]);
|
config->server_host = str_dup(argv[++i]);
|
||||||
} else if (strcmp(argv[i], "--server-port") == 0 && i + 1 < argc) {
|
} else if (strcmp(argv[i], "--server-port") == 0 && i + 1 < argc) {
|
||||||
config->server_port = atoi(argv[++i]);
|
char* end;
|
||||||
|
long p = strtol(argv[++i], &end, 10);
|
||||||
|
if (*end || p <= 0 || p > 65535) {
|
||||||
|
fprintf(stderr, "Error: invalid port '%s' (must be 1-65535)\n", argv[i]);
|
||||||
|
exit_code = 1;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
config->server_port = (int)p;
|
||||||
} else if (strcmp(argv[i], "--bwlimit") == 0 && i + 1 < argc) {
|
} else if (strcmp(argv[i], "--bwlimit") == 0 && i + 1 < argc) {
|
||||||
char* end;
|
char* end;
|
||||||
errno = 0;
|
errno = 0;
|
||||||
@@ -199,6 +214,8 @@ int main(int argc, char* argv[]) {
|
|||||||
} else if (strcmp(argv[i], "--ca") == 0 && i + 1 < argc) {
|
} else if (strcmp(argv[i], "--ca") == 0 && i + 1 < argc) {
|
||||||
free(config->tls_ca);
|
free(config->tls_ca);
|
||||||
config->tls_ca = str_dup(argv[++i]);
|
config->tls_ca = str_dup(argv[++i]);
|
||||||
|
} else if (strcmp(argv[i], "--partial") == 0) {
|
||||||
|
config->partial = true;
|
||||||
} else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
|
} else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
|
||||||
set_log_level(LOG_LEVEL_DEBUG);
|
set_log_level(LOG_LEVEL_DEBUG);
|
||||||
} else if (argv[i][0] == '-') {
|
} else if (argv[i][0] == '-') {
|
||||||
|
|||||||
@@ -86,7 +86,9 @@ static int send_delta(Client* client, File* file, DeltaSignature* sig, Config* c
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int file_type = (int)file->type;
|
||||||
bool ok = send_status(client->file_descriptor, STATUS_DELTA_DATA) &&
|
bool ok = send_status(client->file_descriptor, STATUS_DELTA_DATA) &&
|
||||||
|
send_int(client->file_descriptor, file_type) &&
|
||||||
send_data(client->file_descriptor, to_send);
|
send_data(client->file_descriptor, to_send);
|
||||||
|
|
||||||
if (ok && config->use_metadata)
|
if (ok && config->use_metadata)
|
||||||
@@ -500,7 +502,7 @@ int send_files_multithreaded(Config* config) {
|
|||||||
DirectoryScanner* scanner = directory_scanner_create(
|
DirectoryScanner* scanner = directory_scanner_create(
|
||||||
config->send_directory, config->use_metadata, config->chunk_size, config->exclude_patterns,
|
config->send_directory, config->use_metadata, config->chunk_size, config->exclude_patterns,
|
||||||
config->exclude_count, config->include_patterns, config->include_count, config->max_size,
|
config->exclude_count, config->include_patterns, config->include_count, config->max_size,
|
||||||
config->min_size);
|
config->min_size, config->follow_symlinks);
|
||||||
Chunk* chunk;
|
Chunk* chunk;
|
||||||
int file_count = 0;
|
int file_count = 0;
|
||||||
unsigned long long total_bytes = 0;
|
unsigned long long total_bytes = 0;
|
||||||
|
|||||||
+40
-6
@@ -16,6 +16,26 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.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;
|
||||||
|
}
|
||||||
|
|
||||||
int receive_files(Config* config, int fd) {
|
int receive_files(Config* config, int fd) {
|
||||||
Status status;
|
Status status;
|
||||||
if (!receive_status(fd, &status))
|
if (!receive_status(fd, &status))
|
||||||
@@ -29,8 +49,10 @@ int receive_files(Config* config, int fd) {
|
|||||||
goto next;
|
goto next;
|
||||||
if (file == NULL && !skipped)
|
if (file == NULL && !skipped)
|
||||||
return -1;
|
return -1;
|
||||||
if (config->save_to_disk)
|
if (config->save_to_disk) {
|
||||||
|
if (!should_exclude_file(config, filename_from_path(file->path)))
|
||||||
file_save_to_disk(config->receive_root_directory, file);
|
file_save_to_disk(config->receive_root_directory, file);
|
||||||
|
}
|
||||||
file_destroy(file);
|
file_destroy(file);
|
||||||
} else if (status == STATUS_CHUNK) {
|
} else if (status == STATUS_CHUNK) {
|
||||||
Chunk* chunk = receive_chunk_data(fd, config);
|
Chunk* chunk = receive_chunk_data(fd, config);
|
||||||
@@ -39,9 +61,11 @@ int receive_files(Config* config, int fd) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
for (int i = 0; i < chunk->element_count; i++) {
|
for (int i = 0; i < chunk->element_count; i++) {
|
||||||
if (config->save_to_disk)
|
if (config->save_to_disk) {
|
||||||
|
if (!should_exclude_file(config, filename_from_path(chunk->items[i]->path)))
|
||||||
file_save_to_disk(config->receive_root_directory, chunk->items[i]);
|
file_save_to_disk(config->receive_root_directory, chunk->items[i]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
chunk_destroy(chunk);
|
chunk_destroy(chunk);
|
||||||
} else {
|
} else {
|
||||||
File* file = file_receive(config, fd);
|
File* file = file_receive(config, fd);
|
||||||
@@ -50,8 +74,10 @@ int receive_files(Config* config, int fd) {
|
|||||||
send_status(fd, STATUS_ERROR);
|
send_status(fd, STATUS_ERROR);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (config->save_to_disk)
|
if (config->save_to_disk) {
|
||||||
|
if (!should_exclude_file(config, filename_from_path(file->path)))
|
||||||
file_save_to_disk(config->receive_root_directory, file);
|
file_save_to_disk(config->receive_root_directory, file);
|
||||||
|
}
|
||||||
file_destroy(file);
|
file_destroy(file);
|
||||||
}
|
}
|
||||||
next:
|
next:
|
||||||
@@ -112,13 +138,21 @@ void handler(int file_descriptor) {
|
|||||||
close(file_descriptor);
|
close(file_descriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Signal-safe flag: set by the signal handler, checked in main loop.
|
||||||
|
* We cannot safely access g_server from the signal handler because it's
|
||||||
|
* not sig_atomic_t. Instead, the handler sets this flag and calls _exit
|
||||||
|
* (which is async-signal-safe). The server is fork-based (not threaded),
|
||||||
|
* so g_server is only accessed from the main thread and cleanup() only
|
||||||
|
* runs in the parent process — no concurrent access from children. */
|
||||||
|
static volatile sig_atomic_t g_server_cleanup_requested = 0;
|
||||||
static Server* g_server = NULL;
|
static Server* g_server = NULL;
|
||||||
|
|
||||||
static void cleanup(int sig) {
|
static void cleanup(int sig) {
|
||||||
(void)sig;
|
(void)sig;
|
||||||
if (g_server) {
|
g_server_cleanup_requested = 1;
|
||||||
server_delete(&g_server);
|
/* _exit is async-signal-safe; we must not call server_delete() from a
|
||||||
}
|
* signal handler (it may call non-async-signal-safe functions). The OS
|
||||||
|
* will reclaim resources on exit. */
|
||||||
_exit(0);
|
_exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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_block_size = DELTA_BLOCK_SIZE_DEFAULT;
|
||||||
config->delta_max_file_size = DELTA_MAX_FILE_SIZE;
|
config->delta_max_file_size = DELTA_MAX_FILE_SIZE;
|
||||||
config->use_tls = false;
|
config->use_tls = false;
|
||||||
|
config->partial = false;
|
||||||
|
config->follow_symlinks = false;
|
||||||
config->tls_cert = NULL;
|
config->tls_cert = NULL;
|
||||||
config->tls_key = NULL;
|
config->tls_key = NULL;
|
||||||
config->tls_ca = NULL;
|
config->tls_ca = NULL;
|
||||||
@@ -218,6 +220,8 @@ Config* config_receive(int file_descriptor) {
|
|||||||
config->max_size = 0;
|
config->max_size = 0;
|
||||||
config->min_size = 0;
|
config->min_size = 0;
|
||||||
config->use_tls = false;
|
config->use_tls = false;
|
||||||
|
config->partial = false;
|
||||||
|
config->follow_symlinks = false;
|
||||||
config->tls_cert = NULL;
|
config->tls_cert = NULL;
|
||||||
config->tls_key = NULL;
|
config->tls_key = NULL;
|
||||||
config->tls_ca = NULL;
|
config->tls_ca = NULL;
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ typedef struct Config {
|
|||||||
uint32_t delta_block_size;
|
uint32_t delta_block_size;
|
||||||
unsigned long long delta_max_file_size;
|
unsigned long long delta_max_file_size;
|
||||||
bool use_tls;
|
bool use_tls;
|
||||||
|
bool partial;
|
||||||
|
bool follow_symlinks;
|
||||||
char* server_host;
|
char* server_host;
|
||||||
int server_port;
|
int server_port;
|
||||||
char* tls_cert;
|
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;
|
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,
|
static File* receive_delta_file(int fd, const Config* config, const char* check_path,
|
||||||
void* old_data, unsigned long long old_size) {
|
void* old_data, unsigned long long old_size) {
|
||||||
if (!old_data)
|
if (!old_data)
|
||||||
@@ -264,43 +307,7 @@ static File* receive_delta_file(int fd, const Config* config, const char* check_
|
|||||||
delta_signature_destroy(sig);
|
delta_signature_destroy(sig);
|
||||||
free(old_data);
|
free(old_data);
|
||||||
|
|
||||||
File* file = file_create(check_path);
|
return receive_file_data(fd, config, 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delta_signature_destroy(sig);
|
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(check_path);
|
||||||
free(full_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;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,10 +14,18 @@ typedef struct {
|
|||||||
long mtime_nsec;
|
long mtime_nsec;
|
||||||
} FileMetadata;
|
} FileMetadata;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
FILE_TYPE_REGULAR,
|
||||||
|
FILE_TYPE_SYMLINK,
|
||||||
|
FILE_TYPE_DIRECTORY
|
||||||
|
} FileType;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char* path;
|
char* path;
|
||||||
Data* data;
|
Data* data;
|
||||||
FileMetadata* metadata;
|
FileMetadata* metadata;
|
||||||
|
FileType type;
|
||||||
|
char* link_target;
|
||||||
} File;
|
} File;
|
||||||
|
|
||||||
File* file_create(const char* path);
|
File* file_create(const char* path);
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ void set_log_level(LogLevel level) {
|
|||||||
current_log_level = 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)
|
if (log_level < current_log_level)
|
||||||
return;
|
return;
|
||||||
time_t now = time(NULL);
|
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;
|
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);
|
void set_log_level(LogLevel level);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -13,6 +13,26 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <threads.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,
|
PipelineContextSender* pipeline_context_sender_create(Config* config, Queue* queue_scanner,
|
||||||
Queue* queue_loader) {
|
Queue* queue_loader) {
|
||||||
PipelineContextSender* context = malloc(sizeof(PipelineContextSender));
|
PipelineContextSender* context = malloc(sizeof(PipelineContextSender));
|
||||||
@@ -155,8 +175,10 @@ int write_thread(void* pipeline_context) {
|
|||||||
free(root_directory);
|
free(root_directory);
|
||||||
return thrd_success;
|
return thrd_success;
|
||||||
}
|
}
|
||||||
if (save_to_disk)
|
if (save_to_disk) {
|
||||||
|
if (!should_exclude_file(context->config, filename_from_path(file->path)))
|
||||||
file_save_to_disk(root_directory, file);
|
file_save_to_disk(root_directory, file);
|
||||||
|
}
|
||||||
file_destroy(file);
|
file_destroy(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-6
@@ -34,11 +34,17 @@ static void bw_throttle(size_t bytes_written) {
|
|||||||
struct timespec now;
|
struct timespec now;
|
||||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||||
|
|
||||||
long long elapsed_ns =
|
/* Use unsigned long long for intermediate computation to avoid overflow.
|
||||||
(now.tv_sec - bw_last_refill.tv_sec) * 1000000000LL + (now.tv_nsec - bw_last_refill.tv_nsec);
|
* 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;
|
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;
|
bw_tokens += tokens_to_add;
|
||||||
if (bw_tokens > (long long)io_bwlimit)
|
if (bw_tokens > (long long)io_bwlimit)
|
||||||
bw_tokens = (long long)io_bwlimit;
|
bw_tokens = (long long)io_bwlimit;
|
||||||
@@ -74,14 +80,24 @@ bool send_n_data(int file_descriptor, const void* data, size_t data_size) {
|
|||||||
if (io_bwlimit > 0 && chunk > 65536)
|
if (io_bwlimit > 0 && chunk > 65536)
|
||||||
chunk = 65536;
|
chunk = 65536;
|
||||||
ssize_t bytes_send;
|
ssize_t bytes_send;
|
||||||
if (io_ssl)
|
if (io_ssl) {
|
||||||
bytes_send = SSL_write(io_ssl, (const char*)data + total_bytes_send, chunk);
|
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);
|
bytes_send = write(fd, (const char*)data + total_bytes_send, chunk);
|
||||||
if (bytes_send <= 0) {
|
if (bytes_send <= 0) {
|
||||||
log_message(LOG_LEVEL_ERROR, "Could not send data");
|
log_message(LOG_LEVEL_ERROR, "Could not send data");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
bw_throttle((size_t)bytes_send);
|
bw_throttle((size_t)bytes_send);
|
||||||
total_bytes_send += bytes_send;
|
total_bytes_send += bytes_send;
|
||||||
}
|
}
|
||||||
@@ -95,10 +111,22 @@ bool receive_n_data(int file_descriptor, void* data, size_t data_size) {
|
|||||||
size_t total_bytes_received = 0;
|
size_t total_bytes_received = 0;
|
||||||
while (total_bytes_received < data_size) {
|
while (total_bytes_received < data_size) {
|
||||||
ssize_t bytes_received;
|
ssize_t bytes_received;
|
||||||
if (io_ssl)
|
if (io_ssl) {
|
||||||
bytes_received =
|
bytes_received =
|
||||||
SSL_read(io_ssl, (char*)data + total_bytes_received, data_size - total_bytes_received);
|
SSL_read(io_ssl, (char*)data + total_bytes_received, data_size - total_bytes_received);
|
||||||
|
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
|
else
|
||||||
|
log_message(LOG_LEVEL_ERROR, "Could not receive bytes (SSL error: %d)", err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
bytes_received =
|
bytes_received =
|
||||||
read(fd, (char*)data + total_bytes_received, data_size - total_bytes_received);
|
read(fd, (char*)data + total_bytes_received, data_size - total_bytes_received);
|
||||||
if (bytes_received <= 0) {
|
if (bytes_received <= 0) {
|
||||||
@@ -108,6 +136,7 @@ bool receive_n_data(int file_descriptor, void* data, size_t data_size) {
|
|||||||
log_message(LOG_LEVEL_ERROR, "Could not receive bytes");
|
log_message(LOG_LEVEL_ERROR, "Could not receive bytes");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
total_bytes_received += bytes_received;
|
total_bytes_received += bytes_received;
|
||||||
}
|
}
|
||||||
log_message(LOG_LEVEL_DEBUG, " Received n Data: %zu", total_bytes_received);
|
log_message(LOG_LEVEL_DEBUG, " Received n Data: %zu", total_bytes_received);
|
||||||
@@ -151,6 +180,10 @@ char* receive_str(int file_descriptor) {
|
|||||||
size_t size;
|
size_t size;
|
||||||
if (!receive_n_data(file_descriptor, &size, sizeof(size_t)))
|
if (!receive_n_data(file_descriptor, &size, sizeof(size_t)))
|
||||||
return NULL;
|
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);
|
char* data = (char*)malloc(size + 1);
|
||||||
if (data == NULL)
|
if (data == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|||||||
@@ -61,6 +61,24 @@ char* str_dup(const char* string) {
|
|||||||
bool glob_match(const char* pattern, const char* str) {
|
bool glob_match(const char* pattern, const char* str) {
|
||||||
while (*pattern) {
|
while (*pattern) {
|
||||||
if (*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++;
|
pattern++;
|
||||||
while (*str && *str != '/') {
|
while (*str && *str != '/') {
|
||||||
if (glob_match(pattern, str))
|
if (glob_match(pattern, str))
|
||||||
|
|||||||
@@ -23,6 +23,14 @@ static void test_data_create_empty() {
|
|||||||
data_destroy(d);
|
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() {
|
static void test_data_create_reserve() {
|
||||||
Data* d = data_create_reserve(1024);
|
Data* d = data_create_reserve(1024);
|
||||||
EXPECT_NOT_NULL(d);
|
EXPECT_NOT_NULL(d);
|
||||||
@@ -44,6 +52,7 @@ static void test_data_destroy_normal() {
|
|||||||
void test_data() {
|
void test_data() {
|
||||||
test_data_create();
|
test_data_create();
|
||||||
test_data_create_empty();
|
test_data_create_empty();
|
||||||
|
test_data_create_empty_zero();
|
||||||
test_data_create_reserve();
|
test_data_create_reserve();
|
||||||
test_data_destroy_null();
|
test_data_destroy_null();
|
||||||
test_data_destroy_normal();
|
test_data_destroy_normal();
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#include "test_file_sendfile.h"
|
||||||
|
#include "file.h"
|
||||||
|
#include "data.h"
|
||||||
|
#include "protocol.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "test_utils.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
static void t1() {
|
||||||
|
const char*c="Hi";size_t l=2;
|
||||||
|
EXPECT_TRUE(to_disk("tsf1",c,l));
|
||||||
|
File*f=file_create("tsf1");EXPECT_NOT_NULL(f);
|
||||||
|
f->data->size=l;f->data->data=malloc(l);memcpy(f->data->data,c,l);
|
||||||
|
int p[2];EXPECT_EQ_INT(pipe(p),0);io_set_fds(p[0],p[1]);io_set_bwlimit(0);
|
||||||
|
pid_t pid=fork();
|
||||||
|
if(pid==0){close(p[1]);bool ok=1;unsigned long long rs;
|
||||||
|
ok=ok&&receive_n_data(p[0],&rs,sizeof(rs));ok=ok&&((int)rs==(int)l);
|
||||||
|
if(ok&&l>0){char*b=malloc(rs+1);if(b){ok=ok&&receive_n_data(p[0],b,rs);free(b);}}
|
||||||
|
close(p[0]);_exit(ok?0:1);
|
||||||
|
}else{close(p[0]);bool s=file_send_sendfile(f,p[1],0,0,0);close(p[1]);int st;waitpid(pid,&st,0);
|
||||||
|
file_destroy(f);unlink("tsf1");EXPECT_TRUE(s);EXPECT_TRUE(WIFEXITED(st)&&WEXITSTATUS(st)==0);}
|
||||||
|
}
|
||||||
|
static void t2() {
|
||||||
|
EXPECT_TRUE(to_disk("tsf2","",0));
|
||||||
|
File*f=file_create("tsf2");f->data->size=0;
|
||||||
|
int p[2];EXPECT_EQ_INT(pipe(p),0);io_set_fds(p[0],p[1]);io_set_bwlimit(0);
|
||||||
|
pid_t pid=fork();
|
||||||
|
if(pid==0){close(p[1]);bool ok=1;unsigned long long rs;ok=ok&&receive_n_data(p[0],&rs,sizeof(rs));ok=ok&&((int)rs==0);close(p[0]);_exit(ok?0:1);}
|
||||||
|
else{close(p[0]);bool s=file_send_sendfile(f,p[1],0,0,0);close(p[1]);int st;waitpid(pid,&st,0);file_destroy(f);unlink("tsf2");EXPECT_TRUE(s);EXPECT_TRUE(WIFEXITED(st)&&WEXITSTATUS(st)==0);}
|
||||||
|
}
|
||||||
|
static void t3() {
|
||||||
|
File*f=file_create("tsf3");f->data->size=100;f->data->data=malloc(100);
|
||||||
|
int p[2];EXPECT_EQ_INT(pipe(p),0);io_set_fds(p[0],p[1]);io_set_bwlimit(0);close(p[0]);
|
||||||
|
bool s=file_send_sendfile(f,p[1],0,0,0);close(p[1]);file_destroy(f);EXPECT_FALSE(s);
|
||||||
|
}
|
||||||
|
void test_file_sendfile(){t1();t2();t3();}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#ifndef TEST_FILE_SENDFILE_H
|
||||||
|
#define TEST_FILE_SENDFILE_H
|
||||||
|
void test_file_sendfile();
|
||||||
|
#endif
|
||||||
@@ -49,6 +49,33 @@ static void test_glob_question_star() {
|
|||||||
EXPECT_TRUE(glob_match("?*.txt", "a.txt"));
|
EXPECT_TRUE(glob_match("?*.txt", "a.txt"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_glob_doublestar_all() {
|
||||||
|
EXPECT_TRUE(glob_match("**", "anything"));
|
||||||
|
EXPECT_TRUE(glob_match("**", "path/to/file.txt"));
|
||||||
|
EXPECT_TRUE(glob_match("**", ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_glob_doublestar_prefix() {
|
||||||
|
EXPECT_TRUE(glob_match("a/**/b", "a/b"));
|
||||||
|
EXPECT_TRUE(glob_match("a/**/b", "a/x/b"));
|
||||||
|
EXPECT_TRUE(glob_match("a/**/b", "a/x/y/b"));
|
||||||
|
EXPECT_FALSE(glob_match("a/**/b", "a/x/y/c"));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_glob_doublestar_suffix() {
|
||||||
|
EXPECT_TRUE(glob_match("a/**", "a/b"));
|
||||||
|
EXPECT_TRUE(glob_match("a/**", "a/b/c/d"));
|
||||||
|
EXPECT_TRUE(glob_match("a/**", "a/"));
|
||||||
|
EXPECT_FALSE(glob_match("a/**", "b/a"));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_glob_doublestar_subdir() {
|
||||||
|
EXPECT_TRUE(glob_match("**/foo", "foo"));
|
||||||
|
EXPECT_TRUE(glob_match("**/foo", "bar/foo"));
|
||||||
|
EXPECT_TRUE(glob_match("**/foo", "a/b/c/foo"));
|
||||||
|
EXPECT_FALSE(glob_match("**/foo", "foobar"));
|
||||||
|
}
|
||||||
|
|
||||||
void test_glob() {
|
void test_glob() {
|
||||||
test_glob_exact_match();
|
test_glob_exact_match();
|
||||||
test_glob_question_mark();
|
test_glob_question_mark();
|
||||||
@@ -60,4 +87,8 @@ void test_glob() {
|
|||||||
test_glob_slash_not_matched();
|
test_glob_slash_not_matched();
|
||||||
test_glob_complex();
|
test_glob_complex();
|
||||||
test_glob_question_star();
|
test_glob_question_star();
|
||||||
|
test_glob_doublestar_all();
|
||||||
|
test_glob_doublestar_prefix();
|
||||||
|
test_glob_doublestar_suffix();
|
||||||
|
test_glob_doublestar_subdir();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#include "test_log.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "test_utils.h"
|
||||||
|
void test_log(){
|
||||||
|
set_log_level(LOG_LEVEL_DEBUG);log_message(LOG_LEVEL_DEBUG,"d");log_message(LOG_LEVEL_INFO,"i");log_message(LOG_LEVEL_WARNING,"w");log_message(LOG_LEVEL_ERROR,"e");
|
||||||
|
set_log_level(LOG_LEVEL_WARNING);log_message(LOG_LEVEL_DEBUG,"h");log_message(LOG_LEVEL_WARNING,"s");
|
||||||
|
set_log_level(LOG_LEVEL_DEBUG);log_message(LOG_LEVEL_DEBUG,"d2");set_log_level(LOG_LEVEL_WARNING);log_message(LOG_LEVEL_DEBUG,"h2");log_message(LOG_LEVEL_WARNING,"w2");
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#ifndef TEST_LOG_H
|
||||||
|
#define TEST_LOG_H
|
||||||
|
void test_log();
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#include "test_multiprocessing.h"
|
||||||
|
#include "multiprocessing.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "queue.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "test_utils.h"
|
||||||
|
void test_multiprocessing(){
|
||||||
|
Config*c=config_create(str_dup("1"),str_dup("/t"),str_dup("/t"),0,0,0,0,0,0,0,0);EXPECT_NOT_NULL(c);
|
||||||
|
Queue*q1=queue_create(10,free),*q2=queue_create(10,free);
|
||||||
|
PipelineContextSender*ps=pipeline_context_sender_create(c,q1,q2);EXPECT_NOT_NULL(ps);EXPECT_NULL(ps->manifest);pipeline_context_sender_destroy(ps);
|
||||||
|
pipeline_context_sender_destroy(NULL);
|
||||||
|
Config*c2=config_create(str_dup("1"),str_dup("/t"),str_dup("/t"),0,0,0,0,0,0,0,0);
|
||||||
|
Queue*q=queue_create(10,free);
|
||||||
|
PipelineContextReceiver*pr=pipeline_context_receiver_create(c2,q,42);EXPECT_NOT_NULL(pr);EXPECT_EQ_INT(pr->file_descriptor,42);pipeline_context_receiver_destroy(pr);
|
||||||
|
pipeline_context_receiver_destroy(NULL);
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#ifndef TEST_MULTIPROCESSING_H
|
||||||
|
#define TEST_MULTIPROCESSING_H
|
||||||
|
void test_multiprocessing();
|
||||||
|
#endif
|
||||||
@@ -169,6 +169,23 @@ static void test_receive_str_truncated() {
|
|||||||
close(p[0]);
|
close(p[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_receive_str_oversized() {
|
||||||
|
int p[2];
|
||||||
|
EXPECT_EQ_INT(pipe(p), 0);
|
||||||
|
io_set_fds(p[0], p[1]);
|
||||||
|
io_set_bwlimit(0);
|
||||||
|
|
||||||
|
/* Send a size exceeding MAX_STRING_SIZE */
|
||||||
|
size_t huge = MAX_STRING_SIZE + 1;
|
||||||
|
EXPECT_TRUE(send_n_data(0, &huge, sizeof(size_t)));
|
||||||
|
|
||||||
|
char* received = receive_str(0);
|
||||||
|
EXPECT_NULL(received);
|
||||||
|
|
||||||
|
close(p[0]);
|
||||||
|
close(p[1]);
|
||||||
|
}
|
||||||
|
|
||||||
void test_protocol() {
|
void test_protocol() {
|
||||||
test_send_receive_n_data();
|
test_send_receive_n_data();
|
||||||
test_send_receive_n_data_zero();
|
test_send_receive_n_data_zero();
|
||||||
@@ -179,4 +196,5 @@ void test_protocol() {
|
|||||||
test_send_receive_status();
|
test_send_receive_status();
|
||||||
test_receive_n_data_truncated();
|
test_receive_n_data_truncated();
|
||||||
test_receive_str_truncated();
|
test_receive_str_truncated();
|
||||||
|
test_receive_str_oversized();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#include "test_transport_ssh.h"
|
||||||
|
#include "transport_ssh.h"
|
||||||
|
#include "transport_tcp.h"
|
||||||
|
#include "test_utils.h"
|
||||||
|
void test_transport_ssh(){
|
||||||
|
EXPECT_NULL(client_connect_ssh("nohost.x:22",22));
|
||||||
|
EXPECT_NULL(client_connect_ssh(NULL,22));
|
||||||
|
EXPECT_NULL(client_connect_ssh("bad",22));
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#ifndef TEST_TRANSPORT_SSH_H
|
||||||
|
#define TEST_TRANSPORT_SSH_H
|
||||||
|
void test_transport_ssh();
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#include "test_transport_tcp.h"
|
||||||
|
#include "transport_tcp.h"
|
||||||
|
#include "test_utils.h"
|
||||||
|
void test_transport_tcp(){
|
||||||
|
Client*c=client_create();EXPECT_NOT_NULL(c);client_delete(c);
|
||||||
|
client_delete(NULL);client_disconnect(NULL);
|
||||||
|
c=client_create();EXPECT_FALSE(client_connect(c,"9.9.9.9",1));client_disconnect(c);client_delete(c);
|
||||||
|
c=client_create();EXPECT_FALSE(client_connect(c,"1.1.1.1",0));client_disconnect(c);client_delete(c);
|
||||||
|
c=client_create();EXPECT_FALSE(client_connect(c,NULL,1));client_disconnect(c);client_delete(c);
|
||||||
|
Server*s=server_create(0);if(s){server_delete(&s);}Server*x=NULL;server_delete(&x);
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#ifndef TEST_TRANSPORT_TCP_H
|
||||||
|
#define TEST_TRANSPORT_TCP_H
|
||||||
|
void test_transport_tcp();
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
#include "test_transport_tls.h"
|
||||||
|
#include "transport_tls.h"
|
||||||
|
#include "transport_tcp.h"
|
||||||
|
#include "test_utils.h"
|
||||||
|
void test_transport_tls(){
|
||||||
|
tls_global_init();
|
||||||
|
Client*c=client_create();EXPECT_FALSE(client_connect_tls(c,"1.2.3.4",443,NULL,NULL,NULL));client_disconnect(c);client_delete(c);
|
||||||
|
c=client_create();EXPECT_FALSE(client_connect_tls(c,NULL,0,NULL,NULL,NULL));client_disconnect(c);client_delete(c);
|
||||||
|
Server*s=server_create(0);if(s){EXPECT_FALSE(server_create_tls(s,"/a.pem","/b.pem","/c.pem"));server_delete(&s);}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#ifndef TEST_TRANSPORT_TLS_H
|
||||||
|
#define TEST_TRANSPORT_TLS_H
|
||||||
|
void test_transport_tls();
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user