]> git.ipfire.org Git - thirdparty/util-linux.git/blob - misc-utils/whereis.c
whereis: add --help and --version
[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 static char uflag = 0;
76
77 /* supported types */
78 enum {
79 BIN_DIR = (1 << 1),
80 MAN_DIR = (1 << 2),
81 SRC_DIR = (1 << 3),
82
83 ALL_DIRS = BIN_DIR | MAN_DIR | SRC_DIR
84 };
85
86 /* directories */
87 struct wh_dirlist {
88 int type;
89 dev_t st_dev;
90 ino_t st_ino;
91 char *path;
92
93 struct wh_dirlist *next;
94 };
95
96 static const char *bindirs[] = {
97 "/usr/bin",
98 "/usr/sbin",
99 "/usr/lib",
100 "/usr/lib64",
101 "/bin",
102 "/sbin",
103 "/etc",
104 "/usr/etc",
105 "/lib",
106 "/lib64",
107 "/usr/games",
108 "/usr/games/bin",
109 "/usr/games/lib",
110 "/usr/emacs/etc",
111 "/usr/lib/emacs/*/etc",
112 "/usr/TeX/bin",
113 "/usr/tex/bin",
114 "/usr/interviews/bin/LINUX",
115
116 "/usr/X11R6/bin",
117 "/usr/X386/bin",
118 "/usr/bin/X11",
119 "/usr/X11/bin",
120 "/usr/X11R5/bin",
121
122 "/usr/local/bin",
123 "/usr/local/sbin",
124 "/usr/local/etc",
125 "/usr/local/lib",
126 "/usr/local/games",
127 "/usr/local/games/bin",
128 "/usr/local/emacs/etc",
129 "/usr/local/TeX/bin",
130 "/usr/local/tex/bin",
131 "/usr/local/bin/X11",
132
133 "/usr/contrib",
134 "/usr/hosts",
135 "/usr/include",
136
137 "/usr/g++-include",
138
139 "/usr/ucb",
140 "/usr/old",
141 "/usr/new",
142 "/usr/local",
143 "/usr/libexec",
144 "/usr/share",
145
146 "/opt/*/bin",
147 NULL
148 };
149
150 static const char *mandirs[] = {
151 "/usr/man/*",
152 "/usr/share/man/*",
153 "/usr/X386/man/*",
154 "/usr/X11/man/*",
155 "/usr/TeX/man/*",
156 "/usr/interviews/man/mann",
157 "/usr/share/info",
158 NULL
159 };
160
161 static const char *srcdirs[] = {
162 "/usr/src/*",
163 "/usr/src/lib/libc/*",
164 "/usr/src/lib/libc/net/*",
165 "/usr/src/ucb/pascal",
166 "/usr/src/ucb/pascal/utilities",
167 "/usr/src/undoc",
168 NULL
169 };
170
171 static void whereis_init_debug(void)
172 {
173 __UL_INIT_DEBUG(whereis, WHEREIS_DEBUG_, 0, WHEREIS_DEBUG);
174 }
175
176 static const char *whereis_type_to_name(int type)
177 {
178 switch (type) {
179 case BIN_DIR: return "bin";
180 case MAN_DIR: return "man";
181 case SRC_DIR: return "src";
182 default: return "???";
183 }
184 }
185
186 static void __attribute__((__noreturn__)) usage(void)
187 {
188 FILE *out = stdout;
189
190 fputs(USAGE_HEADER, out);
191 fprintf(out, _(" %s [options] [-BMS <dir>... -f] <name>\n"), program_invocation_short_name);
192
193 fputs(USAGE_SEPARATOR, out);
194 fputs(_("Locate the binary, source, and manual-page files for a command.\n"), out);
195
196 fputs(USAGE_OPTIONS, out);
197 fputs(_(" -b search only for binaries\n"), out);
198 fputs(_(" -B <dirs> define binaries lookup path\n"), out);
199 fputs(_(" -m search only for manuals and infos\n"), out);
200 fputs(_(" -M <dirs> define man and info lookup path\n"), out);
201 fputs(_(" -s search only for sources\n"), out);
202 fputs(_(" -S <dirs> define sources lookup path\n"), out);
203 fputs(_(" -f terminate <dirs> argument list\n"), out);
204 fputs(_(" -u search for unusual entries\n"), out);
205 fputs(_(" -l output effective lookup paths\n"), out);
206
207 fputs(USAGE_SEPARATOR, out);
208 fputs(USAGE_HELP, out);
209 fputs(USAGE_VERSION, out);
210 fprintf(out, USAGE_MAN_TAIL("whereis(1)"));
211 exit(EXIT_SUCCESS);
212 }
213
214 static void dirlist_add_dir(struct wh_dirlist **ls0, int type, const char *dir)
215 {
216 struct stat st;
217 struct wh_dirlist *prev = NULL, *ls = *ls0;
218
219 if (access(dir, R_OK) != 0)
220 return;
221 if (stat(dir, &st) != 0 || !S_ISDIR(st.st_mode))
222 return;
223
224 while (ls) {
225 if (ls->st_ino == st.st_ino &&
226 ls->st_dev == st.st_dev &&
227 ls->type == type) {
228 DBG(LIST, ul_debugobj(*ls0, " ignore (already in list): %s", dir));
229 return;
230 }
231 prev = ls;
232 ls = ls->next;
233 }
234
235
236 ls = xcalloc(1, sizeof(*ls));
237 ls->st_ino = st.st_ino;
238 ls->st_dev = st.st_dev;
239 ls->type = type;
240 ls->path = canonicalize_path(dir);
241
242 if (!*ls0)
243 *ls0 = ls; /* first in the list */
244 else {
245 assert(prev);
246 prev->next = ls; /* add to the end of the list */
247 }
248
249 DBG(LIST, ul_debugobj(*ls0, " add dir: %s", ls->path));
250 return;
251 }
252
253 /* special case for '*' in the paths */
254 static void dirlist_add_subdir(struct wh_dirlist **ls, int type, const char *dir)
255 {
256 char buf[PATH_MAX], *d;
257 DIR *dirp;
258 struct dirent *dp;
259
260 strncpy(buf, dir, PATH_MAX);
261 buf[PATH_MAX - 1] = '\0';
262
263 d = strchr(buf, '*');
264 if (!d)
265 return;
266 *d = 0;
267
268 dirp = opendir(buf);
269 if (!dirp)
270 return;
271
272 DBG(LIST, ul_debugobj(*ls, " scanning subdir: %s", dir));
273
274 while ((dp = readdir(dirp)) != NULL) {
275 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
276 continue;
277 snprintf(d, PATH_MAX - (d - buf), "%s", dp->d_name);
278 /* a dir definition can have a star in middle of path */
279 strcat(buf, strchr(dir, '*') + 1);
280 dirlist_add_dir(ls, type, buf);
281 }
282 closedir(dirp);
283 return;
284 }
285
286 static void construct_dirlist_from_env(const char *env,
287 struct wh_dirlist **ls,
288 int type)
289 {
290 char *key = NULL, *tok = NULL, *pathcp, *path = getenv(env);
291
292 if (!path)
293 return;
294 pathcp = xstrdup(path);
295
296 DBG(ENV, ul_debugobj(*ls, "construct %s dirlist from: %s",
297 whereis_type_to_name(type), path));
298
299 for (tok = strtok_r(pathcp, ":", &key); tok;
300 tok = strtok_r(NULL, ":", &key))
301 dirlist_add_dir(ls, type, tok);
302
303 free(pathcp);
304 return;
305 }
306
307 static void construct_dirlist_from_argv(struct wh_dirlist **ls,
308 int *idx,
309 int argc,
310 char *argv[],
311 int type)
312 {
313 int i;
314
315 DBG(ARGV, ul_debugobj(*ls, "construct %s dirlist from argv[%d..]",
316 whereis_type_to_name(type), *idx));
317
318 for (i = *idx; i < argc; i++) {
319 if (*argv[i] == '-') /* end of the list */
320 break;
321
322 DBG(ARGV, ul_debugobj(*ls, " using argv[%d]: %s", *idx, argv[*idx]));
323 dirlist_add_dir(ls, type, argv[i]);
324 *idx = i;
325 }
326
327 return;
328 }
329
330 static void construct_dirlist(struct wh_dirlist **ls,
331 int type,
332 const char **paths)
333 {
334 size_t i;
335
336 DBG(STATIC, ul_debugobj(*ls, "construct %s dirlist from static array",
337 whereis_type_to_name(type)));
338
339 for (i = 0; paths[i]; i++) {
340 if (!strchr(paths[i], '*'))
341 dirlist_add_dir(ls, type, paths[i]);
342 else
343 dirlist_add_subdir(ls, type, paths[i]);
344 }
345 return;
346 }
347
348 static void free_dirlist(struct wh_dirlist **ls0, int type)
349 {
350 struct wh_dirlist *prev = NULL, *next, *ls = *ls0;
351
352 *ls0 = NULL;
353
354 DBG(LIST, ul_debugobj(*ls0, "free dirlist"));
355
356 while (ls) {
357 if (ls->type & type) {
358 next = ls->next;
359 DBG(LIST, ul_debugobj(*ls0, " free: %s", ls->path));
360 free(ls->path);
361 free(ls);
362 ls = next;
363 if (prev)
364 prev->next = ls;
365 } else {
366 if (!prev)
367 *ls0 = ls; /* first unremoved */
368 prev = ls;
369 ls = ls->next;
370 }
371 }
372
373 return;
374 }
375
376
377 static int filename_equal(const char *cp, const char *dp)
378 {
379 int i = strlen(dp);
380
381 DBG(SEARCH, ul_debug("compare '%s' and '%s'", cp, dp));
382
383 if (dp[0] == 's' && dp[1] == '.' && filename_equal(cp, dp + 2))
384 return 1;
385 if (!strcmp(dp + i - 2, ".Z"))
386 i -= 2;
387 else if (!strcmp(dp + i - 3, ".gz"))
388 i -= 3;
389 else if (!strcmp(dp + i - 3, ".xz"))
390 i -= 3;
391 else if (!strcmp(dp + i - 4, ".bz2"))
392 i -= 4;
393 while (*cp && *dp && *cp == *dp)
394 cp++, dp++, i--;
395 if (*cp == 0 && *dp == 0)
396 return 1;
397 while (isdigit(*dp))
398 dp++;
399 if (*cp == 0 && *dp++ == '.') {
400 --i;
401 while (i > 0 && *dp)
402 if (--i, *dp++ == '.')
403 return (*dp++ == 'C' && *dp++ == 0);
404 return 1;
405 }
406 return 0;
407 }
408
409 static void findin(const char *dir, const char *pattern, int *count, char **wait)
410 {
411 DIR *dirp;
412 struct dirent *dp;
413
414 dirp = opendir(dir);
415 if (dirp == NULL)
416 return;
417
418 DBG(SEARCH, ul_debug("find '%s' in '%s'", pattern, dir));
419
420 while ((dp = readdir(dirp)) != NULL) {
421 if (!filename_equal(pattern, dp->d_name))
422 continue;
423
424 if (uflag && *count == 0)
425 xasprintf(wait, "%s/%s", dir, dp->d_name);
426
427 else if (uflag && *count == 1 && *wait) {
428 printf("%s: %s %s/%s", pattern, *wait, dir, dp->d_name);
429 free(*wait);
430 *wait = NULL;
431 } else
432 printf(" %s/%s", dir, dp->d_name);
433 ++(*count);
434 }
435 closedir(dirp);
436 return;
437 }
438
439 static void lookup(const char *pattern, struct wh_dirlist *ls, int want)
440 {
441 char patbuf[PATH_MAX];
442 int count = 0;
443 char *wait = NULL, *p;
444
445 /* canonicalize pattern -- remove path suffix etc. */
446 p = strrchr(pattern, '/');
447 p = p ? p + 1 : (char *) pattern;
448 strncpy(patbuf, p, PATH_MAX);
449 patbuf[PATH_MAX - 1] = '\0';
450
451 DBG(SEARCH, ul_debug("lookup dirs for '%s' (%s), want: %s %s %s",
452 patbuf, pattern,
453 want & BIN_DIR ? "bin" : "",
454 want & MAN_DIR ? "min" : "",
455 want & SRC_DIR ? "src" : ""));
456 p = strrchr(patbuf, '.');
457 if (p)
458 *p = '\0';
459
460 if (!uflag)
461 /* if -u not specified then we always print the pattern */
462 printf("%s:", patbuf);
463
464 for (; ls; ls = ls->next) {
465 if ((ls->type & want) && ls->path)
466 findin(ls->path, patbuf, &count, &wait);
467 }
468
469 free(wait);
470
471 if (!uflag || count > 1)
472 putchar('\n');
473 return;
474 }
475
476 static void list_dirlist(struct wh_dirlist *ls)
477 {
478 while (ls) {
479 if (ls->path) {
480 switch (ls->type) {
481 case BIN_DIR:
482 printf("bin: ");
483 break;
484 case MAN_DIR:
485 printf("man: ");
486 break;
487 case SRC_DIR:
488 printf("src: ");
489 break;
490 default:
491 abort();
492 }
493 printf("%s\n", ls->path);
494 }
495 ls = ls->next;
496 }
497 }
498
499 int main(int argc, char **argv)
500 {
501 struct wh_dirlist *ls = NULL;
502 int want = ALL_DIRS;
503 int i, want_resetable = 0, opt_f_missing = 0;
504
505 setlocale(LC_ALL, "");
506 bindtextdomain(PACKAGE, LOCALEDIR);
507 textdomain(PACKAGE);
508 atexit(close_stdout);
509
510 if (argc <= 1) {
511 warnx(_("not enough arguments"));
512 errtryhelp(EXIT_FAILURE);
513 } else {
514 /* first arg may be one of our standard longopts */
515 if (!strcmp(argv[1], "--help"))
516 usage();
517 if (!strcmp(argv[1], "--version")) {
518 printf(UTIL_LINUX_VERSION);
519 exit(EXIT_SUCCESS);
520 }
521 }
522
523 whereis_init_debug();
524
525 construct_dirlist(&ls, BIN_DIR, bindirs);
526 construct_dirlist_from_env("PATH", &ls, BIN_DIR);
527
528 construct_dirlist(&ls, MAN_DIR, mandirs);
529 construct_dirlist_from_env("MANPATH", &ls, MAN_DIR);
530
531 construct_dirlist(&ls, SRC_DIR, srcdirs);
532
533 for (i = 1; i < argc; i++) {
534 const char *arg = argv[i];
535 int arg_i = i;
536
537 DBG(ARGV, ul_debug("argv[%d]: %s", i, arg));
538
539 if (*arg != '-') {
540 lookup(arg, ls, want);
541 /*
542 * The lookup mask ("want") is cumulative and it's
543 * resetable only when it has been already used.
544 *
545 * whereis -b -m foo :'foo' mask=BIN|MAN
546 * whereis -b foo bar :'foo' and 'bar' mask=BIN|MAN
547 * whereis -b foo -m bar :'foo' mask=BIN; 'bar' mask=MAN
548 */
549 want_resetable = 1;
550 continue;
551 }
552
553 for (++arg; arg && *arg; arg++) {
554 DBG(ARGV, ul_debug(" arg: %s", arg));
555
556 switch (*arg) {
557 case 'f':
558 opt_f_missing = 0;
559 break;
560 case 'u':
561 uflag = 1;
562 opt_f_missing = 0;
563 break;
564 case 'B':
565 if (*(arg + 1)) {
566 warnx(_("bad usage"));
567 errtryhelp(EXIT_FAILURE);
568 }
569 i++;
570 free_dirlist(&ls, BIN_DIR);
571 construct_dirlist_from_argv(
572 &ls, &i, argc, argv, BIN_DIR);
573 opt_f_missing = 1;
574 break;
575 case 'M':
576 if (*(arg + 1)) {
577 warnx(_("bad usage"));
578 errtryhelp(EXIT_FAILURE);
579 }
580 i++;
581 free_dirlist(&ls, MAN_DIR);
582 construct_dirlist_from_argv(
583 &ls, &i, argc, argv, MAN_DIR);
584 opt_f_missing = 1;
585 break;
586 case 'S':
587 if (*(arg + 1)) {
588 warnx(_("bad usage"));
589 errtryhelp(EXIT_FAILURE);
590 }
591 i++;
592 free_dirlist(&ls, SRC_DIR);
593 construct_dirlist_from_argv(
594 &ls, &i, argc, argv, SRC_DIR);
595 opt_f_missing = 1;
596 break;
597 case 'b':
598 if (want_resetable) {
599 want = ALL_DIRS;
600 want_resetable = 0;
601 }
602 want = want == ALL_DIRS ? BIN_DIR : want | BIN_DIR;
603 opt_f_missing = 0;
604 break;
605 case 'm':
606 if (want_resetable) {
607 want = ALL_DIRS;
608 want_resetable = 0;
609 }
610 want = want == ALL_DIRS ? MAN_DIR : want | MAN_DIR;
611 opt_f_missing = 0;
612 break;
613 case 's':
614 if (want_resetable) {
615 want = ALL_DIRS;
616 want_resetable = 0;
617 }
618 want = want == ALL_DIRS ? SRC_DIR : want | SRC_DIR;
619 opt_f_missing = 0;
620 break;
621 case 'l':
622 list_dirlist(ls);
623 break;
624 case 'V':
625 printf(UTIL_LINUX_VERSION);
626 return EXIT_SUCCESS;
627 case 'h':
628 usage();
629 default:
630 warnx(_("bad usage"));
631 errtryhelp(EXIT_FAILURE);
632 }
633
634 if (arg_i < i) /* moved to the next argv[] item */
635 break;
636 }
637 }
638
639 free_dirlist(&ls, ALL_DIRS);
640 if (opt_f_missing)
641 errx(EXIT_FAILURE, _("option -f is missing"));
642 return EXIT_SUCCESS;
643 }