perf(protocol): pack metadata into one frame (PROTOCOL 2.20.0)
This commit is contained in:
@@ -94,14 +94,14 @@ def _seed_protocol_source(source):
|
||||
class TestProtocol:
|
||||
@pytest.mark.ci
|
||||
def test_protocol_current_version_accepted(self, shared_server):
|
||||
"""--protocol=2.19.0 (the current PROTOCOL_VERSION) is accepted and the
|
||||
"""--protocol=2.20.0 (the current PROTOCOL_VERSION) is accepted and the
|
||||
transfer completes normally."""
|
||||
source = os.path.join(TEST_DATA_DIR, "proto_ok_src")
|
||||
dest = os.path.join(TEST_DATA_DIR, "proto_ok_dst")
|
||||
shutil.rmtree(dest, ignore_errors=True)
|
||||
os.makedirs(dest)
|
||||
_seed_protocol_source(source)
|
||||
result, _ = run_client(source, dest, flags=["--protocol=2.19.0"],
|
||||
result, _ = run_client(source, dest, flags=["--protocol=2.20.0"],
|
||||
port=shared_server.port)
|
||||
assert result.returncode == 0, \
|
||||
f"--protocol current run failed: {(result.stderr or result.stdout)[:400]}"
|
||||
@@ -118,7 +118,7 @@ class TestProtocol:
|
||||
shutil.rmtree(dest, ignore_errors=True)
|
||||
os.makedirs(dest)
|
||||
_seed_protocol_source(source)
|
||||
for bad in ("2.18.0", "2.17.0", "2.15.0", "2.16.0", "216", "31"):
|
||||
for bad in ("2.19.0", "2.18.0", "2.17.0", "2.15.0", "2.16.0", "216", "31"):
|
||||
result, _ = run_client(source, dest, flags=[f"--protocol={bad}"],
|
||||
port=shared_server.port)
|
||||
assert result.returncode != 0, f"--protocol={bad} should be rejected"
|
||||
|
||||
@@ -259,7 +259,7 @@ static void test_parse_args_protocol_accept_current() {
|
||||
Config* cfg = valid_client_config();
|
||||
EXPECT_NOT_NULL(cfg);
|
||||
char* argv_equals[] = {"fastsync", "--source-dir", "/src",
|
||||
"--dest-dir", "/dst", "--protocol=2.19.0"};
|
||||
"--dest-dir", "/dst", "--protocol=2.20.0"};
|
||||
int positional_args[2];
|
||||
int positional_count = 0;
|
||||
EXPECT_EQ_INT(parse_args(cfg, 6, argv_equals, positional_args, &positional_count), 0);
|
||||
@@ -269,7 +269,7 @@ static void test_parse_args_protocol_accept_current() {
|
||||
cfg = valid_client_config();
|
||||
EXPECT_NOT_NULL(cfg);
|
||||
char* argv_space[] = {"fastsync", "--source-dir", "/src", "--dest-dir",
|
||||
"/dst", "--protocol", "2.19.0"};
|
||||
"/dst", "--protocol", "2.20.0"};
|
||||
positional_count = 0;
|
||||
EXPECT_EQ_INT(parse_args(cfg, 7, argv_space, positional_args, &positional_count), 0);
|
||||
EXPECT_EQ_STR(cfg->version, PROTOCOL_VERSION);
|
||||
@@ -279,8 +279,8 @@ static void test_parse_args_protocol_accept_current() {
|
||||
/* Any --protocol value other than the current PROTOCOL_VERSION must end in
|
||||
* failure (parse_args simply stores it; validate_config rejects it up front). */
|
||||
static void test_parse_args_protocol_rejects_other_versions() {
|
||||
static const char* const bad_versions[] = {"2.17", "2.16", "2.15.0", "2.16.0", "2.17.0",
|
||||
"2.18.0", "216", "31", "abc", ""};
|
||||
static const char* const bad_versions[] = {
|
||||
"2.17", "2.16", "2.15.0", "2.16.0", "2.17.0", "2.18.0", "2.19.0", "216", "31", "abc", ""};
|
||||
for (size_t i = 0; i < sizeof(bad_versions) / sizeof(bad_versions[0]); i++) {
|
||||
Config* cfg = valid_client_config();
|
||||
EXPECT_NOT_NULL(cfg);
|
||||
|
||||
+139
-31
@@ -5,6 +5,8 @@
|
||||
#include "test_utils.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
@@ -134,6 +136,115 @@ static void test_metadata_send_null() {
|
||||
close(p[1]);
|
||||
}
|
||||
|
||||
/* protocol 2.20.0: metadata is one packed frame. With metadata present the
|
||||
* wire record is exactly sizeof(int32_t) + FILE_METADATA_WIRE_SIZE bytes (the
|
||||
* present flag followed by the fixed field record); absent metadata is a lone
|
||||
* int32 zero. */
|
||||
static void test_metadata_wire_is_one_packed_frame() {
|
||||
io_set_bwlimit(0);
|
||||
int p[2];
|
||||
EXPECT_EQ_INT(pipe(p), 0);
|
||||
io_set_fds(p[0], p[1]);
|
||||
|
||||
FileMetadata original = {.mode = 0640,
|
||||
.uid = 42,
|
||||
.gid = 43,
|
||||
.mtime_sec = 111,
|
||||
.mtime_nsec = 222,
|
||||
.atime_valid = true,
|
||||
.atime_sec = 333,
|
||||
.atime_nsec = 444,
|
||||
.crtime_valid = false,
|
||||
.crtime_sec = 0,
|
||||
.crtime_nsec = 0};
|
||||
EXPECT_TRUE(metadata_send(p[1], &original));
|
||||
|
||||
unsigned char wire[sizeof(int32_t) + FILE_METADATA_WIRE_SIZE];
|
||||
EXPECT_EQ_INT((int)read(p[0], wire, sizeof(wire)), (int)sizeof(wire));
|
||||
int32_t flag;
|
||||
memcpy(&flag, wire, sizeof(flag));
|
||||
EXPECT_EQ_INT(flag, 1);
|
||||
int avail = -1;
|
||||
EXPECT_EQ_INT(ioctl(p[0], FIONREAD, &avail), 0);
|
||||
EXPECT_EQ_INT(avail, 0);
|
||||
|
||||
/* The present frame decodes in one shot with the shared codec. */
|
||||
char* cursor = (char*)wire;
|
||||
FileMetadata* decoded = metadata_from_buf(&cursor);
|
||||
EXPECT_NOT_NULL(decoded);
|
||||
EXPECT_EQ_INT((int)(cursor - (char*)wire), (int)sizeof(wire));
|
||||
EXPECT_EQ_INT(decoded->mode, 0640);
|
||||
EXPECT_EQ_INT(decoded->uid, 42);
|
||||
EXPECT_EQ_INT(decoded->gid, 43);
|
||||
EXPECT_EQ_INT(decoded->mtime_sec, 111);
|
||||
EXPECT_EQ_INT(decoded->mtime_nsec, 222);
|
||||
EXPECT_TRUE(decoded->atime_valid);
|
||||
EXPECT_EQ_INT(decoded->atime_sec, 333);
|
||||
EXPECT_EQ_INT(decoded->atime_nsec, 444);
|
||||
EXPECT_FALSE(decoded->crtime_valid);
|
||||
free(decoded);
|
||||
|
||||
/* Absent metadata is a lone int32 zero (4 bytes). */
|
||||
EXPECT_TRUE(metadata_send(p[1], NULL));
|
||||
EXPECT_EQ_INT((int)read(p[0], wire, sizeof(int32_t)), (int)sizeof(int32_t));
|
||||
memcpy(&flag, wire, sizeof(flag));
|
||||
EXPECT_EQ_INT(flag, 0);
|
||||
avail = -1;
|
||||
EXPECT_EQ_INT(ioctl(p[0], FIONREAD, &avail), 0);
|
||||
EXPECT_EQ_INT(avail, 0);
|
||||
|
||||
close(p[0]);
|
||||
close(p[1]);
|
||||
}
|
||||
|
||||
/* Round-trip over a socketpair (not just a pipe): present metadata compares
|
||||
* equal field-by-field and absent metadata yields NULL with ok == 1. */
|
||||
static void test_metadata_send_receive_socketpair() {
|
||||
io_set_bwlimit(0);
|
||||
int sv[2];
|
||||
EXPECT_EQ_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
|
||||
io_set_fds(sv[0], sv[1]);
|
||||
|
||||
FileMetadata original = {.mode = 0600,
|
||||
.uid = 7,
|
||||
.gid = 8,
|
||||
.mtime_sec = 1000,
|
||||
.mtime_nsec = 1,
|
||||
.atime_valid = true,
|
||||
.atime_sec = 2000,
|
||||
.atime_nsec = 2,
|
||||
.crtime_valid = true,
|
||||
.crtime_sec = 3000,
|
||||
.crtime_nsec = 3};
|
||||
EXPECT_TRUE(metadata_send(sv[1], &original));
|
||||
|
||||
int ok = 0;
|
||||
FileMetadata* received = metadata_receive(sv[0], &ok);
|
||||
EXPECT_NOT_NULL(received);
|
||||
EXPECT_EQ_INT(ok, 1);
|
||||
EXPECT_EQ_INT(received->mode, 0600);
|
||||
EXPECT_EQ_INT(received->uid, 7);
|
||||
EXPECT_EQ_INT(received->gid, 8);
|
||||
EXPECT_EQ_INT(received->mtime_sec, 1000);
|
||||
EXPECT_EQ_INT(received->mtime_nsec, 1);
|
||||
EXPECT_TRUE(received->atime_valid);
|
||||
EXPECT_EQ_INT(received->atime_sec, 2000);
|
||||
EXPECT_EQ_INT(received->atime_nsec, 2);
|
||||
EXPECT_TRUE(received->crtime_valid);
|
||||
EXPECT_EQ_INT(received->crtime_sec, 3000);
|
||||
EXPECT_EQ_INT(received->crtime_nsec, 3);
|
||||
free(received);
|
||||
|
||||
EXPECT_TRUE(metadata_send(sv[1], NULL));
|
||||
ok = 0;
|
||||
received = metadata_receive(sv[0], &ok);
|
||||
EXPECT_NULL(received);
|
||||
EXPECT_EQ_INT(ok, 1);
|
||||
|
||||
close(sv[0]);
|
||||
close(sv[1]);
|
||||
}
|
||||
|
||||
static void test_metadata_rejects_invalid_values() {
|
||||
int p[2];
|
||||
EXPECT_EQ_INT(pipe(p), 0);
|
||||
@@ -148,36 +259,30 @@ static void test_metadata_rejects_invalid_values() {
|
||||
}
|
||||
|
||||
/* metadata_receive must reject an out-of-range atime/crtime nsec even when the
|
||||
* flag would otherwise be valid (defense-in-depth on the -U/-N wire fields). */
|
||||
* flag would otherwise be valid (defense-in-depth on the -U/-N wire fields).
|
||||
* The packed record is built by the shared codec so the out-of-range value
|
||||
* actually reaches the wire. */
|
||||
static void test_metadata_receive_rejects_bad_optional_times() {
|
||||
int p[2];
|
||||
EXPECT_EQ_INT(pipe(p), 0);
|
||||
io_set_fds(p[0], p[1]);
|
||||
|
||||
int32_t present = 1;
|
||||
int32_t mode = 0644;
|
||||
int32_t uid = 1000;
|
||||
int32_t gid = 1000;
|
||||
int64_t mtime_sec = 1;
|
||||
int64_t mtime_nsec = 0;
|
||||
int32_t atime_valid = 1;
|
||||
int64_t atime_sec = 1;
|
||||
int64_t atime_nsec = 2000000000; /* invalid: >= 1e9 */
|
||||
EXPECT_TRUE(send_n_data(p[1], &present, sizeof(present)));
|
||||
EXPECT_TRUE(send_n_data(p[1], &mode, sizeof(mode)));
|
||||
EXPECT_TRUE(send_n_data(p[1], &uid, sizeof(uid)));
|
||||
EXPECT_TRUE(send_n_data(p[1], &gid, sizeof(gid)));
|
||||
EXPECT_TRUE(send_n_data(p[1], &mtime_sec, sizeof(mtime_sec)));
|
||||
EXPECT_TRUE(send_n_data(p[1], &mtime_nsec, sizeof(mtime_nsec)));
|
||||
EXPECT_TRUE(send_n_data(p[1], &atime_valid, sizeof(atime_valid)));
|
||||
EXPECT_TRUE(send_n_data(p[1], &atime_sec, sizeof(atime_sec)));
|
||||
EXPECT_TRUE(send_n_data(p[1], &atime_nsec, sizeof(atime_nsec)));
|
||||
int32_t crtime_valid = 0;
|
||||
int64_t crtime_sec = 0;
|
||||
int64_t crtime_nsec = 0;
|
||||
EXPECT_TRUE(send_n_data(p[1], &crtime_valid, sizeof(crtime_valid)));
|
||||
EXPECT_TRUE(send_n_data(p[1], &crtime_sec, sizeof(crtime_sec)));
|
||||
EXPECT_TRUE(send_n_data(p[1], &crtime_nsec, sizeof(crtime_nsec)));
|
||||
FileMetadata bad = {.mode = 0644,
|
||||
.uid = 1000,
|
||||
.gid = 1000,
|
||||
.mtime_sec = 1,
|
||||
.mtime_nsec = 0,
|
||||
.atime_valid = true,
|
||||
.atime_sec = 1,
|
||||
.atime_nsec = 2000000000, /* invalid: >= 1e9 */
|
||||
.crtime_valid = false,
|
||||
.crtime_sec = 0,
|
||||
.crtime_nsec = 0};
|
||||
char packed[sizeof(int32_t) + FILE_METADATA_WIRE_SIZE];
|
||||
char* write_ptr = packed;
|
||||
metadata_to_buf(&write_ptr, &bad);
|
||||
EXPECT_TRUE(send_n_data(p[1], packed, sizeof(packed)));
|
||||
|
||||
int ok = 1;
|
||||
EXPECT_NULL(metadata_receive(p[0], &ok));
|
||||
EXPECT_EQ_INT(ok, 0);
|
||||
@@ -235,12 +340,13 @@ static void test_file_restore_metadata() {
|
||||
const char* content = "test content";
|
||||
EXPECT_TRUE(file_write_to_disk(path, content, strlen(content), false, false));
|
||||
|
||||
FileMetadata m;
|
||||
m.mode = 0644;
|
||||
m.uid = getuid();
|
||||
m.gid = getgid();
|
||||
m.mtime_sec = 1234567890;
|
||||
m.mtime_nsec = 0;
|
||||
FileMetadata m = {.mode = 0644,
|
||||
.uid = getuid(),
|
||||
.gid = getgid(),
|
||||
.mtime_sec = 1234567890,
|
||||
.mtime_nsec = 0,
|
||||
.atime_valid = false,
|
||||
.crtime_valid = false};
|
||||
|
||||
file_restore_metadata(path, &m, false);
|
||||
|
||||
@@ -347,6 +453,8 @@ void test_metadata() {
|
||||
test_metadata_from_buf_null();
|
||||
test_metadata_send_receive_roundtrip();
|
||||
test_metadata_send_null();
|
||||
test_metadata_wire_is_one_packed_frame();
|
||||
test_metadata_send_receive_socketpair();
|
||||
test_metadata_rejects_invalid_values();
|
||||
test_metadata_receive_rejects_bad_optional_times();
|
||||
test_metadata_mtime_window();
|
||||
|
||||
Reference in New Issue
Block a user