Files
FastSync/src/shared/xattr.h
T
TapTap 47de05d215 feat(p7-output-fs): sparse hole preservation (-S), partial retention (-P), fake-super replay; block-size verified; crtimes/stderr -> Impossible/Divergence (Wave B)
- write_all_sparse: skips all-zero runs >= 4096 bytes via lseek(SEEK_CUR) and
  ftruncates the final size, wired into the atomic temp+rename and --inplace
  paths with no wire change (full image already in memory).
- --partial retention: on a save failure after the temp held data, rename the
  already-written temp to the destination path (best-effort; falls through to
  unlink; never retains when --partial is off) so --append/--append-verify can
  resume; tested by forcing futimens EINVAL with an out-of-range nsec.
- --block-size aliases --delta-block; verified config->delta_block_size is
  honored by the delta engine end-to-end (unit + integration tests).
- fake_super_restore_fd: parses and re-applies user.fastsync.stat fd-relative
  (fchown best-effort/non-root skipped, fchmod, futimens); a save under
  --fake-super now both records and re-applies.
- -N/--crtimes and --stderr=client promoted to a new 'Impossible/Divergence'
  status bucket (Summary: 136/2/4/3/2 = 147).
- cppcheck/clang-format clean; unit 37/37; integration 407 passed.
2026-09-11 15:58:20 +02:00

97 lines
4.6 KiB
C

#ifndef XATTR_H
#define XATTR_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/*
* Portable extended-attribute (xattr) and POSIX-ACL preservation (Phase 4,
* protocol 2.13.0). --xattrs/-X and --acls/-A are implemented on top of the
* xattr machinery: the SENDER captures a bounded, namespace-whitelisted set of
* `name = value` pairs per file, transmits them in a per-file wire block, and
* the RECEIVER re-applies them fd-relative on the just-written file. Linux
* xattr syscalls are used; libacl is NOT required (ACLs travel as the
* system.posix_acl_access / system.posix_acl_default xattrs).
*
* Security model:
* * A client can never force a `security.*` / privileged xattr onto the
* destination: both capture (sender) and apply (receiver) are restricted to
* the unprivileged `user.*` namespace and the two POSIX ACL xattrs. The
* receiver independently re-validates every incoming name against this
* whitelist, so a malicious sender's `security.capability` payload is
* rejected, not applied.
* * Payloads are bounded (per-name length, per-value length, per-file count
* and total bytes) on BOTH ends to prevent OOM/memory abuse; an oversized
* or malformed frame is a clean protocol rejection, never an allocation
* blowup.
* * Application is confined to the exact destination file descriptor
* (fsetxattr on the just-written fd), never a caller-controlled path.
*/
/* Reserved key used by --fake-super to park the source's privileged ownership
* / mode / mtime on the destination file as an unprivileged user.* xattr, so a
* later privileged restore could re-apply them. Exact documented format:
* uid:gid:mode:mtime_sec:mtime_nsec (decimal, decimal, octal, dec, dec)
* e.g. "1000:1000:644:1765238400:0". */
#define FAKESUPER_XATTR "user.fastsync.stat"
/* --- bounds --- */
#define XATTR_NAME_MAX 255 /* xattr names are limited to 255 bytes */
#define XATTR_VALUE_MAX (1024 * 1024) /* per-value cap (1 MiB) */
#define XATTR_TOTAL_MAX (4 * 1024 * 1024) /* per-file total name+value bytes */
#define XATTR_MAX_COUNT 256
typedef struct {
char* name; /* owned, NUL-terminated */
unsigned char* value; /* owned, may hold embedded NULs */
size_t value_len;
} FileXattr;
typedef struct {
FileXattr* items;
int count;
} FileXattrList;
FileXattrList* xattr_list_new(void);
void xattr_list_free(FileXattrList* list);
/* Append one entry (deep copy). Returns false on allocation failure. */
bool xattr_list_append(FileXattrList* list, const char* name, const void* value, size_t value_len);
/* True when `name` is a well-formed xattr name AND belongs to a namespace this
* build is authorized to apply (user.* or the two POSIX ACL xattrs). Used for
* both capture and receiver-side validation. */
bool xattr_name_appliable(const char* name);
/* Sender: read the whitelisted xattrs of `path` into a new list. Returns NULL
* when the path has no appliable xattrs (or the filesystem has no xattr
* support); an empty-but-valid list is never returned distinct from NULL. */
FileXattrList* xattr_capture_path(const char* path);
/* Wire: bounded serialization. xattr_send returns false on write failure; an
* empty/NULL list transmits a zero-count block. xattr_receive returns NULL and
* sets *ok = 0 on any malformed / oversized / non-whitelisted entry. */
bool xattr_send(int fd, const FileXattrList* list);
FileXattrList* xattr_receive(int fd, int* ok);
/* Receiver: apply every entry fd-relative (fsetxattr) to the just-written file
* descriptor. A per-attribute failure (e.g. ACL set refused for non-root on a
* file the process does not own) is logged and skipped, never fatal. Returns
* true when apply was attempted (allowing callers to treat it as best-effort). */
bool xattr_apply_fd(int fd, const FileXattrList* list);
/* --fake-super: write the source uid/gid/mode/mtime record into the reserved
* FAKESUPER_XATTR on `fd`. Best-effort (logged, never fatal). Only meaningful
* when metadata was transmitted so the values exist. */
void fake_super_store_fd(int fd, uint32_t uid, uint32_t gid, uint32_t mode, int64_t mtime_sec,
int64_t mtime_nsec);
/* --fake-super replay: parse the FAKESUPER_XATTR record previously written on
* `fd` by fake_super_store_fd and re-apply uid/gid/mode/mtime fd-relative.
* Best-effort: absence of the xattr or a malformed record is a silent no-op
* that never fails the transfer, and fchown is applied only when permitted
* (a non-root EPERM is logged and skipped, matching FastSync's identity
* philosophy). Returns true when the xattr was present and parsed. */
bool fake_super_restore_fd(int fd);
#endif