2 * Copyright (c) 1980 The Regents of the University of California.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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
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
38 * Copyright (C) 2013 Karel Zak <kzak@redhat.com>
39 * 2013 Sami Kerola <kerolasa@iki.fi>
42 #include <sys/param.h>
43 #include <sys/types.h>
58 #include "closestream.h"
59 #include "canonicalize.h"
63 static UL_DEBUG_DEFINE_MASK(whereis
);
64 UL_DEBUG_DEFINE_MASKNAMES(whereis
) = UL_DEBUG_EMPTY_MASKNAMES
;
66 #define WHEREIS_DEBUG_INIT (1 << 1)
67 #define WHEREIS_DEBUG_PATH (1 << 2)
68 #define WHEREIS_DEBUG_ENV (1 << 3)
69 #define WHEREIS_DEBUG_ARGV (1 << 4)
70 #define WHEREIS_DEBUG_SEARCH (1 << 5)
71 #define WHEREIS_DEBUG_STATIC (1 << 6)
72 #define WHEREIS_DEBUG_LIST (1 << 7)
73 #define WHEREIS_DEBUG_ALL 0xFFFF
75 #define DBG(m, x) __UL_DBG(whereis, WHEREIS_DEBUG_, m, x)
76 #define ON_DBG(m, x) __UL_DBG_CALL(whereis, WHEREIS_DEBUG_, m, x)
78 #define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(whereis)
90 ALL_DIRS
= BIN_DIR
| MAN_DIR
| SRC_DIR
100 struct wh_dirlist
*next
;
103 static const char *bindirs
[] = {
108 #if defined(MULTIARCHTRIPLET)
109 "/lib/" MULTIARCHTRIPLET
,
110 "/usr/lib/" MULTIARCHTRIPLET
,
111 "/usr/local/lib/" MULTIARCHTRIPLET
,
125 "/usr/lib/emacs/*/etc",
128 "/usr/interviews/bin/LINUX",
141 "/usr/local/games/bin",
142 "/usr/local/emacs/etc",
143 "/usr/local/TeX/bin",
144 "/usr/local/tex/bin",
145 "/usr/local/bin/X11",
164 static const char *mandirs
[] = {
170 "/usr/interviews/man/mann",
175 static const char *srcdirs
[] = {
177 "/usr/src/lib/libc/*",
178 "/usr/src/lib/libc/net/*",
179 "/usr/src/ucb/pascal",
180 "/usr/src/ucb/pascal/utilities",
185 static void whereis_init_debug(void)
187 __UL_INIT_DEBUG_FROM_ENV(whereis
, WHEREIS_DEBUG_
, 0, WHEREIS_DEBUG
);
190 static const char *whereis_type_to_name(int type
)
193 case BIN_DIR
: return "bin";
194 case MAN_DIR
: return "man";
195 case SRC_DIR
: return "src";
196 default: return "???";
200 static void __attribute__((__noreturn__
)) usage(void)
204 fputs(USAGE_HEADER
, out
);
205 fprintf(out
, _(" %s [options] [-BMS <dir>... -f] <name>\n"), program_invocation_short_name
);
207 fputs(USAGE_SEPARATOR
, out
);
208 fputs(_("Locate the binary, source, and manual-page files for a command.\n"), out
);
210 fputs(USAGE_OPTIONS
, out
);
211 fputs(_(" -b search only for binaries\n"), out
);
212 fputs(_(" -B <dirs> define binaries lookup path\n"), out
);
213 fputs(_(" -m search only for manuals and infos\n"), out
);
214 fputs(_(" -M <dirs> define man and info lookup path\n"), out
);
215 fputs(_(" -s search only for sources\n"), out
);
216 fputs(_(" -S <dirs> define sources lookup path\n"), out
);
217 fputs(_(" -f terminate <dirs> argument list\n"), out
);
218 fputs(_(" -u search for unusual entries\n"), out
);
219 fputs(_(" -g interpret name as glob (pathnames pattern)\n"), out
);
220 fputs(_(" -l output effective lookup paths\n"), out
);
222 fputs(USAGE_SEPARATOR
, out
);
223 fprintf(out
, USAGE_HELP_OPTIONS(16));
224 fprintf(out
, USAGE_MAN_TAIL("whereis(1)"));
228 static void dirlist_add_dir(struct wh_dirlist
**ls0
, int type
, const char *dir
)
231 struct wh_dirlist
*prev
= NULL
, *ls
= *ls0
;
233 if (access(dir
, R_OK
) != 0)
235 if (stat(dir
, &st
) != 0 || !S_ISDIR(st
.st_mode
))
239 if (ls
->st_ino
== st
.st_ino
&&
240 ls
->st_dev
== st
.st_dev
&&
242 DBG(LIST
, ul_debugobj(*ls0
, " ignore (already in list): %s", dir
));
250 ls
= xcalloc(1, sizeof(*ls
));
251 ls
->st_ino
= st
.st_ino
;
252 ls
->st_dev
= st
.st_dev
;
254 ls
->path
= canonicalize_path(dir
);
257 *ls0
= ls
; /* first in the list */
260 prev
->next
= ls
; /* add to the end of the list */
263 DBG(LIST
, ul_debugobj(*ls0
, " add dir: %s", ls
->path
));
266 /* special case for '*' in the paths */
267 static void dirlist_add_subdir(struct wh_dirlist
**ls
, int type
, const char *dir
)
269 char buf
[PATH_MAX
], *d
;
275 postfix
= strchr(dir
, '*');
279 /* copy begin of the path to the buffer (part before '*') */
280 len
= (postfix
- dir
) + 1;
281 xstrncpy(buf
, dir
, len
);
283 /* remember place where to append subdirs */
291 /* open parental dir t scan */
296 DBG(LIST
, ul_debugobj(*ls
, " scanning subdirs: %s [%s<subdir>%s]",
297 dir
, buf
, postfix
? postfix
: ""));
299 while ((dp
= readdir(dirp
)) != NULL
) {
300 if (!strcmp(dp
->d_name
, ".") || !strcmp(dp
->d_name
, ".."))
303 snprintf(d
, PATH_MAX
- len
, "%s%s", dp
->d_name
, postfix
);
305 snprintf(d
, PATH_MAX
- len
, "%s", dp
->d_name
);
307 dirlist_add_dir(ls
, type
, buf
);
312 DBG(LIST
, ul_debugobj(*ls
, " ignore path: %s", dir
));
315 static void construct_dirlist_from_env(const char *env
,
316 struct wh_dirlist
**ls
,
319 char *key
= NULL
, *tok
= NULL
, *pathcp
, *path
= getenv(env
);
323 pathcp
= xstrdup(path
);
325 DBG(ENV
, ul_debugobj(*ls
, "construct %s dirlist from: %s",
326 whereis_type_to_name(type
), path
));
328 for (tok
= strtok_r(pathcp
, ":", &key
); tok
;
329 tok
= strtok_r(NULL
, ":", &key
))
330 dirlist_add_dir(ls
, type
, tok
);
335 static void construct_dirlist_from_argv(struct wh_dirlist
**ls
,
343 DBG(ARGV
, ul_debugobj(*ls
, "construct %s dirlist from argv[%d..]",
344 whereis_type_to_name(type
), *idx
));
346 for (i
= *idx
; i
< argc
; i
++) {
347 if (*argv
[i
] == '-') /* end of the list */
350 DBG(ARGV
, ul_debugobj(*ls
, " using argv[%d]: %s", *idx
, argv
[*idx
]));
351 dirlist_add_dir(ls
, type
, argv
[i
]);
356 static void construct_dirlist(struct wh_dirlist
**ls
,
362 DBG(STATIC
, ul_debugobj(*ls
, "construct %s dirlist from static array",
363 whereis_type_to_name(type
)));
365 for (i
= 0; paths
[i
]; i
++) {
366 if (!strchr(paths
[i
], '*'))
367 dirlist_add_dir(ls
, type
, paths
[i
]);
369 dirlist_add_subdir(ls
, type
, paths
[i
]);
373 static void free_dirlist(struct wh_dirlist
**ls0
, int type
)
375 struct wh_dirlist
*prev
= NULL
, *next
, *ls
= *ls0
;
379 DBG(LIST
, ul_debugobj(*ls0
, "free dirlist"));
382 if (ls
->type
& type
) {
384 DBG(LIST
, ul_debugobj(*ls0
, " free: %s", ls
->path
));
392 *ls0
= ls
; /* first unremoved */
400 static int filename_equal(const char *cp
, const char *dp
, int type
)
404 DBG(SEARCH
, ul_debug("compare '%s' and '%s'", cp
, dp
));
408 return fnmatch(cp
, dp
, 0) == 0;
410 if (type
& SRC_DIR
&&
411 dp
[0] == 's' && dp
[1] == '.' && filename_equal(cp
, dp
+ 2, type
))
416 if (type
& MAN_DIR
) {
417 if (i
> 1 && !strcmp(dp
+ i
- 2, ".Z"))
419 else if (i
> 2 && !strcmp(dp
+ i
- 3, ".gz"))
421 else if (i
> 2 && !strcmp(dp
+ i
- 3, ".xz"))
423 else if (i
> 3 && !strcmp(dp
+ i
- 4, ".bz2"))
425 else if (i
> 3 && !strcmp(dp
+ i
- 4, ".zst"))
428 while (*cp
&& *dp
&& *cp
== *dp
)
430 if (*cp
== 0 && *dp
== 0)
432 if (!(type
& BIN_DIR
) && *cp
== 0 && *dp
++ == '.') {
435 if (--i
, *dp
++ == '.')
436 return (*dp
++ == 'C' && *dp
++ == 0);
442 static void findin(const char *dir
, const char *pattern
, int *count
,
443 char **wait
, int type
)
452 DBG(SEARCH
, ul_debug("find '%s' in '%s'", pattern
, dir
));
454 while ((dp
= readdir(dirp
)) != NULL
) {
455 if (!filename_equal(pattern
, dp
->d_name
, type
))
458 if (uflag
&& *count
== 0)
459 xasprintf(wait
, "%s/%s", dir
, dp
->d_name
);
461 else if (uflag
&& *count
== 1 && *wait
) {
462 printf("%s: %s %s/%s", pattern
, *wait
, dir
, dp
->d_name
);
466 printf(" %s/%s", dir
, dp
->d_name
);
472 static void lookup(const char *pattern
, struct wh_dirlist
*ls
, int want
)
474 char patbuf
[PATH_MAX
];
476 char *wait
= NULL
, *p
;
478 /* canonicalize pattern -- remove path suffix etc. */
479 p
= strrchr(pattern
, '/');
480 p
= p
? p
+ 1 : (char *) pattern
;
481 xstrncpy(patbuf
, p
, PATH_MAX
);
483 DBG(SEARCH
, ul_debug("lookup dirs for '%s' (%s), want: %s %s %s",
485 want
& BIN_DIR
? "bin" : "",
486 want
& MAN_DIR
? "man" : "",
487 want
& SRC_DIR
? "src" : ""));
490 /* if -u not specified then we always print the pattern */
491 printf("%s:", patbuf
);
493 for (; ls
; ls
= ls
->next
) {
494 if ((ls
->type
& want
) && ls
->path
)
495 findin(ls
->path
, patbuf
, &count
, &wait
, ls
->type
);
500 if (!uflag
|| count
> 1)
504 static void list_dirlist(struct wh_dirlist
*ls
)
521 printf("%s\n", ls
->path
);
527 int main(int argc
, char **argv
)
529 struct wh_dirlist
*ls
= NULL
;
531 int i
, want_resetable
= 0, opt_f_missing
= 0;
533 setlocale(LC_ALL
, "");
534 bindtextdomain(PACKAGE
, LOCALEDIR
);
536 close_stdout_atexit();
539 warnx(_("not enough arguments"));
540 errtryhelp(EXIT_FAILURE
);
542 /* first arg may be one of our standard longopts */
543 if (!strcmp(argv
[1], "--help"))
545 if (!strcmp(argv
[1], "--version"))
546 print_version(EXIT_SUCCESS
);
549 whereis_init_debug();
551 construct_dirlist(&ls
, BIN_DIR
, bindirs
);
552 construct_dirlist_from_env("PATH", &ls
, BIN_DIR
);
554 construct_dirlist(&ls
, MAN_DIR
, mandirs
);
555 construct_dirlist_from_env("MANPATH", &ls
, MAN_DIR
);
557 construct_dirlist(&ls
, SRC_DIR
, srcdirs
);
559 for (i
= 1; i
< argc
; i
++) {
560 const char *arg
= argv
[i
];
563 DBG(ARGV
, ul_debug("argv[%d]: %s", i
, arg
));
566 lookup(arg
, ls
, want
);
568 * The lookup mask ("want") is cumulative and it's
569 * resettable only when it has been already used.
571 * whereis -b -m foo :'foo' mask=BIN|MAN
572 * whereis -b foo bar :'foo' and 'bar' mask=BIN|MAN
573 * whereis -b foo -m bar :'foo' mask=BIN; 'bar' mask=MAN
579 for (++arg
; arg
&& *arg
; arg
++) {
580 DBG(ARGV
, ul_debug(" arg: %s", arg
));
592 warnx(_("bad usage"));
593 errtryhelp(EXIT_FAILURE
);
596 free_dirlist(&ls
, BIN_DIR
);
597 construct_dirlist_from_argv(
598 &ls
, &i
, argc
, argv
, BIN_DIR
);
603 warnx(_("bad usage"));
604 errtryhelp(EXIT_FAILURE
);
607 free_dirlist(&ls
, MAN_DIR
);
608 construct_dirlist_from_argv(
609 &ls
, &i
, argc
, argv
, MAN_DIR
);
614 warnx(_("bad usage"));
615 errtryhelp(EXIT_FAILURE
);
618 free_dirlist(&ls
, SRC_DIR
);
619 construct_dirlist_from_argv(
620 &ls
, &i
, argc
, argv
, SRC_DIR
);
624 if (want_resetable
) {
628 want
= want
== ALL_DIRS
? BIN_DIR
: want
| BIN_DIR
;
632 if (want_resetable
) {
636 want
= want
== ALL_DIRS
? MAN_DIR
: want
| MAN_DIR
;
640 if (want_resetable
) {
644 want
= want
== ALL_DIRS
? SRC_DIR
: want
| SRC_DIR
;
655 print_version(EXIT_SUCCESS
);
659 warnx(_("bad usage"));
660 errtryhelp(EXIT_FAILURE
);
663 if (arg_i
< i
) /* moved to the next argv[] item */
668 free_dirlist(&ls
, ALL_DIRS
);
670 errx(EXIT_FAILURE
, _("option -f is missing"));