[BACK]Return to man.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / man

File: [local] / src / usr.bin / man / Attic / man.c (download)

Revision 1.49, Fri Jan 16 06:40:09 2015 UTC (9 years, 4 months ago) by deraadt
Branch: MAIN
CVS Tags: OPENBSD_5_7_BASE, OPENBSD_5_7
Changes since 1.48: +1 -2 lines

Replace <sys/param.h> with <limits.h> and other less dirty headers where
possible.  Annotate <sys/param.h> lines with their current reasons.  Switch
to PATH_MAX, NGROUPS_MAX, HOST_NAME_MAX+1, LOGIN_NAME_MAX, etc.  Change
MIN() and MAX() to local definitions of MINIMUM() and MAXIMUM() where
sensible to avoid pulling in the pollution.  These are the files confirmed
through binary verification.
ok guenther, millert, doug (helped with the verification protocol)

/*	$OpenBSD: man.c,v 1.49 2015/01/16 06:40:09 deraadt Exp $	*/
/*	$NetBSD: man.c,v 1.7 1995/09/28 06:05:34 tls Exp $	*/

/*
 * Copyright (c) 2010, 2011, 2012 Ingo Schwarze <schwarze@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * Copyright (c) 1987, 1993, 1994, 1995
 *	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. 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.
 */

#include <sys/types.h>
#include <sys/queue.h>
#include <sys/stat.h>

#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <glob.h>
#include <signal.h>
#include <stdio.h>
#include <libgen.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "config.h"
#include "pathnames.h"

int f_all, f_where;
static char gbuf[MAXPATHLEN * 2];
static TAG *section;

extern char *__progname;

static void	 clearlist(TAG *);
static void	 parse_path(TAG *, const char *);
static void	 append_subdirs(TAG *, const char *);
static void	 build_page(char *, char **);
static void	 cat(char *);
static int	 cleanup(int);
static void	 how(char *);
static void	 jump(char **, char *, char *);
static int	 manual(const char *, TAG *, glob_t *);
static void	 check_companion(char **, TAG *);
static void	 onsig(int);
static void	 usage(void);

sigset_t	blocksigs;

