Author SHA1 Message Date
TapTap 1aebcfd55d refactor: consolidate transfer infrastructure
CI / lint (pull_request) Failing after 3s
CI / build-and-test (pull_request) Skipped
CI / sanitizers (address) (pull_request) Skipped
CI / sanitizers (undefined) (pull_request) Skipped
CI / fuzz-build (pull_request) Skipped
CI / coverage (pull_request) Skipped
CI / valgrind (pull_request) Skipped
2026-09-01 21:21:24 +02:00
34 changed files with 1221 additions and 1504 deletions

No files matched your search

+91 -51
View File
@@ -8,34 +8,30 @@ set(CMAKE_C_STANDARD_REQUIRED ON)
add_compile_options(-Wall -g -O3) add_compile_options(-Wall -g -O3)
# --- Sanitizer option ---
set(SANITIZER "none" CACHE STRING "Sanitizer to enable (address, thread, undefined, none)") set(SANITIZER "none" CACHE STRING "Sanitizer to enable (address, thread, undefined, none)")
set_property(CACHE SANITIZER PROPERTY STRINGS address thread undefined none) set_property(CACHE SANITIZER PROPERTY STRINGS address thread undefined none)
if(SANITIZER STREQUAL "address") if(SANITIZER STREQUAL "address")
add_compile_options(-fsanitize=address -fno-omit-frame-pointer -g) add_compile_options(-fsanitize=address -fno-omit-frame-pointer -g)
add_link_options(-fsanitize=address) add_link_options(-fsanitize=address)
elseif(SANITIZER STREQUAL "thread") elseif(SANITIZER STREQUAL "thread")
add_compile_options(-fsanitize=thread -fno-omit-frame-pointer -g) add_compile_options(-fsanitize=thread -fno-omit-frame-pointer -g)
add_link_options(-fsanitize=thread) add_link_options(-fsanitize=thread)
elseif(SANITIZER STREQUAL "undefined") elseif(SANITIZER STREQUAL "undefined")
add_compile_options(-fsanitize=undefined -fno-omit-frame-pointer -g) add_compile_options(-fsanitize=undefined -fno-omit-frame-pointer -g)
add_link_options(-fsanitize=undefined) add_link_options(-fsanitize=undefined)
elseif(NOT SANITIZER STREQUAL "none") elseif(NOT SANITIZER STREQUAL "none")
message(FATAL_ERROR "Unknown sanitizer: ${SANITIZER}. Supported values: address, thread, undefined, none") message(FATAL_ERROR "Unknown sanitizer: ${SANITIZER}. Supported values: address, thread, undefined, none")
endif() endif()
# --- Strict warnings option ---
option(STRICT_WARNINGS "Enable strict warnings (Wextra, Wpedantic, Werror)" OFF) option(STRICT_WARNINGS "Enable strict warnings (Wextra, Wpedantic, Werror)" OFF)
if(STRICT_WARNINGS) if(STRICT_WARNINGS)
add_compile_options(-Wextra -Wpedantic -Werror) add_compile_options(-Wextra -Wpedantic -Werror)
endif() endif()
# --- Coverage option ---
option(ENABLE_COVERAGE "Enable gcov coverage" OFF) option(ENABLE_COVERAGE "Enable gcov coverage" OFF)
if(ENABLE_COVERAGE) if(ENABLE_COVERAGE)
add_compile_options(--coverage -fprofile-arcs -ftest-coverage -O0 -g) add_compile_options(--coverage -fprofile-arcs -ftest-coverage -O0 -g)
add_link_options(--coverage) add_link_options(--coverage)
endif() endif()
include(FetchContent) include(FetchContent)
@@ -49,58 +45,102 @@ FetchContent_MakeAvailable(xxhash)
set(THREADS_PREFER_PTHREAD_FLAG ON) set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
find_library(ZSTD_LIBRARY zstd) find_library(ZSTD_LIBRARY zstd)
if(NOT ZSTD_LIBRARY) if(NOT ZSTD_LIBRARY)
message(FATAL_ERROR "zstd library not found. Ensure it is in your nix-shell!") message(FATAL_ERROR "zstd library not found. Ensure it is in your nix-shell!")
endif() endif()
find_package(OpenSSL REQUIRED) find_package(OpenSSL REQUIRED)
file(GLOB SHARED_SRCS "src/shared/*.c") set(SHARED_SRCS
set(FILE_STORE_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/src/shared/file_store.c") src/shared/array_list.c
list(REMOVE_ITEM SHARED_SRCS ${FILE_STORE_SRCS}) src/shared/chunk.c
file(GLOB SERVER_SRCS "src/server/*.c") src/shared/compression.c
set(SERVER_RECEIVER_SRCS src/server/receiver.c) src/shared/config.c
file(GLOB CLIENT_SRCS "src/client/*.c") src/shared/data.c
src/shared/delta.c
src/shared/file.c
src/shared/log.c
src/shared/metadata.c
src/shared/multiprocessing.c
src/shared/protocol.c
src/shared/queue.c
src/shared/transport_ssh.c
src/shared/transport_tcp.c
src/shared/transport_tls.c
src/shared/utils.c
)
set(SERVER_SRCS src/server/server.c)
set(CLIENT_SRCS
src/client/client_cli.c
src/client/client_send.c
src/client/scanner.c
src/client/usage.c
)
# --- Main executables --- function(configure_fastsync_target target)
add_executable(server ${SERVER_SRCS} ${SHARED_SRCS} ${FILE_STORE_SRCS}) target_include_directories(${target} PRIVATE src/shared src/server src/client)
target_include_directories(server PRIVATE src/shared src/server src/client) target_link_libraries(${target} PRIVATE Threads::Threads ${ZSTD_LIBRARY} OpenSSL::SSL OpenSSL::Crypto xxhash)
target_link_libraries(server PRIVATE Threads::Threads ${ZSTD_LIBRARY} OpenSSL::SSL OpenSSL::Crypto xxhash) endfunction()
add_executable(client ${CLIENT_SRCS} ${SHARED_SRCS} ${FILE_STORE_SRCS} ${SERVER_RECEIVER_SRCS}) add_executable(server ${SERVER_SRCS} ${SHARED_SRCS})
target_include_directories(client PRIVATE src/shared src/server src/client) configure_fastsync_target(server)
target_link_libraries(client PRIVATE Threads::Threads ${ZSTD_LIBRARY} OpenSSL::SSL OpenSSL::Crypto xxhash) add_executable(client ${CLIENT_SRCS} ${SHARED_SRCS})
configure_fastsync_target(client)
# --- Testing ---
enable_testing() enable_testing()
set(TEST_SRCS
# Common test libraries tests/runner.c
set(TEST_LIBS Threads::Threads ${ZSTD_LIBRARY} OpenSSL::SSL OpenSSL::Crypto xxhash) tests/test_array_list.c
set(TEST_INCLUDES tests src/shared src/server src/client) tests/test_chunk.c
tests/test_client_cli.c
# Monolithic test binary (backward compatible) tests/test_compression.c
file(GLOB TEST_SRCS "tests/test_*.c" "tests/runner.c") tests/test_config.c
add_executable(tests ${TEST_SRCS} ${SHARED_SRCS} ${FILE_STORE_SRCS} ${SERVER_RECEIVER_SRCS} src/client/scanner.c src/client/client_cli.c src/client/client_validation.c src/client/usage.c) tests/test_data.c
target_include_directories(tests PRIVATE ${TEST_INCLUDES}) tests/test_delta.c
tests/test_file.c
tests/test_file_sendfile.c
tests/test_fuzz_smoke.c
tests/test_glob.c
tests/test_log.c
tests/test_metadata.c
tests/test_multiprocessing.c
tests/test_property.c
tests/test_protocol.c
tests/test_queue.c
tests/test_robustness.c
tests/test_scanner.c
tests/test_server.c
tests/test_shared_utils.c
tests/test_stress.c
tests/test_transport_ssh.c
tests/test_transport_tcp.c
tests/test_transport_tls.c
)
add_executable(tests ${TEST_SRCS} ${SHARED_SRCS} src/client/scanner.c src/client/client_cli.c)
configure_fastsync_target(tests)
target_include_directories(tests PRIVATE tests)
target_compile_definitions(tests PRIVATE FASTSYNC_TEST_BUILD) target_compile_definitions(tests PRIVATE FASTSYNC_TEST_BUILD)
target_link_libraries(tests PRIVATE ${TEST_LIBS})
add_test(NAME unit_all COMMAND tests) add_test(NAME unit_all COMMAND tests)
# --- Fuzz targets (requires clang) ---
option(ENABLE_FUZZ "Build fuzz targets (requires clang)" OFF) option(ENABLE_FUZZ "Build fuzz targets (requires clang)" OFF)
if(ENABLE_FUZZ) if(ENABLE_FUZZ)
if(NOT CMAKE_C_COMPILER_ID MATCHES "Clang") if(NOT CMAKE_C_COMPILER_ID MATCHES "Clang")
message(FATAL_ERROR "ENABLE_FUZZ requires Clang (compiler is ${CMAKE_C_COMPILER_ID})") message(FATAL_ERROR "ENABLE_FUZZ requires Clang (compiler is ${CMAKE_C_COMPILER_ID})")
endif() endif()
file(GLOB FUZZ_SRCS "tests/fuzz/*.c") set(FUZZ_SRCS
foreach(FUZZ_SRC ${FUZZ_SRCS}) tests/fuzz/fuzz_chunk_deserialize.c
get_filename_component(FUZZ_NAME ${FUZZ_SRC} NAME_WE) tests/fuzz/fuzz_compress_decompress.c
add_executable(${FUZZ_NAME} ${FUZZ_SRC} ${SHARED_SRCS} ${FILE_STORE_SRCS} ${SERVER_RECEIVER_SRCS}) tests/fuzz/fuzz_delta_deserialize.c
target_include_directories(${FUZZ_NAME} PRIVATE ${TEST_INCLUDES}) tests/fuzz/fuzz_delta_signature_deserialize.c
target_compile_options(${FUZZ_NAME} PRIVATE -fsanitize=fuzzer,address,undefined -fno-omit-frame-pointer) tests/fuzz/fuzz_glob_match.c
target_link_options(${FUZZ_NAME} PRIVATE -fsanitize=fuzzer,address,undefined) tests/fuzz/fuzz_metadata_from_buf.c
target_link_libraries(${FUZZ_NAME} PRIVATE ${TEST_LIBS}) )
endforeach() foreach(FUZZ_SRC ${FUZZ_SRCS})
get_filename_component(FUZZ_NAME ${FUZZ_SRC} NAME_WE)
add_executable(${FUZZ_NAME} ${FUZZ_SRC} ${SHARED_SRCS})
configure_fastsync_target(${FUZZ_NAME})
target_include_directories(${FUZZ_NAME} PRIVATE tests)
target_compile_options(${FUZZ_NAME} PRIVATE -fsanitize=fuzzer,address,undefined -fno-omit-frame-pointer)
target_link_options(${FUZZ_NAME} PRIVATE -fsanitize=fuzzer,address,undefined)
endforeach()
endif() endif()
-1
View File
@@ -105,7 +105,6 @@ working directory, so use a destination below that directory unless the
remote server is otherwise configured with a matching authorized root. remote server is otherwise configured with a matching authorized root.
```bash ```bash
ssh user@host 'mkdir -p destination'
./build/client /path/to/source user@host:destination ./build/client /path/to/source user@host:destination
``` ```
+50 -2
View File
@@ -1,12 +1,10 @@
#include "client_send.h" #include "client_send.h"
#include "client_validation.h"
#include "config.h" #include "config.h"
#include "delta.h" #include "delta.h"
#include "log.h" #include "log.h"
#include "protocol.h" #include "protocol.h"
#include "transport_tcp.h" #include "transport_tcp.h"
#include "transport_tls.h" #include "transport_tls.h"
#include "usage.h"
#include "utils.h" #include "utils.h"
#include <errno.h> #include <errno.h>
#include <limits.h> #include <limits.h>
@@ -14,6 +12,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "usage.c"
#ifndef FASTSYNC_TEST_BUILD #ifndef FASTSYNC_TEST_BUILD
/* Parse environment variables for source/destination directories and save-to-disk flag. */ /* Parse environment variables for source/destination directories and save-to-disk flag. */
@@ -361,6 +360,55 @@ int parse_args(Config* config, int argc, char* argv[], int* positional_args,
return 0; return 0;
} }
#ifndef FASTSYNC_TEST_BUILD
/* Validate config after parsing. Returns true if valid. */
static bool validate_config(const Config* config) {
if (!config->send_directory || !config->receive_root_directory) {
fprintf(stderr, "Error: source and destination directories are required\n");
print_usage();
return false;
}
if (config->use_sendfile && (config->use_chunk_serialization || config->use_compression)) {
fprintf(stderr, "Error: -f/--sendfile cannot be combined with -c (compression) or -s (chunk "
"serialization)\n");
return false;
}
if (config->transport == TRANSPORT_SSH && config->use_sendfile) {
fprintf(stderr, "Error: -f/--sendfile is not supported with SSH transport\n");
return false;
}
if (config->use_incremental && config->use_chunk_serialization) {
fprintf(stderr, "Error: --incremental is not supported with -s (chunk serialization)\n");
return false;
}
if (config->use_delta && !config->use_incremental) {
fprintf(stderr, "Error: --delta requires --incremental\n");
return false;
}
if (config->use_delta && config->use_chunk_serialization) {
fprintf(stderr, "Error: --delta cannot be combined with -s (chunk serialization)\n");
return false;
}
if (config->use_delta && config->use_sendfile) {
fprintf(stderr, "Error: --delta cannot be combined with -f (sendfile)\n");
return false;
}
if (config->append || config->append_verify) {
fprintf(
stderr,
"Error: --append and --append-verify are not supported yet; refusing to ignore option\n");
return false;
}
if (config->use_tls) {
if (!config->tls_cert || !config->tls_key) {
fprintf(stderr, "Error: --tls requires --cert and --key\n");
return false;
}
}
return true;
}
#endif /* FASTSYNC_TEST_BUILD */
static int read_patterns_from_file(const char* filepath, char*** patterns, int* count) { static int read_patterns_from_file(const char* filepath, char*** patterns, int* count) {
FILE* fp = fopen(filepath, "r"); FILE* fp = fopen(filepath, "r");
if (!fp) { if (!fp) {
+176 -172
View File
@@ -28,88 +28,6 @@
/* Forward declaration for progress-reporting thread used in multithreaded send. */ /* Forward declaration for progress-reporting thread used in multithreaded send. */
static int progress_thread_fn(void* arg); static int progress_thread_fn(void* arg);
static ScannerOptions scanner_options_from_config(const Config* config, int num_threads) {
ScannerOptions options = {
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->max_depth,
num_threads, config->follow_symlinks, config->copy_links,
config->safe_links, config->copy_unsafe_links, config->checksum};
return options;
}
/* Select the configured transport for both transfer execution paths. */
static Client* connect_transfer_client(const Config* config) {
if (config->transport == TRANSPORT_SSH) {
if (config->use_sendfile) {
fprintf(stderr, "Error: -f/--sendfile is not supported with SSH transport\n");
return NULL;
}
return client_connect_ssh(config->ssh_destination, config->ssh_port,
config->fastsync_server_path);
}
Client* client = client_create();
if (!client)
return NULL;
bool connected;
if (config->use_tls) {
connected = client_connect_tls(client, config->server_host, config->server_port,
config->tls_cert, config->tls_key, config->tls_ca);
} else {
connected = client_connect(client, config->server_host, config->server_port);
}
if (!connected) {
client_disconnect(client);
client_delete(client);
return NULL;
}
return client;
}
static void disconnect_transfer_client(Client* client) {
if (!client)
return;
client_disconnect(client);
client_delete(client);
}
static ArrayList* create_transfer_manifest(const Config* config) {
return config->use_delete ? array_list_create(free) : NULL;
}
static bool add_chunk_to_manifest(ArrayList* manifest, const Chunk* chunk) {
if (!manifest)
return true;
for (int i = 0; i < chunk->element_count; i++) {
const char* path = chunk->items[i]->path;
if (*path == '/')
path++;
char* entry = str_dup(path);
if (!entry) {
log_message(LOG_LEVEL_ERROR, "Failed to allocate manifest entry");
return false;
}
if (!array_list_add(manifest, entry)) {
free(entry);
return false;
}
}
return true;
}
static bool finalize_transfer(Client* client) {
Status status;
return send_status(client->file_descriptor, STATUS_FINISHED) &&
receive_status(client->file_descriptor, &status) && status == STATUS_OK;
}
static void mark_sender_done(PipelineContextSender* context) {
mtx_lock(&context->mutex_progress);
context->sender_done = true;
mtx_unlock(&context->mutex_progress);
}
static void pipeline_cancel(PipelineContextSender* context) { static void pipeline_cancel(PipelineContextSender* context) {
mtx_lock(&context->mutex_scanner); mtx_lock(&context->mutex_scanner);
mtx_lock(&context->mutex_loader); mtx_lock(&context->mutex_loader);
@@ -125,10 +43,12 @@ static void pipeline_cancel(PipelineContextSender* context) {
} }
/* 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(const Config* config) { static int send_dry_run_manifest(Config* config) {
ScannerOptions options = scanner_options_from_config(config, 0); DirectoryScanner* scanner = directory_scanner_create(
DirectoryScanner* scanner = config->send_directory, config->use_metadata, config->chunk_size, config->exclude_patterns,
directory_scanner_create_with_options(config->send_directory, &options); 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->safe_links, config->copy_unsafe_links, config->checksum);
if (!scanner) if (!scanner)
return -1; return -1;
Chunk* chunk; Chunk* chunk;
@@ -209,11 +129,8 @@ static int incremental_check(Client* client, File* file, const Config* config,
static int send_delta(Client* client, File* file, DeltaSignature* sig, Config* config) { static int send_delta(Client* client, File* file, DeltaSignature* sig, Config* config) {
Delta* delta = delta_compute(file->data->data, file->data->size, sig, config->delta_block_size); Delta* delta = delta_compute(file->data->data, file->data->size, sig, config->delta_block_size);
if (!delta) { if (!delta)
if (!send_status(client->file_descriptor, STATUS_NEXT))
return -1;
return 1; return 1;
}
if (!delta_is_worthwhile(delta, file->data->size)) { if (!delta_is_worthwhile(delta, file->data->size)) {
delta_destroy(delta); delta_destroy(delta);
@@ -374,24 +291,49 @@ int send_chunk(Client* client, Chunk* chunk, Config* config) {
static int send_chunks_multithreaded(void* pipeline_context) { static int send_chunks_multithreaded(void* pipeline_context) {
PipelineContextSender* context = (PipelineContextSender*)pipeline_context; PipelineContextSender* context = (PipelineContextSender*)pipeline_context;
Client* client = connect_transfer_client(context->config); Client* client;
if (!client) { if (context->config->transport == TRANSPORT_SSH) {
if (context->config->transport == TRANSPORT_TCP) if (context->config->use_sendfile) {
fprintf(stderr, "Error: could not connect to server%s\n", fprintf(stderr, "Error: -f/--sendfile is not supported with SSH transport\n");
context->config->use_tls ? " via TLS" : ""); mtx_lock(&context->mutex_progress);
pipeline_cancel(context); context->sender_done = true;
mark_sender_done(context); mtx_unlock(&context->mutex_progress);
return thrd_error; return 1;
}
client = client_connect_ssh(context->config->ssh_destination, context->config->ssh_port,
context->config->fastsync_server_path);
} else if (context->config->use_tls) {
client = client_create();
if (!client || !client_connect_tls(client, context->config->server_host,
context->config->server_port, context->config->tls_cert,
context->config->tls_key, context->config->tls_ca)) {
if (client)
client_delete(client);
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;
}
} else {
client = client_create();
if (!client ||
!client_connect(client, context->config->server_host, context->config->server_port)) {
if (client)
client_delete(client);
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;
}
} }
ProtocolSession session;
protocol_session_init(&session, client->file_descriptor, client->file_descriptor);
protocol_session_set_ssl(&session, (SSL*)client->ssl);
protocol_session_bind(&session);
if (!config_send(client->file_descriptor, context->config)) { if (!config_send(client->file_descriptor, context->config)) {
pipeline_cancel(context); client_disconnect(client);
disconnect_transfer_client(client); client_delete(client);
mark_sender_done(context); mtx_lock(&context->mutex_progress);
protocol_session_unbind(); context->sender_done = true;
mtx_unlock(&context->mutex_progress);
return thrd_error; return thrd_error;
} }
@@ -400,37 +342,39 @@ static int send_chunks_multithreaded(void* pipeline_context) {
context->queue_loader, &context->mutex_loader, &context->condition_not_empty_loader, context->queue_loader, &context->mutex_loader, &context->condition_not_empty_loader,
&context->condition_not_full_loader, &context->loader_done); &context->condition_not_full_loader, &context->loader_done);
if (current_chunk == NULL) { if (current_chunk == NULL) {
if (atomic_load(&context->cancelled)) {
pipeline_cancel(context);
disconnect_transfer_client(client);
mark_sender_done(context);
protocol_session_unbind();
return thrd_error;
}
if (context->config->use_delete) { if (context->config->use_delete) {
if (send_delete_manifest(client->file_descriptor, context->manifest) != 0) if (send_delete_manifest(client->file_descriptor, context->manifest) != 0)
goto send_fail; goto send_fail;
} }
bool ok = finalize_transfer(client); if (!send_status(client->file_descriptor, STATUS_FINISHED))
disconnect_transfer_client(client); goto send_fail;
mark_sender_done(context); Status s;
protocol_session_unbind(); int ok = receive_status(client->file_descriptor, &s) && s == STATUS_OK;
client_disconnect(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:
pipeline_cancel(context); pipeline_cancel(context);
disconnect_transfer_client(client); client_disconnect(client);
mark_sender_done(context); client_delete(client);
protocol_session_unbind(); 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");
chunk_destroy(current_chunk); chunk_destroy(current_chunk);
pipeline_cancel(context); pipeline_cancel(context);
disconnect_transfer_client(client); client_disconnect(client);
mark_sender_done(context); client_delete(client);
protocol_session_unbind(); 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) { if (context->config->show_progress) {
@@ -449,9 +393,13 @@ static int send_chunks_multithreaded(void* pipeline_context) {
static int scan_directory_multithreaded(void* pipeline_context) { static int scan_directory_multithreaded(void* pipeline_context) {
PipelineContextSender* context = (PipelineContextSender*)pipeline_context; PipelineContextSender* context = (PipelineContextSender*)pipeline_context;
ScannerOptions options = scanner_options_from_config(context->config, 4); ParallelScanner* scanner = parallel_scanner_create(
ParallelScanner* scanner = context->config->send_directory, context->config->use_metadata, context->config->chunk_size,
parallel_scanner_create_with_options(context->config->send_directory, &options); context->config->exclude_patterns, context->config->exclude_count,
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->copy_links, context->config->safe_links, context->config->copy_unsafe_links,
context->config->checksum);
Chunk* current_chunk; Chunk* current_chunk;
if (scanner == NULL) { if (scanner == NULL) {
@@ -462,14 +410,28 @@ static int scan_directory_multithreaded(void* pipeline_context) {
while ((current_chunk = parallel_scanner_next(scanner)) != NULL) { while ((current_chunk = parallel_scanner_next(scanner)) != NULL) {
if (context->config->use_delete) { if (context->config->use_delete) {
mtx_lock(&context->mutex_scanner); mtx_lock(&context->mutex_scanner);
bool manifest_ok = add_chunk_to_manifest(context->manifest, current_chunk); for (int i = 0; i < current_chunk->element_count; i++) {
mtx_unlock(&context->mutex_scanner); const char* p = current_chunk->items[i]->path;
if (!manifest_ok) { if (*p == '/')
pipeline_cancel(context); p++;
chunk_destroy(current_chunk); char* manifest_entry = str_dup(p);
parallel_scanner_destroy(scanner); if (!manifest_entry) {
return thrd_error; log_message(LOG_LEVEL_ERROR, "Failed to allocate manifest entry");
mtx_unlock(&context->mutex_scanner);
pipeline_cancel(context);
parallel_scanner_destroy(scanner);
return thrd_error;
}
if (!array_list_add(context->manifest, manifest_entry)) {
free(manifest_entry);
mtx_unlock(&context->mutex_scanner);
pipeline_cancel(context);
chunk_destroy(current_chunk);
parallel_scanner_destroy(scanner);
return thrd_error;
}
} }
mtx_unlock(&context->mutex_scanner);
} }
if (!queue_enqueue_multithreaded_cancel( if (!queue_enqueue_multithreaded_cancel(
context->queue_scanner, current_chunk, &context->mutex_scanner, context->queue_scanner, current_chunk, &context->mutex_scanner,
@@ -519,10 +481,9 @@ static int load_files_multithreaded(void* pipeline_context) {
if (f->data->size > STREAM_THRESHOLD) if (f->data->size > STREAM_THRESHOLD)
continue; continue;
if (!file_load_data(f)) { if (!file_load_data(f)) {
log_message(LOG_LEVEL_ERROR, "Failed to load file data"); log_message(LOG_LEVEL_ERROR, "Failed to load file data, skipping");
chunk_destroy(chunk); file_destroy(f);
pipeline_cancel(context); chunk->items[i] = NULL;
return thrd_error;
} }
} }
} }
@@ -531,7 +492,9 @@ static int load_files_multithreaded(void* pipeline_context) {
&context->condition_not_full_loader, &context->condition_not_full_loader,
&context->cancelled)) { &context->cancelled)) {
chunk_destroy(chunk); chunk_destroy(chunk);
pipeline_cancel(context); atomic_store(&context->cancelled, true);
cnd_broadcast(&context->condition_not_full_loader);
cnd_broadcast(&context->condition_not_empty_loader);
return thrd_error; return thrd_error;
} }
} }
@@ -577,37 +540,61 @@ 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);
Client* client = connect_transfer_client(config); Client* client;
if (!client) { if (config->transport == TRANSPORT_SSH) {
if (config->transport == TRANSPORT_TCP) if (config->use_sendfile) {
fprintf(stderr, "Error: could not connect to server%s\n", config->use_tls ? " via TLS" : ""); fprintf(stderr, "Error: -f/--sendfile is not supported with SSH transport\n");
return 1; return 1;
}
client =
client_connect_ssh(config->ssh_destination, config->ssh_port, config->fastsync_server_path);
if (!client)
return 1;
} else if (config->use_tls) {
client = client_create();
if (!client || !client_connect_tls(client, config->server_host, config->server_port,
config->tls_cert, config->tls_key, config->tls_ca)) {
if (client) {
client_disconnect(client);
client_delete(client);
}
fprintf(stderr, "Error: could not connect to server via TLS\n");
return 1;
}
} else {
client = client_create();
if (!client || !client_connect(client, config->server_host, config->server_port)) {
if (client) {
client_disconnect(client);
client_delete(client);
}
fprintf(stderr, "Error: could not connect to server\n");
return 1;
}
} }
ProtocolSession session;
protocol_session_init(&session, client->file_descriptor, client->file_descriptor);
protocol_session_set_ssl(&session, (SSL*)client->ssl);
protocol_session_bind(&session);
if (!config_send(client->file_descriptor, config)) { if (!config_send(client->file_descriptor, config)) {
disconnect_transfer_client(client); client_disconnect(client);
protocol_session_unbind(); client_delete(client);
return 1; return 1;
} }
ScannerOptions scanner_options = scanner_options_from_config(config, 0); DirectoryScanner* scanner = directory_scanner_create(
DirectoryScanner* scanner = config->send_directory, config->use_metadata, config->chunk_size, config->exclude_patterns,
directory_scanner_create_with_options(config->send_directory, &scanner_options); 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->safe_links, config->copy_unsafe_links, config->checksum);
Chunk* current_chunk; Chunk* current_chunk;
unsigned long long total_bytes = 0; unsigned long long total_bytes = 0;
int total_files = 0; int total_files = 0;
time_t last_progress = 0; time_t last_progress = 0;
time_t start = time(NULL); time_t start = time(NULL);
ArrayList* manifest = create_transfer_manifest(config); ArrayList* manifest = config->use_delete ? array_list_create(free) : NULL;
if (!scanner || (config->use_delete && !manifest)) { if (!scanner || (config->use_delete && !manifest)) {
if (scanner) if (scanner)
directory_scanner_destroy(scanner); directory_scanner_destroy(scanner);
if (manifest) if (manifest)
array_list_delete(manifest); array_list_delete(manifest);
disconnect_transfer_client(client); client_disconnect(client);
protocol_session_unbind(); client_delete(client);
return 1; return 1;
} }
while ((current_chunk = directory_scanner_next(scanner)) != NULL) { while ((current_chunk = directory_scanner_next(scanner)) != NULL) {
@@ -615,10 +602,30 @@ int send_files(Config* config) {
for (int i = 0; i < current_chunk->element_count; i++) { for (int i = 0; i < current_chunk->element_count; i++) {
chunk_bytes += current_chunk->items[i]->data->size; chunk_bytes += current_chunk->items[i]->data->size;
total_files++; total_files++;
} if (manifest) {
if (!add_chunk_to_manifest(manifest, current_chunk)) { const char* p = current_chunk->items[i]->path;
chunk_destroy(current_chunk); if (*p == '/')
goto send_fail; p++;
char* manifest_entry = str_dup(p);
if (!manifest_entry) {
log_message(LOG_LEVEL_ERROR, "Failed to allocate manifest entry");
chunk_destroy(current_chunk);
array_list_delete(manifest);
directory_scanner_destroy(scanner);
client_disconnect(client);
client_delete(client);
return 1;
}
if (!array_list_add(manifest, manifest_entry)) {
free(manifest_entry);
chunk_destroy(current_chunk);
array_list_delete(manifest);
directory_scanner_destroy(scanner);
client_disconnect(client);
client_delete(client);
return 1;
}
}
} }
if (!config->use_sendfile) { if (!config->use_sendfile) {
for (int i = 0; i < current_chunk->element_count; i++) { for (int i = 0; i < current_chunk->element_count; i++) {
@@ -627,8 +634,7 @@ int send_files(Config* config) {
continue; continue;
if (!file_load_data(f)) { if (!file_load_data(f)) {
log_message(LOG_LEVEL_ERROR, "Failed to load file data"); log_message(LOG_LEVEL_ERROR, "Failed to load file data");
chunk_destroy(current_chunk); continue;
goto send_fail;
} }
} }
} }
@@ -658,13 +664,15 @@ int send_files(Config* config) {
if (config->use_delete) { if (config->use_delete) {
if (send_delete_manifest(client->file_descriptor, manifest) != 0) { if (send_delete_manifest(client->file_descriptor, manifest) != 0) {
array_list_delete(manifest); array_list_delete(manifest);
manifest = NULL;
goto send_fail; goto send_fail;
} }
array_list_delete(manifest); array_list_delete(manifest);
manifest = NULL; manifest = NULL;
} }
bool ok = finalize_transfer(client); if (!send_status(client->file_descriptor, STATUS_FINISHED))
goto send_fail;
Status s;
int ok = receive_status(client->file_descriptor, &s) && s == STATUS_OK;
double elapsed_total = difftime(time(NULL), start); double elapsed_total = difftime(time(NULL), start);
if (config->show_progress) { if (config->show_progress) {
double rate = elapsed_total > 0 ? total_bytes / (1048576.0 * elapsed_total) : 0; double rate = elapsed_total > 0 ? total_bytes / (1048576.0 * elapsed_total) : 0;
@@ -676,16 +684,16 @@ int send_files(Config* config) {
rate); rate);
} }
directory_scanner_destroy(scanner); directory_scanner_destroy(scanner);
disconnect_transfer_client(client); client_disconnect(client);
protocol_session_unbind(); client_delete(client);
return ok ? 0 : 1; return ok ? 0 : 1;
send_fail: send_fail:
if (manifest) if (manifest)
array_list_delete(manifest); array_list_delete(manifest);
directory_scanner_destroy(scanner); directory_scanner_destroy(scanner);
disconnect_transfer_client(client); client_disconnect(client);
protocol_session_unbind(); client_delete(client);
return 1; return 1;
} }
@@ -721,11 +729,7 @@ int send_files_multithreaded(Config* config) {
return 1; return 1;
} }
if (config->use_delete) if (config->use_delete)
context->manifest = create_transfer_manifest(config); context->manifest = array_list_create(free);
if (config->use_delete && !context->manifest) {
pipeline_context_sender_destroy(context);
return 1;
}
thrd_t scanner, loader, sender; thrd_t scanner, loader, sender;
bool scanner_created = false; bool scanner_created = false;
-50
View File
@@ -1,50 +0,0 @@
#include "client_validation.h"
#include "usage.h"
#include <stdio.h>
/* Validate config after parsing. Returns true if valid. */
bool validate_config(const Config* config) {
if (!config->send_directory || !config->receive_root_directory) {
fprintf(stderr, "Error: source and destination directories are required\n");
print_usage();
return false;
}
if (config->use_sendfile && (config->use_chunk_serialization || config->use_compression)) {
fprintf(stderr, "Error: -f/--sendfile cannot be combined with -c (compression) or -s (chunk "
"serialization)\n");
return false;
}
if (config->transport == TRANSPORT_SSH && config->use_sendfile) {
fprintf(stderr, "Error: -f/--sendfile is not supported with SSH transport\n");
return false;
}
if (config->use_incremental && config->use_chunk_serialization) {
fprintf(stderr, "Error: --incremental is not supported with -s (chunk serialization)\n");
return false;
}
if (config->use_delta && !config->use_incremental) {
fprintf(stderr, "Error: --delta requires --incremental\n");
return false;
}
if (config->use_delta && config->use_chunk_serialization) {
fprintf(stderr, "Error: --delta cannot be combined with -s (chunk serialization)\n");
return false;
}
if (config->use_delta && config->use_sendfile) {
fprintf(stderr, "Error: --delta cannot be combined with -f (sendfile)\n");
return false;
}
if (config->append || config->append_verify) {
fprintf(
stderr,
"Error: --append and --append-verify are not supported yet; refusing to ignore option\n");
return false;
}
if (config->use_tls) {
if (!config->tls_cert || !config->tls_key) {
fprintf(stderr, "Error: --tls requires --cert and --key\n");
return false;
}
}
return true;
}
-9
View File
@@ -1,9 +0,0 @@
#ifndef CLIENT_VALIDATION_H
#define CLIENT_VALIDATION_H
#include "config.h"
#include <stdbool.h>
bool validate_config(const Config* config);
#endif
+254 -185
View File
@@ -52,84 +52,13 @@ static bool safe_relative_link(const char* source_root, const char* containing_d
return safe; return safe;
} }
typedef struct { DirectoryScanner* directory_scanner_create(const char* root_directory, bool use_metadata,
char* path; unsigned long long chunk_size, char** exclude_patterns,
struct stat stats; int exclude_count, char** include_patterns,
bool is_directory; int include_count, unsigned long long max_size,
} ScannerEntry; unsigned long long min_size, int max_depth,
bool follow_symlinks, bool copy_links, bool safe_links,
/* Inspect symlinks, resolve the entry type, and apply file filters once for both scanners. */ bool copy_unsafe_links, bool checksum) {
static int scanner_inspect_entry(const ScannerOptions* options, const char* source_root,
const char* containing_dir, const char* name,
ScannerEntry* entry) {
entry->path = path_cat(containing_dir, name);
if (!entry->path)
return -1;
struct stat link_stats;
if (lstat(entry->path, &link_stats) != 0) {
free(entry->path);
return 0;
}
bool is_symlink = S_ISLNK(link_stats.st_mode);
if (is_symlink && !options->follow_symlinks && !options->copy_links && !options->safe_links &&
!options->copy_unsafe_links)
goto skip;
if (is_symlink && options->safe_links) {
char link_target[4096];
ssize_t length = readlink(entry->path, link_target, sizeof(link_target) - 1);
if (length < 0)
goto skip;
link_target[length] = '\0';
if (link_target[0] == '/' || !safe_relative_link(source_root, containing_dir, link_target))
goto skip;
}
if (is_symlink && options->copy_unsafe_links && !options->copy_links) {
char link_target[4096];
ssize_t length = readlink(entry->path, link_target, sizeof(link_target) - 1);
if (length < 0)
goto skip;
link_target[length] = '\0';
if (link_target[0] != '/')
goto skip;
}
if (is_symlink && options->follow_symlinks && !options->copy_links)
entry->stats = link_stats;
else if (stat(entry->path, &entry->stats) != 0)
goto skip;
entry->is_directory = S_ISDIR(entry->stats.st_mode);
if (entry->is_directory)
return 1;
for (int i = 0; i < options->exclude_count; i++)
if (glob_match(options->exclude_patterns[i], name))
goto skip;
if (options->include_count > 0) {
bool included = false;
for (int i = 0; i < options->include_count; i++)
if (glob_match(options->include_patterns[i], name))
included = true;
if (!included)
goto skip;
}
if ((options->max_size > 0 && (unsigned long long)entry->stats.st_size > options->max_size) ||
(options->min_size > 0 && (unsigned long long)entry->stats.st_size < options->min_size))
goto skip;
return 1;
skip:
free(entry->path);
entry->path = NULL;
return 0;
}
DirectoryScanner* directory_scanner_create_with_options(const char* root_directory,
const ScannerOptions* options) {
if (!root_directory || !options)
return NULL;
DirectoryScanner* scanner = calloc(1, sizeof(DirectoryScanner)); DirectoryScanner* scanner = calloc(1, sizeof(DirectoryScanner));
if (scanner == NULL) if (scanner == NULL)
return NULL; return NULL;
@@ -140,21 +69,21 @@ DirectoryScanner* directory_scanner_create_with_options(const char* root_directo
} }
scanner->current_dir = NULL; scanner->current_dir = NULL;
scanner->current_path = NULL; scanner->current_path = NULL;
scanner->use_metadata = options->use_metadata; scanner->use_metadata = use_metadata;
scanner->chunk_size = options->chunk_size > 0 ? options->chunk_size : DESIRED_CHUNK_SIZE; scanner->chunk_size = chunk_size > 0 ? chunk_size : DESIRED_CHUNK_SIZE;
scanner->exclude_patterns = options->exclude_patterns; scanner->exclude_patterns = exclude_patterns;
scanner->exclude_count = options->exclude_count; scanner->exclude_count = exclude_count;
scanner->include_patterns = options->include_patterns; scanner->include_patterns = include_patterns;
scanner->include_count = options->include_count; scanner->include_count = include_count;
scanner->max_size = options->max_size; scanner->max_size = max_size;
scanner->min_size = options->min_size; scanner->min_size = min_size;
scanner->max_depth = options->max_depth; scanner->max_depth = max_depth;
scanner->current_depth = 0; scanner->current_depth = 0;
scanner->follow_symlinks = options->follow_symlinks; scanner->follow_symlinks = follow_symlinks;
scanner->copy_links = options->copy_links; scanner->copy_links = copy_links;
scanner->safe_links = options->safe_links; scanner->safe_links = safe_links;
scanner->copy_unsafe_links = options->copy_unsafe_links; scanner->copy_unsafe_links = copy_unsafe_links;
scanner->checksum = options->checksum; scanner->checksum = checksum;
scanner->failed = false; scanner->failed = false;
DirEntry* root = dir_entry_create(root_directory, 0); DirEntry* root = dir_entry_create(root_directory, 0);
if (!root) { if (!root) {
@@ -171,20 +100,6 @@ DirectoryScanner* directory_scanner_create_with_options(const char* root_directo
return scanner; return scanner;
} }
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, int max_depth,
bool follow_symlinks, bool copy_links, bool safe_links,
bool copy_unsafe_links, bool checksum) {
ScannerOptions options = {
use_metadata, chunk_size, exclude_patterns, exclude_count, include_patterns,
include_count, max_size, min_size, max_depth, 0,
follow_symlinks, copy_links, safe_links, copy_unsafe_links, checksum};
return directory_scanner_create_with_options(root_directory, &options);
}
void directory_scanner_destroy(DirectoryScanner* scanner) { void directory_scanner_destroy(DirectoryScanner* scanner) {
if (scanner == NULL) if (scanner == NULL)
return; return;
@@ -252,7 +167,7 @@ Chunk* directory_scanner_next(DirectoryScanner* scanner) {
break; break;
} }
const struct dirent* entry = readdir(scanner->current_dir); struct dirent* entry = readdir(scanner->current_dir);
if (entry == NULL) { if (entry == NULL) {
closedir(scanner->current_dir); closedir(scanner->current_dir);
scanner->current_dir = NULL; scanner->current_dir = NULL;
@@ -264,27 +179,67 @@ Chunk* directory_scanner_next(DirectoryScanner* scanner) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue; continue;
ScannerOptions options = {scanner->use_metadata, scanner->chunk_size, char* cur_path = path_cat(scanner->current_path, entry->d_name);
scanner->exclude_patterns, scanner->exclude_count, if (!cur_path) {
scanner->include_patterns, scanner->include_count,
scanner->max_size, scanner->min_size,
scanner->max_depth, 0,
scanner->follow_symlinks, scanner->copy_links,
scanner->safe_links, scanner->copy_unsafe_links,
scanner->checksum};
ScannerEntry inspected;
int inspection = scanner_inspect_entry(&options, scanner->current_path, scanner->current_path,
entry->d_name, &inspected);
if (inspection < 0) {
scanner->failed = true; scanner->failed = true;
break; break;
} }
if (inspection == 0) struct stat stats;
struct stat lstats;
bool is_symlink = false;
if (lstat(cur_path, &lstats) != 0) {
free(cur_path);
continue; continue;
char* cur_path = inspected.path; }
struct stat stats = inspected.stats; is_symlink = S_ISLNK(lstats.st_mode);
if (inspected.is_directory) { if (is_symlink && !scanner->follow_symlinks && !scanner->copy_links && !scanner->safe_links &&
!scanner->copy_unsafe_links) {
free(cur_path);
continue;
}
if (is_symlink && scanner->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] = '\0';
if (link_target[0] == '/' ||
!safe_relative_link(scanner->current_path, scanner->current_path, link_target)) {
free(cur_path);
continue;
}
}
if (is_symlink && scanner->copy_unsafe_links && !scanner->copy_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] = '\0';
bool unsafe = (link_target[0] == '/');
if (!unsafe) {
free(cur_path);
continue;
}
}
bool use_lstat = is_symlink && scanner->follow_symlinks && !scanner->copy_links;
if (use_lstat) {
stats = lstats;
} else {
if (stat(cur_path, &stats) != 0) {
free(cur_path);
continue;
}
}
if (S_ISDIR(stats.st_mode)) {
int next_depth = scanner->current_depth + 1; int next_depth = scanner->current_depth + 1;
if (scanner->max_depth <= 0 || next_depth < scanner->max_depth) { if (scanner->max_depth <= 0 || next_depth < scanner->max_depth) {
DirEntry* de = dir_entry_create(cur_path, next_depth); DirEntry* de = dir_entry_create(cur_path, next_depth);
@@ -299,6 +254,38 @@ Chunk* directory_scanner_next(DirectoryScanner* scanner) {
free(cur_path); free(cur_path);
continue; continue;
} }
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;
}
}
if ((scanner->max_size > 0 && (unsigned long long)stats.st_size > scanner->max_size) ||
(scanner->min_size > 0 && (unsigned long long)stats.st_size < scanner->min_size)) {
free(cur_path);
continue;
}
File* file = file_create(cur_path); File* file = file_create(cur_path);
if (file == NULL) { if (file == NULL) {
free(cur_path); free(cur_path);
@@ -349,13 +336,29 @@ typedef struct {
ParallelScanner* ps; ParallelScanner* ps;
char** dirs; char** dirs;
int dir_count; int dir_count;
ScannerOptions options; 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;
bool follow_symlinks;
bool copy_links;
bool safe_links;
bool copy_unsafe_links;
bool checksum;
} ParallelWorkerArg; } ParallelWorkerArg;
static int parallel_worker_thread(void* arg) { static int parallel_worker_thread(void* arg) {
ParallelWorkerArg* wa = (ParallelWorkerArg*)arg; ParallelWorkerArg* wa = (ParallelWorkerArg*)arg;
for (int i = 0; i < wa->dir_count; i++) { for (int i = 0; i < wa->dir_count; i++) {
DirectoryScanner* ds = directory_scanner_create_with_options(wa->dirs[i], &wa->options); 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,
wa->follow_symlinks, wa->copy_links, wa->safe_links, wa->copy_unsafe_links, wa->checksum);
if (!ds) { if (!ds) {
mtx_lock(&wa->ps->result_mutex); mtx_lock(&wa->ps->result_mutex);
wa->ps->failed = true; wa->ps->failed = true;
@@ -363,8 +366,6 @@ static int parallel_worker_thread(void* arg) {
cnd_broadcast(&wa->ps->result_not_empty); cnd_broadcast(&wa->ps->result_not_empty);
cnd_broadcast(&wa->ps->result_not_full); cnd_broadcast(&wa->ps->result_not_full);
mtx_unlock(&wa->ps->result_mutex); mtx_unlock(&wa->ps->result_mutex);
for (int j = i; j < wa->dir_count; j++)
free(wa->dirs[j]);
break; break;
} }
Chunk* chunk; Chunk* chunk;
@@ -400,22 +401,13 @@ static int parallel_worker_thread(void* arg) {
return thrd_success; return thrd_success;
} }
static void parallel_scanner_creation_failed(ParallelScanner* ps) { ParallelScanner* parallel_scanner_create(char* root_directory, bool use_metadata,
mtx_lock(&ps->result_mutex); unsigned long long chunk_size, char** exclude_patterns,
ps->failed = true; int exclude_count, char** include_patterns,
atomic_store(&ps->cancelled, true); int include_count, unsigned long long max_size,
ps->expected_threads = ps->created_threads; unsigned long long min_size, int max_depth,
if (ps->completed >= ps->expected_threads) int num_threads, bool follow_symlinks, bool copy_links,
ps->done = true; bool safe_links, bool copy_unsafe_links, bool checksum) {
cnd_broadcast(&ps->result_not_empty);
cnd_broadcast(&ps->result_not_full);
mtx_unlock(&ps->result_mutex);
}
ParallelScanner* parallel_scanner_create_with_options(const char* root_directory,
const ScannerOptions* options) {
if (!root_directory || !options)
return NULL;
ParallelScanner* ps = calloc(1, sizeof(ParallelScanner)); ParallelScanner* ps = calloc(1, sizeof(ParallelScanner));
if (!ps) if (!ps)
return NULL; return NULL;
@@ -468,27 +460,105 @@ ParallelScanner* parallel_scanner_create_with_options(const char* root_directory
parallel_scanner_destroy(ps); parallel_scanner_destroy(ps);
return NULL; return NULL;
} }
const struct dirent* entry; struct dirent* entry;
while ((entry = readdir(dir)) != NULL) { while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue; continue;
ScannerEntry inspected; char* cur_path = path_cat(root_directory, entry->d_name);
int inspection = if (!cur_path)
scanner_inspect_entry(options, root_directory, root_directory, entry->d_name, &inspected); continue;
if (inspection < 0) { struct stat lstats;
ps->failed = true; if (lstat(cur_path, &lstats) != 0) {
free(cur_path);
continue; continue;
} }
if (inspection == 0) 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; continue;
char* cur_path = inspected.path; }
struct stat st = inspected.stats;
if (inspected.is_directory) { // --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] = 0;
if (link_target[0] == '/' ||
!safe_relative_link(root_directory, root_directory, link_target)) {
free(cur_path);
continue;
}
}
// --copy-unsafe-links (without --copy-links): only copy absolute symlinks.
if (is_symlink && copy_unsafe_links && !copy_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] = 0;
bool unsafe = (link_target[0] == '/');
if (!unsafe) {
free(cur_path);
continue;
}
}
// Determine whether to use lstat or stat results for the entry.
struct stat st;
bool use_lstat_res = is_symlink && follow_symlinks && !copy_links;
if (use_lstat_res) {
st = lstats;
} else {
if (stat(cur_path, &st) != 0) {
free(cur_path);
continue;
}
}
if (S_ISDIR(st.st_mode)) {
if (!array_list_add(subdirs, cur_path)) { if (!array_list_add(subdirs, cur_path)) {
free(cur_path); free(cur_path);
ps->failed = true; ps->failed = true;
} }
} else { } 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); File* file = file_create(cur_path);
free(cur_path); free(cur_path);
if (!file) { if (!file) {
@@ -496,9 +566,9 @@ ParallelScanner* parallel_scanner_create_with_options(const char* root_directory
continue; continue;
} }
file->data->size = st.st_size; file->data->size = st.st_size;
if (options->use_metadata) if (use_metadata)
file->metadata = file_metadata_create(&st); file->metadata = file_metadata_create(&st);
if (options->use_metadata && !file->metadata) { if (use_metadata && !file->metadata) {
file_destroy(file); file_destroy(file);
ps->failed = true; ps->failed = true;
continue; continue;
@@ -511,7 +581,7 @@ ParallelScanner* parallel_scanner_create_with_options(const char* root_directory
} }
closedir(dir); closedir(dir);
unsigned long long cs = options->chunk_size > 0 ? options->chunk_size : DESIRED_CHUNK_SIZE; unsigned long long cs = chunk_size > 0 ? chunk_size : DESIRED_CHUNK_SIZE;
if (root_files->size > 0) { if (root_files->size > 0) {
ArrayList* batch = array_list_create(NULL); ArrayList* batch = array_list_create(NULL);
if (!batch) { if (!batch) {
@@ -534,6 +604,7 @@ ParallelScanner* parallel_scanner_create_with_options(const char* root_directory
void** items = array_list_to_array(batch); void** items = array_list_to_array(batch);
if (!items) { if (!items) {
ps->failed = true; ps->failed = true;
batch->item_destroyer = file_destroy;
array_list_delete(batch); array_list_delete(batch);
batch = NULL; batch = NULL;
break; break;
@@ -542,13 +613,11 @@ ParallelScanner* parallel_scanner_create_with_options(const char* root_directory
free(items); free(items);
if (!c) { if (!c) {
ps->failed = true; ps->failed = true;
batch->item_destroyer = file_destroy;
array_list_delete(batch); array_list_delete(batch);
batch = NULL; batch = NULL;
break; break;
} }
int batch_start = i - batch->size + 1;
for (int j = batch_start; j <= i; j++)
root_files->items[j] = NULL;
batch->item_destroyer = NULL; batch->item_destroyer = NULL;
array_list_delete(batch); array_list_delete(batch);
batch = NULL; batch = NULL;
@@ -575,10 +644,11 @@ ParallelScanner* parallel_scanner_create_with_options(const char* root_directory
array_list_delete(batch); array_list_delete(batch);
} }
ps->initial_chunk = first; ps->initial_chunk = first;
root_files->item_destroyer = NULL;
} }
array_list_delete(root_files); array_list_delete(root_files);
int n = options->num_threads > 0 ? options->num_threads : 4; int n = num_threads > 0 ? num_threads : 4;
if (n > subdirs->size) if (n > subdirs->size)
n = subdirs->size > 0 ? subdirs->size : 1; n = subdirs->size > 0 ? subdirs->size : 1;
@@ -601,14 +671,14 @@ ParallelScanner* parallel_scanner_create_with_options(const char* root_directory
break; break;
ParallelWorkerArg* wa = calloc(1, sizeof(ParallelWorkerArg)); ParallelWorkerArg* wa = calloc(1, sizeof(ParallelWorkerArg));
if (!wa) { if (!wa) {
parallel_scanner_creation_failed(ps); ps->failed = true;
break; break;
} }
wa->ps = ps; wa->ps = ps;
wa->dirs = calloc(count, sizeof(char*)); wa->dirs = calloc(count, sizeof(char*));
if (!wa->dirs) { if (!wa->dirs) {
free(wa); free(wa);
parallel_scanner_creation_failed(ps); ps->failed = true;
break; break;
} }
bool dup_ok = true; bool dup_ok = true;
@@ -622,19 +692,37 @@ ParallelScanner* parallel_scanner_create_with_options(const char* root_directory
free(wa->dirs[j]); free(wa->dirs[j]);
free(wa->dirs); free(wa->dirs);
free(wa); free(wa);
parallel_scanner_creation_failed(ps); ps->failed = true;
break; break;
} }
wa->dir_count = count; wa->dir_count = count;
wa->options = *options; wa->use_metadata = use_metadata;
wa->options.chunk_size = cs; 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;
wa->follow_symlinks = follow_symlinks;
wa->copy_links = copy_links;
wa->safe_links = safe_links;
wa->copy_unsafe_links = copy_unsafe_links;
wa->checksum = checksum;
start += count; start += count;
if (thrd_create(&ps->threads[t], parallel_worker_thread, wa) != thrd_success) { if (thrd_create(&ps->threads[t], parallel_worker_thread, wa) != thrd_success) {
for (int j = 0; j < count; j++) for (int j = 0; j < count; j++)
free(wa->dirs[j]); free(wa->dirs[j]);
free(wa->dirs); free(wa->dirs);
free(wa); free(wa);
parallel_scanner_creation_failed(ps); ps->failed = true;
atomic_store(&ps->cancelled, true);
ps->expected_threads = ps->created_threads;
mtx_lock(&ps->result_mutex);
cnd_broadcast(&ps->result_not_empty);
cnd_broadcast(&ps->result_not_full);
mtx_unlock(&ps->result_mutex);
break; break;
} }
ps->num_threads++; ps->num_threads++;
@@ -645,20 +733,6 @@ ParallelScanner* parallel_scanner_create_with_options(const char* root_directory
return ps; return ps;
} }
ParallelScanner* parallel_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, int max_depth,
int num_threads, bool follow_symlinks, bool copy_links,
bool safe_links, bool copy_unsafe_links, bool checksum) {
ScannerOptions options = {use_metadata, chunk_size, exclude_patterns, exclude_count,
include_patterns, include_count, max_size, min_size,
max_depth, num_threads, follow_symlinks, copy_links,
safe_links, copy_unsafe_links, checksum};
return parallel_scanner_create_with_options(root_directory, &options);
}
Chunk* parallel_scanner_next(ParallelScanner* ps) { Chunk* parallel_scanner_next(ParallelScanner* ps) {
if (ps->initial_chunk) { if (ps->initial_chunk) {
Chunk* c = ps->initial_chunk; Chunk* c = ps->initial_chunk;
@@ -667,11 +741,6 @@ Chunk* parallel_scanner_next(ParallelScanner* ps) {
} }
if (ps->num_threads == 0) { if (ps->num_threads == 0) {
mtx_lock(&ps->result_mutex); mtx_lock(&ps->result_mutex);
if (!queue_is_empty(ps->result_queue)) {
Chunk* chunk = queue_dequeue(ps->result_queue);
mtx_unlock(&ps->result_mutex);
return chunk;
}
ps->done = true; ps->done = true;
mtx_unlock(&ps->result_mutex); mtx_unlock(&ps->result_mutex);
return NULL; return NULL;
+1 -23
View File
@@ -8,24 +8,6 @@
#include <threads.h> #include <threads.h>
#include <stdatomic.h> #include <stdatomic.h>
typedef struct {
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;
bool follow_symlinks;
bool copy_links;
bool safe_links;
bool copy_unsafe_links;
bool checksum;
} ScannerOptions;
typedef struct { typedef struct {
Queue* directories; Queue* directories;
DIR* current_dir; DIR* current_dir;
@@ -71,21 +53,17 @@ DirectoryScanner* directory_scanner_create(const char* root_directory, bool use_
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, bool checksum);
DirectoryScanner* directory_scanner_create_with_options(const char* root_directory,
const ScannerOptions* options);
Chunk* directory_scanner_next(DirectoryScanner* scanner); Chunk* directory_scanner_next(DirectoryScanner* scanner);
bool directory_scanner_failed(const DirectoryScanner* scanner); bool directory_scanner_failed(const DirectoryScanner* scanner);
void directory_scanner_destroy(DirectoryScanner* scanner); void directory_scanner_destroy(DirectoryScanner* scanner);
ParallelScanner* parallel_scanner_create(const char* root_directory, bool use_metadata, ParallelScanner* parallel_scanner_create(char* root_directory, bool use_metadata,
unsigned long long chunk_size, char** exclude_patterns, unsigned long long chunk_size, char** exclude_patterns,
int exclude_count, char** include_patterns, int exclude_count, char** include_patterns,
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, bool checksum);
ParallelScanner* parallel_scanner_create_with_options(const char* root_directory,
const ScannerOptions* options);
Chunk* parallel_scanner_next(ParallelScanner* scanner); Chunk* parallel_scanner_next(ParallelScanner* scanner);
bool parallel_scanner_failed(const ParallelScanner* scanner); bool parallel_scanner_failed(const ParallelScanner* scanner);
void parallel_scanner_destroy(ParallelScanner* scanner); void parallel_scanner_destroy(ParallelScanner* scanner);
+2 -3
View File
@@ -1,9 +1,8 @@
#include "usage.h" #include "stdio.h"
#include <stdio.h>
#include <delta.h> #include <delta.h>
#include <chunk.h> #include <chunk.h>
void print_usage(void) { static __attribute__((unused)) void print_usage() {
printf("Usage:\n"); printf("Usage:\n");
printf(" fastsync [options] <source> <destination>\n"); printf(" fastsync [options] <source> <destination>\n");
printf(" fastsync [options] --source-dir <src> --dest-dir <dst>\n"); printf(" fastsync [options] --source-dir <src> --dest-dir <dst>\n");
-6
View File
@@ -1,6 +0,0 @@
#ifndef USAGE_H
#define USAGE_H
void print_usage(void);
#endif
-126
View File
@@ -1,126 +0,0 @@
#include "receiver.h"
#include "chunk.h"
#include "log.h"
#include "protocol.h"
#include "utils.h"
#include <stdlib.h>
#include <sys/stat.h>
static bool receiver_process_chunk(Chunk* chunk, const ReceiverSink* sink) {
for (int i = 0; i < chunk->element_count; i++) {
File* file = chunk->items[i];
chunk->items[i] = NULL;
if (!sink->store_file(file, sink->context)) {
chunk_destroy(chunk);
return false;
}
}
chunk_destroy(chunk);
return true;
}
static bool receiver_process_batch(Config* config, int file_descriptor) {
int count;
if (config->checksum || !receive_int(file_descriptor, &count) || count < 0 ||
count > MAX_MANIFEST_ENTRIES)
return false;
for (int i = 0; i < count; i++) {
char* check_path = receive_str(file_descriptor);
if (!check_path)
return false;
unsigned long long check_size;
long long check_mtime;
if (!receive_n_data(file_descriptor, &check_size, sizeof(check_size)) ||
!receive_n_data(file_descriptor, &check_mtime, sizeof(check_mtime))) {
free(check_path);
return false;
}
if (!utils_valid_batch_path(check_path)) {
free(check_path);
send_status(file_descriptor, STATUS_ERROR);
return false;
}
char* full_path = path_cat(config->receive_root_directory, check_path);
struct stat st;
bool has_old = full_path && lstat(full_path, &st) == 0;
bool match = has_old && (unsigned long long)st.st_size == check_size &&
(long long)st.st_mtime == check_mtime;
bool sent = send_status(file_descriptor, match ? STATUS_OK : STATUS_NEXT);
free(full_path);
free(check_path);
if (!sent)
return false;
}
return true;
}
int receiver_process(Config* config, int file_descriptor, const ReceiverSink* sink) {
Status status;
if (!receive_status(file_descriptor, &status))
return -1;
while (status == STATUS_NEXT || status == STATUS_CHUNK || status == STATUS_CHECK ||
status == STATUS_KEEPALIVE || status == STATUS_ABORT || status == STATUS_CHECK_BATCH) {
if (status == STATUS_KEEPALIVE) {
if (!send_status(file_descriptor, STATUS_KEEPALIVE))
return -1;
goto next;
}
if (status == STATUS_ABORT) {
log_message(LOG_LEVEL_INFO, "Received abort from client, cleaning up");
return -1;
}
if (status == STATUS_CHECK) {
bool skipped;
File* file = receive_incremental_check(file_descriptor, config, &skipped);
if (!skipped && (!file || !sink->store_file(file, sink->context)))
goto receive_error;
} else if (status == STATUS_CHUNK) {
Chunk* chunk = receive_chunk_data(file_descriptor, config);
if (!chunk || !receiver_process_chunk(chunk, sink))
goto receive_error;
} else if (status == STATUS_CHECK_BATCH) {
if (!receiver_process_batch(config, file_descriptor))
return -1;
goto next;
} else {
File* file = file_receive(config, file_descriptor);
if (!file) {
log_message(LOG_LEVEL_ERROR, "Failed to receive file");
goto receive_error;
}
if (!sink->store_file(file, sink->context))
goto receive_error;
}
next:
if (!receive_status(file_descriptor, &status))
goto receive_error;
}
if (status == STATUS_MANIFEST && receive_manifest(file_descriptor, config, &status) != 0)
return -1;
if (status != STATUS_FINISHED) {
log_message(LOG_LEVEL_ERROR, "Did not receive FINISHED Status");
goto receive_error;
}
if (sink->send_success && !send_status(file_descriptor, STATUS_OK))
return -1;
return 0;
receive_error:
if (sink->send_error)
send_status(file_descriptor, STATUS_ERROR);
return -1;
}
static bool receiver_save_file(File* file, void* context) {
Config* config = context;
bool success =
!config->save_to_disk || file_save_to_disk(config->receive_root_directory, file, config);
file_destroy(file);
return success;
}
int receiver_receive_files(Config* config, int file_descriptor) {
ReceiverSink sink = {receiver_save_file, config, true, true};
return receiver_process(config, file_descriptor, &sink);
}
-19
View File
@@ -1,19 +0,0 @@
#ifndef RECEIVER_H
#define RECEIVER_H
#include "config.h"
#include "file.h"
typedef bool (*ReceiverFileSink)(File* file, void* context);
typedef struct {
ReceiverFileSink store_file;
void* context;
bool send_error;
bool send_success;
} ReceiverSink;
int receiver_process(Config* config, int file_descriptor, const ReceiverSink* sink);
int receiver_receive_files(Config* config, int file_descriptor);
#endif
+42 -51
View File
@@ -2,39 +2,38 @@
#include "file.h" #include "file.h"
#include "log.h" #include "log.h"
#include "multiprocessing.h" #include "multiprocessing.h"
#include "protocol.h"
#include "queue.h" #include "queue.h"
#include "receiver.h"
#include "transport_tcp.h" #include "transport_tcp.h"
#include "transport_tls.h" #include "transport_tls.h"
#include "unistd.h" #include "unistd.h"
#include "utils.h" #include "utils.h"
#include <fcntl.h>
#include <limits.h>
#include <signal.h> #include <signal.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <limits.h>
#include <fcntl.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h>
static char* authorized_root; static char* authorized_root;
static int authorized_root_fd = -1; static int authorized_root_fd = -1;
static bool allow_delete; static bool allow_delete;
static void release_authorization(void) {
file_set_authorized_root(-1, NULL);
utils_set_authorized_root_fd(-1);
if (authorized_root_fd >= 0)
close(authorized_root_fd);
authorized_root_fd = -1;
free(authorized_root);
authorized_root = NULL;
}
static bool path_is_within(const char* root, const char* path) { static bool path_is_within(const char* root, const char* path) {
size_t n = strlen(root); size_t n = strlen(root);
return strncmp(root, path, n) == 0 && (path[n] == '\0' || path[n] == '/'); return strncmp(root, path, n) == 0 && (path[n] == '\0' || path[n] == '/');
} }
static bool save_received_file(File* file, void* context) {
Config* config = context;
if (config->save_to_disk && !file_save_to_disk(config->receive_root_directory, file, config))
return false;
file_destroy(file);
return true;
}
static bool __attribute__((unused)) configure_authorization(const char* root) { static bool __attribute__((unused)) configure_authorization(const char* root) {
char resolved[PATH_MAX]; char resolved[PATH_MAX];
if (!root || !realpath(root, resolved)) if (!root || !realpath(root, resolved))
@@ -48,35 +47,27 @@ static bool __attribute__((unused)) configure_authorization(const char* root) {
authorized_root = NULL; authorized_root = NULL;
return false; return false;
} }
if (!file_set_authorized_root(authorized_root_fd, authorized_root)) { file_set_authorized_root(authorized_root_fd, authorized_root);
close(authorized_root_fd);
authorized_root_fd = -1;
free(authorized_root);
authorized_root = NULL;
return false;
}
utils_set_authorized_root_fd(authorized_root_fd); utils_set_authorized_root_fd(authorized_root_fd);
return true; return true;
} }
int receive_files(Config* config, int fd) {
return receive_files_common(config, fd, save_received_file, config, true);
}
void handler(int file_descriptor) { void handler(int file_descriptor) {
SSL* ssl = io_get_ssl(); SSL* ssl = io_get_ssl();
ProtocolSession session;
protocol_session_init(&session, file_descriptor, file_descriptor);
protocol_session_set_ssl(&session, ssl);
protocol_session_bind(&session);
Config* config = config_receive(file_descriptor); Config* config = config_receive(file_descriptor);
if (config == NULL) { if (config == NULL) {
log_message(LOG_LEVEL_ERROR, "Failed to receive config"); log_message(LOG_LEVEL_ERROR, "Failed to receive config");
close(file_descriptor); close(file_descriptor);
protocol_session_unbind();
return; return;
} }
if (!authorized_root) { if (!authorized_root) {
log_message(LOG_LEVEL_ERROR, "No server-side destination root configured"); log_message(LOG_LEVEL_ERROR, "No server-side destination root configured");
config_delete(config); config_delete(config);
close(file_descriptor); close(file_descriptor);
protocol_session_unbind();
return; return;
} }
char resolved_destination[PATH_MAX]; char resolved_destination[PATH_MAX];
@@ -88,7 +79,6 @@ void handler(int file_descriptor) {
free(canonical_destination); free(canonical_destination);
config_delete(config); config_delete(config);
close(file_descriptor); close(file_descriptor);
protocol_session_unbind();
return; return;
} }
if (canonical_destination) if (canonical_destination)
@@ -101,7 +91,6 @@ void handler(int file_descriptor) {
if (!config->receive_root_directory) { if (!config->receive_root_directory) {
config_delete(config); config_delete(config);
close(file_descriptor); close(file_descriptor);
protocol_session_unbind();
return; return;
} }
config->use_delete = config->use_delete && allow_delete; config->use_delete = config->use_delete && allow_delete;
@@ -110,7 +99,6 @@ void handler(int file_descriptor) {
if (q == NULL) { if (q == NULL) {
config_delete(config); config_delete(config);
close(file_descriptor); close(file_descriptor);
protocol_session_unbind();
return; return;
} }
PipelineContextReceiver* context = PipelineContextReceiver* context =
@@ -119,15 +107,14 @@ void handler(int file_descriptor) {
queue_destroy(q); queue_destroy(q);
config_delete(config); config_delete(config);
close(file_descriptor); close(file_descriptor);
protocol_session_unbind();
return; return;
} }
context->session.total_allocated_bytes = session.total_allocated_bytes;
thrd_t receiver, writer; thrd_t receiver, writer;
bool receiver_created = thrd_create(&receiver, receive_thread, context) == thrd_success; bool receiver_created = false;
bool writer_created = false; bool writer_created = false;
receiver_created = (thrd_create(&receiver, receive_thread, context) == thrd_success);
if (receiver_created) if (receiver_created)
writer_created = thrd_create(&writer, write_thread, context) == thrd_success; writer_created = (thrd_create(&writer, write_thread, context) == thrd_success);
if (!receiver_created || !writer_created) { if (!receiver_created || !writer_created) {
perror("Error creating Threads"); perror("Error creating Threads");
if (receiver_created) { if (receiver_created) {
@@ -144,23 +131,22 @@ void handler(int file_descriptor) {
if (writer_created) if (writer_created)
thrd_join(writer, NULL); thrd_join(writer, NULL);
pipeline_context_receiver_destroy(context); pipeline_context_receiver_destroy(context);
protocol_session_unbind();
return; return;
} }
int receiver_result; int receiver_result;
int writer_result; int writer_result;
thrd_join(receiver, &receiver_result); thrd_join(receiver, &receiver_result);
thrd_join(writer, &writer_result); thrd_join(writer, &writer_result);
send_status(file_descriptor, receiver_result == thrd_success && writer_result == thrd_success if (receiver_result == thrd_success && writer_result == thrd_success)
? STATUS_OK send_status(file_descriptor, STATUS_OK);
: STATUS_ERROR); else
send_status(file_descriptor, STATUS_ERROR);
pipeline_context_receiver_destroy(context); pipeline_context_receiver_destroy(context);
} else { } else {
if (receiver_receive_files(config, file_descriptor) != 0) if (receive_files(config, file_descriptor) != 0)
log_message(LOG_LEVEL_ERROR, "Transfer failed"); log_message(LOG_LEVEL_ERROR, "Transfer failed");
config_delete(config); config_delete(config);
} }
protocol_session_unbind();
close(file_descriptor); close(file_descriptor);
} }
@@ -169,14 +155,16 @@ static Server* g_server = NULL;
static void cleanup(int sig) { static void cleanup(int sig) {
(void)sig; (void)sig;
if (g_server) if (g_server) {
server_delete(&g_server); server_delete(&g_server);
}
_exit(0); _exit(0);
} }
static void print_server_usage(void) { static void print_server_usage(void) {
printf("FastSync Server\n"); printf("FastSync Server\n");
printf("Usage: fastsync-server [options]\n\n"); printf("Usage: fastsync-server [options]\n");
printf("\n");
printf("Options:\n"); printf("Options:\n");
printf(" --stdio Run in stdio mode (SSH transport)\n"); printf(" --stdio Run in stdio mode (SSH transport)\n");
printf(" -p <port> TCP port (default: 8080, range: 1-65535)\n"); printf(" -p <port> TCP port (default: 8080, range: 1-65535)\n");
@@ -192,7 +180,9 @@ static void print_server_usage(void) {
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
bool use_tls = false; bool use_tls = false;
char *tls_cert = NULL, *tls_key = NULL, *tls_ca = NULL; char* tls_cert = NULL;
char* tls_key = NULL;
char* tls_ca = NULL;
int port = 8080; int port = 8080;
const char* destination_root = "."; const char* destination_root = ".";
bool stdio_mode = false; bool stdio_mode = false;
@@ -232,8 +222,11 @@ int main(int argc, char* argv[]) {
return 1; return 1;
} }
} }
if (tls_ca && !use_tls)
if (tls_ca && !use_tls) {
log_message(LOG_LEVEL_WARNING, "--ca has no effect without --tls"); log_message(LOG_LEVEL_WARNING, "--ca has no effect without --tls");
}
signal(SIGINT, cleanup); signal(SIGINT, cleanup);
signal(SIGTERM, cleanup); signal(SIGTERM, cleanup);
if (!configure_authorization(destination_root)) { if (!configure_authorization(destination_root)) {
@@ -243,35 +236,33 @@ int main(int argc, char* argv[]) {
if (stdio_mode) { if (stdio_mode) {
io_set_fds(STDIN_FILENO, STDOUT_FILENO); io_set_fds(STDIN_FILENO, STDOUT_FILENO);
handler(STDIN_FILENO); handler(STDIN_FILENO);
release_authorization(); file_set_authorized_root(-1, NULL);
utils_set_authorized_root_fd(-1);
close(authorized_root_fd);
free(authorized_root);
return 0; return 0;
} }
g_server = server_create(port); g_server = server_create(port);
if (!g_server) { if (g_server == NULL) {
log_message(LOG_LEVEL_ERROR, "Failed to create server"); log_message(LOG_LEVEL_ERROR, "Failed to create server");
release_authorization();
return 1; return 1;
} }
if (use_tls) { if (use_tls) {
if (!tls_cert || !tls_key) { if (!tls_cert || !tls_key) {
fprintf(stderr, "Error: --tls requires --cert and --key\n"); fprintf(stderr, "Error: --tls requires --cert and --key\n");
server_delete(&g_server); server_delete(&g_server);
release_authorization();
return 1; return 1;
} }
tls_global_init(); tls_global_init();
if (!server_create_tls(g_server, tls_cert, tls_key, tls_ca)) { if (!server_create_tls(g_server, tls_cert, tls_key, tls_ca)) {
log_message(LOG_LEVEL_ERROR, "Failed to set up TLS"); log_message(LOG_LEVEL_ERROR, "Failed to set up TLS");
server_delete(&g_server); server_delete(&g_server);
release_authorization();
return 1; return 1;
} }
server_listen_tls(g_server, handler); server_listen_tls(g_server, handler);
} else { } else {
server_listen(g_server, handler); server_listen(g_server, handler);
} }
server_delete(&g_server);
release_authorization();
return 0; return 0;
} }
#endif #endif /* !FASTSYNC_SERVER_AS_LIB */
+3 -42
View File
@@ -1,5 +1,4 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@@ -15,7 +14,6 @@
/* Maximum individual file data size within a chunk (64 MB) */ /* Maximum individual file data size within a chunk (64 MB) */
#define MAX_FILE_DATA_SIZE (64ULL * 1024 * 1024) #define MAX_FILE_DATA_SIZE (64ULL * 1024 * 1024)
#define MAX_CHUNK_FILES (1024 * 1024)
Chunk* chunk_create(File** items, int element_count) { Chunk* chunk_create(File** items, int element_count) {
Chunk* chunk = (Chunk*)malloc(sizeof(Chunk)); Chunk* chunk = (Chunk*)malloc(sizeof(Chunk));
@@ -94,11 +92,6 @@ Chunk* chunk_deserialize(Data* data, bool use_metadata) {
size_t remaining_size = data->size; size_t remaining_size = data->size;
while (remaining_size > 0) { while (remaining_size > 0) {
if (files->size >= MAX_CHUNK_FILES) {
log_message(LOG_LEVEL_ERROR, "Chunk contains too many files");
array_list_delete(files);
return NULL;
}
if (remaining_size < sizeof(size_t)) { if (remaining_size < sizeof(size_t)) {
log_message(LOG_LEVEL_ERROR, "Invalid chunk format: not enough data for path length"); log_message(LOG_LEVEL_ERROR, "Invalid chunk format: not enough data for path length");
array_list_delete(files); array_list_delete(files);
@@ -110,7 +103,7 @@ Chunk* chunk_deserialize(Data* data, bool use_metadata) {
data_pointer += sizeof(size_t); data_pointer += sizeof(size_t);
remaining_size -= sizeof(size_t); remaining_size -= sizeof(size_t);
if (path_len > SIZE_MAX - 1 || remaining_size < path_len) { if (remaining_size < path_len) {
log_message(LOG_LEVEL_ERROR, "Invalid chunk format: not enough data for path"); log_message(LOG_LEVEL_ERROR, "Invalid chunk format: not enough data for path");
array_list_delete(files); array_list_delete(files);
return NULL; return NULL;
@@ -129,15 +122,10 @@ Chunk* chunk_deserialize(Data* data, bool use_metadata) {
File* file = file_create(path); File* file = file_create(path);
free(path); free(path);
if (file == NULL) {
array_list_delete(files);
return NULL;
}
if (use_metadata) { if (use_metadata) {
if (remaining_size < sizeof(int)) { if (remaining_size < sizeof(int)) {
log_message(LOG_LEVEL_ERROR, "Invalid chunk format: not enough data for metadata"); log_message(LOG_LEVEL_ERROR, "Invalid chunk format: not enough data for metadata");
file_destroy(file);
array_list_delete(files); array_list_delete(files);
return NULL; return NULL;
} }
@@ -146,7 +134,6 @@ Chunk* chunk_deserialize(Data* data, bool use_metadata) {
memcpy(&present_flag, data_pointer, sizeof(int)); memcpy(&present_flag, data_pointer, sizeof(int));
if (present_flag && remaining_size < sizeof(int) + FILE_METADATA_WIRE_SIZE) { if (present_flag && remaining_size < sizeof(int) + FILE_METADATA_WIRE_SIZE) {
log_message(LOG_LEVEL_ERROR, "Invalid chunk format: not enough data for metadata body"); log_message(LOG_LEVEL_ERROR, "Invalid chunk format: not enough data for metadata body");
file_destroy(file);
array_list_delete(files); array_list_delete(files);
return NULL; return NULL;
} }
@@ -154,16 +141,10 @@ Chunk* chunk_deserialize(Data* data, bool use_metadata) {
remaining_size -= sizeof(int); remaining_size -= sizeof(int);
if (file->metadata) if (file->metadata)
remaining_size -= FILE_METADATA_WIRE_SIZE; remaining_size -= FILE_METADATA_WIRE_SIZE;
else if (present_flag) {
file_destroy(file);
array_list_delete(files);
return NULL;
}
} }
if (remaining_size < sizeof(size_t)) { if (remaining_size < sizeof(size_t)) {
log_message(LOG_LEVEL_ERROR, "Invalid chunk format: not enough data for data size"); log_message(LOG_LEVEL_ERROR, "Invalid chunk format: not enough data for data size");
file_destroy(file);
array_list_delete(files); array_list_delete(files);
return NULL; return NULL;
} }
@@ -175,7 +156,6 @@ Chunk* chunk_deserialize(Data* data, bool use_metadata) {
if (remaining_size < file_data_size) { if (remaining_size < file_data_size) {
log_message(LOG_LEVEL_ERROR, "Invalid chunk format: not enough data for file content"); log_message(LOG_LEVEL_ERROR, "Invalid chunk format: not enough data for file content");
file_destroy(file);
array_list_delete(files); array_list_delete(files);
return NULL; return NULL;
} }
@@ -184,48 +164,29 @@ Chunk* chunk_deserialize(Data* data, bool use_metadata) {
if (file_data_size > MAX_FILE_DATA_SIZE) { if (file_data_size > MAX_FILE_DATA_SIZE) {
log_message(LOG_LEVEL_ERROR, "File data size %zu exceeds maximum %llu", file_data_size, log_message(LOG_LEVEL_ERROR, "File data size %zu exceeds maximum %llu", file_data_size,
(unsigned long long)MAX_FILE_DATA_SIZE); (unsigned long long)MAX_FILE_DATA_SIZE);
file_destroy(file);
array_list_delete(files); array_list_delete(files);
return NULL; return NULL;
} }
void* file_data = malloc(file_data_size > 0 ? file_data_size : 1); void* file_data = malloc(file_data_size);
if (file_data == NULL) { if (file_data == NULL) {
perror("Could not allocate memory for file data"); perror("Could not allocate memory for file data");
file_destroy(file);
array_list_delete(files); array_list_delete(files);
return NULL; return NULL;
} }
memcpy(file_data, data_pointer, file_data_size); memcpy(file_data, data_pointer, file_data_size);
data_destroy(file->data); data_destroy(file->data);
file->data = data_create(file_data, file_data_size); file->data = data_create(file_data, file_data_size);
if (file->data == NULL) {
file_destroy(file);
array_list_delete(files);
return NULL;
}
data_pointer += file_data_size; data_pointer += file_data_size;
remaining_size -= file_data_size; remaining_size -= file_data_size;
if (!array_list_add(files, file)) { array_list_add(files, file);
file_destroy(file);
array_list_delete(files);
return NULL;
}
} }
File** file_array = (File**)array_list_to_array(files); File** file_array = (File**)array_list_to_array(files);
if (file_array == NULL) {
array_list_delete(files);
return NULL;
}
Chunk* chunk = chunk_create(file_array, files->size); Chunk* chunk = chunk_create(file_array, files->size);
free(file_array); free(file_array);
if (chunk == NULL) {
array_list_delete(files);
return NULL;
}
files->item_destroyer = NULL; files->item_destroyer = NULL;
array_list_delete(files); array_list_delete(files);
+87 -166
View File
@@ -173,158 +173,39 @@ void config_delete(Config* config) {
free(config); free(config);
} }
/* Each helper is deliberately ordered to match the wire format. Keep the static bool config_send_string(int file_descriptor, const char* value, bool optional) {
* helper call order in config_send and config_receive unchanged when adding return send_str(file_descriptor, value ? value : (optional ? "" : NULL));
* fields. */
static bool send_core_fields(int fd, const Config* c) {
return send_str(fd, c->version) && send_str(fd, c->send_directory) &&
send_str(fd, c->receive_root_directory) && send_int(fd, c->save_to_disk) &&
send_int(fd, c->use_multithreading) && send_int(fd, c->use_chunk_serialization) &&
send_int(fd, c->use_compression) && send_int(fd, c->use_metadata) &&
send_int(fd, c->compression_level) &&
send_n_data(fd, &c->chunk_size, sizeof(c->chunk_size)) && send_int(fd, c->use_sendfile);
} }
static bool send_delta_fields(int fd, const Config* c) { #define CONFIG_SEND_VERSION(field) \
return send_int(fd, c->use_delete) && send_int(fd, c->use_incremental) && do { \
send_int(fd, c->use_delta) && if (!config_send_string(file_descriptor, config->field, false)) \
send_n_data(fd, &c->delta_block_size, sizeof(c->delta_block_size)) && return false; \
send_n_data(fd, &c->delta_max_file_size, sizeof(unsigned long long)); } while (0);
} #define CONFIG_SEND_STRING(field) \
do { \
static bool send_file_options(int fd, const Config* c) { if (!config_send_string(file_descriptor, config->field, false)) \
return send_int(fd, c->backup) && send_str(fd, c->backup_dir ? c->backup_dir : "") && return false; \
send_int(fd, c->follow_symlinks) && send_int(fd, c->copy_links) && } while (0);
send_int(fd, c->safe_links) && send_int(fd, c->copy_unsafe_links) && #define CONFIG_SEND_OPTIONAL_STRING(field) \
send_int(fd, c->preserve_hard_links) && send_int(fd, c->preserve_acls) && do { \
send_int(fd, c->preserve_xattrs) && send_int(fd, c->preserve_devices) && if (!config_send_string(file_descriptor, config->field, true)) \
send_int(fd, c->preserve_sparse); return false; \
} } while (0);
#define CONFIG_SEND_INTEGER(field) \
static bool send_selection_options(int fd, const Config* c) { do { \
return send_int(fd, c->update) && send_int(fd, c->inplace) && send_int(fd, c->append) && if (!send_int(file_descriptor, config->field)) \
send_int(fd, c->append_verify) && send_int(fd, c->delete_excluded) && return false; \
send_int(fd, c->delete_after) && send_n_data(fd, &c->max_delete, sizeof(c->max_delete)) && } while (0);
send_int(fd, c->relative) && send_int(fd, c->prune_empty_dirs); #define CONFIG_SEND_DATA(field) \
} do { \
if (!send_n_data(file_descriptor, &config->field, sizeof(config->field))) \
static bool send_resume_options(int fd, const Config* c) { return false; \
return send_str(fd, c->temp_dir ? c->temp_dir : "") && send_int(fd, c->partial) && } while (0);
send_str(fd, c->partial_dir ? c->partial_dir : "") &&
send_str(fd, c->suffix ? c->suffix : "") && send_int(fd, c->delete_before) &&
send_int(fd, c->checksum) && send_str(fd, c->compress_choice ? c->compress_choice : "");
}
static bool receive_core_fields(int fd, Config* c) {
int value;
c->send_directory = receive_str(fd);
c->receive_root_directory = receive_str(fd);
if (!c->send_directory || !c->receive_root_directory)
return false;
if (!receive_int(fd, &value))
return false;
c->save_to_disk = value;
if (!receive_int(fd, &value))
return false;
c->use_multithreading = value;
if (!receive_int(fd, &value))
return false;
c->use_chunk_serialization = value;
if (!receive_int(fd, &value))
return false;
c->use_compression = value;
if (!receive_int(fd, &value))
return false;
c->use_metadata = value;
if (!receive_int(fd, &value))
return false;
c->compression_level = value;
if (!receive_n_data(fd, &c->chunk_size, sizeof(c->chunk_size)))
return false;
if (!receive_int(fd, &value))
return false;
c->use_sendfile = value;
return true;
}
static bool receive_delta_fields(int fd, Config* c) {
int value;
if (!receive_int(fd, &value))
return false;
c->use_delete = value;
if (!receive_int(fd, &value))
return false;
c->use_incremental = value;
if (!receive_int(fd, &value))
return false;
c->use_delta = value;
return receive_n_data(fd, &c->delta_block_size, sizeof(c->delta_block_size)) &&
receive_n_data(fd, &c->delta_max_file_size, sizeof(unsigned long long));
}
static bool receive_file_options(int fd, Config* c) {
int value;
if (!receive_int(fd, &value))
return false;
c->backup = value;
c->backup_dir = receive_str(fd);
if (!c->backup_dir)
return false;
bool* flags[] = {&c->follow_symlinks, &c->copy_links, &c->safe_links,
&c->copy_unsafe_links, &c->preserve_hard_links, &c->preserve_acls,
&c->preserve_xattrs, &c->preserve_devices, &c->preserve_sparse};
for (size_t i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
if (!receive_int(fd, &value))
return false;
*flags[i] = value;
}
return true;
}
static bool receive_selection_options(int fd, Config* c) {
int value;
bool* flags[] = {&c->update, &c->inplace, &c->append,
&c->append_verify, &c->delete_excluded, &c->delete_after};
for (size_t i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
if (!receive_int(fd, &value))
return false;
*flags[i] = value;
}
if (!receive_n_data(fd, &c->max_delete, sizeof(c->max_delete)))
return false;
if (!receive_int(fd, &value))
return false;
c->relative = value;
if (!receive_int(fd, &value))
return false;
c->prune_empty_dirs = value;
return true;
}
static bool receive_resume_options(int fd, Config* c) {
int value;
c->temp_dir = receive_str(fd);
if (!c->temp_dir || !receive_int(fd, &value))
return false;
c->partial = value;
c->partial_dir = receive_str(fd);
c->suffix = c->partial_dir ? receive_str(fd) : NULL;
if (!c->partial_dir || !c->suffix || !receive_int(fd, &value))
return false;
c->delete_before = value;
if (!receive_int(fd, &value))
return false;
c->checksum = value;
c->compress_choice = receive_str(fd);
return c->compress_choice != NULL;
}
bool config_send(int file_descriptor, const Config* config) { bool config_send(int file_descriptor, const Config* config) {
if (!send_core_fields(file_descriptor, config) || !send_delta_fields(file_descriptor, config) || CONFIG_WIRE_FIELDS(CONFIG_SEND_VERSION, CONFIG_SEND_STRING, CONFIG_SEND_OPTIONAL_STRING,
!send_file_options(file_descriptor, config) || CONFIG_SEND_INTEGER, CONFIG_SEND_DATA)
!send_selection_options(file_descriptor, config) ||
!send_resume_options(file_descriptor, config))
return false;
Status status; Status status;
if (!receive_status(file_descriptor, &status)) if (!receive_status(file_descriptor, &status))
return false; return false;
@@ -335,26 +216,60 @@ bool config_send(int file_descriptor, const Config* config) {
return true; return true;
} }
#undef CONFIG_SEND_VERSION
#undef CONFIG_SEND_STRING
#undef CONFIG_SEND_OPTIONAL_STRING
#undef CONFIG_SEND_INTEGER
#undef CONFIG_SEND_DATA
static bool config_receive_string(int file_descriptor, char** destination) {
char* value = receive_str(file_descriptor);
if (!value)
return false;
*destination = value;
return true;
}
#define CONFIG_RECEIVE_VERSION(field) \
do { \
free(config->field); \
config->field = receive_str(file_descriptor); \
if (!config->field) \
goto error; \
if (strcmp(config->field, PROTOCOL_VERSION) != 0) { \
fprintf(stderr, "Protocol version mismatch: client=%s, server=%s\n", config->field, \
PROTOCOL_VERSION); \
config_delete(config); \
send_status(file_descriptor, STATUS_ERROR); \
return NULL; \
} \
} while (0);
#define CONFIG_RECEIVE_STRING(field) \
do { \
if (!config_receive_string(file_descriptor, &config->field)) \
goto error; \
} while (0);
#define CONFIG_RECEIVE_OPTIONAL_STRING(field) CONFIG_RECEIVE_STRING(field)
#define CONFIG_RECEIVE_INTEGER(field) \
do { \
if (!receive_int(file_descriptor, &tmp)) \
goto error; \
config->field = tmp; \
} while (0);
#define CONFIG_RECEIVE_DATA(field) \
do { \
if (!receive_n_data(file_descriptor, &config->field, sizeof(config->field))) \
goto error; \
} while (0);
Config* config_receive(int file_descriptor) { Config* config_receive(int file_descriptor) {
Config* config = config_create(); Config* config = (Config*)malloc(sizeof(Config));
if (!config) if (config == NULL)
return NULL; return NULL;
free(config->version); config_set_defaults(config);
config->version = receive_str(file_descriptor); int tmp;
if (!config->version) CONFIG_WIRE_FIELDS(CONFIG_RECEIVE_VERSION, CONFIG_RECEIVE_STRING, CONFIG_RECEIVE_OPTIONAL_STRING,
goto error; CONFIG_RECEIVE_INTEGER, CONFIG_RECEIVE_DATA)
if (strcmp(config->version, PROTOCOL_VERSION) != 0) {
fprintf(stderr, "Protocol version mismatch: client=%s, server=%s\n", config->version,
PROTOCOL_VERSION);
send_status(file_descriptor, STATUS_ERROR);
goto error;
}
if (!receive_core_fields(file_descriptor, config) ||
!receive_delta_fields(file_descriptor, config) ||
!receive_file_options(file_descriptor, config) ||
!receive_selection_options(file_descriptor, config) ||
!receive_resume_options(file_descriptor, config))
goto error;
if (config->compress_choice[0] != '\0' && strcmp(config->compress_choice, "zstd") != 0 && if (config->compress_choice[0] != '\0' && strcmp(config->compress_choice, "zstd") != 0 &&
strcmp(config->compress_choice, "none") != 0) { strcmp(config->compress_choice, "none") != 0) {
fprintf(stderr, "Unsupported compression choice: %s\n", config->compress_choice); fprintf(stderr, "Unsupported compression choice: %s\n", config->compress_choice);
@@ -369,3 +284,9 @@ error:
config_delete(config); config_delete(config);
return NULL; return NULL;
} }
#undef CONFIG_RECEIVE_VERSION
#undef CONFIG_RECEIVE_STRING
#undef CONFIG_RECEIVE_OPTIONAL_STRING
#undef CONFIG_RECEIVE_INTEGER
#undef CONFIG_RECEIVE_DATA
+47
View File
@@ -128,6 +128,53 @@ typedef struct Config {
char* compress_choice; char* compress_choice;
} Config; } Config;
/* Keep the on-wire field order in one place. The first three strings require
* values when sent; optional strings are encoded as empty strings when NULL. */
#define CONFIG_WIRE_FIELDS(VERSION, STRING, OPTIONAL_STRING, INTEGER, DATA) \
VERSION(version) \
STRING(send_directory) \
STRING(receive_root_directory) \
INTEGER(save_to_disk) \
INTEGER(use_multithreading) \
INTEGER(use_chunk_serialization) \
INTEGER(use_compression) \
INTEGER(use_metadata) \
INTEGER(compression_level) \
DATA(chunk_size) \
INTEGER(use_sendfile) \
INTEGER(use_delete) \
INTEGER(use_incremental) \
INTEGER(use_delta) \
DATA(delta_block_size) \
DATA(delta_max_file_size) \
INTEGER(backup) \
OPTIONAL_STRING(backup_dir) \
INTEGER(follow_symlinks) \
INTEGER(copy_links) \
INTEGER(safe_links) \
INTEGER(copy_unsafe_links) \
INTEGER(preserve_hard_links) \
INTEGER(preserve_acls) \
INTEGER(preserve_xattrs) \
INTEGER(preserve_devices) \
INTEGER(preserve_sparse) \
INTEGER(update) \
INTEGER(inplace) \
INTEGER(append) \
INTEGER(append_verify) \
INTEGER(delete_excluded) \
INTEGER(delete_after) \
DATA(max_delete) \
INTEGER(relative) \
INTEGER(prune_empty_dirs) \
OPTIONAL_STRING(temp_dir) \
INTEGER(partial) \
OPTIONAL_STRING(partial_dir) \
OPTIONAL_STRING(suffix) \
INTEGER(delete_before) \
INTEGER(checksum) \
OPTIONAL_STRING(compress_choice)
#define PROTOCOL_VERSION "2.2.0" #define PROTOCOL_VERSION "2.2.0"
#define DEFAULT_CHUNK_SIZE (10 * 1024 * 1024) #define DEFAULT_CHUNK_SIZE (10 * 1024 * 1024)
+268 -32
View File
@@ -19,7 +19,6 @@
#include "config.h" #include "config.h"
#include "data.h" #include "data.h"
#include "file.h" #include "file.h"
#include "file_store.h"
#include "metadata.h" #include "metadata.h"
#include "protocol.h" #include "protocol.h"
#include "utils.h" #include "utils.h"
@@ -154,8 +153,17 @@ bool file_send_single_calls(File* file, int file_descriptor, bool use_metadata,
return true; return true;
} }
bool file_set_authorized_root(int fd, const char* canonical_path) { static bool to_disk_secure(const char* path, const void* data, unsigned long long data_size,
return file_store_set_authorized_root(fd, canonical_path); bool inplace, bool sparse, const FileMetadata* metadata);
static int open_secure_parent(const char* path, char** leaf_out);
static bool rename_secure(const char* old_path, const char* new_path);
static int authorized_root_fd = -1;
static char* authorized_root_path;
void file_set_authorized_root(int fd, const char* canonical_path) {
authorized_root_fd = fd;
free(authorized_root_path);
authorized_root_path = canonical_path ? str_dup(canonical_path) : NULL;
} }
static bool path_is_within_root(const char* root, const char* path) { static bool path_is_within_root(const char* root, const char* path) {
@@ -272,7 +280,7 @@ bool file_save_to_disk(const char* root_directory, const File* file, const Confi
mkdir_r(bdir); mkdir_r(bdir);
free(backup_dir_path); free(backup_dir_path);
} }
if (!file_store_rename_secure(disk_path, backup_path)) { if (!rename_secure(disk_path, backup_path)) {
free(backup_path); free(backup_path);
free(resolved_root); free(resolved_root);
free(confined_backup); free(confined_backup);
@@ -327,8 +335,8 @@ bool file_save_to_disk(const char* root_directory, const File* file, const Confi
free(resolved_dir); free(resolved_dir);
free(resolved_root); free(resolved_root);
bool ok = file_store_write_secure(disk_path, file->data->data, file->data->size, inplace, sparse, bool ok = to_disk_secure(disk_path, file->data->data, file->data->size, inplace, sparse,
file->metadata); file->metadata);
free(confined_backup); free(confined_backup);
free(confined_partial); free(confined_partial);
free(disk_path); free(disk_path);
@@ -336,14 +344,13 @@ bool file_save_to_disk(const char* root_directory, const File* file, const Confi
} }
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, bool* failed) { void* old_data, unsigned long long old_size) {
if (!old_data) if (!old_data)
return NULL; return NULL;
DeltaSignature* sig = delta_signature_create(old_data, old_size, config->delta_block_size); DeltaSignature* sig = delta_signature_create(old_data, old_size, config->delta_block_size);
if (!sig) { if (!sig) {
free(old_data); free(old_data);
*failed = true;
return NULL; return NULL;
} }
@@ -351,7 +358,6 @@ static File* receive_delta_file(int fd, const Config* config, const char* check_
if (!sig_data) { if (!sig_data) {
delta_signature_destroy(sig); delta_signature_destroy(sig);
free(old_data); free(old_data);
*failed = true;
return NULL; return NULL;
} }
@@ -361,7 +367,6 @@ static File* receive_delta_file(int fd, const Config* config, const char* check_
if (!sig_sent) { if (!sig_sent) {
delta_signature_destroy(sig); delta_signature_destroy(sig);
free(old_data); free(old_data);
*failed = true;
return NULL; return NULL;
} }
@@ -369,7 +374,6 @@ static File* receive_delta_file(int fd, const Config* config, const char* check_
if (!receive_status(fd, &resp)) { if (!receive_status(fd, &resp)) {
delta_signature_destroy(sig); delta_signature_destroy(sig);
free(old_data); free(old_data);
*failed = true;
return NULL; return NULL;
} }
@@ -378,7 +382,7 @@ static File* receive_delta_file(int fd, const Config* config, const char* check_
if (!delta_data) { if (!delta_data) {
delta_signature_destroy(sig); delta_signature_destroy(sig);
free(old_data); free(old_data);
*failed = true; send_status(fd, STATUS_ERROR);
return NULL; return NULL;
} }
@@ -389,7 +393,7 @@ static File* receive_delta_file(int fd, const Config* config, const char* check_
if (!raw_delta) { if (!raw_delta) {
free(old_data); free(old_data);
delta_signature_destroy(sig); delta_signature_destroy(sig);
*failed = true; send_status(fd, STATUS_ERROR);
return NULL; return NULL;
} }
} }
@@ -399,7 +403,7 @@ static File* receive_delta_file(int fd, const Config* config, const char* check_
if (!delta) { if (!delta) {
free(old_data); free(old_data);
delta_signature_destroy(sig); delta_signature_destroy(sig);
*failed = true; send_status(fd, STATUS_ERROR);
return NULL; return NULL;
} }
@@ -410,7 +414,7 @@ static File* receive_delta_file(int fd, const Config* config, const char* check_
if (!new_data) { if (!new_data) {
free(old_data); free(old_data);
delta_signature_destroy(sig); delta_signature_destroy(sig);
*failed = true; send_status(fd, STATUS_ERROR);
return NULL; return NULL;
} }
@@ -419,7 +423,7 @@ static File* receive_delta_file(int fd, const Config* config, const char* check_
free(new_data); free(new_data);
free(old_data); free(old_data);
delta_signature_destroy(sig); delta_signature_destroy(sig);
*failed = true; send_status(fd, STATUS_ERROR);
return NULL; return NULL;
} }
@@ -431,7 +435,7 @@ static File* receive_delta_file(int fd, const Config* config, const char* check_
free(new_data); free(new_data);
free(old_data); free(old_data);
delta_signature_destroy(sig); delta_signature_destroy(sig);
*failed = true; send_status(fd, STATUS_ERROR);
return NULL; return NULL;
} }
} }
@@ -450,7 +454,7 @@ static File* receive_delta_file(int fd, const Config* config, const char* check_
File* file = file_create(check_path); File* file = file_create(check_path);
if (!file) { if (!file) {
*failed = true; send_status(fd, STATUS_ERROR);
return NULL; return NULL;
} }
@@ -459,7 +463,7 @@ static File* receive_delta_file(int fd, const Config* config, const char* check_
file->metadata = metadata_receive(fd, &meta_ok); file->metadata = metadata_receive(fd, &meta_ok);
if (!meta_ok) { if (!meta_ok) {
file_destroy(file); file_destroy(file);
*failed = true; send_status(fd, STATUS_ERROR);
return NULL; return NULL;
} }
} }
@@ -467,7 +471,7 @@ static File* receive_delta_file(int fd, const Config* config, const char* check_
Data* file_data = receive_data(fd); Data* file_data = receive_data(fd);
if (file_data == NULL) { if (file_data == NULL) {
file_destroy(file); file_destroy(file);
*failed = true; send_status(fd, STATUS_ERROR);
return NULL; return NULL;
} }
@@ -476,7 +480,7 @@ static File* receive_delta_file(int fd, const Config* config, const char* check_
data_destroy(file_data); data_destroy(file_data);
if (uncompressed == NULL) { if (uncompressed == NULL) {
file_destroy(file); file_destroy(file);
*failed = true; send_status(fd, STATUS_ERROR);
return NULL; return NULL;
} }
file_data = uncompressed; file_data = uncompressed;
@@ -489,7 +493,6 @@ 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);
*failed = true;
return NULL; return NULL;
} }
@@ -497,6 +500,7 @@ File* receive_incremental_check(int fd, const Config* config, bool* skipped) {
*skipped = false; *skipped = false;
char* check_path = receive_str(fd); char* check_path = receive_str(fd);
if (check_path == NULL) { if (check_path == NULL) {
send_status(fd, STATUS_ERROR);
return NULL; return NULL;
} }
@@ -506,16 +510,19 @@ File* receive_incremental_check(int fd, const Config* config, bool* skipped) {
if (!receive_n_data(fd, &check_size, sizeof(check_size)) || if (!receive_n_data(fd, &check_size, sizeof(check_size)) ||
!receive_n_data(fd, &check_mtime, sizeof(check_mtime))) { !receive_n_data(fd, &check_mtime, sizeof(check_mtime))) {
free(check_path); free(check_path);
send_status(fd, STATUS_ERROR);
return NULL; return NULL;
} }
if (config->checksum && !receive_n_data(fd, &check_checksum, sizeof(check_checksum))) { if (config->checksum && !receive_n_data(fd, &check_checksum, sizeof(check_checksum))) {
free(check_path); free(check_path);
send_status(fd, STATUS_ERROR);
return NULL; return NULL;
} }
if (has_path_traversal(check_path)) { if (has_path_traversal(check_path)) {
log_message(LOG_LEVEL_ERROR, "Path traversal detected: %s", check_path); log_message(LOG_LEVEL_ERROR, "Path traversal detected: %s", check_path);
free(check_path); free(check_path);
send_status(fd, STATUS_ERROR);
return NULL; return NULL;
} }
@@ -525,7 +532,7 @@ File* receive_incremental_check(int fd, const Config* config, bool* skipped) {
int old_fd = -1; int old_fd = -1;
if (full_path) { if (full_path) {
char* leaf = NULL; char* leaf = NULL;
int parent_fd = file_store_open_secure_parent(full_path, &leaf); int parent_fd = open_secure_parent(full_path, &leaf);
if (parent_fd >= 0) { if (parent_fd >= 0) {
old_fd = openat(parent_fd, leaf, O_RDONLY | O_CLOEXEC | O_NOFOLLOW); old_fd = openat(parent_fd, leaf, O_RDONLY | O_CLOEXEC | O_NOFOLLOW);
free(leaf); free(leaf);
@@ -583,20 +590,13 @@ File* receive_incremental_check(int fd, const Config* config, bool* skipped) {
delta_should_attempt(old_size, check_size, config->delta_max_file_size); delta_should_attempt(old_size, check_size, config->delta_max_file_size);
if (try_delta) { if (try_delta) {
bool delta_failed = false; File* delta_file = receive_delta_file(fd, config, check_path, old_data, old_size);
File* delta_file =
receive_delta_file(fd, config, check_path, old_data, old_size, &delta_failed);
old_data = NULL; /* receive_delta_file consumes the snapshot on every path */ old_data = NULL; /* receive_delta_file consumes the snapshot on every path */
if (delta_file) { if (delta_file) {
free(full_path); free(full_path);
free(check_path); free(check_path);
return delta_file; return delta_file;
} }
if (delta_failed) {
free(full_path);
free(check_path);
return NULL;
}
free(old_data); free(old_data);
old_data = NULL; old_data = NULL;
try_delta = false; try_delta = false;
@@ -614,6 +614,7 @@ File* receive_incremental_check(int fd, const Config* config, bool* skipped) {
free(check_path); free(check_path);
free(full_path); free(full_path);
if (file == NULL) { if (file == NULL) {
send_status(fd, STATUS_ERROR);
return NULL; return NULL;
} }
@@ -622,6 +623,7 @@ File* receive_incremental_check(int fd, const Config* config, bool* skipped) {
file->metadata = metadata_receive(fd, &meta_ok); file->metadata = metadata_receive(fd, &meta_ok);
if (!meta_ok) { if (!meta_ok) {
file_destroy(file); file_destroy(file);
send_status(fd, STATUS_ERROR);
return NULL; return NULL;
} }
} }
@@ -629,6 +631,7 @@ File* receive_incremental_check(int fd, const Config* config, bool* skipped) {
Data* file_data = receive_data(fd); Data* file_data = receive_data(fd);
if (file_data == NULL) { if (file_data == NULL) {
file_destroy(file); file_destroy(file);
send_status(fd, STATUS_ERROR);
return NULL; return NULL;
} }
@@ -637,6 +640,7 @@ File* receive_incremental_check(int fd, const Config* config, bool* skipped) {
data_destroy(file_data); data_destroy(file_data);
if (uncompressed == NULL) { if (uncompressed == NULL) {
file_destroy(file); file_destroy(file);
send_status(fd, STATUS_ERROR);
return NULL; return NULL;
} }
file_data = uncompressed; file_data = uncompressed;
@@ -647,11 +651,243 @@ File* receive_incremental_check(int fd, const Config* config, bool* skipped) {
return file; return file;
} }
static int open_secure_parent(const char* path, char** leaf_out) {
char* copy = str_dup(path);
if (!copy)
return -1;
char* parent = dirname(copy);
const char* slash = strrchr(path, '/');
char* leaf = str_dup(slash ? slash + 1 : path);
if (!leaf) {
free(copy);
return -1;
}
int fd;
if (authorized_root_fd >= 0 && authorized_root_path && path[0] == '/' &&
path_is_within_root(authorized_root_path, path)) {
fd = dup(authorized_root_fd);
size_t root_len = strlen(authorized_root_path);
char* relative = str_dup(path + root_len);
if (!relative) {
free(copy);
free(leaf);
close(fd);
return -1;
}
free(copy);
copy = relative;
parent = dirname(copy);
} else {
fd = (parent[0] == '/') ? open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC)
: open(".", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
}
if (fd < 0) {
free(copy);
free(leaf);
return -1;
}
char* save = NULL;
char* component = strtok_r(parent, "/", &save);
while (component) {
if (strcmp(component, ".") != 0 && strcmp(component, "..") != 0) {
int next = openat(fd, component, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
if (next < 0 && errno == ENOENT && mkdirat(fd, component, 0755) == 0)
next = openat(fd, component, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
if (next < 0) {
close(fd);
free(copy);
free(leaf);
return -1;
}
close(fd);
fd = next;
}
component = strtok_r(NULL, "/", &save);
}
free(copy);
*leaf_out = leaf;
return fd;
}
static bool rename_secure(const char* old_path, const char* new_path) {
char *old_leaf = NULL, *new_leaf = NULL;
int old_parent = open_secure_parent(old_path, &old_leaf);
int new_parent = open_secure_parent(new_path, &new_leaf);
bool ok = old_parent >= 0 && new_parent >= 0 &&
renameat(old_parent, old_leaf, new_parent, new_leaf) == 0;
if (old_parent >= 0)
close(old_parent);
if (new_parent >= 0)
close(new_parent);
free(old_leaf);
free(new_leaf);
return ok;
}
static bool write_all(int fd, const void* data, unsigned long long size) {
const unsigned char* p = data;
unsigned long long done = 0;
while (done < size) {
ssize_t n = write(fd, p + done, (size_t)(size - done));
if (n < 0 && errno == EINTR)
continue;
if (n <= 0)
return false;
done += (unsigned long long)n;
}
return true;
}
static bool to_disk_secure(const char* path, const void* data, unsigned long long data_size,
bool inplace, bool sparse, const FileMetadata* metadata) {
char* leaf = NULL;
int dirfd = open_secure_parent(path, &leaf);
if (dirfd < 0)
return false;
int fd = -1;
bool ok = false;
if (inplace) {
fd = openat(dirfd, leaf, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW, 0644);
if (fd >= 0) {
if (!sparse || data_size == 0 || ftruncate(fd, (off_t)data_size) == 0)
ok = write_all(fd, data, data_size);
if (ok && metadata)
ok = file_restore_metadata_fd(fd, metadata);
}
} else {
char tmp[NAME_MAX];
for (unsigned int i = 0; i < 100 && !ok; ++i) {
snprintf(tmp, sizeof(tmp), ".%s.tmp.%ld.%u", leaf, (long)getpid(), i);
fd = openat(dirfd, tmp, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC | O_NOFOLLOW, 0600);
if (fd < 0)
continue;
if (sparse && data_size > 0)
ok = ftruncate(fd, (off_t)data_size) == 0;
if (ok || (!sparse || data_size == 0))
ok = write_all(fd, data, data_size);
if (ok && metadata)
ok = file_restore_metadata_fd(fd, metadata);
if (close(fd) != 0)
ok = false;
fd = -1;
if (ok && renameat(dirfd, tmp, dirfd, leaf) != 0)
ok = false;
if (!ok)
unlinkat(dirfd, tmp, 0);
}
}
if (fd >= 0)
close(fd);
close(dirfd);
free(leaf);
return ok;
}
bool to_disk(const char* path, const void* data, unsigned long long data_size, bool inplace, bool to_disk(const char* path, const void* data, unsigned long long data_size, bool inplace,
bool sparse) { bool sparse) {
if (!path || (!data && data_size != 0) || has_path_traversal(path)) if (!path || (!data && data_size != 0) || has_path_traversal(path))
return false; return false;
return file_store_write_secure(path, data, data_size, inplace, sparse, NULL); return to_disk_secure(path, data, data_size, inplace, sparse, NULL);
/* Kept below only as historical context; all writes use descriptor-relative operations. */
char* tmp_path = NULL;
char* directory = NULL;
char* path_dup = str_dup(path);
if (!path_dup)
return false;
const char* dir_result = dirname(path_dup);
directory = str_dup(dir_result);
free(path_dup);
if (!directory)
return false;
bool ok = true;
if (!mkdir_r(directory))
goto done;
if (inplace) {
FILE* file_pointer = fopen(path, "wb");
if (file_pointer == NULL) {
perror("Could not open file for inplace write");
ok = false;
goto done;
}
if (sparse && data_size > 0) {
if (fseek(file_pointer, data_size - 1, SEEK_SET) != 0) {
perror("Failed to seek for sparse file");
fclose(file_pointer);
ok = false;
goto done;
}
if (fwrite("", 1, 1, file_pointer) != 1) {
perror("Failed to write sparse file");
fclose(file_pointer);
ok = false;
goto done;
}
rewind(file_pointer);
}
if (data_size > 0 && fwrite(data, 1, data_size, file_pointer) != data_size) {
perror("Failed to write all data to file");
fclose(file_pointer);
ok = false;
goto done;
}
fclose(file_pointer);
free(directory);
return true;
}
size_t path_len = strlen(path);
tmp_path = malloc(path_len + 5);
if (!tmp_path) {
ok = false;
goto done;
}
memcpy(tmp_path, path, path_len);
memcpy(tmp_path + path_len, ".tmp", 5);
FILE* file_pointer = fopen(tmp_path, "wb");
if (file_pointer == NULL) {
perror("Could not open temporary file");
ok = false;
goto done;
}
if (sparse && data_size > 0) {
if (fseek(file_pointer, data_size - 1, SEEK_SET) != 0) {
perror("Failed to seek for sparse file");
fclose(file_pointer);
ok = false;
goto done;
}
if (fwrite("", 1, 1, file_pointer) != 1) {
perror("Failed to write sparse file");
fclose(file_pointer);
ok = false;
goto done;
}
rewind(file_pointer);
}
if (fwrite(data, 1, data_size, file_pointer) != data_size) {
perror("Failed to write all data to temporary file");
fclose(file_pointer);
unlink(tmp_path);
ok = false;
goto done;
}
fclose(file_pointer);
if (rename(tmp_path, path) != 0) {
perror("Failed to atomically rename temporary file");
unlink(tmp_path);
ok = false;
goto done;
}
done:
free(tmp_path);
free(directory);
return ok;
} }
bool file_send_sendfile(File* file, int file_descriptor, bool use_metadata, int compression_level, bool file_send_sendfile(File* file, int file_descriptor, bool use_metadata, int compression_level,
+1 -1
View File
@@ -38,7 +38,7 @@ void file_metadata_destroy(void* metadata);
bool to_disk(const char* path, const void* data, unsigned long long data_size, bool inplace, bool to_disk(const char* path, const void* data, unsigned long long data_size, bool inplace,
bool sparse); bool sparse);
bool file_save_to_disk(const char* root_directory, const File* file, const Config* config); bool file_save_to_disk(const char* root_directory, const File* file, const Config* config);
bool file_set_authorized_root(int fd, const char* canonical_path); void file_set_authorized_root(int fd, const char* canonical_path);
File* receive_incremental_check(int fd, const Config* config, bool* skipped); File* receive_incremental_check(int fd, const Config* config, bool* skipped);
int receive_manifest(int fd, const Config* config, int* next_status); int receive_manifest(int fd, const Config* config, int* next_status);
-175
View File
@@ -1,175 +0,0 @@
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "file_store.h"
#include "metadata.h"
#include "utils.h"
static int authorized_root_fd = -1;
static char* authorized_root_path;
static bool path_is_within_root(const char* root, const char* path) {
size_t root_length = strlen(root);
return strncmp(root, path, root_length) == 0 &&
(path[root_length] == '\0' || path[root_length] == '/');
}
bool file_store_set_authorized_root(int fd, const char* canonical_path) {
char* new_path = canonical_path ? str_dup(canonical_path) : NULL;
if (canonical_path && !new_path)
return false;
free(authorized_root_path);
authorized_root_path = new_path;
authorized_root_fd = fd;
return true;
}
int file_store_open_secure_parent(const char* path, char** leaf_out) {
char* copy = str_dup(path);
if (!copy)
return -1;
char* parent = dirname(copy);
const char* slash = strrchr(path, '/');
char* leaf = str_dup(slash ? slash + 1 : path);
if (!leaf) {
free(copy);
return -1;
}
int fd;
if (authorized_root_fd >= 0 && authorized_root_path && path[0] == '/' &&
path_is_within_root(authorized_root_path, path)) {
fd = dup(authorized_root_fd);
size_t root_length = strlen(authorized_root_path);
char* relative = str_dup(path + root_length);
if (!relative) {
free(copy);
free(leaf);
close(fd);
return -1;
}
free(copy);
copy = relative;
parent = dirname(copy);
} else {
fd = (parent[0] == '/') ? open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC)
: open(".", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
}
if (fd < 0) {
free(copy);
free(leaf);
return -1;
}
char* save = NULL;
char* component = strtok_r(parent, "/", &save);
while (component) {
if (strcmp(component, ".") != 0 && strcmp(component, "..") != 0) {
int next = openat(fd, component, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
if (next < 0 && errno == ENOENT && mkdirat(fd, component, 0755) == 0)
next = openat(fd, component, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
if (next < 0) {
close(fd);
free(copy);
free(leaf);
return -1;
}
close(fd);
fd = next;
}
component = strtok_r(NULL, "/", &save);
}
free(copy);
*leaf_out = leaf;
return fd;
}
bool file_store_rename_secure(const char* old_path, const char* new_path) {
char *old_leaf = NULL, *new_leaf = NULL;
int old_parent = file_store_open_secure_parent(old_path, &old_leaf);
int new_parent = file_store_open_secure_parent(new_path, &new_leaf);
bool ok = old_parent >= 0 && new_parent >= 0 &&
renameat(old_parent, old_leaf, new_parent, new_leaf) == 0;
if (old_parent >= 0)
close(old_parent);
if (new_parent >= 0)
close(new_parent);
free(old_leaf);
free(new_leaf);
return ok;
}
static bool write_all(int fd, const void* data, unsigned long long size) {
const unsigned char* p = data;
unsigned long long done = 0;
while (done < size) {
ssize_t n = write(fd, p + done, (size_t)(size - done));
if (n < 0 && errno == EINTR)
continue;
if (n <= 0)
return false;
done += (unsigned long long)n;
}
return true;
}
bool file_store_write_secure(const char* path, const void* data, unsigned long long data_size,
bool inplace, bool sparse, const FileMetadata* metadata) {
char* leaf = NULL;
int dirfd = file_store_open_secure_parent(path, &leaf);
if (dirfd < 0)
return false;
int fd = -1;
bool ok = false;
if (inplace) {
fd = openat(dirfd, leaf, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW, 0644);
if (fd >= 0) {
if (!sparse || data_size == 0 || ftruncate(fd, (off_t)data_size) == 0)
ok = write_all(fd, data, data_size);
if (ok && metadata)
ok = file_restore_metadata_fd(fd, metadata);
}
} else {
int tmp_size = snprintf(NULL, 0, ".%s.tmp.%ld.%u", leaf, (long)getpid(), 99U);
if (tmp_size < 0) {
close(dirfd);
free(leaf);
return false;
}
char* tmp = malloc((size_t)tmp_size + 1);
if (!tmp) {
close(dirfd);
free(leaf);
return false;
}
for (unsigned int i = 0; i < 100 && !ok; ++i) {
snprintf(tmp, (size_t)tmp_size + 1, ".%s.tmp.%ld.%u", leaf, (long)getpid(), i);
fd = openat(dirfd, tmp, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC | O_NOFOLLOW, 0600);
if (fd < 0)
continue;
if (sparse && data_size > 0)
ok = ftruncate(fd, (off_t)data_size) == 0;
if (ok || (!sparse || data_size == 0))
ok = write_all(fd, data, data_size);
if (ok && metadata)
ok = file_restore_metadata_fd(fd, metadata);
if (close(fd) != 0)
ok = false;
fd = -1;
if (ok && renameat(dirfd, tmp, dirfd, leaf) != 0)
ok = false;
if (!ok)
unlinkat(dirfd, tmp, 0);
}
free(tmp);
}
if (fd >= 0)
close(fd);
close(dirfd);
free(leaf);
return ok;
}
-13
View File
@@ -1,13 +0,0 @@
#ifndef FILE_STORE_H
#define FILE_STORE_H
#include "file.h"
#include <stdbool.h>
bool file_store_set_authorized_root(int fd, const char* canonical_path);
int file_store_open_secure_parent(const char* path, char** leaf_out);
bool file_store_rename_secure(const char* old_path, const char* new_path);
bool file_store_write_secure(const char* path, const void* data, unsigned long long data_size,
bool inplace, bool sparse, const FileMetadata* metadata);
#endif
+114 -38
View File
@@ -1,5 +1,4 @@
#include "multiprocessing.h" #include "multiprocessing.h"
#include "receiver.h"
#include "array_list.h" #include "array_list.h"
#include "chunk.h" #include "chunk.h"
@@ -14,6 +13,106 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <threads.h> #include <threads.h>
#include <sys/stat.h>
static bool valid_batch_path(const char* path) {
return path && path[0] != '\0' && path[0] != '/' && !has_path_traversal(path);
}
static bool handle_batch_checks(int file_descriptor, const Config* config) {
int count;
if (config->checksum || !receive_int(file_descriptor, &count) || count < 0 ||
count > MAX_MANIFEST_ENTRIES)
return false;
for (int i = 0; i < count; i++) {
char* check_path = receive_str(file_descriptor);
if (!check_path)
return false;
unsigned long long check_size;
long long check_mtime;
bool received = receive_n_data(file_descriptor, &check_size, sizeof(check_size)) &&
receive_n_data(file_descriptor, &check_mtime, sizeof(check_mtime));
if (!received || !valid_batch_path(check_path)) {
free(check_path);
if (received)
send_status(file_descriptor, STATUS_ERROR);
return false;
}
char* full_path = path_cat(config->receive_root_directory, check_path);
struct stat st;
bool has_old = full_path && lstat(full_path, &st) == 0;
bool match = has_old && (unsigned long long)st.st_size == check_size &&
(long long)st.st_mtime == check_mtime;
bool sent = send_status(file_descriptor, match ? STATUS_OK : STATUS_NEXT);
free(full_path);
free(check_path);
if (!sent)
return false;
}
return true;
}
int receive_files_common(const Config* config, int file_descriptor, ReceivedFileHandler handler,
void* context, bool send_completion_status) {
Status status;
if (!receive_status(file_descriptor, &status))
return -1;
while (status == STATUS_NEXT || status == STATUS_CHUNK || status == STATUS_CHECK ||
status == STATUS_KEEPALIVE || status == STATUS_ABORT || status == STATUS_CHECK_BATCH) {
if (status == STATUS_KEEPALIVE) {
if (!send_status(file_descriptor, STATUS_KEEPALIVE))
return -1;
} else if (status == STATUS_ABORT) {
log_message(LOG_LEVEL_INFO, "Received abort from client, cleaning up");
return -1;
} else if (status == STATUS_CHECK_BATCH) {
if (!handle_batch_checks(file_descriptor, config))
return -1;
} else {
if (status == STATUS_CHUNK) {
Chunk* chunk = receive_chunk_data(file_descriptor, config);
if (!chunk)
return -1;
for (int i = 0; i < chunk->element_count; i++) {
File* file = chunk->items[i];
chunk->items[i] = NULL;
if (!handler(file, context)) {
file_destroy(file);
chunk_destroy(chunk);
return -1;
}
}
chunk_destroy(chunk);
} else {
bool skipped = false;
File* file = status == STATUS_CHECK
? receive_incremental_check(file_descriptor, config, &skipped)
: file_receive(config, file_descriptor);
if (!skipped) {
if (!file || !handler(file, context)) {
file_destroy(file);
if (status == STATUS_NEXT)
log_message(LOG_LEVEL_ERROR, "Failed to receive file");
return -1;
}
}
}
}
if (!receive_status(file_descriptor, &status))
return -1;
}
if (status == STATUS_MANIFEST && receive_manifest(file_descriptor, config, &status) != 0)
return -1;
if (status != STATUS_FINISHED) {
log_message(LOG_LEVEL_ERROR, "Did not receive FINISHED Status");
if (send_completion_status)
send_status(file_descriptor, STATUS_ERROR);
return -1;
}
if (send_completion_status && !send_status(file_descriptor, STATUS_OK))
return -1;
return 0;
}
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) {
@@ -98,8 +197,6 @@ PipelineContextReceiver* pipeline_context_receiver_create(Config* config, Queue*
context->queue = queue; context->queue = queue;
context->file_descriptor = file_descriptor; context->file_descriptor = file_descriptor;
context->ssl = ssl; context->ssl = ssl;
protocol_session_init(&context->session, file_descriptor, file_descriptor);
protocol_session_set_ssl(&context->session, ssl);
context->receiver_done = false; context->receiver_done = false;
atomic_init(&context->cancelled, false); atomic_init(&context->cancelled, false);
int init = 0; int init = 0;
@@ -136,62 +233,41 @@ void pipeline_context_receiver_destroy(PipelineContextReceiver* context) {
free(context); free(context);
} }
static bool receiver_enqueue_file(File* file, void* context_pointer) { static bool enqueue_received_file(File* file, void* context) {
PipelineContextReceiver* context = context_pointer; PipelineContextReceiver* receiver = context;
if (queue_enqueue_multithreaded_cancel(context->queue, file, &context->mutex, return queue_enqueue_multithreaded_cancel(receiver->queue, file, &receiver->mutex,
&context->condition_not_empty, &receiver->condition_not_empty,
&context->condition_not_full, &context->cancelled)) &receiver->condition_not_full, &receiver->cancelled);
return true;
file_destroy(file);
return false;
}
static void receiver_thread_fail(PipelineContextReceiver* context) {
mtx_lock(&context->mutex);
atomic_store(&context->cancelled, true);
context->receiver_done = true;
cnd_broadcast(&context->condition_not_empty);
cnd_broadcast(&context->condition_not_full);
mtx_unlock(&context->mutex);
} }
int receive_thread(void* pipeline_context) { int receive_thread(void* pipeline_context) {
PipelineContextReceiver* context = (PipelineContextReceiver*)pipeline_context; PipelineContextReceiver* context = (PipelineContextReceiver*)pipeline_context;
protocol_session_bind(&context->session); if (context->ssl)
io_set_ssl(context->ssl);
mtx_lock(&context->mutex); mtx_lock(&context->mutex);
int file_descriptor = context->file_descriptor; int file_descriptor = context->file_descriptor;
const Config* config = context->config; const Config* config = context->config;
mtx_unlock(&context->mutex); mtx_unlock(&context->mutex);
ReceiverSink sink = {receiver_enqueue_file, context, false, false}; int result = receive_files_common(config, file_descriptor, enqueue_received_file, context, false);
if (receiver_process((Config*)config, file_descriptor, &sink) != 0) {
receiver_thread_fail(context);
protocol_session_unbind();
return thrd_error;
}
mtx_lock(&context->mutex); mtx_lock(&context->mutex);
if (result != 0)
atomic_store(&context->cancelled, true);
context->receiver_done = true; context->receiver_done = true;
cnd_signal(&context->condition_not_empty); cnd_signal(&context->condition_not_empty);
cnd_broadcast(&context->condition_not_full);
mtx_unlock(&context->mutex); mtx_unlock(&context->mutex);
protocol_session_unbind(); return result == 0 ? thrd_success : thrd_error;
return thrd_success;
} }
int write_thread(void* pipeline_context) { int write_thread(void* pipeline_context) {
PipelineContextReceiver* context = (PipelineContextReceiver*)pipeline_context; PipelineContextReceiver* context = (PipelineContextReceiver*)pipeline_context;
if (context->ssl)
io_set_ssl(context->ssl);
mtx_lock(&context->mutex); mtx_lock(&context->mutex);
bool save_to_disk = context->config->save_to_disk; bool save_to_disk = context->config->save_to_disk;
char* root_directory = str_dup(context->config->receive_root_directory); char* root_directory = str_dup(context->config->receive_root_directory);
mtx_unlock(&context->mutex); mtx_unlock(&context->mutex);
if (save_to_disk && !root_directory) {
mtx_lock(&context->mutex);
atomic_store(&context->cancelled, true);
context->receiver_done = true;
cnd_broadcast(&context->condition_not_full);
cnd_broadcast(&context->condition_not_empty);
mtx_unlock(&context->mutex);
return thrd_error;
}
while (true) { while (true) {
File* file = File* file =
+5 -1
View File
@@ -35,7 +35,6 @@ typedef struct PipelineContextReceiver {
Config* config; Config* config;
int file_descriptor; int file_descriptor;
SSL* ssl; SSL* ssl;
ProtocolSession session;
mtx_t mutex; mtx_t mutex;
cnd_t condition_not_full; cnd_t condition_not_full;
cnd_t condition_not_empty; cnd_t condition_not_empty;
@@ -43,6 +42,11 @@ typedef struct PipelineContextReceiver {
atomic_bool cancelled; atomic_bool cancelled;
} PipelineContextReceiver; } PipelineContextReceiver;
typedef bool (*ReceivedFileHandler)(File* file, void* context);
int receive_files_common(const Config* config, int file_descriptor, ReceivedFileHandler handler,
void* context, bool send_completion_status);
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);
void pipeline_context_sender_destroy(PipelineContextSender* context); void pipeline_context_sender_destroy(PipelineContextSender* context);
+63 -154
View File
@@ -18,8 +18,6 @@
static __thread int io_read_fd = -1; static __thread int io_read_fd = -1;
static __thread int io_write_fd = -1; static __thread int io_write_fd = -1;
static __thread SSL* io_ssl; static __thread SSL* io_ssl;
static __thread ProtocolSession* bound_session;
static __thread ProtocolSession legacy_io_session = {.read_fd = -1, .write_fd = -1};
static unsigned long long io_bwlimit = 0; static unsigned long long io_bwlimit = 0;
static long long bw_tokens = 0; static long long bw_tokens = 0;
@@ -27,41 +25,15 @@ static struct timespec bw_last_refill = {0, 0};
static mtx_t bw_mutex; static mtx_t bw_mutex;
static once_flag bw_mutex_once = ONCE_FLAG_INIT; static once_flag bw_mutex_once = ONCE_FLAG_INIT;
static __thread unsigned long long total_allocated_bytes = 0;
void io_set_fds(int read_fd, int write_fd) { void io_set_fds(int read_fd, int write_fd) {
bound_session = NULL;
io_read_fd = read_fd; io_read_fd = read_fd;
io_write_fd = write_fd; io_write_fd = write_fd;
/* A descriptor switch starts a new transport; never reuse a TLS object /* A descriptor switch starts a new transport; never reuse a TLS object
belonging to a previous connection or test pipe. */ belonging to a previous connection or test pipe. */
io_ssl = NULL; io_ssl = NULL;
legacy_io_session.read_fd = read_fd; total_allocated_bytes = 0;
legacy_io_session.write_fd = write_fd;
legacy_io_session.ssl = NULL;
legacy_io_session.total_allocated_bytes = 0;
protocol_session_set_bwlimit(&legacy_io_session, io_bwlimit);
}
void protocol_session_init(ProtocolSession* session, int read_fd, int write_fd) {
if (!session)
return;
memset(session, 0, sizeof(*session));
session->read_fd = read_fd;
session->write_fd = write_fd;
if (io_bwlimit)
protocol_session_set_bwlimit(session, io_bwlimit);
}
void protocol_session_bind(ProtocolSession* session) {
bound_session = session;
}
void protocol_session_unbind(void) {
bound_session = NULL;
}
void protocol_session_set_ssl(ProtocolSession* session, SSL* ssl) {
if (session)
session->ssl = ssl;
} }
static void bw_mutex_init(void) { static void bw_mutex_init(void) {
@@ -77,51 +49,39 @@ void io_set_bwlimit(unsigned long long bytes_per_sec) {
mtx_unlock(&bw_mutex); mtx_unlock(&bw_mutex);
} }
void protocol_session_set_bwlimit(ProtocolSession* session, unsigned long long bytes_per_sec) { static void bw_throttle(size_t bytes_written) {
if (!session) if (io_bwlimit == 0)
return;
session->bwlimit = bytes_per_sec;
session->bw_tokens = (long long)bytes_per_sec;
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
session->bw_last_refill_sec = now.tv_sec;
session->bw_last_refill_nsec = now.tv_nsec;
}
static void bw_throttle_session(ProtocolSession* session, size_t bytes_written) {
if (session->bwlimit == 0)
return; return;
call_once(&bw_mutex_once, bw_mutex_init);
mtx_lock(&bw_mutex);
struct timespec now; struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now); clock_gettime(CLOCK_MONOTONIC, &now);
long long elapsed_ns = (now.tv_sec - session->bw_last_refill_sec) * 1000000000LL + long long elapsed_ns =
(now.tv_nsec - session->bw_last_refill_nsec); (now.tv_sec - bw_last_refill.tv_sec) * 1000000000LL + (now.tv_nsec - bw_last_refill.tv_nsec);
session->bw_last_refill_sec = now.tv_sec; bw_last_refill = now;
session->bw_last_refill_nsec = now.tv_nsec;
long long tokens_to_add = (long long)((double)session->bwlimit * elapsed_ns / 1000000000.0); long long tokens_to_add = (long long)((double)io_bwlimit * elapsed_ns / 1000000000.0);
session->bw_tokens += tokens_to_add; bw_tokens += tokens_to_add;
if (session->bw_tokens > (long long)session->bwlimit) if (bw_tokens > (long long)io_bwlimit)
session->bw_tokens = (long long)session->bwlimit; bw_tokens = (long long)io_bwlimit;
session->bw_tokens -= bytes_written; bw_tokens -= (long long)bytes_written;
if (session->bw_tokens < 0) { if (bw_tokens < 0) {
long long deficit_us = long long deficit_us = (long long)((double)(-bw_tokens) / io_bwlimit * 1000000.0);
(long long)((double)(-session->bw_tokens) / session->bwlimit * 1000000.0);
if (deficit_us >= 1000) if (deficit_us >= 1000)
poll(NULL, 0, (int)(deficit_us / 1000)); poll(NULL, 0, (int)(deficit_us / 1000));
else else
usleep((useconds_t)deficit_us); usleep((useconds_t)deficit_us);
session->bw_tokens = 0; bw_tokens = 0;
session->bw_last_refill_sec = now.tv_sec; clock_gettime(CLOCK_MONOTONIC, &bw_last_refill);
session->bw_last_refill_nsec = now.tv_nsec;
} }
mtx_unlock(&bw_mutex);
} }
void io_set_ssl(SSL* ssl) { void io_set_ssl(SSL* ssl) {
bound_session = NULL;
io_ssl = ssl; io_ssl = ssl;
} }
@@ -129,30 +89,8 @@ SSL* io_get_ssl(void) {
return io_ssl; return io_ssl;
} }
static ProtocolSession* legacy_session(int read_fd, int write_fd) { static int io_fd(int dir_fd, int file_descriptor) {
if (bound_session) return (dir_fd != -1) ? dir_fd : file_descriptor;
return bound_session;
int target_read_fd = io_read_fd != -1 ? io_read_fd : read_fd;
int target_write_fd = io_write_fd != -1 ? io_write_fd : write_fd;
if (legacy_io_session.read_fd != target_read_fd ||
legacy_io_session.write_fd != target_write_fd) {
legacy_io_session.read_fd = target_read_fd;
legacy_io_session.write_fd = target_write_fd;
legacy_io_session.total_allocated_bytes = 0;
protocol_session_set_bwlimit(&legacy_io_session, io_bwlimit);
} else if (legacy_io_session.bwlimit != io_bwlimit) {
protocol_session_set_bwlimit(&legacy_io_session, io_bwlimit);
}
legacy_io_session.ssl = io_ssl;
return &legacy_io_session;
}
bool send_n_data(int file_descriptor, const void* data, size_t data_size) {
return protocol_send_n_data(legacy_session(-1, file_descriptor), data, data_size);
}
bool receive_n_data(int file_descriptor, void* data, size_t data_size) {
return protocol_receive_n_data(legacy_session(file_descriptor, -1), data, data_size);
} }
static int deadline_remaining_ms(const struct timespec* deadline) { static int deadline_remaining_ms(const struct timespec* deadline) {
@@ -166,11 +104,9 @@ static int deadline_remaining_ms(const struct timespec* deadline) {
return ms > INT_MAX ? INT_MAX : (int)ms; return ms > INT_MAX ? INT_MAX : (int)ms;
} }
bool protocol_send_n_data(ProtocolSession* session, const void* data, size_t data_size) { bool send_n_data(int file_descriptor, const void* data, size_t data_size) {
log_message(LOG_LEVEL_DEBUG, " Sending n Data: %zu", data_size); log_message(LOG_LEVEL_DEBUG, " Sending n Data: %zu", data_size);
if (!session) int fd = io_fd(io_write_fd, file_descriptor);
return false;
int fd = session->write_fd;
struct timespec deadline; struct timespec deadline;
clock_gettime(CLOCK_MONOTONIC, &deadline); clock_gettime(CLOCK_MONOTONIC, &deadline);
deadline.tv_sec += SEND_TIMEOUT_SEC; deadline.tv_sec += SEND_TIMEOUT_SEC;
@@ -178,7 +114,7 @@ bool protocol_send_n_data(ProtocolSession* session, const void* data, size_t dat
ssize_t total_bytes_send = 0; ssize_t total_bytes_send = 0;
while ((size_t)total_bytes_send < data_size) { while ((size_t)total_bytes_send < data_size) {
size_t chunk = data_size - total_bytes_send; size_t chunk = data_size - total_bytes_send;
if (session->bwlimit > 0 && chunk > 65536) if (io_bwlimit > 0 && chunk > 65536)
chunk = 65536; chunk = 65536;
struct pollfd pfd = {.fd = fd, .events = wait_events}; struct pollfd pfd = {.fd = fd, .events = wait_events};
int poll_result = poll(&pfd, 1, deadline_remaining_ms(&deadline)); int poll_result = poll(&pfd, 1, deadline_remaining_ms(&deadline));
@@ -191,13 +127,13 @@ bool protocol_send_n_data(ProtocolSession* session, const void* data, size_t dat
if (pfd.revents & (POLLERR | POLLNVAL)) if (pfd.revents & (POLLERR | POLLNVAL))
return false; return false;
ssize_t bytes_send; ssize_t bytes_send;
if (session->ssl) if (io_ssl)
bytes_send = SSL_write(session->ssl, (const char*)data + total_bytes_send, chunk); bytes_send = SSL_write(io_ssl, (const char*)data + total_bytes_send, chunk);
else 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) {
if (session->ssl) { if (io_ssl) {
int ssl_err = SSL_get_error(session->ssl, (int)bytes_send); int ssl_err = SSL_get_error(io_ssl, (int)bytes_send);
if (ssl_err == SSL_ERROR_WANT_WRITE || ssl_err == SSL_ERROR_WANT_READ) { if (ssl_err == SSL_ERROR_WANT_WRITE || ssl_err == SSL_ERROR_WANT_READ) {
wait_events = ssl_err == SSL_ERROR_WANT_WRITE ? POLLOUT : POLLIN; wait_events = ssl_err == SSL_ERROR_WANT_WRITE ? POLLOUT : POLLIN;
continue; continue;
@@ -206,18 +142,16 @@ bool protocol_send_n_data(ProtocolSession* session, const void* data, size_t dat
log_message(LOG_LEVEL_ERROR, "Could not send data"); log_message(LOG_LEVEL_ERROR, "Could not send data");
return false; return false;
} }
bw_throttle_session(session, (size_t)bytes_send); bw_throttle((size_t)bytes_send);
total_bytes_send += bytes_send; total_bytes_send += bytes_send;
} }
log_message(LOG_LEVEL_DEBUG, " Send n Data: %zu", total_bytes_send); log_message(LOG_LEVEL_DEBUG, " Send n Data: %zu", total_bytes_send);
return true; return true;
} }
bool protocol_receive_n_data(ProtocolSession* session, void* data, size_t data_size) { bool receive_n_data(int file_descriptor, void* data, size_t data_size) {
log_message(LOG_LEVEL_DEBUG, " Receiving n Data: %zu", data_size); log_message(LOG_LEVEL_DEBUG, " Receiving n Data: %zu", data_size);
if (!session) int fd = io_fd(io_read_fd, file_descriptor);
return false;
int fd = session->read_fd;
struct timespec deadline; struct timespec deadline;
clock_gettime(CLOCK_MONOTONIC, &deadline); clock_gettime(CLOCK_MONOTONIC, &deadline);
@@ -242,15 +176,15 @@ bool protocol_receive_n_data(ProtocolSession* session, void* data, size_t data_s
return false; return false;
ssize_t bytes_received; ssize_t bytes_received;
if (session->ssl) if (io_ssl)
bytes_received = SSL_read(session->ssl, (char*)data + total_bytes_received, bytes_received =
data_size - total_bytes_received); SSL_read(io_ssl, (char*)data + total_bytes_received, data_size - total_bytes_received);
else 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) {
if (session->ssl) { if (io_ssl) {
int ssl_err = SSL_get_error(session->ssl, (int)bytes_received); int ssl_err = SSL_get_error(io_ssl, (int)bytes_received);
if (ssl_err == SSL_ERROR_WANT_WRITE || ssl_err == SSL_ERROR_WANT_READ) { if (ssl_err == SSL_ERROR_WANT_WRITE || ssl_err == SSL_ERROR_WANT_READ) {
wait_events = ssl_err == SSL_ERROR_WANT_WRITE ? POLLOUT : POLLIN; wait_events = ssl_err == SSL_ERROR_WANT_WRITE ? POLLOUT : POLLIN;
continue; continue;
@@ -297,24 +231,24 @@ static const char* status_to_string(Status status) {
} }
} }
bool protocol_send_str(ProtocolSession* session, const char* data) { bool send_str(int file_descriptor, const char* data) {
if (data == NULL) if (data == NULL)
return false; return false;
size_t size = strlen(data); size_t size = strlen(data);
if (!protocol_send_n_data(session, &size, sizeof(size_t))) if (!send_n_data(file_descriptor, &size, sizeof(size_t)))
return false; return false;
if (!protocol_send_n_data(session, data, size)) if (!send_n_data(file_descriptor, data, size))
return false; return false;
log_message(LOG_LEVEL_DEBUG, "Send String: %s", data); log_message(LOG_LEVEL_DEBUG, "Send String: %s", data);
return true; return true;
} }
char* protocol_receive_str(ProtocolSession* session) { char* receive_str(int file_descriptor) {
size_t size; size_t size;
if (!protocol_receive_n_data(session, &size, sizeof(size_t))) if (!receive_n_data(file_descriptor, &size, sizeof(size_t)))
return NULL; return NULL;
if (size > MAX_STRING_SIZE || size > SIZE_MAX - 1 || if (size > MAX_STRING_SIZE || size > SIZE_MAX - 1 ||
size + 1 > MAX_CONNECTION_MEMORY - session->total_allocated_bytes) { size + 1 > MAX_CONNECTION_MEMORY - total_allocated_bytes) {
log_message(LOG_LEVEL_ERROR, "String size %zu exceeds maximum %llu", size, log_message(LOG_LEVEL_ERROR, "String size %zu exceeds maximum %llu", size,
(unsigned long long)MAX_STRING_SIZE); (unsigned long long)MAX_STRING_SIZE);
return NULL; return NULL;
@@ -322,29 +256,29 @@ char* protocol_receive_str(ProtocolSession* session) {
char* data = (char*)malloc(size + 1); char* data = (char*)malloc(size + 1);
if (data == NULL) if (data == NULL)
return NULL; return NULL;
if (!protocol_receive_n_data(session, data, size)) { if (!receive_n_data(file_descriptor, data, size)) {
free(data); free(data);
return NULL; return NULL;
} }
data[size] = '\0'; data[size] = '\0';
session->total_allocated_bytes += size + 1; total_allocated_bytes += size + 1;
log_message(LOG_LEVEL_DEBUG, "Received String: %s", data); log_message(LOG_LEVEL_DEBUG, "Received String: %s", data);
return data; return data;
} }
bool protocol_send_data(ProtocolSession* session, const Data* data) { bool send_data(int file_descriptor, const Data* data) {
unsigned long long data_size = data->size; unsigned long long data_size = data->size;
if (!protocol_send_n_data(session, &data_size, sizeof(unsigned long long))) if (!send_n_data(file_descriptor, &data_size, sizeof(unsigned long long)))
return false; return false;
if (!protocol_send_n_data(session, data->data, data_size)) if (!send_n_data(file_descriptor, data->data, data_size))
return false; return false;
log_message(LOG_LEVEL_DEBUG, "Send %lld data", data_size); log_message(LOG_LEVEL_DEBUG, "Send %lld data", data_size);
return true; return true;
} }
Data* protocol_receive_data(ProtocolSession* session) { Data* receive_data(int file_descriptor) {
unsigned long long size = 0; unsigned long long size = 0;
if (!protocol_receive_n_data(session, &size, sizeof(unsigned long long))) if (!receive_n_data(file_descriptor, &size, sizeof(unsigned long long)))
return NULL; return NULL;
if (size > MAX_DATA_PAYLOAD_SIZE) { if (size > MAX_DATA_PAYLOAD_SIZE) {
log_message(LOG_LEVEL_ERROR, "Data size %llu exceeds maximum %llu", size, log_message(LOG_LEVEL_ERROR, "Data size %llu exceeds maximum %llu", size,
@@ -352,78 +286,53 @@ Data* protocol_receive_data(ProtocolSession* session) {
return NULL; return NULL;
} }
size_t allocation_size = size == 0 ? 1 : (size_t)size; size_t allocation_size = size == 0 ? 1 : (size_t)size;
if (allocation_size > MAX_CONNECTION_MEMORY - session->total_allocated_bytes) { if (allocation_size > MAX_CONNECTION_MEMORY - total_allocated_bytes) {
log_message(LOG_LEVEL_ERROR, "Per-connection memory limit exceeded (%llu + %llu > %llu)", log_message(LOG_LEVEL_ERROR, "Per-connection memory limit exceeded (%llu + %llu > %llu)",
(unsigned long long)session->total_allocated_bytes, size, (unsigned long long)total_allocated_bytes, size,
(unsigned long long)MAX_CONNECTION_MEMORY); (unsigned long long)MAX_CONNECTION_MEMORY);
return NULL; return NULL;
} }
void* data = malloc(allocation_size); void* data = malloc(allocation_size);
if (data == NULL) if (data == NULL)
return NULL; return NULL;
if (!protocol_receive_n_data(session, data, (size_t)size)) { if (!receive_n_data(file_descriptor, data, (size_t)size)) {
free(data); free(data);
return NULL; return NULL;
} }
session->total_allocated_bytes += allocation_size; total_allocated_bytes += allocation_size;
log_message(LOG_LEVEL_DEBUG, "Received %lld data", size); log_message(LOG_LEVEL_DEBUG, "Received %lld data", size);
Data* result = data_create(data, (size_t)size); Data* result = data_create(data, (size_t)size);
if (!result) { if (!result) {
free(data); free(data);
session->total_allocated_bytes -= allocation_size; total_allocated_bytes -= allocation_size;
} }
return result; return result;
} }
bool protocol_send_int(ProtocolSession* session, int data) { bool send_int(int file_descriptor, int data) {
if (!protocol_send_n_data(session, &data, sizeof(int))) if (!send_n_data(file_descriptor, &data, sizeof(int)))
return false; return false;
log_message(LOG_LEVEL_DEBUG, "Send Int: %d", data); log_message(LOG_LEVEL_DEBUG, "Send Int: %d", data);
return true; return true;
} }
bool protocol_receive_int(ProtocolSession* session, int* data) { bool receive_int(int file_descriptor, int* data) {
if (!protocol_receive_n_data(session, data, sizeof(int))) if (!receive_n_data(file_descriptor, data, sizeof(int)))
return false; return false;
log_message(LOG_LEVEL_DEBUG, "Received Int: %d", *data); log_message(LOG_LEVEL_DEBUG, "Received Int: %d", *data);
return true; return true;
} }
bool protocol_send_status(ProtocolSession* session, Status status) { bool send_status(int file_descriptor, Status status) {
if (!protocol_send_n_data(session, &status, sizeof(Status))) if (!send_n_data(file_descriptor, &status, sizeof(Status)))
return false; return false;
log_message(LOG_LEVEL_DEBUG, "Send Status: %s", status_to_string(status)); log_message(LOG_LEVEL_DEBUG, "Send Status: %s", status_to_string(status));
return true; return true;
} }
bool protocol_receive_status(ProtocolSession* session, Status* status) { bool receive_status(int file_descriptor, Status* status) {
if (!protocol_receive_n_data(session, status, sizeof(Status))) if (!receive_n_data(file_descriptor, status, sizeof(Status)))
return false; return false;
log_message(LOG_LEVEL_DEBUG, "Received Status: %s", status_to_string(*status)); log_message(LOG_LEVEL_DEBUG, "Received Status: %s", status_to_string(*status));
return true; return true;
} }
bool send_str(int fd, const char* data) {
return protocol_send_str(legacy_session(-1, fd), data);
}
char* receive_str(int fd) {
return protocol_receive_str(legacy_session(fd, -1));
}
bool send_data(int fd, const Data* data) {
return protocol_send_data(legacy_session(-1, fd), data);
}
Data* receive_data(int fd) {
return protocol_receive_data(legacy_session(fd, -1));
}
bool send_int(int fd, int data) {
return protocol_send_int(legacy_session(-1, fd), data);
}
bool receive_int(int fd, int* data) {
return protocol_receive_int(legacy_session(fd, -1), data);
}
bool send_status(int fd, Status status) {
return protocol_send_status(legacy_session(-1, fd), status);
}
bool receive_status(int fd, Status* status) {
return protocol_receive_status(legacy_session(fd, -1), status);
}
-34
View File
@@ -19,23 +19,6 @@
typedef struct ssl_st SSL; typedef struct ssl_st SSL;
/*
* Explicit owner of protocol I/O. A session does not own the descriptors or
* SSL object; it only describes the transport used by a transfer. This makes
* it safe to pass the transport to a worker without relying on inherited
* thread-local state.
*/
typedef struct ProtocolSession {
int read_fd;
int write_fd;
SSL* ssl;
unsigned long long bwlimit;
long long bw_tokens;
long long bw_last_refill_sec;
long bw_last_refill_nsec;
unsigned long long total_allocated_bytes;
} ProtocolSession;
typedef int Status; typedef int Status;
enum NET_STATUS { enum NET_STATUS {
STATUS_OK, STATUS_OK,
@@ -56,23 +39,6 @@ void io_set_fds(int read_fd, int write_fd);
void io_set_bwlimit(unsigned long long bytes_per_sec); void io_set_bwlimit(unsigned long long bytes_per_sec);
void io_set_ssl(SSL* ssl); void io_set_ssl(SSL* ssl);
SSL* io_get_ssl(void); SSL* io_get_ssl(void);
void protocol_session_init(ProtocolSession* session, int read_fd, int write_fd);
/* Transitional bridge for helpers whose signatures still carry only an fd. */
void protocol_session_bind(ProtocolSession* session);
void protocol_session_unbind(void);
void protocol_session_set_ssl(ProtocolSession* session, SSL* ssl);
void protocol_session_set_bwlimit(ProtocolSession* session, unsigned long long bytes_per_sec);
bool protocol_send_n_data(ProtocolSession* session, const void* data, size_t data_size);
bool protocol_receive_n_data(ProtocolSession* session, void* data, size_t data_size);
bool protocol_send_str(ProtocolSession* session, const char* data);
char* protocol_receive_str(ProtocolSession* session);
bool protocol_send_data(ProtocolSession* session, const Data* data);
Data* protocol_receive_data(ProtocolSession* session);
bool protocol_send_int(ProtocolSession* session, int data);
bool protocol_receive_int(ProtocolSession* session, int* data);
bool protocol_send_status(ProtocolSession* session, Status status);
bool protocol_receive_status(ProtocolSession* session, Status* status);
bool send_n_data(int file_descriptor, const void* data, size_t data_size); bool send_n_data(int file_descriptor, const void* data, size_t data_size);
bool receive_n_data(int file_descriptor, void* data, size_t data_size); bool receive_n_data(int file_descriptor, void* data, size_t data_size);
+3 -9
View File
@@ -151,10 +151,6 @@ int tcp_get_contimeout_sec(void) {
return g_contimeout_sec; return g_contimeout_sec;
} }
int tcp_get_timeout_sec(void) {
return g_timeout_sec;
}
static void tcp_apply_socket_timeout(int fd) { static void tcp_apply_socket_timeout(int fd) {
struct timeval tv; struct timeval tv;
tv.tv_sec = g_timeout_sec; tv.tv_sec = g_timeout_sec;
@@ -177,7 +173,7 @@ Client* client_create() {
return client; return client;
} }
bool tcp_connect_socket(Client* client, char* host, int port) { bool tcp_connect_socket(Client* client, const char* host, int port) {
struct addrinfo hints; struct addrinfo hints;
struct addrinfo* result; struct addrinfo* result;
memset(&hints, 0, sizeof(hints)); memset(&hints, 0, sizeof(hints));
@@ -226,14 +222,12 @@ bool tcp_connect_socket(Client* client, char* host, int port) {
return false; return false;
} }
tcp_apply_socket_timeout(client->file_descriptor);
return true; return true;
} }
bool client_connect(Client* client, char* host, int port) { bool client_connect(Client* client, char* host, int port) {
if (!tcp_connect_socket(client, host, port)) return tcp_connect_socket(client, host, port);
return false;
tcp_apply_socket_timeout(client->file_descriptor);
return true;
} }
void client_disconnect(Client* client) { void client_disconnect(Client* client) {
+1 -2
View File
@@ -29,12 +29,11 @@ void server_accept_loop(Server* server, void (*child_fn)(int, void*), void* chil
const char* log_fmt); const char* log_fmt);
void server_delete(Server** server); void server_delete(Server** server);
Client* client_create(); Client* client_create();
bool tcp_connect_socket(Client* client, const char* host, int port);
bool client_connect(Client* client, char* host, int port); bool client_connect(Client* client, char* host, int port);
bool tcp_connect_socket(Client* client, char* host, int port);
void client_disconnect(Client* client); void client_disconnect(Client* client);
void client_delete(Client* client); void client_delete(Client* client);
void tcp_set_timeouts(int timeout_sec, int contimeout_sec); void tcp_set_timeouts(int timeout_sec, int contimeout_sec);
int tcp_get_contimeout_sec(void); int tcp_get_contimeout_sec(void);
int tcp_get_timeout_sec(void);
#endif #endif
+3 -5
View File
@@ -2,14 +2,14 @@
#include "log.h" #include "log.h"
#include "protocol.h" #include "protocol.h"
#include "transport_tcp.h" #include "transport_tcp.h"
#include <arpa/inet.h>
#include <openssl/err.h> #include <openssl/err.h>
#include <openssl/ssl.h> #include <openssl/ssl.h>
#include <signal.h> #include <signal.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <time.h> #include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h> #include <unistd.h>
bool tls_global_init(void) { bool tls_global_init(void) {
@@ -93,7 +93,6 @@ static SSL* wrap_fd_with_ssl(int fd, SSL_CTX* ctx, bool is_server, const char* h
} }
// Retry SSL_accept/SSL_connect on WANT_READ/WANT_WRITE (non-blocking handshake) // Retry SSL_accept/SSL_connect on WANT_READ/WANT_WRITE (non-blocking handshake)
time_t deadline = time(NULL) + (is_server ? tcp_get_timeout_sec() : tcp_get_contimeout_sec());
int ret; int ret;
do { do {
if (is_server) if (is_server)
@@ -103,8 +102,7 @@ static SSL* wrap_fd_with_ssl(int fd, SSL_CTX* ctx, bool is_server, const char* h
if (ret <= 0) { if (ret <= 0) {
int ssl_err = SSL_get_error(ssl, ret); int ssl_err = SSL_get_error(ssl, ret);
if ((ssl_err == SSL_ERROR_WANT_READ || ssl_err == SSL_ERROR_WANT_WRITE) && if (ssl_err == SSL_ERROR_WANT_READ || ssl_err == SSL_ERROR_WANT_WRITE)
time(NULL) < deadline)
continue; continue;
log_message(LOG_LEVEL_ERROR, "SSL %s failed", is_server ? "accept" : "connect"); log_message(LOG_LEVEL_ERROR, "SSL %s failed", is_server ? "accept" : "connect");
log_ssl_errors(); log_ssl_errors();
-4
View File
@@ -237,10 +237,6 @@ bool has_path_traversal(const char* path) {
return false; return false;
} }
bool utils_valid_batch_path(const char* path) {
return path && path[0] != '\0' && path[0] != '/' && !has_path_traversal(path);
}
char* path_cat(const char* path1, const char* path2) { char* path_cat(const char* path1, const char* path2) {
if (path1 == NULL || *path1 == '\0') if (path1 == NULL || *path1 == '\0')
return str_dup(path2); return str_dup(path2);
-1
View File
@@ -11,6 +11,5 @@ bool glob_match(const char* pattern, const char* str);
bool delete_extras(const char* dest_root, ArrayList* manifest); bool delete_extras(const char* dest_root, ArrayList* manifest);
void utils_set_authorized_root_fd(int fd); void utils_set_authorized_root_fd(int fd);
bool has_path_traversal(const char* path); bool has_path_traversal(const char* path);
bool utils_valid_batch_path(const char* path);
#endif #endif
+2 -57
View File
@@ -1,5 +1,4 @@
#include "test_client_cli.h" #include "test_client_cli.h"
#include "client_validation.h"
#include "config.h" #include "config.h"
#include "test_utils.h" #include "test_utils.h"
#include "utils.h" #include "utils.h"
@@ -10,56 +9,6 @@
/* Declaration of parse_args from client_cli.c */ /* Declaration of parse_args from client_cli.c */
int parse_args(Config* config, int argc, char* argv[], int* positional_args, int* positional_count); int parse_args(Config* config, int argc, char* argv[], int* positional_args, int* positional_count);
static Config* valid_client_config() {
Config* cfg = config_create();
if (!cfg)
return NULL;
cfg->send_directory = str_dup("/src");
cfg->receive_root_directory = str_dup("/dst");
return cfg;
}
static void test_validate_config_required_paths() {
Config* cfg = config_create();
EXPECT_FALSE(validate_config(cfg));
cfg->send_directory = str_dup("/src");
EXPECT_FALSE(validate_config(cfg));
config_delete(cfg);
}
static void test_validate_config_incompatible_options() {
Config* cfg = valid_client_config();
cfg->use_sendfile = true;
cfg->use_compression = true;
EXPECT_FALSE(validate_config(cfg));
cfg->use_compression = false;
cfg->use_incremental = true;
cfg->use_chunk_serialization = true;
EXPECT_FALSE(validate_config(cfg));
config_delete(cfg);
}
static void test_validate_config_tls_requirements() {
Config* cfg = valid_client_config();
cfg->use_tls = true;
EXPECT_FALSE(validate_config(cfg));
cfg->tls_cert = str_dup("cert.pem");
EXPECT_FALSE(validate_config(cfg));
cfg->tls_key = str_dup("key.pem");
EXPECT_TRUE(validate_config(cfg));
config_delete(cfg);
}
static void test_validate_config_delta_sendfile_constraints() {
Config* cfg = valid_client_config();
cfg->use_delta = true;
EXPECT_FALSE(validate_config(cfg));
cfg->use_incremental = true;
cfg->use_sendfile = true;
EXPECT_FALSE(validate_config(cfg));
config_delete(cfg);
}
/* Test main() with --help flag (early return path, no server connection needed) */ /* Test main() with --help flag (early return path, no server connection needed) */
static void test_cli_help() { static void test_cli_help() {
/* We can't easily call main() because it calls send_files which needs a server. /* We can't easily call main() because it calls send_files which needs a server.
@@ -307,8 +256,8 @@ static void test_parse_args_rejects_unimplemented_options() {
"--ipv4", "--ipv4",
"--daemon", "--daemon",
"--config", "--config",
"--server", "--server",
"--compress-choice"}; "--compress-choice"};
for (size_t i = 0; i < sizeof(options) / sizeof(options[0]); i++) { for (size_t i = 0; i < sizeof(options) / sizeof(options[0]); i++) {
Config* cfg = config_create(); Config* cfg = config_create();
@@ -338,10 +287,6 @@ static void test_parse_args_archive() {
} }
void test_client_cli() { void test_client_cli() {
test_validate_config_required_paths();
test_validate_config_incompatible_options();
test_validate_config_tls_requirements();
test_validate_config_delta_sendfile_constraints();
test_cli_help(); test_cli_help();
test_cli_archive_flags(); test_cli_archive_flags();
test_cli_dry_run(); test_cli_dry_run();
-21
View File
@@ -224,26 +224,6 @@ static void test_config_send_receive_version_mismatch() {
} }
} }
static void test_config_receive_truncated() {
int p[2];
EXPECT_EQ_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, p), 0);
io_set_fds(p[0], p[0]);
io_set_bwlimit(0);
/* A valid prefix exercises cleanup after allocated wire strings and a
* partially received scalar field. */
EXPECT_TRUE(send_str(p[1], PROTOCOL_VERSION));
EXPECT_TRUE(send_str(p[1], "/src"));
EXPECT_TRUE(send_str(p[1], "/dst"));
EXPECT_TRUE(send_int(p[1], 1));
shutdown(p[1], SHUT_WR);
const Config* cfg = config_receive(p[0]);
EXPECT_NULL(cfg);
close(p[0]);
close(p[1]);
}
static void test_is_remote_dest() { static void test_is_remote_dest() {
/* Valid SSH-style destinations */ /* Valid SSH-style destinations */
EXPECT_TRUE(is_remote_dest("user@host:/path")); EXPECT_TRUE(is_remote_dest("user@host:/path"));
@@ -276,7 +256,6 @@ void test_config() {
if (!is_running_under_valgrind()) { if (!is_running_under_valgrind()) {
test_config_send_receive(); test_config_send_receive();
test_config_send_receive_version_mismatch(); test_config_send_receive_version_mismatch();
test_config_receive_truncated();
} }
test_is_remote_dest(); test_is_remote_dest();
} }
-18
View File
@@ -38,23 +38,6 @@ static void test_send_receive_n_data_zero() {
close(p[1]); close(p[1]);
} }
static void test_explicit_session_context() {
int p[2];
EXPECT_EQ_INT(pipe(p), 0);
ProtocolSession session;
protocol_session_init(&session, p[0], p[1]);
protocol_session_set_bwlimit(&session, 0);
const char payload[] = "explicit context";
char received[sizeof(payload)] = {0};
EXPECT_TRUE(protocol_send_n_data(&session, payload, sizeof(payload)));
EXPECT_TRUE(protocol_receive_n_data(&session, received, sizeof(received)));
EXPECT_EQ_INT(memcmp(payload, received, sizeof(payload)), 0);
close(p[0]);
close(p[1]);
}
static void test_send_receive_str() { static void test_send_receive_str() {
int p[2]; int p[2];
EXPECT_EQ_INT(pipe(p), 0); EXPECT_EQ_INT(pipe(p), 0);
@@ -190,7 +173,6 @@ static void test_receive_str_truncated() {
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();
test_explicit_session_context();
test_send_receive_str(); test_send_receive_str();
test_send_receive_str_normal(); test_send_receive_str_normal();
test_send_receive_data(); test_send_receive_data();
-29
View File
@@ -385,34 +385,6 @@ static void test_scanner_no_patterns() {
rmdir(dir); rmdir(dir);
} }
static void test_parallel_scanner_root_chunks_without_workers() {
const char* dir = "test_parallel_scan_root";
const char* file1 = "test_parallel_scan_root/a.txt";
const char* file2 = "test_parallel_scan_root/b.txt";
EXPECT_EQ_INT(mkdir(dir, 0755), 0);
create_test_file(file1, "a");
create_test_file(file2, "b");
ParallelScanner* scanner = parallel_scanner_create(dir, false, 1, NULL, 0, NULL, 0, 0, 0, 0, 0,
false, false, false, false, false);
EXPECT_NOT_NULL(scanner);
int total_files = 0;
Chunk* chunk;
while ((chunk = parallel_scanner_next(scanner)) != NULL) {
total_files += chunk->element_count;
chunk_destroy(chunk);
}
EXPECT_EQ_INT(total_files, 2);
EXPECT_FALSE(parallel_scanner_failed(scanner));
parallel_scanner_destroy(scanner);
unlink(file1);
unlink(file2);
rmdir(dir);
}
void test_scanner() { void test_scanner() {
test_scanner_single_file(); test_scanner_single_file();
test_scanner_multiple_files(); test_scanner_multiple_files();
@@ -427,5 +399,4 @@ void test_scanner() {
test_scanner_size_range(); test_scanner_size_range();
test_scanner_mixed_patterns(); test_scanner_mixed_patterns();
test_scanner_no_patterns(); test_scanner_no_patterns();
test_parallel_scanner_root_chunks_without_workers();
} }
+8 -4
View File
@@ -11,7 +11,11 @@
#include <sys/wait.h> #include <sys/wait.h>
#include <unistd.h> #include <unistd.h>
#include "receiver.h" /* Include server.c but rename main to avoid conflict with test runner's main */
#define main server_main_
#define FASTSYNC_SERVER_AS_LIB
#include "server.c"
#undef main
/* Test receive_files with immediate FINISHED status */ /* Test receive_files with immediate FINISHED status */
static void test_receive_files_finished() { static void test_receive_files_finished() {
@@ -32,7 +36,7 @@ static void test_receive_files_finished() {
/* Child: use p[0] for both read and write */ /* Child: use p[0] for both read and write */
close(p[1]); close(p[1]);
io_set_fds(p[0], p[0]); io_set_fds(p[0], p[0]);
int ret = receiver_receive_files(cfg, p[0]); int ret = receive_files(cfg, p[0]);
close(p[0]); close(p[0]);
config_delete(cfg); config_delete(cfg);
_exit(ret == 0 ? 0 : 1); _exit(ret == 0 ? 0 : 1);
@@ -84,7 +88,7 @@ static void test_receive_files_single_file() {
/* Child: use p[0] for both read and write */ /* Child: use p[0] for both read and write */
close(p[1]); close(p[1]);
io_set_fds(p[0], p[0]); io_set_fds(p[0], p[0]);
int ret = receiver_receive_files(cfg, p[0]); int ret = receive_files(cfg, p[0]);
close(p[0]); close(p[0]);
config_delete(cfg); config_delete(cfg);
_exit(ret == 0 ? 0 : 1); _exit(ret == 0 ? 0 : 1);
@@ -145,7 +149,7 @@ static void test_receive_files_abort() {
if (pid == 0) { if (pid == 0) {
close(p[1]); close(p[1]);
io_set_fds(p[0], p[0]); io_set_fds(p[0], p[0]);
int ret = receiver_receive_files(cfg, p[0]); int ret = receive_files(cfg, p[0]);
close(p[0]); close(p[0]);
config_delete(cfg); config_delete(cfg);
/* Should return -1 on abort */ /* Should return -1 on abort */