chmod: match rsync 3.4.1 --chmod and remove mode masking (#293)

- --chmod no longer implies --preserve-perms; repeated --chmod options
  accumulate, and D/F/X selectors plus s/t special bits are supported with
  rsync's exact parse_chmod/tweak_mode semantics.
- Stop masking group/other write and setuid/setgid/sticky: -p copies the
  source mode exactly, no-p new entries use source&~umask, directories keep
  setgid/sticky, and special nodes follow the same rules.
- Apply ownership before mode on the fd path so a chown cannot clear the
  setuid/setgid bits -p just restored (rsync order).
- Update unit and integration tests, including differential checks against
  rsync 3.4.1.
This commit is contained in:
2026-09-16 01:23:45 +02:00
parent 3f5b0250f4
commit ec206b02d0
13 changed files with 513 additions and 184 deletions
+32 -3
View File
@@ -530,7 +530,9 @@ static void test_parse_args_chmod() {
int positional_count = 0;
EXPECT_EQ_INT(parse_args(cfg, 4, argv, positional_args, &positional_count), 0);
EXPECT_EQ_STR(cfg->chmod_spec, "u=rw,go=r");
EXPECT_TRUE(cfg->preserve_perms);
/* rsync's --chmod does NOT imply --perms: it only tweaks the mode used for a
* new destination unless -p is also given. */
EXPECT_FALSE(cfg->preserve_perms);
EXPECT_TRUE(cfg->use_metadata);
mode_t result;
EXPECT_TRUE(chmod_apply(0777, cfg->chmod_spec, &result));
@@ -545,14 +547,39 @@ static void test_parse_args_numeric_chmod() {
int positional_count = 0;
EXPECT_EQ_INT(parse_args(cfg, 5, argv, positional_args, &positional_count), 0);
EXPECT_EQ_STR(cfg->chmod_spec, "7777");
EXPECT_TRUE(cfg->preserve_perms);
EXPECT_FALSE(cfg->preserve_perms);
EXPECT_TRUE(cfg->use_metadata);
config_delete(cfg);
}
static void test_parse_args_accepts_selector_chmod() {
Config* cfg = config_create();
char* argv[] = {"fastsync", "--chmod=Dg+s,Fo-w,+X", "/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_STR(cfg->chmod_spec, "Dg+s,Fo-w,+X");
EXPECT_FALSE(cfg->preserve_perms);
config_delete(cfg);
}
static void test_parse_args_appends_repeated_chmod() {
Config* cfg = config_create();
char* argv[] = {"fastsync", "--chmod=a+r", "--chmod=a-w", "/src", "/dst"};
int positional_args[2];
int positional_count = 0;
EXPECT_EQ_INT(parse_args(cfg, 5, argv, positional_args, &positional_count), 0);
/* Repeated --chmod options accumulate (rsync >= 3.2.4) instead of replacing. */
EXPECT_EQ_STR(cfg->chmod_spec, "a+r,a-w");
mode_t result;
EXPECT_TRUE(chmod_apply(0644, cfg->chmod_spec, &result));
EXPECT_EQ_INT(result, 0444);
config_delete(cfg);
}
static void test_parse_args_rejects_invalid_chmod() {
Config* cfg = config_create();
char* argv[] = {"fastsync", "--chmod=a+X", "/src", "/dst"};
char* argv[] = {"fastsync", "--chmod=a+r,", "/src", "/dst"};
int positional_args[2];
int positional_count = 0;
EXPECT_EQ_INT(parse_args(cfg, 4, argv, positional_args, &positional_count), -1);
@@ -4135,6 +4162,8 @@ void test_client_cli() {
test_parse_args_executability();
test_parse_args_chmod();
test_parse_args_numeric_chmod();
test_parse_args_accepts_selector_chmod();
test_parse_args_appends_repeated_chmod();
test_parse_args_rejects_invalid_chmod();
test_parse_args_invalid_port();
test_parse_args_non_numeric_port();