Merge remote-tracking branch 'origin/feat/ignore-existing' into dev

This commit is contained in:
2026-09-04 17:59:50 +02:00
parent c650f9a3d0
commit 55172ff6de
11 changed files with 238 additions and 18 deletions
+6 -4
View File
@@ -75,6 +75,7 @@ static void config_set_defaults(Config* config) {
config->human_readable = false;
config->eight_bit_output = false;
config->existing = false;
config->ignore_existing = false;
config->update = false;
config->inplace = false;
config->use_fsync = false;
@@ -131,7 +132,8 @@ static bool validate_received_config(const Config* config) {
valid_wire_bool(config->safe_links) && valid_wire_bool(config->copy_unsafe_links) &&
valid_wire_bool(config->preserve_hard_links) && valid_wire_bool(config->preserve_acls) &&
valid_wire_bool(config->preserve_xattrs) && valid_wire_bool(config->preserve_devices) &&
valid_wire_bool(config->preserve_sparse) && valid_wire_bool(config->existing) &&
valid_wire_bool(config->preserve_sparse) && valid_wire_bool(config->ignore_existing) &&
valid_wire_bool(config->existing) &&
valid_wire_bool(config->update) && valid_wire_bool(config->inplace) &&
valid_wire_bool(config->append) && valid_wire_bool(config->use_fsync) &&
valid_wire_bool(config->append_verify) &&
@@ -257,7 +259,7 @@ static bool send_file_options(int fd, const Config* c) {
}
static bool send_selection_options(int fd, const Config* c) {
return send_int(fd, c->existing) && send_int(fd, c->update) && send_int(fd, c->inplace) &&
return send_int(fd, c->ignore_existing) && send_int(fd, c->existing) && send_int(fd, c->update) && send_int(fd, c->inplace) &&
send_int(fd, c->append) && send_int(fd, c->use_fsync) &&
send_int(fd, c->append_verify) &&
send_int(fd, c->delete_excluded) && send_int(fd, c->delete_after) &&
@@ -328,9 +330,9 @@ static bool receive_file_options(int fd, Config* c) {
}
static bool receive_selection_options(int fd, Config* c) {
bool* flags[] = {&c->existing, &c->update, &c->inplace, &c->append,
bool* flags[] = {&c->ignore_existing, &c->existing, &c->update, &c->inplace, &c->append,
&c->use_fsync,
&c->append_verify, &c->delete_excluded, &c->delete_after};
&c->append_verify, &c->delete_excluded, &c->delete_after};
for (size_t i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
if (!receive_wire_bool(fd, flags[i]))
return false;
+1
View File
@@ -83,6 +83,7 @@ typedef struct Config {
// Issue #127: Transfer modes
bool existing;
bool ignore_existing;
bool update;
bool inplace;
bool use_fsync;
+35 -7
View File
@@ -172,8 +172,17 @@ bool file_set_authorized_root(int fd, const char* canonical_path) {
}
bool file_path_exists_secure(const char* path) {
if (!path)
return false;
char* leaf = NULL;
int parent_fd = file_open_secure_parent(path, &leaf, false);
if (parent_fd < 0)
return false;
struct stat st;
return file_stat_secure(path, &st);
bool exists = fstatat(parent_fd, leaf, &st, AT_SYMLINK_NOFOLLOW) == 0;
close(parent_fd);
free(leaf);
return exists;
}
bool file_stat_secure(const char* path, struct stat* st) {
@@ -318,7 +327,8 @@ bool file_rename_secure(const char* old_path, const char* new_path) {
static bool file_to_disk_secure_impl(const char* path, const void* data,
unsigned long long data_size, bool inplace, bool sparse,
const FileMetadata* metadata, bool update, bool use_fsync) {
const FileMetadata* metadata, bool update, bool no_replace,
bool use_fsync) {
char* leaf = NULL;
int dirfd = file_open_secure_parent(path, &leaf, true);
if (dirfd < 0)
@@ -374,8 +384,20 @@ static bool file_to_disk_secure_impl(const char* path, const void* data,
if (close(fd) != 0)
ok = false;
fd = -1;
if (ok && renameat(dirfd, tmp, dirfd, leaf) != 0)
ok = false;
if (ok) {
if (no_replace) {
/* The probe and commit cannot be one operation. A concurrent
creator may win; EEXIST is then the requested skip. */
if (linkat(dirfd, tmp, dirfd, leaf, 0) == 0 || errno == EEXIST) {
if (unlinkat(dirfd, tmp, 0) != 0 && errno != ENOENT)
ok = false;
} else {
ok = false;
}
} else if (renameat(dirfd, tmp, dirfd, leaf) != 0) {
ok = false;
}
}
if (!ok)
unlinkat(dirfd, tmp, 0);
}
@@ -389,21 +411,27 @@ static bool file_to_disk_secure_impl(const char* path, const void* data,
bool file_to_disk_secure(const char* path, const void* data, unsigned long long data_size,
bool inplace, bool sparse, const FileMetadata* metadata) {
return file_to_disk_secure_impl(path, data, data_size, inplace, sparse, metadata, false, false);
return file_to_disk_secure_impl(path, data, data_size, inplace, sparse, metadata, false, false, false);
}
bool file_to_disk_secure_update(const char* path, const void* data, unsigned long long data_size,
bool inplace, bool sparse, const FileMetadata* metadata) {
return file_to_disk_secure_impl(path, data, data_size, inplace, sparse, metadata, true, false);
return file_to_disk_secure_impl(path, data, data_size, inplace, sparse, metadata, true, false, false);
}
bool file_to_disk_secure_with_fsync(const char* path, const void* data,
unsigned long long data_size, bool inplace, bool sparse,
const FileMetadata* metadata, bool use_fsync) {
return file_to_disk_secure_impl(path, data, data_size, inplace, sparse, metadata, false,
return file_to_disk_secure_impl(path, data, data_size, inplace, sparse, metadata, false, false,
use_fsync);
}
bool file_to_disk_secure_no_replace(const char* path, const void* data,
unsigned long long data_size, bool sparse,
const FileMetadata* metadata) {
return file_to_disk_secure_impl(path, data, data_size, false, sparse, metadata, false, true, false);
}
bool file_write_to_disk(const char* path, const void* data, unsigned long long data_size,
bool inplace, bool sparse) {
if (!path || (!data && data_size != 0) || has_path_traversal(path))
+3
View File
@@ -41,5 +41,8 @@ bool file_to_disk_secure_with_fsync(const char* path, const void* data,
an unavoidable final rename race without filesystem locking. */
bool file_to_disk_secure_update(const char* path, const void* data, unsigned long long data_size,
bool inplace, bool sparse, const FileMetadata* metadata);
bool file_to_disk_secure_no_replace(const char* path, const void* data,
unsigned long long data_size, bool sparse,
const FileMetadata* metadata);
#endif
+25 -7
View File
@@ -22,7 +22,9 @@
#define MAX_FILE_DATA_SIZE MAX_RECEIVE_FILE_SIZE
bool file_save_to_disk(const char* root_directory, const File* file, const Config* config) {
bool backup_enabled = config && config->backup;
/* Backups are incompatible with ignore-existing: moving the entry first
would make a concurrent no-replace commit overwrite its old name. */
bool backup_enabled = config && config->backup && !config->ignore_existing;
bool inplace = config && config->inplace;
bool sparse = config && config->preserve_sparse;
const char* backup_suffix = (config && config->suffix) ? config->suffix : "~";
@@ -73,6 +75,19 @@ bool file_save_to_disk(const char* root_directory, const File* file, const Confi
free(disk_path);
return true;
}
/* --ignore-existing checks the final destination before partial files or
overwrite policies can modify it. */
if (config && config->ignore_existing) {
bool exists = file_path_exists_secure(destination_path);
if (exists) {
free(confined_backup);
free(confined_partial);
free(destination_path);
free(disk_path);
return true;
}
}
free(destination_path);
destination_path = NULL;
@@ -115,12 +130,15 @@ bool file_save_to_disk(const char* root_directory, const File* file, const Confi
}
}
bool ok = config && config->update
? file_to_disk_secure_update(disk_path, file->data->data, file->data->size, inplace,
sparse, file->metadata)
: file_to_disk_secure_with_fsync(disk_path, file->data->data, file->data->size,
inplace, sparse, file->metadata,
config && config->use_fsync);
bool ok = config && config->ignore_existing
? file_to_disk_secure_no_replace(disk_path, file->data->data, file->data->size,
sparse, file->metadata)
: config && config->update
? file_to_disk_secure_update(disk_path, file->data->data, file->data->size,
inplace, sparse, file->metadata)
: file_to_disk_secure_with_fsync(disk_path, file->data->data, file->data->size,
inplace, sparse, file->metadata,
config && config->use_fsync);
free(parent_copy);
free(backup_path);
free(confined_backup);