bug#62385: POSIX behavior of readlink, realpath

Collin Funk <[email protected]>
Newsgroups gmane.comp.gnu.core-utils.bugs
Message-ID <[email protected]>
Hi Eric,

You said:

> Among other things, I can see the following changes that coreutils
> will need to make to become compliant, or else we need to push back on
> the POSIX folks if we have strong reasons to complain that their
> specification will break things:
>
> POSIX wants 'readlink non-symlink' to output a diagnostic; that is, it
> looks like POSIX wants us to behave like '-v' is enabled by default
> (our current behavior of -q by default will be non-compliant).

I have attached a patch that I think is correct for readlink.

It is a bit more complex than just behaving the same as 'readlink -v'
previously would. This is because the behavior of non-POSIX
options. Here are some commands:

    $ touch file
    $ ln -s symlink file
    
    # Example 1
    $ readlink -v file
    readlink: file: Invalid argument

    # Example 2
    $ readlink -v -f file 
    /home/collin/file

If 'readlink -v' where 'POSIXLY_CORRECT=1 readlink' then example 1 would
conform. I think that example 2 would violate POSIX.

So, when POSIXLY_CORRECT is enabled we can call 'readlink', check for
errors, and then canonicalize if it is successful.

Collin
0001-readlink-emit-errors-when-POSIXLY_CORRECT-is-set.patch (text/x-patch, 5.9 KB)
From 49d32775db13eb3f272853eb1c7124fff17b93f8 Mon Sep 17 00:00:00 2001
Message-ID: <49d32775db13eb3f272853eb1c7124fff17b93f8.1754194901.git.collin.funk1@gmail.com>
From: Collin Funk <[email protected]>
Date: Sat, 2 Aug 2025 20:51:30 -0700
Subject: [PATCH] readlink: emit errors when POSIXLY_CORRECT is set

* src/readlink.c (main): Set verbose if the POSIXLY_CORRECT environment
variable is set. Check if the file is a symbolic link if POSIXLY_CORRECT
is set.
* tests/readlink/readlink-posix.sh: New file.
* tests/local.mk (all_tests): Add it.
* NEWS: Mention the change.
* doc/coreutils.texi (readlink invocation): Document the behavior of
POSIXLY_CORRECT.
---
 NEWS                             |  6 ++++
 doc/coreutils.texi               |  5 ++++
 src/readlink.c                   | 24 ++++++++++++++--
 tests/local.mk                   |  1 +
 tests/readlink/readlink-posix.sh | 48 ++++++++++++++++++++++++++++++++
 5 files changed, 81 insertions(+), 3 deletions(-)
 create mode 100755 tests/readlink/readlink-posix.sh

diff --git a/NEWS b/NEWS
index 110e688b1..e79067bb4 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,12 @@ GNU coreutils NEWS                                    -*- outline -*-
   'factor' is now much faster at identifying large prime numbers,
   and significantly faster on composite numbers greater than 2^128.
 
+** New Features
+
+  readlink will print a diagnostic message to standard error and exit
+  with a non-zero status when given a file that is not a symbolic link
+  if the POSIXLY_CORRECT environment variable is defined.
+
 ** Bug fixes
 
   cksum was not compilable by Apple LLVM 10.0.0 x86-64, which
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 40ecf3126..15a231bc3 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -11474,6 +11474,11 @@ @node readlink invocation
 
 @end table
 
+@vindex POSIXLY_CORRECT
+If the @env{POSIXLY_CORRECT} environment variable is set and
+@var{file} is not a symbolic link, @command{readlink} will emit a
+diagnostic message to standard error and exit with a nonzero status.
+
 The @command{readlink} utility first appeared in OpenBSD 2.1.
 
 The @command{realpath} command without options, operates like
diff --git a/src/readlink.c b/src/readlink.c
index 44def1dbb..4586e5820 100644
--- a/src/readlink.c
+++ b/src/readlink.c
@@ -97,6 +97,7 @@ main (int argc, char **argv)
   int status = EXIT_SUCCESS;
   int optc;
   bool use_nuls = false;
+  bool posixly_correct = (getenv ("POSIXLY_CORRECT") != nullptr);
 
   initialize_main (&argc, &argv);
   set_program_name (argv[0]);
@@ -152,12 +153,29 @@ main (int argc, char **argv)
       no_newline = false;
     }
 
+  /* POSIX requires a diagnostic message written to standard error and a
+     non-zero exit status when given a file that is not a symbolic link.  */
+  if (posixly_correct)
+    verbose = true;
+
   for (; optind < argc; ++optind)
     {
       char const *fname = argv[optind];
-      char *value = (can_mode != -1
-                     ? canonicalize_filename_mode (fname, can_mode)
-                     : areadlink_with_size (fname, 63));
+      char *value;
+
+      if (! posixly_correct)
+        value = (can_mode != -1
+                 ? canonicalize_filename_mode (fname, can_mode)
+                 : areadlink_with_size (fname, 63));
+      else
+        {
+          /* We have to check if the file is a symbolic link before
+             canonicalizing.  */
+          value = areadlink_with_size (fname, 63);
+          if (value && can_mode != -1)
+            value = canonicalize_filename_mode (value, can_mode);
+        }
+
       if (value)
         {
           fputs (value, stdout);
diff --git a/tests/local.mk b/tests/local.mk
index 6b527f108..7364ec89f 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -365,6 +365,7 @@ all_tests =					\
   tests/printf/printf-quote.sh			\
   tests/pwd/pwd-long.sh				\
   tests/readlink/readlink-fp-loop.sh		\
+  tests/readlink/readlink-posix.sh		\
   tests/readlink/readlink-root.sh		\
   tests/misc/realpath.sh			\
   tests/runcon/runcon-compute.sh		\
diff --git a/tests/readlink/readlink-posix.sh b/tests/readlink/readlink-posix.sh
new file mode 100755
index 000000000..a301545cf
--- /dev/null
+++ b/tests/readlink/readlink-posix.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+# Test readlink with POSIXLY_CORRECT defined.
+
+# Copyright (C) 2025 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ readlink
+
+touch file || framework_failure_
+ln -s file link1 || framework_failure_
+
+# POSIX requires a diagnostic error and non-zero exit status if the file is not
+# a symbolic link.
+cat <<\EOF > exp || framework_failure_
+readlink: file: Invalid argument
+EOF
+returns_ 1 env POSIXLY_CORRECT=1 readlink file 2>err || fail=1
+compare exp err || fail=1
+returns_ 1 env POSIXLY_CORRECT=1 readlink -f file 2>err || fail=1
+compare exp err || fail=1
+returns_ 1 env POSIXLY_CORRECT=1 readlink -e file 2>err || fail=1
+compare exp err || fail=1
+returns_ 1 env POSIXLY_CORRECT=1 readlink -m file 2>err || fail=1
+
+# Check on a symbolic link.
+cat <<\EOF > exp || framework_failure_
+file
+EOF
+POSIXLY_CORRECT=1 readlink link1 >out || fail=1
+compare exp out || fail=1
+POSIXLY_CORRECT=1 readlink -f link1 || fail=1
+POSIXLY_CORRECT=1 readlink -e link1 || fail=1
+POSIXLY_CORRECT=1 readlink -m link1 || fail=1
+
+Exit $fail
-- 
2.50.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.