(root)/
util-linux-2.39/
misc-utils/
namei.c
       1  /*
       2   * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
       3   *
       4   * This file is part of util-linux.
       5   *
       6   * This file is free software; you can redistribute it and/or modify
       7   * it under the terms of the GNU General Public License as published by
       8   * the Free Software Foundation; either version 2 of the License, or
       9   * (at your option) any later version.
      10   *
      11   * This file is distributed in the hope that it will be useful,
      12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
      13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14   * GNU General Public License for more details.
      15   *
      16   * The original namei(1) was written by:
      17   *	Roger S. Southwick (May 2, 1990)
      18   *	Steve Tell (March 28, 1991)
      19   *	Arkadiusz Miƛkiewicz (1999-02-22)
      20   *	Li Zefan (2007-09-10).
      21   */
      22  #include <stdio.h>
      23  #include <unistd.h>
      24  #include <getopt.h>
      25  #include <string.h>
      26  #include <stdlib.h>
      27  #include <errno.h>
      28  #include <sys/types.h>
      29  #include <sys/stat.h>
      30  #include <sys/param.h>
      31  #include <pwd.h>
      32  #include <grp.h>
      33  
      34  #ifdef HAVE_LIBSELINUX
      35  # include <selinux/selinux.h>
      36  #endif
      37  
      38  #include "c.h"
      39  #include "xalloc.h"
      40  #include "nls.h"
      41  #include "widechar.h"
      42  #include "strutils.h"
      43  #include "closestream.h"
      44  #include "idcache.h"
      45  
      46  #ifndef MAXSYMLINKS
      47  #define MAXSYMLINKS 256
      48  #endif
      49  
      50  #define NAMEI_NOLINKS	(1 << 1)
      51  #define NAMEI_MODES	(1 << 2)
      52  #define NAMEI_MNTS	(1 << 3)
      53  #define NAMEI_OWNERS	(1 << 4)
      54  #define NAMEI_VERTICAL	(1 << 5)
      55  #define NAMEI_CONTEXT	(1 << 6)
      56  
      57  
      58  struct namei {
      59  	struct stat	st;		/* item lstat() */
      60  	char		*name;		/* item name */
      61  	char		*abslink;	/* absolute symlink path */
      62  	int		relstart;	/* offset of relative path in 'abslink' */
      63  	struct namei	*next;		/* next item */
      64  	int		level;
      65  	int		mountpoint;	/* is mount point */
      66  	int		noent;		/* this item not existing (stores errno from stat()) */
      67  #ifdef HAVE_LIBSELINUX
      68  	int		context_len;	/* length of selinux contexts, as returned by lgetfilecon(3) */
      69  	char		*context;	/* selinux contexts, as set by lgetfilecon(3) */
      70  #endif
      71  };
      72  
      73  static int flags;
      74  static struct idcache *gcache;	/* groupnames */
      75  static struct idcache *ucache;	/* usernames */
      76  
      77  static void
      78  free_namei(struct namei *nm)
      79  {
      80  	while (nm) {
      81  		struct namei *next = nm->next;
      82  #ifdef HAVE_LIBSELINUX
      83  		free(nm->context);
      84  #endif
      85  		free(nm->name);
      86  		free(nm->abslink);
      87  		free(nm);
      88  		nm = next;
      89  	}
      90  }
      91  
      92  static void
      93  readlink_to_namei(struct namei *nm, const char *path)
      94  {
      95  	char sym[PATH_MAX];
      96  	ssize_t sz;
      97  	int isrel = 0;
      98  
      99  	sz = readlink(path, sym, sizeof(sym));
     100  	if (sz < 1)
     101  		err(EXIT_FAILURE, _("failed to read symlink: %s"), path);
     102  	if (*sym != '/') {
     103  		char *p = strrchr(path, '/');
     104  
     105  		if (p) {
     106  			isrel = 1;
     107  			nm->relstart = p - path;
     108  			sz += nm->relstart + 1;
     109  		}
     110  	}
     111  	nm->abslink = xmalloc(sz + 1);
     112  
     113  	if (isrel) {
     114  		/* create the absolute path from the relative symlink */
     115  		memcpy(nm->abslink, path, nm->relstart);
     116  		*(nm->abslink + nm->relstart) = '/';
     117  		nm->relstart++;
     118  		memcpy(nm->abslink + nm->relstart, sym, sz - nm->relstart);
     119  	} else
     120  		/* - absolute link (foo -> /path/bar)
     121  		 * - or link without any subdir (foo -> bar)
     122  		 */
     123  		memcpy(nm->abslink, sym, sz);
     124  
     125  	nm->abslink[sz] = '\0';
     126  }
     127  
     128  static struct stat *
     129  dotdot_stat(const char *dirname, struct stat *st)
     130  {
     131  	char *path;
     132  	size_t len;
     133  
     134  #define DOTDOTDIR	"/.."
     135  
     136  	if (!dirname)
     137  		return NULL;
     138  
     139  	len = strlen(dirname);
     140  	path = xmalloc(len + sizeof(DOTDOTDIR));
     141  
     142  	memcpy(path, dirname, len);
     143  	memcpy(path + len, DOTDOTDIR, sizeof(DOTDOTDIR));
     144  
     145  	if (stat(path, st))
     146  		err(EXIT_FAILURE, _("stat of %s failed"), path);
     147  	free(path);
     148  	return st;
     149  }
     150  
     151  static struct namei *
     152  new_namei(struct namei *parent, const char *path, const char *fname, int lev)
     153  {
     154  	struct namei *nm;
     155  
     156  	if (!fname)
     157  		return NULL;
     158  	nm = xcalloc(1, sizeof(*nm));
     159  	if (parent)
     160  		parent->next = nm;
     161  
     162  	nm->level = lev;
     163  	nm->name = xstrdup(fname);
     164  
     165  #ifdef HAVE_LIBSELINUX
     166  	/* Don't use is_selinux_enabled() here. We need info about a context
     167  	 * also on systems where SELinux is (temporary) disabled */
     168  	nm->context_len = lgetfilecon(path, &nm->context);
     169  #endif
     170  	if (lstat(path, &nm->st) != 0) {
     171  		nm->noent = errno;
     172  		return nm;
     173  	}
     174  
     175  	if (S_ISLNK(nm->st.st_mode))
     176  		readlink_to_namei(nm, path);
     177  	if (flags & NAMEI_OWNERS) {
     178  		add_uid(ucache, nm->st.st_uid);
     179  		add_gid(gcache, nm->st.st_gid);
     180  	}
     181  
     182  	if ((flags & NAMEI_MNTS) && S_ISDIR(nm->st.st_mode)) {
     183  		struct stat stbuf, *sb = NULL;
     184  
     185  		if (parent && S_ISDIR(parent->st.st_mode))
     186  			sb = &parent->st;
     187  		else if (!parent || S_ISLNK(parent->st.st_mode))
     188  			sb = dotdot_stat(path, &stbuf);
     189  
     190  		if (sb && (sb->st_dev != nm->st.st_dev ||   /* different device */
     191  		           sb->st_ino == nm->st.st_ino))    /* root directory */
     192  			nm->mountpoint = 1;
     193  	}
     194  
     195  	return nm;
     196  }
     197  
     198  static struct namei *
     199  add_namei(struct namei *parent, const char *orgpath, int start, struct namei **last)
     200  {
     201  	struct namei *nm = NULL, *first = NULL;
     202  	char *fname, *end, *path;
     203  	int level = 0;
     204  
     205  	if (!orgpath)
     206  		return NULL;
     207  	if (parent) {
     208  		nm = parent;
     209  		level = parent->level + 1;
     210  	}
     211  	path = xstrdup(orgpath);
     212  	fname = path + start;
     213  
     214  	/* root directory */
     215  	if (*fname == '/') {
     216  		while (*fname == '/')
     217  			fname++; /* eat extra '/' */
     218  		first = nm = new_namei(nm, "/", "/", level);
     219  	}
     220  
     221  	for (end = fname; fname && end; ) {
     222  		/* set end of filename */
     223  		if (*fname) {
     224  			end = strchr(fname, '/');
     225  			if (end)
     226  				*end = '\0';
     227  
     228  			/* create a new entry */
     229  			nm = new_namei(nm, path, fname, level);
     230  		} else
     231  			end = NULL;
     232  		if (!first)
     233  			first = nm;
     234  		/* set begin of the next filename */
     235  		if (end) {
     236  			*end++ = '/';
     237  			while (*end == '/')
     238  				end++; /* eat extra '/' */
     239  		}
     240  		fname = end;
     241  	}
     242  
     243  	if (last)
     244  		*last = nm;
     245  
     246  	free(path);
     247  
     248  	return first;
     249  }
     250  
     251  static int
     252  follow_symlinks(struct namei *nm)
     253  {
     254  	int symcount = 0;
     255  
     256  	for (; nm; nm = nm->next) {
     257  		struct namei *next, *last;
     258  
     259  		if (nm->noent)
     260  			continue;
     261  		if (!S_ISLNK(nm->st.st_mode))
     262  			continue;
     263  		if (++symcount > MAXSYMLINKS) {
     264  			/* drop the rest of the list */
     265  			free_namei(nm->next);
     266  			nm->next = NULL;
     267  			return -1;
     268  		}
     269  		next = nm->next;
     270  		nm->next = add_namei(nm, nm->abslink, nm->relstart, &last);
     271  		if (last)
     272  			last->next = next;
     273  		else
     274  			nm->next = next;
     275  	}
     276  	return 0;
     277  }
     278  
     279  static int
     280  print_namei(struct namei *nm, char *path)
     281  {
     282  	int i;
     283  
     284  	if (path)
     285  		printf("f: %s\n", path);
     286  
     287  	for (; nm; nm = nm->next) {
     288  		char md[11];
     289  
     290  		if (nm->noent) {
     291  			int blanks = 1;
     292  			if (flags & NAMEI_MODES)
     293  				blanks += 9;
     294  			if (flags & NAMEI_OWNERS)
     295  				blanks += ucache->width + gcache->width + 2;
     296  			if (!(flags & NAMEI_VERTICAL))
     297  				blanks += 1;
     298  			if (!(flags & NAMEI_CONTEXT))
     299  				blanks += 1;
     300  			blanks += nm->level * 2;
     301  			printf("%*s ", blanks, "");
     302  			printf("%s - %s\n", nm->name, strerror(nm->noent));
     303  			return -1;
     304  		}
     305  
     306  		xstrmode(nm->st.st_mode, md);
     307  
     308  		if (nm->mountpoint)
     309  			md[0] = 'D';
     310  
     311  		if (!(flags & NAMEI_VERTICAL)) {
     312  			for (i = 0; i < nm->level; i++)
     313  				fputs("  ", stdout);
     314  			fputc(' ', stdout);
     315  		}
     316  
     317  		if (flags & NAMEI_MODES)
     318  			printf("%s", md);
     319  		else
     320  			printf("%c", md[0]);
     321  
     322  		if (flags & NAMEI_OWNERS) {
     323  			printf(" %-*s", ucache->width,
     324  				get_id(ucache, nm->st.st_uid)->name);
     325  			printf(" %-*s", gcache->width,
     326  				get_id(gcache, nm->st.st_gid)->name);
     327  		}
     328  #ifdef HAVE_LIBSELINUX
     329  		if (flags & NAMEI_CONTEXT) {
     330  			if (nm->context)
     331  				printf(" %-*s", nm->context_len, nm->context);
     332  			else
     333  				printf(" ?");
     334  		}
     335  #endif
     336  		if (flags & NAMEI_VERTICAL)
     337  			for (i = 0; i < nm->level; i++)
     338  				fputs("  ", stdout);
     339  
     340  		if (S_ISLNK(nm->st.st_mode))
     341  			printf(" %s -> %s\n", nm->name,
     342  					nm->abslink + nm->relstart);
     343  		else
     344  			printf(" %s\n", nm->name);
     345  	}
     346  	return 0;
     347  }
     348  
     349  static void __attribute__((__noreturn__)) usage(void)
     350  {
     351  	const char *p = program_invocation_short_name;
     352  	FILE *out = stdout;
     353  
     354  	if (!*p)
     355  		p = "namei";
     356  
     357  	fputs(USAGE_HEADER, out);
     358  	fprintf(out,
     359  	      _(" %s [options] <pathname>...\n"), p);
     360  
     361  	fputs(USAGE_SEPARATOR, out);
     362  	fputs(_("Follow a pathname until a terminal point is found.\n"), out);
     363  
     364  	fputs(USAGE_OPTIONS, out);
     365  	fputs(_(
     366  		" -x, --mountpoints   show mount point directories with a 'D'\n"
     367  		" -m, --modes         show the mode bits of each file\n"
     368  		" -o, --owners        show owner and group name of each file\n"
     369  		" -l, --long          use a long listing format (-m -o -v) \n"
     370  		" -n, --nosymlinks    don't follow symlinks\n"
     371  		" -v, --vertical      vertical align of modes and owners\n"), out);
     372  #ifdef HAVE_LIBSELINUX
     373  	fputs(_( " -Z, --context       print any security context of each file \n"), out);
     374  #endif
     375  
     376  	printf(USAGE_HELP_OPTIONS(21));
     377  
     378  	printf(USAGE_MAN_TAIL("namei(1)"));
     379  	exit(EXIT_SUCCESS);
     380  }
     381  
     382  static const struct option longopts[] =
     383  {
     384  	{ "help",	 no_argument, NULL, 'h' },
     385  	{ "version",     no_argument, NULL, 'V' },
     386  	{ "mountpoints", no_argument, NULL, 'x' },
     387  	{ "modes",	 no_argument, NULL, 'm' },
     388  	{ "owners",	 no_argument, NULL, 'o' },
     389  	{ "long",        no_argument, NULL, 'l' },
     390  	{ "nolinks",	 no_argument, NULL, 'n' },
     391  	{ "vertical",    no_argument, NULL, 'v' },
     392  #ifdef HAVE_LIBSELINUX
     393  	{ "context",	 no_argument, NULL, 'Z' },
     394  #endif
     395  	{ NULL, 0, NULL, 0 },
     396  };
     397  
     398  int
     399  main(int argc, char **argv)
     400  {
     401  	int c;
     402  	int rc = EXIT_SUCCESS;
     403  	static const char *shortopts =
     404  #ifdef HAVE_LIBSELINUX
     405  		"Z"
     406  #endif
     407  		"hVlmnovx";
     408  
     409  	setlocale(LC_ALL, "");
     410  	bindtextdomain(PACKAGE, LOCALEDIR);
     411  	textdomain(PACKAGE);
     412  	close_stdout_atexit();
     413  
     414  	while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) {
     415  		switch(c) {
     416  		case 'l':
     417  			flags |= (NAMEI_OWNERS | NAMEI_MODES | NAMEI_VERTICAL);
     418  			break;
     419  		case 'm':
     420  			flags |= NAMEI_MODES;
     421  			break;
     422  		case 'n':
     423  			flags |= NAMEI_NOLINKS;
     424  			break;
     425  		case 'o':
     426  			flags |= NAMEI_OWNERS;
     427  			break;
     428  		case 'x':
     429  			flags |= NAMEI_MNTS;
     430  			break;
     431  		case 'v':
     432  			flags |= NAMEI_VERTICAL;
     433  			break;
     434  #ifdef HAVE_LIBSELINUX
     435  		case 'Z':
     436  			flags |= NAMEI_CONTEXT;
     437  			break;
     438  #endif
     439  		case 'h':
     440  			usage();
     441  		case 'V':
     442  			print_version(EXIT_SUCCESS);
     443  		default:
     444  			errtryhelp(EXIT_FAILURE);
     445  		}
     446  	}
     447  
     448  	if (optind == argc) {
     449  		warnx(_("pathname argument is missing"));
     450  		errtryhelp(EXIT_FAILURE);
     451  	}
     452  
     453  	ucache = new_idcache();
     454  	if (!ucache)
     455  		err(EXIT_FAILURE, _("failed to allocate UID cache"));
     456  	gcache = new_idcache();
     457  	if (!gcache)
     458  		err(EXIT_FAILURE, _("failed to allocate GID cache"));
     459  
     460  	for(; optind < argc; optind++) {
     461  		char *path = argv[optind];
     462  		struct namei *nm = NULL;
     463  		struct stat st;
     464  
     465  		if (stat(path, &st) != 0)
     466  			rc = EXIT_FAILURE;
     467  
     468  		nm = add_namei(NULL, path, 0, NULL);
     469  		if (nm) {
     470  			int sml = 0;
     471  			if (!(flags & NAMEI_NOLINKS))
     472  				sml = follow_symlinks(nm);
     473  			if (print_namei(nm, path)) {
     474  				rc = EXIT_FAILURE;
     475  				continue;
     476  			}
     477  			free_namei(nm);
     478  			if (sml == -1) {
     479  				rc = EXIT_FAILURE;
     480  				warnx(_("%s: exceeded limit of symlinks"), path);
     481  				continue;
     482  			}
     483  		}
     484  	}
     485  
     486  	free_idcache(ucache);
     487  	free_idcache(gcache);
     488  
     489  	return rc;
     490  }
     491