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

Reply via email to