]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
whereis: rewrite most of the command
authorSami Kerola <kerolasa@iki.fi>
Wed, 13 Mar 2013 22:27:12 +0000 (22:27 +0000)
committerKarel Zak <kzak@redhat.com>
Tue, 19 Mar 2013 14:44:45 +0000 (15:44 +0100)
The earlier code gave little or no change to fix bugs and improve the
command.  This rewrite attempts to make further patching easier.

Signed-off-by: Sami Kerola <kerolasa@iki.fi>
Signed-off-by: Karel Zak <kzak@redhat.com>
Co-Author: Karel Zak <kzak@redhat.com>

misc-utils/whereis.c

index 4194ad5dc8d33f24bb1759e7c35f70d3ef10003d..97ec45cb28eb95830cb3a2654059a5b63c6a7ec1 100644 (file)
  * 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.
- */
-
-/* *:aeb */
-
-/* 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ *
+ * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
  * - added Native Language Support
- */
-
-/* 2011-08-12 Davidlohr Bueso <dave@gnu.org>
+ * 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 <stdlib.h>
 #include <string.h>
 #include <ctype.h>
+#include <assert.h>
 
 #include "xalloc.h"
 #include "nls.h"
 #include "c.h"
 #include "closestream.h"
 
-static char *bindirs[] = {
+/*#define DEBUG*/
+
+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[] = {
        "/bin",
        "/usr/bin",
        "/sbin",
@@ -106,35 +128,40 @@ static char *bindirs[] = {
        "/usr/share",
 
        "/opt/*/bin",
-
-       0
+       NULL
 };
 
-static char *mandirs[] = {
+static const char *mandirs[] = {
        "/usr/man/*",
        "/usr/share/man/*",
        "/usr/X386/man/*",
        "/usr/X11/man/*",
        "/usr/TeX/man/*",
        "/usr/interviews/man/mann",
-       0
+       NULL
 };
 
-static char *srcdirs[] = {
+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",
-       0
+       NULL
 };
 
-static char sflag = 1, bflag = 1, mflag = 1, uflag;
-static char **Sflag, **Bflag, **Mflag, **pathdir, **pathdir_p;
-static int Scnt, Bcnt, Mcnt, count, print;
-
-static void __attribute__ ((__noreturn__)) usage(FILE * out)
+#ifdef DEBUG
+# define DBG(_x)       do { \
+                               printf("DEBUG: "); \
+                               _x; \
+                               fputc('\n', stdout); \
+                       } while (0)
+#else
+# define DBG(_x)
+#endif
+
+static void __attribute__((__noreturn__)) usage(FILE *out)
 {
        fputs(_("\nUsage:\n"), out);
        fprintf(out,
@@ -156,313 +183,338 @@ static void __attribute__ ((__noreturn__)) usage(FILE * out)
        exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
 }
 
-static int
-itsit(char *cp, char *dp)
+static void dirlist_add_dir(struct wh_dirlist **ls0, int type, const char *dir)
 {
-       int i = strlen(dp);
+       struct stat st;
+       struct wh_dirlist *prev = NULL, *ls = *ls0;
 
-       if (dp[0] == 's' && dp[1] == '.' && itsit(cp, dp + 2))
-               return 1;
-       if (!strcmp(dp + i - 2, ".Z"))
-               i -= 2;
-       else if (!strcmp(dp + i - 3, ".gz"))
-               i -= 3;
-       else if (!strcmp(dp + i - 4, ".bz2"))
-               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;
-}
+       DBG(printf("add dir: '%s'", dir));
 
-static void
-findin(char *dir, char *cp)
-{
-       DIR *dirp;
-       struct dirent *dp;
-       char *d, *dd;
-       size_t l;
-       char dirbuf[1024];
-       struct stat statbuf;
-
-       dd = strchr(dir, '*');
-       if (!dd) {
-               dirp = opendir(dir);
-               if (dirp == NULL)
+       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(printf("  already in the list, ignore"));
                        return;
-               while ((dp = readdir(dirp)) != NULL) {
-                       if (itsit(cp, dp->d_name)) {
-                               count++;
-                               if (print)
-                                       printf(" %s/%s", dir, dp->d_name);
-                       }
                }
-               closedir(dirp);
-               return;
+               prev = ls;
+               ls = ls->next;
        }
 
-       l = strlen(dir);
-       if (l < sizeof(dirbuf)) {
-               /* refuse excessively long names */
-               strcpy(dirbuf, dir);
-               d = strchr(dirbuf, '*');
-               if (d)
-                       *d = 0;
-               dirp = opendir(dirbuf);
-               if (dirp == NULL)
-                       return;
-               while ((dp = readdir(dirp)) != NULL) {
-                       if (!strcmp(dp->d_name, ".") ||
-                           !strcmp(dp->d_name, ".."))
-                               continue;
-                       if (strlen(dp->d_name) + l > sizeof(dirbuf))
-                               continue;
-                       sprintf(d, "%s", dp->d_name);
-                       if (stat(dirbuf, &statbuf))
-                               continue;
-                       if (!S_ISDIR(statbuf.st_mode))
-                               continue;
-                       strcat(d, dd + 1);
-                       findin(dirbuf, cp);
-               }
-               closedir(dirp);
+       DBG(printf("  adding new directory"));
+
+       ls = xcalloc(1, sizeof(*ls));
+       ls->st_ino = st.st_ino;
+       ls->st_dev = st.st_dev;
+       ls->type = type;
+       ls->path = xstrdup(dir);
+
+       if (!*ls0)
+               *ls0 = ls;              /* first in the list */
+       else {
+               assert(prev);
+               prev->next = ls;        /* add to the end of the list */
        }
        return;
-
 }
 
-static int inpath(const char *str)
+/* special case for '*' in the paths */
+static void dirlist_add_subdir(struct wh_dirlist **ls, int type, const char *dir)
 {
-       size_t i;
+       char buf[PATH_MAX], *d;
+       DIR *dirp;
+       struct dirent *dp;
 
-       for (i = 0; i < ARRAY_SIZE(bindirs) - 1 ; i++)
-               if (!strcmp(bindirs[i], str))
-                       return 1;
+       strncpy(buf, dir, PATH_MAX);
+       buf[PATH_MAX - 1] = '\0';
 
-       for (i = 0; i < ARRAY_SIZE(mandirs) - 1; i++)
-               if (!strcmp(mandirs[i], str))
-                       return 1;
+       DBG(printf("add subdir: %s", buf));
 
-       for (i = 0; i < ARRAY_SIZE(srcdirs) - 1; i++)
-               if (!strcmp(srcdirs[i], str))
-                       return 1;
+       d = strchr(buf, '*');
+       if (!d)
+               return;
+       *d = 0;
 
-       return 0;
+       dirp = opendir(buf);
+       if (!dirp)
+               return;
+
+       DBG(printf("  scan: %s", buf));
+
+       while ((dp = readdir(dirp)) != NULL) {
+               if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
+                       continue;
+               snprintf(d, PATH_MAX - (d - buf), "%s", dp->d_name);
+               /* a dir definition can have a star in middle of path */
+               strcat(buf, strchr(dir, '*') + 1);
+               dirlist_add_dir(ls, type, buf);
+       }
+       closedir(dirp);
+       return;
 }
 
-static void fillpath(void)
+static void construct_dirlist_from_env(const char *env,
+                                      struct wh_dirlist **ls,
+                                      int type)
 {
-       char *key=NULL, *tok=NULL, *pathcp, *path = getenv("PATH");
-       int i = 0;
-
+       char *key = NULL, *tok = NULL, *pathcp, *path = getenv(env);
 
        if (!path)
                return;
        pathcp = xstrdup(path);
 
-       for (tok = strtok_r(pathcp, ":", &key); tok;
-            tok = strtok_r(NULL, ":", &key)) {
-
-               /* make sure we don't repeat the search path */
-               if (inpath(tok))
-                       continue;
-
-               pathdir = xrealloc(pathdir, (i + 1) * sizeof(char *));
-               pathdir[i++] = xstrdup(tok);
-       }
+       DBG(printf("construct from env: %s", path));
 
-       pathdir = xrealloc(pathdir, (i + 1) * sizeof(char *));
-       pathdir[i] = NULL;
+       for (tok = strtok_r(pathcp, ":", &key); tok;
+            tok = strtok_r(NULL, ":", &key))
+               dirlist_add_dir(ls, type, tok);
 
-       pathdir_p = pathdir;
        free(pathcp);
+       return;
 }
 
-static void freepath(void)
+static void construct_dirlist_from_argv(struct wh_dirlist **ls,
+                                       int *idx,
+                                       int argc,
+                                       char *argv[],
+                                       int type)
 {
-       free(pathdir);
+       DBG(printf("construct argv[%d..]", *idx));
+
+       for (; *idx < argc; (*idx)++) {
+               if (*argv[*idx] == '-')                 /* end of the list */
+                       return;
+               dirlist_add_dir(ls, type, argv[*idx]);
+       }
+       return;
 }
 
-static void
-findv(char **dirv, int dirc, char *cp)
+static void construct_dirlist(struct wh_dirlist **ls,
+                             int type,
+                             const char **paths)
 {
+       size_t i;
 
-       while (dirc > 0)
-               findin(*dirv++, cp), dirc--;
-}
+       DBG(printf("construct from dirs"));
 
-static void
-looksrc(char *cp)
-{
-       if (Sflag == NULL)
-               findv(srcdirs, ARRAY_SIZE(srcdirs)-1, cp);
-       else
-               findv(Sflag, Scnt, cp);
+       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]);
+       }
+       return;
 }
 