int
main(int argc, char *argv[])
{
	extern char *optarg;
	extern int optind;
	TAG *searchlist;
	glob_t pg;
	size_t len;
	int ch, f_cat, f_how, found;
	const char *pager, *p_path;
	char **ap, *cmd, *machine, *p, *p_add, *sflag;
	char *conffile;

	if (argv[1] == NULL && strcmp(basename(__progname), "help") == 0) {
		static char *nargv[3];
		nargv[0] = "man";
		nargv[1] = "help";
		nargv[2] = NULL;
		argv = nargv;
		argc = 2;
	}

	machine = sflag = NULL;
	f_cat = f_how = 0;
	conffile = p_add = NULL;
	p_path = (const char *)NULL;
	while ((ch = getopt(argc, argv, "aC:cfhkM:m:P:s:S:w-")) != -1)
		switch (ch) {
		case 'a':
			f_all = 1;
			break;
		case 'C':
			conffile = optarg;
			break;
		case 'c':
		case '-':		/* Deprecated. */
			f_cat = 1;
			break;
		case 'h':
			f_how = 1;
			break;
		case 'm':
			p_add = optarg;
			break;
		case 'M':
		case 'P':		/* Backward compatibility. */
			p_path = optarg;
			break;
		case 's':		/* SVR4 compatibility. */
			sflag = optarg;
			break;
		case 'S':
			machine = optarg;
			break;
		/*
		 * The -f and -k options are backward compatible
		 * ways of calling whatis(1) and apropos(1).
		 */
		case 'f':
			jump(argv, "-f", "whatis");
			/* NOTREACHED */
		case 'k':
			jump(argv, "-k", "apropos");
			/* NOTREACHED */
		case 'w':
			f_where = 1;
			break;
		case '?':
		default:
			usage();
		}
	argc -= optind;
	argv += optind;

	if (!*argv)
		usage();

	if (!f_cat && !f_how && !f_where) {
		if (!isatty(1))
			f_cat = 1;
		else {
			pager = getenv("MANPAGER");
			if (pager == NULL || *pager == '\0')
				pager = getenv("PAGER");
			if (pager == NULL || *pager == '\0')
				pager = _PATH_PAGER;
		}
	}

	/* Read the configuration file. */
	config(conffile);

	/*
	 * 1: If the user specified a section,
	 *    use the section list from the configuration file.
	 *    Otherwise, fall back to the default list or to an empty list.
	 */
	if (sflag && (section = getlist(sflag)) == NULL)
		errx(1, "unknown manual section `%s'", sflag);
	else if (argv[1] && (section = getlist(*argv)) != NULL)
		++argv;

	searchlist = section;
	if (searchlist == NULL)
		searchlist = getlist("_default");
	if (searchlist == NULL)
		searchlist = addlist("_default");

	/*
	 * 2: If the user set the -M option or defined the MANPATH variable,
	 *    clear what we have and take the user's list instead.
	 */
	if (p_path == NULL)
		p_path = getenv("MANPATH");

	if (p_path) {
		clearlist(searchlist);
		parse_path(searchlist, p_path);
	}

	/*
	 * 3: If the user set the -m option, insert the user's list before
	 *    whatever list we have.
	 */
	if (p_add)
		parse_path(searchlist, p_add);

	/*
	 * 4: Append the _subdir list where appropriate,
	 *    and always append the machine type.
	 */
	if (machine || (machine = getenv("MACHINE"))) {
		/* Avoid mangling argv/environment. */
		if ((machine = strdup(machine)) == NULL)
			err(1, NULL);
		for (p = machine; *p; ++p)
			*p = tolower(*p);
	} else
		machine = MACHINE;

	append_subdirs(searchlist, machine);

	/*
	 * 5: Search for the files.  Set up an interrupt handler, so the
	 *    temporary files go away.
	 */
	(void)signal(SIGINT, onsig);
	(void)signal(SIGHUP, onsig);

	sigemptyset(&blocksigs);
	sigaddset(&blocksigs, SIGINT);
	sigaddset(&blocksigs, SIGHUP);

	memset(&pg, 0, sizeof(pg));
	for (found = 0; *argv; ++argv)
		if (manual(*argv, searchlist, &pg))
			found = 1;

	/* 6: If nothing found, we're done. */
	if (!found) {
		(void)cleanup(0);
		exit (1);
	}

	/* 7: If it's simple, display it fast. */
	if (f_cat) {
		for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
			if (**ap == '\0')
				continue;
			cat(*ap);
		}
		exit (cleanup(0));
	}
	if (f_how) {
		for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
			if (**ap == '\0')
				continue;
			how(*ap);
		}
		exit(cleanup(0));
	}
	if (f_where) {
		for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
			if (**ap == '\0')
				continue;
			(void)puts(*ap);
		}
		exit(cleanup(0));
	}

	/*
	 * 8: We display things in a single command; build a list of things
	 *    to display.
	 */
	for (ap = pg.gl_pathv, len = strlen(pager) + 1; *ap != NULL; ++ap) {
		if (**ap == '\0')
			continue;
		len += strlen(*ap) + 1;
	}
	if ((cmd = malloc(len)) == NULL) {
		warn(NULL);
		(void)cleanup(0);
		exit(1);
	}
	p = cmd;
	len = strlen(pager);
	memcpy(p, pager, len);
	p += len;
	*p++ = ' ';
	for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
		if (**ap == '\0')
			continue;
		len = strlen(*ap);
		memcpy(p, *ap, len);
		p += len;
		*p++ = ' ';
	}
	*--p = '\0';

	/* Use system(3) in case someone's pager is "pager arg1 arg2". */
	(void)system(cmd);

	exit(cleanup(0));
}

/*
 * clearlist --
 *	Remove all entries from a list,
 * 	but leave the list header intact.
 */
static void
clearlist(TAG *t)
{
	ENTRY *e;

	while ((e = TAILQ_FIRST(&t->list)) != NULL) {
		free(e->s);
		TAILQ_REMOVE(&t->list, e, q);
		free(e);
	}
}

/*
 * parse_path --
 *	Split the -M or -m argument or the MANPATH variable at colons,
 *	and insert the parts into the searchlist.
 */
