Re: [patch] nftw(): clarify dangling symlinks case

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi DJ, Carlos,

Below is a copy of my response to the Red Hat bug report at
https://bugzilla.redhat.com/show_bug.cgi?id=1422736

On 22 February 2017 at 18:11, Carlos O'Donell <carlos@xxxxxxxxxx> wrote:
> On 02/21/2017 08:06 PM, DJ Delorie wrote:
>> In the event that nftw() encounters a symbolic link which refers to a
>> nonexisting file, the "struct stat" info will not have been filled in
>> if FTW_PHYS is not specified (because stat() is used instead of
>> lstat()).  This patch clarifies the documentation and corrects the
>> sample code to reflect this.
>>
>> Reported to Fedora in https://bugzilla.redhat.com/show_bug.cgi?id=1422736
>>
>> patch is against today's git
>
> Looks good to me. Thanks for fixing the man page.
>
> --
> Cheers,
> Carlos.

(In reply to Carlos O'Donell from comment #7)
> (In reply to han pingtian from comment #5)
> > (In reply to DJ Delorie from comment #4)
> > > I've reproduced what you see.
> > >
> > > While it's not explicit in the nftw() man page, if you don't call nftw with
> > > FTW_PHYS, it only calls stat() on dangling symlinks, which of course fails.
> > > It returns FTW_SLN for those cases, and the stat data passed is not
> > > initialized.
> > >
> > If it knew it's an dangling symlink, why not returns the stat data of the
> > symblink itself. After all, it has known it's a dangling symblink.

((Yes.)

> Without FTW_PHYS the symlink _must_ be followed,

Why? (See below.)

> and if the symlink target
> doesn't exist then there is no stat-related data to be returned. If you want
> a mixed hybrid API somewhere between FTW_PHYS and not-FTW_PHYS then this
> needs to be discussed usptream. I think it's easy enough to just lstat()
> yourself on the broken symlink if that's what you need. Requiring the
> implementation to do the extra stat() will slow down performance for
> everyone that doesn't need and didn't ask for that information.
>
> In summary, having DJ adjust the documentation is the best way forward.

When I received the man-pages patch, I very nearly applied it, but
something niggled... (I have not applied the man-pages patch.)

I believe this is actually a (very longstanding) glibc bug. Here is
what POSIX says for nftw():

           FTW_NS    The  stat() function failed on the object because of
                     lack of  appropriate  permission.  The  stat  buffer
                     passed to fn is undefined. Failure of stat() for any
                     other reason is considered an error and nftw() shall
                     return −1.

           ....

           FTW_SLN   The  object is a symbolic link that does not name an
                     existing file.  (This condition shall only occur  if
                     the FTW_PHYS flag is not included in flags.)

Note that POSIX explicitly says that the stat buffer is undefined for
FTW_NS, but makes no such statement for FTW_SLN, with the implication
that the stat buffer is valid in this case.

This implies that FTW_SLN should work as Han Pingtian suggested: for a
dangling symlink, the lstat() information on the link should be
returned. This is certainly how I always understood things should
work. (But, obviously, I never tested this on glibc.)

So, what do other implementations do? Every other implementation that
I looked at, does return the lstat() information for the dangling
symlink. I looked at Solaris, OpenBSD, FreeBSD, and musl.  All of this
strongly suggests that glibc got it wrong.

For the points below, I used the following test program (and yes, I
realize by now that the FTW_NS treatment in this code is not correct;
I've fixed the man page already).

8x---8x---8x---8x---8x---8x---8x---8x---8x---8x---8x---8x---
/*#* t_nftw.c

   Copyright Michael Kerrisk 2000

   Demonstrate the use of the nftw(3) function.
*/
#define _GNU_SOURCE
#define _XOPEN_SOURCE 500
#include <ftw.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


static int
displayFileInfo(const char *fpath, const struct stat *sb,
            int tflag, struct FTW *ftwbuf)
{
    printf("%-3s %2d %7lld   %-30s %d %s (st_ino: %ld)\n",
        (tflag == FTW_D) ?   "d"   : (tflag == FTW_DNR) ? "dnr" :
    (tflag == FTW_DP) ?  "dp"  : (tflag == FTW_F) ?   "f" :
    (tflag == FTW_NS) ?  "ns"  : (tflag == FTW_SL) ?  "sl" :
    (tflag == FTW_SLN) ? "sln" : "???",
    ftwbuf->level, (long long) sb->st_size,
    fpath, ftwbuf->base, fpath + ftwbuf->base, (long) sb->st_ino);
    memset((void *) sb, 0, sizeof(struct stat));
    return 0;          /* To tell nftw() to continue */
}


int
main(int argc, char *argv[])
{
    int flags = 0;

    if (argc > 2 && strchr(argv[2], 'd') != NULL)
    flags |= FTW_DEPTH;
    if (argc > 2 && strchr(argv[2], 'p') != NULL)
    flags |= FTW_PHYS;

    if (nftw((argc < 2) ? "." : argv[1], displayFileInfo,
        20, flags) == -1) {
    perror("nftw");
    exit(EXIT_FAILURE);
    }

    exit(EXIT_SUCCESS);
}
8x---8x---8x---8x---8x---8x---8x---8x---8x---8x---8x---8x---


Solaris (Illumos source code)
usr/src/lib/libc/port/gen/nftw.c:

The following code causes the stat buffer to be populated with lstat()
infor in the FTW_SLN case:

        } else {
                /*
                 * Statf has failed. If stat was used instead of lstat,
                 * try using lstat. If lstat doesn't fail, "comp"
                 * must be a symbolic link pointing to a non-existent
                 * file. Such a symbolic link should be ignored.
                 * Also check the file type, if possible, for symbolic
                 * link.
                 */
                if ((vp->statf == stat) && (lstat(comp, &statb) >= 0) &&
                    ((statb.st_mode & S_IFMT) == S_IFLNK)) {

                        /*
                         * Ignore bad symbolic link, let "fn"
                         * report it.
                         */

                        errno = ENOENT;
                        type = FTW_SLN;
                } else {
                        type = FTW_NS;
        fail:


Testing shows that the link info *is* returned in the stat structure:

$ ls -li t
total 4
     45068 -rw-r--r--   1 mtk     csw      29 Feb 24 04:28 f
     45067 lrwxrwxrwx   1 mtk     csw       6 Feb 24 04:28 my_sln -> ssssss
     45069 lrwxrwxrwx   1 mtk     csw       1 Feb 24 04:28 sl_f -> f

$ ./a.out t
d    0       5   t                              0 t (st_ino: 45066)
sln  1       6   t/my_sln                       2 my_sln (st_ino: 45067)
f    1      29   t/f                            2 f (st_ino: 45068)
f    1      29   t/sl_f                         2 sl_f (st_ino: 45068)

======

OpenBSD

I didn't look at the source code, but the test gives the same results
as Solaris:

-bash-4.3$ ls -li
total 4
4693795 -rw-r--r--  1 mtk  mtk  29 Feb 24 04:37 f
4693796 lrwxr-xr-x  1 mtk  mtk   1 Feb 24 04:37 sl_f -> f
4693797 lrwxr-xr-x  1 mtk  mtk  11 Feb 24 04:37 sln -> jajdhfdskjh
-bash-4.3$ ./a.out t
d    0     512   t                              0 t (st_ino: 4693794)
f    1      29   t/f                            2 f (st_ino: 4693795)
f    1      29   t/sl_f                         2 sl_f (st_ino: 4693795)
sln  1      11   t/sln                          2 sln (st_ino: 4693797)

=====

FreeBSD

I don't have access to a FreeBSD test system at the moment, nut my
reading of the source code is that id delivers the same results as
Solaris and OpenBSD

See lib/libc/gen/nftw.c, where FTS_SLN is implemented using the
FTS_SLNONE option, and fts(3) on that system says:

              FTS_SLNONE  A symbolic link with a nonexistent target.  The
                          contents  of  the fts_statp field reference the
                          file characteristic information  for  the  sym‐
                          bolic link itself.

=====
musl libc

src/misc/nftw.c

        if ((flags & FTW_PHYS) ? lstat(path, &st) : stat(path, &st) < 0) {
                if (!(flags & FTW_PHYS) && errno==ENOENT && !lstat(path, &st))
                        type = FTW_SLN;
                else if (errno != EACCES) return -1;
                else type = FTW_NS;
        } else if (S_ISDIR(st.st_mode)) {
                if (access(path, R_OK) < 0) type = FTW_DNR;
                else if (flags & FTW_DEPTH) type = FTW_DP;
                else type = FTW_D;
        } else if (S_ISLNK(st.st_mode)) {
                if (flags & FTW_PHYS) type = FTW_SL;
                else type = FTW_SLN;
        } else {
                type = FTW_F;
        }


Cheers,

Michael

-- 
Michael Kerrisk
Linux man-pages maintainer; http://www.kernel.org/doc/man-pages/
Linux/UNIX System Programming Training: http://man7.org/training/
--
To unsubscribe from this list: send the line "unsubscribe linux-man" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Kernel Documentation]     [Netdev]     [Linux Ethernet Bridging]     [Linux Wireless]     [Kernel Newbies]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux