feat(parity): resolve --chown TO names on receiver via map rules

This commit is contained in:
opencode
2026-09-16 22:41:12 +02:00
parent a0b9d9794b
commit a690109975
3 changed files with 112 additions and 27 deletions
+34 -4
View File
@@ -1044,10 +1044,11 @@ static void test_parse_args_basis_dirs() {
config_delete(cfg);
}
/* Absolute, escaping, or degenerate basis-dir values must be rejected up
front: they would resolve outside the destination root on the receiver. */
/* Escaping or degenerate basis-dir values must be rejected up front (they would
resolve outside the destination root on the receiver); an absolute path is
accepted (rsync parity) and canonicalized with its leading '/' preserved. */
static void test_parse_args_basis_invalid_paths() {
static const char* const invalid[] = {"/abs", "..", "a/../b", "."};
static const char* const invalid[] = {"..", "a/../b", ".", "/", ""};
for (size_t i = 0; i < sizeof(invalid) / sizeof(invalid[0]); i++) {
Config* cfg = config_create();
char* argv[] = {"fastsync", "--link-dest", (char*)invalid[i], "/src", "/dst"};
@@ -1056,6 +1057,15 @@ static void test_parse_args_basis_invalid_paths() {
EXPECT_EQ_INT(parse_args(cfg, 5, argv, positional_args, &positional_count), -1);
config_delete(cfg);
}
Config* cfg = config_create();
char* argv[] = {"fastsync", "--link-dest=/abs/dir", "/src", "/dst"};
int positional_args[2];
int positional_count = 0;
EXPECT_EQ_INT(parse_args(cfg, 4, argv, positional_args, &positional_count), 0);
EXPECT_EQ_INT(cfg->basis_count, 1);
EXPECT_EQ_STR(cfg->basis_dirs[0].path, "/abs/dir");
config_delete(cfg);
}
/* Basis dirs require the per-file incremental handshake, which -s disables. */
@@ -3146,6 +3156,27 @@ static void test_parse_args_chown() {
EXPECT_TRUE(cfg->chown_gid_set);
EXPECT_EQ_INT(cfg->chown_gid, IDENTITY_CURRENT);
config_delete(cfg);
/* A --chown NAME is converted to the equivalent receiver-resolved map rule
* (rsync implements --chown as --usermap=*:USER --groupmap=*:GROUP), so the
* name is carried on the wire as to_name instead of being resolved on the
* sender. A name that does not exist on the sender is accepted and left for
* the receiver to resolve (or warn about), matching rsync. */
cfg = config_create();
positional_count = 0;
char* argv5[] = {"fastsync", "--chown=no_such_user_zzz:no_such_group_zzz", "/src", "/dst"};
EXPECT_EQ_INT(parse_args(cfg, 4, argv5, positional_args, &positional_count), 0);
EXPECT_EQ_INT(cfg->usermap_count, 1);
EXPECT_NOT_NULL(cfg->usermap[0].to_name);
if (cfg->usermap[0].to_name)
EXPECT_EQ_STR(cfg->usermap[0].to_name, "no_such_user_zzz");
EXPECT_FALSE(cfg->chown_uid_set);
EXPECT_EQ_INT(cfg->groupmap_count, 1);
EXPECT_NOT_NULL(cfg->groupmap[0].to_name);
if (cfg->groupmap[0].to_name)
EXPECT_EQ_STR(cfg->groupmap[0].to_name, "no_such_group_zzz");
EXPECT_FALSE(cfg->chown_gid_set);
config_delete(cfg);
}
/* --copy-as=USER[:GROUP] (P7 Wave E): resolve the user/group against the local
@@ -3220,7 +3251,6 @@ static void test_parse_args_rejects_malformed_identity() {
{"--groupmap", "@1"},
{"--groupmap", "no_such_group_qqq:x"},
{"--chown", "a:b:c"},
{"--chown", "no_such_user_zzz:"},
{"--copy-as", ""},
{"--copy-as", ":"},
{"--copy-as", "a:b:c"},
+10 -2
View File
@@ -1192,7 +1192,10 @@ static void test_config_basis_wire_rejects_escaping() {
c->basis_dirs = calloc(1, sizeof(BasisDest));
c->basis_dirs[0].type = BASIS_DEST_LINK;
c->basis_dirs[0].path = str_dup("/abs");
EXPECT_FALSE(roundtrip_config_ok(c));
/* An absolute basis dir is accepted (rsync parity); it is only usable when it
lies within the receiver's authorized root, which file_open_secure_parent
enforces at lookup time. */
EXPECT_TRUE(roundtrip_config_ok(c));
config_delete(c);
/* A well-formed list still round-trips even with a manually built struct. */
@@ -1225,8 +1228,13 @@ static void test_config_basis_normalization() {
/* Degenerate values that normalize away to nothing stay rejected. */
EXPECT_EQ_INT(config_basis_append(c, BASIS_DEST_LINK, "."), -1);
EXPECT_EQ_INT(config_basis_append(c, BASIS_DEST_LINK, ".."), -1);
EXPECT_EQ_INT(config_basis_append(c, BASIS_DEST_LINK, "/abs"), -1);
/* An absolute path is canonicalized (leading '/' preserved) and accepted. */
EXPECT_EQ_INT(config_basis_append(c, BASIS_DEST_LINK, "/abs"), 0);
EXPECT_EQ_STR(c->basis_dirs[c->basis_count - 1].path, "/abs");
EXPECT_EQ_INT(config_basis_append(c, BASIS_DEST_LINK, "/a//b/"), 0);
EXPECT_EQ_STR(c->basis_dirs[c->basis_count - 1].path, "/a/b");
EXPECT_EQ_INT(config_basis_append(c, BASIS_DEST_LINK, "a/../b"), -1);
EXPECT_EQ_INT(config_basis_append(c, BASIS_DEST_LINK, "/"), -1);
EXPECT_EQ_INT(config_basis_append(c, BASIS_DEST_LINK, ""), -1);
config_delete(c);
}