diff options
Diffstat (limited to '')
-rw-r--r-- | misc-utils/namei.c | 450 |
1 files changed, 450 insertions, 0 deletions
diff --git a/misc-utils/namei.c b/misc-utils/namei.c new file mode 100644 index 0000000..9c5f5fa --- /dev/null +++ b/misc-utils/namei.c @@ -0,0 +1,450 @@ +/* + * 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 <stdlib.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/param.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 + +#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) + + +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; + } +} + +static void +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'; +} + +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) +{ + struct namei *nm; + + if (!fname) + return NULL; + nm = xcalloc(1, sizeof(*nm)); + if (parent) + parent->next = nm; + + nm->level = lev; + 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; +} + +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); + } + + 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; + } + + if (last) + *last = nm; + + free(path); + + 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; + } + next = nm->next; + nm->next = add_namei(nm, nm->abslink, nm->relstart, &last); + if (last) + last->next = next; + else + nm->next = next; + } + return 0; +} + +static int +print_namei(struct namei *nm, char *path) +{ + int i; + + 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; + } + + xstrmode(nm->st.st_mode, md); + + if (nm->mountpoint) + md[0] = 'D'; + + if (!(flags & NAMEI_VERTICAL)) { + for (i = 0; i < nm->level; i++) + fputs(" ", stdout); + fputc(' ', stdout); + } + + if (flags & NAMEI_MODES) + printf("%s", md); + else + 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, + nm->abslink + nm->relstart); + else + printf(" %s\n", nm->name); + } + return 0; +} + +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); +} + +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 }, +}; + +int +main(int argc, char **argv) +{ + int c; + int rc = EXIT_SUCCESS; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + while ((c = getopt_long(argc, argv, "hVlmnovx", longopts, NULL)) != -1) { + switch(c) { + 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; + + 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) + 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; + } + } + } + + free_idcache(ucache); + free_idcache(gcache); + + return rc; +} + |