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.
---
NEWS | 4 ++++
src/readlink.c | 2 ++
src/realpath.c | 3 +++
3 files changed, 9 insertions(+)
diff --git a/NEWS b/NEWS
index 822146f98..da733cbd1 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 23f9d3b83..498826882 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 24689a624..d6c386489 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;
}
--
2.52.0