Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 80768d64d4 |
@@ -25,16 +25,13 @@
|
|||||||
|
|
||||||
#define STREAM_THRESHOLD (64ULL * 1024 * 1024)
|
#define STREAM_THRESHOLD (64ULL * 1024 * 1024)
|
||||||
|
|
||||||
/* Forward declaration for progress-reporting thread used in multithreaded send. */
|
|
||||||
static int progress_thread_fn(void* arg);
|
|
||||||
|
|
||||||
/* Print dry-run manifest showing files that would be transferred. Returns 0 on success. */
|
/* Print dry-run manifest showing files that would be transferred. Returns 0 on success. */
|
||||||
static int send_dry_run_manifest(Config* config) {
|
static int send_dry_run_manifest(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->max_depth, config->follow_symlinks, config->copy_links,
|
config->min_size, config->max_depth, config->follow_symlinks, config->copy_links,
|
||||||
config->safe_links, config->copy_unsafe_links, false);
|
config->safe_links, config->copy_unsafe_links);
|
||||||
if (!scanner)
|
if (!scanner)
|
||||||
return -1;
|
return -1;
|
||||||
Chunk* chunk;
|
Chunk* chunk;
|
||||||
@@ -274,9 +271,6 @@ static int send_chunks_multithreaded(void* pipeline_context) {
|
|||||||
if (context->config->transport == TRANSPORT_SSH) {
|
if (context->config->transport == TRANSPORT_SSH) {
|
||||||
if (context->config->use_sendfile) {
|
if (context->config->use_sendfile) {
|
||||||
fprintf(stderr, "Error: -f/--sendfile is not supported with SSH transport\n");
|
fprintf(stderr, "Error: -f/--sendfile is not supported with SSH transport\n");
|
||||||
mtx_lock(&context->mutex_progress);
|
|
||||||
context->sender_done = true;
|
|
||||||
mtx_unlock(&context->mutex_progress);
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
client = client_connect_ssh(context->config->ssh_destination, context->config->ssh_port,
|
client = client_connect_ssh(context->config->ssh_destination, context->config->ssh_port,
|
||||||
@@ -289,9 +283,6 @@ static int send_chunks_multithreaded(void* pipeline_context) {
|
|||||||
if (client)
|
if (client)
|
||||||
client_delete(client);
|
client_delete(client);
|
||||||
fprintf(stderr, "Error: could not connect to server via TLS\n");
|
fprintf(stderr, "Error: could not connect to server via TLS\n");
|
||||||
mtx_lock(&context->mutex_progress);
|
|
||||||
context->sender_done = true;
|
|
||||||
mtx_unlock(&context->mutex_progress);
|
|
||||||
return thrd_error;
|
return thrd_error;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -301,18 +292,12 @@ static int send_chunks_multithreaded(void* pipeline_context) {
|
|||||||
if (client)
|
if (client)
|
||||||
client_delete(client);
|
client_delete(client);
|
||||||
fprintf(stderr, "Error: could not connect to server\n");
|
fprintf(stderr, "Error: could not connect to server\n");
|
||||||
mtx_lock(&context->mutex_progress);
|
|
||||||
context->sender_done = true;
|
|
||||||
mtx_unlock(&context->mutex_progress);
|
|
||||||
return thrd_error;
|
return thrd_error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!config_send(client->file_descriptor, context->config)) {
|
if (!config_send(client->file_descriptor, context->config)) {
|
||||||
client_disconnect(client);
|
client_disconnect(client);
|
||||||
client_delete(client);
|
client_delete(client);
|
||||||
mtx_lock(&context->mutex_progress);
|
|
||||||
context->sender_done = true;
|
|
||||||
mtx_unlock(&context->mutex_progress);
|
|
||||||
return thrd_error;
|
return thrd_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,38 +316,19 @@ static int send_chunks_multithreaded(void* pipeline_context) {
|
|||||||
int ok = receive_status(client->file_descriptor, &s) && s == STATUS_OK;
|
int ok = receive_status(client->file_descriptor, &s) && s == STATUS_OK;
|
||||||
client_disconnect(client);
|
client_disconnect(client);
|
||||||
client_delete(client);
|
client_delete(client);
|
||||||
mtx_lock(&context->mutex_progress);
|
|
||||||
context->sender_done = true;
|
|
||||||
mtx_unlock(&context->mutex_progress);
|
|
||||||
return ok ? thrd_success : thrd_error;
|
return ok ? thrd_success : thrd_error;
|
||||||
|
|
||||||
send_fail:
|
send_fail:
|
||||||
client_disconnect(client);
|
client_disconnect(client);
|
||||||
client_delete(client);
|
client_delete(client);
|
||||||
mtx_lock(&context->mutex_progress);
|
|
||||||
context->sender_done = true;
|
|
||||||
mtx_unlock(&context->mutex_progress);
|
|
||||||
return thrd_error;
|
return thrd_error;
|
||||||
}
|
}
|
||||||
if (send_chunk(client, current_chunk, context->config) != 0) {
|
if (send_chunk(client, current_chunk, context->config) != 0) {
|
||||||
fprintf(stderr, "Error: unexpected error while sending chunk\n");
|
fprintf(stderr, "Error: unexpected error while sending chunk\n");
|
||||||
client_disconnect(client);
|
client_disconnect(client);
|
||||||
client_delete(client);
|
client_delete(client);
|
||||||
mtx_lock(&context->mutex_progress);
|
|
||||||
context->sender_done = true;
|
|
||||||
mtx_unlock(&context->mutex_progress);
|
|
||||||
return thrd_error;
|
return thrd_error;
|
||||||
}
|
}
|
||||||
if (context->config->show_progress) {
|
|
||||||
unsigned long long chunk_bytes = 0;
|
|
||||||
for (int i = 0; i < current_chunk->element_count; i++) {
|
|
||||||
if (current_chunk->items[i] && current_chunk->items[i]->data)
|
|
||||||
chunk_bytes += current_chunk->items[i]->data->size;
|
|
||||||
}
|
|
||||||
mtx_lock(&context->mutex_progress);
|
|
||||||
context->progress_bytes += chunk_bytes;
|
|
||||||
mtx_unlock(&context->mutex_progress);
|
|
||||||
}
|
|
||||||
chunk_destroy(current_chunk);
|
chunk_destroy(current_chunk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -374,7 +340,7 @@ static int scan_directory_multithreaded(void* pipeline_context) {
|
|||||||
context->config->exclude_patterns, context->config->exclude_count,
|
context->config->exclude_patterns, context->config->exclude_count,
|
||||||
context->config->include_patterns, context->config->include_count, context->config->max_size,
|
context->config->include_patterns, context->config->include_count, context->config->max_size,
|
||||||
context->config->min_size, context->config->max_depth, 4, context->config->follow_symlinks,
|
context->config->min_size, context->config->max_depth, 4, context->config->follow_symlinks,
|
||||||
context->config->copy_links, context->config->safe_links, context->config->copy_unsafe_links, false);
|
context->config->copy_links, context->config->safe_links, context->config->copy_unsafe_links);
|
||||||
|
|
||||||
Chunk* current_chunk;
|
Chunk* current_chunk;
|
||||||
while ((current_chunk = parallel_scanner_next(scanner)) != NULL) {
|
while ((current_chunk = parallel_scanner_next(scanner)) != NULL) {
|
||||||
@@ -432,42 +398,6 @@ static int load_files_multithreaded(void* pipeline_context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Progress-reporting thread for multithreaded send. Runs in parallel with
|
|
||||||
the scanner/loader/sender threads and prints periodic progress to stderr. */
|
|
||||||
static int progress_thread_fn(void* arg) {
|
|
||||||
PipelineContextSender* context = (PipelineContextSender*)arg;
|
|
||||||
time_t last_progress = 0;
|
|
||||||
time_t start = time(NULL);
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
mtx_lock(&context->mutex_progress);
|
|
||||||
bool done = context->sender_done;
|
|
||||||
unsigned long long total = context->progress_bytes;
|
|
||||||
mtx_unlock(&context->mutex_progress);
|
|
||||||
|
|
||||||
if (done) {
|
|
||||||
time_t now = time(NULL);
|
|
||||||
double elapsed = difftime(now, start);
|
|
||||||
double rate = elapsed > 0.0 ? total / (1048576.0 * elapsed) : 0.0;
|
|
||||||
fprintf(stderr, "\rSent %.1f MB (%.1f MB/s) Done.\n", total / 1048576.0, rate);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
time_t now = time(NULL);
|
|
||||||
if (now - last_progress >= 1) {
|
|
||||||
last_progress = now;
|
|
||||||
double elapsed = difftime(now, start);
|
|
||||||
double rate = elapsed > 0.0 ? total / (1048576.0 * elapsed) : 0.0;
|
|
||||||
fprintf(stderr, "\rSent %.1f MB (%.1f MB/s) ", total / 1048576.0, rate);
|
|
||||||
fflush(stderr);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct timespec ts = {0, 100 * 1000000L}; /* 100 ms */
|
|
||||||
thrd_sleep(&ts, NULL);
|
|
||||||
}
|
|
||||||
return thrd_success;
|
|
||||||
}
|
|
||||||
|
|
||||||
int send_files(Config* config) {
|
int send_files(Config* config) {
|
||||||
if (config->dry_run)
|
if (config->dry_run)
|
||||||
return send_dry_run_manifest(config);
|
return send_dry_run_manifest(config);
|
||||||
@@ -509,7 +439,7 @@ int send_files(Config* config) {
|
|||||||
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->max_depth, config->follow_symlinks, config->copy_links,
|
config->min_size, config->max_depth, config->follow_symlinks, config->copy_links,
|
||||||
config->safe_links, config->copy_unsafe_links, false);
|
config->safe_links, config->copy_unsafe_links);
|
||||||
Chunk* current_chunk;
|
Chunk* current_chunk;
|
||||||
unsigned long long total_bytes = 0;
|
unsigned long long total_bytes = 0;
|
||||||
time_t last_progress = 0;
|
time_t last_progress = 0;
|
||||||
@@ -617,7 +547,7 @@ int send_files_multithreaded(Config* config) {
|
|||||||
if (config->use_delete)
|
if (config->use_delete)
|
||||||
context->manifest = array_list_create(free);
|
context->manifest = array_list_create(free);
|
||||||
|
|
||||||
thrd_t scanner, loader, sender, progress;
|
thrd_t scanner, loader, sender;
|
||||||
if (thrd_create(&scanner, scan_directory_multithreaded, context) != thrd_success ||
|
if (thrd_create(&scanner, scan_directory_multithreaded, context) != thrd_success ||
|
||||||
thrd_create(&loader, load_files_multithreaded, context) != thrd_success ||
|
thrd_create(&loader, load_files_multithreaded, context) != thrd_success ||
|
||||||
thrd_create(&sender, send_chunks_multithreaded, context) != thrd_success) {
|
thrd_create(&sender, send_chunks_multithreaded, context) != thrd_success) {
|
||||||
@@ -626,26 +556,11 @@ int send_files_multithreaded(Config* config) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config->show_progress) {
|
|
||||||
if (thrd_create(&progress, progress_thread_fn, context) != thrd_success) {
|
|
||||||
perror("Error creating progress thread.\n");
|
|
||||||
/* Non-fatal; continue without progress reporting */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int sender_result;
|
int sender_result;
|
||||||
thrd_join(scanner, NULL);
|
thrd_join(scanner, NULL);
|
||||||
thrd_join(loader, NULL);
|
thrd_join(loader, NULL);
|
||||||
thrd_join(sender, &sender_result);
|
thrd_join(sender, &sender_result);
|
||||||
|
|
||||||
if (config->show_progress) {
|
|
||||||
/* Signal progress thread to exit if it hasn't already */
|
|
||||||
mtx_lock(&context->mutex_progress);
|
|
||||||
context->sender_done = true;
|
|
||||||
mtx_unlock(&context->mutex_progress);
|
|
||||||
thrd_join(progress, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
pipeline_context_sender_destroy(context);
|
pipeline_context_sender_destroy(context);
|
||||||
return sender_result == thrd_success ? 0 : 1;
|
return sender_result == thrd_success ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|||||||
+55
-6
@@ -40,7 +40,7 @@ DirectoryScanner* directory_scanner_create(const char* root_directory, bool use_
|
|||||||
int include_count, unsigned long long max_size,
|
int include_count, unsigned long long max_size,
|
||||||
unsigned long long min_size, int max_depth,
|
unsigned long long min_size, int max_depth,
|
||||||
bool follow_symlinks, bool copy_links, bool safe_links,
|
bool follow_symlinks, bool copy_links, bool safe_links,
|
||||||
bool copy_unsafe_links, bool checksum) {
|
bool copy_unsafe_links) {
|
||||||
DirectoryScanner* scanner = malloc(sizeof(DirectoryScanner));
|
DirectoryScanner* scanner = malloc(sizeof(DirectoryScanner));
|
||||||
if (scanner == NULL)
|
if (scanner == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -61,7 +61,6 @@ DirectoryScanner* directory_scanner_create(const char* root_directory, bool use_
|
|||||||
scanner->copy_links = copy_links;
|
scanner->copy_links = copy_links;
|
||||||
scanner->safe_links = safe_links;
|
scanner->safe_links = safe_links;
|
||||||
scanner->copy_unsafe_links = copy_unsafe_links;
|
scanner->copy_unsafe_links = copy_unsafe_links;
|
||||||
scanner->checksum = checksum;
|
|
||||||
queue_enqueue(scanner->directories, dir_entry_create(root_directory, 0));
|
queue_enqueue(scanner->directories, dir_entry_create(root_directory, 0));
|
||||||
return scanner;
|
return scanner;
|
||||||
}
|
}
|
||||||
@@ -277,7 +276,6 @@ typedef struct {
|
|||||||
bool copy_links;
|
bool copy_links;
|
||||||
bool safe_links;
|
bool safe_links;
|
||||||
bool copy_unsafe_links;
|
bool copy_unsafe_links;
|
||||||
bool checksum;
|
|
||||||
} ParallelWorkerArg;
|
} ParallelWorkerArg;
|
||||||
|
|
||||||
static int parallel_worker_thread(void* arg) {
|
static int parallel_worker_thread(void* arg) {
|
||||||
@@ -286,7 +284,7 @@ static int parallel_worker_thread(void* arg) {
|
|||||||
DirectoryScanner* ds = directory_scanner_create(
|
DirectoryScanner* ds = directory_scanner_create(
|
||||||
wa->dirs[i], wa->use_metadata, wa->chunk_size, wa->exclude_patterns, wa->exclude_count,
|
wa->dirs[i], wa->use_metadata, wa->chunk_size, wa->exclude_patterns, wa->exclude_count,
|
||||||
wa->include_patterns, wa->include_count, wa->max_size, wa->min_size, wa->max_depth,
|
wa->include_patterns, wa->include_count, wa->max_size, wa->min_size, wa->max_depth,
|
||||||
wa->follow_symlinks, wa->copy_links, wa->safe_links, wa->copy_unsafe_links, wa->checksum);
|
wa->follow_symlinks, wa->copy_links, wa->safe_links, wa->copy_unsafe_links);
|
||||||
Chunk* chunk;
|
Chunk* chunk;
|
||||||
while ((chunk = directory_scanner_next(ds)) != NULL) {
|
while ((chunk = directory_scanner_next(ds)) != NULL) {
|
||||||
queue_enqueue_multithreaded(wa->ps->result_queue, chunk, &wa->ps->result_mutex,
|
queue_enqueue_multithreaded(wa->ps->result_queue, chunk, &wa->ps->result_mutex,
|
||||||
@@ -314,7 +312,7 @@ ParallelScanner* parallel_scanner_create(char* root_directory, bool use_metadata
|
|||||||
int include_count, unsigned long long max_size,
|
int include_count, unsigned long long max_size,
|
||||||
unsigned long long min_size, int max_depth,
|
unsigned long long min_size, int max_depth,
|
||||||
int num_threads, bool follow_symlinks, bool copy_links,
|
int num_threads, bool follow_symlinks, bool copy_links,
|
||||||
bool safe_links, bool copy_unsafe_links, bool checksum) {
|
bool safe_links, bool copy_unsafe_links) {
|
||||||
ParallelScanner* ps = calloc(1, sizeof(ParallelScanner));
|
ParallelScanner* ps = calloc(1, sizeof(ParallelScanner));
|
||||||
if (!ps)
|
if (!ps)
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -347,11 +345,63 @@ ParallelScanner* parallel_scanner_create(char* root_directory, bool use_metadata
|
|||||||
char* cur_path = path_cat(root_directory, entry->d_name);
|
char* cur_path = path_cat(root_directory, entry->d_name);
|
||||||
if (!cur_path)
|
if (!cur_path)
|
||||||
continue;
|
continue;
|
||||||
|
struct stat lstats;
|
||||||
|
if (lstat(cur_path, &lstats) != 0) {
|
||||||
|
free(cur_path);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bool is_symlink = S_ISLNK(lstats.st_mode);
|
||||||
|
|
||||||
|
// Skip symlinks unless the user explicitly enabled following/copying them.
|
||||||
|
if (is_symlink && !follow_symlinks && !copy_links && !safe_links &&
|
||||||
|
!copy_unsafe_links) {
|
||||||
|
free(cur_path);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --safe-links: reject symlinks pointing outside the source tree.
|
||||||
|
if (is_symlink && safe_links) {
|
||||||
|
char link_target[4096];
|
||||||
|
ssize_t len = readlink(cur_path, link_target, sizeof(link_target) - 1);
|
||||||
|
if (len < 0) {
|
||||||
|
free(cur_path);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
link_target[len] = ' | ||||||