- #286: --numeric-ids is a mapping modifier only; it no longer activates chown by itself (identity_active_enabled/owner/group predicates), and --fake-super stores the resolved mapping instead of real-chowning. - #286: apply owner/group to directories via the deferred directory metadata path; capture+transmit+apply directory xattrs/ACLs (-aX/-aA), including default ACLs, in STATUS_MKDIR/STATUS_DIR_TIMES. - #294: --usermap/--groupmap support inclusive ranges, '*', empty FROM (unnamed ids), and receiver-side TO name resolution; --chown mixing with a same-side map is rejected like rsync. - Protocol 2.22.0 -> 2.23.0 (map wire entry gains from_hi + to_name; dir frames gain a bounded xattr block).
This commit is contained in:
@@ -1830,6 +1830,75 @@ static bool cli_handle_checksum_options(CliParseCtx* ctx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Determine whether a --chown spec sets the owner and/or group side, honoring
|
||||
* the same escape-aware splitting as identity_parse_chown(): a `\:` is a literal
|
||||
* colon, not a field separator. */
|
||||
static void chown_spec_sides(const char* value, bool* has_owner, bool* has_group) {
|
||||
*has_owner = false;
|
||||
*has_group = false;
|
||||
if (!value)
|
||||
return;
|
||||
bool split = false;
|
||||
for (const char* p = value; *p; p++) {
|
||||
if (*p == '\\' && p[1] == ':') {
|
||||
p++;
|
||||
continue;
|
||||
}
|
||||
if (*p == ':') {
|
||||
split = true;
|
||||
continue;
|
||||
}
|
||||
if (split)
|
||||
*has_group = true;
|
||||
else
|
||||
*has_owner = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* rsync refuses to mix --chown with --usermap/--groupmap on the SAME side
|
||||
* ("--usermap conflicts with prior --chown"). `chown_value` is non-NULL only
|
||||
* for the --chown option itself. Returns true and records a parse error when
|
||||
* the new option conflicts with one already seen. */
|
||||
static bool mapping_option_conflicts(CliParseCtx* ctx, const char* optname, bool is_group,
|
||||
const char* chown_value) {
|
||||
const Config* config = ctx->config;
|
||||
if (chown_value) {
|
||||
bool has_owner;
|
||||
bool has_group;
|
||||
chown_spec_sides(chown_value, &has_owner, &has_group);
|
||||
if (has_owner && config->usermap_count > 0) {
|
||||
log_message(LOG_LEVEL_ERROR, "%s conflicts with prior --usermap", optname);
|
||||
ctx->exit_code = -1;
|
||||
return true;
|
||||
}
|
||||
if (has_group && config->groupmap_count > 0) {
|
||||
log_message(LOG_LEVEL_ERROR, "%s conflicts with prior --groupmap", optname);
|
||||
ctx->exit_code = -1;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!is_group && config->chown_uid_set) {
|
||||
log_message(LOG_LEVEL_ERROR, "%s conflicts with prior --chown", optname);
|
||||
ctx->exit_code = -1;
|
||||
return true;
|
||||
}
|
||||
if (is_group && config->chown_gid_set) {
|
||||
log_message(LOG_LEVEL_ERROR, "%s conflicts with prior --chown", optname);
|
||||
ctx->exit_code = -1;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool usermap_conflicts_with_chown(CliParseCtx* ctx, const char* optname, bool is_group) {
|
||||
return mapping_option_conflicts(ctx, optname, is_group, NULL);
|
||||
}
|
||||
|
||||
static bool chown_conflicts_with_map(CliParseCtx* ctx, const char* optname, const char* value) {
|
||||
return mapping_option_conflicts(ctx, optname, false, value);
|
||||
}
|
||||
|
||||
/* Remote-option, basis-directory and identity-mapping options. Returns true
|
||||
* when the argument was consumed. */
|
||||
static bool cli_handle_remote_basis_options(CliParseCtx* ctx) {
|
||||
@@ -1902,6 +1971,8 @@ static bool cli_handle_remote_basis_options(CliParseCtx* ctx) {
|
||||
return true;
|
||||
}
|
||||
if (strncmp(arg, "--usermap=", 10) == 0) {
|
||||
if (usermap_conflicts_with_chown(ctx, "--usermap", false))
|
||||
return true;
|
||||
if (identity_parse_map(config, arg + 10, false) != 0) {
|
||||
ctx->exit_code = -1;
|
||||
return true;
|
||||
@@ -1915,6 +1986,8 @@ static bool cli_handle_remote_basis_options(CliParseCtx* ctx) {
|
||||
ctx->exit_code = -1;
|
||||
return true;
|
||||
}
|
||||
if (usermap_conflicts_with_chown(ctx, "--usermap", false))
|
||||
return true;
|
||||
if (identity_parse_map(config, ctx->argv[++ctx->i], false) != 0) {
|
||||
ctx->exit_code = -1;
|
||||
return true;
|
||||
@@ -1923,6 +1996,8 @@ static bool cli_handle_remote_basis_options(CliParseCtx* ctx) {
|
||||
return true;
|
||||
}
|
||||
if (strncmp(arg, "--groupmap=", 11) == 0) {
|
||||
if (usermap_conflicts_with_chown(ctx, "--groupmap", true))
|
||||
return true;
|
||||
if (identity_parse_map(config, arg + 11, true) != 0) {
|
||||
ctx->exit_code = -1;
|
||||
return true;
|
||||
@@ -1936,6 +2011,8 @@ static bool cli_handle_remote_basis_options(CliParseCtx* ctx) {
|
||||
ctx->exit_code = -1;
|
||||
return true;
|
||||
}
|
||||
if (usermap_conflicts_with_chown(ctx, "--groupmap", true))
|
||||
return true;
|
||||
if (identity_parse_map(config, ctx->argv[++ctx->i], true) != 0) {
|
||||
ctx->exit_code = -1;
|
||||
return true;
|
||||
@@ -1944,6 +2021,8 @@ static bool cli_handle_remote_basis_options(CliParseCtx* ctx) {
|
||||
return true;
|
||||
}
|
||||
if (strncmp(arg, "--chown=", 8) == 0) {
|
||||
if (chown_conflicts_with_map(ctx, "--chown", arg + 8))
|
||||
return true;
|
||||
if (identity_parse_chown(config, arg + 8) != 0) {
|
||||
ctx->exit_code = -1;
|
||||
return true;
|
||||
@@ -1960,6 +2039,8 @@ static bool cli_handle_remote_basis_options(CliParseCtx* ctx) {
|
||||
ctx->exit_code = -1;
|
||||
return true;
|
||||
}
|
||||
if (chown_conflicts_with_map(ctx, "--chown", ctx->argv[ctx->i + 1]))
|
||||
return true;
|
||||
if (identity_parse_chown(config, ctx->argv[++ctx->i]) != 0) {
|
||||
ctx->exit_code = -1;
|
||||
return true;
|
||||
|
||||
@@ -1429,7 +1429,11 @@ static bool send_directory_entry(const Client* client, File* file, const Config*
|
||||
if (!send_status(client->file_descriptor, STATUS_MKDIR) ||
|
||||
!send_wire_str(client->file_descriptor, file_wire_path(file)))
|
||||
return false;
|
||||
return !config->use_metadata || metadata_send(client->file_descriptor, file->metadata);
|
||||
if (config->use_metadata && !metadata_send(client->file_descriptor, file->metadata))
|
||||
return false;
|
||||
/* Directory xattrs/ACLs (-X/-A) ride the same trailing block as regular files
|
||||
when the xattr transport was negotiated. */
|
||||
return !config->use_xattrs || xattr_send(client->file_descriptor, file->xattrs);
|
||||
}
|
||||
|
||||
/* P7 Wave D: transmit every captured source directory's metadata in terminal
|
||||
@@ -1461,6 +1465,9 @@ static bool send_dir_times(const Client* client, const Config* config, ArrayList
|
||||
return false;
|
||||
if (!send_wire_str(fd, file_wire_path(file)) || !metadata_send(fd, file->metadata))
|
||||
return false;
|
||||
/* Directory xattrs/ACLs travel with the deferred directory metadata. */
|
||||
if (config->use_xattrs && !xattr_send(fd, file->xattrs))
|
||||
return false;
|
||||
}
|
||||
index += chunk;
|
||||
}
|
||||
|
||||
+16
-6
@@ -586,7 +586,8 @@ static Chunk* chunk_data_to_chunk(ArrayList* chunk_data) {
|
||||
* allocation failure is fatal and reported to the caller. */
|
||||
static bool scanner_capture_dir_time(ArrayList* dir_entries, mtx_t* mutex, const char* root_path,
|
||||
const char* fs_path, bool relative_mode, bool preserve_atimes,
|
||||
bool preserve_crtimes) {
|
||||
bool preserve_crtimes, bool preserve_xattrs,
|
||||
bool preserve_acls) {
|
||||
if (!dir_entries || !root_path || !fs_path)
|
||||
return true;
|
||||
struct stat st;
|
||||
@@ -613,6 +614,11 @@ static bool scanner_capture_dir_time(ArrayList* dir_entries, mtx_t* mutex, const
|
||||
file_destroy(file);
|
||||
return false;
|
||||
}
|
||||
/* Directory xattrs/ACLs (-X/-A): captured here so the deferred
|
||||
STATUS_DIR_TIMES frame can carry them and the receiver can re-apply them
|
||||
fd-relative (a regular file's per-file block never covered directories). */
|
||||
if (preserve_xattrs || preserve_acls)
|
||||
file->xattrs = xattr_capture_path(fs_path, preserve_acls);
|
||||
if (relative_mode) {
|
||||
file->send_path = rel;
|
||||
rel = NULL;
|
||||
@@ -700,10 +706,11 @@ static int open_next_directory(DirectoryScanner* scanner) {
|
||||
return -1;
|
||||
}
|
||||
if (scanner->options.capture_dir_times &&
|
||||
!scanner_capture_dir_time(scanner->options.dir_entries, scanner->options.dir_entries_mutex,
|
||||
scanner->root_path, scanner->current_path, scanner->relative_mode,
|
||||
scanner->options.preserve_atimes,
|
||||
scanner->options.preserve_crtimes)) {
|
||||
!scanner_capture_dir_time(
|
||||
scanner->options.dir_entries, scanner->options.dir_entries_mutex, scanner->root_path,
|
||||
scanner->current_path, scanner->relative_mode, scanner->options.preserve_atimes,
|
||||
scanner->options.preserve_crtimes, scanner->options.preserve_xattrs,
|
||||
scanner->options.preserve_acls)) {
|
||||
closedir(scanner->current_dir);
|
||||
scanner->current_dir = NULL;
|
||||
free(scanner->current_path);
|
||||
@@ -753,6 +760,7 @@ static File* dirs_root_dir_file(DirectoryScanner* scanner) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
scanner_capture_xattrs(scanner, file);
|
||||
return file;
|
||||
}
|
||||
|
||||
@@ -839,6 +847,7 @@ static File* dirs_file_for_entry(DirectoryScanner* scanner, const char* entry) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
scanner_capture_xattrs(scanner, file);
|
||||
return file;
|
||||
}
|
||||
|
||||
@@ -1629,7 +1638,8 @@ ParallelScanner* parallel_scanner_create_with_options(const char* root_directory
|
||||
if (options->capture_dir_times &&
|
||||
!scanner_capture_dir_time(options->dir_entries, options->dir_entries_mutex, root_directory,
|
||||
root_directory, options->relative && options->file_list != NULL,
|
||||
options->preserve_atimes, options->preserve_crtimes)) {
|
||||
options->preserve_atimes, options->preserve_crtimes,
|
||||
options->preserve_xattrs, options->preserve_acls)) {
|
||||
array_list_delete(root_files);
|
||||
array_list_delete(subdirs);
|
||||
parallel_scanner_destroy(ps);
|
||||
|
||||
+9
-7
@@ -190,17 +190,19 @@ void print_usage(void) {
|
||||
printf(" within the confined receive root. Never elevates\n");
|
||||
printf(" privileges and never bypasses confinement; ownership\n");
|
||||
printf(" is still applied only with -o/--owner, -g/--group, or an\n");
|
||||
printf(" explicit identity flag (--numeric-ids/--chown/--usermap/\n");
|
||||
printf(" --groupmap/--copy-as)\n");
|
||||
printf(" explicit identity flag (--chown/--usermap/--groupmap/\n");
|
||||
printf(" --copy-as); --numeric-ids only changes how ids map\n");
|
||||
printf(" --no-super Forbid those super-user activities even when the\n");
|
||||
printf(" receiver is running as root\n");
|
||||
printf(" --chmod <changes> Modify transferred permissions (rsync syntax)\n");
|
||||
printf(" --numeric-ids Do not map uid/gid by name: use the source numeric\n");
|
||||
printf(" ids directly when applying ownership\n");
|
||||
printf(" --numeric-ids Map uid/gid by id instead of by name (a modifier, not\n");
|
||||
printf(" an ownership request: combine with -o/-g or a map)\n");
|
||||
printf(" --usermap=MAP Map usernames when applying ownership: comma-separated\n");
|
||||
printf(" FROM:TO rules, first match wins. FROM/TO are names\n");
|
||||
printf(" (resolved on the source machine), * (match any /\n");
|
||||
printf(" current user), or @N numeric ids. e.g. *:nobody\n");
|
||||
printf(" FROM:TO rules, first match wins. FROM is a name (from\n");
|
||||
printf(" the source), an id, an inclusive LOW-HIGH range, *\n");
|
||||
printf(" (any id), or empty (ids with no name). TO is an id, *\n");
|
||||
printf(" (current user), or a name resolved on the receiver.\n");
|
||||
printf(" e.g. 0-99:nobody,*:normal (cannot mix with --chown)\n");
|
||||
printf(" --groupmap=MAP Map group names when applying ownership (same syntax)\n");
|
||||
printf(" --chown=USER:GROUP Override the ownership of transferred files. Forms:\n");
|
||||
printf(" USER:GROUP, USER (owner only), :GROUP (group only); a\n");
|
||||
|
||||
Reference in New Issue
Block a user