Subject: bug#63931: ls colors one symlink too much as non-broken in symlink chain

From: Pádraig Brady <[email-masked]>
Date: Tue, 6 Jun 2023 21:32:43 +0100

On 06/06/2023 18:24, Martin Schulte wrote:
> Hello coreutils-maintainers,
> 
> I create a long chain of symlinks - ls colors the 41st element as ok while the kernel already gives up after 40 symlinks:
> 
> $ uname -a
> Linux martnix4 5.10.0-23-amd64 #1 SMP Debian 5.10.179-1 (2023-05-12) x86_64 GNU/Linux
> $ ls --version | head -n 1
> ls (GNU coreutils) 8.32
> $ mkdir empty
> $ cd empty
> $ last=00 ; touch $last ; for ln in {01..50}; do ln -s $last $ln; last=$ln; done
> $ ls --color -l {38..42}
> lrwxrwxrwx 1 u g 2 Jun  6 19:00 38 -> 37   <- 38 colored as symlink
> lrwxrwxrwx 1 u g 2 Jun  6 19:00 39 -> 38   <- 39 colored as symlink
> lrwxrwxrwx 1 u g 2 Jun  6 19:00 40 -> 39   <- 40 colored as symlink
> lrwxrwxrwx 1 u g 2 Jun  6 19:00 41 -> 40   <- 41 colored as symlink
> lrwxrwxrwx 1 u g 2 Jun  6 19:00 42 -> 41   <- 42 and 41 colored as broken link
> $ cat 40
> $ cat 41
> cat: 41: Too many levels of symbolic links
> 
> This could be reproduced with "ls (GNU coreutils) 9.3.45-6a618" compiled from the git repository.
> 
> Best regards,

This is because we lstat() the link, and stat() the target,
I suppose as a small optimization in the normal case.
The following adjusts to stat the link instead,
which works for your case and passes existing tests.

I'll think a bit more about it and add a test before applying.

cheers,
Pádraig

diff --git a/src/ls.c b/src/ls.c
index 71d94fd6a..77be54248 100644
--- a/src/ls.c
+++ b/src/ls.c
@@ -3586,7 +3586,7 @@ gobble_file (char const *name, enum filetype type, ino_t inode,
               they won't be traced and when no indicator is needed.  */
            if (linkname
                && (file_type <= indicator_style || check_symlink_mode)
-              && stat_for_mode (linkname, &linkstats) == 0)
+              && stat_for_mode (full_name, &linkstats) == 0)
              {
                f->linkok = true;
                f->linkmode = linkstats.st_mode;