/* * 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. */ /* * 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include "pathnames.h" 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) { 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; *dirlist = realloc(*dirlist, (i + 1) * sizeof(char *)); (*dirlist)[i] = NULL; while (optind < argc && strcmp(argv[optind], "-f") != 0 && strcmp(argv[optind], "-B") != 0 && strcmp(argv[optind], "-M") != 0 && strcmp(argv[optind], "-S") != 0) { decolonify(argv[optind], dirlist, &i); optind++; } 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; default: usage(); } breakout: if (optind == argc) usage(); query = argv + optind; } /* * 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++; } 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 decolonify(char *s, ccharp **cppp, int *ip) { char *cp; 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 + * /usr/games + 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 + 3) * sizeof(char *)); if (bindirs == NULL) abort(); bindirs[nele++] = PATH_LIBEXEC; bindirs[nele++] = PATH_GAMES; 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); }