Complete rewrite, once again.

This is basically a ``C compilation'' of the former whereis.pl file,
employing the same algorithms, and aiming at being mostly
UI-compatible to the old (legally tainted) 4.3BSD whereis(1).  In
comparision, the 4.4BSD-Lite version is just another variant of
which(1) only, where in particular the option to search for source
directories is sorely missing.

While i was at it, i added two more options which i contemplated doing
long since.  -x will suppress the run of locate(1) to find sources
that could not be found otherwise, potentially saving a lot of time
(but obviously, risking to not find some sources that are well hidden
in the tree).  -q will omit the leading name of the query, so in
particular, you can now do something like:

	cd `whereis -qs ls`

I'd explicitly like to thank johan for his review which was quite a
bit more than an average review, including sending me a lot of diffs.

Reviewed by:	johan
This commit is contained in:
joerg 2002-07-11 21:20:54 +00:00
parent cef3a16df0
commit 1d2b625a3d
5 changed files with 722 additions and 341 deletions

View File

@ -2,5 +2,7 @@
PROG= whereis
MAN= whereis.1
WARNS?= 6
NO_WERROR= true
.include <bsd.prog.mk>

View File

@ -0,0 +1,50 @@
/*
* Copyright © 2002, Jörg Wunsch
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* $FreeBSD$
*/
/* Where to look for sources. */
#define PATH_SOURCES \
"/usr/src/bin:/usr/src/usr.bin:/usr/src/sbin:" \
"/usr/src/usr.sbin:/usr/src/libexec:" \
"/usr/src/gnu/bin:/usr/src/gnu/usr.bin:" \
"/usr/src/gnu/sbin:/usr/src/gnu/usr.sbin:" \
"/usr/src/gnu/libexec:/usr/src/contrib:" \
"/usr/src/secure/bin:/usr/src/secure/usr.bin:" \
"/usr/src/secure/sbin:/usr/src/secure/usr.sbin:" \
"/usr/src/secure/libexec:/usr/src/crypto"
/* Each subdirectory of PATH_PORTS will be appended to PATH_SOURCES. */
#define PATH_PORTS "/usr/ports"
/* How to query the current manpath. */
#define MANPATHCMD "manpath -q"
/* How to obtain the location of manpages, and how to match this result. */
#define MANWHEREISCMD "man -S1:8 -w %s 2>/dev/null"
#define MANWHEREISMATCH "^.* [(]source: (.*)[)]$"
/* Command used to locate sources that have not been found yet. */
#define LOCATECMD "locate '*'/%s 2>/dev/null"

View File

