From: Collin Funk Date: Fri, 9 Jan 2026 06:52:33 +0000 (-0800) Subject: readlink,realpath: promptly diagnose write errors X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b34e329f75c10e47d8a539075a4c6ca09277d401;p=thirdparty%2Fcoreutils.git readlink,realpath: promptly diagnose write errors 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 * src/readlink.c (main): Check if standard output has it's error flag set after printing a file name. * src/realpath.c (process_path): Likewise. * NEWS: Mention the improvement. --- diff --git a/NEWS b/NEWS index 822146f985..da733cbd15 100644 --- a/NEWS +++ b/NEWS @@ -81,6 +81,10 @@ GNU coreutils NEWS -*- outline -*- 'pinky' will now exit immediately upon receiving a write error, which is significant when reading large plan or project files. + 'readlink' and 'realpath' will now exit promptly upon receiving a write error, + which is significant when canonicalizing multiple file names longer than + PATH_MAX. + '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. diff --git a/src/readlink.c b/src/readlink.c index 23f9d3b837..498826882c 100644 --- a/src/readlink.c +++ b/src/readlink.c @@ -171,6 +171,8 @@ main (int argc, char **argv) if (! no_newline) putchar (use_nuls ? '\0' : '\n'); free (value); + if (ferror (stdout)) + write_error (); } else { diff --git a/src/realpath.c b/src/realpath.c index 24689a6241..d6c3864890 100644 --- a/src/realpath.c +++ b/src/realpath.c @@ -166,6 +166,9 @@ process_path (char const *fname, int can_mode) free (can_fname); + if (ferror (stdout)) + write_error (); + return true; }