On 02/04/2026 10:13, Collin Funk wrote:
Pádraig Brady <[email protected]> writes:
Cool. The read/write fallback should work.
I see this is now diagnosed:
$ strace -e inject=splice:error=EIO:when=3 \ src/cat /dev/zero > t.c
However errors on the writing splice are not always diagnosed:
$ strace -e inject=splice:error=EIO:when=4 \
src/cat /dev/zero > t.c
I think think that just needs a clause like you have already done on the read
side.
Oops, right. Done in the attached patch. I also simplified things to put
the error call in a single place after the "done" label. I find it
easier to follow that way.
As for tests, the odd and even straces above seem useful.
I also added those.
Another issue I noticed is that in yes.c we avoid the splice
if increase_pipe_size() returns 0. cat.c should do the same.
The increase_pipe_size() function will never return 0. It returns a
reasonable guess if fcntl fails. I think that you were probably thinking
of pipe_splice_size() in src/yes.c. That function returns 0 if the pipe
size is smaller than the buffer.
Looking good.
As for the error message, we'll need to distinguish input from output
as the splice_cat() is dealing with both. With the attached tweaks I now get:
$ (trap '' PIPE; src/cat /dev/zero) | head -c1M >/dev/null
cat: splice error: Broken pipe
$ strace -o /dev/null -e inject=splice:error=ENOMEM:when=2 src/cat /dev/zero | cat
> /dev/nullcat: splice error: Cannot allocate memory
$ strace -o /dev/null -e inject=splice:error=EIO:when=3 src/cat /dev/zero >
/dev/null
cat: /dev/zero: Input/output error
$ strace -o /dev/null -e inject=splice:error=EIO:when=4 src/cat /dev/zero >
/dev/null
cat: standard output: Input/output error
I also adjusted the test to ensure the strace injection is supported,
before checking for a specific error dependent on it.
cheers,
Padraig
diff --git a/NEWS b/NEWS
index dc6d384f4..1b8c02c0a 100644
--- a/NEWS
+++ b/NEWS
@@ -56,8 +56,8 @@ GNU coreutils NEWS -*- outline -*-
** Improvements
- 'cat' now uses zero-copy I/O on Linux when the input or output are pipes to
- significantly increase throughput.
+ 'cat' now uses zero-copy I/O on Linux when the input or output are pipes,
+ to significantly increase throughput.
E.g., throughput improved 5x from 12.6GiB/s to 61.3GiB/s on a Power10 system.
'df --local' recognises more file system types as remote.
diff --git a/src/cat.c b/src/cat.c
index 12a968635..a25505490 100644
--- a/src/cat.c
+++ b/src/cat.c
@@ -556,7 +556,8 @@ static int
splice_cat (void)
{
bool some_copied = false;
- bool ok = true;
+ bool in_ok = true;
+ bool out_ok = true;
#if HAVE_SPLICE
@@ -593,7 +594,9 @@ splice_cat (void)
/* If we successfully splice'd input previously, assume that any
subsequent error is fatal. If not, then fall back to read
and write. */
- ok = 0 <= bytes_read || ! some_copied;
+ in_ok = 0 <= bytes_read || ! some_copied;
+ if (!in_ok && bytes_read < 0 && outfd == STDOUT_FILENO)
+ out_ok = false; /* Don't know if failure was in or out. */
if (bytes_read <= 0)
goto done;
if (outfd == STDOUT_FILENO)
@@ -611,7 +614,7 @@ splice_cat (void)
if (bytes_written < 0)
{
if (some_copied)
- ok = false;
+ out_ok = false;
else
{
char buf[BUFSIZ];
@@ -619,7 +622,8 @@ splice_cat (void)
{
ssize_t count = MIN (bytes_read, sizeof buf);
ssize_t n_read = read (pipefd[0], buf, count);
- ok = 0 <= n_read;
+ /* Failure not associated with in or out. */
+ in_ok = out_ok = 0 <= n_read;
if (n_read <= 0)
goto done;
if (full_write (STDOUT_FILENO, buf, n_read)
@@ -638,8 +642,12 @@ splice_cat (void)
}
done:
- if (! ok)
+ if (! in_ok && ! out_ok)
+ error (0, errno, "%s", _("splice error"));
+ else if (! in_ok)
error (0, errno, "%s", quotef (infile));
+ else if (! out_ok)
+ error (0, errno, "%s", _("standard output"));
if (0 <= pipefd[0])
{
int saved_errno = errno;
@@ -649,7 +657,7 @@ splice_cat (void)
}
#endif
- return ok ? some_copied : -1;
+ return (in_ok && out_ok) ? some_copied : -1;
}
int
diff --git a/tests/cat/splice.sh b/tests/cat/splice.sh
index d47481e16..ad1ebb73b 100755
--- a/tests/cat/splice.sh
+++ b/tests/cat/splice.sh
@@ -28,15 +28,17 @@ if timeout 10 true; then
fi
# Test that splice errors are diagnosed.
-cat <<EOF > exp || framework_failure_
-cat: /dev/zero: $EIO
-EOF
-for when in 3 4; do
- returns_ 1 timeout 10 strace -o /dev/null \
- -e inject=splice:error=EIO:when=$when \
- cat /dev/zero >/dev/null 2>err || fail=1
- compare exp err || fail=1
-done
+# Odd numbers are for input, even for output
+if strace -o /dev/null -e inject=splice:error=EIO:when=3 true; then
+ for when in 3 4; do
+ test "$when" = 4 && efile='standard output' || efile='/dev/zero'
+ printf 'cat: %s: %s\n' "$efile" "$EIO" > exp || framework_failure_
+ returns_ 1 timeout 10 strace -o /dev/null \
+ -e inject=splice:error=EIO:when=$when \
+ cat /dev/zero >/dev/null 2>err || fail=1
+ compare exp err || fail=1
+ done
+fi
# Ensure we fallback to write() if there is an issue with (async) zero-copy
zc_syscalls='io_uring_setup io_uring_enter io_uring_register memfd_create