The receiver's per-file incremental check now consults the ordered basis-dir
list whenever the destination is not already up to date. An exact basis match
requires equal size, equal mtime (unless --size-only; --ignore-times disables
basis matching like rsync), and an equal content xxHash64 -- the sender sends
its xxHash for every file whenever basis dirs are configured (not only under
--checksum), so a hard link or local copy is only ever made from byte-identical
content.
On a match:
- compare-dest: reply STATUS_OK and skip data only when the destination does
not already hold the file (sparse, rsync parity). A destination that holds
a DIFFERENT version falls back to a normal transfer instead of rsync's
delete, keeping the mirror complete.
- copy-dest: reply STATUS_OK and hand a synthetic File (bytes read from the
basis file, basis metadata) to the normal store sink, so the file is
installed as a real local copy through the existing atomic temp+rename
engine and honors --existing/--ignore-existing/--update/--backup/
--delay-updates/--partial-dir unchanged.
- link-dest: same, but File.basis_link records the basis path and the store
engine calls the new file_to_disk_secure_link(): an atomic temp hard link +
rename. Cross-filesystem/refused links fall back to a byte-identical local
copy (never a corrupt or partial file); the copy fallback applies metadata,
while a successful link keeps the basis inode's own attributes so the basis
file is never mutated.
Basis-materialized files carry File.skip so they are not acknowledged to a
--remove-source-files sender (the sender already saw STATUS_OK and keeps the
source). The no-match path is byte-for-byte identical to the existing delta /
full-data transfer.
75 lines
4.2 KiB
C
75 lines
4.2 KiB
C
#ifndef FILE_H
|
|
#define FILE_H
|
|
|
|
#include "file_send.h"
|
|
#include "file_receive.h"
|
|
#include "file_types.h"
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <sys/stat.h>
|
|
|
|
/* File/FileMetadata lifecycle, local disk helpers, and secure filesystem
|
|
primitives shared by the send/receive pipelines. */
|
|
|
|
File* file_create(const char* path);
|
|
void file_destroy(void* item);
|
|
bool file_load_data(File* file);
|
|
bool file_checksum(File* file, uint64_t* checksum);
|
|
size_t file_content_to_buffer(File* file);
|
|
FileMetadata* file_metadata_create(const struct stat* stats);
|
|
void file_metadata_destroy(void* metadata);
|
|
bool file_write_to_disk(const char* path, const void* data, unsigned long long data_size,
|
|
bool inplace, bool sparse);
|
|
|
|
/* A configured fd without a canonical identity deliberately rejects paths. */
|
|
bool file_set_authorized_root(int fd, const char* canonical_path);
|
|
|
|
/* Secure path/filesystem primitives (symlink-safe, O_NOFOLLOW, root-confined). */
|
|
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);
|
|
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);
|
|
/* Open a private 0700 directory (creating it on demand) that must live below
|
|
the authorized root. Used for the --temp-dir scratch directory and the
|
|
--delay-updates staging directory. */
|
|
int file_open_private_dir(const char* dir_path);
|
|
|
|
/* The file_to_disk_secure* variants write a temporary copy in the destination
|
|
directory and atomically rename it over `path`. temp_dir is an absolute,
|
|
root-confined scratch directory (already validated by the caller): when it
|
|
is non-NULL the temporary copy is instead created there (with a name unique
|
|
across the whole scratch directory) and atomically renamed into the
|
|
destination directory once fully written and fsynced. A rename across
|
|
filesystems (EXDEV) fails the write with an error; the file is never
|
|
silently copied into place. Pass NULL for the historical same-directory
|
|
behavior. --inplace writes never use temp_dir. */
|
|
bool file_to_disk_secure(const char* path, const void* data, unsigned long long data_size,
|
|
bool inplace, bool sparse, const FileMetadata* metadata,
|
|
bool preserve_executability, const char* temp_dir);
|
|
bool file_to_disk_secure_with_fsync(const char* path, const void* data,
|
|
unsigned long long data_size, bool inplace, bool sparse,
|
|
const FileMetadata* metadata, bool preserve_executability,
|
|
bool use_fsync, const char* temp_dir);
|
|
/* With update enabled, an existing newer destination is left untouched. The
|
|
check is descriptor-based for inplace writes; atomic replacement still has
|
|
an unavoidable final rename race without filesystem locking. */
|
|
bool file_to_disk_secure_update(const char* path, const void* data, unsigned long long data_size,
|
|
bool inplace, bool sparse, const FileMetadata* metadata,
|
|
bool preserve_executability, const char* temp_dir);
|
|
bool file_to_disk_secure_no_replace(const char* path, const void* data,
|
|
unsigned long long data_size, bool sparse,
|
|
const FileMetadata* metadata, bool preserve_executability,
|
|
const char* temp_dir);
|
|
/* Atomic --link-dest install: replace `path` with a hard link to `basis_path`
|
|
(via a temp name + rename); fall back to a byte-identical local copy from
|
|
`data` when the link is impossible (EXDEV/EPERM/unsupported filesystem).
|
|
`metadata` is applied only on the copy fallback. */
|
|
bool file_to_disk_secure_link(const char* path, const char* basis_path, const void* data,
|
|
unsigned long long data_size, const FileMetadata* metadata,
|
|
bool preserve_executability, bool use_fsync, const char* temp_dir);
|
|
|
|
#endif
|