/*
* 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)
#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;
};
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 != '/') {
/* 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 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 */
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);
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';
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 = 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 -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);
while ((c = getopt_long(argc, argv, "+h?lmnovx", longopts, NULL)) != -1) {
switch(c) {
case 'h':
case '?':
usage(EXIT_SUCCESS);
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;
}