Zach van Rijn wrote in
<https://lists.gnu.org/archive/html/bug-gnulib/2023-04/msg00072.html>:
> I am investigating a possible regression in gnulib that manifests
> as a behavioral change in coreutils `pwd` on some Linux platforms.
> 
> Comparing coreutils 9.1 and 9.2 release tarballs, freshly built:
> 
> $ x=$(pwd) && (cd //tmp && for k in 9.1 9.2; do echo -n $k:\  && 
> ${x}/coreutils-$k/src/pwd; done)
> 9.1: /tmp
> 9.2: //tmp

The reproducer is not only

   $ (cd //bin && /coreutils/src/pwd)

but also

   $ (cd /bin && /coreutils/src/pwd)

Using /bin instead of /tmp is a better reproducer, because e.g. on Alpine Linux
/tmp is a mount point, and the getcwd code treats mount points specially
(see lib/getcwd.c line 310).

> The coreutils commit that updated gnulib is:
> 
>     d42e4e9191abb818fc0e28507085a3945eed1477
> 
> The gnulib commit that caused this behavioral change is:
> 
>     356a414e8c15ef3f8cc7b7157427c8ce9a9f7c1b
> 
> More information:
> 
>     https://git.adelielinux.org/adelie/packages/-/issues/987

I reproduce it only on Adelie Linux, not on Alpine Linux or Ubuntu, for example,
because in config.h:

  * On Adelie Linux
    /* #undef HAVE_MINIMALLY_WORKING_GETCWD */
    /* #undef HAVE_PARTLY_WORKING_GETCWD */

  * On Ubuntu
    #define HAVE_MINIMALLY_WORKING_GETCWD 1
    /* #undef HAVE_PARTLY_WORKING_GETCWD */

  * On Alpine Linux
    #define HAVE_MINIMALLY_WORKING_GETCWD 1
    #define HAVE_PARTLY_WORKING_GETCWD 1

Also, it is reproducible only if /proc is mounted.

An strace log looks like this:

execve("/home/bruno/coreutils-9.5/src/pwd", 
["/home/bruno/coreutils-9.5/src/pw"...], 0x7ffdf3982670 /* 19 vars */) = 0
arch_prctl(ARCH_SET_FS, 0x7f1946be2a88) = 0
set_tid_address(0x7f1946be3034)         = 19425
brk(NULL)                               = 0x55c1f29d7000
brk(0x55c1f29d9000)                     = 0x55c1f29d9000
mmap(0x55c1f29d7000, 4096, PROT_NONE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 
0) = 0x55c1f29d7000
mprotect(0x7f1946bdf000, 4096, PROT_READ) = 0
mprotect(0x55c1f28d9000, 4096, PROT_READ) = 0
mmap(NULL, 16384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 
0x7f1946b29000
lstat(".", {st_mode=S_IFDIR|0755, st_size=12288, ...}) = 0
lstat("/", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
openat(AT_FDCWD, "..", O_RDONLY|O_LARGEFILE) = 3
fstat(3, {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
fstat(3, {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
fcntl(3, F_GETFL)                       = 0x8000 (flags O_RDONLY|O_LARGEFILE)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 
0x7f1946b27000
fcntl(3, F_SETFD, FD_CLOEXEC)           = 0
getdents64(3, 0x7f1946b27048 /* 21 entries */, 2048) = 520
newfstatat(3, "bin", {st_mode=S_IFDIR|0755, st_size=12288, ...}, 
AT_SYMLINK_NOFOLLOW) = 0
readlink("/proc/self/fd/3", "/", 4096)  = 1
close(3)                                = 0
munmap(0x7f1946b27000, 8192)            = 0
munmap(0x7f1946b29000, 16384)           = 0
ioctl(1, TIOCGWINSZ, {ws_row=37, ws_col=100, ws_xpixel=0, ws_ypixel=400}) = 0
writev(1, [{iov_base="//bin", iov_len=5}, {iov_base="\n", iov_len=1}], 2) = 6
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

So, the readlink on the root directory returns "/", not "//". The double
slash comes from not-so-careful string concatenation.

This patch fixes it.


2024-12-31  Bruno Haible  <br...@clisp.org>

        getcwd: Return "/bin" instead of "//bin" on Adélie Linux.
        Reported by Zach van Rijn <m...@zv.io> in
        <https://lists.gnu.org/archive/html/bug-gnulib/2023-04/msg00072.html>
        and <https://git.adelielinux.org/adelie/packages/-/issues/987>.
        * lib/getcwd.c (__getcwd_generic): Remove a trailing slash from the
        result of readlink().

diff --git a/lib/getcwd.c b/lib/getcwd.c
index 214950a144..d637b4e576 100644
--- a/lib/getcwd.c
+++ b/lib/getcwd.c
@@ -478,6 +478,12 @@ __getcwd_generic (char *buf, size_t size)
             }
           else
             {
+              /* *dirp is already a '/' (see above).  Therefore if the 
directory
+                 ends in a slash, we can remove that slash.  This happens in
+                 particular if the directory is "/".  */
+              if (linklen > 0 && linkbuf[linklen - 1] == '/')
+                linklen--;
+
               dirroom = dirp - dir;
               if (dirroom < linklen)
                 {




Reply via email to