copy: retire splice use() for copying files on disk

Apparently splice() is quite problematic, hence just don't anymore. It's
also unnecessary these days since either copy_file_range() or sendfile()
nowadays typically work, the splice() fallback doesn't give us much
anymore.

(At least I am not aware of a combo of fds where splice() would work
where neither cfr nor sf would work).

This leaves one use of splice() in place, in
src/shared/socket-forward.c. We should probably kill that too, but
that'd require some reworking to use sendfile() I guess, and I am too
lazy for that right now. Moreover, in contrast to the other uses it's
probably even safe, since it uses an intermediary pipe always. But what
do I know...

Fixes: #29044
This commit is contained in:
Lennart Poettering
2026-05-13 15:29:36 +01:00
committed by Luca Boccassi
parent 3061ec2707
commit 94e05aca0d
2 changed files with 7 additions and 117 deletions
-22
View File
@@ -59,7 +59,6 @@ typedef struct TarExport {
RateLimit progress_ratelimit;
bool eof;
bool tried_splice;
} TarExport;
TarExport *tar_export_unref(TarExport *e) {
@@ -189,27 +188,6 @@ static int tar_export_process(TarExport *e) {
assert(e);
if (!e->tried_splice && compressor_type(e->compress) == COMPRESSION_NONE) {
l = splice(e->tar_fd, NULL, e->output_fd, NULL, COMPRESS_PIPE_BUFFER_SIZE, 0);
if (l < 0) {
if (errno == EAGAIN)
return 0;
e->tried_splice = true;
} else if (l == 0) {
r = tar_export_finish(e);
goto finish;
} else {
e->written_uncompressed += l;
e->written_compressed += l;
tar_export_report_progress(e);
return 0;
}
}
while (e->buffer_size <= 0) {
uint8_t input[COMPRESS_PIPE_BUFFER_SIZE];
+7 -95
View File
@@ -71,31 +71,6 @@ static ssize_t try_copy_file_range(
return r;
}
enum {
FD_IS_NO_PIPE,
FD_IS_BLOCKING_PIPE,
FD_IS_NONBLOCKING_PIPE,
};
static int fd_is_nonblock_pipe(int fd) {
struct stat st;
int flags;
/* Checks whether the specified file descriptor refers to a pipe, and if so if O_NONBLOCK is set. */
if (fstat(fd, &st) < 0)
return -errno;
if (!S_ISFIFO(st.st_mode))
return FD_IS_NO_PIPE;
flags = fcntl(fd, F_GETFL);
if (flags < 0)
return -errno;
return FLAGS_SET(flags, O_NONBLOCK) ? FD_IS_NONBLOCKING_PIPE : FD_IS_BLOCKING_PIPE;
}
static int look_for_signals(CopyFlags copy_flags) {
int r;
@@ -164,9 +139,9 @@ int copy_bytes_full(
void *userdata) {
_cleanup_close_ int fdf_opened = -EBADF, fdt_opened = -EBADF;
bool try_cfr = true, try_sendfile = true, try_splice = true;
bool try_cfr = true, try_sendfile = true;
uint64_t copied_total = 0;
int r, nonblock_pipe = -1;
int r;
assert(fdf >= 0);
assert(fdt >= 0);
@@ -366,7 +341,7 @@ int copy_bytes_full(
}
}
/* First try copy_file_range(), unless we already tried */
/* First, try copy_file_range(), unless we already tried */
if (try_cfr) {
n = try_copy_file_range(fdf, NULL, fdt, NULL, m, 0u);
if (n < 0) {
@@ -387,13 +362,13 @@ int copy_bytes_full(
* back to simple read()s in case we encounter empty files.
*
* See: https://lwn.net/Articles/846403/ */
try_cfr = try_sendfile = try_splice = false;
try_cfr = try_sendfile = false;
} else
/* Success! */
goto next;
}
/* First try sendfile(), unless we already tried */
/* Second, try sendfile(), unless we already tried */
if (try_sendfile) {
n = sendfile(fdt, fdf, NULL, m);
if (n < 0) {
@@ -407,76 +382,13 @@ int copy_bytes_full(
if (copied_total > 0)
break;
try_sendfile = try_splice = false; /* same logic as above for copy_file_range() */
try_sendfile = false; /* same logic as above for copy_file_range() */
} else
/* Success! */
goto next;
}
/* Then try splice, unless we already tried. */
if (try_splice) {
/* splice()'s asynchronous I/O support is a bit weird. When it encounters a pipe file
* descriptor, then it will ignore its O_NONBLOCK flag and instead only honour the
* SPLICE_F_NONBLOCK flag specified in its flag parameter. Let's hide this behaviour
* here, and check if either of the specified fds are a pipe, and if so, let's pass
* the flag automatically, depending on O_NONBLOCK being set.
*
* Here's a twist though: when we use it to move data between two pipes of which one
* has O_NONBLOCK set and the other has not, then we have no individual control over
* O_NONBLOCK behaviour. Hence in that case we can't use splice() and still guarantee
* systematic O_NONBLOCK behaviour, hence don't. */
if (nonblock_pipe < 0) {
int a, b;
/* Check if either of these fds is a pipe, and if so non-blocking or not */
a = fd_is_nonblock_pipe(fdf);
if (a < 0)
return a;
b = fd_is_nonblock_pipe(fdt);
if (b < 0)
return b;
if ((a == FD_IS_NO_PIPE && b == FD_IS_NO_PIPE) ||
(a == FD_IS_BLOCKING_PIPE && b == FD_IS_NONBLOCKING_PIPE) ||
(a == FD_IS_NONBLOCKING_PIPE && b == FD_IS_BLOCKING_PIPE))
/* splice() only works if one of the fds is a pipe. If neither is,
* let's skip this step right-away. As mentioned above, if one of the
* two fds refers to a blocking pipe and the other to a non-blocking
* pipe, we can't use splice() either, hence don't try either. This
* hence means we can only use splice() if either only one of the two
* fds is a pipe, or if both are pipes with the same nonblocking flag
* setting. */
try_splice = false;
else
nonblock_pipe = a == FD_IS_NONBLOCKING_PIPE || b == FD_IS_NONBLOCKING_PIPE;
}
}
if (try_splice) {
n = splice(fdf, NULL, fdt, NULL, m, nonblock_pipe ? SPLICE_F_NONBLOCK : 0);
if (n < 0) {
if (!IN_SET(errno, EINVAL, ENOSYS))
return -errno;
try_splice = false;
/* use fallback below */
} else if (n == 0) { /* likely EOF */
if (copied_total > 0)
break;
try_splice = false; /* same logic as above for copy_file_range() + sendfile() */
} else
/* Success! */
goto next;
}
/* As a fallback just copy bits by hand */
/* Third, as a fallback just copy bits by hand */
{
uint8_t buf[MIN(m, COPY_BUFFER_SIZE)], *p = buf;
ssize_t z;