Apply PR #143 content on top of latest main

This commit is contained in:
2026-07-29 18:18:07 +02:00
parent 3b621e591a
commit e68e33c0c5
33 changed files with 1114 additions and 1145 deletions
+118 -25
View File
@@ -3,6 +3,7 @@
#include "delta.h"
#include "log.h"
#include "protocol.h"
#include "transport_tcp.h"
#include "transport_tls.h"
#include "utils.h"
#include <errno.h>
@@ -31,6 +32,8 @@ static void print_usage(void) {
printf(" --delete Delete files on receiver not in source\n");
printf(" --exclude <pattern> Exclude files matching pattern\n");
printf(" --include <pattern> Only include files matching pattern\n");
printf(" --exclude-from <file> Read exclude patterns from file\n");
printf(" --include-from <file> Read include patterns from file\n");
printf(" --max-size <n> Skip files larger than n bytes\n");
printf(" --min-size <n> Skip files smaller than n bytes\n");
printf(" --incremental Skip files unchanged since last transfer\n");
@@ -55,11 +58,51 @@ static void print_usage(void) {
printf(" --cert <path> TLS certificate file (PEM)\n");
printf(" --key <path> TLS private key file (PEM)\n");
printf(" --ca <path> TLS CA certificate file (PEM)\n");
printf(" --timeout <sec> I/O timeout in seconds (default: 30)\n");
printf(" --contimeout <sec> Connection timeout in seconds (default: 10)\n");
printf(" -q, --quiet Suppress non-error output\n");
printf(" --silent Alias for --quiet\n");
printf(" --backup Backup existing files before overwriting\n");
printf(" --backup-dir <dir> Directory for backups (requires --backup)\n");
printf(" --stats Print transfer statistics at end\n");
printf(" --max-depth <n> Maximum directory depth (0=unlimited)\n");
printf(" --log-file <path> Write log messages to file\n");
printf(" --queue-size <n> Queue capacity for multithreaded mode (default: 100)\n");
printf(" --partial Keep partial files on interrupted transfer\n");
printf(" --fastsync-server-path <path>\n");
printf(" Path to fastsync-server on remote (default: fastsync-server)\n");
printf(" --help Show this help\n");
printf(" -V, --version Show version and exit\n");
}
static int read_patterns_from_file(const char* filepath, char*** patterns, int* count) {
FILE* fp = fopen(filepath, "r");
if (!fp) {
fprintf(stderr, "Error: could not open pattern file '%s': %s\n", filepath, strerror(errno));
return -1;
}
char line[4096];
while (fgets(line, sizeof(line), fp)) {
char* p = line;
while (*p == ' ' || *p == '\t')
p++;
if (*p == '#' || *p == '\n' || *p == '\0')
continue;
size_t len = strlen(p);
while (len > 0 && (p[len - 1] == '\n' || p[len - 1] == '\r'))
p[--len] = '\0';
if (len == 0)
continue;
char** tmp = realloc(*patterns, (*count + 1) * sizeof(char*));
if (!tmp) {
fprintf(stderr, "Error: memory allocation failed for pattern file\n");
fclose(fp);
return -1;
}
*patterns = tmp;
(*patterns)[(*count)++] = str_dup(p);
}
fclose(fp);
return 0;
}
int main(int argc, char* argv[]) {
@@ -73,13 +116,16 @@ int main(int argc, char* argv[]) {
}
int exit_code = 0;
Config* config = NULL;
bool config_owned_by_pipeline = false;
Config* config = config_create(str_dup(PROTOCOL_VERSION), NULL, NULL, save_to_disk, false, false,
false, false, 5, false, 0);
if (config == NULL) {
char* config_version = str_dup(PROTOCOL_VERSION);
if (!config_version) {
exit_code = 1;
goto cleanup;
}
config = config_create(config_version, NULL, NULL, save_to_disk, false, false, false, false, 5,
false, 0);
int positional_args[2];
int positional_count = 0;
@@ -88,9 +134,6 @@ int main(int argc, char* argv[]) {
if (strcmp(argv[i], "--help") == 0) {
print_usage();
goto cleanup;
} else if (strcmp(argv[i], "-V") == 0 || strcmp(argv[i], "--version") == 0) {
printf("fastsync version %s\n", PROTOCOL_VERSION);
goto cleanup;
} else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--archive") == 0) {
config->use_compression = true;
config->use_multithreading = true;
@@ -99,14 +142,7 @@ 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) {
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;
config->ssh_port = atoi(argv[++i]);
} else if (strcmp(argv[i], "--delete") == 0) {
config->use_delete = true;
} else if (strcmp(argv[i], "--exclude") == 0 && i + 1 < argc) {
@@ -183,14 +219,7 @@ 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) {
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;
config->server_port = atoi(argv[++i]);
} else if (strcmp(argv[i], "--bwlimit") == 0 && i + 1 < argc) {
char* end;
errno = 0;
@@ -224,6 +253,64 @@ int main(int argc, char* argv[]) {
} else if (strcmp(argv[i], "--ca") == 0 && i + 1 < argc) {
free(config->tls_ca);
config->tls_ca = str_dup(argv[++i]);
} else if (strcmp(argv[i], "--timeout") == 0 && i + 1 < argc) {
config->timeout = atoi(argv[++i]);
if (config->timeout <= 0) {
fprintf(stderr, "Error: --timeout must be a positive integer\n");
exit_code = 1;
goto cleanup;
}
} else if (strcmp(argv[i], "--contimeout") == 0 && i + 1 < argc) {
config->contimeout = atoi(argv[++i]);
if (config->contimeout <= 0) {
fprintf(stderr, "Error: --contimeout must be a positive integer\n");
exit_code = 1;
goto cleanup;
}
} else if (strcmp(argv[i], "-q") == 0 || strcmp(argv[i], "--quiet") == 0 ||
strcmp(argv[i], "--silent") == 0) {
config->quiet = true;
} else if (strcmp(argv[i], "--backup") == 0) {
config->backup = true;
} else if (strcmp(argv[i], "--backup-dir") == 0 && i + 1 < argc) {
config->backup_dir = str_dup(argv[++i]);
} else if (strcmp(argv[i], "--stats") == 0) {
config->stats = true;
} else if (strcmp(argv[i], "--max-depth") == 0 && i + 1 < argc) {
config->max_depth = atoi(argv[++i]);
if (config->max_depth < 0) {
fprintf(stderr, "Error: --max-depth must be a non-negative integer\n");
exit_code = 1;
goto cleanup;
}
} else if (strcmp(argv[i], "--log-file") == 0 && i + 1 < argc) {
FILE* lf = fopen(argv[++i], "a");
if (!lf) {
fprintf(stderr, "Error: could not open log file '%s': %s\n", argv[i], strerror(errno));
exit_code = 1;
goto cleanup;
}
config->log_file = lf;
log_set_file(lf);
} else if (strcmp(argv[i], "--queue-size") == 0 && i + 1 < argc) {
config->queue_size = atoi(argv[++i]);
if (config->queue_size <= 0) {
fprintf(stderr, "Error: --queue-size must be a positive integer\n");
exit_code = 1;
goto cleanup;
}
} else if (strcmp(argv[i], "--exclude-from") == 0 && i + 1 < argc) {
if (read_patterns_from_file(argv[++i], &config->exclude_patterns, &config->exclude_count) !=
0) {
exit_code = 1;
goto cleanup;
}
} else if (strcmp(argv[i], "--include-from") == 0 && i + 1 < argc) {
if (read_patterns_from_file(argv[++i], &config->include_patterns, &config->include_count) !=
0) {
exit_code = 1;
goto cleanup;
}
} else if (strcmp(argv[i], "--partial") == 0) {
config->partial = true;
} else if (strcmp(argv[i], "--fastsync-server-path") == 0 && i + 1 < argc) {
@@ -327,6 +414,8 @@ int main(int argc, char* argv[]) {
tls_global_init();
}
tcp_set_timeouts(config->timeout, config->contimeout);
if (config->use_multithreading) {
config_owned_by_pipeline = true;
exit_code = send_files_multithreaded(config);
@@ -335,7 +424,11 @@ int main(int argc, char* argv[]) {
}
cleanup:
if (!config_owned_by_pipeline)
config_delete(config);
if (config) {
if (config->log_file)
fclose(config->log_file);
if (!config_owned_by_pipeline)
config_delete(config);
}
return exit_code;
}
+39 -17
View File
@@ -21,6 +21,9 @@
#include <string.h>
#include <threads.h>
#include <time.h>
#include <unistd.h>
#define STREAM_THRESHOLD (64ULL * 1024 * 1024)
static int incremental_check(Client* client, File* file, DeltaSignature** out_sig) {
*out_sig = NULL;
@@ -208,10 +211,13 @@ int send_chunk(Client* client, Chunk* chunk, Config* config) {
return 0;
}
bool use_sendfile = config->use_sendfile && !config->use_compression;
for (int i = 0; i < chunk->element_count; i++) {
int rc =
send_single_file(client, chunk->items[i], config, config->use_incremental, use_sendfile);
File* f = chunk->items[i];
if (f == NULL)
continue;
bool stream = f->data->data == NULL && f->data->size > 0;
bool use_sendfile = (config->use_sendfile && !config->use_compression) || stream;
int rc = send_single_file(client, f, config, config->use_incremental, use_sendfile);
if (rc == 1)
continue;
if (rc < 0)
@@ -296,16 +302,14 @@ static int send_chunks_multithreaded(void* pipeline_context) {
static int scan_directory_multithreaded(void* pipeline_context) {
PipelineContextSender* context = (PipelineContextSender*)pipeline_context;
mtx_lock(&context->mutex_scanner);
DirectoryScanner* scanner = directory_scanner_create(
ParallelScanner* scanner = parallel_scanner_create(
context->config->send_directory, context->config->use_metadata, context->config->chunk_size,
context->config->exclude_patterns, context->config->exclude_count,
context->config->include_patterns, context->config->include_count, context->config->max_size,
context->config->min_size);
mtx_unlock(&context->mutex_scanner);
context->config->min_size, context->config->max_depth, 4);
Chunk* current_chunk;
while ((current_chunk = directory_scanner_next(scanner)) != NULL) {
while ((current_chunk = parallel_scanner_next(scanner)) != NULL) {
if (context->config->use_delete) {
mtx_lock(&context->mutex_scanner);
for (int i = 0; i < current_chunk->element_count; i++) {
@@ -325,7 +329,7 @@ static int scan_directory_multithreaded(void* pipeline_context) {
cnd_signal(&context->condition_not_empty_scanner);
mtx_unlock(&context->mutex_scanner);
directory_scanner_destroy(scanner);
parallel_scanner_destroy(scanner);
return thrd_success;
}
@@ -344,9 +348,12 @@ static int load_files_multithreaded(void* pipeline_context) {
}
if (!context->config->use_sendfile) {
for (int i = 0; i < chunk->element_count; i++) {
if (!file_load_data(chunk->items[i])) {
File* f = chunk->items[i];
if (f->data->size > STREAM_THRESHOLD)
continue;
if (!file_load_data(f)) {
log_message(LOG_LEVEL_ERROR, "Failed to load file data, skipping");
file_destroy(chunk->items[i]);
file_destroy(f);
chunk->items[i] = NULL;
}
}
@@ -362,7 +369,7 @@ int send_files(Config* config) {
DirectoryScanner* scanner = directory_scanner_create(
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->min_size);
config->min_size, config->max_depth);
Chunk* chunk;
int file_count = 0;
unsigned long long total_bytes = 0;
@@ -416,7 +423,7 @@ int send_files(Config* config) {
DirectoryScanner* scanner = directory_scanner_create(
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->min_size);
config->min_size, config->max_depth);
Chunk* current_chunk;
unsigned long long total_bytes = 0;
time_t last_progress = 0;
@@ -435,7 +442,10 @@ int send_files(Config* config) {
}
if (!config->use_sendfile) {
for (int i = 0; i < current_chunk->element_count; i++) {
if (!file_load_data(current_chunk->items[i])) {
File* f = current_chunk->items[i];
if (f->data->size > STREAM_THRESHOLD)
continue;
if (!file_load_data(f)) {
log_message(LOG_LEVEL_ERROR, "Failed to load file data");
continue;
}
@@ -502,7 +512,7 @@ int send_files_multithreaded(Config* config) {
DirectoryScanner* scanner = directory_scanner_create(
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->min_size);
config->min_size, config->max_depth);
Chunk* chunk;
int file_count = 0;
unsigned long long total_bytes = 0;
@@ -520,8 +530,20 @@ int send_files_multithreaded(Config* config) {
return 0;
}
Queue* q1 = queue_create(100, chunk_destroy);
Queue* q2 = queue_create(100, chunk_destroy);
long pages = sysconf(_SC_AVPHYS_PAGES);
long page_size = sysconf(_SC_PAGE_SIZE);
unsigned long long available_memory =
pages > 0 && page_size > 0 ? (unsigned long long)pages * (unsigned long long)page_size
: 512ULL * 1024 * 1024;
unsigned long long avg_file_size = 1024 * 1024;
int qsize = (int)(available_memory / avg_file_size);
if (qsize < 10)
qsize = 10;
if (qsize > 1000)
qsize = 1000;
Queue* q1 = queue_create(qsize, chunk_destroy);
Queue* q2 = queue_create(qsize, chunk_destroy);
if (!q1 || !q2) {
if (q1)
queue_destroy(q1);
+298 -155
View File
@@ -9,101 +9,53 @@
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <threads.h>
#include <unistd.h>
DirectoryScanner* directory_scanner_create(char* root_directory, bool use_metadata,
typedef struct {
char* path;
int depth;
} DirEntry;
static void dir_entry_destroy(void* item) {
if (item) {
DirEntry* de = (DirEntry*)item;
free(de->path);
free(de);
}
}
static DirEntry* dir_entry_create(const char* path, int depth) {
DirEntry* de = malloc(sizeof(DirEntry));
if (de) {
de->path = str_dup(path);
de->depth = depth;
}
return de;
}
DirectoryScanner* directory_scanner_create(const char* root_directory, bool use_metadata,
unsigned long long chunk_size, char** exclude_patterns,
int exclude_count, char** include_patterns,
int include_count, unsigned long long max_size,
unsigned long long min_size) {
return directory_scanner_create_full(root_directory, use_metadata, chunk_size, exclude_patterns,
exclude_count, include_patterns, include_count, max_size,
min_size, true);
}
DirectoryScanner* directory_scanner_create_full(char* root_directory, bool use_metadata,
unsigned long long chunk_size,
char** exclude_patterns, int exclude_count,
char** include_patterns, int include_count,
unsigned long long max_size,
unsigned long long min_size, bool follow_symlinks) {
unsigned long long min_size, int max_depth) {
DirectoryScanner* scanner = malloc(sizeof(DirectoryScanner));
if (scanner == NULL)
return NULL;
scanner->directories = queue_create(100, free);
scanner->directories = queue_create(100, dir_entry_destroy);
scanner->current_dir = NULL;
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;
scanner->follow_symlinks = follow_symlinks;
char* root_copy = str_dup(root_directory);
if (root_copy == NULL) {
for (int i = 0; i < scanner->include_count; i++)
free(scanner->include_patterns[i]);
free(scanner->include_patterns);
for (int i = 0; i < scanner->exclude_count; i++)
free(scanner->exclude_patterns[i]);
free(scanner->exclude_patterns);
queue_destroy(scanner->directories);
free(scanner);
return NULL;
}
queue_enqueue(scanner->directories, root_copy);
scanner->max_depth = max_depth;
scanner->current_depth = 0;
queue_enqueue(scanner->directories, dir_entry_create(root_directory, 0));
return scanner;
}
@@ -115,12 +67,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);
}
@@ -134,7 +80,6 @@ static Chunk* chunk_data_to_chunk(ArrayList* chunk_data) {
return chunk;
}
// Returns: 1 on success, 0 if no more directories in queue, -1 on opendir failure
static int open_next_directory(DirectoryScanner* scanner) {
if (scanner->current_dir) {
closedir(scanner->current_dir);
@@ -145,7 +90,10 @@ static int open_next_directory(DirectoryScanner* scanner) {
if (queue_is_empty(scanner->directories))
return 0;
scanner->current_path = (char*)queue_dequeue(scanner->directories);
DirEntry* de = (DirEntry*)queue_dequeue(scanner->directories);
scanner->current_path = de->path;
scanner->current_depth = de->depth;
free(de);
scanner->current_dir = opendir(scanner->current_path);
if (scanner->current_dir == NULL) {
perror("Could not open directory");
@@ -183,82 +131,24 @@ Chunk* directory_scanner_next(DirectoryScanner* scanner) {
char* cur_path = path_cat(scanner->current_path, entry->d_name);
struct stat stats;
// Use lstat to detect symlinks
if (lstat(cur_path, &stats) != 0) {
if (stat(cur_path, &stats) != 0) {
free(cur_path);
continue;
}
// If follow_symlinks is enabled and this is a symlink, resolve it
if (scanner->follow_symlinks && S_ISLNK(stats.st_mode)) {
struct stat target_stats;
if (stat(cur_path, &target_stats) != 0) {
// Broken symlink, skip
free(cur_path);
continue;
}
stats = target_stats;
}
if (S_ISDIR(stats.st_mode)) {
queue_enqueue(scanner->directories, (void*)cur_path);
} else if (S_ISLNK(stats.st_mode)) {
// Handle symlink (not following)
bool excluded = false;
for (int i = 0; i < scanner->exclude_count; i++) {
if (glob_match(scanner->exclude_patterns[i], entry->d_name)) {
excluded = true;
break;
}
}
if (excluded) {
free(cur_path);
continue;
}
if (scanner->include_count > 0) {
bool included = false;
for (int i = 0; i < scanner->include_count; i++) {
if (glob_match(scanner->include_patterns[i], entry->d_name)) {
included = true;
break;
}
}
if (!included) {
free(cur_path);
continue;
}
}
File* file = file_create(cur_path);
if (file == NULL) {
free(cur_path);
continue;
}
file->type = FILE_TYPE_SYMLINK;
// Read link target
char link_buf[4096];
ssize_t link_len = readlink(cur_path, link_buf, sizeof(link_buf) - 1);
if (link_len >= 0) {
link_buf[link_len] = '\0';
file->link_target = str_dup(link_buf);
if (file->link_target == NULL) {
file_destroy(file);
free(cur_path);
continue;
}
}
file->data->size = 0;
if (scanner->use_metadata)
file->metadata = file_metadata_create(&stats);
array_list_add(chunk_data, file);
chunk_data_size += 1; // small size for symlinks
if (chunk_data_size > scanner->chunk_size) {
free(cur_path);
return chunk_data_to_chunk(chunk_data);
int next_depth = scanner->current_depth + 1;
if (scanner->max_depth <= 0 || next_depth < scanner->max_depth) {
DirEntry* de = dir_entry_create(cur_path, next_depth);
if (!queue_enqueue(scanner->directories, de))
dir_entry_destroy(de);
}
free(cur_path);
} else {
if (scanner->max_depth > 0 && scanner->current_depth + 1 > scanner->max_depth) {
free(cur_path);
continue;
}
bool excluded = false;
for (int i = 0; i < scanner->exclude_count; i++) {
if (glob_match(scanner->exclude_patterns[i], entry->d_name)) {
@@ -314,3 +204,256 @@ Chunk* directory_scanner_next(DirectoryScanner* scanner) {
array_list_delete(chunk_data);
return NULL;
}
typedef struct {
ParallelScanner* ps;
char** dirs;
int dir_count;
bool use_metadata;
unsigned long long chunk_size;
char** exclude_patterns;
int exclude_count;
char** include_patterns;
int include_count;
unsigned long long max_size;
unsigned long long min_size;
int max_depth;
} ParallelWorkerArg;
static int parallel_worker_thread(void* arg) {
ParallelWorkerArg* wa = (ParallelWorkerArg*)arg;
for (int i = 0; i < wa->dir_count; i++) {
DirectoryScanner* ds = directory_scanner_create(
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);
Chunk* chunk;
while ((chunk = directory_scanner_next(ds)) != NULL) {
queue_enqueue_multithreaded(wa->ps->result_queue, chunk, &wa->ps->result_mutex,
&wa->ps->result_not_empty, &wa->ps->result_not_full);
}
directory_scanner_destroy(ds);
free(wa->dirs[i]);
}
ParallelScanner* ps = wa->ps;
free(wa->dirs);
free(wa);
mtx_lock(&ps->result_mutex);
ps->completed++;
if (ps->completed >= ps->num_threads) {
ps->done = true;
cnd_signal(&ps->result_not_empty);
}
mtx_unlock(&ps->result_mutex);
return thrd_success;
}
ParallelScanner* parallel_scanner_create(char* root_directory, bool use_metadata,
unsigned long long chunk_size, char** exclude_patterns,
int exclude_count, char** include_patterns,
int include_count, unsigned long long max_size,
unsigned long long min_size, int max_depth,
int num_threads) {
ParallelScanner* ps = calloc(1, sizeof(ParallelScanner));
if (!ps)
return NULL;
ps->result_queue = queue_create(100, chunk_destroy);
if (!ps->result_queue) {
free(ps);
return NULL;
}
if (mtx_init(&ps->result_mutex, mtx_plain) != thrd_success ||
cnd_init(&ps->result_not_empty) != thrd_success ||
cnd_init(&ps->result_not_full) != thrd_success) {
queue_destroy(ps->result_queue);
free(ps);
return NULL;
}
DIR* dir = opendir(root_directory);
if (!dir) {
perror("Could not open root directory for parallel scan");
parallel_scanner_destroy(ps);
return NULL;
}
ArrayList* root_files = array_list_create(file_destroy);
ArrayList* subdirs = array_list_create(free);
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
char* cur_path = path_cat(root_directory, entry->d_name);
if (!cur_path)
continue;
struct stat st;
if (stat(cur_path, &st) != 0) {
free(cur_path);
continue;
}
if (S_ISDIR(st.st_mode)) {
array_list_add(subdirs, cur_path);
} else {
bool excluded = false;
for (int i = 0; i < exclude_count; i++) {
if (glob_match(exclude_patterns[i], entry->d_name)) {
excluded = true;
break;
}
}
if (excluded) {
free(cur_path);
continue;
}
if (include_count > 0) {
bool included = false;
for (int i = 0; i < include_count; i++) {
if (glob_match(include_patterns[i], entry->d_name)) {
included = true;
break;
}
}
if (!included) {
free(cur_path);
continue;
}
}
if ((max_size > 0 && (unsigned long long)st.st_size > max_size) ||
(min_size > 0 && (unsigned long long)st.st_size < min_size)) {
free(cur_path);
continue;
}
File* file = file_create(cur_path);
free(cur_path);
if (!file)
continue;
file->data->size = st.st_size;
if (use_metadata)
file->metadata = file_metadata_create(&st);
array_list_add(root_files, file);
}
}
closedir(dir);
unsigned long long cs = chunk_size > 0 ? chunk_size : DESIRED_CHUNK_SIZE;
if (root_files->size > 0) {
ArrayList* batch = array_list_create(NULL);
unsigned long long batch_size = 0;
Chunk* first = NULL;
for (int i = 0; i < root_files->size; i++) {
File* f = (File*)root_files->items[i];
array_list_add(batch, f);
batch_size += f->data->size;
if (batch_size >= cs || i == root_files->size - 1) {
void** items = array_list_to_array(batch);
Chunk* c = chunk_create((File**)items, batch->size);
free(items);
batch->item_destroyer = NULL;
array_list_delete(batch);
batch = NULL;
if (!first) {
first = c;
} else {
queue_enqueue_multithreaded(ps->result_queue, c, &ps->result_mutex, &ps->result_not_empty,
&ps->result_not_full);
}
if (i < root_files->size - 1) {
batch = array_list_create(NULL);
batch_size = 0;
}
}
}
if (batch) {
batch->item_destroyer = NULL;
array_list_delete(batch);
}
ps->initial_chunk = first;
root_files->item_destroyer = NULL;
}
array_list_delete(root_files);
int n = num_threads > 0 ? num_threads : 4;
if (n > subdirs->size)
n = subdirs->size > 0 ? subdirs->size : 1;
if (subdirs->size > 0) {
ps->num_threads = n;
ps->threads = calloc(n, sizeof(thrd_t));
if (!ps->threads) {
array_list_delete(subdirs);
parallel_scanner_destroy(ps);
return NULL;
}
int dirs_per_thread = subdirs->size / n;
int remainder = subdirs->size % n;
int start = 0;
for (int t = 0; t < n; t++) {
int count = dirs_per_thread + (t < remainder ? 1 : 0);
if (count == 0)
break;
ParallelWorkerArg* wa = calloc(1, sizeof(ParallelWorkerArg));
if (!wa)
break;
wa->ps = ps;
wa->dirs = calloc(count, sizeof(char*));
if (!wa->dirs) {
free(wa);
break;
}
for (int j = 0; j < count; j++)
wa->dirs[j] = str_dup((char*)subdirs->items[start + j]);
wa->dir_count = count;
wa->use_metadata = use_metadata;
wa->chunk_size = cs;
wa->exclude_patterns = exclude_patterns;
wa->exclude_count = exclude_count;
wa->include_patterns = include_patterns;
wa->include_count = include_count;
wa->max_size = max_size;
wa->min_size = min_size;
wa->max_depth = max_depth;
start += count;
if (thrd_create(&ps->threads[t], parallel_worker_thread, wa) != thrd_success) {
for (int j = 0; j < count; j++)
free(wa->dirs[j]);
free(wa->dirs);
free(wa);
ps->num_threads = t;
break;
}
}
}
array_list_delete(subdirs);
return ps;
}
Chunk* parallel_scanner_next(ParallelScanner* ps) {
if (ps->initial_chunk) {
Chunk* c = ps->initial_chunk;
ps->initial_chunk = NULL;
return c;
}
if (ps->num_threads == 0) {
ps->done = true;
return NULL;
}
Chunk* chunk = queue_dequeue_multithreaded(
ps->result_queue, &ps->result_mutex, &ps->result_not_empty, &ps->result_not_full, &ps->done);
return chunk;
}
void parallel_scanner_destroy(ParallelScanner* ps) {
if (!ps)
return;
ps->done = true;
cnd_signal(&ps->result_not_empty);
for (int i = 0; i < ps->num_threads; i++)
thrd_join(ps->threads[i], NULL);
free(ps->threads);
if (ps->initial_chunk)
chunk_destroy(ps->initial_chunk);
queue_destroy(ps->result_queue);
mtx_destroy(&ps->result_mutex);
cnd_destroy(&ps->result_not_empty);
cnd_destroy(&ps->result_not_full);
free(ps);
}
+26 -9
View File
@@ -5,6 +5,7 @@
#include "queue.h"
#include <dirent.h>
#include <stdbool.h>
#include <threads.h>
typedef struct {
Queue* directories;
@@ -18,21 +19,37 @@ typedef struct {
int include_count;
unsigned long long max_size;
unsigned long long min_size;
bool follow_symlinks;
int max_depth;
int current_depth;
} DirectoryScanner;
DirectoryScanner* directory_scanner_create(char* root_directory, bool use_metadata,
typedef struct {
Queue* result_queue;
mtx_t result_mutex;
cnd_t result_not_empty;
cnd_t result_not_full;
int num_threads;
thrd_t* threads;
bool done;
int completed;
Chunk* initial_chunk;
} ParallelScanner;
DirectoryScanner* directory_scanner_create(const char* root_directory, bool use_metadata,
unsigned long long chunk_size, char** exclude_patterns,
int exclude_count, char** include_patterns,
int include_count, unsigned long long max_size,
unsigned long long min_size);
DirectoryScanner* directory_scanner_create_full(char* root_directory, bool use_metadata,
unsigned long long chunk_size,
char** exclude_patterns, int exclude_count,
char** include_patterns, int include_count,
unsigned long long max_size,
unsigned long long min_size, bool follow_symlinks);
unsigned long long min_size, int max_depth);
Chunk* directory_scanner_next(DirectoryScanner* scanner);
void directory_scanner_destroy(DirectoryScanner* scanner);
ParallelScanner* parallel_scanner_create(char* root_directory, bool use_metadata,
unsigned long long chunk_size, char** exclude_patterns,
int exclude_count, char** include_patterns,
int include_count, unsigned long long max_size,
unsigned long long min_size, int max_depth,
int num_threads);
Chunk* parallel_scanner_next(ParallelScanner* scanner);
void parallel_scanner_destroy(ParallelScanner* scanner);
#endif