489 lines
9.8 KiB
C
489 lines
9.8 KiB
C
/*
|
|
* Copyright (C) 2008 Karel Zak <kzak@redhat.com>
|
|
*
|
|
* 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 <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 "nls.h"
|
|
#include "widechar.h"
|
|
|
|
#ifndef MAXSYMLINKS
|
|
#define MAXSYMLINKS 256
|
|
#endif
|
|
|
|
#ifndef PATH_MAX
|
|
#define PATH_MAX 4096
|
|
#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)
|
|
|
|
|
|
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;
|
|
};
|
|
|
|
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 : strlen(path);
|
|
sz += nm->relstart + 1;
|
|
}
|
|
nm->abslink = malloc(sz + 1);
|
|
if (!nm->abslink)
|
|
err(EXIT_FAILURE, _("out of memory?"));
|
|
|
|
if (*sym != '/') {
|
|
memcpy(nm->abslink, path, nm->relstart);
|
|
*(nm->abslink + nm->relstart) = '/';
|
|
nm->relstart++;
|
|
memcpy(nm->abslink + nm->relstart, sym, sz);
|
|
} else
|
|
memcpy(nm->abslink, sym, sz);
|
|
nm->abslink[sz] = '\0';
|
|
}
|
|
|
|
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);
|
|
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 */
|
|
end = strchr(fname, '/');
|
|
if (end)
|
|
*end = '\0';
|
|
|
|
/* create a new entry */
|
|
nm = new_namei(nm, path, fname, level);
|
|
if (!first)
|
|
first = nm;
|
|
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);
|
|
}
|
|
/* 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 ((flags & NAMEI_MNTS) && prev &&
|
|
S_ISDIR(nm->st.st_mode) && S_ISDIR(prev->st.st_mode) &&
|
|
prev->st.st_dev != nm->st.st_dev)
|
|
md[0] = 'D';
|
|
|
|
for (i = 0; i < nm->level; i++)
|
|
fputs(" ", 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 (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 = program_invocation_short_name;
|
|
|
|
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)\n"
|
|
" -n, --nosymlinks don't follow symlinks\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' },
|
|
{ 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);
|
|
|
|
while ((c = getopt_long(argc, argv, "+h?lmnox", longopts, NULL)) != -1) {
|
|
switch(c) {
|
|
case 'h':
|
|
case '?':
|
|
usage(EXIT_SUCCESS);
|
|
break;
|
|
case 'l':
|
|
flags |= (NAMEI_OWNERS | NAMEI_MODES);
|
|
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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|