Merge feat/p4-metadata-capture: atimes/crtimes/open-noatime/omit-dir-times/omit-link-times
# Conflicts: # src/shared/config.c # tests/integration/test_features.py
This commit is contained in:
+27
-2
@@ -148,6 +148,11 @@ static void config_set_defaults(Config* config) {
|
||||
config->groupmap = NULL;
|
||||
config->groupmap_count = 0;
|
||||
config->delay_context = NULL;
|
||||
config->preserve_atimes = false;
|
||||
config->preserve_crtimes = false;
|
||||
config->omit_dir_times = false;
|
||||
config->omit_link_times = false;
|
||||
config->open_noatime = false;
|
||||
}
|
||||
|
||||
static bool valid_wire_bool(int value) {
|
||||
@@ -197,6 +202,8 @@ static bool validate_received_config(const Config* config) {
|
||||
!((config->append || config->append_verify) && config->use_chunk_serialization) &&
|
||||
!(config->preserve_hard_links && config->use_chunk_serialization) &&
|
||||
!(config->preserve_hard_links && (config->append || config->append_verify)) &&
|
||||
valid_wire_bool(config->preserve_atimes) && valid_wire_bool(config->preserve_crtimes) &&
|
||||
valid_wire_bool(config->omit_dir_times) && valid_wire_bool(config->omit_link_times) &&
|
||||
(!config->use_compression ||
|
||||
(config->compression_level >= 1 && config->compression_level <= 22)) &&
|
||||
config->chunk_size > 0 && config->chunk_size <= MAX_CHUNK_SIZE &&
|
||||
@@ -748,6 +755,22 @@ static bool receive_identity_options(int fd, Config* c) {
|
||||
receive_identity_map(fd, &c->groupmap_count, &c->groupmap);
|
||||
}
|
||||
|
||||
/* -U/--atimes, -N/--crtimes (affect both sender capture and receiver apply)
|
||||
* and -O/--omit-dir-times, -J/--omit-link-times (receiver-side prefs) all cross
|
||||
* the wire so the receiver knows what to apply / suppress. --open-noatime is
|
||||
* client-only (it only governs the sender's source reads) and is never
|
||||
* serialized. Trailing fields; protocol 2.12.0. */
|
||||
static bool send_metadata_times_options(int fd, const Config* c) {
|
||||
return send_int(fd, c->preserve_atimes) && send_int(fd, c->preserve_crtimes) &&
|
||||
send_int(fd, c->omit_dir_times) && send_int(fd, c->omit_link_times);
|
||||
}
|
||||
|
||||
static bool receive_metadata_times_options(int fd, Config* c) {
|
||||
return receive_wire_bool(fd, &c->preserve_atimes) &&
|
||||
receive_wire_bool(fd, &c->preserve_crtimes) && receive_wire_bool(fd, &c->omit_dir_times) &&
|
||||
receive_wire_bool(fd, &c->omit_link_times);
|
||||
}
|
||||
|
||||
bool config_send(int file_descriptor, const Config* config) {
|
||||
protocol_session_set_max_alloc(NULL, config->max_alloc);
|
||||
if (!send_core_fields(file_descriptor, config) || !send_delta_fields(file_descriptor, config) ||
|
||||
@@ -756,7 +779,8 @@ bool config_send(int file_descriptor, const Config* config) {
|
||||
!send_resume_options(file_descriptor, config) ||
|
||||
!send_basis_options(file_descriptor, config) || !send_fuzzy_option(file_descriptor, config) ||
|
||||
!send_checksum_options(file_descriptor, config) ||
|
||||
!send_identity_options(file_descriptor, config))
|
||||
!send_identity_options(file_descriptor, config) ||
|
||||
!send_metadata_times_options(file_descriptor, config))
|
||||
return false;
|
||||
Status status;
|
||||
if (!receive_status(file_descriptor, &status))
|
||||
@@ -792,7 +816,8 @@ Config* config_receive(int file_descriptor) {
|
||||
!receive_basis_options(file_descriptor, config) ||
|
||||
!receive_fuzzy_option(file_descriptor, config) ||
|
||||
!receive_checksum_options(file_descriptor, config) ||
|
||||
!receive_identity_options(file_descriptor, config))
|
||||
!receive_identity_options(file_descriptor, config) ||
|
||||
!receive_metadata_times_options(file_descriptor, config))
|
||||
goto error;
|
||||
if (config->compress_choice[0] != '\0' && strcmp(config->compress_choice, "zstd") != 0 &&
|
||||
strcmp(config->compress_choice, "none") != 0) {
|
||||
|
||||
@@ -291,6 +291,25 @@ typedef struct Config {
|
||||
// Receiver-side runtime staging registry for --delay-updates. Never sent
|
||||
// over the wire and never set on the sender side.
|
||||
DelayUpdatesContext* delay_context;
|
||||
|
||||
// Phase 4: metadata time preservation. -U/--atimes and -N/--crtimes capture
|
||||
// and transmit the source access / birth time (both sender and receiver
|
||||
// effect, so they CROSS the wire). --omit-dir-times/-O and
|
||||
// --omit-link-times/-J are receiver-side prefs (CROSS the wire). Their
|
||||
// exact capture/transmit/apply semantics are documented in RSYNC_COMPAT.md.
|
||||
/* -U/--atimes: preserve source access times on the destination. */
|
||||
bool preserve_atimes;
|
||||
/* -N/--crtimes: capture+transmit source birth time; see RSYNC_COMPAT for the
|
||||
* receiver not-applied divergence. */
|
||||
bool preserve_crtimes;
|
||||
/* -O/--omit-dir-times: do not apply mtimes to directories. */
|
||||
bool omit_dir_times;
|
||||
/* -J/--omit-link-times: do not apply times to symlinks. */
|
||||
bool omit_link_times;
|
||||
/* --open-noatime: CLIENT-ONLY (never crosses the wire). The sender opens
|
||||
* source files with O_NOATIME so reading for transfer does not bump the
|
||||
* source access time. */
|
||||
bool open_noatime;
|
||||
} Config;
|
||||
|
||||
#define PROTOCOL_VERSION "2.12.0"
|
||||
|
||||
+74
-2
@@ -1,3 +1,6 @@
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE /* statx + STATX_BTIME for --crtimes birth-time capture */
|
||||
#endif
|
||||
#include <errno.h>
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
@@ -133,7 +136,8 @@ void file_destroy(void* item) {
|
||||
free(file);
|
||||
}
|
||||
|
||||
FileMetadata* file_metadata_create(const struct stat* stats) {
|
||||
FileMetadata* file_metadata_create(const char* path, const struct stat* stats, bool capture_atime,
|
||||
bool capture_crtime) {
|
||||
FileMetadata* m = protocol_alloc(sizeof(FileMetadata));
|
||||
if (m == NULL) {
|
||||
log_perror("ERROR: Could not allocate memory for file metadata");
|
||||
@@ -148,6 +152,37 @@ FileMetadata* file_metadata_create(const struct stat* stats) {
|
||||
#else
|
||||
m->mtime_nsec = 0;
|
||||
#endif
|
||||
/* -U/--atimes: capture the access time from the same pre-read stat the
|
||||
scanner already took, so the value is not clobbered by a later read for
|
||||
transfer. The timestamp is populated (and atime_valid set) only on Linux,
|
||||
where st_atim is populated; on other platforms the atime is left alone
|
||||
rather than clobbered to the default 0/epoch by an unpopulated value. */
|
||||
#ifdef __linux__
|
||||
m->atime_valid = capture_atime;
|
||||
m->atime_sec = stats->st_atim.tv_sec;
|
||||
m->atime_nsec = stats->st_atim.tv_nsec;
|
||||
#else
|
||||
m->atime_valid = false;
|
||||
m->atime_sec = 0;
|
||||
m->atime_nsec = 0;
|
||||
#endif
|
||||
/* -N/--crtimes: birth time is not available via struct stat in general; on
|
||||
Linux it needs statx STATX_BTIME. If unavailable it is captured as a
|
||||
documented no-op (the flag stays accepted, crtime_valid stays false). */
|
||||
m->crtime_valid = false;
|
||||
m->crtime_sec = 0;
|
||||
m->crtime_nsec = 0;
|
||||
if (capture_crtime) {
|
||||
#ifdef STATX_BTIME
|
||||
struct statx stx;
|
||||
if (path != NULL && statx(AT_FDCWD, path, AT_STATX_SYNC_AS_STAT, STATX_BTIME, &stx) == 0 &&
|
||||
(stx.stx_mask & STATX_BTIME) != 0) {
|
||||
m->crtime_valid = true;
|
||||
m->crtime_sec = (time_t)stx.stx_btime.tv_sec;
|
||||
m->crtime_nsec = (long)stx.stx_btime.tv_nsec;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
@@ -155,6 +190,37 @@ void file_metadata_destroy(void* metadata) {
|
||||
free(metadata);
|
||||
}
|
||||
|
||||
/* --open-noatime: process-wide sender policy (client-only, never crosses the
|
||||
* wire). When enabled, opening a source file for transfer uses O_NOATIME so
|
||||
* the read does not bump the source's on-disk access time. It degrades safely
|
||||
* to a normal open where O_NOATIME is unavailable (not defined) or refused
|
||||
* (EPERM, because it needs CAP_FOWNER): the data path never silently changes,
|
||||
* only the atime-bump is skipped. */
|
||||
static bool file_open_noatime = false;
|
||||
|
||||
void file_set_open_noatime(bool enable) {
|
||||
file_open_noatime = enable;
|
||||
}
|
||||
|
||||
bool file_get_open_noatime(void) {
|
||||
return file_open_noatime;
|
||||
}
|
||||
|
||||
/* Open `path` read-only for transfer, honouring --open-noatime when set. */
|
||||
int file_open_for_read(const char* path) {
|
||||
int flags = O_RDONLY;
|
||||
#ifdef O_NOATIME
|
||||
if (file_get_open_noatime())
|
||||
flags |= O_NOATIME;
|
||||
#endif
|
||||
int fd = open(path, flags);
|
||||
#ifdef O_NOATIME
|
||||
if (fd < 0 && (flags & O_NOATIME))
|
||||
fd = open(path, O_RDONLY); /* degrade safely on EPERM / unsupported fs */
|
||||
#endif
|
||||
return fd;
|
||||
}
|
||||
|
||||
bool file_load_data(File* file) {
|
||||
if (file == NULL || !file->data)
|
||||
return false;
|
||||
@@ -181,8 +247,14 @@ bool file_load_data(File* file) {
|
||||
size_t file_content_to_buffer(File* file) {
|
||||
if (!file || !file->path || !file->data || (!file->data->data && file->data->size != 0))
|
||||
return 0;
|
||||
FILE* file_pointer = fopen(file->path, "rb");
|
||||
int fd = file_open_for_read(file->path);
|
||||
if (fd < 0) {
|
||||
log_perror("Could not open the file!");
|
||||
return 0;
|
||||
}
|
||||
FILE* file_pointer = fdopen(fd, "rb");
|
||||
if (file_pointer == NULL) {
|
||||
close(fd);
|
||||
log_perror("Could not open the file!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
+7
-1
@@ -22,8 +22,14 @@ bool file_load_data(File* file);
|
||||
bool file_checksum(File* file, ChecksumAlgo algo, uint64_t seed, uint8_t* out, size_t out_capacity,
|
||||
size_t* out_len);
|
||||
size_t file_content_to_buffer(File* file);
|
||||
FileMetadata* file_metadata_create(const struct stat* stats);
|
||||
FileMetadata* file_metadata_create(const char* path, const struct stat* stats, bool capture_atime,
|
||||
bool capture_crtime);
|
||||
void file_metadata_destroy(void* metadata);
|
||||
/* --open-noatime process-wide sender policy; see file.c. */
|
||||
void file_set_open_noatime(bool enable);
|
||||
bool file_get_open_noatime(void);
|
||||
/* Open `path` read-only for transfer, honouring --open-noatime when set. */
|
||||
int file_open_for_read(const char* path);
|
||||
bool file_write_to_disk(const char* path, const void* data, unsigned long long data_size,
|
||||
bool inplace, bool sparse);
|
||||
|
||||
|
||||
@@ -1416,7 +1416,7 @@ File* receive_incremental_check(int fd, const Config* config, bool* skipped) {
|
||||
if (materialized && basis.content) {
|
||||
materialized->data = basis.content;
|
||||
basis.content = NULL;
|
||||
materialized->metadata = file_metadata_create(&basis.st);
|
||||
materialized->metadata = file_metadata_create(NULL, &basis.st, false, false);
|
||||
materialized->skip = true; /* receiver must not ack this as a data file */
|
||||
if (basis.type == BASIS_DEST_LINK) {
|
||||
materialized->basis_link = basis.basis_path;
|
||||
|
||||
@@ -78,7 +78,7 @@ bool file_send_sendfile_with_skip(File* file, int file_descriptor, bool use_meta
|
||||
if (use_metadata && !metadata_send(file_descriptor, file->metadata))
|
||||
return false;
|
||||
|
||||
int fd = open(file->path, O_RDONLY);
|
||||
int fd = file_open_for_read(file->path);
|
||||
if (fd == -1) {
|
||||
log_perror("Could not open file for sendfile");
|
||||
return false;
|
||||
|
||||
@@ -13,6 +13,18 @@ typedef struct {
|
||||
gid_t gid;
|
||||
time_t mtime_sec;
|
||||
long mtime_nsec;
|
||||
/* Optional access time (-U/--atimes) and creation/birth time (-N/--crtimes),
|
||||
* appended for protocol 2.12.0. The SENDER sets the corresponding *_valid
|
||||
* flag only when the preserve option is active (and, for crtime, only when
|
||||
* the source platform exposed a birth time via statx STATX_BTIME). The wire
|
||||
* always carries the fields and the flags; a false flag tells the receiver to
|
||||
* ignore the value. */
|
||||
bool atime_valid;
|
||||
time_t atime_sec;
|
||||
long atime_nsec;
|
||||
bool crtime_valid;
|
||||
time_t crtime_sec;
|
||||
long crtime_nsec;
|
||||
} FileMetadata;
|
||||
|
||||
typedef struct {
|
||||
|
||||
+134
-3
@@ -70,6 +70,24 @@ void metadata_to_buf(char** buf, const FileMetadata* m) {
|
||||
int64_t mtime_nsec = (int64_t)m->mtime_nsec;
|
||||
memcpy(*buf, &mtime_nsec, sizeof(mtime_nsec));
|
||||
*buf += sizeof(mtime_nsec);
|
||||
int32_t atime_valid = m->atime_valid ? 1 : 0;
|
||||
memcpy(*buf, &atime_valid, sizeof(atime_valid));
|
||||
*buf += sizeof(atime_valid);
|
||||
int64_t atime_sec = (int64_t)m->atime_sec;
|
||||
memcpy(*buf, &atime_sec, sizeof(atime_sec));
|
||||
*buf += sizeof(atime_sec);
|
||||
int64_t atime_nsec = (int64_t)m->atime_nsec;
|
||||
memcpy(*buf, &atime_nsec, sizeof(atime_nsec));
|
||||
*buf += sizeof(atime_nsec);
|
||||
int32_t crtime_valid = m->crtime_valid ? 1 : 0;
|
||||
memcpy(*buf, &crtime_valid, sizeof(crtime_valid));
|
||||
*buf += sizeof(crtime_valid);
|
||||
int64_t crtime_sec = (int64_t)m->crtime_sec;
|
||||
memcpy(*buf, &crtime_sec, sizeof(crtime_sec));
|
||||
*buf += sizeof(crtime_sec);
|
||||
int64_t crtime_nsec = (int64_t)m->crtime_nsec;
|
||||
memcpy(*buf, &crtime_nsec, sizeof(crtime_nsec));
|
||||
*buf += sizeof(crtime_nsec);
|
||||
}
|
||||
|
||||
FileMetadata* metadata_from_buf(char** buf) {
|
||||
@@ -103,8 +121,34 @@ FileMetadata* metadata_from_buf(char** buf) {
|
||||
memcpy(&mtime_nsec, *buf, sizeof(mtime_nsec));
|
||||
*buf += sizeof(mtime_nsec);
|
||||
m->mtime_nsec = (long)mtime_nsec;
|
||||
int32_t atime_valid;
|
||||
memcpy(&atime_valid, *buf, sizeof(atime_valid));
|
||||
*buf += sizeof(atime_valid);
|
||||
int64_t atime_sec;
|
||||
memcpy(&atime_sec, *buf, sizeof(atime_sec));
|
||||
*buf += sizeof(atime_sec);
|
||||
int64_t atime_nsec;
|
||||
memcpy(&atime_nsec, *buf, sizeof(atime_nsec));
|
||||
*buf += sizeof(atime_nsec);
|
||||
int32_t crtime_valid;
|
||||
memcpy(&crtime_valid, *buf, sizeof(crtime_valid));
|
||||
*buf += sizeof(crtime_valid);
|
||||
int64_t crtime_sec;
|
||||
memcpy(&crtime_sec, *buf, sizeof(crtime_sec));
|
||||
*buf += sizeof(crtime_sec);
|
||||
int64_t crtime_nsec;
|
||||
memcpy(&crtime_nsec, *buf, sizeof(crtime_nsec));
|
||||
*buf += sizeof(crtime_nsec);
|
||||
m->atime_valid = atime_valid != 0;
|
||||
m->atime_sec = (time_t)atime_sec;
|
||||
m->atime_nsec = (long)atime_nsec;
|
||||
m->crtime_valid = crtime_valid != 0;
|
||||
m->crtime_sec = (time_t)crtime_sec;
|
||||
m->crtime_nsec = (long)crtime_nsec;
|
||||
if (present != 1 || mtime_nsec < 0 || mtime_nsec >= 1000000000LL || mode < 0 || uid < 0 ||
|
||||
gid < 0) {
|
||||
gid < 0 || atime_valid < 0 || atime_valid > 1 || crtime_valid < 0 || crtime_valid > 1 ||
|
||||
(atime_valid && (atime_nsec < 0 || atime_nsec >= 1000000000LL)) ||
|
||||
(crtime_valid && (crtime_nsec < 0 || crtime_nsec >= 1000000000LL))) {
|
||||
free(m);
|
||||
return NULL;
|
||||
}
|
||||
@@ -122,12 +166,24 @@ bool metadata_send(int file_descriptor, const FileMetadata* m) {
|
||||
int32_t gid = (int32_t)m->gid;
|
||||
int64_t mtime_sec = (int64_t)m->mtime_sec;
|
||||
int64_t mtime_nsec = (int64_t)m->mtime_nsec;
|
||||
int32_t atime_valid = m->atime_valid ? 1 : 0;
|
||||
int64_t atime_sec = (int64_t)m->atime_sec;
|
||||
int64_t atime_nsec = (int64_t)m->atime_nsec;
|
||||
int32_t crtime_valid = m->crtime_valid ? 1 : 0;
|
||||
int64_t crtime_sec = (int64_t)m->crtime_sec;
|
||||
int64_t crtime_nsec = (int64_t)m->crtime_nsec;
|
||||
return send_n_data(file_descriptor, &present, sizeof(present)) &&
|
||||
send_n_data(file_descriptor, &mode, sizeof(mode)) &&
|
||||
send_n_data(file_descriptor, &uid, sizeof(uid)) &&
|
||||
send_n_data(file_descriptor, &gid, sizeof(gid)) &&
|
||||
send_n_data(file_descriptor, &mtime_sec, sizeof(mtime_sec)) &&
|
||||
send_n_data(file_descriptor, &mtime_nsec, sizeof(mtime_nsec));
|
||||
send_n_data(file_descriptor, &mtime_nsec, sizeof(mtime_nsec)) &&
|
||||
send_n_data(file_descriptor, &atime_valid, sizeof(atime_valid)) &&
|
||||
send_n_data(file_descriptor, &atime_sec, sizeof(atime_sec)) &&
|
||||
send_n_data(file_descriptor, &atime_nsec, sizeof(atime_nsec)) &&
|
||||
send_n_data(file_descriptor, &crtime_valid, sizeof(crtime_valid)) &&
|
||||
send_n_data(file_descriptor, &crtime_sec, sizeof(crtime_sec)) &&
|
||||
send_n_data(file_descriptor, &crtime_nsec, sizeof(crtime_nsec));
|
||||
}
|
||||
|
||||
FileMetadata* metadata_receive(int file_descriptor, int* ok) {
|
||||
@@ -193,7 +249,58 @@ FileMetadata* metadata_receive(int file_descriptor, int* ok) {
|
||||
return NULL;
|
||||
}
|
||||
m->mtime_nsec = (long)mtime_nsec;
|
||||
if (mtime_nsec < 0 || mtime_nsec >= 1000000000LL || mode < 0 || uid < 0 || gid < 0) {
|
||||
int32_t atime_valid;
|
||||
if (!receive_n_data(file_descriptor, &atime_valid, sizeof(atime_valid))) {
|
||||
free(m);
|
||||
if (ok)
|
||||
*ok = 0;
|
||||
return NULL;
|
||||
}
|
||||
int64_t atime_sec;
|
||||
if (!receive_n_data(file_descriptor, &atime_sec, sizeof(atime_sec))) {
|
||||
free(m);
|
||||
if (ok)
|
||||
*ok = 0;
|
||||
return NULL;
|
||||
}
|
||||
int64_t atime_nsec;
|
||||
if (!receive_n_data(file_descriptor, &atime_nsec, sizeof(atime_nsec))) {
|
||||
free(m);
|
||||
if (ok)
|
||||
*ok = 0;
|
||||
return NULL;
|
||||
}
|
||||
int32_t crtime_valid;
|
||||
if (!receive_n_data(file_descriptor, &crtime_valid, sizeof(crtime_valid))) {
|
||||
free(m);
|
||||
if (ok)
|
||||
*ok = 0;
|
||||
return NULL;
|
||||
}
|
||||
int64_t crtime_sec;
|
||||
if (!receive_n_data(file_descriptor, &crtime_sec, sizeof(crtime_sec))) {
|
||||
free(m);
|
||||
if (ok)
|
||||
*ok = 0;
|
||||
return NULL;
|
||||
}
|
||||
int64_t crtime_nsec;
|
||||
if (!receive_n_data(file_descriptor, &crtime_nsec, sizeof(crtime_nsec))) {
|
||||
free(m);
|
||||
if (ok)
|
||||
*ok = 0;
|
||||
return NULL;
|
||||
}
|
||||
m->atime_valid = atime_valid != 0;
|
||||
m->atime_sec = (time_t)atime_sec;
|
||||
m->atime_nsec = (long)atime_nsec;
|
||||
m->crtime_valid = crtime_valid != 0;
|
||||
m->crtime_sec = (time_t)crtime_sec;
|
||||
m->crtime_nsec = (long)crtime_nsec;
|
||||
if (mtime_nsec < 0 || mtime_nsec >= 1000000000LL || mode < 0 || uid < 0 || gid < 0 ||
|
||||
atime_valid < 0 || atime_valid > 1 || crtime_valid < 0 || crtime_valid > 1 ||
|
||||
(atime_valid && (atime_nsec < 0 || atime_nsec >= 1000000000LL)) ||
|
||||
(crtime_valid && (crtime_nsec < 0 || crtime_nsec >= 1000000000LL))) {
|
||||
free(m);
|
||||
if (ok)
|
||||
*ok = 0;
|
||||
@@ -232,6 +339,16 @@ void file_restore_metadata(const char* path, const FileMetadata* metadata,
|
||||
times[0].tv_nsec = UTIME_OMIT;
|
||||
times[1].tv_sec = metadata->mtime_sec;
|
||||
times[1].tv_nsec = metadata->mtime_nsec;
|
||||
if (metadata->atime_valid) {
|
||||
times[0].tv_sec = metadata->atime_sec;
|
||||
times[0].tv_nsec = metadata->atime_nsec;
|
||||
}
|
||||
if (metadata->crtime_valid) {
|
||||
log_message(LOG_LEVEL_DEBUG,
|
||||
"crtime (birth time) %lld.%09ld transmitted for %s but not applied: no portable "
|
||||
"setter exists",
|
||||
(long long)metadata->crtime_sec, metadata->crtime_nsec, path);
|
||||
}
|
||||
if (utimensat(AT_FDCWD, path, times, 0) != 0) {
|
||||
char* escaped_path = output_escape(path, log_get_8_bit_output());
|
||||
log_message(LOG_LEVEL_WARNING, "Failed to set timestamps on %s: %s",
|
||||
@@ -261,6 +378,20 @@ bool file_restore_metadata_fd(int fd, const FileMetadata* metadata, bool preserv
|
||||
identity_apply_ownership(fd, (int32_t)metadata->uid, (int32_t)metadata->gid);
|
||||
struct timespec times[2] = {{.tv_sec = 0, .tv_nsec = UTIME_OMIT},
|
||||
{.tv_sec = metadata->mtime_sec, .tv_nsec = metadata->mtime_nsec}};
|
||||
if (metadata->atime_valid) {
|
||||
times[0].tv_sec = metadata->atime_sec;
|
||||
times[0].tv_nsec = metadata->atime_nsec;
|
||||
}
|
||||
/* --crtimes captures and transmits the source birth time, but there is no
|
||||
* portable way to set a birth time (utimensat can only set atime/mtime), so
|
||||
* the receiver deliberately does NOT apply it. This is explicit, honest
|
||||
* unsupported-attribute handling: log a debug note and continue — never fail
|
||||
* the transfer and never pretend the crtime was applied. */
|
||||
if (metadata->crtime_valid) {
|
||||
log_message(LOG_LEVEL_DEBUG,
|
||||
"crtime (birth time) %lld.%09ld transmitted but not applied: no portable setter",
|
||||
(long long)metadata->crtime_sec, metadata->crtime_nsec);
|
||||
}
|
||||
if (futimens(fd, times) != 0)
|
||||
ok = false;
|
||||
return ok;
|
||||
|
||||
@@ -15,6 +15,12 @@
|
||||
* int32_t gid (was gid_t, platform-dependent)
|
||||
* int64_t mtime_sec (was time_t, platform-dependent)
|
||||
* int64_t mtime_nsec (was long, platform-dependent)
|
||||
* int32_t atime_valid (-U/--atimes; protocol 2.12.0)
|
||||
* int64_t atime_sec
|
||||
* int64_t atime_nsec
|
||||
* int32_t crtime_valid (-N/--crtimes; protocol 2.12.0)
|
||||
* int64_t crtime_sec
|
||||
* int64_t crtime_nsec
|
||||
*
|
||||
* Prior to 2.0.0 the wire format used the raw platform-dependent types,
|
||||
* which broke compatiblity across different systems. All fields are now
|
||||
@@ -23,8 +29,8 @@
|
||||
|
||||
/* Size of metadata fields on wire, excluding the int32_t `present` field that
|
||||
* is always sent first. The total wire size for present metadata is
|
||||
* sizeof(int32_t) + FILE_METADATA_WIRE_SIZE (32 bytes on most platforms). */
|
||||
#define FILE_METADATA_WIRE_SIZE (sizeof(int32_t) * 3 + sizeof(int64_t) * 2)
|
||||
* sizeof(int32_t) + FILE_METADATA_WIRE_SIZE (68 bytes on most platforms). */
|
||||
#define FILE_METADATA_WIRE_SIZE (sizeof(int32_t) * 5 + sizeof(int64_t) * 6)
|
||||
|
||||
void metadata_to_buf(char** buf, const FileMetadata* m);
|
||||
FileMetadata* metadata_from_buf(char** buf);
|
||||
|
||||
Reference in New Issue
Block a user