CI / lint (pull_request) Successful in 26s
CI / sanitizers (address) (pull_request) Failing after 41s
CI / sanitizers (undefined) (pull_request) Successful in 40s
CI / fuzz-build (pull_request) Successful in 18s
CI / coverage (pull_request) Successful in 35s
CI / valgrind (pull_request) Failing after 37s
CI / build-and-test (pull_request) Failing after 2m35s
RSYNC_COMPAT Phase-2 row M: path-list construction and destination directory creation while preserving traversal safety. - -R/--relative with --files-from: transmit each listed entry under its bare relative destination path (no source-root mirror). Files keep an absolute local read path plus a separate wire/dest path (File.send_path); manifest, incremental quick-check and change output follow the wire path, so --delete and --remove-source-files stay consistent. -R without --files-from is unchanged (full mirror). - --no-implied-dirs: client-only, only meaningful with -R + --files-from. A listed file whose parent dir is not itself (or via an ancestor) explicitly listed cannot be placed; the run fails up front with a clear error. No effect otherwise. - --dirs/-d + --old-dirs/--old-d aliases: -d <dir> transmits the source root as an explicit empty directory entry (STATUS_MKDIR frame); with --files-from listed dirs are created empty and listed files transferred, never descending. Works single-threaded, -m (sequential scanner in the -m scan thread) and chunk-serialization (per-file type marker). Directory entries appear in the delete manifest. - --mkpath: new wire bool; server creates the destination root (and missing leading components under its authorized root) at connection start. A missing destination root is now rejected by default. - Protocol bumped to 2.7.0 (mkpath wire field + STATUS_MKDIR + chunk type marker). All receive paths funnel through file_save_to_disk_full which creates directories via the secure confined mkdir engine; dir entries are excluded from --remove-source-files outcome acknowledgements on both ends. - Unit coverage: CLI parse (relative/dirs aliases/mkpath/no-implied-dirs), config round-trip (relative + mkpath), scanner --dirs non-recursion and -R send_path (sequential + parallel), receiver dir-entry save. - Integration coverage: TestRelativeFilesFrom, TestNoImpliedDirs, TestDirs, TestMkpath (single and -m). - RSYNC_COMPAT: 4 rows move to Implemented (Summary 67/3/5/1/71 = 147).
155 lines
5.2 KiB
C
155 lines
5.2 KiB
C
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <poll.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/sendfile.h>
|
|
#include <sys/stat.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include "compression.h"
|
|
#include "data.h"
|
|
#include "file.h"
|
|
#include "log.h"
|
|
#include "metadata.h"
|
|
#include "protocol.h"
|
|
|
|
bool file_send_single_calls(File* file, int file_descriptor, bool use_metadata,
|
|
int compression_level, bool send_path) {
|
|
return file_send_single_calls_with_skip(file, file_descriptor, use_metadata, compression_level,
|
|
send_path, NULL, -1, 0);
|
|
}
|
|
|
|
bool file_send_single_calls_with_skip(File* file, int file_descriptor, bool use_metadata,
|
|
int compression_level, bool send_path,
|
|
char* const* skip_suffixes, int skip_count,
|
|
int compression_threads) {
|
|
if (!file || !file->path || !file->data || (file->data->size != 0 && !file->data->data))
|
|
return false;
|
|
const Data* data_to_send = file->data;
|
|
Data* compressed_data = NULL;
|
|
if (compression_level > 0 &&
|
|
!compression_should_skip_with_suffixes(file->path, skip_suffixes, skip_count)) {
|
|
compressed_data =
|
|
data_compress_with_threads(file->data, compression_level, compression_threads);
|
|
if (compressed_data == NULL) {
|
|
log_message(LOG_LEVEL_ERROR, "Failed to compress file data");
|
|
return false;
|
|
}
|
|
data_to_send = compressed_data;
|
|
}
|
|
if (send_path && !send_str(file_descriptor, file_wire_path(file))) {
|
|
data_destroy(compressed_data);
|
|
return false;
|
|
}
|
|
if (use_metadata && !metadata_send(file_descriptor, file->metadata)) {
|
|
data_destroy(compressed_data);
|
|
return false;
|
|
}
|
|
if (!send_data(file_descriptor, data_to_send)) {
|
|
data_destroy(compressed_data);
|
|
return false;
|
|
}
|
|
data_destroy(compressed_data);
|
|
return true;
|
|
}
|
|
|
|
bool file_send_sendfile(File* file, int file_descriptor, bool use_metadata, int compression_level,
|
|
bool send_path) {
|
|
return file_send_sendfile_with_skip(file, file_descriptor, use_metadata, compression_level,
|
|
send_path, NULL, -1, 0);
|
|
}
|
|
|
|
bool file_send_sendfile_with_skip(File* file, int file_descriptor, bool use_metadata,
|
|
int compression_level, bool send_path, char* const* skip_suffixes,
|
|
int skip_count, int compression_threads) {
|
|
if (!file || !file->path || !file->data)
|
|
return false;
|
|
if (compression_level > 0)
|
|
return file_send_single_calls_with_skip(file, file_descriptor, use_metadata, compression_level,
|
|
send_path, skip_suffixes, skip_count,
|
|
compression_threads);
|
|
|
|
if (send_path && !send_str(file_descriptor, file_wire_path(file)))
|
|
return false;
|
|
if (use_metadata && !metadata_send(file_descriptor, file->metadata))
|
|
return false;
|
|
|
|
int fd = open(file->path, O_RDONLY);
|
|
if (fd == -1) {
|
|
log_perror("Could not open file for sendfile");
|
|
return false;
|
|
}
|
|
|
|
unsigned long long file_size = file->data->size;
|
|
struct stat source_stat;
|
|
if (fstat(fd, &source_stat) != 0 || !S_ISREG(source_stat.st_mode) ||
|
|
(unsigned long long)source_stat.st_size < file_size) {
|
|
close(fd);
|
|
return false;
|
|
}
|
|
if (!send_n_data(file_descriptor, &file_size, sizeof(unsigned long long))) {
|
|
close(fd);
|
|
return false;
|
|
}
|
|
|
|
/* sendfile cannot encrypt TLS records. Keep the framing identical but
|
|
route encrypted transfers through the deadline-aware IO layer. */
|
|
if (io_get_ssl() != NULL) {
|
|
unsigned char buffer[64 * 1024];
|
|
unsigned long long remaining = file_size;
|
|
bool ok = true;
|
|
while (remaining > 0) {
|
|
size_t want = remaining > sizeof(buffer) ? sizeof(buffer) : (size_t)remaining;
|
|
ssize_t got = read(fd, buffer, want);
|
|
if (got <= 0 || !send_n_data(file_descriptor, buffer, (size_t)got)) {
|
|
ok = false;
|
|
break;
|
|
}
|
|
remaining -= (unsigned long long)got;
|
|
}
|
|
close(fd);
|
|
return ok;
|
|
}
|
|
|
|
off_t offset = 0;
|
|
struct timespec deadline;
|
|
clock_gettime(CLOCK_MONOTONIC, &deadline);
|
|
deadline.tv_sec += 60;
|
|
while ((unsigned long long)offset < file_size) {
|
|
struct timespec now;
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
long long remaining = (long long)(deadline.tv_sec - now.tv_sec) * 1000LL +
|
|
(deadline.tv_nsec - now.tv_nsec) / 1000000LL;
|
|
if (remaining <= 0) {
|
|
close(fd);
|
|
return false;
|
|
}
|
|
struct pollfd pfd = {.fd = file_descriptor, .events = POLLOUT};
|
|
int timeout = remaining > INT_MAX ? INT_MAX : (int)remaining;
|
|
int polled = poll(&pfd, 1, timeout);
|
|
if (polled <= 0 || (pfd.revents & (POLLERR | POLLHUP | POLLNVAL))) {
|
|
close(fd);
|
|
return false;
|
|
}
|
|
ssize_t sent = sendfile(file_descriptor, fd, &offset, file_size - offset);
|
|
if (sent == -1) {
|
|
if (errno == EAGAIN || errno == EINTR)
|
|
continue;
|
|
log_perror("sendfile failed");
|
|
close(fd);
|
|
return false;
|
|
}
|
|
if (sent == 0) {
|
|
close(fd);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
close(fd);
|
|
return true;
|
|
}
|