Files
FastSync/src/shared/file_send.c
T

324 lines
12 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 "charset.h"
#include "compression.h"
#include "data.h"
#include "file.h"
#include "log.h"
#include "metadata.h"
#include "protocol.h"
#include "xattr.h"
/* Transmit a device/special node (--devices / --specials) as a STATUS_SPECIAL
* frame: the destination path, the metadata frame (whose mode's S_IFMT bits
* carry the node kind) and the device rdev major/minor. The receiver validates
* the kind and rdev and recreates the node (privilege-gating the mknod). */
bool file_send_special(const File* file, int file_descriptor, bool use_metadata) {
if (!file || !file_wire_path(file))
return false;
if (!send_status(file_descriptor, STATUS_SPECIAL))
return false;
if (!send_wire_str(file_descriptor, file_wire_path(file)))
return false;
if (use_metadata && !metadata_send(file_descriptor, file->metadata))
return false;
int32_t major = file->rdev_major;
int32_t minor = file->rdev_minor;
return send_n_data(file_descriptor, &major, sizeof(major)) &&
send_n_data(file_descriptor, &minor, sizeof(minor));
}
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, false);
}
/* Stream-compress a whole source file into a temp file, then transmit it as the
* normal length-prefixed data frame. Used when the source was too large to
* load (File.data.data == NULL): the source is read in bounded chunks through a
* streaming codec, so neither the raw nor the compressed image is held in
* memory. The compressed length must be known before the frame is sent (the
* wire is length-prefixed), so the stream lands in a private temp file first. */
bool file_send_compressed_stream_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, bool send_xattrs) {
/* The caller has already decided this file compresses; the skip list is part
of the public signature for symmetry with the buffered path. */
(void)skip_suffixes;
(void)skip_count;
if (!file || !file->path || !file->data || file->data->size == 0 || file->data->data != NULL)
return false;
CompressionAlgo algo = compression_get_algo();
CompressionStreamCompressor* compressor =
compression_stream_compressor_create(algo, compression_level, compression_threads);
if (!compressor) {
log_message(LOG_LEVEL_ERROR, "streaming compression is not available for this codec; "
"the source was not loaded for the buffered path");
return false;
}
int src = file_open_for_read(file->path);
if (src < 0) {
compression_stream_compressor_destroy(compressor);
return false;
}
struct stat src_st;
if (fstat(src, &src_st) != 0 || !S_ISREG(src_st.st_mode) ||
(unsigned long long)src_st.st_size < file->data->size) {
close(src);
compression_stream_compressor_destroy(compressor);
return false;
}
const char* tmpdir = getenv("TMPDIR");
if (!tmpdir || tmpdir[0] == '\0')
tmpdir = "/tmp";
size_t tmplen = strlen(tmpdir) + strlen("/fastsync-z-XXXXXX") + 1;
char* tmpl = malloc(tmplen);
if (!tmpl) {
close(src);
compression_stream_compressor_destroy(compressor);
return false;
}
snprintf(tmpl, tmplen, "%s/fastsync-z-XXXXXX", tmpdir);
int tmp_fd = mkstemp(tmpl);
if (tmp_fd < 0) {
log_perror("Could not create compression temp file");
free(tmpl);
close(src);
compression_stream_compressor_destroy(compressor);
return false;
}
unlink(tmpl);
free(tmpl);
bool ok = compression_stream_compressor_begin(compressor, file->data->size, tmp_fd);
unsigned char buf[64 * 1024];
unsigned long long remaining = file->data->size;
while (ok && remaining > 0) {
size_t want = remaining < sizeof(buf) ? (size_t)remaining : sizeof(buf);
ssize_t got = read(src, buf, want);
if (got <= 0) {
ok = false;
break;
}
if (!compression_stream_compressor_feed(compressor, buf, (size_t)got, tmp_fd))
ok = false;
remaining -= (unsigned long long)got;
}
if (ok)
ok = compression_stream_compressor_finish(compressor, tmp_fd);
close(src);
compression_stream_compressor_destroy(compressor);
if (!ok) {
close(tmp_fd);
return false;
}
struct stat tmp_st;
if (fstat(tmp_fd, &tmp_st) != 0 || tmp_st.st_size < 0) {
close(tmp_fd);
return false;
}
unsigned long long compressed_size = (unsigned long long)tmp_st.st_size;
if (lseek(tmp_fd, 0, SEEK_SET) == (off_t)-1) {
close(tmp_fd);
return false;
}
ok = true;
if (send_path && !send_wire_str(file_descriptor, file_wire_path(file)))
ok = false;
if (ok && use_metadata && !metadata_send(file_descriptor, file->metadata))
ok = false;
if (ok && send_xattrs && !xattr_send(file_descriptor, file ? file->xattrs : NULL))
ok = false;
if (ok && !send_n_data(file_descriptor, &compressed_size, sizeof(compressed_size)))
ok = false;
unsigned long long left = compressed_size;
while (ok && left > 0) {
size_t want = left < sizeof(buf) ? (size_t)left : sizeof(buf);
ssize_t got = read(tmp_fd, buf, want);
if (got <= 0 || !send_n_data(file_descriptor, buf, (size_t)got)) {
ok = false;
break;
}
left -= (unsigned long long)got;
}
close(tmp_fd);
return ok;
}
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, bool send_xattrs) {
if (!file || !file->path || !file->data)
return false;
/* A source too large to load is streamed: compression streams through a
* temp file, no compression must have taken the sendfile path instead. */
if (file->data->size != 0 && file->data->data == NULL) {
if (compression_level <= 0 ||
compression_should_skip_with_suffixes(file->path, skip_suffixes, skip_count))
return false;
return file_send_compressed_stream_with_skip(file, file_descriptor, use_metadata,
compression_level, send_path, skip_suffixes,
skip_count, compression_threads, send_xattrs);
}
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_wire_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_xattrs && !xattr_send(file_descriptor, file ? file->xattrs : NULL)) {
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, false);
}
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, bool send_xattrs) {
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, send_xattrs);
if (send_path && !send_wire_str(file_descriptor, file_wire_path(file)))
return false;
if (use_metadata && !metadata_send(file_descriptor, file->metadata))
return false;
if (send_xattrs && !xattr_send(file_descriptor, file ? file->xattrs : NULL))
return false;
int fd = file_open_for_read(file->path);
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. Resolve
the transport from the bound session, not the thread-local io_ssl: a
worker thread running a TLS transfer has its SSL only on the session it
bound, so io_get_ssl() would be NULL there and the raw sendfile() path
would be taken on an encrypted socket. */
if (protocol_current_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;
/* A non-positive --timeout disables the deadline: poll blocks until the
* socket is writable (rsync's --timeout=0 default). */
int io_timeout_sec = protocol_get_io_timeout_sec();
struct timespec deadline;
if (io_timeout_sec > 0) {
clock_gettime(CLOCK_MONOTONIC, &deadline);
deadline.tv_sec += io_timeout_sec;
}
while ((unsigned long long)offset < file_size) {
int timeout = -1;
if (io_timeout_sec > 0) {
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;
}
timeout = remaining > INT_MAX ? INT_MAX : (int)remaining;
}
struct pollfd pfd = {.fd = file_descriptor, .events = POLLOUT};
int polled = poll(&pfd, 1, timeout);
if (polled < 0 && errno == EINTR)
continue;
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;
}
protocol_note_bytes_written((unsigned long long)sent);
protocol_throttle_bytes(file_descriptor, (size_t)sent);
}
close(fd);
return true;
}