]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - misc-utils/namei.c
Merge branch '2018wk48' of https://github.com/kerolasa/util-linux
[thirdparty/util-linux.git] / misc-utils / namei.c
index 948067537f06b2451572a35318d612df25a34e2f..60171f60028225f3b616ad6145ba7f0884413254 100644 (file)
-/*-------------------------------------------------------------
-
-The namei program
-
-By: Roger S. Southwick
-
-May 2, 1990
-
-
-Modifications by Steve Tell  March 28, 1991
-
-usage: namei pathname [pathname ... ]
-
-This program reads it's arguments as pathnames to any type
-of Unix file (symlinks, files, directories, and so forth). 
-The program then follows each pathname until a terminal 
-point is found (a file, directory, char device, etc).
-If it finds a symbolic link, we show the link, and start
-following it, indenting the output to show the context.
-
-This program is useful for finding a "too many levels of
-symbolic links" problems.
-
-For each line output, the program puts a file type first:
-
-   f: = the pathname we are currently trying to resolve
-    d = directory
-    D = directory that is a mount point
-    l = symbolic link (both the link and it's contents are output)
-    s = socket
-    b = block device
-    c = character device
-    p = FIFO (named pipe)
-    - = regular file
-    ? = an error of some kind
-
-The program prints an informative messages when we exceed
-the maximum number of symbolic links this system can have.
-
-The program exits with a 1 status ONLY if it finds it cannot
-chdir to /,  or if it encounters an unknown file type.
-
-1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL>
-- added Native Language Support
-
-2006-12-15 Karel Zak <kzak@redhat.com>
-- fixed logic; don't follow the path if a component is not directory
-- fixed infinite loop of symbolic links; stack size is very limited
-
-2007-09-10 Li Zefan <lizf@cn.fujitsu.com>
-- added to identify FIFO
-
--------------------------------------------------------------*/
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file is part of util-linux.
+ *
+ * This file 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This file 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.
+ *
+ * The original namei(1) was written by:
+ *     Roger S. Southwick (May 2, 1990)
+ *     Steve Tell (March 28, 1991)
+ *     Arkadiusz Miśkiewicz (1999-02-22)
+ *     Li Zefan (2007-09-10).
+ */
 
 #include <stdio.h>
 #include <unistd.h>
+#include <getopt.h>
 #include <string.h>
-#include <strings.h>
 #include <stdlib.h>
 #include <errno.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <sys/param.h>
-#include "nls.h"
-
-#define ERR    strerror(errno),errno
+#include <pwd.h>
+#include <grp.h>
 
-int symcount;
-int mflag = 0;
-int xflag = 0;
+#include "c.h"
+#include "xalloc.h"
+#include "nls.h"
+#include "widechar.h"
+#include "strutils.h"
+#include "closestream.h"
+#include "idcache.h"
 
 #ifndef MAXSYMLINKS
 #define MAXSYMLINKS 256
 #endif
 
-static char *pperm(unsigned short);
-static void namei(char *, int, mode_t *);
-static void usage(void);
+#define NAMEI_NOLINKS  (1 << 1)
+#define NAMEI_MODES    (1 << 2)
+#define NAMEI_MNTS     (1 << 3)
+#define NAMEI_OWNERS   (1 << 4)
+#define NAMEI_VERTICAL (1 << 5)
 
