Merge remote-tracking branch 'origin/feat/ignore-existing' into dev
This commit is contained in:
@@ -230,6 +230,7 @@ static const OptionEntry OPTION_TABLE[] = {
|
||||
{"--checksum", NULL, OPT_FLAG, offsetof(Config, checksum)},
|
||||
{"--8-bit-output", "-8", OPT_FLAG, offsetof(Config, eight_bit_output)},
|
||||
{"--existing", NULL, OPT_FLAG, offsetof(Config, existing)},
|
||||
{"--ignore-existing", NULL, OPT_FLAG, offsetof(Config, ignore_existing)},
|
||||
|
||||
{"--source-dir", NULL, OPT_STRING, offsetof(Config, send_directory)},
|
||||
{"--dest-dir", NULL, OPT_STRING, offsetof(Config, receive_root_directory)},
|
||||
|
||||
@@ -23,6 +23,7 @@ void print_usage(void) {
|
||||
printf(" --progress Show transfer progress\n");
|
||||
printf(" -8, --8-bit-output Leave high-bit characters unescaped in output\n");
|
||||
printf(" --delete Delete files on receiver not in source\n");
|
||||
printf(" --ignore-existing Skip files that already exist on receiver\n");
|
||||
printf(" --exclude <pattern> Exclude files matching pattern\n");
|
||||
printf(" --include <pattern> Only include files matching pattern\n");
|
||||
printf(" --exclude-from <file> Read exclude patterns from file\n");
|
||||
|
||||
+6
-4
@@ -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;
|
||||
|
||||
@@ -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
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user