@ -1,9 +1,8 @@
.\" $NetBSD: whereis.1,v 1.12 2001/12/01 16:43:27 wiz Exp $
.\" $FreeBSD$
.\"
.\" Copyright (c) 1993
.\" The Regents of the University of California. All rights reserved.
.\"
.\" Copyright 2002 Joerg Wunsch
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
@ -32,9 +31,11 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\" @(#)whereis.1 8.3 (Berkeley) 4/27/95
.\" @(#)whereis.1 8.2 (Berkeley) 12/30/93
.\"
.Dd April 27, 1995
.\" $FreeBSD$
.\"
.Dd July 11, 2002
.Dt WHEREIS 1
.Os
.Sh NAME
@ -42,27 +43,133 @@
.Nd locate programs
.Sh SYNOPSIS
.Nm
.Op Fl bmqsux
.Op Fl BMS Ar dir Ar ... Fl f
.Ar program ...
.Sh DESCRIPTION
The
.Nm
utility checks the standard binary directories for the specified programs,
printing out the paths of any it finds.
utility checks the standard binary, manual page, and source
directories for the specified programs, printing out the paths of any
it finds. The supplied program names are first stripped of leading
path name components, any single trailing extension added by
.Xr gzip 1 ,
.Xr compress 1 ,
or
.Xr bzip2 1 ,
and the leading
.Ql s.\&
or trailing
.Ql ,v
from a source code control system.
.Pp
The default path searched is the string returned by the
.Xr sysctl 8
utility for the
.Dq user.cs_path
string.
string, with
.Pa /usr/libexec
and the current user's
.Ev $PATH
appended. Manual pages are searched by default along the
.Ev $MANPATH .
Program sources are located in a list of known standard places,
including all the subdirectories of
.Pa /usr/src
and
.Pa /usr/ports .
.Pp
The following options are available:
.Bl -tag -width indent
.It Fl B
Specify directories to search for binaries. Requires the
.Fl f
option.
.It Fl M
Specify directories to search for manual pages. Requires the
.Fl f
option.
.It Fl S
Specify directories to search for program sources. Requires the
.Fl f
option.
.It Fl b
Search for binaries.
.It Fl f
Delimits the list of directories after the
.Fl B ,
.Fl M ,
or
.Fl S
options, and indicates the beginning of the
.Ar program
list.
.It Fl m
Search for manual pages.
.It Fl q
.Pq Dq quiet .
Suppress the output of the utility name in front of the normal
output line.
This can become handy for use in a backquote substitution of a
shell command line, see
.Sx EXAMPLES .
.It Fl s
Search for source directories.
.It Fl u
Search for
.Dq unusual
entries. A file is said to be unusual if it does not have one entry
of each requested type.
Only the name of the unusual entry is printed.
.It Fl x
Do not use
.Dq expensive
tools when searching for source directories.
Normally, after unsuccessfully searching all the first-level
subdirectories of the source directory list,
.Nm
will ask
.Xr locate 1
to find the entry on its behalf.
Since this can take much longer, it can be turned off with
.Fl x .
.El
.Sh EXAMPLES
The following finds all utilities under
.Pa /usr/bin
that do not have documentation:
.Pp
.Dl whereis -m -u /usr/bin/*
.Pp
Change to the source code directory of
.Xr ls 1 :
.Pp
.Dl cd `whereis -sq ls`
.Sh SEE ALSO
.Xr find 1 ,
.Xr locate 1 ,
.Xr man 1 ,
.Xr which 1 ,
.Xr sysctl 8
.Sh COMPATIBILITY
The historic flags and arguments for the
.Nm
utility are no longer available in this version.
.Sh HISTORY
The
.Nm
command appeared in
.Bx 3.0 .
This version re-implements the historical
functionality that was lost in
.Bx 4.4 .
.Sh AUTHORS
This implementation of the
.Nm
command was written by
.An J\(:org Wunsch .
.Sh BUGS
This re-implementation of the
.Nm
utility is not bug-for-bug compatible with historical versions.
It is believed to be compatible with the version that was shipping with
.Fx 2.2
through
.Fx 4.5
though.

View File

@ -1,6 +1,5 @@
/*-
* Copyright (c) 1993
* The Regents of the University of California. All rights reserved.
/*
* Copyright © 2002, Jörg Wunsch
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@ -10,120 +9,585 @@
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef lint
static char copyright[] =
"@(#) Copyright (c) 1993\n\
The Regents of the University of California. All rights reserved.\n";
#endif /* not lint */
/*
* 4.3BSD UI-compatible whereis(1) utility. Rewritten from scratch
* since the original 4.3BSD version suffers legal problems that
* prevent it from being redistributed, and since the 4.4BSD version
* was pretty inferior in functionality.
*/
#if 0
#ifndef lint
static char sccsid[] = "@(#)whereis.c 8.3 (Berkeley) 5/4/95";
#endif /* not lint */
__RCSID("$NetBSD: whereis.c,v 1.11 2002/06/11 06:06:21 itojun Exp $");
#endif
#include <sys/types.h>
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <dirent.h>
#include <err.h>
#include <errno.h>
#include <regex.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>
void usage __P((void));
int main __P((int, char *[]));
#include "pathnames.h"
int
main(argc, argv)
int argc;
char *argv[];
typedef const char *ccharp;
int opt_b, opt_m, opt_q, opt_s, opt_u, opt_x;
ccharp *bindirs, *mandirs, *sourcedirs;
char **query;
const char *sourcepath = PATH_SOURCES;
char *colonify(ccharp *);
int contains(ccharp *, const char *);
void decolonify(char *, ccharp **, int *);
void defaults(void);
void scanopts(int, char **);
void usage(void);
/*
* Throughout this program, a number of strings are dynamically
* allocated but never freed. Their memory is written to when
* splitting the strings into string lists which will later be
* processed. Since it's important that those string lists remain
* valid even after the functions allocating the memory returned,
* those functions cannot free them. They could be freed only at end
* of main(), which is pretty pointless anyway.
*
* The overall amount of memory to be allocated for processing the
* strings is not expected to exceed a few kilobytes. For that
* reason, allocation can usually always be assumed to succeed (within
* a virtual memory environment), thus we simply bail out using
* abort(3) in case of an allocation failure.
*/
void
usage(void)
{
struct stat sb;
size_t len;
int ch, sverrno, mib[2];
char *p, *t, *std, path[MAXPATHLEN];
errx(EX_USAGE,
"usage: whereis [-bmqsux] [-BMS dir... -f] name ...");
}
/*
* Scan options passed to program.
*
* Note that the -B/-M/-S options expect a list of directory
* names that must be terminated with -f.
*/
void
scanopts(int argc, char **argv)
{
int c, i, opt_f;
ccharp **dirlist;
opt_f = 0;
while ((c = getopt(argc, argv, "BMSbfmqsux")) != -1)
switch (c) {
case 'B':
dirlist = &bindirs;
goto dolist;
case 'M':
dirlist = &mandirs;
goto dolist;
case 'S':
dirlist = &sourcedirs;
dolist:
i = 0;
while (optind < argc &&
strcmp(argv[optind], "-f") != 0 &&
strcmp(argv[optind], "-B") != 0 &&
strcmp(argv[optind], "-M") != 0 &&
strcmp(argv[optind], "-S") != 0) {
*dirlist = realloc(*dirlist,
(i + 2) * sizeof(char *));
if (*dirlist == NULL)
abort();
(*dirlist)[i] = argv[optind];
i++;
optind++;
}
(*dirlist)[i] = NULL;
break;
case 'b':
opt_b = 1;
break;
case 'f':
goto breakout;
case 'm':
opt_m = 1;
break;
case 'q':
opt_q = 1;
break;
case 's':
opt_s = 1;
break;
case 'u':
opt_u = 1;
break;
case 'x':
opt_x = 1;
break;
while ((ch = getopt(argc, argv, "")) != -1)
switch (ch) {
case '?':
default:
usage();
}
argc -= optind;
argv += optind;
if (argc == 0)
breakout:
if (optind == argc)
usage();
query = argv + optind;
}
/* Retrieve the standard path. */
mib[0] = CTL_USER;
mib[1] = USER_CS_PATH;
if (sysctl(mib, 2, NULL, &len, NULL, 0) == -1)
return (-1);
if (len == 0)
err(1, "user_cs_path: sysctl: zero length");
if ((std = malloc(len)) == NULL)
err(1, NULL);
if (sysctl(mib, 2, std, &len, NULL, 0) == -1) {
sverrno = errno;
free(std);
errno = sverrno;
err(1, "sysctl: user_cs_path");
/*
* Find out whether string `s' is contained in list `cpp'.
*/
int
contains(ccharp *cpp, const char *s)
{
ccharp cp;
if (cpp == NULL)
return (0);
while ((cp = *cpp) != NULL) {
if (strcmp(cp, s) == 0)
return (1);
cpp++;
}
/* For each path, for each program... */
for (; *argv; ++argv)
for (p = std;; *p++ = ':') {
t = p;
if ((p = strchr(p, ':')) != NULL) {
*p = '\0';
if (t == p)
t = ".";
} else
if (strlen(t) == 0)
t = ".";
(void)snprintf(path, sizeof(path), "%s/%s", t, *argv);
if (!stat(path, &sb))
(void)printf("%s\n", path);
if (p == NULL)
break;
}
return (0);
}
/*
* Split string `s' at colons, and pass it to the string list pointed
* to by `cppp' (which has `*ip' elements). Note that the original
* string is modified by replacing the colon with a NUL byte. The
* partial string is only added if it has a length greater than 0, and
* if it's not already contained in the string list.
*/
void
usage()
decolonify(char *s, ccharp **cppp, int *ip)
{
char *cp;
(void)fprintf(stderr, "usage: whereis program ...\n");
exit (1);
while ((cp = strchr(s, ':')), *s != '\0') {
if (cp)
*cp = '\0';
if (strlen(s) && !contains(*cppp, s)) {
*cppp = realloc(*cppp, (*ip + 2) * sizeof(char *));
if (cppp == NULL)
abort();
(*cppp)[*ip] = s;
(*cppp)[*ip + 1] = NULL;
(*ip)++;
}
if (cp)
s = cp + 1;
else
break;
}
}
/*
* Join string list `cpp' into a colon-separated string.
*/
char *
colonify(ccharp *cpp)
{
size_t s;
char *cp;
int i;
if (cpp == NULL)
return (0);
for (s = 0, i = 0; cpp[i] != NULL; i++)
s += strlen(cpp[i]) + 1;
if ((cp = malloc(s + 1)) == NULL)
abort();
for (i = 0, *cp = '\0'; cpp[i] != NULL; i++) {
strcat(cp, cpp[i]);
strcat(cp, ":");
}
cp[s - 1] = '\0'; /* eliminate last colon */
return (cp);
}
/*
* Provide defaults for all options and directory lists.
*/
void
defaults(void)
{
size_t s;
char *b, buf[BUFSIZ], *cp;
int nele;
FILE *p;
DIR *dir;
struct stat sb;
struct dirent *dirp;
/* default to -bms if none has been specified */
if (!opt_b && !opt_m && !opt_s)
opt_b = opt_m = opt_s = 1;
/* -b defaults to default path + /usr/libexec + user's path */
if (!bindirs) {
if (sysctlbyname("user.cs_path", (void *)NULL, &s,
(void *)NULL, 0) == -1)
err(EX_OSERR, "sysctlbyname(\"user.cs_path\")");
if ((b = malloc(s + 1)) == NULL)
abort();
if (sysctlbyname("user.cs_path", b, &s, (void *)NULL, 0) == -1)
err(EX_OSERR, "sysctlbyname(\"user.cs_path\")");
nele = 0;
decolonify(b, &bindirs, &nele);
bindirs = realloc(bindirs, (nele + 2) * sizeof(char *));
if (bindirs == NULL)
abort();
bindirs[nele++] = "/usr/libexec";
bindirs[nele] = NULL;
if ((cp = getenv("PATH")) != NULL) {
/* don't destroy the original environment... */
if ((b = malloc(strlen(cp) + 1)) == NULL)
abort();
strcpy(b, cp);
decolonify(b, &bindirs, &nele);
}
}
/* -m defaults to $(manpath) */
if (!mandirs) {
if ((p = popen(MANPATHCMD, "r")) == NULL)
err(EX_OSERR, "cannot execute manpath command");
if (fgets(buf, BUFSIZ - 1, p) == NULL ||
pclose(p))
err(EX_OSERR, "error processing manpath results");
if ((b = strchr(buf, '\n')) != NULL)
*b = '\0';
if ((b = malloc(strlen(buf) + 1)) == NULL)
abort();
strcpy(b, buf);
nele = 0;
decolonify(b, &mandirs, &nele);
}
/* -s defaults to precompiled list, plus subdirs of /usr/ports */
if (!sourcedirs) {
if ((b = malloc(strlen(sourcepath) + 1)) == NULL)
abort();
strcpy(b, sourcepath);
nele = 0;
decolonify(b, &sourcedirs, &nele);
if (stat(PATH_PORTS, &sb) == -1) {
if (errno == ENOENT)
/* no /usr/ports, we are done */
return;
err(EX_OSERR, "stat(" PATH_PORTS ")");
}
if ((sb.st_mode & S_IFMT) != S_IFDIR)
/* /usr/ports is not a directory, ignore */
return;
if (access(PATH_PORTS, R_OK | X_OK) != 0)
return;
if ((dir = opendir(PATH_PORTS)) == NULL)
err(EX_OSERR, "opendir" PATH_PORTS ")");
while ((dirp = readdir(dir)) != NULL) {
if (dirp->d_name[0] == '.' ||
strcmp(dirp->d_name, "CVS") == 0)
/* ignore dot entries and CVS subdir */
continue;
if ((b = malloc(sizeof PATH_PORTS + 1 + dirp->d_namlen))
== NULL)
abort();
strcpy(b, PATH_PORTS);
strcat(b, "/");
strcat(b, dirp->d_name);
if (stat(b, &sb) == -1 ||
(sb.st_mode & S_IFMT) != S_IFDIR ||
access(b, R_OK | X_OK) != 0) {
free(b);
continue;
}
sourcedirs = realloc(sourcedirs,
(nele + 2) * sizeof(char *));
if (sourcedirs == NULL)
abort();
sourcedirs[nele++] = b;
sourcedirs[nele] = NULL;
}
closedir(dir);
}
}
int
main(int argc, char **argv)
{
int unusual, i, printed;
char *bin, buf[BUFSIZ], *cp, *cp2, *man, *name, *src;
ccharp *dp;
size_t s;
struct stat sb;
regex_t re, re2;
regmatch_t matches[2];
regoff_t rlen;
FILE *p;
scanopts(argc, argv);
defaults();
if (mandirs == NULL)
opt_m = 0;
if (bindirs == NULL)
opt_b = 0;
if (sourcedirs == NULL)
opt_s = 0;
if (opt_m + opt_b + opt_s == 0)
errx(EX_DATAERR, "no directories to search");
if (opt_m) {
setenv("MANPATH", colonify(mandirs), 1);
if ((i = regcomp(&re, MANWHEREISMATCH, REG_EXTENDED)) != 0) {
regerror(i, &re, buf, BUFSIZ - 1);
errx(EX_UNAVAILABLE, "regcomp(%s) failed: %s",
MANWHEREISMATCH, buf);
}
}
for (; (name = *query) != NULL; query++) {
/* strip leading path name component */
if ((cp = strrchr(name, '/')) != NULL)
name = cp + 1;
/* strip SCCS or RCS suffix/prefix */
if (strlen(name) > 2 && strncmp(name, "s.", 2) == 0)
name += 2;
if ((s = strlen(name)) > 2 && strcmp(name + s - 2, ",v") == 0)
name[s - 2] = '\0';
/* compression suffix */
s = strlen(name);
if (s > 2 &&
(strcmp(name + s - 2, ".z") == 0 ||
strcmp(name + s - 2, ".Z") == 0))
name[s - 2] = '\0';
else if (s > 3 &&
strcmp(name + s - 3, ".gz") == 0)
name[s - 3] = '\0';
else if (s > 4 &&
strcmp(name + s - 4, ".bz2") == 0)
name[s - 4] = '\0';
unusual = 0;
bin = man = src = NULL;
s = strlen(name);
if (opt_b) {
/*
* Binaries have to match exactly, and must be regular
* executable files.
*/
unusual++;
for (dp = bindirs; *dp != NULL; dp++) {
cp = malloc(strlen(*dp) + 1 + s + 1);
if (cp == NULL)
abort();
strcpy(cp, *dp);
strcat(cp, "/");
strcat(cp, name);
if (stat(cp, &sb) == 0 &&
(sb.st_mode & S_IFMT) == S_IFREG &&
(sb.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
!= 0) {
unusual--;
bin = cp;
break;
}
free(cp);
}
}
if (opt_m) {
/*
* Ask the man command to perform the search for us.
*/
unusual++;
cp = malloc(sizeof MANWHEREISCMD - 2 + s);
if (cp == NULL)
abort();
sprintf(cp, MANWHEREISCMD, name);
if ((p = popen(cp, "r")) != NULL &&
fgets(buf, BUFSIZ - 1, p) != NULL &&
pclose(p) == 0) {
unusual--;
if ((cp2 = strchr(buf, '\n')) != NULL)
*cp2 = '\0';
if (regexec(&re, buf, 2, matches, 0) == 0 &&
(rlen = matches[1].rm_eo - matches[1].rm_so)
> 0) {
/*
* man -w found compressed
* page, need to pick up
* source page name.
*/
cp2 = malloc(rlen + 1);
if (cp2 == NULL)
abort();
memcpy(cp2, buf + matches[1].rm_so,
rlen);
cp2[rlen] = '\0';
man = cp2;
} else {
/*
* man -w found plain source
* page, use it.
*/
s = strlen(buf);
cp2 = malloc(s + 1);
if (cp2 == NULL)
abort();
strcpy(cp2, buf);
man = cp2;
}
}
free(cp);
}
if (opt_s) {
/*
* Sources match if a subdir with the exact
* name is found.
*/
unusual++;
for (dp = sourcedirs; *dp != NULL; dp++) {
cp = malloc(strlen(*dp) + 1 + s + 1);
if (cp == NULL)
abort();
strcpy(cp, *dp);
strcat(cp, "/");
strcat(cp, name);
if (stat(cp, &sb) == 0 &&
(sb.st_mode & S_IFMT) == S_IFDIR) {
unusual--;
src = cp;
break;
}
free(cp);
}
/*
* If still not found, ask locate to search it
* for us. This will find sources for things
* like lpr that are well hidden in the
* /usr/src tree, but takes a lot longer.
* Thus, option -x (`expensive') prevents this
* search.
*
* Do only match locate output that starts
* with one of our source directories, and at
* least one further level of subdirectories.
*/
if (opt_x || src)
goto done_sources;
cp = malloc(sizeof LOCATECMD - 2 + s);
if (cp == NULL)
abort();
sprintf(cp, LOCATECMD, name);
if ((p = popen(cp, "r")) == NULL)
goto done_sources;
while (src == NULL &&
(fgets(buf, BUFSIZ - 1, p)) != NULL) {
if ((cp2 = strchr(buf, '\n')) != NULL)
*cp2 = '\0';
for (dp = sourcedirs;
src == NULL && *dp != NULL;
dp++) {
cp2 = malloc(strlen(*dp) + 9);
if (cp2 == NULL)
abort();
strcpy(cp2, "^");
strcat(cp2, *dp);
strcat(cp2, "/[^/]+/");
if ((i = regcomp(&re2, cp2,
REG_EXTENDED|REG_NOSUB))
!= 0) {
regerror(i, &re, buf,
BUFSIZ - 1);
errx(EX_UNAVAILABLE,
"regcomp(%s) failed: %s",
cp2, buf);
}
free(cp2);
if (regexec(&re2, buf, 0,
(regmatch_t *)NULL, 0)
== 0) {
unusual--;
src = buf;
}
regfree(&re2);
}
}
pclose(p);
free(cp);
}
done_sources:
if (opt_u && !unusual)
continue;
printed = 0;
if (!opt_q) {
printf("%s:", name);
printed++;
}
if (bin) {
if (printed++)
putchar(' ');
fputs(bin, stdout);
}
if (man) {
if (printed++)
putchar(' ');
fputs(man, stdout);
}
if (src) {
if (printed++)
putchar(' ');
fputs(src, stdout);
}
if (printed)
putchar('\n');
}
if (opt_m)
regfree(&re);
return (0);
}

View File

@ -1,242 +0,0 @@
#!/usr/bin/perl
#
# Copyright © 1995, 1996 Jörg Wunsch
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE DEVELOPERS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# whereis -- search for binaries, man pages and source directories.
#
# Rewritten from scratch for FreeBSD after the 4.3BSD manual page.
#
# $FreeBSD$
#
sub usage
{
print STDERR "usage: $0 [-bms] [-u] [-BMS dir... -f] name ...\n";
exit 1;
}
sub scanopts
{
local($i, $j);
arg:
while ($ARGV[$i] =~ /^-/) {
opt:
for ($j = 1; $j < length($ARGV[$i]); $j++) {
local($_) = substr($ARGV[$i], $j, 1);
local($what, @list);
$opt_b++, next opt if /b/;
$opt_m++, next opt if /m/;
$opt_s++, next opt if /s/;
$opt_u++, next opt if /u/;
&usage unless /[BMS]/;
# directory list processing
$what = $_; @list = ();
push(@list, substr($ARGV[$i], $j+1)) if $j+1 < length($ARGV[$i]);
$i++;
while ($i <= $#ARGV && $ARGV[$i] !~ /^-/) {
push(@list, $ARGV[$i++]);
}
if ($what eq "B") {@binaries = @list;}
elsif ($what eq "M") {@manuals = @list;}
elsif ($what eq "S") {@sources = @list;}
$i++, last arg if $ARGV[$i] =~ /^-f$/;
next arg;
}
$i++;
}
&usage if $i > $#ARGV;
while ($ARGV[$i]) {
push(@names, $ARGV[$i++]);
}
}
sub decolonify
{
local($list) = @_;
local($_, @rv);
foreach(split(/:/, $list)) {
push(@rv, $_);
}
return @rv;
}
&scanopts;
# default to all if no type requested
if ($opt_b + $opt_m + $opt_s == 0) {$opt_b = $opt_m = $opt_s = 1;}
if (!defined(@binaries)) {
#
# first, use default path, then append /usr/libexec and the user's path
#
local($cs_path) = `/sbin/sysctl -n user.cs_path`;
local(@list, %path);
chop($cs_path);
@list = &decolonify($cs_path);
push(@list, "/usr/libexec");
push(@list, &decolonify($ENV{'PATH'}));
# resolve ~, remove duplicates
foreach (@list) {
s/^~/$ENV{'HOME'}/ if /^~/;
push(@binaries, $_) if !$path{$_};
$path{$_}++;
}
}
if (!defined(@manuals)) {
#
# first, use default manpath, then append user's $MANPATH
#
local($usermanpath) = $ENV{'MANPATH'};
delete $ENV{'MANPATH'};
local($manpath) = `/usr/bin/manpath`;
local(@list, %path, $i);
chop($manpath);
@list = &decolonify($manpath);
push(@list, &decolonify($usermanpath));
# remove duplicates
foreach (@list) {
push(@manuals, $_) if !$path{$_};
$path{$_}++;
}
}
if (!defined(@sources)) {
#
# default command sources
#
local($_);
@sources = ("/usr/src/bin", "/usr/src/usr.bin", "/usr/src/sbin",
"/usr/src/usr.sbin", "/usr/src/libexec",
"/usr/src/gnu/bin", "/usr/src/gnu/usr.bin",
"/usr/src/gnu/sbin", "/usr/src/gnu/usr.sbin",
"/usr/src/gnu/libexec", "/usr/src/contrib");
#
# if /usr/ports exists, look in all its subdirs, too
#
if (-d "/usr/ports" && opendir(PORTS, "/usr/ports")) {
while ($_ = readdir(PORTS)) {
next if /^\.\.?$/;
next if /^distfiles$/; # magic
next if ! -d "/usr/ports/$_";
push(@sources, "/usr/ports/$_");
}
closedir(PORTS);
}
}
if ($opt_m) {
# construct a new MANPATH
foreach (@manuals) {
next if ! -d $_;
if ($manpath) { $manpath .= ":$_"; }
else { $manpath = $_; }
}
}
#
# main loop
#
foreach $name (@names) {
$name =~ s|^.*/||; # strip leading path name component
$name =~ s/,v$//; $name =~ s/^s\.//; # RCS or SCCS suffix/prefix
$name =~ s/\.(Z|z|gz)$//; # compression suffix
$line = "";
$unusual = 0;
if ($opt_b) {
#
# Binaries have to match exactly, and must be regular executable
# files.
#
$unusual++;
foreach (@binaries) {
$line .= " $_/$name", $unusual--, last if -f "$_/$name" && -x _;
}
}
if ($opt_m) {
#
# Ask the man command to do the search for us.
#
$unusual++;
chop($result = `man -S 1:8 -M $manpath -w $name 2> /dev/null`);
if ($result ne '') {
$unusual--;
($cat, $junk, $src) = split(/[() \t\n]+/, $result);
if ($src ne '') { $line .= " $src"; }
else { $line .= " $cat"; }
}
}
if ($opt_s) {
#
# Sources match if a subdir with the exact name is found.
#
$found = 0;
$unusual++;
foreach (@sources) {
$line .= " $_/$name", $unusual--, $found++ if -d "$_/$name";
}
#
# If not yet found, ask locate(1) to do the search for us.
# This will find sources for things like lpr, but take longer.
# Do only match locate output that starts with one of our
# source directories, and at least one further level of
# subdirectories.
#
if (!$found && open(LOCATE, "locate */$name 2>/dev/null |")) {
locate_item:
while (chop($loc = <LOCATE>)) {
foreach (@sources) {
$line .= " $loc", $unusual--, last locate_item
if $loc =~ m|^$_/[^/]+/|;
}
}
close(LOCATE);
}
}
if ($opt_u) {
print "$name:\n" if $unusual;
} else {
print "$name:$line\n";
}
}