feat: add rsync-compatible fsync
CI / lint (pull_request) Successful in 12s
CI / sanitizers (address) (pull_request) Successful in 40s
CI / sanitizers (undefined) (pull_request) Successful in 38s
CI / fuzz-build (pull_request) Successful in 14s
CI / coverage (pull_request) Successful in 31s
CI / build-and-test (pull_request) Successful in 1m15s
CI / valgrind (pull_request) Successful in 33s
CI / lint (pull_request) Successful in 12s
CI / sanitizers (address) (pull_request) Successful in 40s
CI / sanitizers (undefined) (pull_request) Successful in 38s
CI / fuzz-build (pull_request) Successful in 14s
CI / coverage (pull_request) Successful in 31s
CI / build-and-test (pull_request) Successful in 1m15s
CI / valgrind (pull_request) Successful in 33s
This commit is contained in:
+1
-1
@@ -229,7 +229,7 @@ This document maps rsync's full feature set to FastSync's current implementation
|
|||||||
|------|-------------------|-----------------|-------|
|
|------|-------------------|-----------------|-------|
|
||||||
| `--stop-after=MINS` | Stop after N minutes | ❌ Not Implemented | |
|
| `--stop-after=MINS` | Stop after N minutes | ❌ Not Implemented | |
|
||||||
| `--stop-at=TIME` | Stop at specified time | ❌ Not Implemented | |
|
| `--stop-at=TIME` | Stop at specified time | ❌ Not Implemented | |
|
||||||
| `--fsync` | Fsync every written file | ❌ Not Implemented | |
|
| `--fsync` | Fsync every written file before publication | ✅ Implemented | |
|
||||||
| `--protocol=NUM` | Force older protocol version | ❌ Not Implemented | |
|
| `--protocol=NUM` | Force older protocol version | ❌ Not Implemented | |
|
||||||
| `--iconv=CONVERT_SPEC` | Charset conversion | ❌ Not Implemented | |
|
| `--iconv=CONVERT_SPEC` | Charset conversion | ❌ Not Implemented | |
|
||||||
| `--checksum-seed=NUM` | Set checksum seed | ❌ Not Implemented | |
|
| `--checksum-seed=NUM` | Set checksum seed | ❌ Not Implemented | |
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ static const OptionEntry OPTION_TABLE[] = {
|
|||||||
{"--copy-unsafe-links", NULL, OPT_FLAG, offsetof(Config, copy_unsafe_links)},
|
{"--copy-unsafe-links", NULL, OPT_FLAG, offsetof(Config, copy_unsafe_links)},
|
||||||
{"--sparse", "-S", OPT_FLAG, offsetof(Config, preserve_sparse)},
|
{"--sparse", "-S", OPT_FLAG, offsetof(Config, preserve_sparse)},
|
||||||
{"--inplace", NULL, OPT_FLAG, offsetof(Config, inplace)},
|
{"--inplace", NULL, OPT_FLAG, offsetof(Config, inplace)},
|
||||||
|
{"--fsync", NULL, OPT_FLAG, offsetof(Config, use_fsync)},
|
||||||
{"--checksum", NULL, OPT_FLAG, offsetof(Config, checksum)},
|
{"--checksum", NULL, OPT_FLAG, offsetof(Config, checksum)},
|
||||||
|
|
||||||
{"--source-dir", NULL, OPT_STRING, offsetof(Config, send_directory)},
|
{"--source-dir", NULL, OPT_STRING, offsetof(Config, send_directory)},
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ void print_usage(void) {
|
|||||||
printf(" --copy-unsafe-links Only transform unsafe symlinks into referent files\n");
|
printf(" --copy-unsafe-links Only transform unsafe symlinks into referent files\n");
|
||||||
printf(" -S, --sparse Handle sparse files efficiently\n");
|
printf(" -S, --sparse Handle sparse files efficiently\n");
|
||||||
printf(" --inplace Update files in-place (no temp+rename)\n");
|
printf(" --inplace Update files in-place (no temp+rename)\n");
|
||||||
|
printf(" --fsync Fsync every written file before publication\n");
|
||||||
printf(" --compress-level <n> Compression level (default: 5)\n");
|
printf(" --compress-level <n> Compression level (default: 5)\n");
|
||||||
printf(" --help Show this help\n");
|
printf(" --help Show this help\n");
|
||||||
printf(" -V, --version Show version\n");
|
printf(" -V, --version Show version\n");
|
||||||
|
|||||||
+11
-8
@@ -70,6 +70,7 @@ static void config_set_defaults(Config* config) {
|
|||||||
config->human_readable = false;
|
config->human_readable = false;
|
||||||
config->update = false;
|
config->update = false;
|
||||||
config->inplace = false;
|
config->inplace = false;
|
||||||
|
config->use_fsync = false;
|
||||||
config->append = false;
|
config->append = false;
|
||||||
config->append_verify = false;
|
config->append_verify = false;
|
||||||
config->delete_excluded = false;
|
config->delete_excluded = false;
|
||||||
@@ -125,10 +126,11 @@ static bool validate_received_config(const Config* config) {
|
|||||||
valid_wire_bool(config->preserve_xattrs) && valid_wire_bool(config->preserve_devices) &&
|
valid_wire_bool(config->preserve_xattrs) && valid_wire_bool(config->preserve_devices) &&
|
||||||
valid_wire_bool(config->preserve_sparse) && valid_wire_bool(config->update) &&
|
valid_wire_bool(config->preserve_sparse) && valid_wire_bool(config->update) &&
|
||||||
valid_wire_bool(config->inplace) && valid_wire_bool(config->append) &&
|
valid_wire_bool(config->inplace) && valid_wire_bool(config->append) &&
|
||||||
valid_wire_bool(config->append_verify) && valid_wire_bool(config->delete_excluded) &&
|
valid_wire_bool(config->use_fsync) && valid_wire_bool(config->append_verify) &&
|
||||||
valid_wire_bool(config->delete_after) && valid_wire_bool(config->relative) &&
|
valid_wire_bool(config->delete_excluded) && valid_wire_bool(config->delete_after) &&
|
||||||
valid_wire_bool(config->prune_empty_dirs) && valid_wire_bool(config->partial) &&
|
valid_wire_bool(config->relative) && valid_wire_bool(config->prune_empty_dirs) &&
|
||||||
valid_wire_bool(config->delete_before) && valid_wire_bool(config->checksum) &&
|
valid_wire_bool(config->partial) && valid_wire_bool(config->delete_before) &&
|
||||||
|
valid_wire_bool(config->checksum) &&
|
||||||
(!config->use_compression ||
|
(!config->use_compression ||
|
||||||
(config->compression_level >= 1 && config->compression_level <= 22)) &&
|
(config->compression_level >= 1 && config->compression_level <= 22)) &&
|
||||||
config->chunk_size > 0 && config->chunk_size <= MAX_CHUNK_SIZE &&
|
config->chunk_size > 0 && config->chunk_size <= MAX_CHUNK_SIZE &&
|
||||||
@@ -244,9 +246,10 @@ static bool send_file_options(int fd, const Config* c) {
|
|||||||
|
|
||||||
static bool send_selection_options(int fd, const Config* c) {
|
static bool send_selection_options(int fd, const Config* c) {
|
||||||
return send_int(fd, c->update) && send_int(fd, c->inplace) && send_int(fd, c->append) &&
|
return send_int(fd, c->update) && send_int(fd, c->inplace) && send_int(fd, c->append) &&
|
||||||
send_int(fd, c->append_verify) && send_int(fd, c->delete_excluded) &&
|
send_int(fd, c->use_fsync) && send_int(fd, c->append_verify) &&
|
||||||
send_int(fd, c->delete_after) && send_n_data(fd, &c->max_delete, sizeof(c->max_delete)) &&
|
send_int(fd, c->delete_excluded) && send_int(fd, c->delete_after) &&
|
||||||
send_int(fd, c->relative) && send_int(fd, c->prune_empty_dirs);
|
send_n_data(fd, &c->max_delete, sizeof(c->max_delete)) && send_int(fd, c->relative) &&
|
||||||
|
send_int(fd, c->prune_empty_dirs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool send_resume_options(int fd, const Config* c) {
|
static bool send_resume_options(int fd, const Config* c) {
|
||||||
@@ -304,7 +307,7 @@ static bool receive_file_options(int fd, Config* c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool receive_selection_options(int fd, Config* c) {
|
static bool receive_selection_options(int fd, Config* c) {
|
||||||
bool* flags[] = {&c->update, &c->inplace, &c->append,
|
bool* flags[] = {&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++) {
|
for (size_t i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
|
||||||
if (!receive_wire_bool(fd, flags[i]))
|
if (!receive_wire_bool(fd, flags[i]))
|
||||||
|
|||||||
+2
-1
@@ -78,6 +78,7 @@ typedef struct Config {
|
|||||||
// Issue #127: Transfer modes
|
// Issue #127: Transfer modes
|
||||||
bool update;
|
bool update;
|
||||||
bool inplace;
|
bool inplace;
|
||||||
|
bool use_fsync;
|
||||||
bool append;
|
bool append;
|
||||||
bool append_verify;
|
bool append_verify;
|
||||||
|
|
||||||
@@ -128,7 +129,7 @@ typedef struct Config {
|
|||||||
char* compress_choice;
|
char* compress_choice;
|
||||||
} Config;
|
} Config;
|
||||||
|
|
||||||
#define PROTOCOL_VERSION "2.2.0"
|
#define PROTOCOL_VERSION "2.3.0"
|
||||||
#define DEFAULT_CHUNK_SIZE (10 * 1024 * 1024)
|
#define DEFAULT_CHUNK_SIZE (10 * 1024 * 1024)
|
||||||
|
|
||||||
Config* config_create(void);
|
Config* config_create(void);
|
||||||
|
|||||||
+12
-2
@@ -302,8 +302,9 @@ bool file_rename_secure(const char* old_path, const char* new_path) {
|
|||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool file_to_disk_secure(const char* path, const void* data, unsigned long long data_size,
|
bool file_to_disk_secure_with_fsync(const char* path, const void* data,
|
||||||
bool inplace, bool sparse, const FileMetadata* metadata) {
|
unsigned long long data_size, bool inplace, bool sparse,
|
||||||
|
const FileMetadata* metadata, bool use_fsync) {
|
||||||
char* leaf = NULL;
|
char* leaf = NULL;
|
||||||
int dirfd = file_open_secure_parent(path, &leaf, true);
|
int dirfd = file_open_secure_parent(path, &leaf, true);
|
||||||
if (dirfd < 0)
|
if (dirfd < 0)
|
||||||
@@ -317,6 +318,8 @@ bool file_to_disk_secure(const char* path, const void* data, unsigned long long
|
|||||||
ok = write_all(fd, data, data_size);
|
ok = write_all(fd, data, data_size);
|
||||||
if (ok && metadata)
|
if (ok && metadata)
|
||||||
ok = file_restore_metadata_fd(fd, metadata);
|
ok = file_restore_metadata_fd(fd, metadata);
|
||||||
|
if (ok && use_fsync)
|
||||||
|
ok = fsync(fd) == 0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
char tmp[NAME_MAX];
|
char tmp[NAME_MAX];
|
||||||
@@ -331,6 +334,8 @@ bool file_to_disk_secure(const char* path, const void* data, unsigned long long
|
|||||||
ok = write_all(fd, data, data_size);
|
ok = write_all(fd, data, data_size);
|
||||||
if (ok && metadata)
|
if (ok && metadata)
|
||||||
ok = file_restore_metadata_fd(fd, metadata);
|
ok = file_restore_metadata_fd(fd, metadata);
|
||||||
|
if (ok && use_fsync)
|
||||||
|
ok = fsync(fd) == 0;
|
||||||
if (close(fd) != 0)
|
if (close(fd) != 0)
|
||||||
ok = false;
|
ok = false;
|
||||||
fd = -1;
|
fd = -1;
|
||||||
@@ -347,6 +352,11 @@ bool file_to_disk_secure(const char* path, const void* data, unsigned long long
|
|||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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_with_fsync(path, data, data_size, inplace, sparse, metadata, false);
|
||||||
|
}
|
||||||
|
|
||||||
bool file_write_to_disk(const char* path, const void* data, unsigned long long data_size,
|
bool file_write_to_disk(const char* path, const void* data, unsigned long long data_size,
|
||||||
bool inplace, bool sparse) {
|
bool inplace, bool sparse) {
|
||||||
if (!path || (!data && data_size != 0) || has_path_traversal(path))
|
if (!path || (!data && data_size != 0) || has_path_traversal(path))
|
||||||
|
|||||||
@@ -32,5 +32,8 @@ bool file_ensure_directory_secure(const char* path);
|
|||||||
bool file_rename_secure(const char* old_path, const char* new_path);
|
bool file_rename_secure(const char* old_path, const char* new_path);
|
||||||
bool file_to_disk_secure(const char* path, const void* data, unsigned long long data_size,
|
bool file_to_disk_secure(const char* path, const void* data, unsigned long long data_size,
|
||||||
bool inplace, bool sparse, const FileMetadata* metadata);
|
bool inplace, bool sparse, const FileMetadata* metadata);
|
||||||
|
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);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -103,8 +103,8 @@ bool file_save_to_disk(const char* root_directory, const File* file, const Confi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ok = file_to_disk_secure(disk_path, file->data->data, file->data->size, inplace, sparse,
|
bool ok = file_to_disk_secure_with_fsync(disk_path, file->data->data, file->data->size, inplace,
|
||||||
file->metadata);
|
sparse, file->metadata, config && config->use_fsync);
|
||||||
free(parent_copy);
|
free(parent_copy);
|
||||||
free(backup_path);
|
free(backup_path);
|
||||||
free(confined_backup);
|
free(confined_backup);
|
||||||
|
|||||||
@@ -339,6 +339,17 @@ static void test_parse_args_archive() {
|
|||||||
config_delete(cfg);
|
config_delete(cfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_parse_args_fsync() {
|
||||||
|
Config* cfg = config_create();
|
||||||
|
char* argv[] = {"fastsync", "--fsync", "/src", "/dst"};
|
||||||
|
int positional_args[2];
|
||||||
|
int positional_count = 0;
|
||||||
|
|
||||||
|
EXPECT_EQ_INT(parse_args(cfg, 4, argv, positional_args, &positional_count), 0);
|
||||||
|
EXPECT_TRUE(cfg->use_fsync);
|
||||||
|
config_delete(cfg);
|
||||||
|
}
|
||||||
|
|
||||||
void test_client_cli() {
|
void test_client_cli() {
|
||||||
test_validate_config_required_paths();
|
test_validate_config_required_paths();
|
||||||
test_validate_config_incompatible_options();
|
test_validate_config_incompatible_options();
|
||||||
@@ -360,4 +371,5 @@ void test_client_cli() {
|
|||||||
test_parse_args_unknown_option();
|
test_parse_args_unknown_option();
|
||||||
test_parse_args_rejects_unimplemented_options();
|
test_parse_args_rejects_unimplemented_options();
|
||||||
test_parse_args_archive();
|
test_parse_args_archive();
|
||||||
|
test_parse_args_fsync();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ static void test_config_send_receive() {
|
|||||||
send_cfg->use_chunk_serialization = true;
|
send_cfg->use_chunk_serialization = true;
|
||||||
send_cfg->use_compression = true;
|
send_cfg->use_compression = true;
|
||||||
send_cfg->use_metadata = true;
|
send_cfg->use_metadata = true;
|
||||||
|
send_cfg->use_fsync = true;
|
||||||
send_cfg->compression_level = 5;
|
send_cfg->compression_level = 5;
|
||||||
send_cfg->chunk_size = 1024;
|
send_cfg->chunk_size = 1024;
|
||||||
|
|
||||||
@@ -160,6 +161,8 @@ static void test_config_send_receive() {
|
|||||||
ok = false;
|
ok = false;
|
||||||
if (recv_cfg->chunk_size != 1024)
|
if (recv_cfg->chunk_size != 1024)
|
||||||
ok = false;
|
ok = false;
|
||||||
|
if (!recv_cfg->use_fsync)
|
||||||
|
ok = false;
|
||||||
}
|
}
|
||||||
config_delete(recv_cfg);
|
config_delete(recv_cfg);
|
||||||
close(p[0]);
|
close(p[0]);
|
||||||
|
|||||||
@@ -108,6 +108,17 @@ static void test_file_write_to_disk_basic() {
|
|||||||
unlink("test_file_write_to_disk_basic.txt");
|
unlink("test_file_write_to_disk_basic.txt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_file_write_to_disk_with_fsync() {
|
||||||
|
const char* path = "test_file_write_to_disk_fsync.txt";
|
||||||
|
const char* content = "fsync file content";
|
||||||
|
EXPECT_TRUE(
|
||||||
|
file_to_disk_secure_with_fsync(path, content, strlen(content), false, false, NULL, true));
|
||||||
|
struct stat st;
|
||||||
|
EXPECT_EQ_INT(stat(path, &st), 0);
|
||||||
|
EXPECT_EQ_INT((int)st.st_size, (int)strlen(content));
|
||||||
|
unlink(path);
|
||||||
|
}
|
||||||
|
|
||||||
static void test_file_write_to_disk_creates_dirs() {
|
static void test_file_write_to_disk_creates_dirs() {
|
||||||
const char* content = "Nested dir test";
|
const char* content = "Nested dir test";
|
||||||
EXPECT_TRUE(file_write_to_disk("test_nested_tmp/nested/file.txt", content, strlen(content), false,
|
EXPECT_TRUE(file_write_to_disk("test_nested_tmp/nested/file.txt", content, strlen(content), false,
|
||||||
@@ -459,6 +470,7 @@ void test_file() {
|
|||||||
test_file_load_data_missing_file();
|
test_file_load_data_missing_file();
|
||||||
test_file_save_to_disk();
|
test_file_save_to_disk();
|
||||||
test_file_write_to_disk_basic();
|
test_file_write_to_disk_basic();
|
||||||
|
test_file_write_to_disk_with_fsync();
|
||||||
test_file_write_to_disk_creates_dirs();
|
test_file_write_to_disk_creates_dirs();
|
||||||
test_file_write_to_disk_does_not_follow_symlink();
|
test_file_write_to_disk_does_not_follow_symlink();
|
||||||
test_file_content_to_buffer();
|
test_file_content_to_buffer();
|
||||||
|
|||||||
Reference in New Issue
Block a user