feat: add SSH transport, rsync-style CLI, and --stdio server mode
- Replace send()/recv() with read()/write() + io_set_fds() for fd abstraction - Add client_connect_ssh() via socketpair + fork/exec; reap child in client_disconnect() - Add server --stdio flag for SSH invocation - Replace handle_arg() with rsync-style positional args (fastsync <src> <dest>) - Add SSH vs TCP auto-detection via is_remote_dest() - Add TransportType and ssh_destination to Config, fix uninitialized fields in config_receive() (pre-existing multithreaded crash) - Sender threads now wait for server STATUS_OK before disconnecting - Add --help, error handling for sendfile+SSH combos - Add pre-flight checks and Posix Args test case to test.py - Add test_config_ssh_dest() unit test
This commit is contained in:
+125
-30
@@ -99,12 +99,21 @@ int load_files_multithreaded(void *pipeline_context) {
|
||||
|
||||
int send_chunks_multithreaded(void *pipeline_context) {
|
||||
PipelineContextSender *context = (PipelineContextSender *)pipeline_context;
|
||||
Client *client = client_create();
|
||||
const char *env_ip = getenv("FASTSYNC_SERVER_IP");
|
||||
const char *ip = env_ip ? env_ip : "127.0.0.1";
|
||||
const char *env_port = getenv("FASTSYNC_SERVER_PORT");
|
||||
int port = env_port ? atoi(env_port) : 8080;
|
||||
client_connect(client, (char *)ip, port);
|
||||
Client *client;
|
||||
if (context->config->transport == TRANSPORT_SSH) {
|
||||
if (context->config->use_sendfile) {
|
||||
fprintf(stderr, "Error: -f/--sendfile is not supported with SSH transport\n");
|
||||
return 1;
|
||||
}
|
||||
client = client_connect_ssh(context->config->ssh_destination);
|
||||
} else {
|
||||
client = client_create();
|
||||
const char *env_ip = getenv("FASTSYNC_SERVER_IP");
|
||||
const char *ip = env_ip ? env_ip : "127.0.0.1";
|
||||
const char *env_port = getenv("FASTSYNC_SERVER_PORT");
|
||||
int port = env_port ? atoi(env_port) : 8080;
|
||||
client_connect(client, (char *)ip, port);
|
||||
}
|
||||
config_send(client->file_descriptor, context->config);
|
||||
|
||||
while (true) {
|
||||
@@ -114,6 +123,11 @@ int send_chunks_multithreaded(void *pipeline_context) {
|
||||
&context->condition_not_full_loader, &context->loader_done);
|
||||
if (current_chunk == NULL) {
|
||||
send_status(client->file_descriptor, STATUS_FINISHED);
|
||||
if (receive_status(client->file_descriptor) != STATUS_OK) {
|
||||
client_disconnect(client);
|
||||
client_delete(client);
|
||||
return 1;
|
||||
}
|
||||
client_disconnect(client);
|
||||
client_delete(client);
|
||||
return thrd_success;
|
||||
@@ -127,12 +141,21 @@ int send_chunks_multithreaded(void *pipeline_context) {
|
||||
}
|
||||
|
||||
int send_files(Config *config) {
|
||||
Client *client = client_create();
|
||||
const char *env_ip = getenv("FASTSYNC_SERVER_IP");
|
||||
const char *ip = env_ip ? env_ip : "127.0.0.1";
|
||||
const char *env_port = getenv("FASTSYNC_SERVER_PORT");
|
||||
int port = env_port ? atoi(env_port) : 8080;
|
||||
client_connect(client, (char *)ip, port);
|
||||
Client *client;
|
||||
if (config->transport == TRANSPORT_SSH) {
|
||||
if (config->use_sendfile) {
|
||||
fprintf(stderr, "Error: -f/--sendfile is not supported with SSH transport\n");
|
||||
return 1;
|
||||
}
|
||||
client = client_connect_ssh(config->ssh_destination);
|
||||
} else {
|
||||
client = client_create();
|
||||
const char *env_ip = getenv("FASTSYNC_SERVER_IP");
|
||||
const char *ip = env_ip ? env_ip : "127.0.0.1";
|
||||
const char *env_port = getenv("FASTSYNC_SERVER_PORT");
|
||||
int port = env_port ? atoi(env_port) : 8080;
|
||||
client_connect(client, (char *)ip, port);
|
||||
}
|
||||
config_send(client->file_descriptor, config);
|
||||
DirectoryScanner *scanner = directory_scanner_create(config->send_directory, config->use_metadata);
|
||||
Chunk *current_chunk;
|
||||
@@ -176,37 +199,62 @@ int send_files_multithreaded(Config *config) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void handle_arg(char *argument_given, char *argument_to_set, bool *result,
|
||||
char *message) {
|
||||
if (strcmp(argument_given, argument_to_set) == 0) {
|
||||
*result = true;
|
||||
log_message(LOG_LEVEL_INFO, message);
|
||||
static int is_remote_dest(const char *s) {
|
||||
const char *colon = strchr(s, ':');
|
||||
if (!colon) return 0;
|
||||
if (colon == s) return 0;
|
||||
for (const char *p = s; p < colon; p++) {
|
||||
if (*p == '/') return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void print_usage(void) {
|
||||
printf("Usage:\n");
|
||||
printf(" fastsync [options] <source> <destination>\n");
|
||||
printf(" fastsync [options] --source-dir <src> --dest-dir <dst>\n");
|
||||
printf("\n");
|
||||
printf("Destination formats:\n");
|
||||
printf(" user@host:/path SSH transport (rsync-style)\n");
|
||||
printf(" host:/path SSH transport (current user)\n");
|
||||
printf(" /local/path TCP transport (requires server on localhost:8080)\n");
|
||||
printf("\n");
|
||||
printf("Options:\n");
|
||||
printf(" -c [level] Enable compression (level 1-22, default 5)\n");
|
||||
printf(" -m Enable multithreading\n");
|
||||
printf(" -s Enable chunk serialization\n");
|
||||
printf(" -f Enable sendfile (TCP only, not with -c or -s)\n");
|
||||
printf(" -M, --preserve Preserve file metadata\n");
|
||||
printf(" --source-dir <path> Source directory\n");
|
||||
printf(" --dest-dir <path> Destination directory\n");
|
||||
printf(" --save-to-disk Write received files to disk\n");
|
||||
printf(" --help Show this help\n");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
const char *env_source = getenv("FASTSYNC_SOURCE_DIR");
|
||||
const char *env_dest = getenv("FASTSYNC_DEST_DIR");
|
||||
const char *env_save = getenv("FASTSYNC_SAVE_TO_DISK");
|
||||
|
||||
char *source_dir =
|
||||
env_source ? str_dup((char *)env_source)
|
||||
: str_dup("/home/taptap/Nextcloud/Uni/moodle/B. Schnor: "
|
||||
"Konzepte Paralleler Programmierung, SoSe 2026");
|
||||
char *dest_dir =
|
||||
env_dest ? str_dup((char *)env_dest) : str_dup("./data_copied");
|
||||
bool save_to_disk = false;
|
||||
if (env_save &&
|
||||
(strcmp(env_save, "true") == 0 || strcmp(env_save, "1") == 0)) {
|
||||
save_to_disk = true;
|
||||
}
|
||||
|
||||
Config *config = config_create(str_dup("1.0.0"), source_dir, dest_dir,
|
||||
Config *config = config_create(str_dup("1.0.0"), NULL, NULL,
|
||||
save_to_disk, false, false, false, false, 5, 20, false);
|
||||
|
||||
int positional_args[2];
|
||||
int positional_count = 0;
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "-c") == 0) {
|
||||
if (strcmp(argv[i], "--help") == 0) {
|
||||
print_usage();
|
||||
return 0;
|
||||
} else if (strcmp(argv[i], "-c") == 0) {
|
||||
config->use_compression = true;
|
||||
log_message(LOG_LEVEL_INFO, "Enabled Compression");
|
||||
|
||||
if (i + 1 < argc) {
|
||||
char *end_ptr;
|
||||
int level = strtol(argv[i + 1], &end_ptr, 10);
|
||||
@@ -214,6 +262,7 @@ int main(int argc, char *argv[]) {
|
||||
config->compression_level = level;
|
||||
log_message(LOG_LEVEL_INFO, "Set Compression level to %d",
|
||||
config->compression_level);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
} else if (strcmp(argv[i], "--source-dir") == 0 && i + 1 < argc) {
|
||||
@@ -230,11 +279,52 @@ int main(int argc, char *argv[]) {
|
||||
} else if (strcmp(argv[i], "-f") == 0 || strcmp(argv[i], "--sendfile") == 0) {
|
||||
config->use_sendfile = true;
|
||||
log_message(LOG_LEVEL_INFO, "Enabled sendfile");
|
||||
} else if (strcmp(argv[i], "-m") == 0) {
|
||||
config->use_multithreading = true;
|
||||
log_message(LOG_LEVEL_INFO, "Enabled Multithreading");
|
||||
} else if (strcmp(argv[i], "-s") == 0) {
|
||||
config->use_chunk_serialization = true;
|
||||
log_message(LOG_LEVEL_INFO, "Enabled Chunk Serialization");
|
||||
} else if (argv[i][0] == '-') {
|
||||
fprintf(stderr, "Unknown option: %s\n", argv[i]);
|
||||
print_usage();
|
||||
return 1;
|
||||
} else {
|
||||
handle_arg(argv[i], "-m", &config->use_multithreading,
|
||||
"Enabled Multithreading");
|
||||
handle_arg(argv[i], "-s", &config->use_chunk_serialization,
|
||||
"Enabled Chunk Serialization");
|
||||
if (positional_count < 2)
|
||||
positional_args[positional_count++] = i;
|
||||
else {
|
||||
fprintf(stderr, "Unexpected argument: %s\n", argv[i]);
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (positional_count == 2) {
|
||||
free(config->send_directory);
|
||||
config->send_directory = str_dup(argv[positional_args[0]]);
|
||||
free(config->receive_root_directory);
|
||||
config->receive_root_directory = str_dup(argv[positional_args[1]]);
|
||||
config->save_to_disk = true;
|
||||
|
||||
if (is_remote_dest(config->receive_root_directory)) {
|
||||
config->transport = TRANSPORT_SSH;
|
||||
config->ssh_destination = str_dup(config->receive_root_directory);
|
||||
}
|
||||
} else if (positional_count == 1) {
|
||||
fprintf(stderr, "Error: missing destination argument\n");
|
||||
print_usage();
|
||||
return 1;
|
||||
} else {
|
||||
if (!config->send_directory) {
|
||||
config->send_directory =
|
||||
env_source ? str_dup((char *)env_source)
|
||||
: str_dup("/home/taptap/Nextcloud/Uni/moodle/B. Schnor: "
|
||||
"Konzepte Paralleler Programmierung, SoSe 2026");
|
||||
}
|
||||
if (!config->receive_root_directory) {
|
||||
config->receive_root_directory =
|
||||
env_dest ? str_dup((char *)env_dest) : str_dup("./data_copied");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,6 +333,11 @@ int main(int argc, char *argv[]) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (config->transport == TRANSPORT_SSH && config->use_sendfile) {
|
||||
fprintf(stderr, "Error: -f/--sendfile is not supported with SSH transport\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (config->use_multithreading)
|
||||
return send_files_multithreaded(config);
|
||||
return send_files(config);
|
||||
|
||||
Reference in New Issue
Block a user