static void
parse_path(TAG *t, const char *path)
{
	ENTRY *eplast = NULL, *ep;
	char *p, *slashp, *path_copy;

	if ((path_copy = strdup(path)) == NULL)
		err(1, NULL);

	while ((p = strsep(&path_copy, ":")) != NULL) {
		/* Skip empty fields */
		if (*p == '\0')
			continue;

		if ((ep = malloc(sizeof(ENTRY))) == NULL)
			err(1, NULL);

		/* 
		 * Bring section specific paths to the same format as
		 * used in the configuration file, ending in /{cat,man}N.
		 */
		if (section) {
			slashp = p[strlen(p) - 1] == '/' ? "" : "/";
			(void)snprintf(gbuf, sizeof(gbuf),
			    "%s%s{cat,man}%s", p, slashp, t->s);
			if ((ep->s = strdup(gbuf)) == NULL)
				err(1, NULL);
		}

		/* Without a section, subdirs will be appended later. */
		else
			if ((ep->s = strdup(p)) == NULL)
				err(1, NULL);

		/*
		 * Even in case of -M, inserting in front is fine:
		 * We have just cleared the list.
		 */
		if (eplast)
			TAILQ_INSERT_AFTER(&t->list, eplast, ep, q);
		else
			TAILQ_INSERT_HEAD(&t->list, ep, q);
		eplast = ep;
	}

	free(path_copy);
}

/*
 * append_subdirs --
 *	Iterate the searchlist and append section and machine
 *	subdirectories as needed.
 */
static void
append_subdirs(TAG *t, const char *machine)
{
	TAG *tsub;
	ENTRY *eold, *elast, *enew, *esub;
	char *slashp;

	eold = elast = TAILQ_FIRST(&t->list);
	while (eold) {

		/*
		 * Section subdirectories *not* ending in a slash
		 * only get the machine suffix: They already had
		 * the {cat,man}N part in the configuration file
		 * or got it in parse_path().
		 */
		if (section && eold->s[strlen(eold->s)-1] != '/') {
			(void)snprintf(gbuf, sizeof(gbuf), "%s{/%s,}",
			    eold->s, machine);
			free(eold->s);
			if ((eold->s = strdup(gbuf)) == NULL)
				err(1, NULL);
			eold = elast = TAILQ_NEXT(eold, q);
			continue;
		}

		/*
		 * Without a section, expand each entry using the
		 * subdir list, then drop the original entry.
		 */
		esub = (tsub = getlist("_subdir")) == NULL ?
		    NULL : TAILQ_FIRST(&tsub->list);
		while (esub) {
			slashp = eold->s[strlen(eold->s)-1] == '/' ? "" : "/";
			(void)snprintf(gbuf, sizeof(gbuf), "%s%s%s{/%s,}",
			    eold->s, slashp, esub->s, machine);
			if ((enew = malloc(sizeof(ENTRY))) == NULL ||
			    (enew->s = strdup(gbuf)) == NULL)
				err(1, NULL);
			TAILQ_INSERT_AFTER(&t->list, elast, enew, q);
			elast = enew;
			esub = TAILQ_NEXT(esub, q);
		}
		elast = TAILQ_NEXT(elast, q);
		TAILQ_REMOVE(&t->list, eold, q);
		eold = elast;
	}
}

/*
 * manual --
 *	Search the manuals for the pages.
 */
