diff options
Diffstat (limited to 'misc-utils/whereis.c')
-rw-r--r-- | misc-utils/whereis.c | 656 |
1 files changed, 656 insertions, 0 deletions
diff --git a/misc-utils/whereis.c b/misc-utils/whereis.c new file mode 100644 index 0000000..0247dbe --- /dev/null +++ b/misc-utils/whereis.c @@ -0,0 +1,656 @@ +/*- + * Copyright (c) 1980 The Regents of the University of California. + * 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. + * 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. + * + * 1999-02-22 Arkadiusz MiĆkiewicz <misiek@pld.ORG.PL> + * - added Native Language Support + * 2011-08-12 Davidlohr Bueso <dave@gnu.org> + * - added $PATH lookup + * + * Copyright (C) 2013 Karel Zak <kzak@redhat.com> + * 2013 Sami Kerola <kerolasa@iki.fi> + */ + +#include <sys/param.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> + +#include "xalloc.h" +#include "nls.h" +#include "c.h" +#include "closestream.h" +#include "canonicalize.h" + +#include "debug.h" + +static UL_DEBUG_DEFINE_MASK(whereis); +UL_DEBUG_DEFINE_MASKNAMES(whereis) = UL_DEBUG_EMPTY_MASKNAMES; + +#define WHEREIS_DEBUG_INIT (1 << 1) +#define WHEREIS_DEBUG_PATH (1 << 2) +#define WHEREIS_DEBUG_ENV (1 << 3) +#define WHEREIS_DEBUG_ARGV (1 << 4) +#define WHEREIS_DEBUG_SEARCH (1 << 5) +#define WHEREIS_DEBUG_STATIC (1 << 6) +#define WHEREIS_DEBUG_LIST (1 << 7) +#define WHEREIS_DEBUG_ALL 0xFFFF + +#define DBG(m, x) __UL_DBG(whereis, WHEREIS_DEBUG_, m, x) +#define ON_DBG(m, x) __UL_DBG_CALL(whereis, WHEREIS_DEBUG_, m, x) + +#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(whereis) +#include "debugobj.h" + +static char uflag = 0; + +/* supported types */ +enum { + BIN_DIR = (1 << 1), + MAN_DIR = (1 << 2), + SRC_DIR = (1 << 3), + + ALL_DIRS = BIN_DIR | MAN_DIR | SRC_DIR +}; + +/* directories */ +struct wh_dirlist { + int type; + dev_t st_dev; + ino_t st_ino; + char *path; + + struct wh_dirlist *next; +}; + +static const char *bindirs[] = { + "/usr/bin", + "/usr/sbin", + "/bin", + "/sbin", +#if defined(MULTIARCHTRIPLET) + "/lib/" MULTIARCHTRIPLET, + "/usr/lib/" MULTIARCHTRIPLET, + "/usr/local/lib/" MULTIARCHTRIPLET, +#endif + "/usr/lib", + "/usr/lib64", + "/etc", + "/usr/etc", + "/lib", + "/lib64", + "/usr/games", + "/usr/games/bin", + "/usr/games/lib", + "/usr/emacs/etc", + "/usr/lib/emacs/*/etc", + "/usr/TeX/bin", + "/usr/tex/bin", + "/usr/interviews/bin/LINUX", + + "/usr/X11R6/bin", + "/usr/X386/bin", + "/usr/bin/X11", + "/usr/X11/bin", + "/usr/X11R5/bin", + + "/usr/local/bin", + "/usr/local/sbin", + "/usr/local/etc", + "/usr/local/lib", + "/usr/local/games", + "/usr/local/games/bin", + "/usr/local/emacs/etc", + "/usr/local/TeX/bin", + "/usr/local/tex/bin", + "/usr/local/bin/X11", + + "/usr/contrib", + "/usr/hosts", + "/usr/include", + + "/usr/g++-include", + + "/usr/ucb", + "/usr/old", + "/usr/new", + "/usr/local", + "/usr/libexec", + "/usr/share", + + "/opt/*/bin", + NULL +}; + +static const char *mandirs[] = { + "/usr/man/*", + "/usr/share/man/*", + "/usr/X386/man/*", + "/usr/X11/man/*", + "/usr/TeX/man/*", + "/usr/interviews/man/mann", + "/usr/share/info", + NULL +}; + +static const char *srcdirs[] = { + "/usr/src/*", + "/usr/src/lib/libc/*", + "/usr/src/lib/libc/net/*", + "/usr/src/ucb/pascal", + "/usr/src/ucb/pascal/utilities", + "/usr/src/undoc", + NULL +}; + +static void whereis_init_debug(void) +{ + __UL_INIT_DEBUG_FROM_ENV(whereis, WHEREIS_DEBUG_, 0, WHEREIS_DEBUG); +} + +static const char *whereis_type_to_name(int type) +{ + switch (type) { + case BIN_DIR: return "bin"; + case MAN_DIR: return "man"; + case SRC_DIR: return "src"; + default: return "???"; + } +} + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options] [-BMS <dir>... -f] <name>\n"), program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Locate the binary, source, and manual-page files for a command.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -b search only for binaries\n"), out); + fputs(_(" -B <dirs> define binaries lookup path\n"), out); + fputs(_(" -m search only for manuals and infos\n"), out); + fputs(_(" -M <dirs> define man and info lookup path\n"), out); + fputs(_(" -s search only for sources\n"), out); + fputs(_(" -S <dirs> define sources lookup path\n"), out); + fputs(_(" -f terminate <dirs> argument list\n"), out); + fputs(_(" -u search for unusual entries\n"), out); + fputs(_(" -l output effective lookup paths\n"), out); + + fputs(USAGE_SEPARATOR, out); + printf(USAGE_HELP_OPTIONS(16)); + printf(USAGE_MAN_TAIL("whereis(1)")); + exit(EXIT_SUCCESS); +} + +static void dirlist_add_dir(struct wh_dirlist **ls0, int type, const char *dir) +{ + struct stat st; + struct wh_dirlist *prev = NULL, *ls = *ls0; + + if (access(dir, R_OK) != 0) + return; + if (stat(dir, &st) != 0 || !S_ISDIR(st.st_mode)) + return; + + while (ls) { + if (ls->st_ino == st.st_ino && + ls->st_dev == st.st_dev && + ls->type == type) { + DBG(LIST, ul_debugobj(*ls0, " ignore (already in list): %s", dir)); + return; + } + prev = ls; + ls = ls->next; + } + + + ls = xcalloc(1, sizeof(*ls)); + ls->st_ino = st.st_ino; + ls->st_dev = st.st_dev; + ls->type = type; + ls->path = canonicalize_path(dir); + + if (!*ls0) + *ls0 = ls; /* first in the list */ + else { + assert(prev); + prev->next = ls; /* add to the end of the list */ + } + + DBG(LIST, ul_debugobj(*ls0, " add dir: %s", ls->path)); +} + +/* special case for '*' in the paths */ +static void dirlist_add_subdir(struct wh_dirlist **ls, int type, const char *dir) +{ + char buf[PATH_MAX], *d; + DIR *dirp; + struct dirent *dp; + char *postfix; + size_t len; + + postfix = strchr(dir, '*'); + if (!postfix) + goto ignore; + + /* copy begin of the path to the buffer (part before '*') */ + len = (postfix - dir) + 1; + xstrncpy(buf, dir, len); + + /* remember place where to append subdirs */ + d = buf + len - 1; + + /* skip '*' */ + postfix++; + if (!*postfix) + postfix = NULL; + + /* open parental dir t scan */ + dirp = opendir(buf); + if (!dirp) + goto ignore; + + DBG(LIST, ul_debugobj(*ls, " scanning subdirs: %s [%s<subdir>%s]", + dir, buf, postfix ? postfix : "")); + + while ((dp = readdir(dirp)) != NULL) { + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) + continue; + if (postfix) + snprintf(d, PATH_MAX - len, "%s%s", dp->d_name, postfix); + else + snprintf(d, PATH_MAX - len, "%s", dp->d_name); + + dirlist_add_dir(ls, type, buf); + } + closedir(dirp); + return; +ignore: + DBG(LIST, ul_debugobj(*ls, " ignore path: %s", dir)); +} + +static void construct_dirlist_from_env(const char *env, + struct wh_dirlist **ls, + int type) +{ + char *key = NULL, *tok = NULL, *pathcp, *path = getenv(env); + + if (!path) + return; + pathcp = xstrdup(path); + + DBG(ENV, ul_debugobj(*ls, "construct %s dirlist from: %s", + whereis_type_to_name(type), path)); + + for (tok = strtok_r(pathcp, ":", &key); tok; + tok = strtok_r(NULL, ":", &key)) + dirlist_add_dir(ls, type, tok); + + free(pathcp); +} + +static void construct_dirlist_from_argv(struct wh_dirlist **ls, + int *idx, + int argc, + char *argv[], + int type) +{ + int i; + + DBG(ARGV, ul_debugobj(*ls, "construct %s dirlist from argv[%d..]", + whereis_type_to_name(type), *idx)); + + for (i = *idx; i < argc; i++) { + if (*argv[i] == '-') /* end of the list */ + break; + + DBG(ARGV, ul_debugobj(*ls, " using argv[%d]: %s", *idx, argv[*idx])); + dirlist_add_dir(ls, type, argv[i]); + *idx = i; + } +} + +static void construct_dirlist(struct wh_dirlist **ls, + int type, + const char **paths) +{ + size_t i; + + DBG(STATIC, ul_debugobj(*ls, "construct %s dirlist from static array", + whereis_type_to_name(type))); + + for (i = 0; paths[i]; i++) { + if (!strchr(paths[i], '*')) + dirlist_add_dir(ls, type, paths[i]); + else + dirlist_add_subdir(ls, type, paths[i]); + } +} + +static void free_dirlist(struct wh_dirlist **ls0, int type) +{ + struct wh_dirlist *prev = NULL, *next, *ls = *ls0; + + *ls0 = NULL; + + DBG(LIST, ul_debugobj(*ls0, "free dirlist")); + + while (ls) { + if (ls->type & type) { + next = ls->next; + DBG(LIST, ul_debugobj(*ls0, " free: %s", ls->path)); + free(ls->path); + free(ls); + ls = next; + if (prev) + prev->next = ls; + } else { + if (!prev) + *ls0 = ls; /* first unremoved */ + prev = ls; + ls = ls->next; + } + } +} + + +static int filename_equal(const char *cp, const char *dp) +{ + int i = strlen(dp); + + DBG(SEARCH, ul_debug("compare '%s' and '%s'", cp, dp)); + + if (dp[0] == 's' && dp[1] == '.' && filename_equal(cp, dp + 2)) + return 1; + if (i > 1 && !strcmp(dp + i - 2, ".Z")) + i -= 2; + else if (i > 2 && !strcmp(dp + i - 3, ".gz")) + i -= 3; + else if (i > 2 && !strcmp(dp + i - 3, ".xz")) + i -= 3; + else if (i > 3 && !strcmp(dp + i - 4, ".bz2")) + i -= 4; + else if (i > 3 && !strcmp(dp + i - 4, ".zst")) + i -= 4; + while (*cp && *dp && *cp == *dp) + cp++, dp++, i--; + if (*cp == 0 && *dp == 0) + return 1; + while (isdigit(*dp)) + dp++; + if (*cp == 0 && *dp++ == '.') { + --i; + while (i > 0 && *dp) + if (--i, *dp++ == '.') + return (*dp++ == 'C' && *dp++ == 0); + return 1; + } + return 0; +} + +static void findin(const char *dir, const char *pattern, int *count, char **wait) +{ + DIR *dirp; + struct dirent *dp; + + dirp = opendir(dir); + if (dirp == NULL) + return; + + DBG(SEARCH, ul_debug("find '%s' in '%s'", pattern, dir)); + + while ((dp = readdir(dirp)) != NULL) { + if (!filename_equal(pattern, dp->d_name)) + continue; + + if (uflag && *count == 0) + xasprintf(wait, "%s/%s", dir, dp->d_name); + + else if (uflag && *count == 1 && *wait) { + printf("%s: %s %s/%s", pattern, *wait, dir, dp->d_name); + free(*wait); + *wait = NULL; + } else + printf(" %s/%s", dir, dp->d_name); + ++(*count); + } + closedir(dirp); +} + +static void lookup(const char *pattern, struct wh_dirlist *ls, int want) +{ + char patbuf[PATH_MAX]; + int count = 0; + char *wait = NULL, *p; + + /* canonicalize pattern -- remove path suffix etc. */ + p = strrchr(pattern, '/'); + p = p ? p + 1 : (char *) pattern; + xstrncpy(patbuf, p, PATH_MAX); + + DBG(SEARCH, ul_debug("lookup dirs for '%s' (%s), want: %s %s %s", + patbuf, pattern, + want & BIN_DIR ? "bin" : "", + want & MAN_DIR ? "man" : "", + want & SRC_DIR ? "src" : "")); + p = strrchr(patbuf, '.'); + if (p) + *p = '\0'; + + if (!uflag) + /* if -u not specified then we always print the pattern */ + printf("%s:", patbuf); + + for (; ls; ls = ls->next) { + if ((ls->type & want) && ls->path) + findin(ls->path, patbuf, &count, &wait); + } + + free(wait); + + if (!uflag || count > 1) + putchar('\n'); +} + +static void list_dirlist(struct wh_dirlist *ls) +{ + while (ls) { + if (ls->path) { + switch (ls->type) { + case BIN_DIR: + printf("bin: "); + break; + case MAN_DIR: + printf("man: "); + break; + case SRC_DIR: + printf("src: "); + break; + default: + abort(); + } + printf("%s\n", ls->path); + } + ls = ls->next; + } +} + +int main(int argc, char **argv) +{ + struct wh_dirlist *ls = NULL; + int want = ALL_DIRS; + int i, want_resetable = 0, opt_f_missing = 0; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + if (argc <= 1) { + warnx(_("not enough arguments")); + errtryhelp(EXIT_FAILURE); + } else { + /* first arg may be one of our standard longopts */ + if (!strcmp(argv[1], "--help")) + usage(); + if (!strcmp(argv[1], "--version")) + print_version(EXIT_SUCCESS); + } + + whereis_init_debug(); + + construct_dirlist(&ls, BIN_DIR, bindirs); + construct_dirlist_from_env("PATH", &ls, BIN_DIR); + + construct_dirlist(&ls, MAN_DIR, mandirs); + construct_dirlist_from_env("MANPATH", &ls, MAN_DIR); + + construct_dirlist(&ls, SRC_DIR, srcdirs); + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + int arg_i = i; + + DBG(ARGV, ul_debug("argv[%d]: %s", i, arg)); + + if (*arg != '-') { + lookup(arg, ls, want); + /* + * The lookup mask ("want") is cumulative and it's + * resettable only when it has been already used. + * + * whereis -b -m foo :'foo' mask=BIN|MAN + * whereis -b foo bar :'foo' and 'bar' mask=BIN|MAN + * whereis -b foo -m bar :'foo' mask=BIN; 'bar' mask=MAN + */ + want_resetable = 1; + continue; + } + + for (++arg; arg && *arg; arg++) { + DBG(ARGV, ul_debug(" arg: %s", arg)); + + switch (*arg) { + case 'f': + opt_f_missing = 0; + break; + case 'u': + uflag = 1; + opt_f_missing = 0; + break; + case 'B': + if (*(arg + 1)) { + warnx(_("bad usage")); + errtryhelp(EXIT_FAILURE); + } + i++; + free_dirlist(&ls, BIN_DIR); + construct_dirlist_from_argv( + &ls, &i, argc, argv, BIN_DIR); + opt_f_missing = 1; + break; + case 'M': + if (*(arg + 1)) { + warnx(_("bad usage")); + errtryhelp(EXIT_FAILURE); + } + i++; + free_dirlist(&ls, MAN_DIR); + construct_dirlist_from_argv( + &ls, &i, argc, argv, MAN_DIR); + opt_f_missing = 1; + break; + case 'S': + if (*(arg + 1)) { + warnx(_("bad usage")); + errtryhelp(EXIT_FAILURE); + } + i++; + free_dirlist(&ls, SRC_DIR); + construct_dirlist_from_argv( + &ls, &i, argc, argv, SRC_DIR); + opt_f_missing = 1; + break; + case 'b': + if (want_resetable) { + want = ALL_DIRS; + want_resetable = 0; + } + want = want == ALL_DIRS ? BIN_DIR : want | BIN_DIR; + opt_f_missing = 0; + break; + case 'm': + if (want_resetable) { + want = ALL_DIRS; + want_resetable = 0; + } + want = want == ALL_DIRS ? MAN_DIR : want | MAN_DIR; + opt_f_missing = 0; + break; + case 's': + if (want_resetable) { + want = ALL_DIRS; + want_resetable = 0; + } + want = want == ALL_DIRS ? SRC_DIR : want | SRC_DIR; + opt_f_missing = 0; + break; + case 'l': + list_dirlist(ls); + break; + + case 'V': + print_version(EXIT_SUCCESS); + case 'h': + usage(); + default: + warnx(_("bad usage")); + errtryhelp(EXIT_FAILURE); + } + + if (arg_i < i) /* moved to the next argv[] item */ + break; + } + } + + free_dirlist(&ls, ALL_DIRS); + if (opt_f_missing) + errx(EXIT_FAILURE, _("option -f is missing")); + return EXIT_SUCCESS; +} |