summaryrefslogtreecommitdiffstats
path: root/misc-utils/namei.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--misc-utils/namei.c450
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..3d50b20
--- /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 (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;
+}
+