diff --git a/src/client/scanner.c b/src/client/scanner.c index 55f38f1..4d8ca5b 100644 --- a/src/client/scanner.c +++ b/src/client/scanner.c @@ -149,7 +149,7 @@ DirectoryScanner* directory_scanner_create_with_options(const char* root_directo scanner->options = *options; if (scanner->options.chunk_size == 0) 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) { free(scanner); return NULL; @@ -1101,61 +1101,23 @@ static ScannerAction scanner_process_entry(DirectoryScanner* scanner, ArrayList* free(rel_copy); return SCANNER_ACTION_CONTINUE; } - File* file = file_create(cur_path); - if (file == NULL) { - free(rel_copy); - free(inspected->link_target); - inspected->link_target = NULL; + /* Entry construction (data size, -R wire path, special/devices, hardlink + group, metadata, xattrs) is shared with the parallel scanner. */ + File* file = NULL; + bool build_failed = false; + 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; + if (status == SCANNER_BUILD_SKIP) return SCANNER_ACTION_CONTINUE; - } - 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); + if (status != SCANNER_BUILD_OK) { 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)) { - free(rel_copy); file_destroy(file); scanner->failed = true; return SCANNER_ACTION_BREAK; @@ -1163,14 +1125,12 @@ static ScannerAction scanner_process_entry(DirectoryScanner* scanner, ArrayList* scanner->current_dir_produced = true; *chunk_data_size += file->data->size; if (*chunk_data_size > scanner->options.chunk_size) { - free(rel_copy); Chunk* result = chunk_data_to_chunk(chunk_data); if (!result) scanner->failed = true; *out_chunk = result; return SCANNER_ACTION_CHUNK; } - free(rel_copy); return SCANNER_ACTION_CONTINUE; } diff --git a/src/client/scanner.h b/src/client/scanner.h index 88ff79d..406e4b1 100644 --- a/src/client/scanner.h +++ b/src/client/scanner.h @@ -19,6 +19,11 @@ * keeps one transfer from spawning an unbounded pool on a very large machine. */ #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 { bool use_metadata; /* Phase 4 metadata capture: -U/--atimes and -N/--crtimes tell the scanner to diff --git a/src/client/scanner_filter.c b/src/client/scanner_filter.c index d4b0007..155b835 100644 --- a/src/client/scanner_filter.c +++ b/src/client/scanner_filter.c @@ -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 * 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. */ -void scanner_capture_xattrs(const DirectoryScanner* scanner, File* file) { - if (!scanner || !file || !(scanner->options.preserve_xattrs || scanner->options.preserve_acls)) +void scanner_capture_xattrs_opts(const ScannerOptions* options, File* file) { + if (!options || !file || !(options->preserve_xattrs || options->preserve_acls)) return; - file->xattrs = file->is_symlink - ? xattr_capture_path_nofollow(file->path, scanner->options.preserve_acls) - : xattr_capture_path(file->path, scanner->options.preserve_acls); + file->xattrs = file->is_symlink ? xattr_capture_path_nofollow(file->path, options->preserve_acls) + : xattr_capture_path(file->path, 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 * 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 - * first member is left untouched (data present, link_first). Allocation - * failure is fatal: the scanner is marked failed. */ -void scanner_assign_hardlink(DirectoryScanner* scanner, HardLinkTable* table, File* file, - const struct stat* stats) { + * first member is left untouched (data present, link_first). Returns false on + * allocation failure (the caller marks the scan failed); the File stays usable + * either way. */ +bool scanner_assign_hardlink(HardLinkTable* table, File* file, const struct stat* stats) { if (!table || !file || !stats) - return; + return true; int gid; bool is_first; char* first_path = NULL; if (!hardlink_table_assign(table, file_wire_path(file), stats->st_dev, stats->st_ino, &gid, - &is_first, &first_path)) { - if (scanner) - scanner->failed = true; - return; - } + &is_first, &first_path)) + return false; file->link_group = gid; file->link_first = is_first; if (!is_first) { @@ -319,6 +321,7 @@ void scanner_assign_hardlink(DirectoryScanner* scanner, HardLinkTable* table, Fi } else { free(first_path); } + return true; } /* 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); } +/* 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 * directory: `[sender] skipping mount-point dir NAME` (the client is the * sender). Plain `-x` keeps the empty directory and prints nothing, matching diff --git a/src/client/scanner_internal.h b/src/client/scanner_internal.h index d32bd40..400d661 100644 --- a/src/client/scanner_internal.h +++ b/src/client/scanner_internal.h @@ -62,6 +62,17 @@ typedef enum { SCANNER_SPECIAL_SKIP, /* non-regular entry not requested: skip */ } 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 */ void filter_node_destroy(void* item); 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, bool per_dir_filters, bool exclude_filter_files, bool* protect_out); void scanner_capture_xattrs(const DirectoryScanner* scanner, File* file); -void scanner_assign_hardlink(DirectoryScanner* scanner, HardLinkTable* table, File* file, - const struct stat* stats); +void scanner_capture_xattrs_opts(const ScannerOptions* options, File* file); +bool scanner_assign_hardlink(HardLinkTable* table, File* file, const struct stat* stats); ScannerSpecial scanner_prepare_special(bool preserve_devices, bool preserve_specials, 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); void scanner_note_nonreg(const ScannerOptions* options, const char* fs_path); void scanner_note_mount(const ScannerOptions* options, const char* fs_path); diff --git a/src/client/scanner_parallel.c b/src/client/scanner_parallel.c index 40e4330..071a1d8 100644 --- a/src/client/scanner_parallel.c +++ b/src/client/scanner_parallel.c @@ -109,7 +109,7 @@ static void parallel_scanner_creation_failed(ParallelScanner* ps) { /* Initialize result queue and synchronization primitives. Returns true on success. */ 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) return false; atomic_init(&ps->cancelled, false); @@ -210,6 +210,157 @@ static Chunk* batch_files(ArrayList* files, unsigned long long chunk_size, Queue 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. */ static void scan_root_entry(const ScannerOptions* options, const FilterNode* root_node, const char* root_directory, const struct dirent* entry, @@ -223,51 +374,16 @@ static void scan_root_entry(const ScannerOptions* options, const FilterNode* roo return; } if (inspection == 0) { - 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) { - /* 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); - } - } - } + scan_root_record_skipped(options, root_directory, entry->d_name, &inspected, ps); return; } char* cur_path = inspected.path; - struct stat st = inspected.stats; - bool is_dir = inspected.is_directory; char* rel = str_dup(entry->d_name); if (!rel) { - free(cur_path); ps->failed = true; - return; + goto done; } + bool is_dir = inspected.is_directory; bool protect = false; bool passes = entry_passes_selection(options->file_list, options->base_filters, root_node, rel, 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. */ bool use_rel = options->relative && options->file_list != NULL; if (!passes || protect) { - /* --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, 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 (!scan_root_record_protection(options, rel, entry->d_name, cur_path, protect, passes, + use_rel, ps)) { + ps->failed = true; + goto done; } if (!passes) { scanner_note_filter(options, entry->d_name); - free(rel); - free(cur_path); - return; + goto done; } } if (is_dir) { - 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); - 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; + scan_root_dir(options, cur_path, rel, &inspected.stats, root_files, subdirs, root_dev, ps); + } else if (S_ISCHR(inspected.stats.st_mode) || S_ISBLK(inspected.stats.st_mode) || + S_ISFIFO(inspected.stats.st_mode) || S_ISSOCK(inspected.stats.st_mode)) { + scan_root_special(options, &inspected, rel, root_files, ps); } else { - file->data->size = st.st_size; - } - 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; + scan_root_file(options, &inspected, rel, root_files, ps); } +done: free(rel); + free(cur_path); } /* Scan the root directory itself, collecting root files and subdirectories. diff --git a/src/shared/identity.c b/src/shared/identity.c index f2b32ad..80e30ee 100644 --- a/src/shared/identity.c +++ b/src/shared/identity.c @@ -276,7 +276,7 @@ static bool identity_wire_map_valid(const IdentityMap* map) { } if (map->to < IDENTITY_CURRENT) 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 true; } diff --git a/src/shared/identity.h b/src/shared/identity.h index a7548c2..7b99bbe 100644 --- a/src/shared/identity.h +++ b/src/shared/identity.h @@ -6,6 +6,11 @@ #include #include +/* 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. * diff --git a/src/shared/transport_tcp.c b/src/shared/transport_tcp.c index 059e4f1..2238a94 100644 --- a/src/shared/transport_tcp.c +++ b/src/shared/transport_tcp.c @@ -134,7 +134,7 @@ Server* server_create_ex(int port, const ServerBindOptions* bind_opts) { server->file_descriptor = file_descriptor; server->ssl_ctx = NULL; - server->max_connections = 100; + server->max_connections = SERVER_DEFAULT_MAX_CONNECTIONS; server->active_connections = 0; server->limit_registry = NULL; diff --git a/src/shared/transport_tcp.h b/src/shared/transport_tcp.h index c3862a6..59f6e89 100644 --- a/src/shared/transport_tcp.h +++ b/src/shared/transport_tcp.h @@ -11,6 +11,10 @@ * stored here so the transport layer does not depend on daemon config. */ 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 { struct sockaddr_storage address; unsigned int address_length;