-static void
-lookbin(char *cp)
+static void free_dirlist(struct wh_dirlist **ls0, int type)
 {
-       if (Bflag == NULL) {
-               findv(bindirs, ARRAY_SIZE(bindirs)-1, cp);
-               while (*pathdir_p)
-                       findin(*pathdir_p++, cp);               /* look $PATH */
-        } else
-               findv(Bflag, Bcnt, cp);
-}
+       struct wh_dirlist *prev = NULL, *next, *ls = *ls0;
 
-static void
-lookman(char *cp)
-{
-       if (Mflag == NULL)
-               findv(mandirs, ARRAY_SIZE(mandirs)-1, cp);
-       else
-               findv(Mflag, Mcnt, cp);
-}
+       *ls0 = NULL;
 
-static void
-getlist(int *argcp, char ***argvp, char ***flagp, int *cntp)
-{
-       (*argvp)++;
-       *flagp = *argvp;
-       *cntp = 0;
-       for ((*argcp)--; *argcp > 0 && (*argvp)[0][0] != '-'; (*argcp)--)
-               (*cntp)++, (*argvp)++;
-       (*argcp)++;
-       (*argvp)--;
+       DBG(printf("freeing dirlist"));
+
+       while (ls) {
+               if (ls->type & type) {
+                       next = ls->next;
+
+                       DBG(printf("freeing dir: %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;
+               }
+       }
+
+       return;
 }
 
-static void
-zerof(void)
+
+static int filename_equal(const char *cp, const char *dp)
 {
-       if (sflag && bflag && mflag)
-               sflag = bflag = mflag = 0;
+       int i = strlen(dp);
+
+       /*DBG(printf("compare '%s' and '%s'", cp, dp));*/
+
+       if (dp[0] == 's' && dp[1] == '.' && filename_equal(cp, dp + 2))
+               return 1;
+       if (!strcmp(dp + i - 2, ".Z"))
+               i -= 2;
+       else if (!strcmp(dp + i - 3, ".gz"))
+               i -= 3;
+       else if (!strcmp(dp + i - 3, ".xz"))
+               i -= 3;
+       else if (!strcmp(dp + i - 4, ".bz2"))
+               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 int
-print_again(char *cp)
+static void findin(const char *dir, const char *pattern, int *count, char **wait)
 {
-       if (print)
-               printf("%s:", cp);
-       if (sflag) {
-               looksrc(cp);
-               if (uflag && print == 0 && count != 1) {
-                       print = 1;
-                       return 1;
-               }
-       }
-       count = 0;
-       if (bflag) {
-               lookbin(cp);
-               if (uflag && print == 0 && count != 1) {
-                       print = 1;
-                       return 1;
-               }
-       }
-       count = 0;
-       if (mflag) {
-               lookman(cp);
-               if (uflag && print == 0 && count != 1) {
-                       print = 1;
-                       return 1;
-               }
+       DIR *dirp;
+       struct dirent *dp;
+
+       dirp = opendir(dir);
+       if (dirp == NULL)
+               return;
+
+       DBG(printf("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);
        }
-       return 0;
+       closedir(dirp);
+       return;
 }
 
-static void
-lookup(char *cp)
+static void lookup(const char *pattern, struct wh_dirlist *ls, int want)
 {
-       register char *dp;
-
-       for (dp = cp; *dp; dp++)
-               continue;
-       for (; dp > cp; dp--) {
-               if (*dp == '.') {
-                       *dp = 0;
-                       break;
-               }
+       char patbuf[PATH_MAX];
+       int count = 0;
+       char *wait = NULL, *p;
+
+       DBG(printf("lookup dirs for '%s' (%s)", patbuf, pattern));
+
+       /* canonicalize pattern -- remove path suffix etc. */
+       p = strrchr(pattern, '/');
+       p = p ? p + 1 : (char *) pattern;
+       strncpy(patbuf, p, PATH_MAX);
+       patbuf[PATH_MAX - 1] = '\0';
+       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);
        }
-       for (dp = cp; *dp; dp++)
-               if (*dp == '/')
-                       cp = dp + 1;
-       if (uflag) {
-               print = 0;
-               count = 0;
-       } else
-               print = 1;
-
-       while (print_again(cp))
-               /* all in print_again() */ ;
-
-       if (print)
-               printf("\n");
+
+       free(wait);
+
+       if ((count && !uflag) || (uflag && count > 1))
+               putchar('\n');
+       return;
 }
 
-/*
- * whereis name
- * look for source, documentation and binaries
- */
-int
-main(int argc, char **argv)
+int main(int argc, char **argv)
 {
+       struct wh_dirlist *ls = NULL;
+       int want = ALL_DIRS;
+       int i;
+
        setlocale(LC_ALL, "");
        bindtextdomain(PACKAGE, LOCALEDIR);
        textdomain(PACKAGE);
        atexit(close_stdout);
 
-       argc--, argv++;
-       if (argc == 0)
+       if (argc == 1)
                usage(stderr);
 
-       do
-               if (argv[0][0] == '-') {
-                       register char *cp = argv[0] + 1;
-                       while (*cp) switch (*cp++) {
+       construct_dirlist(&ls, BIN_DIR, bindirs);
+       construct_dirlist_from_env("PATH", &ls, BIN_DIR);
+
+       construct_dirlist(&ls, MAN_DIR, mandirs);
+       construct_dirlist(&ls, SRC_DIR, srcdirs);
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
 
+               if (*arg != '-') {
+                       lookup(arg, ls, want);
+                       continue;
+               }
+
+               if (i > 1 && *argv[i - 1] != '-')
+                       /* the list of search patterns has been interupted by
+                        * any non-pattern option, then reset the mask for
+                        * wanted directories. For example:
+                        *
+                        *    whereis -m ls -b tr
+                        *
+                        * search for "ls" in mandirs and "tr" in bindirs
+                        */
+                       want = ALL_DIRS;
+
+               for (++arg; arg && *arg; arg++) {
+                       switch (*arg) {
                        case 'f':
                                break;
-
-                       case 'S':
-                               getlist(&argc, &argv, &Sflag, &Scnt);
+                       case 'u':
+                               uflag = 1;
                                break;
-
                        case 'B':
-                               getlist(&argc, &argv, &Bflag, &Bcnt);
+                               if (*(arg + 1))
+                                       usage(stderr);
+                               i++;
+                               free_dirlist(&ls, BIN_DIR);
+                               construct_dirlist_from_argv(
+                                       &ls, &i, argc, argv, BIN_DIR);
                                break;
-
                        case 'M':
-                               getlist(&argc, &argv, &Mflag, &Mcnt);
+                               if (*(arg + 1))
+                                       usage(stderr);
+                               i++;
+                               free_dirlist(&ls, MAN_DIR);
+                               construct_dirlist_from_argv(
+                                       &ls, &i, argc, argv, MAN_DIR);
+                               break;
+                       case 'S':
+                               if (*(arg + 1))
+                                       usage(stderr);
+                               i++;
+                               free_dirlist(&ls, SRC_DIR);
+                               construct_dirlist_from_argv(
+                                       &ls, &i, argc, argv, SRC_DIR);
                                break;
-
-                       case 's':
-                               zerof();
-                               sflag++;
-                               continue;
-
-                       case 'u':
-                               uflag++;
-                               continue;
-
                        case 'b':
-                               zerof();
-                               bflag++;
-                               continue;
-
+                               want = want == ALL_DIRS ? BIN_DIR : want | BIN_DIR;
+                               break;
                        case 'm':
-                               zerof();
-                               mflag++;
-                               continue;
+                               want = want == ALL_DIRS ? MAN_DIR : want | MAN_DIR;
+                               break;
+                       case 's':
+                               want = want == ALL_DIRS ? SRC_DIR : want | SRC_DIR;
+                               break;
                        case 'V':
                                printf(UTIL_LINUX_VERSION);
                                return EXIT_SUCCESS;
@@ -471,14 +523,9 @@ main(int argc, char **argv)
                        default:
                                usage(stderr);
                        }
-                       argv++;
-               } else {
-                       if (Bcnt == 0 && pathdir == NULL)
-                               fillpath();
-                       lookup(*argv++);
                }
-       while (--argc > 0);
+       }
 
-       freepath();
+       free_dirlist(&ls, ALL_DIRS);
        return EXIT_SUCCESS;
 }