static int
manual(const char *page, TAG *tag, glob_t *pg)
{
	ENTRY *ep, *e_sufp, *e_tag;
	TAG *missp, *sufp;
	int anyfound, cnt, found;
	char *p, buf[MAXPATHLEN];

	anyfound = 0;
	buf[0] = '*';

	/* Expand the search path. */
	if (f_all != f_where) {
		e_tag = tag == NULL ? NULL : TAILQ_FIRST(&tag->list);
		while (e_tag != NULL) {
			if (glob(e_tag->s, GLOB_BRACE | GLOB_NOSORT,
			    NULL, pg)) {
				/* No GLOB_NOMATCH here due to {arch,}. */
				warn("globbing directories");
				(void)cleanup(0);
				exit(1);
			}
			for (cnt = 0; cnt < pg->gl_pathc; cnt++) {
				if ((ep = malloc(sizeof(ENTRY))) == NULL ||
				    (ep->s = strdup(pg->gl_pathv[cnt])) ==
						NULL) {
					warn(NULL);
					(void)cleanup(0);
					exit(1);
				}
				TAILQ_INSERT_BEFORE(e_tag, ep, q);
			}
			ep = e_tag;
			e_tag = TAILQ_NEXT(e_tag, q);
			free(ep->s);
			TAILQ_REMOVE(&tag->list, ep, q);
			free(ep);
			globfree(pg);
			pg->gl_pathc = 0;
		}
	}

	/* For each element in the list... */
	e_tag = tag == NULL ? NULL : TAILQ_FIRST(&tag->list);
	for (; e_tag != NULL; e_tag = TAILQ_NEXT(e_tag, q)) {
		(void)snprintf(buf, sizeof(buf), "%s/%s.*", e_tag->s, page);
		switch (glob(buf, GLOB_APPEND | GLOB_BRACE | GLOB_NOSORT,
		    NULL, pg)) {
		case (0):
			break;
		case (GLOB_NOMATCH):
			continue;
		default:
			warn("globbing files");
			(void)cleanup(0);
			exit(1);
		}
		if (pg->gl_matchc == 0)
			continue;

		/* Find out if it's really a man page. */
		for (cnt = pg->gl_pathc - pg->gl_matchc;
		    cnt < pg->gl_pathc; ++cnt) {

			if (!f_all || !f_where) {
				check_companion(pg->gl_pathv + cnt, tag);
				if (*pg->gl_pathv[cnt] == '\0')
					continue;
			}

			/*
			 * Try the _suffix key words first.
			 *
			 * XXX
			 * Older versions of man.conf didn't have the suffix
			 * key words, it was assumed that everything was a .0.
			 * We just test for .0 first, it's fast and probably
			 * going to hit.
			 */
			(void)snprintf(buf, sizeof(buf), "*/%s.0", page);
			if (!fnmatch(buf, pg->gl_pathv[cnt], 0))
				goto next;

			e_sufp = (sufp = getlist("_suffix")) == NULL ?
			    NULL : TAILQ_FIRST(&sufp->list);
			for (found = 0;
			    e_sufp != NULL; e_sufp = TAILQ_NEXT(e_sufp, q)) {
				(void)snprintf(buf,
				     sizeof(buf), "*/%s%s", page, e_sufp->s);
				if (!fnmatch(buf, pg->gl_pathv[cnt], 0)) {
					found = 1;
					break;
				}
			}
			if (found)
				goto next;

			/* Try the _build key words next. */
			e_sufp = (sufp = getlist("_build")) == NULL ?
			    NULL : TAILQ_FIRST(&sufp->list);
			for (found = 0;
			    e_sufp != NULL; e_sufp = TAILQ_NEXT(e_sufp, q)) {
				for (p = e_sufp->s;
				    *p != '\0' && !isspace(*p); ++p);
				if (*p == '\0')
					continue;
				*p = '\0';
				(void)snprintf(buf,
				     sizeof(buf), "*/%s%s", page, e_sufp->s);
				if (!fnmatch(buf, pg->gl_pathv[cnt], 0)) {
					if (!f_where)
						build_page(p + 1,
						    &pg->gl_pathv[cnt]);
					*p = ' ';
					found = 1;
					break;
				}
				*p = ' ';
			}
			if (found) {
next:				anyfound = 1;
				if (!f_all && !f_where) {
					/* Delete any other matches. */
					while (++cnt< pg->gl_pathc)
						pg->gl_pathv[cnt] = "";
					break;
				}
				continue;
			}

			/* It's not a man page, forget about it. */
			pg->gl_pathv[cnt] = "";
		}

		if (anyfound && !f_all && !f_where)
			break;
	}

	/* If not found, enter onto the missing list. */
	if (!anyfound) {
		sigset_t osigs;

		sigprocmask(SIG_BLOCK, &blocksigs, &osigs);

		if ((missp = getlist("_missing")) == NULL)
			missp = addlist("_missing");
		if ((ep = malloc(sizeof(ENTRY))) == NULL ||
		    (ep->s = strdup(page)) == NULL) {
			warn(NULL);
			(void)cleanup(0);
			exit(1);
		}
		TAILQ_INSERT_TAIL(&missp->list, ep, q);
		sigprocmask(SIG_SETMASK, &osigs, NULL);
	}
	return (anyfound);
}

/*
 * check_companion --
 *	Check for a companion [un]formatted page.
 *	If one is found, skip this page.
 *	Use the companion instead, unless it will be found anyway.
 */
