feat(stats): receiver-observed created/literal counters (protocol 2.28.0)

- PROTOCOL_VERSION 2.27.0 -> 2.28.0; STATUS_STATS gains literal_bytes and
  the created reg/dir/link/special counters (golden wire updated)
- receiver reports which destination entries it newly created, including
  implicitly-created parent directories below the logical transfer root, so
  Number of created files carries rsync's per-type breakdown
- Literal data is now exact for a delta transfer (receiver counts the literal
  fragments it stored)
- differential-tested vs rsync 3.4.1 for fresh-create, update and delta
This commit is contained in:
2026-09-19 10:45:23 +02:00
parent 25062352f6
commit 6119e1e75c
21 changed files with 646 additions and 95 deletions
+15 -2
View File
@@ -82,7 +82,7 @@ typedef struct {
typedef enum SuperMode { SUPER_MODE_AUTO = 0, SUPER_MODE_ON = 1, SUPER_MODE_OFF = 2 } SuperMode;
/* ===========================================================================
* Config wire-field table (single source of truth for protocol 2.27.0).
* Config wire-field table (single source of truth for protocol 2.28.0).
*
* Every field below crosses the wire. The table is the ONLY place a
* serialized field is named: config.h expands CONFIG_WIRE_FIELDS() to declare
@@ -1006,7 +1006,20 @@ typedef struct Config {
* the sender can print rsync's `deleting PATH` lines for a real deletion. No
* change to the fixed STATUS_STATS record itself; only a new trailing config
* bool, which still requires the version bump for the strict lockstep. */
#define PROTOCOL_VERSION "2.27.0"
/* (8) --stats receiver-observed counters (protocol 2.28.0): the fixed
* STATUS_STATS record grows from three counters to eight. The receiver now
* reports the bytes it literally stored (`literal_data`) and the count of
* destination entries it newly CREATED, split by type
* (reg/dir/link/special), so the sender can print rsync's exact
* `Number of created files: N (reg: X, dir: Y, link: Z, special: W)` line and
* an exact `Literal data` total even for delta transfers. The config-frame
* LAYOUT is unchanged (no new config field), but the STATUS_STATS body grows,
* so a 2.27 peer that does not consume the five new fixed-width counters would
* desynchronize on the trailing would-delete path list; the strict
* same-version handshake (config_receive rejects a mismatched version before
* parsing anything else) keeps mixed deployments from ever reaching that
* state. */
#define PROTOCOL_VERSION "2.28.0"
#define DEFAULT_CHUNK_SIZE (10 * 1024 * 1024)
/* Upper bound on total basis-dir entries (rsync caps --link-dest at 20). */
#define MAX_BASIS_DIRS 64
+156 -21
View File
@@ -187,6 +187,7 @@ File* file_create(const char* path) {
file->xattrs = NULL;
file->dest_state = (OutputDestState){0};
file->matched_bytes = 0;
file->literal_bytes = 0;
return file;
}
@@ -614,6 +615,103 @@ static int open_dir_beneath_root(const char* resolved, const char* root) {
}
int file_open_secure_parent(const char* path, char** leaf_out, bool create_dirs) {
return file_open_secure_parent_counted(path, leaf_out, create_dirs, NULL, NULL);
}
/* The logical transfer root expressed in the same coordinate as the secure
* parent walk's `rel_buf` (relative to the authorized root, with a leading
* '/'), used as the floor at or below which a created directory is a real
* file-list entry. The on-disk transfer root is the receive root joined to the
* wire path; the mirror scaffolding above it (the absolute source path below
* the destination root) is not an rsync entry. Returns an allocated string or
* NULL (count every created component). */
static char* transfer_root_floor(const Config* config) {
if (!config || !config->send_directory || config->send_directory[0] == '\0')
return NULL;
const char* spec = config->send_directory;
const char* after = spec;
if (spec[0] == '.' && spec[1] == '/') {
after = spec + 2;
} else {
const char* cut = strstr(spec, "/./");
if (cut)
after = cut + 3;
}
while (*after == '/')
after++;
char* wire_root = str_dup(after);
if (!wire_root)
return NULL;
size_t wlen = strlen(wire_root);
while (wlen > 0 && wire_root[wlen - 1] == '/')
wire_root[--wlen] = '\0';
if (wlen == 0) {
free(wire_root);
return NULL;
}
char* disk_root = config->receive_root_directory
? path_cat(config->receive_root_directory, wire_root)
: str_dup(wire_root);
free(wire_root);
if (!disk_root)
return NULL;
const char* root_path = utils_get_authorized_root_path();
const char* floor = disk_root;
if (root_path && root_path[0] == '/') {
size_t rl = strlen(root_path);
while (rl > 0 && root_path[rl - 1] == '/')
rl--;
if (strncmp(disk_root, root_path, rl) == 0 && (disk_root[rl] == '/' || disk_root[rl] == '\0'))
floor = disk_root + rl;
}
while (*floor == '/')
floor++;
char* out = str_dup(floor);
free(disk_root);
if (!out)
return NULL;
if (out[0] == '\0') {
free(out);
return NULL;
}
return out;
}
/* A created parent component counts toward `Number of created files` only when
* its receive-root-relative path is at or below the logical transfer root
* (`count_floor`). The transfer root itself corresponds to rsync's `.` entry
* (created on a fresh destination, pre-existing otherwise); the mirror
* scaffolding above it is FastSync's absolute-path layout, not an rsync entry. */
static bool created_dir_counts(const char* count_floor, const char* rel_buf,
const char* component) {
if (!count_floor)
return true;
char candidate[PATH_MAX];
int n = snprintf(candidate, sizeof(candidate), "%s/%s", rel_buf, component);
if (n < 0 || (size_t)n >= sizeof(candidate))
return false;
const char* cand = candidate;
while (*cand == '/')
cand++;
size_t fl = strlen(count_floor);
if (strncmp(cand, count_floor, fl) != 0)
return false;
return cand[fl] == '\0' || cand[fl] == '/';
}
/* Public wrapper for the receiver's created-directory accounting: the logical
* transfer root expressed receive-root-relative, or NULL when the wire paths
* carry no mirror scaffolding above it (--relative and --files-from, whose
* paths are already relative to the transfer root). The caller frees a
* non-NULL result. */
char* file_transfer_root_floor(const Config* config) {
if (!config || config->relative || config->files_from_set != NULL)
return NULL;
return transfer_root_floor(config);
}
int file_open_secure_parent_counted(const char* path, char** leaf_out, bool create_dirs,
unsigned* dirs_created, const char* count_floor) {
char* copy = str_dup(path);
if (!copy)
return -1;
@@ -674,6 +772,14 @@ int file_open_secure_parent(const char* path, char** leaf_out, bool create_dirs)
if (next < 0 && create_dirs && errno == ENOENT) {
bool created = mkdirat(fd, component, (mode_t)(0777 & ~(mode_t)file_process_umask())) == 0;
if (created || errno == EEXIST) {
/* Protocol 2.28.0: only directories the logical file list would
create count toward `Number of created files`; the mirror
scaffolding above the transfer root (e.g. the absolute source path
under the destination root) is not an rsync entry. `count_floor`
is a receive-root-relative prefix that must be reached before a
created component is counted. */
if (created && dirs_created && created_dir_counts(count_floor, rel_buf, component))
(*dirs_created)++;
/* P7 Wave E: --copy-as owns EVERY entry, including the intermediate
directories this walk creates implicitly. Its target ids are a
global policy, so they are available here without per-entry source
@@ -1026,15 +1132,14 @@ static void restore_extra_fd(int fd, const FileMetadata* metadata, const FileXat
}
}
static bool file_to_disk_secure_impl(const char* path, const void* data,
unsigned long long data_size, bool inplace, bool sparse,
bool preallocate, const FileMetadata* metadata,
FileAttrPolicy policy, bool update, bool no_replace,
bool use_fsync, const char* temp_dir,
const FileXattrList* xattrs, bool fake_super,
bool keep_partial) {
static bool
file_to_disk_secure_impl(const char* path, const void* data, unsigned long long data_size,
bool inplace, bool sparse, bool preallocate, const FileMetadata* metadata,
FileAttrPolicy policy, bool update, bool no_replace, bool use_fsync,
const char* temp_dir, const FileXattrList* xattrs, bool fake_super,
bool keep_partial, unsigned* dirs_created, const char* count_floor) {
char* leaf = NULL;
int dirfd = file_open_secure_parent(path, &leaf, true);
int dirfd = file_open_secure_parent_counted(path, &leaf, true, dirs_created, count_floor);
if (dirfd < 0)
return false;
int fd = -1;
@@ -1325,7 +1430,7 @@ static bool file_to_disk_secure_impl(const char* path, const void* data,
"non-atomic copy into the destination directory");
return file_to_disk_secure_impl(path, data, data_size, inplace, sparse, preallocate, metadata,
policy, update, no_replace, use_fsync, NULL, xattrs, fake_super,
keep_partial);
keep_partial, dirs_created, count_floor);
}
return ok;
}
@@ -1334,7 +1439,8 @@ bool file_to_disk_secure(const char* path, const void* data, unsigned long long
bool inplace, bool sparse, bool preallocate, const FileMetadata* metadata,
FileAttrPolicy policy, const char* temp_dir) {
return file_to_disk_secure_impl(path, data, data_size, inplace, sparse, preallocate, metadata,
policy, false, false, false, temp_dir, NULL, false, false);
policy, false, false, false, temp_dir, NULL, false, false, NULL,
NULL);
}
bool file_to_disk_secure_update(const char* path, const void* data, unsigned long long data_size,
@@ -1342,7 +1448,8 @@ bool file_to_disk_secure_update(const char* path, const void* data, unsigned lon
const FileMetadata* metadata, FileAttrPolicy policy,
const char* temp_dir) {
return file_to_disk_secure_impl(path, data, data_size, inplace, sparse, preallocate, metadata,
policy, true, false, false, temp_dir, NULL, false, false);
policy, true, false, false, temp_dir, NULL, false, false, NULL,
NULL);
}
bool file_to_disk_secure_with_fsync(const char* path, const void* data,
@@ -1350,7 +1457,8 @@ bool file_to_disk_secure_with_fsync(const char* path, const void* data,
bool preallocate, const FileMetadata* metadata,
FileAttrPolicy policy, bool use_fsync, const char* temp_dir) {
return file_to_disk_secure_impl(path, data, data_size, inplace, sparse, preallocate, metadata,
policy, false, false, use_fsync, temp_dir, NULL, false, false);
policy, false, false, use_fsync, temp_dir, NULL, false, false,
NULL, NULL);
}
bool file_to_disk_secure_no_replace(const char* path, const void* data,
@@ -1358,7 +1466,8 @@ bool file_to_disk_secure_no_replace(const char* path, const void* data,
const FileMetadata* metadata, FileAttrPolicy policy,
const char* temp_dir) {
return file_to_disk_secure_impl(path, data, data_size, false, sparse, preallocate, metadata,
policy, false, true, false, temp_dir, NULL, false, false);
policy, false, true, false, temp_dir, NULL, false, false, NULL,
NULL);
}
/* Receiver write-path variant that also applies the per-file xattrs (-X/-A)
@@ -1371,9 +1480,21 @@ bool file_to_disk_secure_attrs(const char* path, const void* data, unsigned long
const FileMetadata* metadata, FileAttrPolicy policy, bool update,
bool no_replace, bool use_fsync, const FileXattrList* xattrs,
bool fake_super, bool keep_partial, const char* temp_dir) {
return file_to_disk_secure_attrs_counted(path, data, data_size, inplace, sparse, preallocate,
metadata, policy, update, no_replace, use_fsync, xattrs,
fake_super, keep_partial, temp_dir, NULL, NULL);
}
bool file_to_disk_secure_attrs_counted(const char* path, const void* data,
unsigned long long data_size, bool inplace, bool sparse,
bool preallocate, const FileMetadata* metadata,
FileAttrPolicy policy, bool update, bool no_replace,
bool use_fsync, const FileXattrList* xattrs, bool fake_super,
bool keep_partial, const char* temp_dir,
unsigned* dirs_created, const char* count_floor) {
return file_to_disk_secure_impl(path, data, data_size, inplace, sparse, preallocate, metadata,
policy, update, no_replace, use_fsync, temp_dir, xattrs,
fake_super, keep_partial);
fake_super, keep_partial, dirs_created, count_floor);
}
/* Atomic --link-dest install. The destination is replaced (via a temporary
@@ -1396,11 +1517,12 @@ static bool file_to_disk_secure_link_impl(const char* path, const char* basis_pa
bool preallocate, const FileMetadata* metadata,
FileAttrPolicy policy, bool use_fsync,
const FileXattrList* xattrs, bool fake_super,
const char* temp_dir) {
const char* temp_dir, unsigned* dirs_created,
const char* count_floor) {
if (!path || !basis_path)
return false;
char* leaf = NULL;
int dirfd = file_open_secure_parent(path, &leaf, true);
int dirfd = file_open_secure_parent_counted(path, &leaf, true, dirs_created, count_floor);
if (dirfd < 0)
return false;
@@ -1483,9 +1605,9 @@ static bool file_to_disk_secure_link_impl(const char* path, const char* basis_pa
free(leaf);
/* The basis file could not be linked in (missing, cross-device, refused
by the filesystem). Write a byte-identical local copy instead. */
return file_to_disk_secure_attrs(path, data, data_size, false, false, preallocate, metadata,
policy, false, false, use_fsync, xattrs, fake_super, false,
temp_dir);
return file_to_disk_secure_attrs_counted(
path, data, data_size, false, false, preallocate, metadata, policy, false, false, use_fsync,
xattrs, fake_super, false, temp_dir, dirs_created, count_floor);
}
if (scratch_dirfd >= 0)
@@ -1500,7 +1622,7 @@ bool file_to_disk_secure_link(const char* path, const char* basis_path, const vo
const FileMetadata* metadata, FileAttrPolicy policy, bool use_fsync,
const char* temp_dir) {
return file_to_disk_secure_link_impl(path, basis_path, data, data_size, preallocate, metadata,
policy, use_fsync, NULL, false, temp_dir);
policy, use_fsync, NULL, false, temp_dir, NULL, NULL);
}
bool file_to_disk_secure_link_attrs(const char* path, const char* basis_path, const void* data,
@@ -1508,8 +1630,21 @@ bool file_to_disk_secure_link_attrs(const char* path, const char* basis_path, co
const FileMetadata* metadata, FileAttrPolicy policy,
bool use_fsync, const FileXattrList* xattrs, bool fake_super,
const char* temp_dir) {
return file_to_disk_secure_link_attrs_counted(path, basis_path, data, data_size, preallocate,
metadata, policy, use_fsync, xattrs, fake_super,
temp_dir, NULL, NULL);
}
bool file_to_disk_secure_link_attrs_counted(const char* path, const char* basis_path,
const void* data, unsigned long long data_size,
bool preallocate, const FileMetadata* metadata,
FileAttrPolicy policy, bool use_fsync,
const FileXattrList* xattrs, bool fake_super,
const char* temp_dir, unsigned* dirs_created,
const char* count_floor) {
return file_to_disk_secure_link_impl(path, basis_path, data, data_size, preallocate, metadata,
policy, use_fsync, xattrs, fake_super, temp_dir);
policy, use_fsync, xattrs, fake_super, temp_dir,
dirs_created, count_floor);
}
bool file_write_to_disk(const char* path, const void* data, unsigned long long data_size,
+27
View File
@@ -87,6 +87,11 @@ bool file_path_exists_secure(const char* path);
bool file_stat_secure(const char* path, struct stat* st);
bool file_destination_is_newer_secure(const char* path, const FileMetadata* metadata);
int file_open_secure_parent(const char* path, char** leaf_out, bool create_dirs);
/* Protocol 2.28.0 variant: also increments *dirs_created for every missing
* parent directory this walk creates that lies strictly below `count_floor`
* (a receive-root-relative path, or NULL to count all of them). */
int file_open_secure_parent_counted(const char* path, char** leaf_out, bool create_dirs,
unsigned* dirs_created, const char* count_floor);
bool file_ensure_directory_secure(const char* path);
bool file_directory_exists_secure(const char* path);
bool file_rename_secure(const char* old_path, const char* new_path);
@@ -158,5 +163,27 @@ bool file_to_disk_secure_link_attrs(const char* path, const char* basis_path, co
const FileMetadata* metadata, FileAttrPolicy policy,
bool use_fsync, const FileXattrList* xattrs, bool fake_super,
const char* temp_dir);
/* Protocol 2.28.0 receiver-stat variants: like the two above but additionally
* report through `dirs_created` (when non-NULL) how many parent directories the
* confined secure walk had to create that lie strictly below `count_floor` (a
* receive-root-relative prefix, or NULL for all). Used to reproduce rsync's
* `Number of created files` directory count on a fresh destination. */
bool file_to_disk_secure_attrs_counted(const char* path, const void* data,
unsigned long long data_size, bool inplace, bool sparse,
bool preallocate, const FileMetadata* metadata,
FileAttrPolicy policy, bool update, bool no_replace,
bool use_fsync, const FileXattrList* xattrs, bool fake_super,
bool keep_partial, const char* temp_dir,
unsigned* dirs_created, const char* count_floor);
bool file_to_disk_secure_link_attrs_counted(const char* path, const char* basis_path,
const void* data, unsigned long long data_size,
bool preallocate, const FileMetadata* metadata,
FileAttrPolicy policy, bool use_fsync,
const FileXattrList* xattrs, bool fake_super,
const char* temp_dir, unsigned* dirs_created,
const char* count_floor);
/* The logical transfer root expressed receive-root-relative, or NULL when the
* wire paths carry no mirror scaffolding above it. Caller frees non-NULL. */
char* file_transfer_root_floor(const Config* config);
#endif
+77 -12
View File
@@ -37,7 +37,12 @@
#define MANIFEST_ENTRY_OVERHEAD (sizeof(char*) + 16)
bool file_save_to_disk(const char* root_directory, const File* file, const Config* config) {
return file_save_to_disk_full(root_directory, file, config) != FILE_SAVE_ERROR;
return file_save_to_disk_full_ex(root_directory, file, config, NULL, NULL) != FILE_SAVE_ERROR;
}
FileSaveResult file_save_to_disk_full(const char* root_directory, const File* file,
const Config* config) {
return file_save_to_disk_full_ex(root_directory, file, config, NULL, NULL);
}
/* --delay-updates receiver path: write the file into a private staging tree
@@ -208,13 +213,14 @@ static FileSaveResult hardlink_sibling_absent_first(const char* destination_path
--existing/--ignore-existing/--update policies are decided against the final
destination like every normal write. */
static FileSaveResult file_save_hardlink_sibling(const char* root_directory, const File* file,
const Config* config) {
const Config* config, bool* created) {
Config* cfg = (Config*)config;
if (!root_directory || !file || !file->path || !file->hardlink_target)
return FILE_SAVE_ERROR;
char* destination_path = path_cat(root_directory, file->path);
if (!destination_path)
return FILE_SAVE_ERROR;
bool existed = file_path_exists_secure(destination_path);
if (cfg->existing && !file_path_exists_secure(destination_path)) {
free(destination_path);
@@ -278,6 +284,8 @@ static FileSaveResult file_save_hardlink_sibling(const char* root_directory, con
free(staged_first);
free(staged_sibling);
free(destination_path);
if (ok && created && !existed)
*created = true;
return ok ? FILE_SAVE_WRITTEN : FILE_SAVE_ERROR;
}
@@ -324,6 +332,8 @@ static FileSaveResult file_save_hardlink_sibling(const char* root_directory, con
free(content);
free(first_disk);
free(destination_path);
if (ok && created && !existed)
*created = true;
return ok ? FILE_SAVE_WRITTEN : FILE_SAVE_ERROR;
}
@@ -360,7 +370,7 @@ bool file_special_rdev_valid(int32_t major, int32_t minor, mode_t mode) {
* non-device entry must carry an empty rdev.
*/
static FileSaveResult file_save_special_to_disk(const char* root_directory, const File* file,
const Config* config) {
const Config* config, bool* created) {
/* The empty-path and structural checks stay unconditional; the redundant
".." list-path re-check is skipped under --trust-sender exactly like the
receive layer (confinement is deferred to the secure parent walk below,
@@ -412,6 +422,7 @@ static FileSaveResult file_save_special_to_disk(const char* root_directory, cons
char* destination = path_cat(root_directory, file->path);
if (!destination)
return FILE_SAVE_ERROR;
bool existed = file_path_exists_secure(destination);
char* leaf = NULL;
int parent_fd = file_open_secure_parent(destination, &leaf, true);
if (parent_fd < 0) {
@@ -532,6 +543,8 @@ static FileSaveResult file_save_special_to_disk(const char* root_directory, cons
free(destination);
/* A failed required --copy-as ownership marks the node as failed; every other
* identity policy stays best-effort. */
if (owner_ok && created && !existed)
*created = true;
return owner_ok ? FILE_SAVE_WRITTEN : FILE_SAVE_ERROR;
}
@@ -610,8 +623,13 @@ static FileSaveResult file_save_write_device(const char* root_directory, const F
return ok ? FILE_SAVE_WRITTEN : FILE_SAVE_SKIPPED;
}
FileSaveResult file_save_to_disk_full(const char* root_directory, const File* file,
const Config* config) {
FileSaveResult file_save_to_disk_full_ex(const char* root_directory, const File* file,
const Config* config, bool* created,
unsigned* created_dirs) {
if (created)
*created = false;
if (created_dirs)
*created_dirs = 0;
/* Central no-mutation guard: a server-contacting --dry-run (or a local batch
apply that somehow carries dry_run) must never touch the destination, no
matter which caller reached this primitive. The per-caller guards remain,
@@ -658,7 +676,7 @@ FileSaveResult file_save_to_disk_full(const char* root_directory, const File* fi
/* Device/special node (--devices/--specials): recreate the node instead of
writing content (privilege-gated, confined, rdev-validated). */
if (file->is_special)
return file_save_special_to_disk(root_directory, file, config);
return file_save_special_to_disk(root_directory, file, config, created);
/* --write-devices: write straight into an existing device node. Writing
into a device is a super-user activity, so --no-super must suppress it just
like device-node creation; the default AUTO/--super attempt it (the wide
@@ -689,6 +707,7 @@ FileSaveResult file_save_to_disk_full(const char* root_directory, const File* fi
char* dir_path = path_cat(root_directory, file->path);
if (!dir_path)
return FILE_SAVE_ERROR;
bool dir_existed = file_path_exists_secure(dir_path);
bool ok = file_ensure_directory_secure(dir_path);
/* P7 Wave E: apply the negotiated ownership to the directory ITSELF (not
just the files inside it). --copy-as and every explicit identity policy
@@ -712,6 +731,8 @@ FileSaveResult file_save_to_disk_full(const char* root_directory, const File* fi
free(leaf);
}
free(dir_path);
if (ok && created && !dir_existed)
*created = true;
return ok ? FILE_SAVE_WRITTEN : FILE_SAVE_ERROR;
}
@@ -728,6 +749,7 @@ FileSaveResult file_save_to_disk_full(const char* root_directory, const File* fi
char* link_path = path_cat(root_directory, file->path);
if (!link_path)
return FILE_SAVE_ERROR;
bool link_existed = file_path_exists_secure(link_path);
/* The link value is stored verbatim (rsync -l parity: absolute and
".."-bearing targets are preserved; the scanner's --safe-links /
--copy-unsafe-links decide which links are sent at all). --munge-links
@@ -768,6 +790,8 @@ FileSaveResult file_save_to_disk_full(const char* root_directory, const File* fi
ok = file_restore_symlink_metadata(link_path, file->metadata, link_policy,
config->omit_link_times);
}
if (ok && created && !link_existed)
*created = true;
free(link_path);
return ok ? FILE_SAVE_WRITTEN : FILE_SAVE_ERROR;
}
@@ -777,7 +801,7 @@ FileSaveResult file_save_to_disk_full(const char* root_directory, const File* fi
byte-identical copy of) the group's first member. Handled entirely here,
before the normal data-write paths (which would create an empty file). */
if (file->link_group != 0 && !file->link_first && file->hardlink_target != NULL) {
return file_save_hardlink_sibling(root_directory, file, config);
return file_save_hardlink_sibling(root_directory, file, config, created);
}
/* These options arrive from the client. --backup-dir, --partial-dir and
@@ -808,12 +832,18 @@ FileSaveResult file_save_to_disk_full(const char* root_directory, const File* fi
free(disk_path);
return FILE_SAVE_ERROR;
}
/* Snapshot the final destination's existence BEFORE any backup/force/partial
step can move or remove it, so the receiver can report rsync's
`Number of created files` (protocol 2.28.0). */
bool dest_existed = file_path_exists_secure(destination_path);
/* --delay-updates diverts the whole write into the staging tree; the rest of
this function is the immediate-install path. */
if (config && config->delay_updates) {
FileSaveResult result =
file_stage_delayed_update(root_directory, destination_path, file, (Config*)config);
if (result == FILE_SAVE_WRITTEN && created && !dest_existed)
*created = true;
free(confined_backup);
free(confined_partial);
free(destination_path);
@@ -939,19 +969,23 @@ FileSaveResult file_save_to_disk_full(const char* root_directory, const File* fi
existing/ignore-existing/update/backup preamble above has already made the
policy decision. */
bool ok;
char* count_floor = file_transfer_root_floor(config);
if (config && file->basis_link) {
ok = file_to_disk_secure_link_attrs(
ok = file_to_disk_secure_link_attrs_counted(
disk_path, file->basis_link, file->data->data, file->data->size, config->preallocate,
metadata, policy, config->use_fsync, file->xattrs, config->fake_super, confined_temp);
metadata, policy, config->use_fsync, file->xattrs, config->fake_super, confined_temp,
created_dirs, count_floor);
} else {
/* The plain no-replace / update / with-fsync engines, plus per-file xattr
(-X/-A) and --fake-super application on the written fd. */
ok = file_to_disk_secure_attrs(
ok = file_to_disk_secure_attrs_counted(
disk_path, file->data->data, file->data->size, inplace, sparse,
config && config->preallocate, metadata, policy, config && config->update,
config && config->ignore_existing, config && config->use_fsync, file->xattrs,
config ? config->fake_super : false, config ? config->partial : false, confined_temp);
config ? config->fake_super : false, config ? config->partial : false, confined_temp,
created_dirs, count_floor);
}
free(count_floor);
free(confined_temp);
confined_temp = NULL;
if (!ok)
@@ -972,6 +1006,8 @@ FileSaveResult file_save_to_disk_full(const char* root_directory, const File* fi
free(confined_partial);
free(destination_path);
free(disk_path);
if (created && !dest_existed)
*created = true;
return FILE_SAVE_WRITTEN;
fail:
@@ -984,6 +1020,30 @@ fail:
return FILE_SAVE_ERROR;
}
void receiver_stats_note_saved(ReceiverStats* stats, const File* file, bool created,
unsigned created_dirs) {
if (!stats || !file)
return;
bool is_sibling = file->link_group != 0 && !file->link_first;
if (!file->is_dir && !file->is_symlink && !file->is_special && !is_sibling) {
unsigned long long literal = file->literal_bytes;
if (literal == 0 && file->matched_bytes == 0)
literal = file->data ? file->data->size : 0;
stats->literal_bytes += literal;
}
stats->created_dir += created_dirs;
if (!created)
return;
if (file->is_dir)
stats->created_dir++;
else if (file->is_symlink)
stats->created_link++;
else if (file->is_special)
stats->created_special++;
else
stats->created_reg++;
}
/* Receive a file's xattr block (when the config enables xattr transport) and
* attach it to `file`. Returns false on a malformed/oversized frame. */
static bool receive_file_xattrs(File* file, int fd, const Config* config) {
@@ -1094,11 +1154,15 @@ static File* receive_delta_file(int fd, const Config* config, const char* check_
return NULL;
}
/* Wire-stats tally: bytes taken straight from the basis file (matched
delta blocks). Computed before the delta is destroyed. */
delta blocks) and bytes shipped literally (protocol 2.28.0). Computed
before the delta is destroyed. */
unsigned long long matched = 0;
unsigned long long literal = 0;
for (uint32_t k = 0; k < delta->instruction_count; k++) {
if (delta->instructions[k].type == DELTA_INSTR_BLOCK_MATCH)
matched += delta->instructions[k].match.length;
else if (delta->instructions[k].type == DELTA_INSTR_LITERAL)
literal += delta->instructions[k].literal.length;
}
void* new_data = delta_apply(old_data, old_size, delta, config->delta_block_size);
delta_destroy(delta);
@@ -1119,6 +1183,7 @@ static File* receive_delta_file(int fd, const Config* config, const char* check_
return NULL;
}
file->matched_bytes = matched;
file->literal_bytes = literal;
if (config->use_metadata) {
int meta_ok = 1;
+16
View File
@@ -179,6 +179,22 @@ typedef enum { FILE_SAVE_ERROR = 0, FILE_SAVE_WRITTEN = 1, FILE_SAVE_SKIPPED = 2
FileSaveResult file_save_to_disk_full(const char* root_directory, const File* file,
const Config* config);
/* Protocol 2.28.0 variant: also reports through `created` (when non-NULL)
* whether the destination entry did not exist before this save, and through
* `created_dirs` how many parent directories the confined walk created, so the
* receiver can build rsync's `Number of created files` breakdown. The plain
* file_save_to_disk_full() is this with both out-params NULL. */
FileSaveResult file_save_to_disk_full_ex(const char* root_directory, const File* file,
const Config* config, bool* created,
unsigned* created_dirs);
bool file_save_to_disk(const char* root_directory, const File* file, const Config* config);
/* Protocol 2.28.0 receiver counter accumulator: fold one successfully saved
* entry into `stats`, adding its receiver-observed literal bytes and, when
* `created`, the matching created-by-type counter (regular file / symlink /
* special) plus `created_dirs` implicitly-created parent directories.
* Non-first hardlink siblings contribute no literal bytes. */
void receiver_stats_note_saved(ReceiverStats* stats, const File* file, bool created,
unsigned created_dirs);
#endif
+5
View File
@@ -97,6 +97,11 @@ typedef struct {
* basis file (matched delta blocks) for this entry. 0 when the file was sent
* whole. Accumulated into ReceiverStats.matched_data by the receiver sink. */
unsigned long long matched_bytes;
/* Receiver-only (protocol 2.28.0) wire-stats tally: the literal delta fragment
* bytes this entry carried (DELTA_INSTR_LITERAL). 0 when the file was sent
* whole; the sink then falls back to the whole payload size. Accumulated
* into ReceiverStats.literal_bytes. */
unsigned long long literal_bytes;
} File;
/* The path that should be sent on the wire and used for the receiver-side
+15 -13
View File
@@ -105,25 +105,27 @@ bool format_dest_state_receive(int fd, OutputDestState* state) {
bool format_stats_send(int fd, const ReceiverStats* stats) {
if (!stats)
return false;
unsigned long long matched = stats->matched_data;
unsigned long long deleted = stats->deleted_files;
unsigned long long would = stats->would_delete_count;
return send_n_data(fd, &matched, sizeof(matched)) && send_n_data(fd, &deleted, sizeof(deleted)) &&
send_n_data(fd, &would, sizeof(would));
unsigned long long fields[8] = {
stats->matched_data, stats->deleted_files, stats->would_delete_count, stats->literal_bytes,
stats->created_reg, stats->created_dir, stats->created_link, stats->created_special,
};
return send_n_data(fd, fields, sizeof(fields));
}
bool format_stats_receive(int fd, ReceiverStats* stats) {
if (!stats)
return false;
unsigned long long matched = 0;
unsigned long long deleted = 0;
unsigned long long would = 0;
if (!receive_n_data(fd, &matched, sizeof(matched)) ||
!receive_n_data(fd, &deleted, sizeof(deleted)) || !receive_n_data(fd, &would, sizeof(would)))
unsigned long long fields[8] = {0};
if (!receive_n_data(fd, fields, sizeof(fields)))
return false;
memset(stats, 0, sizeof(*stats));
stats->matched_data = matched;
stats->deleted_files = deleted;
stats->would_delete_count = would;
stats->matched_data = fields[0];
stats->deleted_files = fields[1];
stats->would_delete_count = fields[2];
stats->literal_bytes = fields[3];
stats->created_reg = fields[4];
stats->created_dir = fields[5];
stats->created_link = fields[6];
stats->created_special = fields[7];
return true;
}
+16 -4
View File
@@ -57,14 +57,26 @@ bool format_dest_state_send(int fd, const OutputDestState* state);
bool format_dest_state_receive(int fd, OutputDestState* state);
/* End-of-transfer receiver counters reported through STATUS_STATS (protocol
* 2.25.0) when the wire config carries report_stats. `would_delete_count` is
* the number of destination-relative paths the receiver would have deleted in a
* -n/--dry-run --delete run; that many wire strings immediately follow the
* fixed record (sent/read by the caller). */
* 2.25.0, extended in 2.28.0) when the wire config carries report_stats.
* `would_delete_count` is the number of destination-relative paths the receiver
* would have deleted in a -n/--dry-run --delete run; that many wire strings
* immediately follow the fixed record (sent/read by the caller).
*
* Protocol 2.28.0 adds the receiver-observed counters the sender cannot see:
* `literal_bytes` is the file data the receiver actually stored literally
* (whole files plus the literal fragments of a delta) and the four `created_*`
* counters split the destination entries the receiver newly created by type,
* reproducing rsync's `Number of created files` breakdown and an exact
* `Literal data` for a delta run. */
typedef struct {
unsigned long long matched_data;
unsigned long long deleted_files;
unsigned long long would_delete_count;
unsigned long long literal_bytes;
unsigned long long created_reg;
unsigned long long created_dir;
unsigned long long created_link;
unsigned long long created_special;
} ReceiverStats;
/* Fixed-width STATUS_STATS counter record. The status frame and the optional