]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - misc-utils/namei.c
lsblk: Fall back to ID_SERIAL
[thirdparty/util-linux.git] / misc-utils / namei.c
index da7696a46b9c0631a33930c16f2ab2412223214e..9c5f5fa4ac3952423c1c5f9e4b4d3ae4b157c148 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
  *
- * This file is part of util-linux-ng.
+ * 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
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
- * The original namei(1) was writtent by:
+ * The original namei(1) was written by:
  *     Roger S. Southwick (May 2, 1990)
  *     Steve Tell (March 28, 1991)
- *     Arkadiusz Mikiewicz (1999-02-22)
+ *     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 <err.h>
+#include <pwd.h>
+#include <grp.h>
+
+#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
 
-#ifndef PATH_MAX
-#define PATH_MAX 4096
-#endif
-
 #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)
 
-static int flags;
 
 struct namei {
        struct stat     st;             /* item lstat() */
@@ -53,8 +58,14 @@ struct namei {
        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)
 {
@@ -71,7 +82,8 @@ static void
 readlink_to_namei(struct namei *nm, const char *path)
 {
        char sym[PATH_MAX];
-       size_t sz;
+       ssize_t sz;
+       int isrel = 0;
 
        sz = readlink(path, sym, sizeof(sym));
        if (sz < 1)
@@ -79,23 +91,52 @@ readlink_to_namei(struct namei *nm, const char *path)
        if (*sym != '/') {
                char *p = strrchr(path, '/');
 
-               nm->relstart = p ? p - path : strlen(path);
-               sz += nm->relstart + 1;
+               if (p) {
+                       isrel = 1;
+                       nm->relstart = p - path;
+                       sz += nm->relstart + 1;
+               }
        }
-       nm->abslink = malloc(sz + 1);
-       if (!nm->abslink)
-               err(EXIT_FAILURE, _("out of memory?"));
+       nm->abslink = xmalloc(sz + 1);
 
-       if (*sym != '/') {
+       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);
+               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';
 }
 
+static struct stat *
+dotdot_stat(const char *dirname, struct stat *st)
+{
+       char *path;
+       size_t len;
+
+#define DOTDOTDIR      "/.."
+
+       if (!dirname)
+               return NULL;
+
+       len = strlen(dirname);
+       path = xmalloc(len + sizeof(DOTDOTDIR));
+
+       memcpy(path, dirname, len);
+       memcpy(path + len, DOTDOTDIR, sizeof(DOTDOTDIR));
+
+       if (stat(path, st))
+               err(EXIT_FAILURE, _("stat of %s failed"), path);
+       free(path);
+       return st;
+}
+
 static struct namei *
 new_namei(struct namei *parent, const char *path, const char *fname, int lev)
 {
@@ -103,18 +144,38 @@ new_namei(struct namei *parent, const char *path, const char *fname, int lev)
 
        if (!fname)
                return NULL;
-       nm = calloc(1, sizeof(*nm));
-       if (!nm)
-               err(EXIT_FAILURE, _("out of memory?"));
+       nm = xcalloc(1, sizeof(*nm));
        if (parent)
                parent->next = nm;
 
        nm->level = lev;
-       nm->name = strdup(fname);
-       if (!nm->name)
-               err(EXIT_FAILURE, _("out of memory?"));
-       if (lstat(path, &nm->st) == -1)
-               err(EXIT_FAILURE, _("could not stat '%s'"), path);
+       nm->name = xstrdup(fname);
+
+       if (lstat(path, &nm->st) != 0) {
+               nm->noent = errno;
+               return nm;
+       }
+
+       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);
+       }
+
+       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;
+       }
+
        return nm;
 }
 
@@ -131,9 +192,7 @@ add_namei(struct namei *parent, const char *orgpath, int start, struct namei **l
                nm = parent;
                level = parent->level + 1;
        }
