]> git.ipfire.org Git - thirdparty/util-linux.git/blob - misc-utils/whereis.c
debd5da55f4904954635f2d623b5b7dcf7d0be40
[thirdparty/util-linux.git] / misc-utils / whereis.c
1 /*-
2 * Copyright (c) 1980 The Regents of the University of California.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 * must display the following acknowledgement:
15 * This product includes software developed by the University of
16 * California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 *
33 * 1999-02-22 Arkadiusz Miƛkiewicz <misiek@pld.ORG.PL>
34 * - added Native Language Support
35 * 2011-08-12 Davidlohr Bueso <dave@gnu.org>
36 * - added $PATH lookup
37 *
38 * Copyright (C) 2013 Karel Zak <kzak@redhat.com>
39 * 2013 Sami Kerola <kerolasa@iki.fi>
40 */
41
42 #include <sys/param.h>
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <dirent.h>
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <ctype.h>
50 #include <assert.h>
51
52 #include "xalloc.h"
53 #include "nls.h"
54 #include "c.h"
55 #include "closestream.h"
56 #include "canonicalize.h"
57
58 #include "debug.h"
59
60 static UL_DEBUG_DEFINE_MASK(whereis);
61 UL_DEBUG_DEFINE_MASKNAMES(whereis) = UL_DEBUG_EMPTY_MASKNAMES;
62
63 #define WHEREIS_DEBUG_INIT (1 << 1)
64 #define WHEREIS_DEBUG_PATH (1 << 2)
65 #define WHEREIS_DEBUG_ENV (1 << 3)
66 #define WHEREIS_DEBUG_ARGV (1 << 4)
67 #define WHEREIS_DEBUG_SEARCH (1 << 5)
68 #define WHEREIS_DEBUG_STATIC (1 << 6)
69 #define WHEREIS_DEBUG_LIST (1 << 7)
70 #define WHEREIS_DEBUG_ALL 0xFFFF
71
72 #define DBG(m, x) __UL_DBG(whereis, WHEREIS_DEBUG_, m, x)
73 #define ON_DBG(m, x) __UL_DBG_CALL(whereis, WHEREIS_DEBUG_, m, x)
74
75 #define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(whereis)
76 #include "debugobj.h"
77
78 static char uflag = 0;
79
80 /* supported types */
81 enum {
82 BIN_DIR = (1 << 1),
83 MAN_DIR = (1 << 2),
84 SRC_DIR = (1 << 3),
85
86 ALL_DIRS = BIN_DIR | MAN_DIR | SRC_DIR
87 };
88
89 /* directories */
90 struct wh_dirlist {
91 int type;
92 dev_t st_dev;
93 ino_t st_ino;
94 char *path;
95
96 struct wh_dirlist *next;
97 };
98
99 static const char *bindirs[] = {
100 "/usr/bin",
101 "/usr/sbin",
102 "/bin",
103 "/sbin",
104 #if defined(MULTIARCHTRIPLET)
105 "/lib/" MULTIARCHTRIPLET,
106 "/usr/lib/" MULTIARCHTRIPLET,
107 "/usr/local/lib/" MULTIARCHTRIPLET,
108 #endif
109 "/usr/lib",
110 "/usr/lib64",
111 "/etc",
112 "/usr/etc",
113 "/lib",
114 "/lib64",
115 "/usr/games",
116 "/usr/games/bin",
117 "/usr/games/lib",
118 "/usr/emacs/etc",
119 "/usr/lib/emacs/*/etc",
120 "/usr/TeX/bin",
121 "/usr/tex/bin",
122 "/usr/interviews/bin/LINUX",
123
124 "/usr/X11R6/bin",
125 "/usr/X386/bin",
126 "/usr/bin/X11",
127 "/usr/X11/bin",
128 "/usr/X11R5/bin",
129
130 "/usr/local/bin",
131 "/usr/local/sbin",
132 "/usr/local/etc",
133 "/usr/local/lib",
134 "/usr/local/games",
135 "/usr/local/games/bin",
136 "/usr/local/emacs/etc",
137 "/usr/local/TeX/bin",
138 "/usr/local/tex/bin",
139 "/usr/local/bin/X11",
140
141 "/usr/contrib",
142 "/usr/hosts",
143 "/usr/include",
144
145 "/usr/g++-include",
146
147 "/usr/ucb",
148 "/usr/old",
149 "/usr/new",
150 "/usr/local",
151 "/usr/libexec",
152 "/usr/share",
153
154 "/opt/*/bin",
155 NULL
156 };
157
158 static const char *mandirs[] = {
159 "/usr/man/*",
160 "/usr/share/man/*",
161 "/usr/X386/man/*",
162 "/usr/X11/man/*",
163 "/usr/TeX/man/*",
164 "/usr/interviews/man/mann",
165 "/usr/share/info",
166 NULL
167 };
168
169 static const char *srcdirs[] = {
170 "/usr/src/*",
171 "/usr/src/lib/libc/*",
172 "/usr/src/lib/libc/net/*",
173 "/usr/src/ucb/pascal",
174 "/usr/src/ucb/pascal/utilities",
175 "/usr/src/undoc",
176 NULL
177 };
178
179 static void whereis_init_debug(void)
180 {
181 __UL_INIT_DEBUG_FROM_ENV(whereis, WHEREIS_DEBUG_, 0, WHEREIS_DEBUG);
182 }
183
184 static const char *whereis_type_to_name(int type)
185 {
186 switch (type) {
187 case BIN_DIR: return "bin";
188 case MAN_DIR: return "man";
189 case SRC_DIR: return "src";
190 default: return "???";
191 }
192 }
193
194 static void __attribute__((__noreturn__)) usage(void)
195 {
196 FILE *out = stdout;
197
198 fputs(USAGE_HEADER, out);
199 fprintf(out, _(" %s [options] [-BMS <dir>... -f] <name>\n"), program_invocation_short_name);
200
201 fputs(USAGE_SEPARATOR, out);
202 fputs(_("Locate the binary, source, and manual-page files for a command.\n"), out);
203
204 fputs(USAGE_OPTIONS, out);
205 fputs(_(" -b search only for binaries\n"), out);
206 fputs(_(" -B <dirs> define binaries lookup path\n"), out);
207 fputs(_(" -m search only for manuals and infos\n"), out);
208 fputs(_(" -M <dirs> define man and info lookup path\n"), out);
209 fputs(_(" -s search only for sources\n"), out);
210 fputs(_(" -S <dirs> define sources lookup path\n"), out);
211 fputs(_(" -f terminate <dirs> argument list\n"), out);
212 fputs(_(" -u search for unusual entries\n"), out);
213 fputs(_(" -l output effective lookup paths\n"), out);
214
215 fputs(USAGE_SEPARATOR, out);
216 printf(USAGE_HELP_OPTIONS(16));
217 printf(USAGE_MAN_TAIL("whereis(1)"));
218 exit(EXIT_SUCCESS);
219 }
220
221 static void dirlist_add_dir(struct wh_dirlist **ls0, int type, const char *dir)
222 {
223 struct stat st;
224 struct wh_dirlist *prev = NULL, *ls = *ls0;
225
226 if (access(dir, R_OK) != 0)
227 return;
228 if (stat(dir, &st) != 0 || !S_ISDIR(st.st_mode))
229 return;
230
231 while (ls) {
232 if (ls->st_ino == st.st_ino &&
233 ls->st_dev == st.st_dev &&
234 ls->type == type) {
235 DBG(LIST, ul_debugobj(*ls0, " ignore (already in list): %s", dir));
236 return;
237 }
238 prev = ls;
239 ls = ls->next;
240 }
241
242
243 ls = xcalloc(1, sizeof(*ls));
244 ls->st_ino = st.st_ino;
245 ls->st_dev = st.st_dev;
246 ls->type = type;
247 ls->path = canonicalize_path(dir);
248
249 if (!*ls0)
250 *ls0 = ls; /* first in the list */
251 else {
252 assert(prev);
253 prev->next = ls; /* add to the end of the list */
254 }
255
256 DBG(LIST, ul_debugobj(*ls0, " add dir: %s", ls->path));
257 return;
258 }
259
260 /* special case for '*' in the paths */
261 static void dirlist_add_subdir(struct wh_dirlist **ls, int type, const char *dir)
262 {
263 char buf[PATH_MAX], *d;
264 DIR *dirp;
265 struct dirent *dp;
266
267 xstrncpy(buf, dir, PATH_MAX);
268
269 d = strchr(buf, '*');
270 if (!d)
271 return;
272 *d = 0;
273
274 dirp = opendir(buf);
275 if (!dirp)
276 return;
277
278 DBG(LIST, ul_debugobj(*ls, " scanning subdir: %s", dir));
279
280 while ((dp = readdir(dirp)) != NULL) {
281 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
282 continue;
283 snprintf(d, PATH_MAX - (d - buf), "%s", dp->d_name);
284 /* a dir definition can have a star in middle of path */
285 strcat(buf, strchr(dir, '*') + 1);
286 dirlist_add_dir(ls, type, buf);
287 }
288 closedir(dirp);
289 return;
290 }
291
292 static void construct_dirlist_from_env(const char *env,
293 struct wh_dirlist **ls,
294 int type)
295 {
296 char *key = NULL, *tok = NULL, *pathcp, *path = getenv(env);
297
298 if (!path)
299 return;
300 pathcp = xstrdup(path);
301
302 DBG(ENV, ul_debugobj(*ls, "construct %s dirlist from: %s",
303 whereis_type_to_name(type), path));
304
305 for (tok = strtok_r(pathcp, ":", &key); tok;
306 tok = strtok_r(NULL, ":", &key))
307 dirlist_add_dir(ls, type, tok);
308
309 free(pathcp);
310 return;
311 }
312
313 static void construct_dirlist_from_argv(struct wh_dirlist **ls,
314 int *idx,
315 int argc,
316 char *argv[],
317 int type)
318 {
319 int i;
320
321 DBG(ARGV, ul_debugobj(*ls, "construct %s dirlist from argv[%d..]",
322 whereis_type_to_name(type), *idx));
323
324 for (i = *idx; i < argc; i++) {
325 if (*argv[i] == '-') /* end of the list */
326 break;
327
328 DBG(ARGV, ul_debugobj(*ls, " using argv[%d]: %s", *idx, argv[*idx]));
329 dirlist_add_dir(ls, type, argv[i]);
330 *idx = i;
331 }
332
333 return;
334 }
335
336 static void construct_dirlist(struct wh_dirlist **ls,
337 int type,
338 const char **paths)
339 {
340 size_t i;
341
342 DBG(STATIC, ul_debugobj(*ls, "construct %s dirlist from static array",
343 whereis_type_to_name(type)));
344
345 for (i = 0; paths[i]; i++) {
346 if (!strchr(paths[i], '*'))
347 dirlist_add_dir(ls, type, paths[i]);
348 else
349 dirlist_add_subdir(ls, type, paths[i]);
350 }
351 return;
352 }
353
354 static void free_dirlist(struct wh_dirlist **ls0, int type)
355 {
356 struct wh_dirlist *prev = NULL, *next, *ls = *ls0;
357
358 *ls0 = NULL;
359
360 DBG(LIST, ul_debugobj(*ls0, "free dirlist"));
361
362 while (ls) {
363 if (ls->type & type) {
364 next = ls->next;
365 DBG(LIST, ul_debugobj(*ls0, " free: %s", ls->path));
366 free(ls->path);
367 free(ls);
368 ls = next;
369 if (prev)
370 prev->next = ls;
371 } else {
372 if (!prev)
373 *ls0 = ls; /* first unremoved */
374 prev = ls;
375 ls = ls->next;
376 }
377 }
378
379 return;
380 }
381
382
383 static int filename_equal(const char *cp, const char *dp)
384 {
385 int i = strlen(dp);
386
387 DBG(SEARCH, ul_debug("compare '%s' and '%s'", cp, dp));
388
389 if (dp[0] == 's' && dp[1] == '.' && filename_equal(cp, dp + 2))
390 return 1;
391 if (!strcmp(dp + i - 2, ".Z"))
392 i -= 2;
393 else if (!strcmp(dp + i - 3, ".gz"))
394 i -= 3;
395 else if (!strcmp(dp + i - 3, ".xz"))
396 i -= 3;
397 else if (!strcmp(dp + i - 4, ".bz2"))
398 i -= 4;
399 while (*cp && *dp && *cp == *dp)
400 cp++, dp++, i--;
401 if (*cp == 0 && *dp == 0)
402 return 1;
403 while (isdigit(*dp))
404 dp++;
405 if (*cp == 0 && *dp++ == '.') {
406 --i;
407 while (i > 0 && *dp)
408 if (--i, *dp++ == '.')
409 return (*dp++ == 'C' && *dp++ == 0);
410 return 1;
411 }
412 return 0;
413 }
414
415 static void findin(const char *dir, const char *pattern, int *count, char **wait)
416 {
417 DIR *dirp;
418 struct dirent *dp;
419
420 dirp = opendir(dir);
421 if (dirp == NULL)
422 return;
423
424 DBG(SEARCH, ul_debug("find '%s' in '%s'", pattern, dir));
425
426 while ((dp = readdir(dirp)) != NULL) {
427 if (!filename_equal(pattern, dp->d_name))
428 continue;
429
430 if (uflag && *count == 0)
431 xasprintf(wait, "%s/%s", dir, dp->d_name);
432
433 else if (uflag && *count == 1 && *wait) {
434 printf("%s: %s %s/%s", pattern, *wait, dir, dp->d_name);
435 free(*wait);
436 *wait = NULL;
437 } else
438 printf(" %s/%s", dir, dp->d_name);
439 ++(*count);
440 }
441 closedir(dirp);
442 return;
443 }
444
445 static void lookup(const char *pattern, struct wh_dirlist *ls, int want)
446 {
447 char patbuf[PATH_MAX];
448 int count = 0;
449 char *wait = NULL, *p;
450
451 /* canonicalize pattern -- remove path suffix etc. */
452 p = strrchr(pattern, '/');
453 p = p ? p + 1 : (char *) pattern;
454 xstrncpy(patbuf, p, PATH_MAX);
455
456 DBG(SEARCH, ul_debug("lookup dirs for '%s' (%s), want: %s %s %s",
457 patbuf, pattern,
458 want & BIN_DIR ? "bin" : "",
459 want & MAN_DIR ? "man" : "",
460 want & SRC_DIR ? "src" : ""));
461 p = strrchr(patbuf, '.');
462 if (p)
463 *p = '\0';
464
465 if (!uflag)
466 /* if -u not specified then we always print the pattern */
467 printf("%s:", patbuf);
468
469 for (; ls; ls = ls->next) {
470 if ((ls->type & want) && ls->path)
471 findin(ls->path, patbuf, &count, &wait);
472 }
473
474 free(wait);
475
476 if (!uflag || count > 1)
477 putchar('\n');
478 return;
479 }
480
481 static void list_dirlist(struct wh_dirlist *ls)
482 {
483 while (ls) {
484 if (ls->path) {
485 switch (ls->type) {
486 case BIN_DIR:
487 printf("bin: ");
488 break;
489 case MAN_DIR:
490 printf("man: ");
491 break;
492 case SRC_DIR:
493 printf("src: ");
494 break;
495 default:
496 abort();
497 }
498 printf("%s\n", ls->path);
499 }
500 ls = ls->next;
501 }
502 }
503
504 int main(int argc, char **argv)
505 {
506 struct wh_dirlist *ls = NULL;
507 int want = ALL_DIRS;
508 int i, want_resetable = 0, opt_f_missing = 0;
509
510 setlocale(LC_ALL, "");
511 bindtextdomain(PACKAGE, LOCALEDIR);
512 textdomain(PACKAGE);
513 close_stdout_atexit();
514
515 if (argc <= 1) {
516 warnx(_("not enough arguments"));
517 errtryhelp(EXIT_FAILURE);
518 } else {
519 /* first arg may be one of our standard longopts */
520 if (!strcmp(argv[1], "--help"))
521 usage();
522 if (!strcmp(argv[1], "--version"))
523 print_version(EXIT_SUCCESS);
524 }
525
526 whereis_init_debug();
527
528 construct_dirlist(&ls, BIN_DIR, bindirs);
529 construct_dirlist_from_env("PATH", &ls, BIN_DIR);
530
531 construct_dirlist(&ls, MAN_DIR, mandirs);
532 construct_dirlist_from_env("MANPATH", &ls, MAN_DIR);
533
534 construct_dirlist(&ls, SRC_DIR, srcdirs);
535
536 for (i = 1; i < argc; i++) {
537 const char *arg = argv[i];
538 int arg_i = i;
539
540 DBG(ARGV, ul_debug("argv[%d]: %s", i, arg));
541
542 if (*arg != '-') {
543 lookup(arg, ls, want);
544 /*
545 * The lookup mask ("want") is cumulative and it's
546 * resettable only when it has been already used.
547 *
548 * whereis -b -m foo :'foo' mask=BIN|MAN
549 * whereis -b foo bar :'foo' and 'bar' mask=BIN|MAN
550 * whereis -b foo -m bar :'foo' mask=BIN; 'bar' mask=MAN
551 */
552 want_resetable = 1;
553 continue;
554 }
555
556 for (++arg; arg && *arg; arg++) {
557 DBG(ARGV, ul_debug(" arg: %s", arg));
558
559 switch (*arg) {
560 case 'f':
561 opt_f_missing = 0;
562 break;
563 case 'u':
564 uflag = 1;
565 opt_f_missing = 0;
566 break;
567 case 'B':
568 if (*(arg + 1)) {
569 warnx(_("bad usage"));
570 errtryhelp(EXIT_FAILURE);
571 }
572 i++;
573 free_dirlist(&ls, BIN_DIR);
574 construct_dirlist_from_argv(
575 &ls, &i, argc, argv, BIN_DIR);
576 opt_f_missing = 1;
577 break;
578 case 'M':
579 if (*(arg + 1)) {
580 warnx(_("bad usage"));
581 errtryhelp(EXIT_FAILURE);
582 }
583 i++;
584 free_dirlist(&ls, MAN_DIR);
585 construct_dirlist_from_argv(
586 &ls, &i, argc, argv, MAN_DIR);
587 opt_f_missing = 1;
588 break;
589 case 'S':
590 if (*(arg + 1)) {
591 warnx(_("bad usage"));
592 errtryhelp(EXIT_FAILURE);
593 }
594 i++;
595 free_dirlist(&ls, SRC_DIR);
596 construct_dirlist_from_argv(
597 &ls, &i, argc, argv, SRC_DIR);
598 opt_f_missing = 1;
599 break;
600 case 'b':
601 if (want_resetable) {
602 want = ALL_DIRS;
603 want_resetable = 0;
604 }
605 want = want == ALL_DIRS ? BIN_DIR : want | BIN_DIR;
606 opt_f_missing = 0;
607 break;
608 case 'm':
609 if (want_resetable) {
610 want = ALL_DIRS;
611 want_resetable = 0;
612 }
613 want = want == ALL_DIRS ? MAN_DIR : want | MAN_DIR;
614 opt_f_missing = 0;
615 break;
616 case 's':
617 if (want_resetable) {
618 want = ALL_DIRS;
619 want_resetable = 0;
620 }
621 want = want == ALL_DIRS ? SRC_DIR : want | SRC_DIR;
622 opt_f_missing = 0;
623 break;
624 case 'l':
625 list_dirlist(ls);
626 break;
627
628 case 'V':
629 print_version(EXIT_SUCCESS);
630 case 'h':
631 usage();
632 default:
633 warnx(_("bad usage"));
634 errtryhelp(EXIT_FAILURE);
635 }
636
637 if (arg_i < i) /* moved to the next argv[] item */
638 break;
639 }
640 }
641
642 free_dirlist(&ls, ALL_DIRS);
643 if (opt_f_missing)
644 errx(EXIT_FAILURE, _("option -f is missing"));
645 return EXIT_SUCCESS;
646 }