Merge feat/p4-devices

# Conflicts:
#	src/client/client_send.c
#	src/server/receiver.c
#	src/shared/chunk.c
#	src/shared/file.c
#	src/shared/file_receive.c
#	src/shared/file_receive.h
#	src/shared/file_types.h
#	src/shared/protocol.h
#	tests/test_chunk.c
This commit is contained in:
2026-09-08 22:33:52 +02:00
23 changed files with 910 additions and 29 deletions
+20
View File
@@ -540,6 +540,11 @@ static const OptionEntry OPTION_TABLE[] = {
{"--numeric-ids", NULL, OPT_FLAG, offsetof(Config, numeric_ids)},
{"--atimes", "-U", OPT_FLAG, offsetof(Config, preserve_atimes)},
{"--crtimes", "-N", OPT_FLAG, offsetof(Config, preserve_crtimes)},
/* -D is handled separately (it implies both --devices and --specials). */
{"--devices", NULL, OPT_FLAG, offsetof(Config, preserve_devices)},
{"--specials", NULL, OPT_FLAG, offsetof(Config, preserve_specials)},
{"--copy-devices", NULL, OPT_FLAG, offsetof(Config, copy_devices)},
{"--write-devices", NULL, OPT_FLAG, offsetof(Config, write_devices)},
{"--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)},
@@ -834,6 +839,12 @@ int parse_args(Config* config, int argc, char* argv[], int* positional_args,
} else if (opt_is(argv[i], "-V", "--version")) {
printf("fastsync version %s\n", PROTOCOL_VERSION);
return 1;
} else if (opt_is(argv[i], "-D", NULL)) {
/* rsync -D == --devices --specials. -D is otherwise unassigned in
FastSync (verified: no collision), so it is free to imply both. */
config->preserve_devices = true;
config->preserve_specials = true;
log_info_message(LOG_INFO_MISC, "Enabled preservation of device and special files (-D)");
} else if (opt_is(argv[i], "-a", "--archive")) {
config->use_compression =
!config->compress_choice || strcmp(config->compress_choice, "zstd") == 0;
@@ -1203,6 +1214,15 @@ int parse_args(Config* config, int argc, char* argv[], int* positional_args,
config->files_from_set = set;
}
/* Device/special preservation recreates a node from its metadata mode (whose
S_IFMT bits carry the node kind), so --devices/--specials/-D imply metadata
transmission. --copy-devices/--write-devices treat the entry as data but a
mtime/mode-preserving transfer still benefits from metadata, so all four
imply it (FastSync's broad -M bundle; ownership stays opt-in). */
if (config->preserve_devices || config->preserve_specials || config->copy_devices ||
config->write_devices)
config->use_metadata = true;
/* The "unchanged" decision for --compare-dest/--copy-dest/--link-dest must
* be made on the receiver against the basis directories, which requires the
* per-file STATUS_CHECK handshake: basis-dir options therefore imply
+12 -1
View File
@@ -106,6 +106,9 @@ static bool prepare_scanner(const Config* config, int num_threads, PreparedScann
options->munge_links = config->munge_links;
options->checksum = config->checksum;
options->one_file_system = config->one_file_system;
options->preserve_devices = config->preserve_devices;
options->preserve_specials = config->preserve_specials;
options->copy_devices = config->copy_devices;
options->file_list = (const FileListSet*)config->files_from_set;
options->base_filters = out->base_filters;
options->per_dir_filters = config->per_dir_filter;
@@ -1264,13 +1267,21 @@ static int send_chunk_with_removal(Client* client, Chunk* chunk, Config* config,
change_emit_file_sent(config, f);
continue;
}
/* Symlink entry (-l / -k keep-as-symlink): only the target rides the wire. */
/* Symlink entry (-l / -k keep-as-symlink): only the target rides the wire. */
if (f->is_symlink) {
if (!send_symlink_entry(client, f, config))
return -1;
change_emit_file_sent(config, f);
continue;
}
/* --devices/--specials: a device/special node is recreated on the receiver,
not transferred as content. Send the dedicated STATUS_SPECIAL frame. */
if (f->is_special) {
if (!file_send_special(f, client->file_descriptor, config->use_metadata))
return -1;
change_emit_file_sent(config, f);
continue;
}
bool stream = f->data->data == NULL && f->data->size > 0;
bool use_sendfile =
(config->use_sendfile && !config->use_compression) || (stream && !config->use_compression);
+36
View File
@@ -10,6 +10,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <threads.h>
#include <unistd.h>
#include <limits.h>
@@ -199,6 +200,34 @@ static void scanner_assign_hardlink(DirectoryScanner* scanner, HardLinkTable* ta
}
}
/* Phase 4 special/devices: detect a device (char/block), FIFO or socket entry
and, when the matching --devices/--specials flag asks it be preserved,
convert the File into a node to recreate (is_special, empty payload) with its
device rdev captured from the source stat. When the entry is not preserved
(or --copy-devices instead copies its content as an ordinary regular file)
the File is left as a normal data file. Returns true when converted. */
static bool scanner_prepare_special(bool preserve_devices, bool preserve_specials, File* file,
const struct stat* stats) {
if (!file || !stats)
return false;
bool is_device = S_ISCHR(stats->st_mode) || S_ISBLK(stats->st_mode);
bool is_fifo = S_ISFIFO(stats->st_mode);
bool is_socket = S_ISSOCK(stats->st_mode);
if (!is_device && !is_fifo && !is_socket)
return false;
bool preserve = is_device ? preserve_devices : preserve_specials;
if (!preserve)
return false;
file->is_special = true;
file->data->size = 0;
file->data->data = NULL;
if (is_device) {
file->rdev_major = (int32_t)major(stats->st_rdev);
file->rdev_minor = (int32_t)minor(stats->st_rdev);
}
return true;
}
/* Append `rel` to the caller's exclusion sink, taking `mtx` when shared across
parallel worker threads. Returns false on allocation failure (list left
unchanged). */
@@ -420,6 +449,9 @@ DirectoryScanner* directory_scanner_create_with_options(const char* root_directo
scanner->munge_links = options->munge_links;
scanner->checksum = options->checksum;
scanner->one_file_system = options->one_file_system;
scanner->preserve_devices = options->preserve_devices;
scanner->preserve_specials = options->preserve_specials;
scanner->copy_devices = options->copy_devices;
scanner->failed = false;
scanner->root_path = str_dup(root_directory);
if (!scanner->root_path) {
@@ -990,6 +1022,9 @@ Chunk* directory_scanner_next(DirectoryScanner* scanner) {
file->send_path = rel_copy;
rel_copy = NULL;
}
/* --devices/--specials: a device/FIFO/socket entry marked for preservation
becomes a node to recreate (is_special, no data, rdev captured). */
scanner_prepare_special(scanner->preserve_devices, scanner->preserve_specials, file, &stats);
if (scanner->hardlinks && S_ISREG(stats.st_mode))
scanner_assign_hardlink(scanner, scanner->hardlinks, file, &stats);
if (scanner->use_metadata)
@@ -1316,6 +1351,7 @@ static void scan_root_entry(const ScannerOptions* options, const FilterNode* roo
file->send_path = rel;
rel = NULL;
}
scanner_prepare_special(options->preserve_devices, options->preserve_specials, file, &st);
if (options->hardlinks && S_ISREG(st.st_mode)) {
int gid;
bool is_first;
+10
View File
@@ -42,6 +42,12 @@ typedef struct {
bool munge_links;
bool checksum;
bool one_file_system;
/* Phase 4 special/devices: whether device nodes (--devices) and special files
* (--specials) are preserved via recreation, and whether --copy-devices
* copies a device's content as an ordinary regular file. */
bool preserve_devices;
bool preserve_specials;
bool copy_devices;
/* Phase 2 (files-from / filter layer). All pointers are shared read-only
* across scanner instances and worker threads; ownership stays with the
* caller (client_send). */
@@ -114,6 +120,10 @@ typedef struct {
bool one_file_system;
dev_t root_dev;
bool failed;
/* Phase 4 special/devices (see ScannerOptions). */
bool preserve_devices;
bool preserve_specials;
bool copy_devices;
/* Phase 2 (files-from / filter layer). */
char* root_path; /* transfer root (fs path) for rel computation */
char* current_rel; /* rel path of the open directory ("" == root) */
+9
View File
@@ -185,6 +185,15 @@ void print_usage(void) {
printf(" --munge-links Munge symlink targets on the wire (sender)\n");
printf(" -H, --hard-links Preserve hard-link relationships across the transfer\n");
printf(" -S, --sparse Handle sparse files efficiently\n");
printf(
" -D Preserve device and special files (implies --devices --specials)\n");
printf(
" --devices Recreate device nodes on the destination (privileged; skipped when\n");
printf(" the receiver lacks CAP_MKNOD)\n");
printf(" --specials Recreate special files (FIFOs) on the destination (sockets "
"skipped)\n");
printf(" --copy-devices Copy a source device's content as a regular file instead\n");
printf(" --write-devices Write received data into an existing destination device node\n");
printf(" --inplace Update files in-place (no temp+rename)\n");
printf(
" --preallocate Allocate destination file space up front (fail-fast on full disk)\n");