-       path = strdup(orgpath);
-       if (!path)
-               err(EXIT_FAILURE, _("out of memory?"));
+       path = xstrdup(orgpath);
        fname = path + start;
 
        /* root directory */
@@ -145,17 +204,17 @@ add_namei(struct namei *parent, const char *orgpath, int start, struct namei **l
 
        for (end = fname; fname && end; ) {
                /* set end of filename */
-               end = strchr(fname, '/');
-               if (end)
-                       *end = '\0';
-
-               /* create a new entry */
-               nm = new_namei(nm, path, fname, level);
+               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;
-               if (S_ISLNK(nm->st.st_mode))
-                       readlink_to_namei(nm, path);
-
                /* set begin of the next filename */
                if (end) {
                        *end++ = '/';
@@ -167,10 +226,12 @@ add_namei(struct namei *parent, const char *orgpath, int start, struct namei **l
 
        if (last)
                *last = nm;
+
+       free(path);
+
        return first;
 }
 
-
 static int
 follow_symlinks(struct namei *nm)
 {
@@ -179,6 +240,8 @@ follow_symlinks(struct namei *nm)
        for (; nm; nm = nm->next) {
                struct namei *next, *last;
 
+               if (nm->noent)
+                       continue;
                if (!S_ISLNK(nm->st.st_mode))
                        continue;
                if (++symcount > MAXSYMLINKS) {
@@ -197,68 +260,57 @@ follow_symlinks(struct namei *nm)
        return 0;
 }
 
-static void
-strmode(mode_t mode, char *str)
-{
-       if (S_ISDIR(mode))
-               str[0] = 'd';
-       else if (S_ISLNK(mode))
-               str[0] = 'l';
-       else if (S_ISCHR(mode))
-               str[0] = 'c';
-       else if (S_ISBLK(mode))
-               str[0] = 'b';
-       else if (S_ISSOCK(mode))
-               str[0] = 's';
-       else if (S_ISFIFO(mode))
-               str[0] = 'p';
-       else if (S_ISREG(mode))
-               str[0] = '-';
-
-       str[1] = mode & S_IRUSR ? 'r' : '-';
-       str[2] = mode & S_IWUSR ? 'w' : '-';
-       str[3] = (mode & S_ISUID
-               ? (mode & S_IXUSR ? 's' : 'S')
-               : (mode & S_IXUSR ? 'x' : '-'));
-       str[4] = mode & S_IRGRP ? 'r' : '-';
-       str[5] = mode & S_IWGRP ? 'w' : '-';
-       str[6] = (mode & S_ISGID
-               ? (mode & S_IXGRP ? 's' : 'S')
-               : (mode & S_IXGRP ? 'x' : '-'));
-       str[7] = mode & S_IROTH ? 'r' : '-';
-       str[8] = mode & S_IWOTH ? 'w' : '-';
-       str[9] = (mode & S_ISVTX
-               ? (mode & S_IXOTH ? 't' : 'T')
-               : (mode & S_IXOTH ? 'x' : '-'));
-       str[10] = '\0';
-}
-
-static void
+static int
 print_namei(struct namei *nm, char *path)
 {
-       struct namei *prev = NULL;
        int i;
 
        if (path)
                printf("f: %s\n", path);
 
-       for (; nm; prev = nm, nm = nm->next) {
+       for (; nm; nm = nm->next) {
                char md[11];
 
-               strmode(nm->st.st_mode, md);
+               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;
+               }
+
+               xstrmode(nm->st.st_mode, md);
 
-               if ((flags & NAMEI_MNTS) && prev &&
-                   S_ISDIR(nm->st.st_mode) && S_ISDIR(prev->st.st_mode) &&
-                   prev->st.st_dev != nm->st.st_dev)
+               if (nm->mountpoint)
                        md[0] = 'D';
 
-               for (i = 0; i < nm->level; i++)
-                       fputs("  ", stdout);
+               if (!(flags & NAMEI_VERTICAL)) {
+                       for (i = 0; i < nm->level; i++)
+                               fputs("  ", stdout);
+                       fputc(' ', stdout);
+               }
 
                if (flags & NAMEI_MODES)
-                       printf(" %s", md);
+                       printf("%s", md);
                else
-                       printf(" %c", md[0]);
+                       printf("%c", md[0]);
+
+               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);
+               }
+
+               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,
@@ -266,92 +318,133 @@ print_namei(struct namei *nm, char *path)
                else
                        printf(" %s\n", nm->name);
        }
+       return 0;
 }
 
-static void
-usage(int rc)
+static void __attribute__((__noreturn__)) usage(void)
 {
        const char *p = program_invocation_short_name;
+       FILE *out = stdout;
 
        if (!*p)
                p = "namei";
 
-       printf(_("\nUsage: %s [options] pathname [pathname ...]\n"), p);
-       printf(_("\nOptions:\n"));
-
-       printf(_(
-       " -h, --help          displays this help text\n"
-       " -x, --mountpoints   show mount point directories with a 'D'\n"
-       " -m, --modes         show the mode bits of each file\n"
-       " -n, --nosymlinks    don't follow symlinks\n"));
-
-       printf(_("\nFor more information see namei(1).\n"));
-       exit(rc);
+       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);
 }
 
-struct option longopts[] =
+static const struct option longopts[] =
 {
-       { "help",       0, 0, 'h' },
-       { "mountpoints",0, 0, 'x' },
-       { "modes",      0, 0, 'm' },
-       { "nolinks",    0, 0, 'n' },
-       { NULL,         0, 0, 0 },
+       { "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 },
 };
 
 int
 main(int argc, char **argv)
 {
-       extern int optind;
        int c;
+       int rc = EXIT_SUCCESS;
 
        setlocale(LC_ALL, "");
        bindtextdomain(PACKAGE, LOCALEDIR);
        textdomain(PACKAGE);
+       close_stdout_atexit();
 
-       if (argc < 2)
-               usage(EXIT_FAILURE);
-
-       while ((c = getopt_long(argc, argv, "+h?xmn", longopts, NULL)) != -1) {
+       while ((c = getopt_long(argc, argv, "hVlmnovx", longopts, NULL)) != -1) {
                switch(c) {
-               case 'h':
-               case '?':
-                       usage(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 'n':
-                       flags |= NAMEI_NOLINKS;
+               case 'v':
+                       flags |= NAMEI_VERTICAL;
                        break;
+
+               case 'h':
+                       usage();
+               case 'V':
+                       print_version(EXIT_SUCCESS);
+               default:
+                       errtryhelp(EXIT_FAILURE);
                }
        }
 
+       if (optind == argc) {
+               warnx(_("pathname argument is missing"));
+               errtryhelp(EXIT_FAILURE);
+       }
+
+       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)
-                       err(EXIT_FAILURE, _("failed to stat: %s"), path);
+                       rc = EXIT_FAILURE;
 
                nm = add_namei(NULL, path, 0, NULL);
                if (nm) {
                        int sml = 0;
-
                        if (!(flags & NAMEI_NOLINKS))
                                sml = follow_symlinks(nm);
-                       print_namei(nm, path);
+                       if (print_namei(nm, path)) {
+                               rc = EXIT_FAILURE;
+                               continue;
+                       }
                        free_namei(nm);
-                       if (sml == -1)
-                               errx(EXIT_FAILURE,
-                                       _("%s: exceeded limit of symlinks"),
-                                       path);
+                       if (sml == -1) {
+                               rc = EXIT_FAILURE;
+                               warnx(_("%s: exceeded limit of symlinks"), path);
+                               continue;
+                       }
                }
        }
 
-       return EXIT_SUCCESS;
+       free_idcache(ucache);
+       free_idcache(gcache);
+
+       return rc;
 }