#251 --remove-source-files deletes sources that were skipped receiver-side (--existing/--ignore-existing/--update). The receiver now reports a per-file outcome for every processed data file when the sender requests removal; the client only unlinks sources the receiver actually wrote. add remove_source_files to the wire config and bump the protocol to 2.5.0. #252 --backup/--suffix/--backup-dir broken by NULL-vs-empty wire loss. Receivers canonicalize the empty wire string back to NULL for backup_dir, temp_dir, partial_dir and suffix, and --suffix is received unconditionally. #253 --partial --partial-dir never installed completed files. file_save_to_disk now renames a fully written partial-dir file into the real destination. #255 STATUS_CHECK read the entire old file before the size/mtime quick check. Old contents are only read when a checksum compare or delta needs them. #256 receive_delta_file failure paths did not set *failed, so the caller sent STATUS_NEXT and waited for a body that never came. Every NULL return now marks the transfer failed. #257 files >64 MiB could not transfer. Whole-file receive caps raised to the 256 MiB connection/allocation ceiling (chunk caps stay 64 MiB) and the client ignores SIGPIPE so a server-side close surfaces as a clean error. Unit tests added: config NULL-vs-empty round trip, incremental quick-check skip/NEXT paths, delta oversize failure, partial-dir install, save-result skip reporting.
This commit is contained in:
@@ -109,16 +109,13 @@ static bool add_chunk_to_manifest(ArrayList* manifest, const Chunk* chunk) {
|
||||
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;
|
||||
}
|
||||
/* (finalize_transfer is defined after the SourceFile helpers below.) */
|
||||
|
||||
typedef struct {
|
||||
typedef struct SourceFile {
|
||||
char* path;
|
||||
dev_t device;
|
||||
ino_t inode;
|
||||
bool skipped; /* receiver reported the file was not written */
|
||||
} SourceFile;
|
||||
|
||||
static void source_file_destroy(void* item) {
|
||||
@@ -135,6 +132,8 @@ static void remove_transferred_sources(const Config* config, ArrayList* paths) {
|
||||
return;
|
||||
for (int i = 0; i < paths->size; i++) {
|
||||
SourceFile* source = paths->items[i];
|
||||
if (source->skipped)
|
||||
continue;
|
||||
const char* slash = strrchr(source->path, '/');
|
||||
const char* leaf = slash ? slash + 1 : source->path;
|
||||
char parent[PATH_MAX];
|
||||
@@ -177,6 +176,7 @@ static SourceFile* source_file_create(const File* file) {
|
||||
source->path = str_dup(file->path);
|
||||
source->device = st.st_dev;
|
||||
source->inode = st.st_ino;
|
||||
source->skipped = false;
|
||||
if (!source->path) {
|
||||
source_file_destroy(source);
|
||||
return NULL;
|
||||
@@ -203,6 +203,33 @@ static void mark_sender_done(PipelineContextSender* context) {
|
||||
mtx_unlock(&context->mutex_progress);
|
||||
}
|
||||
|
||||
/* Send the final STATUS_FINISHED frame and await the receiver's verdict.
|
||||
When --remove-source-files is active the receiver acknowledges each data
|
||||
file it processed, in send order: STATUS_NEXT means the file was written,
|
||||
STATUS_OK means the file was skipped/unchanged. Skipped sources are marked
|
||||
so the later removal pass keeps them. */
|
||||
static bool finalize_transfer(Client* client, const Config* config, ArrayList* remove_sources) {
|
||||
if (!send_status(client->file_descriptor, STATUS_FINISHED))
|
||||
return false;
|
||||
if (config->remove_source_files && remove_sources) {
|
||||
for (int i = 0; i < remove_sources->size; i++) {
|
||||
Status per_file;
|
||||
if (!receive_status(client->file_descriptor, &per_file))
|
||||
return false;
|
||||
if (per_file == STATUS_ERROR)
|
||||
return false;
|
||||
if (per_file == STATUS_OK) {
|
||||
((SourceFile*)remove_sources->items[i])->skipped = true;
|
||||
} else if (per_file != STATUS_NEXT) {
|
||||
log_message(LOG_LEVEL_ERROR, "Unexpected per-file status from receiver");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Status status;
|
||||
return receive_status(client->file_descriptor, &status) && status == STATUS_OK;
|
||||
}
|
||||
|
||||
static void pipeline_cancel(PipelineContextSender* context) {
|
||||
mtx_lock(&context->mutex_scanner);
|
||||
mtx_lock(&context->mutex_loader);
|
||||
@@ -575,7 +602,7 @@ static int send_chunks_multithreaded(void* pipeline_context) {
|
||||
if (send_delete_manifest(client->file_descriptor, context->manifest) != 0)
|
||||
goto send_fail;
|
||||
}
|
||||
bool ok = finalize_transfer(client);
|
||||
bool ok = finalize_transfer(client, context->config, context->remove_source_files);
|
||||
if (ok)
|
||||
remove_transferred_sources(context->config, context->remove_source_files);
|
||||
mtx_lock(&context->mutex_progress);
|
||||
@@ -866,7 +893,7 @@ int send_files(Config* config) {
|
||||
array_list_delete(manifest);
|
||||
manifest = NULL;
|
||||
}
|
||||
bool ok = finalize_transfer(client);
|
||||
bool ok = finalize_transfer(client, config, remove_sources);
|
||||
if (ok)
|
||||
remove_transferred_sources(config, remove_sources);
|
||||
if (config->show_progress && !config->quiet)
|
||||
|
||||
Reference in New Issue
Block a user