xref: /minix3/usr.bin/man/man.c (revision 0a6a1f1d05b60e214de2f05a7310ddd1f0e590e7)
1  /*	$NetBSD: man.c,v 1.62 2014/08/14 15:31:12 apb Exp $	*/
2  
3  /*
4   * Copyright (c) 1987, 1993, 1994, 1995
5   *	The Regents of the University of California.  All rights reserved.
6   *
7   * Redistribution and use in source and binary forms, with or without
8   * modification, are permitted provided that the following conditions
9   * are met:
10   * 1. Redistributions of source code must retain the above copyright
11   *    notice, this list of conditions and the following disclaimer.
12   * 2. Redistributions in binary form must reproduce the above copyright
13   *    notice, this list of conditions and the following disclaimer in the
14   *    documentation and/or other materials provided with the distribution.
15   * 3. Neither the name of the University nor the names of its contributors
16   *    may be used to endorse or promote products derived from this software
17   *    without specific prior written permission.
18   *
19   * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22   * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23   * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25   * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28   * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29   * SUCH DAMAGE.
30   */
31  
32  #include <sys/cdefs.h>
33  
34  #ifndef lint
35  __COPYRIGHT("@(#) Copyright (c) 1987, 1993, 1994, 1995\
36   The Regents of the University of California.  All rights reserved.");
37  #endif /* not lint */
38  
39  #ifndef lint
40  #if 0
41  static char sccsid[] = "@(#)man.c	8.17 (Berkeley) 1/31/95";
42  #else
43  __RCSID("$NetBSD: man.c,v 1.62 2014/08/14 15:31:12 apb Exp $");
44  #endif
45  #endif /* not lint */
46  
47  #include <sys/param.h>
48  #include <sys/queue.h>
49  #include <sys/stat.h>
50  #include <sys/utsname.h>
51  
52  #include <ctype.h>
53  #include <err.h>
54  #include <errno.h>
55  #include <fcntl.h>
56  #include <fnmatch.h>
57  #include <glob.h>
58  #include <signal.h>
59  #include <stdio.h>
60  #include <stdlib.h>
61  #include <string.h>
62  #include <unistd.h>
63  #include <util.h>
64  #include <locale.h>
65  
66  #include "manconf.h"
67  #include "pathnames.h"
68  
69  #ifndef MAN_DEBUG
70  #define MAN_DEBUG 0		/* debug path output */
71  #endif
72  
73  /*
74   * manstate: structure collecting the current global state so we can
75   * easily identify it and pass it to helper functions in one arg.
76   */
77  struct manstate {
78  	/* command line flags */
79  	int all;		/* -a: show all matches rather than first */
80  	int cat;		/* -c: do not use a pager */
81  	char *conffile;		/* -C: use alternate config file */
82  	int how;		/* -h: show SYNOPSIS only */
83  	char *manpath;		/* -M: alternate MANPATH */
84  	char *addpath;		/* -m: add these dirs to front of manpath */
85  	char *pathsearch;	/* -S: path of man must contain this string */
86  	char *sectionname;	/* -s: limit search to a given man section */
87  	int where;		/* -w: just show paths of all matching files */
88  	int getpath;	/* -p: print the path of directories containing man pages */
89  
90  	/* important tags from the config file */
91  	TAG *defaultpath;	/* _default: default MANPATH */
92  	TAG *subdirs;		/* _subdir: default subdir search list */
93  	TAG *suffixlist;	/* _suffix: for files that can be cat()'d */
94  	TAG *buildlist;		/* _build: for files that must be built */
95  
96  	/* tags for internal use */
97  	TAG *intmp;		/* _intmp: tmp files we must cleanup */
98  	TAG *missinglist;	/* _missing: pages we couldn't find */
99  	TAG *mymanpath;		/* _new_path: final version of MANPATH */
100  	TAG *section;		/* <sec>: tag for m.sectionname */
101  
102  	/* other misc stuff */
103  	const char *pager;	/* pager to use */
104  	size_t pagerlen;	/* length of the above */
105  	const char *machine;	/* machine */
106  	const char *machclass;	/* machine class */
107  };
108  
109  /*
110   * prototypes
111   */
112  static void	 build_page(const char *, char **, struct manstate *);
113  static void	 cat(const char *);
114  static const char	*check_pager(const char *);
115  static int	 cleanup(void);
116  static void	 how(const char *);
117  static void	 jump(char **, const char *, const char *) __dead;
118  static int	 manual(char *, struct manstate *, glob_t *);
119  static void	 onsig(int) __dead;
120  static void	 usage(void) __dead;
121  static void	 addpath(struct manstate *, const char *, size_t, const char *);
122  static const char *getclass(const char *);
123  static void printmanpath(struct manstate *);
124  
125  /*
126   * main function
127   */
128  int
main(int argc,char ** argv)129  main(int argc, char **argv)
130  {
131  	static struct manstate m;
132  	int ch, abs_section, found;
133  	ENTRY *esubd, *epath;
134  	char *p, **ap, *cmd;
135  	size_t len;
136  	glob_t pg;
137  
138  	setprogname(argv[0]);
139  	setlocale(LC_ALL, "");
140  	/*
141  	 * parse command line...
142  	 */
143  	while ((ch = getopt(argc, argv, "-aC:cfhkM:m:P:ps:S:w")) != -1)
144  		switch (ch) {
145  		case 'a':
146  			m.all = 1;
147  			break;
148  		case 'C':
149  			m.conffile = optarg;
150  			break;
151  		case 'c':
152  		case '-':	/* XXX: '-' is a deprecated version of '-c' */
153  			m.cat = 1;
154  			break;
155  		case 'h':
156  			m.how = 1;
157  			break;
158  		case 'm':
159  			m.addpath = optarg;
160  			break;
161  		case 'M':
162  		case 'P':	/* -P for backward compatibility */
163  			m.manpath = strdup(optarg);
164  			break;
165  		case 'p':
166  			m.getpath = 1;
167  			break;
168  		/*
169  		 * The -f and -k options are backward compatible,
170  		 * undocumented ways of calling whatis(1) and apropos(1).
171  		 */
172  		case 'f':
173  			jump(argv, "-f", "whatis");
174  			/* NOTREACHED */
175  		case 'k':
176  			jump(argv, "-k", "apropos");
177  			/* NOTREACHED */
178  		case 's':
179  			if (m.sectionname != NULL)
180  				usage();
181  			m.sectionname = optarg;
182  			break;
183  		case 'S':
184  			m.pathsearch = optarg;
185  			break;
186  		case 'w':
187  			m.all = m.where = 1;
188  			break;
189  		case '?':
190  		default:
191  			usage();
192  		}
193  	argc -= optind;
194  	argv += optind;
195  
196  	if (!m.getpath && !argc)
197  		usage();
198  
199  	/*
200  	 * read the configuration file and collect any other information
201  	 * we will need (machine type, pager, section [if specified
202  	 * without '-s'], and MANPATH through the environment).
203  	 */
204  	config(m.conffile);    /* exits on error ... */
205  
206  	if ((m.machine = getenv("MACHINE")) == NULL) {
207  		struct utsname utsname;
208  
209  		if (uname(&utsname) == -1)
210  			err(EXIT_FAILURE, "uname");
211  		m.machine = utsname.machine;
212  	}
213  
214  	m.machclass = getclass(m.machine);
215  
216  	if (!m.cat && !m.how && !m.where) {  /* if we need a pager ... */
217  		if (!isatty(STDOUT_FILENO)) {
218  			m.cat = 1;
219  		} else {
220  			if ((m.pager = getenv("PAGER")) != NULL &&
221  			    m.pager[0] != '\0')
222  				m.pager = check_pager(m.pager);
223  			else
224  				m.pager = _PATH_PAGER;
225  			m.pagerlen = strlen(m.pager);
226  		}
227  	}
228  
229  	/* do we need to set m.section to a non-null value? */
230  	if (m.sectionname) {
231  
232  		m.section = gettag(m.sectionname, 0); /* -s must be a section */
233  		if (m.section == NULL)
234  			errx(EXIT_FAILURE, "unknown section: %s", m.sectionname);
235  
236  	} else if (argc > 1) {
237  
238  		m.section = gettag(*argv, 0);  /* might be a section? */
239  		if (m.section) {
240  			argv++;
241  			argc--;
242  		}
243  
244  	}
245  
246  	if (m.manpath == NULL)
247  		m.manpath = getenv("MANPATH"); /* note: -M overrides getenv */
248  
249  
250  	/*
251  	 * get default values from config file, plus create the tags we
252  	 * use for keeping internal state.  make sure all our mallocs
253  	 * go through.
254  	 */
255  	/* from cfg file */
256  	m.defaultpath = gettag("_default", 1);
257  	m.subdirs = gettag("_subdir", 1);
258  	m.suffixlist = gettag("_suffix", 1);
259  	m.buildlist = gettag("_build", 1);
260  	/* internal use */
261  	m.mymanpath = gettag("_new_path", 1);
262  	m.missinglist = gettag("_missing", 1);
263  	m.intmp = gettag("_intmp", 1);
264  	if (!m.defaultpath || !m.subdirs || !m.suffixlist || !m.buildlist ||
265  	    !m.mymanpath || !m.missinglist || !m.intmp)
266  		errx(EXIT_FAILURE, "malloc failed");
267  
268  	/*
269  	 * are we using a section whose elements are all absolute paths?
270  	 * (we only need to look at the first entry on the section list,
271  	 * as config() will ensure that any additional entries will match
272  	 * the first one.)
273  	 */
274  	abs_section = (m.section != NULL &&
275  		!TAILQ_EMPTY(&m.section->entrylist) &&
276  	    		*(TAILQ_FIRST(&m.section->entrylist)->s) == '/');
277  
278  	/*
279  	 * now that we have all the data we need, we must determine the
280  	 * manpath we are going to use to find the requested entries using
281  	 * the following steps...
282  	 *
283  	 * [1] if the user specified a section and that section's elements
284  	 *     from the config file are all absolute paths, then we override
285  	 *     defaultpath and -M/MANPATH with the section's absolute paths.
286  	 */
287  	if (abs_section) {
288  		m.manpath = NULL;	   	/* ignore -M/MANPATH */
289  		m.defaultpath = m.section;	/* overwrite _default path */
290  		m.section = NULL;		/* promoted to defaultpath */
291  	}
292  
293  	/*
294  	 * [2] section can now only be non-null if the user asked for
295  	 *     a section and that section's elements did not have
296           *     absolute paths.  in this case we use the section's
297  	 *     elements to override _subdir from the config file.
298  	 *
299  	 * after this step, we are done processing "m.section"...
300  	 */
301  	if (m.section)
302  		m.subdirs = m.section;
303  
304  	/*
305  	 * [3] we need to setup the path we want to use (m.mymanpath).
306  	 *     if the user gave us a path (m.manpath) use it, otherwise
307  	 *     go with the default.   in either case we need to append
308  	 *     the subdir and machine spec to each element of the path.
309  	 *
310  	 *     for absolute section paths that come from the config file,
311  	 *     we only append the subdir spec if the path ends in
312  	 *     a '/' --- elements that do not end in '/' are assumed to
313  	 *     not have subdirectories.  this is mainly for backward compat,
314  	 *     but it allows non-subdir configs like:
315  	 *	sect3       /usr/share/man/{old/,}cat3
316  	 *	doc         /usr/{pkg,share}/doc/{sendmail/op,sendmail/intro}
317  	 *
318  	 *     note that we try and be careful to not put double slashes
319  	 *     in the path (e.g. we want /usr/share/man/man1, not
320  	 *     /usr/share/man//man1) because "more" will put the filename
321  	 *     we generate in its prompt and the double slashes look ugly.
322  	 */
323  	if (m.manpath) {
324  
325  		/* note: strtok is going to destroy m.manpath */
326  		for (p = strtok(m.manpath, ":") ; p ; p = strtok(NULL, ":")) {
327  			len = strlen(p);
328  			if (len < 1)
329  				continue;
330  			TAILQ_FOREACH(esubd, &m.subdirs->entrylist, q)
331  				addpath(&m, p, len, esubd->s);
332  		}
333  
334  	} else {
335  
336  		TAILQ_FOREACH(epath, &m.defaultpath->entrylist, q) {
337  			/* handle trailing "/" magic here ... */
338  		  	if (abs_section && epath->s[epath->len - 1] != '/') {
339  				addpath(&m, "", 1, epath->s);
340  				continue;
341  			}
342  
343  			TAILQ_FOREACH(esubd, &m.subdirs->entrylist, q)
344  				addpath(&m, epath->s, epath->len, esubd->s);
345  		}
346  
347  	}
348  
349  	/*
350  	 * [4] finally, prepend the "-m" m.addpath to mymanpath if it
351  	 *     was specified.   subdirs and machine are always applied to
352  	 *     m.addpath.
353  	 */
354  	if (m.addpath) {
355  
356  		/* note: strtok is going to destroy m.addpath */
357  		for (p = strtok(m.addpath, ":") ; p ; p = strtok(NULL, ":")) {
358  			len = strlen(p);
359  			if (len < 1)
360  				continue;
361  			TAILQ_FOREACH(esubd, &m.subdirs->entrylist, q)
362  				addpath(&m, p, len, esubd->s);
363  		}
364  
365  	}
366  
367  	if (m.getpath)
368  		printmanpath(&m);
369  
370  	/*
371  	 * now m.mymanpath is complete!
372  	 */
373  #if MAN_DEBUG
374  	printf("mymanpath:\n");
375  	TAILQ_FOREACH(epath, &m.mymanpath->entrylist, q) {
376  		printf("\t%s\n", epath->s);
377  	}
378  #endif
379  
380  	/*
381  	 * start searching for matching files and format them if necessary.
382  	 * setup an interrupt handler so that we can ensure that temporary
383  	 * files go away.
384  	 */
385  	(void)signal(SIGINT, onsig);
386  	(void)signal(SIGHUP, onsig);
387  	(void)signal(SIGPIPE, onsig);
388  
389  	memset(&pg, 0, sizeof(pg));
390  	for (found = 0; *argv; ++argv)
391  		if (manual(*argv, &m, &pg)) {
392  			found = 1;
393  		}
394  
395  	/* if nothing found, we're done. */
396  	if (!found) {
397  		(void)cleanup();
398  		exit(EXIT_FAILURE);
399  	}
400  
401  	/*
402  	 * handle the simple display cases first (m.cat, m.how, m.where)
403  	 */
404  	if (m.cat) {
405  		for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
406  			if (**ap == '\0')
407  				continue;
408  			cat(*ap);
409  		}
410  		exit(cleanup());
411  	}
412  	if (m.how) {
413  		for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
414  			if (**ap == '\0')
415  				continue;
416  			how(*ap);
417  		}
418  		exit(cleanup());
419  	}
420  	if (m.where) {
421  		for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
422  			if (**ap == '\0')
423  				continue;
424  			(void)printf("%s\n", *ap);
425  		}
426  		exit(cleanup());
427  	}
428  
429  	/*
430  	 * normal case - we display things in a single command, so
431           * build a list of things to display.  first compute total
432  	 * length of buffer we will need so we can malloc it.
433  	 */
434  	for (ap = pg.gl_pathv, len = m.pagerlen + 1; *ap != NULL; ++ap) {
435  		if (**ap == '\0')
436  			continue;
437  		len += strlen(*ap) + 1;
438  	}
439  	if ((cmd = malloc(len)) == NULL) {
440  		warn("malloc");
441  		(void)cleanup();
442  		exit(EXIT_FAILURE);
443  	}
444  
445  	/* now build the command string... */
446  	p = cmd;
447  	len = m.pagerlen;
448  	memcpy(p, m.pager, len);
449  	p += len;
450  	*p++ = ' ';
451  	for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
452  		if (**ap == '\0')
453  			continue;
454  		len = strlen(*ap);
455  		memcpy(p, *ap, len);
456  		p += len;
457  		*p++ = ' ';
458  	}
459  	*--p = '\0';
460  
461  	/* Use system(3) in case someone's pager is "pager arg1 arg2". */
462  	(void)system(cmd);
463  
464  	exit(cleanup());
465  }
466  
467  static int
manual_find_literalfile(struct manstate * mp,char ** pv)468  manual_find_literalfile(struct manstate *mp, char **pv)
469  {
470  	ENTRY *suffix;
471  	int found;
472  	char buf[MAXPATHLEN];
473  	const char *p;
474  	int suflen;
475  
476  	found = 0;
477  
478  	/*
479  	 * Expand both '*' and suffix to force an actual
480  	 * match via fnmatch(3). Since the only match in pg
481  	 * is the literal file, the match is genuine.
482  	 */
483  
484  	TAILQ_FOREACH(suffix, &mp->buildlist->entrylist, q) {
485  		for (p = suffix->s, suflen = 0;
486  		    *p != '\0' && !isspace((unsigned char)*p);
487  		    ++p)
488  			++suflen;
489  		if (*p == '\0')
490  			continue;
491  
492  		(void)snprintf(buf, sizeof(buf), "*%.*s", suflen, suffix->s);
493  
494  		if (!fnmatch(buf, *pv, 0)) {
495  			if (!mp->where)
496  				build_page(p + 1, pv, mp);
497  			found = 1;
498  			break;
499  		}
500  	}
501  
502  	return found;
503  }
504  
505  static int
manual_find_buildkeyword(const char * prefix,const char * escpage,struct manstate * mp,char ** pv)506  manual_find_buildkeyword(const char *prefix, const char *escpage,
507      struct manstate *mp, char **pv)
508  {
509  	ENTRY *suffix;
510  	int found;
511  	char buf[MAXPATHLEN];
512  	const char *p;
513  	int suflen;
514  
515  	found = 0;
516  	/* Try the _build keywords next. */
517  	TAILQ_FOREACH(suffix, &mp->buildlist->entrylist, q) {
518  		for (p = suffix->s, suflen = 0;
519  		    *p != '\0' && !isspace((unsigned char)*p);
520  		    ++p)
521  			++suflen;
522  		if (*p == '\0')
523  			continue;
524  
525  		(void)snprintf(buf, sizeof(buf), "%s%s%.*s",
526  		    prefix, escpage, suflen, suffix->s);
527  		if (!fnmatch(buf, *pv, 0)) {
528  			if (!mp->where)
529  				build_page(p + 1, pv, mp);
530  			found = 1;
531  			break;
532  		}
533  	}
534  
535  	return found;
536  }
537  
538  /*
539   * manual --
540   *	Search the manuals for the pages.
541   */
542  static int
manual(char * page,struct manstate * mp,glob_t * pg)543  manual(char *page, struct manstate *mp, glob_t *pg)
544  {
545  	ENTRY *suffix, *mdir;
546  	int anyfound, error, found;
547  	size_t cnt;
548  	char *p, buf[MAXPATHLEN], *escpage, *eptr;
549  	static const char escglob[] = "\\~?*{}[]";
550  
551  	anyfound = 0;
552  
553  	/*
554  	 * Fixup page which may contain glob(3) special characters, e.g.
555  	 * the famous "No man page for [" FAQ.
556  	 */
557  	if ((escpage = malloc((2 * strlen(page)) + 1)) == NULL) {
558  		warn("malloc");
559  		(void)cleanup();
560  		exit(EXIT_FAILURE);
561  	}
562  
563  	p = page;
564  	eptr = escpage;
565  
566  	while (*p) {
567  		if (strchr(escglob, *p) != NULL) {
568  			*eptr++ = '\\';
569  			*eptr++ = *p++;
570  		} else
571  			*eptr++ = *p++;
572  	}
573  
574  	*eptr = '\0';
575  
576  	/*
577  	 * If 'page' is given with an absolute path,
578  	 * or a relative path explicitly beginning with "./"
579  	 * or "../", then interpret it as a file specification.
580  	 */
581  	if ((page[0] == '/')
582  	    || (page[0] == '.' && page[1] == '/')
583  	    || (page[0] == '.' && page[1] == '.' && page[2] == '/')
584  	    ) {
585  		/* check if file actually exists */
586  		(void)strlcpy(buf, escpage, sizeof(buf));
587  		error = glob(buf, GLOB_APPEND | GLOB_BRACE | GLOB_NOSORT, NULL, pg);
588  		if (error != 0) {
589  			if (error == GLOB_NOMATCH) {
590  				goto notfound;
591  			} else {
592  				errx(EXIT_FAILURE, "glob failed");
593  			}
594  		}
595  
596  		if (pg->gl_matchc == 0)
597  			goto notfound;
598  
599  		/* literal file only yields one match */
600  		cnt = pg->gl_pathc - pg->gl_matchc;
601  
602  		if (manual_find_literalfile(mp, &pg->gl_pathv[cnt])) {
603  			anyfound = 1;
604  		} else {
605  			/* It's not a man page, forget about it. */
606  			*pg->gl_pathv[cnt] = '\0';
607  		}
608  
609    notfound:
610  		if (!anyfound) {
611  			if (addentry(mp->missinglist, page, 0) < 0) {
612  				warn("malloc");
613  				(void)cleanup();
614  				exit(EXIT_FAILURE);
615  			}
616  		}
617  		free(escpage);
618  		return anyfound;
619  	}
620  
621  	/* For each man directory in mymanpath ... */
622  	TAILQ_FOREACH(mdir, &mp->mymanpath->entrylist, q) {
623  
624  		/*
625  		 * use glob(3) to look in the filesystem for matching files.
626  		 * match any suffix here, as we will check that later.
627  		 */
628  		(void)snprintf(buf, sizeof(buf), "%s/%s.*", mdir->s, escpage);
629  		if ((error = glob(buf,
630  		    GLOB_APPEND | GLOB_BRACE | GLOB_NOSORT, NULL, pg)) != 0) {
631  			if (error == GLOB_NOMATCH)
632  				continue;
633  			else {
634  				warn("globbing");
635  				(void)cleanup();
636  				exit(EXIT_FAILURE);
637  			}
638  		}
639  		if (pg->gl_matchc == 0)
640  			continue;
641  
642  		/*
643  		 * start going through the matches glob(3) just found and
644  		 * use m.pathsearch (if present) to filter out pages we
645  		 * don't want.  then verify the suffix is valid, and build
646  		 * the page if we have a _build suffix.
647  		 */
648  		for (cnt = pg->gl_pathc - pg->gl_matchc;
649  		    cnt < pg->gl_pathc; ++cnt) {
650  
651  			/* filter on directory path name */
652  			if (mp->pathsearch) {
653  				p = strstr(pg->gl_pathv[cnt], mp->pathsearch);
654  				if (!p || strchr(p, '/') == NULL) {
655  					*pg->gl_pathv[cnt] = '\0'; /* zap! */
656  					continue;
657  				}
658  			}
659  
660  			/*
661  			 * Try the _suffix keywords first.
662  			 *
663  			 * XXX
664  			 * Older versions of man.conf didn't have the _suffix
665  			 * keywords, it was assumed that everything was a .0.
666  			 * We just test for .0 first, it's fast and probably
667  			 * going to hit.
668  			 */
669  			(void)snprintf(buf, sizeof(buf), "*/%s.0", escpage);
670  			if (!fnmatch(buf, pg->gl_pathv[cnt], 0))
671  				goto next;
672  
673  			found = 0;
674  			TAILQ_FOREACH(suffix, &mp->suffixlist->entrylist, q) {
675  				(void)snprintf(buf,
676  				     sizeof(buf), "*/%s%s", escpage,
677  				     suffix->s);
678  				if (!fnmatch(buf, pg->gl_pathv[cnt], 0)) {
679  					found = 1;
680  					break;
681  				}
682  			}
683  			if (found)
684  				goto next;
685  
686  			/* Try the _build keywords next. */
687  			found = manual_find_buildkeyword("*/", escpage,
688  				mp, &pg->gl_pathv[cnt]);
689  			if (found) {
690  next:				anyfound = 1;
691  				if (!mp->all) {
692  					/* Delete any other matches. */
693  					while (++cnt< pg->gl_pathc)
694  						*pg->gl_pathv[cnt] = '\0';
695  					break;
696  				}
697  				continue;
698  			}
699  
700  			/* It's not a man page, forget about it. */
701  			*pg->gl_pathv[cnt] = '\0';
702  		}
703  
704  		if (anyfound && !mp->all)
705  			break;
706  	}
707  
708  	/* If not found, enter onto the missing list. */
709  	if (!anyfound) {
710  		if (addentry(mp->missinglist, page, 0) < 0) {
711  			warn("malloc");
712  			(void)cleanup();
713  			exit(EXIT_FAILURE);
714  		}
715  	}
716  
717  	free(escpage);
718  	return anyfound;
719  }
720  
721  /*
722   * A do-nothing counterpart to fmtcheck(3) that only supplies the
723   * __format_arg marker.  Actual fmtcheck(3) call is done once in
724   * config().
725   */
726  __always_inline __format_arg(2)
727  static inline const char *
fmtcheck_ok(const char * userfmt,const char * template)728  fmtcheck_ok(const char *userfmt, const char *template)
729  {
730  	return userfmt;
731  }
732  
733  /*
734   * build_page --
735   *	Build a man page for display.
736   */
737  static void
build_page(const char * fmt,char ** pathp,struct manstate * mp)738  build_page(const char *fmt, char **pathp, struct manstate *mp)
739  {
740  	static int warned;
741  	int olddir, fd, n;
742  	size_t tmpdirlen;
743  	char *p, *b;
744  	char buf[MAXPATHLEN], cmd[MAXPATHLEN], tpath[MAXPATHLEN];
745  	const char *tmpdir;
746  
747  	/* Let the user know this may take awhile. */
748  	if (!warned) {
749  		warned = 1;
750  		warnx("Formatting manual page...");
751  	}
752  
753         /*
754          * Historically man chdir'd to the root of the man tree.
755          * This was used in man pages that contained relative ".so"
756          * directives (including other man pages for command aliases etc.)
757          * It even went one step farther, by examining the first line
758          * of the man page and parsing the .so filename so it would
759          * make hard(?) links to the cat'ted man pages for space savings.
760          * (We don't do that here, but we could).
761          */
762  
763         /* copy and find the end */
764         for (b = buf, p = *pathp; (*b++ = *p++) != '\0';)
765                 continue;
766  
767  	/*
768  	 * skip the last two path components, page name and man[n] ...
769  	 * (e.g. buf will be "/usr/share/man" and p will be "man1/man.1")
770  	 * we also save a pointer to our current directory so that we
771  	 * can fchdir() back to it.  this allows relative MANDIR paths
772  	 * to work with multiple man pages... e.g. consider:
773  	 *   	cd /usr/share && man -M ./man cat ls
774  	 * when no "cat1" subdir files are present.
775  	 */
776  	olddir = -1;
777  	for (--b, --p, n = 2; b != buf; b--, p--)
778  		if (*b == '/')
779  			if (--n == 0) {
780  				*b = '\0';
781  				olddir = open(".", O_RDONLY);
782  				(void) chdir(buf);
783  				p++;
784  				break;
785  			}
786  
787  
788  	/* advance fmt past the suffix spec to the printf format string */
789  	for (; *fmt && isspace((unsigned char)*fmt); ++fmt)
790  		continue;
791  
792  	/*
793  	 * Get a temporary file and build a version of the file
794  	 * to display.  Replace the old file name with the new one.
795  	 */
796  	if ((tmpdir = getenv("TMPDIR")) == NULL)
797  		tmpdir = _PATH_TMP;
798  	tmpdirlen = strlen(tmpdir);
799  	(void)snprintf(tpath, sizeof (tpath), "%s%s%s", tmpdir,
800  	    (tmpdirlen > 0 && tmpdir[tmpdirlen-1] == '/') ? "" : "/", TMPFILE);
801  	if ((fd = mkstemp(tpath)) == -1) {
802  		warn("%s", tpath);
803  		(void)cleanup();
804  		exit(EXIT_FAILURE);
805  	}
806  	(void)snprintf(buf, sizeof(buf), "%s > %s", fmt, tpath);
807  	(void)snprintf(cmd, sizeof(cmd), fmtcheck_ok(buf, "%s"), p);
808  	(void)system(cmd);
809  	(void)close(fd);
810  	if ((*pathp = strdup(tpath)) == NULL) {
811  		warn("malloc");
812  		(void)cleanup();
813  		exit(EXIT_FAILURE);
814  	}
815  
816  	/* Link the built file into the remove-when-done list. */
817  	if (addentry(mp->intmp, *pathp, 0) < 0) {
818  		warn("malloc");
819  		(void)cleanup();
820  		exit(EXIT_FAILURE);
821  	}
822  
823  	/* restore old directory so relative manpaths still work */
824  	if (olddir != -1) {
825  		fchdir(olddir);
826  		close(olddir);
827  	}
828  }
829  
830  /*
831   * how --
832   *	display how information
833   */
834  static void
how(const char * fname)835  how(const char *fname)
836  {
837  	FILE *fp;
838  
839  	int lcnt, print;
840  	char buf[256];
841  	const char *p;
842  
843  	if (!(fp = fopen(fname, "r"))) {
844  		warn("%s", fname);
845  		(void)cleanup();
846  		exit(EXIT_FAILURE);
847  	}
848  #define	S1	"SYNOPSIS"
849  #define	S2	"S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS"
850  #define	D1	"DESCRIPTION"
851  #define	D2	"D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN"
852  	for (lcnt = print = 0; fgets(buf, sizeof(buf), fp);) {
853  		if (!strncmp(buf, S1, sizeof(S1) - 1) ||
854  		    !strncmp(buf, S2, sizeof(S2) - 1)) {
855  			print = 1;
856  			continue;
857  		} else if (!strncmp(buf, D1, sizeof(D1) - 1) ||
858  		    !strncmp(buf, D2, sizeof(D2) - 1)) {
859  			if (fp)
860  				(void)fclose(fp);
861  			return;
862  		}
863  		if (!print)
864  			continue;
865  		if (*buf == '\n')
866  			++lcnt;
867  		else {
868  			for(; lcnt; --lcnt)
869  				(void)putchar('\n');
870  			for (p = buf; isspace((unsigned char)*p); ++p)
871  				continue;
872  			(void)fputs(p, stdout);
873  		}
874  	}
875  	(void)fclose(fp);
876  }
877  
878  /*
879   * cat --
880   *	cat out the file
881   */
882  static void
cat(const char * fname)883  cat(const char *fname)
884  {
885  	int fd;
886  	ssize_t n;
887  	char buf[2048];
888  
889  	if ((fd = open(fname, O_RDONLY, 0)) < 0) {
890  		warn("%s", fname);
891  		(void)cleanup();
892  		exit(EXIT_FAILURE);
893  	}
894  	while ((n = read(fd, buf, sizeof(buf))) > 0)
895  		if (write(STDOUT_FILENO, buf, (size_t)n) != n) {
896  			warn("write");
897  			(void)cleanup();
898  			exit(EXIT_FAILURE);
899  		}
900  	if (n == -1) {
901  		warn("read");
902  		(void)cleanup();
903  		exit(EXIT_FAILURE);
904  	}
905  	(void)close(fd);
906  }
907  
908  /*
909   * check_pager --
910   *	check the user supplied page information
911   */
912  static const char *
check_pager(const char * name)913  check_pager(const char *name)
914  {
915  	const char *p;
916  
917  	/*
918  	 * if the user uses "more", we make it "more -s"; watch out for
919  	 * PAGER = "mypager /usr/ucb/more"
920  	 */
921  	for (p = name; *p && !isspace((unsigned char)*p); ++p)
922  		continue;
923  	for (; p > name && *p != '/'; --p);
924  	if (p != name)
925  		++p;
926  
927  	/* make sure it's "more", not "morex" */
928  	if (!strncmp(p, "more", 4) && (!p[4] || isspace((unsigned char)p[4]))){
929  		char *newname;
930  		(void)asprintf(&newname, "%s %s", p, "-s");
931  		name = newname;
932  	}
933  
934  	return name;
935  }
936  
937  /*
938   * jump --
939   *	strip out flag argument and jump
940   */
941  static void
jump(char ** argv,const char * flag,const char * name)942  jump(char **argv, const char *flag, const char *name)
943  {
944  	char **arg;
945  
946  	argv[0] = __UNCONST(name);
947  	for (arg = argv + 1; *arg; ++arg)
948  		if (!strcmp(*arg, flag))
949  			break;
950  	for (; *arg; ++arg)
951  		arg[0] = arg[1];
952  	execvp(name, argv);
953  	err(EXIT_FAILURE, "Cannot execute `%s'", name);
954  }
955  
956  /*
957   * onsig --
958   *	If signaled, delete the temporary files.
959   */
960  static void
onsig(int signo)961  onsig(int signo)
962  {
963  
964  	(void)cleanup();
965  
966  	(void)raise_default_signal(signo);
967  
968  	/* NOTREACHED */
969  	exit(EXIT_FAILURE);
970  }
971  
972  /*
973   * cleanup --
974   *	Clean up temporary files, show any error messages.
975   */
976  static int
cleanup(void)977  cleanup(void)
978  {
979  	TAG *intmpp, *missp;
980  	ENTRY *ep;
981  	int rval;
982  
983  	rval = EXIT_SUCCESS;
984  	/*
985  	 * note that _missing and _intmp were created by main(), so
986  	 * gettag() cannot return NULL here.
987  	 */
988  	missp = gettag("_missing", 0);	/* missing man pages */
989  	intmpp = gettag("_intmp", 0);	/* tmp files we need to unlink */
990  
991  	TAILQ_FOREACH(ep, &missp->entrylist, q) {
992  		warnx("no entry for %s in the manual.", ep->s);
993  		rval = EXIT_FAILURE;
994  	}
995  
996  	TAILQ_FOREACH(ep, &intmpp->entrylist, q)
997  		(void)unlink(ep->s);
998  
999  	return rval;
1000  }
1001  
1002  static const char *
getclass(const char * machine)1003  getclass(const char *machine)
1004  {
1005  	char buf[BUFSIZ];
1006  	TAG *t;
1007  	snprintf(buf, sizeof(buf), "_%s", machine);
1008  	t = gettag(buf, 0);
1009  	return t != NULL && !TAILQ_EMPTY(&t->entrylist) ?
1010  	    TAILQ_FIRST(&t->entrylist)->s : NULL;
1011  }
1012  
1013  static void
addpath(struct manstate * m,const char * dir,size_t len,const char * sub)1014  addpath(struct manstate *m, const char *dir, size_t len, const char *sub)
1015  {
1016  	char buf[2 * MAXPATHLEN + 1];
1017  	(void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,%s%s%s}",
1018  	     dir, (dir[len - 1] == '/') ? "" : "/", sub, m->machine,
1019  	     m->machclass ? "/" : "", m->machclass ? m->machclass : "",
1020  	     m->machclass ? "," : "");
1021  	if (addentry(m->mymanpath, buf, 0) < 0)
1022  		errx(EXIT_FAILURE, "malloc failed");
1023  }
1024  
1025  /*
1026   * usage --
1027   *	print usage message and die
1028   */
1029  static void
usage(void)1030  usage(void)
1031  {
1032  	(void)fprintf(stderr, "Usage: %s [-acw|-h] [-C cfg] [-M path] "
1033  	    "[-m path] [-S srch] [[-s] sect] name ...\n", getprogname());
1034  	(void)fprintf(stderr,
1035  	    "Usage: %s -k [-C cfg] [-M path] [-m path] keyword ...\n",
1036  	    getprogname());
1037  	(void)fprintf(stderr, "Usage: %s -p\n", getprogname());
1038  	exit(EXIT_FAILURE);
1039  }
1040  
1041  /*
1042   * printmanpath --
1043   *	Prints a list of directories containing man pages.
1044   */
1045  static void
printmanpath(struct manstate * m)1046  printmanpath(struct manstate *m)
1047  {
1048  	ENTRY *esubd;
1049  	char *defaultpath = NULL; /* _default tag value from man.conf. */
1050  	char *buf; /* for storing temporary values */
1051  	char **ap;
1052  	glob_t pg;
1053  	struct stat sb;
1054  	TAG *path = m->defaultpath;
1055  	TAG *subdirs = m->subdirs;
1056  
1057  	/* the tail queue is empty if no _default tag is defined in * man.conf */
1058  	if (TAILQ_EMPTY(&path->entrylist))
1059  		errx(EXIT_FAILURE, "Empty manpath");
1060  
1061  	defaultpath = TAILQ_LAST(&path->entrylist, tqh)->s;
1062  
1063  	if (glob(defaultpath, GLOB_BRACE | GLOB_NOSORT, NULL, &pg) != 0)
1064  		err(EXIT_FAILURE, "glob failed");
1065  
1066  	if (pg.gl_matchc == 0) {
1067  		warnx("Default path in %s doesn't exist", _PATH_MANCONF);
1068  		globfree(&pg);
1069  		return;
1070  	}
1071  
1072  	TAILQ_FOREACH(esubd, &subdirs->entrylist, q) {
1073  		/* Drop cat page directory, only sources are relevant. */
1074  		if (strncmp(esubd->s, "man", 3))
1075  			continue;
1076  
1077  		for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
1078  			if (asprintf(&buf, "%s%s", *ap, esubd->s) == -1)
1079  				err(EXIT_FAILURE, "memory allocation error");
1080  			/* Skip non-directories. */
1081  			if (stat(buf, &sb) == 0 && S_ISDIR(sb.st_mode))
1082  				printf("%s\n", buf);
1083  
1084  			free(buf);
1085  		}
1086  	}
1087  	globfree(&pg);
1088  }
1089