Collin Funk <collin.fu...@gmail.com> writes:

> Pádraig Brady <p...@draigbrady.com> writes:
>
>>> Done, v2 attached.
>>
>> Looks good.
>
> Pushed, thanks to you and Dmitry for the review.
>
> Leaving this bug open for realpath. I'll have a look at that as well,
> just requires some more reading than readlink did. :)

It looks like 'realpath -E' is the same as Coreutils 'readlink -f', and
default behavior of Coreutils 'realpath'.  POSIX us to keep our default
behavior [1]:

    If no options are specified, realpath shall canonicalize the specified
    pathname in an unspecified manner such that the resulting absolute
    pathname does not contain any components that refer to files of type
    symbolic link and does not contain any components that are dot or
    dot-dot.

Here is a proposed patch.

I don't love having 'realpath -E' and 'readlink -f' with the same long
option name, but having the same functionality with a different long
option name seems worse.

Collin

[1] https://pubs.opengroup.org/onlinepubs/9799919799/utilities/realpath.html

>From 9e8803c4552ef03aa25c48df90efba59670782c8 Mon Sep 17 00:00:00 2001
Message-ID: <9e8803c4552ef03aa25c48df90efba59670782c8.1754787534.git.collin.fu...@gmail.com>
From: Collin Funk <collin.fu...@gmail.com>
Date: Sat, 9 Aug 2025 17:53:29 -0700
Subject: [PATCH] realpath: support the -E option required by POSIX

* src/realpath.c (longopts): Add the option.
(main): Likewise.
(usage): Add the option to the --help message.
* tests/misc/realpath.sh: Add a simple test.
* doc/coreutils.texi (realpath invocation): Mention the new option.
* NEWS: Likewise.
---
 NEWS                   |  4 ++++
 doc/coreutils.texi     | 14 ++++++++++++--
 src/realpath.c         | 16 ++++++++++++----
 tests/misc/realpath.sh |  3 +++
 4 files changed, 31 insertions(+), 6 deletions(-)

diff --git a/NEWS b/NEWS
index f80d0150e..e4575d0db 100644
--- a/NEWS
+++ b/NEWS
@@ -80,6 +80,10 @@ GNU coreutils NEWS                                    -*- outline -*-
   basenc supports the --base58 option to encode and decode
   the visually unambiguous Base58 encoding.
 
+  realpath supports the -E option as required by POSIX.  The behavior is
+  the same as realpath with no options.  The corresponding long option
+  is --canonicalize.
+
 ** Improvements
 
   'factor' is now much faster at identifying large prime numbers,
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 19de577e5..766c27033 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -15002,8 +15002,7 @@ @node realpath invocation
 @findex realpath
 
 @command{realpath} expands all symbolic links and resolves references to
-@samp{/./}, @samp{/../} and extra @samp{/} characters.  By default,
-all but the last component of the specified files must exist.  Synopsis:
+@samp{/./}, @samp{/../} and extra @samp{/} characters.  Synopsis:
 
 @example
 realpath [@var{option}]@dots{} @var{file}@dots{}
@@ -15018,6 +15017,17 @@ @node realpath invocation
 
 @table @samp
 
+@item -E
+@itemx --canonicalize
+@opindex -E
+@opindex --canonicalize
+
+Ensure all but the last component of the specified file name exist.
+Otherwise, @command{realpath} will output a diagnostic unless the
+@option{-q} option is specified, and exit with a nonzero exit code.  A
+trailing slash is ignored.  This option is the default behavior, but is
+included for POSIX compatibility.
+
 @item -e
 @itemx --canonicalize-existing
 @opindex -e
diff --git a/src/realpath.c b/src/realpath.c
index 4bc00edc2..b06e5e845 100644
--- a/src/realpath.c
+++ b/src/realpath.c
@@ -44,6 +44,7 @@ static char const *can_relative_base;
 
 static struct option const longopts[] =
 {
+  {"canonicalize", no_argument, nullptr, 'E'},
   {"canonicalize-existing", no_argument, nullptr, 'e'},
   {"canonicalize-missing", no_argument, nullptr, 'm'},
   {"relative-to", required_argument, nullptr, RELATIVE_TO_OPTION},
@@ -68,11 +69,14 @@ usage (int status)
     {
       printf (_("Usage: %s [OPTION]... FILE...\n"), program_name);
       fputs (_("\
-Print the resolved absolute file name;\n\
-all but the last component must exist\n\
-\n\
+Print the resolved absolute file name.\n\
 "), stdout);
       fputs (_("\
+  -E, --canonicalize           canonicalize by following every symlink in\n\
+                               every component of the given name recursively;\
+\n\
+                               all but the last component must exist\n\
+                               (this is the default)\n\
   -e, --canonicalize-existing  all components of the path must exist\n\
   -m, --canonicalize-missing   no path components need exist or be a directory\
 \n\
@@ -186,11 +190,15 @@ main (int argc, char **argv)
 
   while (true)
     {
-      int c = getopt_long (argc, argv, "eLmPqsz", longopts, nullptr);
+      int c = getopt_long (argc, argv, "EeLmPqsz", longopts, nullptr);
       if (c == -1)
         break;
       switch (c)
         {
+        case 'E':
+          can_mode &= ~CAN_MODE_MASK;
+          can_mode |= CAN_ALL_BUT_LAST;
+          break;
         case 'e':
           can_mode &= ~CAN_MODE_MASK;
           can_mode |= CAN_EXISTING;
diff --git a/tests/misc/realpath.sh b/tests/misc/realpath.sh
index 2356cac58..3ba9bd36c 100755
--- a/tests/misc/realpath.sh
+++ b/tests/misc/realpath.sh
@@ -50,6 +50,9 @@ returns_ 1 realpath --relative-base . || fail=1
 returns_ 1 realpath -e --relative-to=dir1/f --relative-base=. . || fail=1
 realpath -e --relative-to=dir1/  --relative-base=. . || fail=1
 
+# Check that using -E after -e uses -E as specified by POSIX.
+realpath -e -E --relative-to=dir1/f --relative-base=. . || fail=1
+
 # Note NUL params are unconditionally rejected by canonicalize_filename_mode
 returns_ 1 realpath -m '' || fail=1
 returns_ 1 realpath --relative-base= --relative-to=. . || fail=1
-- 
2.50.1

Reply via email to