/* * Copyright (C) 2008 Karel Zak * * This file is part of util-linux-ng. * * 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 writtent by: * Roger S. Southwick (May 2, 1990) * Steve Tell (March 28, 1991) * Arkadiusz Mikiewicz (1999-02-22) * Li Zefan (2007-09-10). */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "c.h" #include "nls.h" #include "widechar.h" #ifndef MAXSYMLINKS #define MAXSYMLINKS 256 #endif #ifndef LOGIN_NAME_MAX #define LOGIN_NAME_MAX 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 */ }; struct idcache { unsigned long int id; char *name; struct idcache *next; }; static int flags; static int uwidth; /* maximal width of username */ static int gwidth; /* maximal width of groupname */ static struct idcache *gcache; /* groupnames */ static struct idcache *ucache; /* usernames */ static struct idcache * get_id(struct idcache *ic, unsigned long int id) { while(ic) { if (ic->id == id) return ic; ic = ic->next; } return NULL; } static void free_idcache(struct idcache *ic) { while(ic) { struct idcache *next = ic->next; free(ic->name); free(ic); ic = next; } } static void add_id(struct idcache **ic, char *name, unsigned long int id, int *width) { struct idcache *nc, *x; int w = 0; nc = calloc(1, sizeof(*nc)); if (!nc) goto alloc_err; nc->id = id; if (name) { #ifdef HAVE_WIDECHAR wchar_t wc[LOGIN_NAME_MAX + 1]; if (mbstowcs(wc, name, LOGIN_NAME_MAX) > 0) { wc[LOGIN_NAME_MAX] = '\0'; w = wcswidth(wc, LOGIN_NAME_MAX); } else #endif w = strlen(name); } /* note, we ignore names with non-printable widechars */ if (w > 0) nc->name = strdup(name); else if (asprintf(&nc->name, "%lu", id) == -1) nc->name = NULL; if (!nc->name) goto alloc_err; for (x = *ic; x && x->next; x = x->next); /* add 'nc' at end of the 'ic' list */ if (x) x->next = nc; else *ic = nc; if (w <= 0) w = strlen(nc->name); *width = *width < w ? w : *width; return; alloc_err: err(EXIT_FAILURE, _("out of memory?")); } static void add_uid(unsigned long int id) { struct idcache *ic = get_id(ucache, id); if (!ic) { struct passwd *pw = getpwuid((uid_t) id); add_id(&ucache, pw ? pw->pw_name : NULL, id, &uwidth); } } static void add_gid(unsigned long int id) { struct idcache *ic = get_id(gcache, id); if (!ic) { struct group *gr = getgrgid((gid_t) id); add_id(&gcache, gr ? gr->gr_name : NULL, id, &gwidth); } } 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]; size_t sz; sz = readlink(path, sym, sizeof(sym)); if (sz < 1) err(EXIT_FAILURE, _("failed to read symlink: %s"), path); if (*sym != '/') { char *p = strrchr(path, '/'); nm->relstart = p ? p - path : 0; if (nm->relstart) sz += nm->relstart + 1; } nm->abslink = malloc(sz + 1); if (!nm->abslink) err(EXIT_FAILURE, _("out of memory?")); if (*sym != '/' && nm->relstart) { /* 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 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 = malloc(len + sizeof(DOTDOTDIR)); if (!path) err(EXIT_FAILURE, _("out of memory?")); memcpy(path, dirname, len); memcpy(path + len, DOTDOTDIR, sizeof(DOTDOTDIR)); if (stat(path, st)) err(EXIT_FAILURE, _("could not stat '%s'"), 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 = calloc(1, sizeof(*nm)); if (!nm) err(EXIT_FAILURE, _("out of memory?")); 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); if (S_ISLNK(nm->st.st_mode)) readlink_to_namei(nm, path); if (flags & NAMEI_OWNERS) { add_uid(nm->st.st_uid); add_gid(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 = strdup(orgpath); if (!path) err(EXIT_FAILURE, _("out of memory?")); 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; return first; } static int follow_symlinks(struct namei *nm) { int symcount = 0; for (; nm; nm = nm->next) { struct namei *next, *last; 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 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 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) { char md[11]; strmode(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", uwidth, get_id(ucache, nm->st.st_uid)->name); printf(" %-*s", gwidth, 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); } } static void usage(int rc, const char *p) { 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" " -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")); printf(_("\nFor more information see namei(1).\n")); exit(rc); } struct option longopts[] = { { "help", 0, 0, 'h' }, { "mountpoints",0, 0, 'x' }, { "modes", 0, 0, 'm' }, { "owners", 0, 0, 'o' }, { "long", 0, 0, 'l' }, { "nolinks", 0, 0, 'n' }, { "vertical", 0, 0, 'v' }, { NULL, 0, 0, 0 }, }; int main(int argc, char **argv) { extern int optind; int c; setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); if (argc < 2) usage(EXIT_FAILURE, argv[0]); while ((c = getopt_long(argc, argv, "+h?lmnovx", longopts, NULL)) != -1) { switch(c) { case 'h': case '?': usage(EXIT_SUCCESS, argv[0]); break; 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; } } 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); nm = add_namei(NULL, path, 0, NULL); if (nm) { int sml = 0; if (!(flags & NAMEI_NOLINKS)) sml = follow_symlinks(nm); print_namei(nm, path); free_namei(nm); if (sml == -1) errx(EXIT_FAILURE, _("%s: exceeded limit of symlinks"), path); } } free_idcache(ucache); free_idcache(gcache); return EXIT_SUCCESS; }