quality: refactors (#321-#326) + fake-super directories (#319) #329
+13
-53
@@ -149,7 +149,7 @@ DirectoryScanner* directory_scanner_create_with_options(const char* root_directo
|
|||||||
scanner->options = *options;
|
scanner->options = *options;
|
||||||
if (scanner->options.chunk_size == 0)
|
if (scanner->options.chunk_size == 0)
|
||||||
scanner->options.chunk_size = DESIRED_CHUNK_SIZE;
|
scanner->options.chunk_size = DESIRED_CHUNK_SIZE;
|
||||||
scanner->directories = queue_create(100, dir_entry_destroy);
|
scanner->directories = queue_create(SCANNER_RESULT_QUEUE_CAP, dir_entry_destroy);
|
||||||
if (!scanner->directories) {
|
if (!scanner->directories) {
|
||||||
free(scanner);
|
free(scanner);
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -1101,61 +1101,23 @@ static ScannerAction scanner_process_entry(DirectoryScanner* scanner, ArrayList*
|
|||||||
free(rel_copy);
|
free(rel_copy);
|
||||||
return SCANNER_ACTION_CONTINUE;
|
return SCANNER_ACTION_CONTINUE;
|
||||||
}
|
}
|
||||||
File* file = file_create(cur_path);
|
/* Entry construction (data size, -R wire path, special/devices, hardlink
|
||||||
if (file == NULL) {
|
group, metadata, xattrs) is shared with the parallel scanner. */
|
||||||
free(rel_copy);
|
File* file = NULL;
|
||||||
free(inspected->link_target);
|
bool build_failed = false;
|
||||||
inspected->link_target = NULL;
|
ScannerBuildStatus status =
|
||||||
|
scanner_build_file_entry(&scanner->options, inspected, rel_copy, &file, &build_failed);
|
||||||
|
free(rel_copy);
|
||||||
|
rel_copy = NULL;
|
||||||
|
if (build_failed)
|
||||||
scanner->failed = true;
|
scanner->failed = true;
|
||||||
|
if (status == SCANNER_BUILD_SKIP)
|
||||||
return SCANNER_ACTION_CONTINUE;
|
return SCANNER_ACTION_CONTINUE;
|
||||||
}
|
if (status != SCANNER_BUILD_OK) {
|
||||||
if (inspected->is_symlink) {
|
|
||||||
file->is_symlink = true;
|
|
||||||
file->symlink_target = inspected->link_target;
|
|
||||||
inspected->link_target = NULL;
|
|
||||||
} else {
|
|
||||||
file->data->size = stats.st_size;
|
|
||||||
}
|
|
||||||
if (scanner->relative_mode) {
|
|
||||||
file->send_path = rel_copy;
|
|
||||||
rel_copy = NULL;
|
|
||||||
} else if (scanner->options.relative_prefix) {
|
|
||||||
file->send_path = scanner_prefix_send_path(scanner->options.relative_prefix, rel_copy);
|
|
||||||
free(rel_copy);
|
|
||||||
rel_copy = NULL;
|
|
||||||
if (!file->send_path) {
|
|
||||||
file_destroy(file);
|
|
||||||
scanner->failed = true;
|
|
||||||
return SCANNER_ACTION_BREAK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* --devices/--specials: a device/FIFO/socket entry marked for preservation
|
|
||||||
becomes a node to recreate (is_special, no data, rdev captured); an
|
|
||||||
unrequested non-regular entry is skipped (rsync default). */
|
|
||||||
ScannerSpecial special =
|
|
||||||
scanner_prepare_special(scanner->options.preserve_devices, scanner->options.preserve_specials,
|
|
||||||
scanner->options.copy_devices, file, &stats);
|
|
||||||
if (special == SCANNER_SPECIAL_SKIP) {
|
|
||||||
scanner_note_nonreg(&scanner->options, file->path);
|
|
||||||
free(rel_copy);
|
|
||||||
file_destroy(file);
|
|
||||||
return SCANNER_ACTION_CONTINUE;
|
|
||||||
}
|
|
||||||
if (scanner->options.hardlinks && S_ISREG(stats.st_mode))
|
|
||||||
scanner_assign_hardlink(scanner, scanner->options.hardlinks, file, &stats);
|
|
||||||
if (scanner->options.use_metadata)
|
|
||||||
file->metadata = file_metadata_create(file->path, &stats, scanner->options.preserve_atimes,
|
|
||||||
scanner->options.preserve_crtimes);
|
|
||||||
if (scanner->options.use_metadata && !file->metadata) {
|
|
||||||
free(rel_copy);
|
|
||||||
file_destroy(file);
|
|
||||||
scanner->failed = true;
|
scanner->failed = true;
|
||||||
return SCANNER_ACTION_BREAK;
|
return status == SCANNER_BUILD_FAIL_CONTINUE ? SCANNER_ACTION_CONTINUE : SCANNER_ACTION_BREAK;
|
||||||
}
|
}
|
||||||
if (!(file->link_group != 0 && !file->link_first))
|
|
||||||
scanner_capture_xattrs(scanner, file);
|
|
||||||
if (!array_list_add(chunk_data, file)) {
|
if (!array_list_add(chunk_data, file)) {
|
||||||
free(rel_copy);
|
|
||||||
file_destroy(file);
|
file_destroy(file);
|
||||||
scanner->failed = true;
|
scanner->failed = true;
|
||||||
return SCANNER_ACTION_BREAK;
|
return SCANNER_ACTION_BREAK;
|
||||||
@@ -1163,14 +1125,12 @@ static ScannerAction scanner_process_entry(DirectoryScanner* scanner, ArrayList*
|
|||||||
scanner->current_dir_produced = true;
|
scanner->current_dir_produced = true;
|
||||||
*chunk_data_size += file->data->size;
|
*chunk_data_size += file->data->size;
|
||||||
if (*chunk_data_size > scanner->options.chunk_size) {
|
if (*chunk_data_size > scanner->options.chunk_size) {
|
||||||
free(rel_copy);
|
|
||||||
Chunk* result = chunk_data_to_chunk(chunk_data);
|
Chunk* result = chunk_data_to_chunk(chunk_data);
|
||||||
if (!result)
|
if (!result)
|
||||||
scanner->failed = true;
|
scanner->failed = true;
|
||||||
*out_chunk = result;
|
*out_chunk = result;
|
||||||
return SCANNER_ACTION_CHUNK;
|
return SCANNER_ACTION_CHUNK;
|
||||||
}
|
}
|
||||||
free(rel_copy);
|
|
||||||
return SCANNER_ACTION_CONTINUE;
|
return SCANNER_ACTION_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,11 @@
|
|||||||
* keeps one transfer from spawning an unbounded pool on a very large machine. */
|
* keeps one transfer from spawning an unbounded pool on a very large machine. */
|
||||||
#define MAX_SCANNER_THREADS 256
|
#define MAX_SCANNER_THREADS 256
|
||||||
|
|
||||||
|
/* Depth of the scanner's work queues: the sequential scanner's pending-directory
|
||||||
|
* stack and the parallel scanner's result queue. Bounds memory for a very wide
|
||||||
|
* or very deep tree while leaving ample headroom for normal scans. */
|
||||||
|
#define SCANNER_RESULT_QUEUE_CAP 100
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
bool use_metadata;
|
bool use_metadata;
|
||||||
/* Phase 4 metadata capture: -U/--atimes and -N/--crtimes tell the scanner to
|
/* Phase 4 metadata capture: -U/--atimes and -N/--crtimes tell the scanner to
|
||||||
|
|||||||
+88
-15
@@ -285,32 +285,34 @@ bool entry_passes_selection(const FileListSet* file_list, const FilterRuleList*
|
|||||||
* read xattrs is non-fatal: the file is transferred without them. A symlink
|
* read xattrs is non-fatal: the file is transferred without them. A symlink
|
||||||
* entry reads the LINK's own xattrs (never the referent's) with the no-follow
|
* entry reads the LINK's own xattrs (never the referent's) with the no-follow
|
||||||
* variant; on Linux the VFS refuses xattrs on symlinks, so that yields NULL. */
|
* variant; on Linux the VFS refuses xattrs on symlinks, so that yields NULL. */
|
||||||
void scanner_capture_xattrs(const DirectoryScanner* scanner, File* file) {
|
void scanner_capture_xattrs_opts(const ScannerOptions* options, File* file) {
|
||||||
if (!scanner || !file || !(scanner->options.preserve_xattrs || scanner->options.preserve_acls))
|
if (!options || !file || !(options->preserve_xattrs || options->preserve_acls))
|
||||||
return;
|
return;
|
||||||
file->xattrs = file->is_symlink
|
file->xattrs = file->is_symlink ? xattr_capture_path_nofollow(file->path, options->preserve_acls)
|
||||||
? xattr_capture_path_nofollow(file->path, scanner->options.preserve_acls)
|
: xattr_capture_path(file->path, options->preserve_acls);
|
||||||
: xattr_capture_path(file->path, scanner->options.preserve_acls);
|
}
|
||||||
|
|
||||||
|
void scanner_capture_xattrs(const DirectoryScanner* scanner, File* file) {
|
||||||
|
if (!scanner)
|
||||||
|
return;
|
||||||
|
scanner_capture_xattrs_opts(&scanner->options, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Apply --hard-links (-H) detection to one regular File. On a sibling (a
|
/* 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
|
* 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
|
* and the first member's wire path but carries NO data payload (size 0); the
|
||||||
* first member is left untouched (data present, link_first). Allocation
|
* first member is left untouched (data present, link_first). Returns false on
|
||||||
* failure is fatal: the scanner is marked failed. */
|
* allocation failure (the caller marks the scan failed); the File stays usable
|
||||||
void scanner_assign_hardlink(DirectoryScanner* scanner, HardLinkTable* table, File* file,
|
* either way. */
|
||||||
const struct stat* stats) {
|
bool scanner_assign_hardlink(HardLinkTable* table, File* file, const struct stat* stats) {
|
||||||
if (!table || !file || !stats)
|
if (!table || !file || !stats)
|
||||||
return;
|
return true;
|
||||||
int gid;
|
int gid;
|
||||||
bool is_first;
|
bool is_first;
|
||||||
char* first_path = NULL;
|
char* first_path = NULL;
|
||||||
if (!hardlink_table_assign(table, file_wire_path(file), stats->st_dev, stats->st_ino, &gid,
|
if (!hardlink_table_assign(table, file_wire_path(file), stats->st_dev, stats->st_ino, &gid,
|
||||||
&is_first, &first_path)) {
|
&is_first, &first_path))
|
||||||
if (scanner)
|
return false;
|
||||||
scanner->failed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
file->link_group = gid;
|
file->link_group = gid;
|
||||||
file->link_first = is_first;
|
file->link_first = is_first;
|
||||||
if (!is_first) {
|
if (!is_first) {
|
||||||
@@ -319,6 +321,7 @@ void scanner_assign_hardlink(DirectoryScanner* scanner, HardLinkTable* table, Fi
|
|||||||
} else {
|
} else {
|
||||||
free(first_path);
|
free(first_path);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Phase 4 special/devices decision for one non-regular entry, matching rsync:
|
/* Phase 4 special/devices decision for one non-regular entry, matching rsync:
|
||||||
@@ -399,6 +402,76 @@ void scanner_note_nonreg(const ScannerOptions* options, const char* fs_path) {
|
|||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Construct one non-directory File from an inspected entry. Shared by the
|
||||||
|
* sequential and parallel scanners so entry construction has a single
|
||||||
|
* implementation: data size (or carried symlink), -R wire path, special/devices
|
||||||
|
* classification, hardlink group, metadata and xattr capture all happen here in
|
||||||
|
* the same order for both. See the declaration for the ownership contract. */
|
||||||
|
ScannerBuildStatus scanner_build_file_entry(const ScannerOptions* options, ScannerEntry* inspected,
|
||||||
|
const char* rel, File** out_file, bool* failed) {
|
||||||
|
*out_file = NULL;
|
||||||
|
if (failed)
|
||||||
|
*failed = false;
|
||||||
|
File* file = file_create(inspected->path);
|
||||||
|
if (!file) {
|
||||||
|
/* The File never existed, so drop the not-yet-transferred symlink target
|
||||||
|
here; the caller's entry teardown would otherwise double-free it. */
|
||||||
|
free(inspected->link_target);
|
||||||
|
inspected->link_target = NULL;
|
||||||
|
return SCANNER_BUILD_FAIL_CONTINUE;
|
||||||
|
}
|
||||||
|
if (inspected->is_symlink) {
|
||||||
|
file->is_symlink = true;
|
||||||
|
file->symlink_target = inspected->link_target;
|
||||||
|
inspected->link_target = NULL;
|
||||||
|
} else {
|
||||||
|
file->data->size = inspected->stats.st_size;
|
||||||
|
}
|
||||||
|
/* -R + --files-from uses the bare transfer-relative path; -R without
|
||||||
|
--files-from prefixes it. Plain scans keep the source path. */
|
||||||
|
bool relative_mode = options->relative && options->file_list != NULL;
|
||||||
|
if (relative_mode) {
|
||||||
|
file->send_path = str_dup(rel);
|
||||||
|
} else if (options->relative_prefix) {
|
||||||
|
file->send_path = scanner_prefix_send_path(options->relative_prefix, rel);
|
||||||
|
}
|
||||||
|
if ((relative_mode || options->relative_prefix) && !file->send_path) {
|
||||||
|
file_destroy(file);
|
||||||
|
return SCANNER_BUILD_FAIL_BREAK;
|
||||||
|
}
|
||||||
|
/* --devices/--specials: a device/FIFO/socket entry marked for preservation
|
||||||
|
becomes a node to recreate (is_special, no data, rdev captured); an
|
||||||
|
unrequested non-regular entry is skipped (rsync default). */
|
||||||
|
ScannerSpecial special =
|
||||||
|
scanner_prepare_special(options->preserve_devices, options->preserve_specials,
|
||||||
|
options->copy_devices, file, &inspected->stats);
|
||||||
|
if (special == SCANNER_SPECIAL_SKIP) {
|
||||||
|
scanner_note_nonreg(options, file->path);
|
||||||
|
file_destroy(file);
|
||||||
|
return SCANNER_BUILD_SKIP;
|
||||||
|
}
|
||||||
|
if (options->hardlinks && S_ISREG(inspected->stats.st_mode) &&
|
||||||
|
!scanner_assign_hardlink(options->hardlinks, file, &inspected->stats)) {
|
||||||
|
/* Allocation failure is non-fatal to this entry (it is still emitted) but
|
||||||
|
marks the scan failed, matching the historical inlined behaviour. */
|
||||||
|
if (failed)
|
||||||
|
*failed = true;
|
||||||
|
}
|
||||||
|
if (options->use_metadata) {
|
||||||
|
file->metadata = file_metadata_create(file->path, &inspected->stats, options->preserve_atimes,
|
||||||
|
options->preserve_crtimes);
|
||||||
|
if (!file->metadata) {
|
||||||
|
file_destroy(file);
|
||||||
|
return SCANNER_BUILD_FAIL_BREAK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* A hardlink sibling carries no data, so it carries no xattrs. */
|
||||||
|
if (!(file->link_group != 0 && !file->link_first))
|
||||||
|
scanner_capture_xattrs_opts(options, file);
|
||||||
|
*out_file = file;
|
||||||
|
return SCANNER_BUILD_OK;
|
||||||
|
}
|
||||||
|
|
||||||
/* rsync 3.4.1's `--info=mount` line, emitted when `-xx` drops a mount-point
|
/* rsync 3.4.1's `--info=mount` line, emitted when `-xx` drops a mount-point
|
||||||
* directory: `[sender] skipping mount-point dir NAME` (the client is the
|
* directory: `[sender] skipping mount-point dir NAME` (the client is the
|
||||||
* sender). Plain `-x` keeps the empty directory and prints nothing, matching
|
* sender). Plain `-x` keeps the empty directory and prints nothing, matching
|
||||||
|
|||||||
@@ -62,6 +62,17 @@ typedef enum {
|
|||||||
SCANNER_SPECIAL_SKIP, /* non-regular entry not requested: skip */
|
SCANNER_SPECIAL_SKIP, /* non-regular entry not requested: skip */
|
||||||
} ScannerSpecial;
|
} ScannerSpecial;
|
||||||
|
|
||||||
|
/* Result of scanner_build_file_entry(). The two failure variants preserve the
|
||||||
|
* sequential scanner's historical distinction between a failure before the
|
||||||
|
* File existed (which kept walking the directory) and one afterwards (which cut
|
||||||
|
* the chunk short); both mark the scan failed. */
|
||||||
|
typedef enum {
|
||||||
|
SCANNER_BUILD_OK, /* File built; caller owns it */
|
||||||
|
SCANNER_BUILD_SKIP, /* non-regular entry not preserved; no File */
|
||||||
|
SCANNER_BUILD_FAIL_CONTINUE, /* failed before the File existed */
|
||||||
|
SCANNER_BUILD_FAIL_BREAK, /* failed after the File existed */
|
||||||
|
} ScannerBuildStatus;
|
||||||
|
|
||||||
/* scanner_filter.c */
|
/* scanner_filter.c */
|
||||||
void filter_node_destroy(void* item);
|
void filter_node_destroy(void* item);
|
||||||
FilterNode* filter_node_alloc(FilterNode* parent, FilterRuleList* own);
|
FilterNode* filter_node_alloc(FilterNode* parent, FilterRuleList* own);
|
||||||
@@ -79,10 +90,21 @@ bool entry_passes_selection(const FileListSet* file_list, const FilterRuleList*
|
|||||||
const FilterNode* node, const char* rel, const char* leaf, bool is_dir,
|
const FilterNode* node, const char* rel, const char* leaf, bool is_dir,
|
||||||
bool per_dir_filters, bool exclude_filter_files, bool* protect_out);
|
bool per_dir_filters, bool exclude_filter_files, bool* protect_out);
|
||||||
void scanner_capture_xattrs(const DirectoryScanner* scanner, File* file);
|
void scanner_capture_xattrs(const DirectoryScanner* scanner, File* file);
|
||||||
void scanner_assign_hardlink(DirectoryScanner* scanner, HardLinkTable* table, File* file,
|
void scanner_capture_xattrs_opts(const ScannerOptions* options, File* file);
|
||||||
const struct stat* stats);
|
bool scanner_assign_hardlink(HardLinkTable* table, File* file, const struct stat* stats);
|
||||||
ScannerSpecial scanner_prepare_special(bool preserve_devices, bool preserve_specials,
|
ScannerSpecial scanner_prepare_special(bool preserve_devices, bool preserve_specials,
|
||||||
bool copy_devices, File* file, const struct stat* stats);
|
bool copy_devices, File* file, const struct stat* stats);
|
||||||
|
/* Build one non-directory transfer File from an inspected entry. `rel` is the
|
||||||
|
* entry's transfer-root-relative path (used for the -R wire path); `inspected`
|
||||||
|
* supplies the on-disk path, stats and (for a carried symlink) the target whose
|
||||||
|
* ownership transfers to the File. Populates data size, send_path, special-node
|
||||||
|
* state, hardlink group, metadata and xattrs. On SCANNER_BUILD_OK the caller
|
||||||
|
* owns *out_file; on SCANNER_BUILD_SKIP it is NULL and the entry is dropped; on
|
||||||
|
* either failure it is NULL and the caller must mark the scan failed. `*failed`
|
||||||
|
* additionally reports a non-fatal hardlink-table allocation failure, in which
|
||||||
|
* case a usable File is still returned. */
|
||||||
|
ScannerBuildStatus scanner_build_file_entry(const ScannerOptions* options, ScannerEntry* inspected,
|
||||||
|
const char* rel, File** out_file, bool* failed);
|
||||||
bool excluded_sink_append(ArrayList* list, mtx_t* mtx, const char* rel);
|
bool excluded_sink_append(ArrayList* list, mtx_t* mtx, const char* rel);
|
||||||
void scanner_note_nonreg(const ScannerOptions* options, const char* fs_path);
|
void scanner_note_nonreg(const ScannerOptions* options, const char* fs_path);
|
||||||
void scanner_note_mount(const ScannerOptions* options, const char* fs_path);
|
void scanner_note_mount(const ScannerOptions* options, const char* fs_path);
|
||||||
|
|||||||
+167
-193
@@ -109,7 +109,7 @@ static void parallel_scanner_creation_failed(ParallelScanner* ps) {
|
|||||||
|
|
||||||
/* Initialize result queue and synchronization primitives. Returns true on success. */
|
/* Initialize result queue and synchronization primitives. Returns true on success. */
|
||||||
static bool parallel_scanner_init(ParallelScanner* ps) {
|
static bool parallel_scanner_init(ParallelScanner* ps) {
|
||||||
ps->result_queue = queue_create(100, chunk_destroy);
|
ps->result_queue = queue_create(SCANNER_RESULT_QUEUE_CAP, chunk_destroy);
|
||||||
if (!ps->result_queue)
|
if (!ps->result_queue)
|
||||||
return false;
|
return false;
|
||||||
atomic_init(&ps->cancelled, false);
|
atomic_init(&ps->cancelled, false);
|
||||||
@@ -210,6 +210,157 @@ static Chunk* batch_files(ArrayList* files, unsigned long long chunk_size, Queue
|
|||||||
return first;
|
return first;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Record the delete-protection mirror of a root entry that
|
||||||
|
* scanner_inspect_entry() skipped (inspection == 0): a dereferenced symlink
|
||||||
|
* with no referent is a partial-transfer I/O error and a user-selection or size
|
||||||
|
* prune protects the entry's destination mirror. */
|
||||||
|
static void scan_root_record_skipped(const ScannerOptions* options, const char* root_directory,
|
||||||
|
const char* name, const ScannerEntry* inspected,
|
||||||
|
ParallelScanner* ps) {
|
||||||
|
if (inspected->referent_error)
|
||||||
|
ps->io_error = true;
|
||||||
|
ArrayList* sink = NULL;
|
||||||
|
if (inspected->excluded)
|
||||||
|
sink = inspected->size_excluded ? options->size_skipped_paths : options->excluded_paths;
|
||||||
|
if (!sink)
|
||||||
|
return;
|
||||||
|
/* A root-level prune protects the destination mirror of the entry's wire
|
||||||
|
path: under -R + --files-from that is the bare relative name, otherwise it
|
||||||
|
is the full source path with a leading '/' removed (matching the
|
||||||
|
send_path/file_wire_path the scanner hands the sender). */
|
||||||
|
if (options->relative && options->file_list != NULL) {
|
||||||
|
if (!excluded_sink_append(sink, options->excluded_mutex, name))
|
||||||
|
ps->failed = true;
|
||||||
|
} else if (options->relative_prefix) {
|
||||||
|
char* wrel = scanner_prefix_send_path(options->relative_prefix, name);
|
||||||
|
if (!wrel) {
|
||||||
|
ps->failed = true;
|
||||||
|
} else {
|
||||||
|
if (!excluded_sink_append(sink, options->excluded_mutex, wrel))
|
||||||
|
ps->failed = true;
|
||||||
|
free(wrel);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
char* abs_path = path_cat(root_directory, name);
|
||||||
|
if (!abs_path) {
|
||||||
|
ps->failed = true;
|
||||||
|
} else {
|
||||||
|
const char* rel = *abs_path == '/' ? abs_path + 1 : abs_path;
|
||||||
|
if (!excluded_sink_append(sink, options->excluded_mutex, rel))
|
||||||
|
ps->failed = true;
|
||||||
|
free(abs_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Record the delete-protection mirror of a root entry dropped by the
|
||||||
|
* --files-from allow-set or a filter rule. Returns false only when the -R
|
||||||
|
* prefix could not be built (the caller must abandon the entry immediately);
|
||||||
|
* other allocation failures mark the scan failed but let the caller continue to
|
||||||
|
* the filter-notice step, matching the historical inlined flow. */
|
||||||
|
static bool scan_root_record_protection(const ScannerOptions* options, const char* rel,
|
||||||
|
const char* name, const char* cur_path, bool protect,
|
||||||
|
bool passes, bool use_rel, ParallelScanner* ps) {
|
||||||
|
if (passes && !protect)
|
||||||
|
return true;
|
||||||
|
/* --files-from subset pruning is not a filter exclusion; -R bare-wire-path
|
||||||
|
exclusions are never recorded (see ScannerOptions.excluded_paths). */
|
||||||
|
bool files_from_prune = options->file_list && !file_list_affects(options->file_list, rel);
|
||||||
|
if ((!files_from_prune && !use_rel) || protect) {
|
||||||
|
const char* rel_path;
|
||||||
|
char* prefixed = NULL;
|
||||||
|
if (use_rel) {
|
||||||
|
/* -R + --files-from: the destination/wire path is the bare relative
|
||||||
|
name, not the source path. */
|
||||||
|
rel_path = rel;
|
||||||
|
} else if (options->relative_prefix) {
|
||||||
|
prefixed = scanner_prefix_send_path(options->relative_prefix, name);
|
||||||
|
if (!prefixed)
|
||||||
|
return false;
|
||||||
|
rel_path = prefixed;
|
||||||
|
} else {
|
||||||
|
rel_path = *cur_path == '/' ? cur_path + 1 : cur_path;
|
||||||
|
}
|
||||||
|
if (options->excluded_paths &&
|
||||||
|
!excluded_sink_append(options->excluded_paths, options->excluded_mutex, rel_path))
|
||||||
|
ps->failed = true;
|
||||||
|
free(prefixed);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Root-level directory node: apply -x/--one-file-system and either emit the
|
||||||
|
* mount-point directory (plain -x) or queue the directory for a worker. */
|
||||||
|
static void scan_root_dir(const ScannerOptions* options, const char* cur_path, const char* rel,
|
||||||
|
const struct stat* st, ArrayList* root_files, ArrayList* subdirs,
|
||||||
|
dev_t root_dev, ParallelScanner* ps) {
|
||||||
|
if (!scanner_same_filesystem(options->one_file_system, root_dev, st->st_dev)) {
|
||||||
|
if (options->one_file_system > 1) {
|
||||||
|
/* -xx: drop the mount-point directory entirely (rsync) and print the
|
||||||
|
--info=mount line when enabled. */
|
||||||
|
scanner_note_mount(options, cur_path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* -x/--one-file-system: emit the mount-point directory entry (empty) but do
|
||||||
|
not descend into it (see the sequential scanner for the same rule). */
|
||||||
|
File* mount = scanner_build_dir_file(cur_path, st, options);
|
||||||
|
if (!mount) {
|
||||||
|
ps->failed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (options->relative_prefix) {
|
||||||
|
mount->send_path = scanner_prefix_send_path(options->relative_prefix, rel);
|
||||||
|
if (!mount->send_path) {
|
||||||
|
file_destroy(mount);
|
||||||
|
ps->failed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!array_list_add(root_files, mount)) {
|
||||||
|
file_destroy(mount);
|
||||||
|
ps->failed = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
char* dir = str_dup(cur_path);
|
||||||
|
if (!dir || !array_list_add(subdirs, dir)) {
|
||||||
|
free(dir);
|
||||||
|
ps->failed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Build a non-directory root entry through the shared construction path and add
|
||||||
|
* it to `root_files`. A non-regular entry the options do not preserve is
|
||||||
|
* dropped by the builder (which prints rsync's nonreg line); an allocation
|
||||||
|
* failure marks the scan failed. */
|
||||||
|
static void scan_root_add_non_dir(const ScannerOptions* options, ScannerEntry* inspected,
|
||||||
|
const char* rel, ArrayList* root_files, ParallelScanner* ps) {
|
||||||
|
File* file = NULL;
|
||||||
|
bool failed = false;
|
||||||
|
ScannerBuildStatus status = scanner_build_file_entry(options, inspected, rel, &file, &failed);
|
||||||
|
if (failed)
|
||||||
|
ps->failed = true;
|
||||||
|
if (status != SCANNER_BUILD_OK)
|
||||||
|
return;
|
||||||
|
if (!array_list_add(root_files, file)) {
|
||||||
|
file_destroy(file);
|
||||||
|
ps->failed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Regular file or carried symlink at the transfer root. */
|
||||||
|
static void scan_root_file(const ScannerOptions* options, ScannerEntry* inspected, const char* rel,
|
||||||
|
ArrayList* root_files, ParallelScanner* ps) {
|
||||||
|
scan_root_add_non_dir(options, inspected, rel, root_files, ps);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Device/FIFO/socket at the transfer root: recreated under --devices/--specials,
|
||||||
|
* otherwise dropped by the shared builder. */
|
||||||
|
static void scan_root_special(const ScannerOptions* options, ScannerEntry* inspected,
|
||||||
|
const char* rel, ArrayList* root_files, ParallelScanner* ps) {
|
||||||
|
scan_root_add_non_dir(options, inspected, rel, root_files, ps);
|
||||||
|
}
|
||||||
|
|
||||||
/* Scan one root-directory entry into either the subdirs or files list. */
|
/* Scan one root-directory entry into either the subdirs or files list. */
|
||||||
static void scan_root_entry(const ScannerOptions* options, const FilterNode* root_node,
|
static void scan_root_entry(const ScannerOptions* options, const FilterNode* root_node,
|
||||||
const char* root_directory, const struct dirent* entry,
|
const char* root_directory, const struct dirent* entry,
|
||||||
@@ -223,51 +374,16 @@ static void scan_root_entry(const ScannerOptions* options, const FilterNode* roo
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (inspection == 0) {
|
if (inspection == 0) {
|
||||||
if (inspected.referent_error)
|
scan_root_record_skipped(options, root_directory, entry->d_name, &inspected, ps);
|
||||||
ps->io_error = true;
|
|
||||||
ArrayList* sink = NULL;
|
|
||||||
if (inspected.excluded)
|
|
||||||
sink = inspected.size_excluded ? options->size_skipped_paths : options->excluded_paths;
|
|
||||||
if (sink) {
|
|
||||||
/* A root-level prune protects the destination mirror of the entry's wire
|
|
||||||
path: under -R + --files-from that is the bare relative name, otherwise
|
|
||||||
it is the full source path with a leading '/' removed (matching the
|
|
||||||
send_path/file_wire_path the scanner hands the sender). */
|
|
||||||
if (options->relative && options->file_list != NULL) {
|
|
||||||
if (!excluded_sink_append(sink, options->excluded_mutex, entry->d_name))
|
|
||||||
ps->failed = true;
|
|
||||||
} else if (options->relative_prefix) {
|
|
||||||
char* wrel = scanner_prefix_send_path(options->relative_prefix, entry->d_name);
|
|
||||||
if (!wrel) {
|
|
||||||
ps->failed = true;
|
|
||||||
} else {
|
|
||||||
if (!excluded_sink_append(sink, options->excluded_mutex, wrel))
|
|
||||||
ps->failed = true;
|
|
||||||
free(wrel);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
char* abs_path = path_cat(root_directory, entry->d_name);
|
|
||||||
if (!abs_path) {
|
|
||||||
ps->failed = true;
|
|
||||||
} else {
|
|
||||||
const char* rel = *abs_path == '/' ? abs_path + 1 : abs_path;
|
|
||||||
if (!excluded_sink_append(sink, options->excluded_mutex, rel))
|
|
||||||
ps->failed = true;
|
|
||||||
free(abs_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
char* cur_path = inspected.path;
|
char* cur_path = inspected.path;
|
||||||
struct stat st = inspected.stats;
|
|
||||||
bool is_dir = inspected.is_directory;
|
|
||||||
char* rel = str_dup(entry->d_name);
|
char* rel = str_dup(entry->d_name);
|
||||||
if (!rel) {
|
if (!rel) {
|
||||||
free(cur_path);
|
|
||||||
ps->failed = true;
|
ps->failed = true;
|
||||||
return;
|
goto done;
|
||||||
}
|
}
|
||||||
|
bool is_dir = inspected.is_directory;
|
||||||
bool protect = false;
|
bool protect = false;
|
||||||
bool passes = entry_passes_selection(options->file_list, options->base_filters, root_node, rel,
|
bool passes = entry_passes_selection(options->file_list, options->base_filters, root_node, rel,
|
||||||
entry->d_name, is_dir, options->per_dir_filters,
|
entry->d_name, is_dir, options->per_dir_filters,
|
||||||
@@ -275,169 +391,27 @@ static void scan_root_entry(const ScannerOptions* options, const FilterNode* roo
|
|||||||
/* -R + --files-from: root-level files keep their bare relative send path. */
|
/* -R + --files-from: root-level files keep their bare relative send path. */
|
||||||
bool use_rel = options->relative && options->file_list != NULL;
|
bool use_rel = options->relative && options->file_list != NULL;
|
||||||
if (!passes || protect) {
|
if (!passes || protect) {
|
||||||
/* --files-from subset pruning is not a filter exclusion; -R bare-wire-path
|
if (!scan_root_record_protection(options, rel, entry->d_name, cur_path, protect, passes,
|
||||||
exclusions are never recorded (see ScannerOptions.excluded_paths). */
|
use_rel, ps)) {
|
||||||
bool files_from_prune = options->file_list && !file_list_affects(options->file_list, rel);
|
ps->failed = true;
|
||||||
if ((!files_from_prune && !use_rel) || protect) {
|
goto done;
|
||||||
const char* rel_path;
|
|
||||||
char* prefixed = NULL;
|
|
||||||
if (use_rel) {
|
|
||||||
/* -R + --files-from: the destination/wire path is the bare relative
|
|
||||||
name, not the source path. */
|
|
||||||
rel_path = rel;
|
|
||||||
} else if (options->relative_prefix) {
|
|
||||||
prefixed = scanner_prefix_send_path(options->relative_prefix, entry->d_name);
|
|
||||||
if (!prefixed) {
|
|
||||||
free(rel);
|
|
||||||
free(cur_path);
|
|
||||||
ps->failed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rel_path = prefixed;
|
|
||||||
} else {
|
|
||||||
rel_path = *cur_path == '/' ? cur_path + 1 : cur_path;
|
|
||||||
}
|
|
||||||
if (options->excluded_paths &&
|
|
||||||
!excluded_sink_append(options->excluded_paths, options->excluded_mutex, rel_path))
|
|
||||||
ps->failed = true;
|
|
||||||
free(prefixed);
|
|
||||||
}
|
}
|
||||||
if (!passes) {
|
if (!passes) {
|
||||||
scanner_note_filter(options, entry->d_name);
|
scanner_note_filter(options, entry->d_name);
|
||||||
free(rel);
|
goto done;
|
||||||
free(cur_path);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (is_dir) {
|
if (is_dir) {
|
||||||
if (!scanner_same_filesystem(options->one_file_system, root_dev, st.st_dev)) {
|
scan_root_dir(options, cur_path, rel, &inspected.stats, root_files, subdirs, root_dev, ps);
|
||||||
if (options->one_file_system > 1) {
|
} else if (S_ISCHR(inspected.stats.st_mode) || S_ISBLK(inspected.stats.st_mode) ||
|
||||||
/* -xx: drop the mount-point directory entirely (rsync) and print the
|
S_ISFIFO(inspected.stats.st_mode) || S_ISSOCK(inspected.stats.st_mode)) {
|
||||||
--info=mount line when enabled. */
|
scan_root_special(options, &inspected, rel, root_files, ps);
|
||||||
scanner_note_mount(options, cur_path);
|
|
||||||
free(rel);
|
|
||||||
free(cur_path);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/* -x/--one-file-system: emit the mount-point directory entry (empty) but
|
|
||||||
do not descend into it (see the sequential scanner for the same rule). */
|
|
||||||
File* mount = file_create(cur_path);
|
|
||||||
free(cur_path);
|
|
||||||
if (mount == NULL) {
|
|
||||||
free(rel);
|
|
||||||
ps->failed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mount->is_dir = true;
|
|
||||||
if (options->use_metadata) {
|
|
||||||
mount->metadata = file_metadata_create(mount->path, &st, options->preserve_atimes,
|
|
||||||
options->preserve_crtimes);
|
|
||||||
if (!mount->metadata) {
|
|
||||||
free(rel);
|
|
||||||
file_destroy(mount);
|
|
||||||
ps->failed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (options->relative_prefix) {
|
|
||||||
mount->send_path = scanner_prefix_send_path(options->relative_prefix, rel);
|
|
||||||
if (!mount->send_path) {
|
|
||||||
free(rel);
|
|
||||||
file_destroy(mount);
|
|
||||||
ps->failed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
free(rel);
|
|
||||||
if (!array_list_add(root_files, mount)) {
|
|
||||||
file_destroy(mount);
|
|
||||||
ps->failed = true;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
free(rel);
|
|
||||||
if (!array_list_add(subdirs, cur_path)) {
|
|
||||||
free(cur_path);
|
|
||||||
ps->failed = true;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
File* file = file_create(cur_path);
|
|
||||||
free(cur_path);
|
|
||||||
if (!file) {
|
|
||||||
free(rel);
|
|
||||||
free(inspected.link_target);
|
|
||||||
inspected.link_target = NULL;
|
|
||||||
ps->failed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (inspected.is_symlink) {
|
|
||||||
file->is_symlink = true;
|
|
||||||
file->symlink_target = inspected.link_target;
|
|
||||||
inspected.link_target = NULL;
|
|
||||||
} else {
|
} else {
|
||||||
file->data->size = st.st_size;
|
scan_root_file(options, &inspected, rel, root_files, ps);
|
||||||
}
|
|
||||||
if (use_rel) {
|
|
||||||
file->send_path = rel;
|
|
||||||
rel = NULL;
|
|
||||||
} else if (options->relative_prefix) {
|
|
||||||
file->send_path = scanner_prefix_send_path(options->relative_prefix, rel);
|
|
||||||
free(rel);
|
|
||||||
rel = NULL;
|
|
||||||
if (!file->send_path) {
|
|
||||||
file_destroy(file);
|
|
||||||
ps->failed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ScannerSpecial special = scanner_prepare_special(
|
|
||||||
options->preserve_devices, options->preserve_specials, options->copy_devices, file, &st);
|
|
||||||
if (special == SCANNER_SPECIAL_SKIP) {
|
|
||||||
scanner_note_nonreg(ps->options, file->path);
|
|
||||||
free(rel);
|
|
||||||
file_destroy(file);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (options->hardlinks && S_ISREG(st.st_mode)) {
|
|
||||||
int gid;
|
|
||||||
bool is_first;
|
|
||||||
char* first_path = NULL;
|
|
||||||
if (!hardlink_table_assign((HardLinkTable*)options->hardlinks, file_wire_path(file), st.st_dev,
|
|
||||||
st.st_ino, &gid, &is_first, &first_path)) {
|
|
||||||
ps->failed = true;
|
|
||||||
} else {
|
|
||||||
file->link_group = gid;
|
|
||||||
file->link_first = is_first;
|
|
||||||
if (!is_first) {
|
|
||||||
file->hardlink_target = first_path;
|
|
||||||
file->data->size = 0;
|
|
||||||
} else {
|
|
||||||
free(first_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (options->use_metadata)
|
|
||||||
file->metadata =
|
|
||||||
file_metadata_create(file->path, &st, options->preserve_atimes, options->preserve_crtimes);
|
|
||||||
if (options->use_metadata && !file->metadata) {
|
|
||||||
free(rel);
|
|
||||||
file_destroy(file);
|
|
||||||
ps->failed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ((options->preserve_xattrs || options->preserve_acls) &&
|
|
||||||
!(file->link_group != 0 && !file->link_first))
|
|
||||||
file->xattrs = file->is_symlink
|
|
||||||
? xattr_capture_path_nofollow(file->path, options->preserve_acls)
|
|
||||||
: xattr_capture_path(file->path, options->preserve_acls);
|
|
||||||
if (!array_list_add(root_files, file)) {
|
|
||||||
free(rel);
|
|
||||||
file_destroy(file);
|
|
||||||
ps->failed = true;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
done:
|
||||||
free(rel);
|
free(rel);
|
||||||
|
free(cur_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Scan the root directory itself, collecting root files and subdirectories.
|
/* Scan the root directory itself, collecting root files and subdirectories.
|
||||||
|
|||||||
@@ -276,7 +276,7 @@ static bool identity_wire_map_valid(const IdentityMap* map) {
|
|||||||
}
|
}
|
||||||
if (map->to < IDENTITY_CURRENT)
|
if (map->to < IDENTITY_CURRENT)
|
||||||
return false;
|
return false;
|
||||||
if (map->to_name && strlen(map->to_name) > 255)
|
if (map->to_name && strlen(map->to_name) > IDENTITY_MAX_NAME_LEN)
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,11 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
/* Maximum length of a receiver-resolved identity name in a FROM:TO map's TO
|
||||||
|
* field. Bounded so a malicious/huge name can never cross the wire (see
|
||||||
|
* identity_wire_map_valid). */
|
||||||
|
#define IDENTITY_MAX_NAME_LEN 255
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Identity mapping: --numeric-ids / --usermap / --groupmap / --chown / --copy-as.
|
* Identity mapping: --numeric-ids / --usermap / --groupmap / --chown / --copy-as.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ Server* server_create_ex(int port, const ServerBindOptions* bind_opts) {
|
|||||||
|
|
||||||
server->file_descriptor = file_descriptor;
|
server->file_descriptor = file_descriptor;
|
||||||
server->ssl_ctx = NULL;
|
server->ssl_ctx = NULL;
|
||||||
server->max_connections = 100;
|
server->max_connections = SERVER_DEFAULT_MAX_CONNECTIONS;
|
||||||
server->active_connections = 0;
|
server->active_connections = 0;
|
||||||
server->limit_registry = NULL;
|
server->limit_registry = NULL;
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,10 @@
|
|||||||
* stored here so the transport layer does not depend on daemon config. */
|
* stored here so the transport layer does not depend on daemon config. */
|
||||||
struct DaemonLimitRegistry;
|
struct DaemonLimitRegistry;
|
||||||
|
|
||||||
|
/* Connection cap applied by server_create_ex() until the daemon's configured
|
||||||
|
* `max connections` overrides it via server_set_max_connections(). */
|
||||||
|
#define SERVER_DEFAULT_MAX_CONNECTIONS 100
|
||||||
|
|
||||||
typedef struct Server {
|
typedef struct Server {
|
||||||
struct sockaddr_storage address;
|
struct sockaddr_storage address;
|
||||||
unsigned int address_length;
|
unsigned int address_length;
|
||||||
|
|||||||
Reference in New Issue
Block a user