fix: complete skip-compress protocol handling
CI / lint (pull_request) Successful in 11s
CI / sanitizers (address) (pull_request) Successful in 37s
CI / sanitizers (undefined) (pull_request) Successful in 37s
CI / fuzz-build (pull_request) Successful in 15s
CI / coverage (pull_request) Successful in 32s
CI / build-and-test (pull_request) Successful in 1m17s
CI / valgrind (pull_request) Successful in 33s
CI / lint (pull_request) Successful in 11s
CI / sanitizers (address) (pull_request) Successful in 37s
CI / sanitizers (undefined) (pull_request) Successful in 37s
CI / fuzz-build (pull_request) Successful in 15s
CI / coverage (pull_request) Successful in 32s
CI / build-and-test (pull_request) Successful in 1m17s
CI / valgrind (pull_request) Successful in 33s
This commit is contained in:
@@ -465,7 +465,7 @@ defaults to the current directory. |
|
|||||||
|
|
||||||
## Protocol and Security
|
## Protocol and Security
|
||||||
|
|
||||||
FastSync protocol version `2.2.0` is shared by the client and server. The
|
FastSync protocol version `2.2.1` is shared by the client and server. The
|
||||||
current protocol is sender-driven and includes configuration negotiation,
|
current protocol is sender-driven and includes configuration negotiation,
|
||||||
incremental checks, checksums, manifests, keep-alives, abort handling, and
|
incremental checks, checksums, manifests, keep-alives, abort handling, and
|
||||||
FastSync-native delta messages. Client and server versions must currently
|
FastSync-native delta messages. Client and server versions must currently
|
||||||
|
|||||||
+1
-2
@@ -174,7 +174,7 @@ This document maps rsync's full feature set to FastSync's current implementation
|
|||||||
| `--compress-choice=STR` | Choose compression algorithm | ❌ Not Implemented | Removed because it had no effect; FastSync always uses zstd |
|
| `--compress-choice=STR` | Choose compression algorithm | ❌ Not Implemented | Removed because it had no effect; FastSync always uses zstd |
|
||||||
| `--compress-level=NUM` | Set compression level | ✅ Implemented | 1-22, default 5 |
|
| `--compress-level=NUM` | Set compression level | ✅ Implemented | 1-22, default 5 |
|
||||||
| `--compress-threads=NUM` | Set compression threads | ❌ Not Implemented | |
|
| `--compress-threads=NUM` | Set compression threads | ❌ Not Implemented | |
|
||||||
| `--skip-compress=LIST` | Skip compress for suffixes | ✅ Implemented | Comma-separated, case-insensitive suffix list; empty list skips none |
|
| `--skip-compress=LIST` | Skip compress for suffixes | ✅ Implemented | Comma-separated, case-insensitive suffix list; empty list skips none; incompatible with FastSync chunk serialization (`-s`) |
|
||||||
|
|
||||||
## 13. Connectivity
|
## 13. Connectivity
|
||||||
|
|
||||||
@@ -253,7 +253,6 @@ Ranked by user demand, implementation complexity, and interoperability impact:
|
|||||||
| 7 | `--delay-updates` | Medium | High — atomic updates |
|
| 7 | `--delay-updates` | Medium | High — atomic updates |
|
||||||
| 8 | `--chmod` | Low | Medium — permission flexibility |
|
| 8 | `--chmod` | Low | Medium — permission flexibility |
|
||||||
| 9 | `--executability` / `-E` | Low | Low — simple flag |
|
| 9 | `--executability` / `-E` | Low | Low — simple flag |
|
||||||
| 10 | `--skip-compress` | Low | Medium — performance tuning |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ bool validate_config(const Config* config) {
|
|||||||
log_message(LOG_LEVEL_ERROR, "--incremental is not supported with -s (chunk serialization)");
|
log_message(LOG_LEVEL_ERROR, "--incremental is not supported with -s (chunk serialization)");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (config->skip_compress_set && config->use_chunk_serialization) {
|
||||||
|
log_message(LOG_LEVEL_ERROR,
|
||||||
|
"--skip-compress cannot be combined with -s (chunk serialization)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (config->use_delta && !config->use_incremental) {
|
if (config->use_delta && !config->use_incremental) {
|
||||||
log_message(LOG_LEVEL_ERROR, "--delta requires --incremental");
|
log_message(LOG_LEVEL_ERROR, "--delta requires --incremental");
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ static bool validate_received_config(const Config* config) {
|
|||||||
valid_wire_bool(config->delete_after) && valid_wire_bool(config->relative) &&
|
valid_wire_bool(config->delete_after) && valid_wire_bool(config->relative) &&
|
||||||
valid_wire_bool(config->prune_empty_dirs) && valid_wire_bool(config->partial) &&
|
valid_wire_bool(config->prune_empty_dirs) && valid_wire_bool(config->partial) &&
|
||||||
valid_wire_bool(config->delete_before) && valid_wire_bool(config->checksum) &&
|
valid_wire_bool(config->delete_before) && valid_wire_bool(config->checksum) &&
|
||||||
|
!(config->skip_compress_set && config->use_chunk_serialization) &&
|
||||||
(!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 &&
|
||||||
|
|||||||
+1
-1
@@ -132,7 +132,7 @@ typedef struct Config {
|
|||||||
bool skip_compress_set;
|
bool skip_compress_set;
|
||||||
} Config;
|
} Config;
|
||||||
|
|
||||||
#define PROTOCOL_VERSION "2.2.0"
|
#define PROTOCOL_VERSION "2.2.1"
|
||||||
#define DEFAULT_CHUNK_SIZE (10 * 1024 * 1024)
|
#define DEFAULT_CHUNK_SIZE (10 * 1024 * 1024)
|
||||||
|
|
||||||
Config* config_create(void);
|
Config* config_create(void);
|
||||||
|
|||||||
@@ -459,7 +459,10 @@ File* receive_incremental_check(int fd, const Config* config, bool* skipped) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config->use_compression) {
|
if (config->use_compression &&
|
||||||
|
!compression_should_skip_with_suffixes(file->path, config->skip_compress_suffixes,
|
||||||
|
config->skip_compress_set ? config->skip_compress_count
|
||||||
|
: -1)) {
|
||||||
Data* uncompressed = data_decompress_limited(file_data, MAX_RECEIVE_FILE_SIZE);
|
Data* uncompressed = data_decompress_limited(file_data, MAX_RECEIVE_FILE_SIZE);
|
||||||
data_destroy(file_data);
|
data_destroy(file_data);
|
||||||
if (uncompressed == NULL) {
|
if (uncompressed == NULL) {
|
||||||
|
|||||||
@@ -80,6 +80,35 @@ class TestSkipCompress:
|
|||||||
assert not missing, f"Missing: {missing}"
|
assert not missing, f"Missing: {missing}"
|
||||||
assert not mismatches, f"Mismatch: {mismatches}"
|
assert not mismatches, f"Mismatch: {mismatches}"
|
||||||
|
|
||||||
|
def test_skip_compress_incremental_full_fallback(self, shared_server):
|
||||||
|
clean_dir(DEST_DIR)
|
||||||
|
path = os.path.join(SOURCE_DIR, "incremental-skip.TXT")
|
||||||
|
with open(path, "wb") as f:
|
||||||
|
f.write(b"original skipped content\n")
|
||||||
|
flags = ["-c", "-M", "--skip-compress=.txt"]
|
||||||
|
result, _ = run_client(SOURCE_DIR, DEST_DIR, flags=flags, port=shared_server.port)
|
||||||
|
assert result.returncode == 0, f"Initial sync failed: {(result.stderr or result.stdout)[:200]}"
|
||||||
|
with open(path, "wb") as f:
|
||||||
|
f.write(b"updated skipped content\n")
|
||||||
|
result, _ = run_client(
|
||||||
|
SOURCE_DIR, DEST_DIR,
|
||||||
|
flags=flags + ["--incremental"],
|
||||||
|
port=shared_server.port,
|
||||||
|
)
|
||||||
|
assert result.returncode == 0, f"Incremental sync failed: {(result.stderr or result.stdout)[:200]}"
|
||||||
|
received = get_dest_received_dir(DEST_DIR, SOURCE_DIR)
|
||||||
|
with open(os.path.join(received, "incremental-skip.TXT"), "rb") as f:
|
||||||
|
assert f.read() == b"updated skipped content\n"
|
||||||
|
|
||||||
|
def test_skip_compress_rejects_chunk_serialization(self, shared_server):
|
||||||
|
result, _ = run_client(
|
||||||
|
SOURCE_DIR, DEST_DIR,
|
||||||
|
flags=["-c", "-s", "--skip-compress=.txt"],
|
||||||
|
port=shared_server.port,
|
||||||
|
)
|
||||||
|
assert result.returncode != 0
|
||||||
|
assert "cannot be combined" in (result.stderr or result.stdout)
|
||||||
|
|
||||||
|
|
||||||
class TestExclude:
|
class TestExclude:
|
||||||
def test_exclude_single(self, shared_server):
|
def test_exclude_single(self, shared_server):
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ static void test_validate_config_incompatible_options() {
|
|||||||
cfg->use_incremental = true;
|
cfg->use_incremental = true;
|
||||||
cfg->use_chunk_serialization = true;
|
cfg->use_chunk_serialization = true;
|
||||||
EXPECT_FALSE(validate_config(cfg));
|
EXPECT_FALSE(validate_config(cfg));
|
||||||
|
cfg->use_incremental = false;
|
||||||
|
cfg->skip_compress_set = true;
|
||||||
|
EXPECT_FALSE(validate_config(cfg));
|
||||||
config_delete(cfg);
|
config_delete(cfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+9
-2
@@ -121,11 +121,15 @@ static void test_config_send_receive() {
|
|||||||
send_cfg->receive_root_directory = str_dup("/send/dst");
|
send_cfg->receive_root_directory = str_dup("/send/dst");
|
||||||
send_cfg->save_to_disk = true;
|
send_cfg->save_to_disk = true;
|
||||||
send_cfg->use_multithreading = true;
|
send_cfg->use_multithreading = true;
|
||||||
send_cfg->use_chunk_serialization = true;
|
send_cfg->use_chunk_serialization = false;
|
||||||
send_cfg->use_compression = true;
|
send_cfg->use_compression = true;
|
||||||
send_cfg->use_metadata = true;
|
send_cfg->use_metadata = true;
|
||||||
send_cfg->compression_level = 5;
|
send_cfg->compression_level = 5;
|
||||||
send_cfg->chunk_size = 1024;
|
send_cfg->chunk_size = 1024;
|
||||||
|
send_cfg->skip_compress_set = true;
|
||||||
|
send_cfg->skip_compress_count = 1;
|
||||||
|
send_cfg->skip_compress_suffixes = calloc(1, sizeof(char*));
|
||||||
|
send_cfg->skip_compress_suffixes[0] = str_dup(".zip");
|
||||||
|
|
||||||
/* Use socketpair for bidirectional communication */
|
/* Use socketpair for bidirectional communication */
|
||||||
int p[2];
|
int p[2];
|
||||||
@@ -154,12 +158,15 @@ static void test_config_send_receive() {
|
|||||||
ok = false;
|
ok = false;
|
||||||
if (!recv_cfg->use_multithreading)
|
if (!recv_cfg->use_multithreading)
|
||||||
ok = false;
|
ok = false;
|
||||||
if (!recv_cfg->use_chunk_serialization)
|
if (recv_cfg->use_chunk_serialization)
|
||||||
ok = false;
|
ok = false;
|
||||||
if (recv_cfg->compression_level != 5)
|
if (recv_cfg->compression_level != 5)
|
||||||
ok = false;
|
ok = false;
|
||||||
if (recv_cfg->chunk_size != 1024)
|
if (recv_cfg->chunk_size != 1024)
|
||||||
ok = false;
|
ok = false;
|
||||||
|
if (!recv_cfg->skip_compress_set || recv_cfg->skip_compress_count != 1 ||
|
||||||
|
strcmp(recv_cfg->skip_compress_suffixes[0], ".zip") != 0)
|
||||||
|
ok = false;
|
||||||
}
|
}
|
||||||
config_delete(recv_cfg);
|
config_delete(recv_cfg);
|
||||||
close(p[0]);
|
close(p[0]);
|
||||||
|
|||||||
Reference in New Issue
Block a user