-int
-main(int argc, char **argv) {
-    extern int optind;
-    int c;
-    char curdir[MAXPATHLEN];
-
-    setlocale(LC_ALL, "");
-    bindtextdomain(PACKAGE, LOCALEDIR);
-    textdomain(PACKAGE);
-    
-    if(argc < 2)
-       usage();
-
-    while((c = getopt(argc, argv, "mx")) != -1){
-       switch(c){
-           case 'm':
-               mflag = !mflag;
-               break;
-           
-           case 'x':
-               xflag = !xflag;
-               break;
-
-           case '?':
-           default:
-               usage();
-       }
-    }
-
-    if(getcwd(curdir, sizeof(curdir)) == NULL){
-       (void)fprintf(stderr,
-                     _("namei: unable to get current directory - %s\n"),
-                     curdir);
-       exit(1);
-    }
-
-
-    for(; optind < argc; optind++){
-       mode_t lastmode = 0;
-       (void)printf("f: %s\n", argv[optind]);
-       symcount = 1;
-       namei(argv[optind], 0, &lastmode);
-
-       if(chdir(curdir) == -1){
-           (void)fprintf(stderr,
-                         _("namei: unable to chdir to %s - %s (%d)\n"),
-                         curdir, ERR);
-           exit(1);
+
+struct namei {
+       struct stat     st;             /* item lstat() */
+       char            *name;          /* item name */
+       char            *abslink;       /* absolute symlink path */
+       int             relstart;       /* offset of relative path in 'abslink' */
+       struct namei    *next;          /* next item */
+       int             level;
+       int             mountpoint;     /* is mount point */
+       int             noent;          /* this item not existing (stores errno from stat()) */
+};
+
+static int flags;
+static struct idcache *gcache; /* groupnames */
+static struct idcache *ucache; /* usernames */
+
+static void
+free_namei(struct namei *nm)
+{
+       while (nm) {
+               struct namei *next = nm->next;
+               free(nm->name);
+               free(nm->abslink);
+               free(nm);
+               nm = next;
        }
-    }
-    return 0;
 }
 
 static void
-usage(void) {
-    (void)fprintf(stderr,_("usage: namei [-mx] pathname [pathname ...]\n"));
-    exit(1);
+readlink_to_namei(struct namei *nm, const char *path)
+{
+       char sym[PATH_MAX];
+       ssize_t sz;
+       int isrel = 0;
+
+       sz = readlink(path, sym, sizeof(sym));
+       if (sz < 1)
+               err(EXIT_FAILURE, _("failed to read symlink: %s"), path);
+       if (*sym != '/') {
+               char *p = strrchr(path, '/');
+
+               if (p) {
+                       isrel = 1;
+                       nm->relstart = p - path;
+                       sz += nm->relstart + 1;
+               }
+       }
+       nm->abslink = xmalloc(sz + 1);
+
+       if (*sym != '/' && isrel) {
+               /* create the absolute path from the relative symlink */
+               memcpy(nm->abslink, path, nm->relstart);
+               *(nm->abslink + nm->relstart) = '/';
+               nm->relstart++;
+               memcpy(nm->abslink + nm->relstart, sym, sz - nm->relstart);
+       } else
+               /* - absolute link (foo -> /path/bar)
+                * - or link without any subdir (foo -> bar)
+                */
+               memcpy(nm->abslink, sym, sz);
+
+       nm->abslink[sz] = '\0';
 }
 
-#ifndef NODEV
-#define NODEV          (dev_t)(-1)
-#endif
+static struct stat *
+dotdot_stat(const char *dirname, struct stat *st)
+{
+       char *path;
+       size_t len;
 
-int kzak;
+#define DOTDOTDIR      "/.."
 
-static void
-namei(char *file, int lev, mode_t *lastmode) {
-    char *cp;
-    char buf[BUFSIZ], sym[BUFSIZ];
-    struct stat stb;
-    int i;
-    dev_t lastdev = NODEV;
-
-    /*
-     * See if the file has a leading /, and if so cd to root
-     */
-
-    if(file && *file == '/'){
-       while(*file == '/')
-           file++;
-
-       if(chdir("/") == -1){
-           (void)fprintf(stderr,_("namei: could not chdir to root!\n"));
-           exit(1);
-       }
-       for(i = 0; i < lev; i++)
-           (void)printf("  ");
+       if (!dirname)
+               return NULL;
 
-       if(stat("/", &stb) == -1){
-           (void)fprintf(stderr, _("namei: could not stat root!\n"));
-           exit(1);
-       }
-       lastdev = stb.st_dev;
+       len = strlen(dirname);
+       path = xmalloc(len + sizeof(DOTDOTDIR));
 
-       if(mflag)
-           (void)printf(" d%s /\n", pperm(stb.st_mode));
-       else
-           (void)printf(" d /\n");
-    }
+       memcpy(path, dirname, len);
+       memcpy(path + len, DOTDOTDIR, sizeof(DOTDOTDIR));
 
-    for(; file && *file;){
+       if (stat(path, st))
+               err(EXIT_FAILURE, _("stat of %s failed"), path);
+       free(path);
+       return st;
+}
 
-       if (strlen(file) >= BUFSIZ) {
-               fprintf(stderr,_("namei: buf overflow\n"));
-               return;
-       }
+static struct namei *
+new_namei(struct namei *parent, const char *path, const char *fname, int lev)
+{
+       struct namei *nm;
 
-       /*
-        * Copy up to the next / (or nil) into buf
-        */
-       
-       for(cp = buf; *file != '\0' && *file != '/'; cp++, file++)
-           *cp = *file;
-       
-       while(*file == '/')     /* eat extra /'s        */
-           file++;
+       if (!fname)
+               return NULL;
+       nm = xcalloc(1, sizeof(*nm));
+       if (parent)
+               parent->next = nm;
 
-       *cp = '\0';
+       nm->level = lev;
+       nm->name = xstrdup(fname);
 
-       if(buf[0] == '\0'){
+       if (lstat(path, &nm->st) != 0) {
+               nm->noent = errno;
+               return nm;
+       }
 
-           /*
-            * Buf is empty, so therefore we are done
-            * with this level of file
-            */
+       if (S_ISLNK(nm->st.st_mode))
+               readlink_to_namei(nm, path);
+       if (flags & NAMEI_OWNERS) {
+               add_uid(ucache, nm->st.st_uid);
+               add_gid(gcache, nm->st.st_gid);
+       }
 
-           return;
+       if ((flags & NAMEI_MNTS) && S_ISDIR(nm->st.st_mode)) {
+               struct stat stbuf, *sb = NULL;
+
+               if (parent && S_ISDIR(parent->st.st_mode))
+                       sb = &parent->st;
+               else if (!parent || S_ISLNK(parent->st.st_mode))
+                       sb = dotdot_stat(path, &stbuf);
+
+               if (sb && (sb->st_dev != nm->st.st_dev ||   /* different device */
+                          sb->st_ino == nm->st.st_ino))    /* root directory */
+                       nm->mountpoint = 1;
        }
 
-       for(i = 0; i < lev; i++)
-           (void)printf("  ");
-
-
-       /*
-        * We cannot walk on *path* if a previous element, in the path wasn't
-        * directory, because there could be a component with same name. Try:
-        *
-        * $ touch a b
-        * $ namei a/b    <-- "a" is not directory so namei shouldn't 
-        *                    check for "b"
-        */
-       if (*lastmode && S_ISDIR(*lastmode)==0 && S_ISLNK(*lastmode)==0){
-           (void)printf(" ? %s - %s (%d)\n", buf, strerror(ENOENT), ENOENT);
-           return;
+       return nm;
+}
+
+static struct namei *
+add_namei(struct namei *parent, const char *orgpath, int start, struct namei **last)
+{
+       struct namei *nm = NULL, *first = NULL;
+       char *fname, *end, *path;
+       int level = 0;
+
+       if (!orgpath)
+               return NULL;
+       if (parent) {
+               nm = parent;
+               level = parent->level + 1;
+       }
+       path = xstrdup(orgpath);
+       fname = path + start;
+
+       /* root directory */
+       if (*fname == '/') {
+               while (*fname == '/')
+                       fname++; /* eat extra '/' */
+               first = nm = new_namei(nm, "/", "/", level);
        }
 
-       /*
-        * See what type of critter this file is
-        */
-       
-       if(lstat(buf, &stb) == -1){
-           (void)printf(" ? %s - %s (%d)\n", buf, ERR);
-           return;
+       for (end = fname; fname && end; ) {
+               /* set end of filename */
+               if (*fname) {
+                       end = strchr(fname, '/');
+                       if (end)
+                               *end = '\0';
+
+                       /* create a new entry */
+                       nm = new_namei(nm, path, fname, level);
+               } else
+                       end = NULL;
+               if (!first)
+                       first = nm;
+               /* set begin of the next filename */
+               if (end) {
+                       *end++ = '/';
+                       while (*end == '/')
+                               end++; /* eat extra '/' */
+               }
+               fname = end;
        }
 
-       *lastmode = stb.st_mode;
+       if (last)
+               *last = nm;
 
-       switch(stb.st_mode & S_IFMT){
-           case S_IFDIR:
+       free(path);
 
-               /*
-                * File is a directory, chdir to it
-                */
-               
-               if(chdir(buf) == -1){
-                   (void)printf(_(" ? could not chdir into %s - %s (%d)\n"), buf, ERR );
-                   return;
-               }
-               if(xflag && lastdev != stb.st_dev && lastdev != NODEV){
-                   /* Across mnt point */
-                   if(mflag)
-                       (void)printf(" D%s %s\n", pperm(stb.st_mode), buf);
-                   else
-                       (void)printf(" D %s\n", buf);
-               }
-               else {
-                   if(mflag)
-                       (void)printf(" d%s %s\n", pperm(stb.st_mode), buf);
-                   else
-                       (void)printf(" d %s\n", buf);
+       return first;
+}
+
+static int
+follow_symlinks(struct namei *nm)
+{
+       int symcount = 0;
+
+       for (; nm; nm = nm->next) {
+               struct namei *next, *last;
+
+               if (nm->noent)
+                       continue;
+               if (!S_ISLNK(nm->st.st_mode))
+                       continue;
+               if (++symcount > MAXSYMLINKS) {
+                       /* drop the rest of the list */
+                       free_namei(nm->next);
+                       nm->next = NULL;
+                       return -1;
                }
-               lastdev = stb.st_dev;
+               next = nm->next;
+               nm->next = add_namei(nm, nm->abslink, nm->relstart, &last);
+               if (last)
+                       last->next = next;
+               else
+                       nm->next = next;
+       }
+       return 0;
+}
 
-               (void)fflush(stdout);
-               break;
+static int
+print_namei(struct namei *nm, char *path)
+{
+       int i;
 
-           case S_IFLNK:
-               /*
-                * Sigh, another symlink.  Read its contents and
-                * call namei()
-                */
-               bzero(sym, BUFSIZ);
-               if(readlink(buf, sym, BUFSIZ) == -1){
-                   (void)printf(_(" ? problems reading symlink %s - %s (%d)\n"), buf, ERR);
-                   return;
+       if (path)
+               printf("f: %s\n", path);
+
+       for (; nm; nm = nm->next) {
+               char md[11];
+
+               if (nm->noent) {
+                       int blanks = 1;
+                       if (flags & NAMEI_MODES)
+                               blanks += 9;
+                       if (flags & NAMEI_OWNERS)
+                               blanks += ucache->width + gcache->width + 2;
+                       if (!(flags & NAMEI_VERTICAL))
+                               blanks += 1;
+                       blanks += nm->level * 2;
+                       printf("%*s ", blanks, "");
+                       printf("%s - %s\n", nm->name, strerror(nm->noent));
+                       return -1;
                }
 
-               if(mflag)
-                   (void)printf(" l%s %s -> %s", pperm(stb.st_mode), buf, sym);
-               else
-                   (void)printf(" l %s -> %s", buf, sym);
+               xstrmode(nm->st.st_mode, md);
 
-               if(symcount > 0 && symcount++ > MAXSYMLINKS){
-                   (void)printf(_("  *** EXCEEDED UNIX LIMIT OF SYMLINKS ***\n"));
-               } else {
-                   (void)printf("\n");
-                   namei(sym, lev + 1, lastmode);
+               if (nm->mountpoint)
+                       md[0] = 'D';
+
+               if (!(flags & NAMEI_VERTICAL)) {
+                       for (i = 0; i < nm->level; i++)
+                               fputs("  ", stdout);
+                       fputc(' ', stdout);
                }
-               if (symcount > MAXSYMLINKS)
-                   return;
-               break;
 
-           case S_IFCHR:
-               if(mflag)
-                   (void)printf(" c%s %s\n", pperm(stb.st_mode), buf);
-               else
-                   (void)printf(" c %s\n", buf);
-               break;
-           
-           case S_IFBLK:
-               if(mflag)
-                   (void)printf(" b%s %s\n", pperm(stb.st_mode), buf);
+               if (flags & NAMEI_MODES)
+                       printf("%s", md);
                else
-                   (void)printf(" b %s\n", buf);
-               break;
-           
-           case S_IFSOCK:
-               if(mflag)
-                   (void)printf(" s%s %s\n", pperm(stb.st_mode), buf);
-               else
-                   (void)printf(" s %s\n", buf);
-               break;
+                       printf("%c", md[0]);
 
-               case S_IFIFO:
-               if (mflag)
-                       printf(" p%s %s\n", pperm(stb.st_mode), buf);
-               else
-                       printf(" p %s\n", buf);
-               break;
+               if (flags & NAMEI_OWNERS) {
+                       printf(" %-*s", ucache->width,
+                               get_id(ucache, nm->st.st_uid)->name);
+                       printf(" %-*s", gcache->width,
+                               get_id(gcache, nm->st.st_gid)->name);
+               }
 
-           case S_IFREG:
-               if(mflag)
-                   (void)printf(" -%s %s\n", pperm(stb.st_mode), buf);
+               if (flags & NAMEI_VERTICAL)
+                       for (i = 0; i < nm->level; i++)
+                               fputs("  ", stdout);
+
+               if (S_ISLNK(nm->st.st_mode))
+                       printf(" %s -> %s\n", nm->name,
+                                       nm->abslink + nm->relstart);
                else
-                   (void)printf(" - %s\n", buf);
-               break;
-               
-           default:
-               (void)fprintf(stderr,_("namei: unknown file type 0%06o on file %s\n"), stb.st_mode, buf );
-               exit(1);
-           
+                       printf(" %s\n", nm->name);
        }
-    }
+       return 0;
 }
 
-/* Take a 
- * Mode word, as from a struct stat, and return
- * a pointer to a static string containing a printable version like ls.
- * For example 0755 produces "rwxr-xr-x"
- */
-static char *
-pperm(unsigned short mode) {
-       unsigned short m;
-       static char buf[16];
-       char *bp;
-       char *lschars = "xwrxwrxwr";  /* the complete string backwards */
-       char *cp;
-       int i;
-
-       for(i = 0, cp = lschars, m = mode, bp = &buf[8];
-           i < 9;
-           i++, cp++, m >>= 1, bp--) {
+static void __attribute__((__noreturn__)) usage(void)
+{
+       const char *p = program_invocation_short_name;
+       FILE *out = stdout;
+
+       if (!*p)
+               p = "namei";
+
+       fputs(USAGE_HEADER, out);
+       fprintf(out,
+             _(" %s [options] <pathname>...\n"), p);
+
+       fputs(USAGE_SEPARATOR, out);
+       fputs(_("Follow a pathname until a terminal point is found.\n"), out);
+
+       fputs(USAGE_OPTIONS, out);
+       fputs(_(
+               " -x, --mountpoints   show mount point directories with a 'D'\n"
+               " -m, --modes         show the mode bits of each file\n"
+               " -o, --owners        show owner and group name of each file\n"
+               " -l, --long          use a long listing format (-m -o -v) \n"
+               " -n, --nosymlinks    don't follow symlinks\n"
+               " -v, --vertical      vertical align of modes and owners\n"), out);
+       printf(USAGE_HELP_OPTIONS(21));
+
+       printf(USAGE_MAN_TAIL("namei(1)"));
+       exit(EXIT_SUCCESS);
+}
 
-               if(m & 1)
-                       *bp = *cp;
-               else
-                       *bp = '-';
-           }
-       buf[9] = '\0';
+static const struct option longopts[] =
+{
+       { "help",        no_argument, NULL, 'h' },
+       { "version",     no_argument, NULL, 'V' },
+       { "mountpoints", no_argument, NULL, 'x' },
+       { "modes",       no_argument, NULL, 'm' },
+       { "owners",      no_argument, NULL, 'o' },
+       { "long",        no_argument, NULL, 'l' },
+       { "nolinks",     no_argument, NULL, 'n' },
+       { "vertical",    no_argument, NULL, 'v' },
+       { NULL, 0, NULL, 0 },
+};
 
-       if(mode & S_ISUID)  {
-               if(buf[2] == 'x')
-                       buf[2] = 's';
-               else
-                       buf[2] = 'S';
+int
+main(int argc, char **argv)
+{
+       int c;
+       int rc = EXIT_SUCCESS;
+
+       setlocale(LC_ALL, "");
+       bindtextdomain(PACKAGE, LOCALEDIR);
+       textdomain(PACKAGE);
+       atexit(close_stdout);
+
+       while ((c = getopt_long(argc, argv, "hVlmnovx", longopts, NULL)) != -1) {
+               switch(c) {
+               case 'h':
+                       usage();
+                       break;
+               case 'V':
+                       printf(UTIL_LINUX_VERSION);
+                       return EXIT_SUCCESS;
+               case 'l':
+                       flags |= (NAMEI_OWNERS | NAMEI_MODES | NAMEI_VERTICAL);
+                       break;
+               case 'm':
+                       flags |= NAMEI_MODES;
+                       break;
+               case 'n':
+                       flags |= NAMEI_NOLINKS;
+                       break;
+               case 'o':
+                       flags |= NAMEI_OWNERS;
+                       break;
+               case 'x':
+                       flags |= NAMEI_MNTS;
+                       break;
+               case 'v':
+                       flags |= NAMEI_VERTICAL;
+                       break;
+               default:
+                       errtryhelp(EXIT_FAILURE);
+               }
        }
-       if(mode & S_ISGID)  {
-               if(buf[5] == 'x')
-                       buf[5] = 's';
-               else
-                       buf[5] = 'S';
+
+       if (optind == argc) {
+               warnx(_("pathname argument is missing"));
+               errtryhelp(EXIT_FAILURE);
        }
-       if(mode & S_ISVTX)  {
-               if(buf[8] == 'x')
-                       buf[8] = 't';
-               else
-                       buf[8] = 'T';
+
+       ucache = new_idcache();
+       if (!ucache)
+               err(EXIT_FAILURE, _("failed to allocate UID cache"));
+       gcache = new_idcache();
+       if (!gcache)
+               err(EXIT_FAILURE, _("failed to allocate GID cache"));
+
+       for(; optind < argc; optind++) {
+               char *path = argv[optind];
+               struct namei *nm = NULL;
+               struct stat st;
+
+               if (stat(path, &st) != 0)
+                       rc = EXIT_FAILURE;
+
+               nm = add_namei(NULL, path, 0, NULL);
+               if (nm) {
+                       int sml = 0;
+                       if (!(flags & NAMEI_NOLINKS))
+                               sml = follow_symlinks(nm);
+                       if (print_namei(nm, path)) {
+                               rc = EXIT_FAILURE;
+                               continue;
+                       }
+                       free_namei(nm);
+                       if (sml == -1) {
+                               rc = EXIT_FAILURE;
+                               warnx(_("%s: exceeded limit of symlinks"), path);
+                               continue;
+                       }
+               }
        }
 
-       return &buf[0];
+       free_idcache(ucache);
+       free_idcache(gcache);
+
+       return rc;
 }
 
-