static void
check_companion(char **orig, TAG *tag) {
	struct stat sb_orig, sb_comp;
	char *p, *pext, comp[MAXPATHLEN];
	ENTRY *entry;
	size_t len;
	int found;

	len = strlcpy(comp, *orig, sizeof(comp));
	/* The minus 2 avoids a buffer overrun in case of a trailing dot. */
	p = comp + len - 2;

	/* Locate the file name extension. */
	while (p > comp && *p != '.' && *p != '/')
		p--;
	if (*p != '.')
		return;
	pext = p + 1;

	/* Search for slashes. */
	for (found = 0; 1; p--) {
		if (*p != '/')
			continue;

		/* Did not find /{cat,man}. */
		if (p == comp)
			return;

		/* Pass over one slash, the one before "page". */
		if (!found++) {
			len = p - comp;
			continue;
		}

		/* Rewrite manN/page.N <-> catN/page.0. */
		if (!strncmp(p+1, "man", 3)) {
			memcpy(++p, "cat", 3);
			*pext++ = '0';
			break;
		} else if (!strncmp(p+1, "cat", 3)) {
			memcpy(++p, "man", 3);
			p += 3;
			while (*p != '/' && pext < comp + sizeof(comp) - 1)
				*pext++ = *p++;
			break;

		/* Accept one architecture subdir, but not more. */
		} else if (found > 2)
			return;
	}
	*pext = '\0';

	/* Check whether both files exist. */
	if (stat(*orig, &sb_orig) || stat(comp, &sb_comp))
		return;

	/* No action if the companion file is older. */
	if (sb_orig.st_mtim.tv_sec  > sb_comp.st_mtim.tv_sec || (
	    sb_orig.st_mtim.tv_sec == sb_comp.st_mtim.tv_sec &&
	    sb_orig.st_mtim.tv_nsec > sb_comp.st_mtim.tv_nsec))
		return;

	/* Drop the companion if it is in the path, too. */
	if (f_all || f_where)
		for(entry = TAILQ_FIRST(&tag->list); entry != NULL;
		    entry = TAILQ_NEXT(entry, q))
			if (!strncmp(entry->s, comp, len)) {
				**orig = '\0';
				return;
			}

	/* The companion file is newer, use it. */
	free(*orig);
	if ((p = strdup(comp)) == NULL) {
		warn(NULL);
		(void)cleanup(0);
		exit(1);
	}
	*orig = p;
}

/*
 * build_page --
 *	Build a man page for display.
 */
static void
build_page(char *fmt, char **pathp)
{
	ENTRY *ep;
	TAG *intmpp;
	int fd, n;
	char *p, *b;
	char buf[MAXPATHLEN], cmd[MAXPATHLEN], tpath[MAXPATHLEN];
	sigset_t osigs;

       /*
        * Historically man chdir'd to the root of the man tree.
        * This was used in man pages that contained relative ".so"
        * directives (including other man pages for command aliases etc.)
        * It even went one step farther, by examining the first line
        * of the man page and parsing the .so filename so it would
        * make hard(?) links to the cat'ted man pages for space savings.
        * (We don't do that here, but we could).
        */

       /* copy and find the end */
       for (b = buf, p = *pathp; (*b++ = *p++) != '\0';)
               continue;

       /* skip the last two path components, page name and man[n] */
       for (--b, n = 2; b != buf; b--)
               if (*b == '/')
                       if (--n == 0) {
                               *b = '\0';
                               (void) chdir(buf);
                       }


	/* Add a remove-when-done list. */
	sigprocmask(SIG_BLOCK, &blocksigs, &osigs);
	if ((intmpp = getlist("_intmp")) == NULL)
		intmpp = addlist("_intmp");
	sigprocmask(SIG_SETMASK, &osigs, NULL);

	/* Move to the printf(3) format string. */
	for (; *fmt && isspace(*fmt); ++fmt)
		;

	/*
	 * Get a temporary file and build a version of the file
	 * to display.  Replace the old file name with the new one.
	 */
	(void)strlcpy(tpath, _PATH_TMPFILE, sizeof(tpath));
	if ((fd = mkstemp(tpath)) == -1) {
		warn("%s", tpath);
		(void)cleanup(0);
		exit(1);
	}
	(void)snprintf(buf, sizeof(buf), "%s > %s", fmt, tpath);
	(void)snprintf(cmd, sizeof(cmd), buf, *pathp);
	(void)system(cmd);
	(void)close(fd);
	if ((*pathp = strdup(tpath)) == NULL) {
		warn(NULL);
		(void)cleanup(0);
		exit(1);
	}

	/* Link the built file into the remove-when-done list. */
	if ((ep = malloc(sizeof(ENTRY))) == NULL) {
		warn(NULL);
		(void)cleanup(0);
		exit(1);
	}
	ep->s = *pathp;

	sigprocmask(SIG_BLOCK, &blocksigs, &osigs);
	TAILQ_INSERT_TAIL(&intmpp->list, ep, q);
	sigprocmask(SIG_SETMASK, &osigs, NULL);
}

