acls/xattrs: -X/--xattrs, -A/--acls, --fake-super
CI / lint (pull_request) Failing after 55s
CI / build-and-test (pull_request) Skipped
CI / sanitizers (address) (pull_request) Skipped
CI / sanitizers (undefined) (pull_request) Skipped
CI / fuzz-build (pull_request) Skipped
CI / coverage (pull_request) Skipped
CI / valgrind (pull_request) Skipped
CI / lint (pull_request) Failing after 55s
CI / build-and-test (pull_request) Skipped
CI / sanitizers (address) (pull_request) Skipped
CI / sanitizers (undefined) (pull_request) Skipped
CI / fuzz-build (pull_request) Skipped
CI / coverage (pull_request) Skipped
CI / valgrind (pull_request) Skipped
New src/shared/xattr.{c,h}: capture user.* + POSIX ACL xattrs, transmit a bounded
per-file block, re-apply fd-relative. security.*/trusted.*/other system.* never
transmitted/applied (receiver re-validates). Bounds: name<=255 value<=1MiB count
<=256 total<=4MiB. --fake-super records uid:gid:mode:mtime in reserved
user.fastsync.stat (receiver-only). PROTOCOL_VERSION 2.12.0->2.13.0. Review
fixes: reserved key not forwardable, link/hardlink copy-fallback preserves
xattrs, no const-param mutation, per-file warning dedup.
This commit is contained in:
@@ -540,6 +540,9 @@ static const OptionEntry OPTION_TABLE[] = {
|
||||
{"--omit-dir-times", "-O", OPT_FLAG, offsetof(Config, omit_dir_times)},
|
||||
{"--omit-link-times", "-J", OPT_FLAG, offsetof(Config, omit_link_times)},
|
||||
{"--open-noatime", NULL, OPT_FLAG, offsetof(Config, open_noatime)},
|
||||
{"--xattrs", "-X", OPT_FLAG, offsetof(Config, preserve_xattrs)},
|
||||
{"--acls", "-A", OPT_FLAG, offsetof(Config, preserve_acls)},
|
||||
{"--fake-super", NULL, OPT_FLAG, offsetof(Config, fake_super)},
|
||||
};
|
||||
|
||||
/* Only boolean options with no required argument are safe to negate. */
|
||||
@@ -574,6 +577,9 @@ static const NegatableOption NEGATABLE_OPTIONS[] = {
|
||||
{"preserve", "M", offsetof(Config, use_metadata)},
|
||||
{"sendfile", "f", offsetof(Config, use_sendfile)},
|
||||
{"chunk-serialization", "s", offsetof(Config, use_chunk_serialization)},
|
||||
{"xattrs", "X", offsetof(Config, preserve_xattrs)},
|
||||
{"acls", "A", offsetof(Config, preserve_acls)},
|
||||
{"fake-super", NULL, offsetof(Config, fake_super)},
|
||||
};
|
||||
|
||||
static bool opt_is(const char* arg, const char* name, const char* alias) {
|
||||
@@ -810,6 +816,13 @@ int parse_args(Config* config, int argc, char* argv[], int* positional_args,
|
||||
if (entry->offset == offsetof(Config, preserve_atimes) ||
|
||||
entry->offset == offsetof(Config, preserve_crtimes))
|
||||
config->use_metadata = true;
|
||||
if (entry->offset == offsetof(Config, preserve_xattrs) ||
|
||||
entry->offset == offsetof(Config, preserve_acls)) {
|
||||
config->use_metadata = true;
|
||||
config->use_xattrs = config->preserve_acls || config->preserve_xattrs;
|
||||
}
|
||||
if (entry->offset == offsetof(Config, fake_super))
|
||||
config->use_metadata = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1242,6 +1255,10 @@ int parse_args(Config* config, int argc, char* argv[], int* positional_args,
|
||||
log_message(LOG_LEVEL_INFO, "Enabling metadata preservation for incremental/delta transfer");
|
||||
config->use_metadata = true;
|
||||
}
|
||||
/* Recompute the derived xattr flag from the FINAL preserve flags (after any
|
||||
* --no-xattrs/--no-acls negation) so the sender's wire gate always matches
|
||||
* the flags the receiver will recompute from the received config. */
|
||||
config->use_xattrs = config->preserve_acls || config->preserve_xattrs;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "transport_ssh.h"
|
||||
#include "transport_tls.h"
|
||||
#include "utils.h"
|
||||
#include "xattr.h"
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
@@ -89,6 +90,8 @@ static bool prepare_scanner(const Config* config, int num_threads, PreparedScann
|
||||
options->use_metadata = config->use_metadata;
|
||||
options->preserve_atimes = config->preserve_atimes;
|
||||
options->preserve_crtimes = config->preserve_crtimes;
|
||||
options->preserve_xattrs = config->preserve_xattrs;
|
||||
options->preserve_acls = config->preserve_acls;
|
||||
options->chunk_size = config->chunk_size;
|
||||
options->exclude_patterns = config->exclude_patterns;
|
||||
options->exclude_count = config->exclude_count;
|
||||
@@ -958,6 +961,9 @@ static int send_delta(Client* client, File* file, DeltaSignature* sig, Config* c
|
||||
if (ok && config->use_metadata)
|
||||
ok = metadata_send(client->file_descriptor, file->metadata);
|
||||
|
||||
if (ok && config->use_xattrs)
|
||||
ok = xattr_send(client->file_descriptor, file->xattrs);
|
||||
|
||||
data_destroy(to_send);
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
@@ -999,7 +1005,7 @@ static int send_append(const Client* client, File* file, Config* config,
|
||||
transfer (byte-identical, never a corrupt prefix+tail blend). */
|
||||
int rc = file_send_single_calls_with_skip(file, fd, config->use_metadata, compression_level,
|
||||
false, config->skip_compress_suffixes, skip_count,
|
||||
config->compression_threads)
|
||||
config->compression_threads, config->use_xattrs)
|
||||
? 1
|
||||
: -1;
|
||||
return rc;
|
||||
@@ -1016,6 +1022,9 @@ static int send_append(const Client* client, File* file, Config* config,
|
||||
if (config->use_metadata && !metadata_send(fd, file->metadata)) {
|
||||
return -1;
|
||||
}
|
||||
if (config->use_xattrs && !xattr_send(fd, file->xattrs)) {
|
||||
return -1;
|
||||
}
|
||||
bool ok;
|
||||
if (compress) {
|
||||
/* Compression needs an owned copy of the tail to compress. */
|
||||
@@ -1053,7 +1062,7 @@ static bool send_file_direct(File* file, int fd, bool use_metadata, int compress
|
||||
int skip_count = config->skip_compress_set ? config->skip_compress_count : -1;
|
||||
return file_send_single_calls_with_skip(file, fd, use_metadata, compression_level, true,
|
||||
config->skip_compress_suffixes, skip_count,
|
||||
config->compression_threads);
|
||||
config->compression_threads, config->use_xattrs);
|
||||
}
|
||||
|
||||
/* Transmit one explicit directory entry (--dirs): a STATUS_MKDIR frame whose
|
||||
@@ -1074,7 +1083,7 @@ static bool send_file_direct_sendfile(File* file, int fd, bool use_metadata, con
|
||||
int skip_count = config->skip_compress_set ? config->skip_compress_count : -1;
|
||||
return file_send_sendfile_with_skip(file, fd, use_metadata, 0, true,
|
||||
config->skip_compress_suffixes, skip_count,
|
||||
config->compression_threads);
|
||||
config->compression_threads, config->use_xattrs);
|
||||
}
|
||||
|
||||
// Process one file in a chunk: either via incremental check or direct send.
|
||||
@@ -1132,7 +1141,7 @@ static int send_single_file(Client* client, File* file, Config* config, bool use
|
||||
int skip_count = config->skip_compress_set ? config->skip_compress_count : -1;
|
||||
if (!file_send_sendfile_with_skip(file, client->file_descriptor, config->use_metadata, 0, false,
|
||||
config->skip_compress_suffixes, skip_count,
|
||||
config->compression_threads))
|
||||
config->compression_threads, config->use_xattrs))
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
@@ -1181,7 +1190,8 @@ static int send_single_file(Client* client, File* file, Config* config, bool use
|
||||
int skip_count = config->skip_compress_set ? config->skip_compress_count : -1;
|
||||
if (!file_send_single_calls_with_skip(file, client->file_descriptor, config->use_metadata,
|
||||
compression_level, false, config->skip_compress_suffixes,
|
||||
skip_count, config->compression_threads))
|
||||
skip_count, config->compression_threads,
|
||||
config->use_xattrs))
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -79,6 +79,14 @@ bool validate_config(const Config* config) {
|
||||
"--hard-links/-H cannot be combined with -s (chunk serialization)");
|
||||
return false;
|
||||
}
|
||||
/* -X/-A ride the per-file metadata frame; the buffer-based chunk-serialization
|
||||
wire format does not carry the xattr block, so the pair is rejected up front
|
||||
(mirroring -H + -s) rather than silently dropping attributes. */
|
||||
if ((config->preserve_xattrs || config->preserve_acls) && config->use_chunk_serialization) {
|
||||
log_message(LOG_LEVEL_ERROR,
|
||||
"--xattrs/-X and --acls/-A cannot be combined with -s (chunk serialization)");
|
||||
return false;
|
||||
}
|
||||
if (config->preserve_hard_links && (config->append || config->append_verify)) {
|
||||
log_message(LOG_LEVEL_ERROR,
|
||||
"--hard-links/-H cannot be combined with --append/--append-verify");
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#include <unistd.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "xattr.h"
|
||||
|
||||
typedef struct {
|
||||
char* path;
|
||||
int depth;
|
||||
@@ -165,6 +167,14 @@ static bool entry_passes_selection(const FileListSet* file_list, const FilterRul
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Best-effort capture of the file's whitelisted xattrs (-X/-A). A failure to
|
||||
* read xattrs is non-fatal: the file is transferred without them. */
|
||||
static void scanner_capture_xattrs(const DirectoryScanner* scanner, File* file) {
|
||||
if (!scanner || !file || !(scanner->preserve_xattrs || scanner->preserve_acls))
|
||||
return;
|
||||
file->xattrs = xattr_capture_path(file->path);
|
||||
}
|
||||
|
||||
/* Apply --hard-links (-H) detection to one regular File. On a sibling (a
|
||||
* later member of an already-seen source inode) the File keeps the group id
|
||||
* and the first member's wire path but carries NO data payload (size 0); the
|
||||
@@ -350,6 +360,8 @@ DirectoryScanner* directory_scanner_create_with_options(const char* root_directo
|
||||
scanner->use_metadata = options->use_metadata;
|
||||
scanner->preserve_atimes = options->preserve_atimes;
|
||||
scanner->preserve_crtimes = options->preserve_crtimes;
|
||||
scanner->preserve_xattrs = options->preserve_xattrs;
|
||||
scanner->preserve_acls = options->preserve_acls;
|
||||
scanner->chunk_size = options->chunk_size > 0 ? options->chunk_size : DESIRED_CHUNK_SIZE;
|
||||
scanner->exclude_patterns = options->exclude_patterns;
|
||||
scanner->exclude_count = options->exclude_count;
|
||||
@@ -936,6 +948,8 @@ Chunk* directory_scanner_next(DirectoryScanner* scanner) {
|
||||
scanner->failed = true;
|
||||
break;
|
||||
}
|
||||
if (!(file->link_group != 0 && !file->link_first))
|
||||
scanner_capture_xattrs(scanner, file);
|
||||
if (!array_list_add(chunk_data, file)) {
|
||||
free(rel_copy);
|
||||
file_destroy(file);
|
||||
@@ -1270,6 +1284,9 @@ static void scan_root_entry(const ScannerOptions* options, const FilterNode* roo
|
||||
ps->failed = true;
|
||||
return;
|
||||
}
|
||||
if ((options->preserve_xattrs || options->preserve_acls) &&
|
||||
!(file->link_group != 0 && !file->link_first))
|
||||
file->xattrs = xattr_capture_path(file->path);
|
||||
if (!array_list_add(root_files, file)) {
|
||||
free(rel);
|
||||
file_destroy(file);
|
||||
|
||||
@@ -19,6 +19,10 @@ typedef struct {
|
||||
* capture the source access / birth time into each entry's FileMetadata. */
|
||||
bool preserve_atimes;
|
||||
bool preserve_crtimes;
|
||||
/* Phase 4 xattrs: when preserve_xattrs || preserve_acls is set the scanner
|
||||
* captures each regular file's whitelisted xattr set onto the File. */
|
||||
bool preserve_xattrs;
|
||||
bool preserve_acls;
|
||||
unsigned long long chunk_size;
|
||||
char** exclude_patterns;
|
||||
int exclude_count;
|
||||
@@ -87,6 +91,8 @@ typedef struct {
|
||||
bool use_metadata;
|
||||
bool preserve_atimes;
|
||||
bool preserve_crtimes;
|
||||
bool preserve_xattrs;
|
||||
bool preserve_acls;
|
||||
unsigned long long chunk_size;
|
||||
char** exclude_patterns;
|
||||
int exclude_count;
|
||||
|
||||
@@ -128,6 +128,16 @@ void print_usage(void) {
|
||||
printf(" none suppresses info even with --verbose\n");
|
||||
printf(" -M, --preserve Preserve file metadata\n");
|
||||
printf(" -E, --executability Preserve executable permission bits\n");
|
||||
printf(" -X, --xattrs Preserve user extended attributes (user.* only;\n");
|
||||
printf(" privileged security.*/trusted.* namespaces are\n");
|
||||
printf(" never captured or applied)\n");
|
||||
printf(" -A, --acls Preserve POSIX ACLs (the system.posix_acl_* xattrs;\n");
|
||||
printf(" setting an ACL the receiver is not permitted to\n");
|
||||
printf(" set is warned and skipped, never fatal)\n");
|
||||
printf(" --fake-super Store the source uid/gid/mode/mtime in a reserved\n");
|
||||
printf(" user.fastsync.stat xattr on each written file instead\n");
|
||||
printf(" of applying ownership (for a later privileged restore);\n");
|
||||
printf(" partial: full rsync fake-super replay is out of scope\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");
|
||||
|
||||
Reference in New Issue
Block a user