1  /*-
       2   * Copyright (c) 1980 The Regents of the University of California.
       3   * All rights reserved.
       4   *
       5   * Redistribution and use in source and binary forms, with or without
       6   * modification, are permitted provided that the following conditions
       7   * are met:
       8   * 1. Redistributions of source code must retain the above copyright
       9   *    notice, this list of conditions and the following disclaimer.
      10   * 2. Redistributions in binary form must reproduce the above copyright
      11   *    notice, this list of conditions and the following disclaimer in the
      12   *    documentation and/or other materials provided with the distribution.
      13   * 3. All advertising materials mentioning features or use of this software
      14   *    must display the following acknowledgement:
      15   *	This product includes software developed by the University of
      16   *	California, Berkeley and its contributors.
      17   * 4. Neither the name of the University nor the names of its contributors
      18   *    may be used to endorse or promote products derived from this software
      19   *    without specific prior written permission.
      20   *
      21   * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
      22   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
      23   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
      24   * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
      25   * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
      26   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
      27   * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
      28   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
      29   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
      30   * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
      31   * SUCH DAMAGE.
      32   *
      33   * 1999-02-22 Arkadiusz MiĆkiewicz <misiek@pld.ORG.PL>
      34   * - added Native Language Support
      35   * 2011-08-12 Davidlohr Bueso <dave@gnu.org>
      36   * - added $PATH lookup
      37   *
      38   * Copyright (C) 2013 Karel Zak <kzak@redhat.com>
      39   *               2013 Sami Kerola <kerolasa@iki.fi>
      40   */
      41  
      42  #include <sys/param.h>
      43  #include <sys/types.h>
      44  #include <sys/stat.h>
      45  #include <dirent.h>
      46  #include <stdio.h>
      47  #include <stdlib.h>
      48  #include <string.h>
      49  #include <ctype.h>
      50  #include <assert.h>
      51  #ifdef HAVE_FNMATCH
      52  # include <fnmatch.h>
      53  #endif
      54  
      55  #include "xalloc.h"
      56  #include "nls.h"
      57  #include "c.h"
      58  #include "closestream.h"
      59  #include "canonicalize.h"
      60  
      61  #include "debug.h"
      62  
      63  static UL_DEBUG_DEFINE_MASK(whereis);
      64  UL_DEBUG_DEFINE_MASKNAMES(whereis) = UL_DEBUG_EMPTY_MASKNAMES;
      65  
      66  #define WHEREIS_DEBUG_INIT	(1 << 1)
      67  #define WHEREIS_DEBUG_PATH	(1 << 2)
      68  #define WHEREIS_DEBUG_ENV	(1 << 3)
      69  #define WHEREIS_DEBUG_ARGV	(1 << 4)
      70  #define WHEREIS_DEBUG_SEARCH	(1 << 5)
      71  #define WHEREIS_DEBUG_STATIC	(1 << 6)
      72  #define WHEREIS_DEBUG_LIST	(1 << 7)
      73  #define WHEREIS_DEBUG_ALL	0xFFFF
      74  
      75  #define DBG(m, x)       __UL_DBG(whereis, WHEREIS_DEBUG_, m, x)
      76  #define ON_DBG(m, x)    __UL_DBG_CALL(whereis, WHEREIS_DEBUG_, m, x)
      77  
      78  #define UL_DEBUG_CURRENT_MASK	UL_DEBUG_MASK(whereis)
      79  #include "debugobj.h"
      80  
      81  static char uflag;
      82  static char use_glob;
      83  
      84  /* supported types */
      85  enum {
      86  	BIN_DIR = (1 << 1),
      87  	MAN_DIR = (1 << 2),
      88  	SRC_DIR = (1 << 3),
      89  
      90  	ALL_DIRS = BIN_DIR | MAN_DIR | SRC_DIR
      91  };
      92  
      93  /* directories */
      94  struct wh_dirlist {
      95  	int	type;
      96  	dev_t	st_dev;
      97  	ino_t	st_ino;
      98  	char	*path;
      99  
     100  	struct wh_dirlist *next;
     101  };
     102  
     103  static const char *bindirs[] = {
     104  	"/usr/bin",
     105  	"/usr/sbin",
     106  	"/bin",
     107  	"/sbin",
     108  #if defined(MULTIARCHTRIPLET)
     109  	"/lib/" MULTIARCHTRIPLET,
     110  	"/usr/lib/" MULTIARCHTRIPLET,
     111  	"/usr/local/lib/" MULTIARCHTRIPLET,
     112  #endif
     113  	"/usr/lib",
     114  	"/usr/lib32",
     115  	"/usr/lib64",
     116  	"/etc",
     117  	"/usr/etc",
     118  	"/lib",
     119  	"/lib32",
     120  	"/lib64",
     121  	"/usr/games",
     122  	"/usr/games/bin",
     123  	"/usr/games/lib",
     124  	"/usr/emacs/etc",
     125  	"/usr/lib/emacs/*/etc",
     126  	"/usr/TeX/bin",
     127  	"/usr/tex/bin",
     128  	"/usr/interviews/bin/LINUX",
     129  
     130  	"/usr/X11R6/bin",
     131  	"/usr/X386/bin",
     132  	"/usr/bin/X11",
     133  	"/usr/X11/bin",
     134  	"/usr/X11R5/bin",
     135  
     136  	"/usr/local/bin",
     137  	"/usr/local/sbin",
     138  	"/usr/local/etc",
     139  	"/usr/local/lib",
     140  	"/usr/local/games",
     141  	"/usr/local/games/bin",
     142  	"/usr/local/emacs/etc",
     143  	"/usr/local/TeX/bin",
     144  	"/usr/local/tex/bin",
     145  	"/usr/local/bin/X11",
     146  
     147  	"/usr/contrib",
     148  	"/usr/hosts",
     149  	"/usr/include",
     150  
     151  	"/usr/g++-include",
     152  
     153  	"/usr/ucb",
     154  	"/usr/old",
     155  	"/usr/new",
     156  	"/usr/local",
     157  	"/usr/libexec",
     158  	"/usr/share",
     159  
     160  	"/opt/*/bin",
     161  	NULL
     162  };
     163  
     164  static const char *mandirs[] = {
     165  	"/usr/man/*",
     166  	"/usr/share/man/*",
     167  	"/usr/X386/man/*",
     168  	"/usr/X11/man/*",
     169  	"/usr/TeX/man/*",
     170  	"/usr/interviews/man/mann",
     171  	"/usr/share/info",
     172  	NULL
     173  };
     174  
     175  static const char *srcdirs[] = {
     176  	"/usr/src/*",
     177  	"/usr/src/lib/libc/*",
     178  	"/usr/src/lib/libc/net/*",
     179  	"/usr/src/ucb/pascal",
     180  	"/usr/src/ucb/pascal/utilities",
     181  	"/usr/src/undoc",
     182  	NULL
     183  };
     184  
     185  static void whereis_init_debug(void)
     186  {
     187  	__UL_INIT_DEBUG_FROM_ENV(whereis, WHEREIS_DEBUG_, 0, WHEREIS_DEBUG);
     188  }
     189  
     190  static const char *whereis_type_to_name(int type)
     191  {
     192  	switch (type) {
     193  	case BIN_DIR: return "bin";
     194  	case MAN_DIR: return "man";
     195  	case SRC_DIR: return "src";
     196  	default:      return "???";
     197  	}
     198  }
     199  
     200  static void __attribute__((__noreturn__)) usage(void)
     201  {
     202  	FILE *out = stdout;
     203  
     204  	fputs(USAGE_HEADER, out);
     205  	fprintf(out, _(" %s [options] [-BMS <dir>... -f] <name>\n"), program_invocation_short_name);
     206  
     207  	fputs(USAGE_SEPARATOR, out);
     208  	fputs(_("Locate the binary, source, and manual-page files for a command.\n"), out);
     209  
     210  	fputs(USAGE_OPTIONS, out);
     211  	fputs(_(" -b         search only for binaries\n"), out);
     212  	fputs(_(" -B <dirs>  define binaries lookup path\n"), out);
     213  	fputs(_(" -m         search only for manuals and infos\n"), out);
     214  	fputs(_(" -M <dirs>  define man and info lookup path\n"), out);
     215  	fputs(_(" -s         search only for sources\n"), out);
     216  	fputs(_(" -S <dirs>  define sources lookup path\n"), out);
     217  	fputs(_(" -f         terminate <dirs> argument list\n"), out);
     218  	fputs(_(" -u         search for unusual entries\n"), out);
     219  	fputs(_(" -g         interpret name as glob (pathnames pattern)\n"), out);
     220  	fputs(_(" -l         output effective lookup paths\n"), out);
     221  
     222  	fputs(USAGE_SEPARATOR, out);
     223  	printf(USAGE_HELP_OPTIONS(16));
     224  	printf(USAGE_MAN_TAIL("whereis(1)"));
     225  	exit(EXIT_SUCCESS);
     226  }
     227  
     228  static void dirlist_add_dir(struct wh_dirlist **ls0, int type, const char *dir)
     229  {
     230  	struct stat st;
     231  	struct wh_dirlist *prev = NULL, *ls = *ls0;
     232  
     233  	if (access(dir, R_OK) != 0)
     234  		return;
     235  	if (stat(dir, &st) != 0 || !S_ISDIR(st.st_mode))
     236  		return;
     237  
     238  	while (ls) {
     239  		if (ls->st_ino == st.st_ino &&
     240  		    ls->st_dev == st.st_dev &&
     241  		    ls->type == type) {
     242  			DBG(LIST, ul_debugobj(*ls0, "  ignore (already in list): %s", dir));
     243  			return;
     244  		}
     245  		prev = ls;
     246  		ls = ls->next;
     247  	}
     248  
     249  
     250  	ls = xcalloc(1, sizeof(*ls));
     251  	ls->st_ino = st.st_ino;
     252  	ls->st_dev = st.st_dev;
     253  	ls->type = type;
     254  	ls->path = canonicalize_path(dir);
     255  
     256  	if (!*ls0)
     257  		*ls0 = ls;		/* first in the list */
     258  	else {
     259  		assert(prev);
     260  		prev->next = ls;	/* add to the end of the list */
     261  	}
     262  
     263  	DBG(LIST, ul_debugobj(*ls0, "  add dir: %s", ls->path));
     264  }
     265  
     266  /* special case for '*' in the paths */
     267  static void dirlist_add_subdir(struct wh_dirlist **ls, int type, const char *dir)
     268  {
     269  	char buf[PATH_MAX], *d;
     270  	DIR *dirp;
     271  	struct dirent *dp;
     272  	char *postfix;
     273  	size_t len;
     274  
     275  	postfix = strchr(dir, '*');
     276  	if (!postfix)
     277  		goto ignore;
     278  
     279  	/* copy begin of the path to the buffer (part before '*') */
     280  	len = (postfix - dir) + 1;
     281  	xstrncpy(buf, dir, len);
     282  
     283  	/* remember place where to append subdirs */
     284  	d = buf + len - 1;
     285  
     286  	/* skip '*' */
     287  	postfix++;
     288  	if (!*postfix)
     289  		postfix = NULL;
     290  
     291  	/* open parental dir t scan */
     292  	dirp = opendir(buf);
     293  	if (!dirp)
     294  		goto ignore;
     295  
     296  	DBG(LIST, ul_debugobj(*ls, " scanning subdirs: %s [%s<subdir>%s]",
     297  				dir, buf, postfix ? postfix : ""));
     298  
     299  	while ((dp = readdir(dirp)) != NULL) {
     300  		if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
     301  			continue;
     302  		if (postfix)
     303  			snprintf(d, PATH_MAX - len, "%s%s", dp->d_name, postfix);
     304  		else
     305  			snprintf(d, PATH_MAX - len, "%s", dp->d_name);
     306  
     307  		dirlist_add_dir(ls, type, buf);
     308  	}
     309  	closedir(dirp);
     310  	return;
     311  ignore:
     312  	DBG(LIST, ul_debugobj(*ls, " ignore path: %s", dir));
     313  }
     314  
     315  static void construct_dirlist_from_env(const char *env,
     316  				       struct wh_dirlist **ls,
     317  				       int type)
     318  {
     319  	char *key = NULL, *tok = NULL, *pathcp, *path = getenv(env);
     320  
     321  	if (!path)
     322  		return;
     323  	pathcp = xstrdup(path);
     324  
     325  	DBG(ENV, ul_debugobj(*ls, "construct %s dirlist from: %s",
     326  				whereis_type_to_name(type), path));
     327  
     328  	for (tok = strtok_r(pathcp, ":", &key); tok;
     329  	     tok = strtok_r(NULL, ":", &key))
     330  		dirlist_add_dir(ls, type, tok);
     331  
     332  	free(pathcp);
     333  }
     334  
     335  static void construct_dirlist_from_argv(struct wh_dirlist **ls,
     336  					int *idx,
     337  					int argc,
     338  					char *argv[],
     339  					int type)
     340  {
     341  	int i;
     342  
     343  	DBG(ARGV, ul_debugobj(*ls, "construct %s dirlist from argv[%d..]",
     344  				whereis_type_to_name(type), *idx));
     345  
     346  	for (i = *idx; i < argc; i++) {
     347  		if (*argv[i] == '-')			/* end of the list */
     348  			break;
     349  
     350  		DBG(ARGV, ul_debugobj(*ls, "  using argv[%d]: %s", *idx, argv[*idx]));
     351  		dirlist_add_dir(ls, type, argv[i]);
     352  		*idx = i;
     353  	}
     354  }
     355  
     356  static void construct_dirlist(struct wh_dirlist **ls,
     357  			      int type,
     358  			      const char **paths)
     359  {
     360  	size_t i;
     361  
     362  	DBG(STATIC, ul_debugobj(*ls, "construct %s dirlist from static array",
     363  				whereis_type_to_name(type)));
     364  
     365  	for (i = 0; paths[i]; i++) {
     366  		if (!strchr(paths[i], '*'))
     367  			dirlist_add_dir(ls, type, paths[i]);
     368  		else
     369  			dirlist_add_subdir(ls, type, paths[i]);
     370  	}
     371  }
     372  
     373  static void free_dirlist(struct wh_dirlist **ls0, int type)
     374  {
     375  	struct wh_dirlist *prev = NULL, *next, *ls = *ls0;
     376  
     377  	*ls0 = NULL;
     378  
     379  	DBG(LIST, ul_debugobj(*ls0, "free dirlist"));
     380  
     381  	while (ls) {
     382  		if (ls->type & type) {
     383  			next = ls->next;
     384  			DBG(LIST, ul_debugobj(*ls0, " free: %s", ls->path));
     385  			free(ls->path);
     386  			free(ls);
     387  			ls = next;
     388  			if (prev)
     389  				prev->next = ls;
     390  		} else {
     391  			if (!prev)
     392  				*ls0 = ls;	/* first unremoved */
     393  			prev = ls;
     394  			ls = ls->next;
     395  		}
     396  	}
     397  }
     398  
     399  
     400  static int filename_equal(const char *cp, const char *dp, int type)
     401  {
     402  	size_t i;
     403  
     404  	DBG(SEARCH, ul_debug("compare '%s' and '%s'", cp, dp));
     405  
     406  #ifdef HAVE_FNMATCH
     407  	if (use_glob)
     408  		return fnmatch(cp, dp, 0) == 0;
     409  #endif
     410  	if (type & SRC_DIR &&
     411  	    dp[0] == 's' && dp[1] == '.' && filename_equal(cp, dp + 2, type))
     412  		return 1;
     413  
     414  	i = strlen(dp);
     415  
     416  	if (type & MAN_DIR) {
     417  		if (i > 1 && !strcmp(dp + i - 2, ".Z"))
     418  			i -= 2;
     419  		else if (i > 2 && !strcmp(dp + i - 3, ".gz"))
     420  			i -= 3;
     421  		else if (i > 2 && !strcmp(dp + i - 3, ".xz"))
     422  			i -= 3;
     423  		else if (i > 3 && !strcmp(dp + i - 4, ".bz2"))
     424  			i -= 4;
     425  		else if (i > 3 && !strcmp(dp + i - 4, ".zst"))
     426  			i -= 4;
     427  	}
     428  	while (*cp && *dp && *cp == *dp)
     429  		cp++, dp++, i--;
     430  	if (*cp == 0 && *dp == 0)
     431  		return 1;
     432  	if (!(type & BIN_DIR) && *cp == 0 && *dp++ == '.') {
     433  		--i;
     434  		while (i > 0 && *dp)
     435  			if (--i, *dp++ == '.')
     436  				return (*dp++ == 'C' && *dp++ == 0);
     437  		return 1;
     438  	}
     439  	return 0;
     440  }
     441  
     442  static void findin(const char *dir, const char *pattern, int *count,
     443  		   char **wait, int type)
     444  {
     445  	DIR *dirp;
     446  	struct dirent *dp;
     447  
     448  	dirp = opendir(dir);
     449  	if (dirp == NULL)
     450  		return;
     451  
     452  	DBG(SEARCH, ul_debug("find '%s' in '%s'", pattern, dir));
     453  
     454  	while ((dp = readdir(dirp)) != NULL) {
     455  		if (!filename_equal(pattern, dp->d_name, type))
     456  			continue;
     457  
     458  		if (uflag && *count == 0)
     459  			xasprintf(wait, "%s/%s", dir, dp->d_name);
     460  
     461  		else if (uflag && *count == 1 && *wait) {
     462  			printf("%s: %s %s/%s", pattern, *wait, dir,  dp->d_name);
     463  			free(*wait);
     464  			*wait = NULL;
     465  		} else
     466  			printf(" %s/%s", dir, dp->d_name);
     467  		++(*count);
     468  	}
     469  	closedir(dirp);
     470  }
     471  
     472  static void lookup(const char *pattern, struct wh_dirlist *ls, int want)
     473  {
     474  	char patbuf[PATH_MAX];
     475  	int count = 0;
     476  	char *wait = NULL, *p;
     477  
     478  	/* canonicalize pattern -- remove path suffix etc. */
     479  	p = strrchr(pattern, '/');
     480  	p = p ? p + 1 : (char *) pattern;
     481  	xstrncpy(patbuf, p, PATH_MAX);
     482  
     483  	DBG(SEARCH, ul_debug("lookup dirs for '%s' (%s), want: %s %s %s",
     484  				patbuf, pattern,
     485  				want & BIN_DIR ? "bin" : "",
     486  				want & MAN_DIR ? "man" : "",
     487  				want & SRC_DIR ? "src" : ""));
     488  
     489  	if (!uflag)
     490  		/* if -u not specified then we always print the pattern */
     491  		printf("%s:", patbuf);
     492  
     493  	for (; ls; ls = ls->next) {
     494  		if ((ls->type & want) && ls->path)
     495  			findin(ls->path, patbuf, &count, &wait, ls->type);
     496  	}
     497  
     498  	free(wait);
     499  
     500  	if (!uflag || count > 1)
     501  		putchar('\n');
     502  }
     503  
     504  static void list_dirlist(struct wh_dirlist *ls)
     505  {
     506  	while (ls) {
     507  		if (ls->path) {
     508  			switch (ls->type) {
     509  			case BIN_DIR:
     510  				printf("bin: ");
     511  				break;
     512  			case MAN_DIR:
     513  				printf("man: ");
     514  				break;
     515  			case SRC_DIR:
     516  				printf("src: ");
     517  				break;
     518  			default:
     519  				abort();
     520  			}
     521  			printf("%s\n", ls->path);
     522  		}
     523  		ls = ls->next;
     524  	}
     525  }
     526  
     527  int main(int argc, char **argv)
     528  {
     529  	struct wh_dirlist *ls = NULL;
     530  	int want = ALL_DIRS;
     531  	int i, want_resetable = 0, opt_f_missing = 0;
     532  
     533  	setlocale(LC_ALL, "");
     534  	bindtextdomain(PACKAGE, LOCALEDIR);
     535  	textdomain(PACKAGE);
     536  	close_stdout_atexit();
     537  
     538  	if (argc <= 1) {
     539  		warnx(_("not enough arguments"));
     540  		errtryhelp(EXIT_FAILURE);
     541  	} else {
     542  		/* first arg may be one of our standard longopts */
     543  		if (!strcmp(argv[1], "--help"))
     544  			usage();
     545  		if (!strcmp(argv[1], "--version"))
     546  			print_version(EXIT_SUCCESS);
     547  	}
     548  
     549  	whereis_init_debug();
     550  
     551  	construct_dirlist(&ls, BIN_DIR, bindirs);
     552  	construct_dirlist_from_env("PATH", &ls, BIN_DIR);
     553  
     554  	construct_dirlist(&ls, MAN_DIR, mandirs);
     555  	construct_dirlist_from_env("MANPATH", &ls, MAN_DIR);
     556  
     557  	construct_dirlist(&ls, SRC_DIR, srcdirs);
     558  
     559  	for (i = 1; i < argc; i++) {
     560  		const char *arg = argv[i];
     561  		int arg_i = i;
     562  
     563  		DBG(ARGV, ul_debug("argv[%d]: %s", i, arg));
     564  
     565  		if (*arg != '-') {
     566  			lookup(arg, ls, want);
     567  			/*
     568  			 * The lookup mask ("want") is cumulative and it's
     569  			 * resettable only when it has been already used.
     570  			 *
     571  			 *  whereis -b -m foo     :'foo' mask=BIN|MAN
     572  			 *  whereis -b foo bar    :'foo' and 'bar' mask=BIN|MAN
     573  			 *  whereis -b foo -m bar :'foo' mask=BIN; 'bar' mask=MAN
     574  			 */
     575  			want_resetable = 1;
     576  			continue;
     577  		}
     578  
     579  		for (++arg; arg && *arg; arg++) {
     580  			DBG(ARGV, ul_debug("  arg: %s", arg));
     581  
     582  			switch (*arg) {
     583  			case 'f':
     584  				opt_f_missing = 0;
     585  				break;
     586  			case 'u':
     587  				uflag = 1;
     588  				opt_f_missing = 0;
     589  				break;
     590  			case 'B':
     591  				if (*(arg + 1)) {
     592  					warnx(_("bad usage"));
     593  					errtryhelp(EXIT_FAILURE);
     594  				}
     595  				i++;
     596  				free_dirlist(&ls, BIN_DIR);
     597  				construct_dirlist_from_argv(
     598  					&ls, &i, argc, argv, BIN_DIR);
     599  				opt_f_missing = 1;
     600  				break;
     601  			case 'M':
     602  				if (*(arg + 1)) {
     603  					warnx(_("bad usage"));
     604  					errtryhelp(EXIT_FAILURE);
     605  				}
     606  				i++;
     607  				free_dirlist(&ls, MAN_DIR);
     608  				construct_dirlist_from_argv(
     609  					&ls, &i, argc, argv, MAN_DIR);
     610  				opt_f_missing = 1;
     611  				break;
     612  			case 'S':
     613  				if (*(arg + 1)) {
     614  					warnx(_("bad usage"));
     615  					errtryhelp(EXIT_FAILURE);
     616  				}
     617  				i++;
     618  				free_dirlist(&ls, SRC_DIR);
     619  				construct_dirlist_from_argv(
     620  					&ls, &i, argc, argv, SRC_DIR);
     621  				opt_f_missing = 1;
     622  				break;
     623  			case 'b':
     624  				if (want_resetable) {
     625  					want = ALL_DIRS;
     626  					want_resetable = 0;
     627  				}
     628  				want = want == ALL_DIRS ? BIN_DIR : want | BIN_DIR;
     629  				opt_f_missing = 0;
     630  				break;
     631  			case 'm':
     632  				if (want_resetable) {
     633  					want = ALL_DIRS;
     634  					want_resetable = 0;
     635  				}
     636  				want = want == ALL_DIRS ? MAN_DIR : want | MAN_DIR;
     637  				opt_f_missing = 0;
     638  				break;
     639  			case 's':
     640  				if (want_resetable) {
     641  					want = ALL_DIRS;
     642  					want_resetable = 0;
     643  				}
     644  				want = want == ALL_DIRS ? SRC_DIR : want | SRC_DIR;
     645  				opt_f_missing = 0;
     646  				break;
     647  			case 'l':
     648  				list_dirlist(ls);
     649  				break;
     650  			case 'g':
     651  				use_glob = 1;
     652  				break;
     653  
     654  			case 'V':
     655  				print_version(EXIT_SUCCESS);
     656  			case 'h':
     657  				usage();
     658  			default:
     659  				warnx(_("bad usage"));
     660  				errtryhelp(EXIT_FAILURE);
     661  			}
     662  
     663  			if (arg_i < i)		/* moved to the next argv[] item */
     664  				break;
     665  		}
     666  	}
     667  
     668  	free_dirlist(&ls, ALL_DIRS);
     669  	if (opt_f_missing)
     670  		errx(EXIT_FAILURE, _("option -f is missing"));
     671  	return EXIT_SUCCESS;
     672  }