/*
 * how --
 *	display how information
 */
static void
how(char *fname)
{
	FILE *fp;

	int lcnt, print;
	char *p, buf[256];

	if (!(fp = fopen(fname, "r"))) {
		warn("%s", fname);
		(void)cleanup(0);
		exit (1);
	}
#define	S1	"SYNOPSIS"
#define	S2	"S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS"
	for (lcnt = print = 0; fgets(buf, sizeof(buf), fp);) {
		if (!strncmp(buf, S1, sizeof(S1) - 1) ||
		    !strncmp(buf, S2, sizeof(S2) - 1)) {
			print = 1;
			continue;
		} else if (print) {
			char *p = buf;
			int allcaps = 0;

			while (*p) {
				if (!allcaps && isalpha(*p))
					allcaps = 1;
				if (isalpha(*p) && !isupper(*p)) {
					allcaps = 0;
					break;
				}
				p++;
			}
			if (allcaps) {
				(void)fclose(fp);
				return;
			}
		}
		if (!print)
			continue;
		if (*buf == '\n')
			++lcnt;
		else {
			while (lcnt) {
				--lcnt;
				(void)putchar('\n');
			}
			for (p = buf; isspace(*p); ++p)
				;
			(void)fputs(p, stdout);
		}
	}
	(void)fclose(fp);
}

/*
 * cat --
 *	cat out the file
 */
static void
cat(char *fname)
{
	int fd, n;
	char buf[2048];

	if ((fd = open(fname, O_RDONLY, 0)) < 0) {
		warn("%s", fname);
		(void)cleanup(0);
		exit(1);
	}
	while ((n = read(fd, buf, sizeof(buf))) > 0)
		if (write(STDOUT_FILENO, buf, n) != n) {
			warn("write");
			(void)cleanup(0);
			exit (1);
		}
	if (n == -1) {
		warn("read");
		(void)cleanup(0);
		exit(1);
	}
	(void)close(fd);
}

/*
 * jump --
 *	strip out flag argument and jump
 */
static void
jump(char **argv, char *flag, char *name)
{
	char **arg;

	argv[0] = name;
	for (arg = argv + 1; *arg; ++arg)
		if (!strcmp(*arg, flag))
			break;
	for (; *arg; ++arg)
		arg[0] = arg[1];
	execvp(name, argv);
	(void)fprintf(stderr, "%s: Command not found.\n", name);
	exit(1);
}

/*
 * onsig --
 *	If signaled, delete the temporary files.
 */
static void
onsig(int signo)
{
	(void)cleanup(1);

	(void)signal(signo, SIG_DFL);
	(void)kill(getpid(), signo);

	/* NOTREACHED */
	_exit(1);
}

/*
 * cleanup --
 *	Clean up temporary files, show any error messages.
 */
static int
cleanup(int insig)
{
	TAG *intmpp, *missp;
	ENTRY *ep;
	int rval = 0;

	if (insig == 0) {
		ep = (missp = getlist("_missing")) == NULL ?
		    NULL : TAILQ_FIRST(&missp->list);
		if (ep != NULL)
			for (; ep != NULL; ep = TAILQ_NEXT(ep, q)) {
				if (section)
					warnx("no entry for %s in "
					    "section %s of the manual.",
						ep->s, section->s);
				else
					warnx("no entry for %s in the manual.",
					    ep->s);
				rval = 1;
			}
	}

	ep = (intmpp = getlist("_intmp")) == NULL ?
	    NULL : TAILQ_FIRST(&intmpp->list);
	for (; ep != NULL; ep = TAILQ_NEXT(ep, q))
		(void)unlink(ep->s);
	return (rval);
}

/*
 * usage --
 *	print usage message and die
 */
static void
usage(void)
{
	(void)fprintf(stderr, "usage: %s [-achw] [-C file] [-M path] [-m path] "
	    "[-S subsection] [-s section]\n\t   [section] name ...\n",
	    __progname);
	(void)fprintf(stderr, "       %s -f command ...\n", __progname);
	(void)fprintf(stderr, "       %s -k keyword ...\n", __progname);
	exit(1);
}