]> git.ipfire.org Git - thirdparty/util-linux.git/blob - misc-utils/namei.c
namei: fix relative symlinks evaluation
[thirdparty/util-linux.git] / 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 writtent by:
17 * Roger S. Southwick (May 2, 1990)
18 * Steve Tell (March 28, 1991)
19 * Arkadiusz Mikiewicz (1999-02-22)
20 * Li Zefan (2007-09-10).
21 */
22
23 #include <stdio.h>
24 #include <unistd.h>
25 #include <getopt.h>
26 #include <string.h>
27 #include <strings.h>
28 #include <stdlib.h>
29 #include <errno.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <sys/param.h>
33 #include <pwd.h>
34 #include <grp.h>
35
36 #include "c.h"
37 #include "xalloc.h"
38 #include "nls.h"
39 #include "widechar.h"
40 #include "strutils.h"
41 #include "closestream.h"
42
43 #ifndef MAXSYMLINKS
44 #define MAXSYMLINKS 256
45 #endif
46
47 #ifndef LOGIN_NAME_MAX
48 #define LOGIN_NAME_MAX 256
49 #endif
50
51 #define NAMEI_NOLINKS (1 << 1)
52 #define NAMEI_MODES (1 << 2)
53 #define NAMEI_MNTS (1 << 3)
54 #define NAMEI_OWNERS (1 << 4)
55 #define NAMEI_VERTICAL (1 << 5)
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; /* is this item not existing */
67 };
68
69 struct idcache {
70 unsigned long int id;
71 char *name;
72 struct idcache *next;
73 };
74
75 static int flags;
76 static int uwidth; /* maximal width of username */
77 static int gwidth; /* maximal width of groupname */
78 static struct idcache *gcache; /* groupnames */
79 static struct idcache *ucache; /* usernames */
80
81 static struct idcache *
82 get_id(struct idcache *ic, unsigned long int id)
83 {
84 while(ic) {
85 if (ic->id == id)
86 return ic;
87 ic = ic->next;
88 }
89 return NULL;
90 }
91
92 static void
93 free_idcache(struct idcache *ic)
94 {
95 while(ic) {
96 struct idcache *next = ic->next;
97 free(ic->name);
98 free(ic);
99 ic = next;
100 }
101 }
102
103 static void
104 add_id(struct idcache **ic, char *name, unsigned long int id, int *width)
105 {
106 struct idcache *nc, *x;
107 int w = 0;
108
109 nc = xcalloc(1, sizeof(*nc));
110 nc->id = id;
111
112 if (name) {
113 #ifdef HAVE_WIDECHAR
114 wchar_t wc[LOGIN_NAME_MAX + 1];
115
116 if (mbstowcs(wc, name, LOGIN_NAME_MAX) > 0) {
117 wc[LOGIN_NAME_MAX] = '\0';
118 w = wcswidth(wc, LOGIN_NAME_MAX);
119 }
120 else
121 #endif
122 w = strlen(name);
123 }
124 /* note, we ignore names with non-printable widechars */
125 if (w > 0)
126 nc->name = xstrdup(name);
127 else if (xasprintf(&nc->name, "%lu", id) == -1)
128 nc->name = NULL;
129
130 for (x = *ic; x && x->next; x = x->next);
131
132 /* add 'nc' at end of the 'ic' list */
133 if (x)
134 x->next = nc;
135 else
136 *ic = nc;
137 if (w <= 0)
138 w = nc->name ? strlen(nc->name) : 0;
139
140 *width = *width < w ? w : *width;
141 return;
142 }
143
144 static void
145 add_uid(unsigned long int id)
146 {
147 struct idcache *ic = get_id(ucache, id);
148
149 if (!ic) {
150 struct passwd *pw = getpwuid((uid_t) id);
151 add_id(&ucache, pw ? pw->pw_name : NULL, id, &uwidth);
152 }
153 }
154
155 static void
156 add_gid(unsigned long int id)
157 {
158 struct idcache *ic = get_id(gcache, id);
159
160 if (!ic) {
161 struct group *gr = getgrgid((gid_t) id);
162 add_id(&gcache, gr ? gr->gr_name : NULL, id, &gwidth);
163 }
164 }
165
166 static void
167 free_namei(struct namei *nm)
168 {
169 while (nm) {
170 struct namei *next = nm->next;
171 free(nm->name);
172 free(nm->abslink);
173 free(nm);
174 nm = next;
175 }
176 }
177
178 static void
179 readlink_to_namei(struct namei *nm, const char *path)
180 {
181 char sym[PATH_MAX];
182 ssize_t sz;
183 int isrel = 0;
184
185 sz = readlink(path, sym, sizeof(sym));
186 if (sz < 1)
187 err(EXIT_FAILURE, _("failed to read symlink: %s"), path);
188 if (*sym != '/') {
189 char *p = strrchr(path, '/');
190
191 if (p) {
192 isrel = 1;
193 nm->relstart = p ? p - path : 0;
194 sz += nm->relstart + 1;
195 }
196 }
197 nm->abslink = xmalloc(sz + 1);
198
199 if (*sym != '/' && isrel) {
200 /* create the absolute path from the relative symlink */
201 memcpy(nm->abslink, path, nm->relstart);
202 *(nm->abslink + nm->relstart) = '/';
203 nm->relstart++;
204 memcpy(nm->abslink + nm->relstart, sym, sz - nm->relstart);
205 } else
206 /* - absolute link (foo -> /path/bar)
207 * - or link without any subdir (foo -> bar)
208 */
209 memcpy(nm->abslink, sym, sz);
210
211 nm->abslink[sz] = '\0';
212 }
213
214 static struct stat *
215 dotdot_stat(const char *dirname, struct stat *st)
216 {
217 char *path;
218 size_t len;
219
220 #define DOTDOTDIR "/.."
221
222 if (!dirname)
223 return NULL;
224
225 len = strlen(dirname);
226 path = xmalloc(len + sizeof(DOTDOTDIR));
227
228 memcpy(path, dirname, len);
229 memcpy(path + len, DOTDOTDIR, sizeof(DOTDOTDIR));
230
231 if (stat(path, st))
232 err(EXIT_FAILURE, _("could not stat '%s'"), path);
233 free(path);
234 return st;
235 }
236
237 static struct namei *
238 new_namei(struct namei *parent, const char *path, const char *fname, int lev)
239 {
240 struct namei *nm;
241
242 if (!fname)
243 return NULL;
244 nm = xcalloc(1, sizeof(*nm));
245 if (parent)
246 parent->next = nm;
247
248 nm->level = lev;
249 nm->name = xstrdup(fname);
250
251 nm->noent = (lstat(path, &nm->st) == -1);
252 if (nm->noent)
253 return nm;
254
255 if (S_ISLNK(nm->st.st_mode))
256 readlink_to_namei(nm, path);
257 if (flags & NAMEI_OWNERS) {
258 add_uid(nm->st.st_uid);
259 add_gid(nm->st.st_gid);
260 }
261
262 if ((flags & NAMEI_MNTS) && S_ISDIR(nm->st.st_mode)) {
263 struct stat stbuf, *sb = NULL;
264
265 if (parent && S_ISDIR(parent->st.st_mode))
266 sb = &parent->st;
267 else if (!parent || S_ISLNK(parent->st.st_mode))
268 sb = dotdot_stat(path, &stbuf);
269
270 if (sb && (sb->st_dev != nm->st.st_dev || /* different device */
271 sb->st_ino == nm->st.st_ino)) /* root directory */
272 nm->mountpoint = 1;
273 }
274
275 return nm;
276 }
277
278 static struct namei *
279 add_namei(struct namei *parent, const char *orgpath, int start, struct namei **last)
280 {
281 struct namei *nm = NULL, *first = NULL;
282 char *fname, *end, *path;
283 int level = 0;
284
285 if (!orgpath)
286 return NULL;
287 if (parent) {
288 nm = parent;
289 level = parent->level + 1;
290 }
291 path = xstrdup(orgpath);
292 fname = path + start;
293
294 /* root directory */
295 if (*fname == '/') {
296 while (*fname == '/')
297 fname++; /* eat extra '/' */
298 first = nm = new_namei(nm, "/", "/", level);
299 }
300
301 for (end = fname; fname && end; ) {
302 /* set end of filename */
303 if (*fname) {
304 end = strchr(fname, '/');
305 if (end)
306 *end = '\0';
307
308 /* create a new entry */
309 nm = new_namei(nm, path, fname, level);
310 } else
311 end = NULL;
312 if (!first)
313 first = nm;
314 /* set begin of the next filename */
315 if (end) {
316 *end++ = '/';
317 while (*end == '/')
318 end++; /* eat extra '/' */
319 }
320 fname = end;
321 }
322
323 if (last)
324 *last = nm;
325
326 free(path);
327
328 return first;
329 }
330
331 static int
332 follow_symlinks(struct namei *nm)
333 {
334 int symcount = 0;
335
336 for (; nm; nm = nm->next) {
337 struct namei *next, *last;
338
339 if (nm->noent)
340 continue;
341 if (!S_ISLNK(nm->st.st_mode))
342 continue;
343 if (++symcount > MAXSYMLINKS) {
344 /* drop the rest of the list */
345 free_namei(nm->next);
346 nm->next = NULL;
347 return -1;
348 }
349 next = nm->next;
350 nm->next = add_namei(nm, nm->abslink, nm->relstart, &last);
351 if (last)
352 last->next = next;
353 else
354 nm->next = next;
355 }
356 return 0;
357 }
358
359 static int
360 print_namei(struct namei *nm, char *path)
361 {
362 int i;
363
364 if (path)
365 printf("f: %s\n", path);
366
367 for (; nm; nm = nm->next) {
368 char md[11];
369
370 if (nm->noent) {
371 printf(_("%s - No such file or directory\n"), nm->name);
372 return -1;
373 }
374
375 strmode(nm->st.st_mode, md);
376
377 if (nm->mountpoint)
378 md[0] = 'D';
379
380 if (!(flags & NAMEI_VERTICAL)) {
381 for (i = 0; i < nm->level; i++)
382 fputs(" ", stdout);
383 fputc(' ', stdout);
384 }
385
386 if (flags & NAMEI_MODES)
387 printf("%s", md);
388 else
389 printf("%c", md[0]);
390
391 if (flags & NAMEI_OWNERS) {
392 printf(" %-*s", uwidth,
393 get_id(ucache, nm->st.st_uid)->name);
394 printf(" %-*s", gwidth,
395 get_id(gcache, nm->st.st_gid)->name);
396 }
397
398 if (flags & NAMEI_VERTICAL)
399 for (i = 0; i < nm->level; i++)
400 fputs(" ", stdout);
401
402 if (S_ISLNK(nm->st.st_mode))
403 printf(" %s -> %s\n", nm->name,
404 nm->abslink + nm->relstart);
405 else
406 printf(" %s\n", nm->name);
407 }
408 return 0;
409 }
410
411 static void usage(int rc)
412 {
413 const char *p = program_invocation_short_name;
414 FILE *out = rc == EXIT_FAILURE ? stderr : stdout;
415
416 if (!*p)
417 p = "namei";
418
419 fputs(_("\nUsage:\n"), out);
420 fprintf(out,
421 _(" %s [options] pathname [pathname ...]\n"), p);
422
423 fputs(_("\nOptions:\n"), out);
424 fputs(_(" -h, --help displays this help text\n"
425 " -V, --version output version information and exit\n"
426 " -x, --mountpoints show mount point directories with a 'D'\n"
427 " -m, --modes show the mode bits of each file\n"
428 " -o, --owners show owner and group name of each file\n"
429 " -l, --long use a long listing format (-m -o -v) \n"
430 " -n, --nosymlinks don't follow symlinks\n"
431 " -v, --vertical vertical align of modes and owners\n"), out);
432
433 fputs(_("\nFor more information see namei(1).\n"), out);
434 exit(rc);
435 }
436
437 static const struct option longopts[] =
438 {
439 { "help", 0, 0, 'h' },
440 { "version", 0, 0, 'V' },
441 { "mountpoints",0, 0, 'x' },
442 { "modes", 0, 0, 'm' },
443 { "owners", 0, 0, 'o' },
444 { "long", 0, 0, 'l' },
445 { "nolinks", 0, 0, 'n' },
446 { "vertical", 0, 0, 'v' },
447 { NULL, 0, 0, 0 },
448 };
449
450 int
451 main(int argc, char **argv)
452 {
453 int c;
454 int rc = EXIT_SUCCESS;
455
456 setlocale(LC_ALL, "");
457 bindtextdomain(PACKAGE, LOCALEDIR);
458 textdomain(PACKAGE);
459 atexit(close_stdout);
460
461 while ((c = getopt_long(argc, argv, "hVlmnovx", longopts, NULL)) != -1) {
462 switch(c) {
463 case 'h':
464 usage(EXIT_SUCCESS);
465 break;
466 case 'V':
467 printf(_("%s from %s\n"), program_invocation_short_name,
468 PACKAGE_STRING);
469 return EXIT_SUCCESS;
470 case 'l':
471 flags |= (NAMEI_OWNERS | NAMEI_MODES | NAMEI_VERTICAL);
472 break;
473 case 'm':
474 flags |= NAMEI_MODES;
475 break;
476 case 'n':
477 flags |= NAMEI_NOLINKS;
478 break;
479 case 'o':
480 flags |= NAMEI_OWNERS;
481 break;
482 case 'x':
483 flags |= NAMEI_MNTS;
484 break;
485 case 'v':
486 flags |= NAMEI_VERTICAL;
487 break;
488 default:
489 usage(EXIT_FAILURE);
490 }
491 }
492
493 if (optind == argc) {
494 warnx(_("pathname argument is missing"));
495 usage(EXIT_FAILURE);
496 }
497
498 for(; optind < argc; optind++) {
499 char *path = argv[optind];
500 struct namei *nm = NULL;
501 struct stat st;
502
503 if (stat(path, &st) != 0)
504 rc = EXIT_FAILURE;
505
506 nm = add_namei(NULL, path, 0, NULL);
507 if (nm) {
508 int sml = 0;
509 if (!(flags & NAMEI_NOLINKS))
510 sml = follow_symlinks(nm);
511 if (print_namei(nm, path)) {
512 rc = EXIT_FAILURE;
513 continue;
514 }
515 free_namei(nm);
516 if (sml == -1) {
517 rc = EXIT_FAILURE;
518 warnx(_("%s: exceeded limit of symlinks"), path);
519 continue;
520 }
521 }
522 }
523
524 free_idcache(ucache);
525 free_idcache(gcache);
526
527 return rc;
528 }
529