On 09/01/2026 07:19, Collin Funk wrote:
The 'readlink' and 'realpath' programs have an uncommon case where they
can run for a very long time. When canonicalizing file names longer than
PATH_MAX, we have to call 'openat' for each directory up the tree until
we reach root which takes a long time. Here is an example of the current
behavior:

     $ mkdir -p $(yes a/ | head -n $((32 * 1024)) | tr -d '\n')
     $ while cd $(yes a/ | head -n 1024 | tr -d '\n'); do :; \
         done 2>/dev/null
     $ pwd | tr '/' '\n' | wc -l
     32771
     $ env time --format=%E readlink -f $(yes . | head -n 5) > /dev/full
     readlink: write error: No space left on device
     Command exited with non-zero status 1
     0:59.72
     $ env time --format=%E realpath $(yes . | head -n 5) > /dev/full
     realpath: write error: No space left on device
     Command exited with non-zero status 1
     1:00.32

It is better to exit as soon as there is an error writing to standard
output:

     $ env time --format=%E readlink -f $(yes . | head -n 5) > /dev/full
     readlink: write error: No space left on device
     Command exited with non-zero status 1
     0:11.88
     $ env time --format=%E realpath $(yes . | head -n 5) > /dev/full
     realpath: write error: No space left on device
     Command exited with non-zero status 1
     0:12.04

Yes this is worth doing, as even though these commands can't run indefinitely,
they can run for a while, and the very simple code addition is worth it.
Consider also the time difference with something like:

  yes | xargs sh -c 'realpath -ms "$@" || exit 255' >/dev/full

Since this is just a perf thing there is no real test I can think of.

Though that got me thinking that commands with --files0-from
can have indefinite output, so I adjusted du and wc to output early
and added tests in the attached.

cheers,
Padraig
From d629d542aab336cf219cb1bbd303dc9a60cb0b70 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <[email protected]>
Date: Fri, 9 Jan 2026 14:16:04 +0000
Subject: [PATCH 1/3] wc: promptly diagnose write errors

* src/wc.c (write_counts): Call write_error() if any pending errors.
* tests/misc/write-errors.sh: Add a test case.
* NEWS: Mention the improvement.
---
 NEWS                       | 3 +++
 src/wc.c                   | 3 +++
 tests/misc/write-errors.sh | 1 +
 3 files changed, 7 insertions(+)

diff --git a/NEWS b/NEWS
index da733cbd1..a3239537e 100644
--- a/NEWS
+++ b/NEWS
@@ -88,6 +88,9 @@ GNU coreutils NEWS                                    -*- outline -*-
   'timeout' on Linux will always terminate the child in the case where the
   timeout process itself dies, like when it receives a KILL signal for example.
 
+  'wc' now exits promptly upon receiving a write error,
+  which is significant when processing many input files.
+
 ** Build-related
 
   'kill' and 'uptime' are no longer built by default.  These programs can be
diff --git a/src/wc.c b/src/wc.c
index a6285ba0b..77bfc66bb 100644
--- a/src/wc.c
+++ b/src/wc.c
@@ -258,6 +258,9 @@ write_counts (uintmax_t lines,
   if (file)
     printf (" %s", strchr (file, '\n') ? quotef (file) : file);
   putchar ('\n');
+
+  if (ferror (stdout))
+    write_error ();
 }
 
 /* Read FD and return a summary.  */
diff --git a/tests/misc/write-errors.sh b/tests/misc/write-errors.sh
index 0f85e6476..a84cb8d7a 100755
--- a/tests/misc/write-errors.sh
+++ b/tests/misc/write-errors.sh
@@ -56,6 +56,7 @@ tee < /dev/zero
 tr . . < /dev/zero
 unexpand /dev/zero
 uniq -z -D /dev/zero
+wc --version; yes /dev/null | tr '\\\\n' '\\\\0' | wc --files0-from=-
 yes
 " |
 sort -k 1b,1 > all_writers || framework_failure_
-- 
2.52.0

From 5ccb381dd453f7dca9ba261bce37682cc15af69a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <[email protected]>
Date: Fri, 9 Jan 2026 14:20:56 +0000
Subject: [PATCH 2/3] du: promptly diagnose write errors

* src/du.c (print_size): Call write_error() if can't flush.
* tests/misc/write-errors.sh: Add a test case.
* NEWS: Mention the improvement.
---
 NEWS                       | 6 +++---
 src/du.c                   | 4 +++-
 tests/misc/write-errors.sh | 1 +
 3 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/NEWS b/NEWS
index a3239537e..63db27dee 100644
--- a/NEWS
+++ b/NEWS
@@ -78,6 +78,9 @@ GNU coreutils NEWS                                    -*- outline -*-
   'du' now processes directories with 10,000 or more entries up to 9 times
   faster on the Lustre file system.
 
+  'du', and 'wc' now exit promptly upon receiving a write error,
+  which is significant when processing many input files.
+
   'pinky' will now exit immediately upon receiving a write error, which is
   significant when reading large plan or project files.
 
@@ -88,9 +91,6 @@ GNU coreutils NEWS                                    -*- outline -*-
   'timeout' on Linux will always terminate the child in the case where the
   timeout process itself dies, like when it receives a KILL signal for example.
 
-  'wc' now exits promptly upon receiving a write error,
-  which is significant when processing many input files.
-
 ** Build-related
 
   'kill' and 'uptime' are no longer built by default.  These programs can be
diff --git a/src/du.c b/src/du.c
index 1565c9078..a38c96174 100644
--- a/src/du.c
+++ b/src/du.c
@@ -406,7 +406,9 @@ print_size (const struct duinfo *pdui, char const *string)
         }
     }
   printf ("\t%s%c", string, opt_nul_terminate_output ? '\0' : '\n');
-  fflush (stdout);
+  if (fflush (stdout) != 0)
+    write_error ();
+
 }
 
 /* Fill the di_mnt set with local mount point dev/ino pairs.  */
diff --git a/tests/misc/write-errors.sh b/tests/misc/write-errors.sh
index a84cb8d7a..719b17d68 100755
--- a/tests/misc/write-errors.sh
+++ b/tests/misc/write-errors.sh
@@ -34,6 +34,7 @@ cut -z -f1- /dev/zero
 date +%${OFF64_T_MAX}c
 date --version; yes 0 | date -f-
 dd if=/dev/zero
+du --version; yes /dev/null | tr '\\\\n' '\\\\0' | du -l --files0-from=-
 expand /dev/zero
 factor --version; yes 1 | factor
 fmt /dev/zero
-- 
2.52.0

From 621a785261ed58c86a189d771213603bba861f4d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <[email protected]>
Date: Fri, 9 Jan 2026 14:25:24 +0000
Subject: [PATCH 3/3] maint: remove redundant processing in a test

* tests/misc/write-errors.sh: This was a no-op anyway
due to inadequate escaping.  Also document the escaping requirement.
---
 tests/misc/write-errors.sh | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/tests/misc/write-errors.sh b/tests/misc/write-errors.sh
index 719b17d68..e335e52d6 100755
--- a/tests/misc/write-errors.sh
+++ b/tests/misc/write-errors.sh
@@ -24,8 +24,9 @@ if ! test -w /dev/full || ! test -c /dev/full; then
   skip_ '/dev/full is required'
 fi
 
-# Writers that may output data indefinitely
-# First word in command line is checked against built programs
+# Writers that may output data indefinitely.
+# First word in command line is checked against built programs.
+# Escapes must be double escaped.
 printf '%s' "\
 cat /dev/zero
 comm -z /dev/zero /dev/zero
@@ -42,7 +43,7 @@ fmt --version; yes | fmt
 fold /dev/zero
 fold -b /dev/zero
 fold -c /dev/zero
-fold --version; yes | tr -d '\\n' | fold
+fold --version; yes | fold
 head -z -n-1 /dev/zero
 join -a 1 -z /dev/zero /dev/null
 nl --version; yes | nl
-- 
2.52.0

Reply via email to