2 * Copyright © 2002, Jörg Wunsch
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
14 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT,
17 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
21 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
22 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
23 * POSSIBILITY OF SUCH DAMAGE.
24 * $FreeBSD: src/usr.bin/whereis/whereis.c,v 1.12 2002/08/22 01:50:51 johan Exp $
25 * $DragonFly: src/usr.bin/whereis/whereis.c,v 1.5 2006/07/01 19:34:43 swildner Exp $
29 * 4.3BSD UI-compatible whereis(1) utility. Rewritten from scratch
30 * since the original 4.3BSD version suffers legal problems that
31 * prevent it from being redistributed, and since the 4.4BSD version
32 * was pretty inferior in functionality.
35 #include <sys/types.h>
39 #include <sys/sysctl.h>
52 #include "pathnames.h"
54 #define NO_BIN_FOUND 1
55 #define NO_MAN_FOUND 2
56 #define NO_SRC_FOUND 4
58 typedef const char *ccharp
;
60 int opt_a
, opt_b
, opt_m
, opt_q
, opt_s
, opt_u
, opt_x
;
61 ccharp
*bindirs
, *mandirs
, *sourcedirs
;
64 const char *sourcepath
= PATH_SOURCES
;
66 char *colonify(ccharp
*);
67 int contains(ccharp
*, const char *);
68 void decolonify(char *, ccharp
**, int *);
70 void scanopts(int, char **);
74 * Throughout this program, a number of strings are dynamically
75 * allocated but never freed. Their memory is written to when
76 * splitting the strings into string lists which will later be
77 * processed. Since it's important that those string lists remain
78 * valid even after the functions allocating the memory returned,
79 * those functions cannot free them. They could be freed only at end
80 * of main(), which is pretty pointless anyway.
82 * The overall amount of memory to be allocated for processing the
83 * strings is not expected to exceed a few kilobytes. For that
84 * reason, allocation can usually always be assumed to succeed (within
85 * a virtual memory environment), thus we simply bail out using
86 * abort(3) in case of an allocation failure.
93 "usage: whereis [-abmqsux] [-BMS dir... -f] name ...");
97 * Scan options passed to program.
99 * Note that the -B/-M/-S options expect a list of directory
100 * names that must be terminated with -f.
103 scanopts(int argc
, char **argv
)
108 while ((c
= getopt(argc
, argv
, "BMSabfmqsux")) != -1)
119 dirlist
= &sourcedirs
;
122 *dirlist
= realloc(*dirlist
, (i
+ 1) * sizeof(char *));
123 (*dirlist
)[i
] = NULL
;
124 while (optind
< argc
&&
125 strcmp(argv
[optind
], "-f") != 0 &&
126 strcmp(argv
[optind
], "-B") != 0 &&
127 strcmp(argv
[optind
], "-M") != 0 &&
128 strcmp(argv
[optind
], "-S") != 0) {
129 decolonify(argv
[optind
], dirlist
, &i
);
171 query
= argv
+ optind
;
175 * Find out whether string `s' is contained in list `cpp'.
178 contains(ccharp
*cpp
, const char *s
)
185 while ((cp
= *cpp
) != NULL
) {
186 if (strcmp(cp
, s
) == 0)
194 * Split string `s' at colons, and pass it to the string list pointed
195 * to by `cppp' (which has `*ip' elements). Note that the original
196 * string is modified by replacing the colon with a NUL byte. The
197 * partial string is only added if it has a length greater than 0, and
198 * if it's not already contained in the string list.
201 decolonify(char *s
, ccharp
**cppp
, int *ip
)
205 while ((cp
= strchr(s
, ':')), *s
!= '\0') {
208 if (strlen(s
) && !contains(*cppp
, s
)) {
209 *cppp
= realloc(*cppp
, (*ip
+ 2) * sizeof(char *));
213 (*cppp
)[*ip
+ 1] = NULL
;
224 * Join string list `cpp' into a colon-separated string.
227 colonify(ccharp
*cpp
)
236 for (s
= 0, i
= 0; cpp
[i
] != NULL
; i
++)
237 s
+= strlen(cpp
[i
]) + 1;
238 if ((cp
= malloc(s
+ 1)) == NULL
)
240 for (i
= 0, *cp
= '\0'; cpp
[i
] != NULL
; i
++) {
244 cp
[s
- 1] = '\0'; /* eliminate last colon */
250 * Provide defaults for all options and directory lists.
256 char *b
, buf
[BUFSIZ
], *cp
;
263 /* default to -bms if none has been specified */
264 if (!opt_b
&& !opt_m
&& !opt_s
)
265 opt_b
= opt_m
= opt_s
= 1;
267 /* -b defaults to default path + /usr/libexec +
268 * /usr/games + user's path */
270 if (sysctlbyname("user.cs_path", (void *)NULL
, &s
,
271 (void *)NULL
, 0) == -1)
272 err(EX_OSERR
, "sysctlbyname(\"user.cs_path\")");
273 if ((b
= malloc(s
+ 1)) == NULL
)
275 if (sysctlbyname("user.cs_path", b
, &s
, (void *)NULL
, 0) == -1)
276 err(EX_OSERR
, "sysctlbyname(\"user.cs_path\")");
278 decolonify(b
, &bindirs
, &nele
);
279 bindirs
= realloc(bindirs
, (nele
+ 3) * sizeof(char *));
282 bindirs
[nele
++] = PATH_LIBEXEC
;
283 bindirs
[nele
++] = PATH_GAMES
;
284 bindirs
[nele
] = NULL
;
285 if ((cp
= getenv("PATH")) != NULL
) {
286 /* don't destroy the original environment... */
287 if ((b
= malloc(strlen(cp
) + 1)) == NULL
)
290 decolonify(b
, &bindirs
, &nele
);
294 /* -m defaults to $(manpath) */
296 if ((p
= popen(MANPATHCMD
, "r")) == NULL
)
297 err(EX_OSERR
, "cannot execute manpath command");
298 if (fgets(buf
, BUFSIZ
- 1, p
) == NULL
||
300 err(EX_OSERR
, "error processing manpath results");
301 if ((b
= strchr(buf
, '\n')) != NULL
)
303 if ((b
= malloc(strlen(buf
) + 1)) == NULL
)
307 decolonify(b
, &mandirs
, &nele
);
310 /* -s defaults to precompiled list, plus subdirs of /usr/pkgsrc */
312 if ((b
= malloc(strlen(sourcepath
) + 1)) == NULL
)
314 strcpy(b
, sourcepath
);
316 decolonify(b
, &sourcedirs
, &nele
);
318 if (stat(PATH_PKGSRC
, &sb
) == -1) {
320 /* no /usr/pkgsrc, we are done */
322 err(EX_OSERR
, "stat(" PATH_PKGSRC
")");
324 if ((sb
.st_mode
& S_IFMT
) != S_IFDIR
)
325 /* /usr/pkgsrc is not a directory, ignore */
327 if (access(PATH_PKGSRC
, R_OK
| X_OK
) != 0)
329 if ((dir
= opendir(PATH_PKGSRC
)) == NULL
)
330 err(EX_OSERR
, "opendir" PATH_PKGSRC
")");
331 while ((dirp
= readdir(dir
)) != NULL
) {
332 if (dirp
->d_name
[0] == '.' ||
333 strcmp(dirp
->d_name
, "CVS") == 0)
334 /* ignore dot entries and CVS subdir */
336 if ((b
= malloc(sizeof PATH_PKGSRC
+ 1 + dirp
->d_namlen
))
339 strcpy(b
, PATH_PKGSRC
);
341 strcat(b
, dirp
->d_name
);
342 if (stat(b
, &sb
) == -1 ||
343 (sb
.st_mode
& S_IFMT
) != S_IFDIR
||
344 access(b
, R_OK
| X_OK
) != 0) {
348 sourcedirs
= realloc(sourcedirs
,
349 (nele
+ 2) * sizeof(char *));
350 if (sourcedirs
== NULL
)
352 sourcedirs
[nele
++] = b
;
353 sourcedirs
[nele
] = NULL
;
360 main(int argc
, char **argv
)
362 int unusual
, i
, printed
;
363 char *bin
, buf
[BUFSIZ
], *cp
, *cp2
, *man
, *name
, *src
;
365 size_t nlen
, olen
, s
;
368 regmatch_t matches
[2];
372 setlocale(LC_ALL
, "");
373 scanopts(argc
, argv
);
380 if (sourcedirs
== NULL
)
382 if (opt_m
+ opt_b
+ opt_s
== 0)
383 errx(EX_DATAERR
, "no directories to search");
386 if (setenv("MANPATH", colonify(mandirs
), 1) == -1)
387 err(1, "setenv: cannot set MANPATH=%s", colonify(mandirs
));
388 if ((i
= regcomp(&re
, MANWHEREISMATCH
, REG_EXTENDED
)) != 0) {
389 regerror(i
, &re
, buf
, BUFSIZ
- 1);
390 errx(EX_UNAVAILABLE
, "regcomp(%s) failed: %s",
391 MANWHEREISMATCH
, buf
);
395 for (; (name
= *query
) != NULL
; query
++) {
396 /* strip leading path name component */
397 if ((cp
= strrchr(name
, '/')) != NULL
)
399 /* strip SCCS or RCS suffix/prefix */
400 if (strlen(name
) > 2 && strncmp(name
, "s.", 2) == 0)
402 if ((s
= strlen(name
)) > 2 && strcmp(name
+ s
- 2, ",v") == 0)
404 /* compression suffix */
407 (strcmp(name
+ s
- 2, ".z") == 0 ||
408 strcmp(name
+ s
- 2, ".Z") == 0))
411 strcmp(name
+ s
- 3, ".gz") == 0)
414 strcmp(name
+ s
- 4, ".bz2") == 0)
418 bin
= man
= src
= NULL
;
423 * Binaries have to match exactly, and must be regular
426 unusual
= unusual
| NO_BIN_FOUND
;
427 for (dp
= bindirs
; *dp
!= NULL
; dp
++) {
428 cp
= malloc(strlen(*dp
) + 1 + s
+ 1);
434 if (stat(cp
, &sb
) == 0 &&
435 (sb
.st_mode
& S_IFMT
) == S_IFREG
&&
436 (sb
.st_mode
& (S_IXUSR
| S_IXGRP
| S_IXOTH
))
438 unusual
= unusual
& ~NO_BIN_FOUND
;
462 * Ask the man command to perform the search for us.
464 unusual
= unusual
| NO_MAN_FOUND
;
466 cp
= malloc(sizeof MANWHEREISALLCMD
- 2 + s
);
468 cp
= malloc(sizeof MANWHEREISCMD
- 2 + s
);
474 sprintf(cp
, MANWHEREISALLCMD
, name
);
476 sprintf(cp
, MANWHEREISCMD
, name
);
478 if ((p
= popen(cp
, "r")) != NULL
) {
480 while (fgets(buf
, BUFSIZ
- 1, p
) != NULL
) {
481 unusual
= unusual
& ~NO_MAN_FOUND
;
483 if ((cp2
= strchr(buf
, '\n')) != NULL
)
485 if (regexec(&re
, buf
, 2,
487 (rlen
= matches
[1].rm_eo
-
488 matches
[1].rm_so
) > 0) {
490 * man -w found formated
491 * page, need to pick up
494 cp2
= malloc(rlen
+ 1);
498 buf
+ matches
[1].rm_so
,
503 * man -w found plain source
538 * Sources match if a subdir with the exact
541 unusual
= unusual
| NO_SRC_FOUND
;
542 for (dp
= sourcedirs
; *dp
!= NULL
; dp
++) {
543 cp
= malloc(strlen(*dp
) + 1 + s
+ 1);
549 if (stat(cp
, &sb
) == 0 &&
550 (sb
.st_mode
& S_IFMT
) == S_IFDIR
) {
551 unusual
= unusual
& ~NO_SRC_FOUND
;
572 * If still not found, ask locate to search it
573 * for us. This will find sources for things
574 * like lpr that are well hidden in the
575 * /usr/src tree, but takes a lot longer.
576 * Thus, option -x (`expensive') prevents this
579 * Do only match locate output that starts
580 * with one of our source directories, and at
581 * least one further level of subdirectories.
583 if (opt_x
|| (src
&& !opt_a
))
586 cp
= malloc(sizeof LOCATECMD
- 2 + s
);
589 sprintf(cp
, LOCATECMD
, name
);
590 if ((p
= popen(cp
, "r")) == NULL
)
592 while ((src
== NULL
|| opt_a
) &&
593 (fgets(buf
, BUFSIZ
- 1, p
)) != NULL
) {
594 if ((cp2
= strchr(buf
, '\n')) != NULL
)
596 for (dp
= sourcedirs
;
597 (src
== NULL
|| opt_a
) && *dp
!= NULL
;
599 cp2
= malloc(strlen(*dp
) + 9);
604 strcat(cp2
, "/[^/]+/");
605 if ((i
= regcomp(&re2
, cp2
,
606 REG_EXTENDED
|REG_NOSUB
))
608 regerror(i
, &re
, buf
,
611 "regcomp(%s) failed: %s",
615 if (regexec(&re2
, buf
, 0,
616 (regmatch_t
*)NULL
, 0)
642 if (opt_u
&& !unusual
)