]> git.ipfire.org Git - thirdparty/util-linux.git/blame - misc-utils/namei.c
libblkid: make example more robust
[thirdparty/util-linux.git] / misc-utils / namei.c
CommitLineData
c84a633a
KZ
1/*
2 * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
3 *
601d12fb 4 * This file is part of util-linux.
c84a633a
KZ
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 *
b50945d4 16 * The original namei(1) was written by:
c84a633a
KZ
17 * Roger S. Southwick (May 2, 1990)
18 * Steve Tell (March 28, 1991)
b50945d4 19 * Arkadiusz Miƛkiewicz (1999-02-22)
c84a633a
KZ
20 * Li Zefan (2007-09-10).
21 */
6dbe3af9 22#include <stdio.h>
fd6b7a7f 23#include <unistd.h>
c84a633a 24#include <getopt.h>
fd6b7a7f 25#include <string.h>
66ee8158 26#include <stdlib.h>
22853e4a 27#include <errno.h>
6dbe3af9
KZ
28#include <sys/types.h>
29#include <sys/stat.h>
30#include <sys/param.h>
4cfcf09b
KZ
31#include <pwd.h>
32#include <grp.h>
fd03d92d 33
e928e8ed
KZ
34#ifdef HAVE_LIBSELINUX
35# include <selinux/selinux.h>
36#endif
37
fd03d92d 38#include "c.h"
87f3feac 39#include "xalloc.h"
7eda085c 40#include "nls.h"
4cfcf09b 41#include "widechar.h"
ce877f2d 42#include "strutils.h"
c05a80ca 43#include "closestream.h"
04a5cb58 44#include "idcache.h"
6dbe3af9 45
6dbe3af9
KZ
46#ifndef MAXSYMLINKS
47#define MAXSYMLINKS 256
48#endif
49
c84a633a
KZ
50#define NAMEI_NOLINKS (1 << 1)
51#define NAMEI_MODES (1 << 2)
52#define NAMEI_MNTS (1 << 3)
4cfcf09b 53#define NAMEI_OWNERS (1 << 4)
c3ecdb3e 54#define NAMEI_VERTICAL (1 << 5)
e928e8ed 55#define NAMEI_CONTEXT (1 << 6)
6dbe3af9 56
db8a3e23 57
c84a633a
KZ
58struct 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;
c21e7023 65 int mountpoint; /* is mount point */
e19cc7b6 66 int noent; /* this item not existing (stores errno from stat()) */
e928e8ed
KZ
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
c84a633a 71};
6dbe3af9 72
4cfcf09b 73static int flags;
4cfcf09b
KZ
74static struct idcache *gcache; /* groupnames */
75static struct idcache *ucache; /* usernames */
76
c84a633a
KZ
77static void
78free_namei(struct namei *nm)
79{
80 while (nm) {
81 struct namei *next = nm->next;
e928e8ed
KZ
82#ifdef HAVE_LIBSELINUX
83 free(nm->context);
84#endif
c84a633a
KZ
85 free(nm->name);
86 free(nm->abslink);
87 free(nm);
88 nm = next;
6dbe3af9 89 }
6dbe3af9
KZ
90}
91
22853e4a 92static void
c84a633a
KZ
93readlink_to_namei(struct namei *nm, const char *path)
94{
95 char sym[PATH_MAX];
eb9a65bb 96 ssize_t sz;
89394013 97 int isrel = 0;
c84a633a
KZ
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
89394013
KZ
105 if (p) {
106 isrel = 1;
38efd1e2 107 nm->relstart = p - path;
d1dca710 108 sz += nm->relstart + 1;
89394013 109 }
c84a633a 110 }
87f3feac 111 nm->abslink = xmalloc(sz + 1);
c84a633a 112
403cd108 113 if (isrel) {
f7ed29a7 114 /* create the absolute path from the relative symlink */
c84a633a
KZ
115 memcpy(nm->abslink, path, nm->relstart);
116 *(nm->abslink + nm->relstart) = '/';
117 nm->relstart++;
f7ed29a7 118 memcpy(nm->abslink + nm->relstart, sym, sz - nm->relstart);
c84a633a 119 } else
89394013
KZ
120 /* - absolute link (foo -> /path/bar)
121 * - or link without any subdir (foo -> bar)
122 */
c84a633a 123 memcpy(nm->abslink, sym, sz);
89394013 124
c84a633a 125 nm->abslink[sz] = '\0';
6dbe3af9
KZ
126}
127
c21e7023
KZ
128static struct stat *
129dotdot_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);
b9779517 140 path = xmalloc(len + sizeof(DOTDOTDIR));
c21e7023
KZ
141
142 memcpy(path, dirname, len);
143 memcpy(path + len, DOTDOTDIR, sizeof(DOTDOTDIR));
144
145 if (stat(path, st))
fc14ceba 146 err(EXIT_FAILURE, _("stat of %s failed"), path);
c21e7023
KZ
147 free(path);
148 return st;
149}
150
c84a633a
KZ
151static struct namei *
152new_namei(struct namei *parent, const char *path, const char *fname, int lev)
153{
154 struct namei *nm;
155
156 if (!fname)
157 return NULL;
b9779517 158 nm = xcalloc(1, sizeof(*nm));
c84a633a
KZ
159 if (parent)
160 parent->next = nm;
161
162 nm->level = lev;
b9779517 163 nm->name = xstrdup(fname);
c49e31f4 164
e928e8ed
KZ
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
e19cc7b6
KZ
170 if (lstat(path, &nm->st) != 0) {
171 nm->noent = errno;
c49e31f4 172 return nm;
e19cc7b6 173 }
a439d194
KZ
174
175 if (S_ISLNK(nm->st.st_mode))
176 readlink_to_namei(nm, path);
177 if (flags & NAMEI_OWNERS) {
04a5cb58
KZ
178 add_uid(ucache, nm->st.st_uid);
179 add_gid(gcache, nm->st.st_gid);
a439d194
KZ
180 }
181
c21e7023
KZ
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
c84a633a
KZ
195 return nm;
196}
6dbe3af9 197
c84a633a
KZ
198static struct namei *
199add_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;
6dbe3af9 210 }
b9779517 211 path = xstrdup(orgpath);
c84a633a
KZ
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);
6dbe3af9 219 }
6dbe3af9 220
c84a633a
KZ
221 for (end = fname; fname && end; ) {
222 /* set end of filename */
922a121b
KZ
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;
c84a633a
KZ
232 if (!first)
233 first = nm;
c84a633a
KZ
234 /* set begin of the next filename */
235 if (end) {
236 *end++ = '/';
237 while (*end == '/')
238 end++; /* eat extra '/' */
239 }
240 fname = end;
e8f26419
KZ
241 }
242
c84a633a
KZ
243 if (last)
244 *last = nm;
bf13602c
DB
245
246 free(path);
247
c84a633a
KZ
248 return first;
249}
6dbe3af9 250
c84a633a
KZ
251static int
252follow_symlinks(struct namei *nm)
253{
254 int symcount = 0;
6dbe3af9 255
c84a633a
KZ
256 for (; nm; nm = nm->next) {
257 struct namei *next, *last;
6dbe3af9 258
c49e31f4
SK
259 if (nm->noent)
260 continue;
c84a633a
KZ
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;
d8ef4c19 275 }
c84a633a
KZ
276 return 0;
277}
d8ef4c19 278
c49e31f4 279static int
c84a633a
KZ
280print_namei(struct namei *nm, char *path)
281{
c84a633a 282 int i;
d8ef4c19 283
c84a633a
KZ
284 if (path)
285 printf("f: %s\n", path);
6dbe3af9 286
8fec558b 287 for (; nm; nm = nm->next) {
c84a633a 288 char md[11];
db8a3e23 289
c49e31f4 290 if (nm->noent) {
cd804450
SK
291 int blanks = 1;
292 if (flags & NAMEI_MODES)
293 blanks += 9;
294 if (flags & NAMEI_OWNERS)
04a5cb58 295 blanks += ucache->width + gcache->width + 2;
cd804450
SK
296 if (!(flags & NAMEI_VERTICAL))
297 blanks += 1;
e928e8ed
KZ
298 if (!(flags & NAMEI_CONTEXT))
299 blanks += 1;
cd804450
SK
300 blanks += nm->level * 2;
301 printf("%*s ", blanks, "");
14056588 302 printf("%s - %s\n", nm->name, strerror(nm->noent));
c49e31f4
SK
303 return -1;
304 }
305
b0b24b11 306 xstrmode(nm->st.st_mode, md);
6dbe3af9 307
c21e7023 308 if (nm->mountpoint)
c84a633a 309 md[0] = 'D';
6dbe3af9 310
c3ecdb3e
KZ
311 if (!(flags & NAMEI_VERTICAL)) {
312 for (i = 0; i < nm->level; i++)
313 fputs(" ", stdout);
314 fputc(' ', stdout);
315 }
6dbe3af9 316
c84a633a 317 if (flags & NAMEI_MODES)
c3ecdb3e 318 printf("%s", md);
6dbe3af9 319 else
c3ecdb3e 320 printf("%c", md[0]);
db8a3e23 321
4cfcf09b 322 if (flags & NAMEI_OWNERS) {
04a5cb58 323 printf(" %-*s", ucache->width,
4cfcf09b 324 get_id(ucache, nm->st.st_uid)->name);
04a5cb58 325 printf(" %-*s", gcache->width,
4cfcf09b
KZ
326 get_id(gcache, nm->st.st_gid)->name);
327 }
e928e8ed
KZ
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
c3ecdb3e
KZ
336 if (flags & NAMEI_VERTICAL)
337 for (i = 0; i < nm->level; i++)
338 fputs(" ", stdout);
339
c84a633a
KZ
340 if (S_ISLNK(nm->st.st_mode))
341 printf(" %s -> %s\n", nm->name,
342 nm->abslink + nm->relstart);
6dbe3af9 343 else
c84a633a
KZ
344 printf(" %s\n", nm->name);
345 }
c49e31f4 346 return 0;
c84a633a 347}
db8a3e23 348
5118d1be 349static void __attribute__((__noreturn__)) usage(void)
c84a633a
KZ
350{
351 const char *p = program_invocation_short_name;
5118d1be 352 FILE *out = stdout;
6dbe3af9 353
c84a633a
KZ
354 if (!*p)
355 p = "namei";
f062c8a6 356
db433bf7 357 fputs(USAGE_HEADER, out);
54f07289 358 fprintf(out,
09af3db4 359 _(" %s [options] <pathname>...\n"), p);
451dbcfa
BS
360
361 fputs(USAGE_SEPARATOR, out);
362 fputs(_("Follow a pathname until a terminal point is found.\n"), out);
363
db433bf7 364 fputs(USAGE_OPTIONS, out);
b3054454 365 fputs(_(
54f07289
KZ
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);
e928e8ed
KZ
372#ifdef HAVE_LIBSELINUX
373 fputs(_( " -Z, --context print any security context of each file \n"), out);
374#endif
375
bad4c729 376 fprintf(out, USAGE_HELP_OPTIONS(21));
54f07289 377
bad4c729 378 fprintf(out, USAGE_MAN_TAIL("namei(1)"));
5118d1be 379 exit(EXIT_SUCCESS);
6dbe3af9
KZ
380}
381
6c7d5ae9 382static const struct option longopts[] =
c84a633a 383{
87918040
SK
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' },
e928e8ed
KZ
392#ifdef HAVE_LIBSELINUX
393 { "context", no_argument, NULL, 'Z' },
394#endif
87918040 395 { NULL, 0, NULL, 0 },
c84a633a 396};
6dbe3af9 397
c84a633a
KZ
398int
399main(int argc, char **argv)
400{
c84a633a 401 int c;
c49e31f4 402 int rc = EXIT_SUCCESS;
e928e8ed
KZ
403 static const char *shortopts =
404#ifdef HAVE_LIBSELINUX
405 "Z"
406#endif
407 "hVlmnovx";
c84a633a
KZ
408
409 setlocale(LC_ALL, "");
410 bindtextdomain(PACKAGE, LOCALEDIR);
411 textdomain(PACKAGE);
2c308875 412 close_stdout_atexit();
c84a633a 413
e928e8ed 414 while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) {
c84a633a 415 switch(c) {
4cfcf09b 416 case 'l':
c3ecdb3e 417 flags |= (NAMEI_OWNERS | NAMEI_MODES | NAMEI_VERTICAL);
4cfcf09b 418 break;
c84a633a
KZ
419 case 'm':
420 flags |= NAMEI_MODES;
421 break;
c84a633a
KZ
422 case 'n':
423 flags |= NAMEI_NOLINKS;
424 break;
4cfcf09b
KZ
425 case 'o':
426 flags |= NAMEI_OWNERS;
427 break;
428 case 'x':
429 flags |= NAMEI_MNTS;
430 break;
c3ecdb3e
KZ
431 case 'v':
432 flags |= NAMEI_VERTICAL;
5a0d9255 433 break;
e928e8ed
KZ
434#ifdef HAVE_LIBSELINUX
435 case 'Z':
436 flags |= NAMEI_CONTEXT;
437 break;
438#endif
2c308875
KZ
439 case 'h':
440 usage();
441 case 'V':
442 print_version(EXIT_SUCCESS);
5a0d9255 443 default:
677ec86c 444 errtryhelp(EXIT_FAILURE);
c84a633a 445 }
6dbe3af9 446 }
c84a633a 447
5a0d9255
SK
448 if (optind == argc) {
449 warnx(_("pathname argument is missing"));
5118d1be 450 errtryhelp(EXIT_FAILURE);
5a0d9255
SK
451 }
452
04a5cb58
KZ
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
c84a633a
KZ
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)
c49e31f4 466 rc = EXIT_FAILURE;
c84a633a
KZ
467
468 nm = add_namei(NULL, path, 0, NULL);
469 if (nm) {
470 int sml = 0;
c84a633a
KZ
471 if (!(flags & NAMEI_NOLINKS))
472 sml = follow_symlinks(nm);
c49e31f4
SK
473 if (print_namei(nm, path)) {
474 rc = EXIT_FAILURE;
475 continue;
476 }
c84a633a 477 free_namei(nm);
c49e31f4
SK
478 if (sml == -1) {
479 rc = EXIT_FAILURE;
480 warnx(_("%s: exceeded limit of symlinks"), path);
481 continue;
482 }
c84a633a 483 }
6dbe3af9
KZ
484 }
485
4cfcf09b
KZ
486 free_idcache(ucache);
487 free_idcache(gcache);
488
c49e31f4 489 return rc;
6dbe3af9 490}