summaryrefslogtreecommitdiff
path: root/usr.bin/man/man.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/man/man.c')
-rw-r--r--usr.bin/man/man.c1077
1 files changed, 1077 insertions, 0 deletions
diff --git a/usr.bin/man/man.c b/usr.bin/man/man.c
new file mode 100644
index 0000000..cba36bc
--- /dev/null
+++ b/usr.bin/man/man.c
@@ -0,0 +1,1077 @@
+/* $NetBSD: man.c,v 1.67 2018/06/15 20:16:35 mrg Exp $ */
+
+/*
+ * 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/cdefs.h>
+
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1987, 1993, 1994, 1995\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)man.c 8.17 (Berkeley) 1/31/95";
+#else
+__RCSID("$NetBSD: man.c,v 1.67 2018/06/15 20:16:35 mrg Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/utsname.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <glob.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+#include <locale.h>
+
+#include "manconf.h"
+#include "pathnames.h"
+
+#ifndef MAN_DEBUG
+#define MAN_DEBUG 0 /* debug path output */
+#endif
+
+/*
+ * manstate: structure collecting the current global state so we can
+ * easily identify it and pass it to helper functions in one arg.
+ */
+struct manstate {
+ /* command line flags */
+ int all; /* -a: show all matches rather than first */
+ int cat; /* -c: do not use a pager */
+ char *conffile; /* -C: use alternate config file */
+ int how; /* -h: show SYNOPSIS only */
+ char *manpath; /* -M: alternate MANPATH */
+ char *addpath; /* -m: add these dirs to front of manpath */
+ char *pathsearch; /* -S: path of man must contain this string */
+ char *sectionname; /* -s: limit search to a given man section */
+ int where; /* -w: just show paths of all matching files */
+ int getpath; /* -p: print the path of directories containing man pages */
+
+ /* important tags from the config file */
+ TAG *defaultpath; /* _default: default MANPATH */
+ TAG *subdirs; /* _subdir: default subdir search list */
+ TAG *suffixlist; /* _suffix: for files that can be cat()'d */
+ TAG *buildlist; /* _build: for files that must be built */
+
+ /* tags for internal use */
+ TAG *intmp; /* _intmp: tmp files we must cleanup */
+ TAG *missinglist; /* _missing: pages we couldn't find */
+ TAG *mymanpath; /* _new_path: final version of MANPATH */
+ TAG *section; /* <sec>: tag for m.sectionname */
+
+ /* other misc stuff */
+ const char *pager; /* pager to use */
+ size_t pagerlen; /* length of the above */
+ const char *machine; /* machine */
+ const char *machclass; /* machine class */
+};
+
+/*
+ * prototypes
+ */
+static void build_page(const char *, char **, struct manstate *);
+static void cat(const char *);
+static const char *check_pager(const char *);
+static int cleanup(void);
+static void how(const char *);
+static void jump(char **, const char *, const char *) __dead;
+static int manual(char *, struct manstate *, glob_t *);
+static void onsig(int) __dead;
+static void usage(void) __dead;
+static void addpath(struct manstate *, const char *, size_t, const char *);
+static const char *getclass(const char *);
+static void printmanpath(struct manstate *);
+
+/*
+ * main function
+ */
+int
+main(int argc, char **argv)
+{
+ static struct manstate m;
+ struct utsname utsname;
+ int ch, abs_section, found;
+ ENTRY *esubd, *epath;
+ char *p, **ap, *cmd;
+ size_t len;
+ glob_t pg;
+
+ setprogname(argv[0]);
+ setlocale(LC_ALL, "");
+ /*
+ * parse command line...
+ */
+ while ((ch = getopt(argc, argv, "-aC:cfhkM:m:P:ps:S:w")) != -1)
+ switch (ch) {
+ case 'a':
+ m.all = 1;
+ break;
+ case 'C':
+ m.conffile = optarg;
+ break;
+ case 'c':
+ case '-': /* XXX: '-' is a deprecated version of '-c' */
+ m.cat = 1;
+ break;
+ case 'h':
+ m.how = 1;
+ break;
+ case 'm':
+ m.addpath = optarg;
+ break;
+ case 'M':
+ case 'P': /* -P for backward compatibility */
+ if ((m.manpath = strdup(optarg)) == NULL)
+ err(EXIT_FAILURE, "malloc failed");
+ break;
+ case 'p':
+ m.getpath = 1;
+ break;
+ /*
+ * The -f and -k options are backward compatible,
+ * undocumented ways of calling whatis(1) and apropos(1).
+ */
+ case 'f':
+ jump(argv, "-f", "whatis");
+ /* NOTREACHED */
+ case 'k':
+ jump(argv, "-k", "apropos");
+ /* NOTREACHED */
+ case 's':
+ if (m.sectionname != NULL)
+ usage();
+ m.sectionname = optarg;
+ break;
+ case 'S':
+ m.pathsearch = optarg;
+ break;
+ case 'w':
+ m.all = m.where = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (!m.getpath && !argc)
+ usage();
+
+ /*
+ * read the configuration file and collect any other information
+ * we will need (machine type, pager, section [if specified
+ * without '-s'], and MANPATH through the environment).
+ */
+ config(m.conffile); /* exits on error ... */
+
+ if ((m.machine = getenv("MACHINE")) == NULL) {
+ if (uname(&utsname) == -1)
+ err(EXIT_FAILURE, "uname");
+ m.machine = utsname.machine;
+ }
+
+ m.machclass = getclass(m.machine);
+
+ if (!m.cat && !m.how && !m.where) { /* if we need a pager ... */
+ if (!isatty(STDOUT_FILENO)) {
+ m.cat = 1;
+ } else {
+ if ((m.pager = getenv("PAGER")) != NULL &&
+ m.pager[0] != '\0')
+ m.pager = check_pager(m.pager);
+ else
+ m.pager = _PATH_PAGER;
+ m.pagerlen = strlen(m.pager);
+ }
+ }
+
+ /* do we need to set m.section to a non-null value? */
+ if (m.sectionname) {
+
+ m.section = gettag(m.sectionname, 0); /* -s must be a section */
+ if (m.section == NULL)
+ errx(EXIT_FAILURE, "unknown section: %s", m.sectionname);
+
+ } else if (argc > 1) {
+
+ m.section = gettag(*argv, 0); /* might be a section? */
+ if (m.section) {
+ argv++;
+ argc--;
+ }
+
+ }
+
+ if (m.manpath == NULL)
+ m.manpath = getenv("MANPATH"); /* note: -M overrides getenv */
+
+
+ /*
+ * get default values from config file, plus create the tags we
+ * use for keeping internal state. make sure all our mallocs
+ * go through.
+ */
+ /* from cfg file */
+ m.defaultpath = gettag("_default", 1);
+ m.subdirs = gettag("_subdir", 1);
+ m.suffixlist = gettag("_suffix", 1);
+ m.buildlist = gettag("_build", 1);
+ /* internal use */
+ m.mymanpath = gettag("_new_path", 1);
+ m.missinglist = gettag("_missing", 1);
+ m.intmp = gettag("_intmp", 1);
+ if (!m.defaultpath || !m.subdirs || !m.suffixlist || !m.buildlist ||
+ !m.mymanpath || !m.missinglist || !m.intmp)
+ errx(EXIT_FAILURE, "malloc failed");
+
+ /*
+ * are we using a section whose elements are all absolute paths?
+ * (we only need to look at the first entry on the section list,
+ * as config() will ensure that any additional entries will match
+ * the first one.)
+ */
+ abs_section = (m.section != NULL &&
+ !TAILQ_EMPTY(&m.section->entrylist) &&
+ *(TAILQ_FIRST(&m.section->entrylist)->s) == '/');
+
+ /*
+ * now that we have all the data we need, we must determine the
+ * manpath we are going to use to find the requested entries using
+ * the following steps...
+ *
+ * [1] if the user specified a section and that section's elements
+ * from the config file are all absolute paths, then we override
+ * defaultpath and -M/MANPATH with the section's absolute paths.
+ */
+ if (abs_section) {
+ m.manpath = NULL; /* ignore -M/MANPATH */
+ m.defaultpath = m.section; /* overwrite _default path */
+ m.section = NULL; /* promoted to defaultpath */
+ }
+
+ /*
+ * [2] section can now only be non-null if the user asked for
+ * a section and that section's elements did not have
+ * absolute paths. in this case we use the section's
+ * elements to override _subdir from the config file.
+ *
+ * after this step, we are done processing "m.section"...
+ */
+ if (m.section)
+ m.subdirs = m.section;
+
+ /*
+ * [3] we need to setup the path we want to use (m.mymanpath).
+ * if the user gave us a path (m.manpath) use it, otherwise
+ * go with the default. in either case we need to append
+ * the subdir and machine spec to each element of the path.
+ *
+ * for absolute section paths that come from the config file,
+ * we only append the subdir spec if the path ends in
+ * a '/' --- elements that do not end in '/' are assumed to
+ * not have subdirectories. this is mainly for backward compat,
+ * but it allows non-subdir configs like:
+ * sect3 /usr/share/man/{old/,}cat3
+ * doc /usr/{pkg,share}/doc/{sendmail/op,sendmail/intro}
+ *
+ * note that we try and be careful to not put double slashes
+ * in the path (e.g. we want /usr/share/man/man1, not
+ * /usr/share/man//man1) because "more" will put the filename
+ * we generate in its prompt and the double slashes look ugly.
+ */
+ if (m.manpath) {
+
+ /* note: strtok is going to destroy m.manpath */
+ for (p = strtok(m.manpath, ":") ; p ; p = strtok(NULL, ":")) {
+ len = strlen(p);
+ if (len < 1)
+ continue;
+ TAILQ_FOREACH(esubd, &m.subdirs->entrylist, q)
+ addpath(&m, p, len, esubd->s);
+ }
+
+ } else {
+
+ TAILQ_FOREACH(epath, &m.defaultpath->entrylist, q) {
+ /* handle trailing "/" magic here ... */
+ if (abs_section && epath->s[epath->len - 1] != '/') {
+ addpath(&m, "", 1, epath->s);
+ continue;
+ }
+
+ TAILQ_FOREACH(esubd, &m.subdirs->entrylist, q)
+ addpath(&m, epath->s, epath->len, esubd->s);
+ }
+
+ }
+
+ /*
+ * [4] finally, prepend the "-m" m.addpath to mymanpath if it
+ * was specified. subdirs and machine are always applied to
+ * m.addpath.
+ */
+ if (m.addpath) {
+
+ /* note: strtok is going to destroy m.addpath */
+ for (p = strtok(m.addpath, ":") ; p ; p = strtok(NULL, ":")) {
+ len = strlen(p);
+ if (len < 1)
+ continue;
+ TAILQ_FOREACH(esubd, &m.subdirs->entrylist, q)
+ addpath(&m, p, len, esubd->s);
+ }
+
+ }
+
+ if (m.getpath)
+ printmanpath(&m);
+
+ /*
+ * now m.mymanpath is complete!
+ */
+#if MAN_DEBUG
+ printf("mymanpath:\n");
+ TAILQ_FOREACH(epath, &m.mymanpath->entrylist, q) {
+ printf("\t%s\n", epath->s);
+ }
+#endif
+
+ /*
+ * start searching for matching files and format them if necessary.
+ * setup an interrupt handler so that we can ensure that temporary
+ * files go away.
+ */
+ (void)signal(SIGINT, onsig);
+ (void)signal(SIGHUP, onsig);
+ (void)signal(SIGPIPE, onsig);
+
+ memset(&pg, 0, sizeof(pg));
+ for (found = 0; *argv; ++argv)
+ if (manual(*argv, &m, &pg)) {
+ found = 1;
+ }
+
+ /* if nothing found, we're done. */
+ if (!found) {
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+
+ /*
+ * handle the simple display cases first (m.cat, m.how, m.where)
+ */
+ if (m.cat) {
+ for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
+ if (**ap == '\0')
+ continue;
+ cat(*ap);
+ }
+ exit(cleanup());
+ }
+ if (m.how) {
+ for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
+ if (**ap == '\0')
+ continue;
+ how(*ap);
+ }
+ exit(cleanup());
+ }
+ if (m.where) {
+ for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
+ if (**ap == '\0')
+ continue;
+ (void)printf("%s\n", *ap);
+ }
+ exit(cleanup());
+ }
+
+ /*
+ * normal case - we display things in a single command, so
+ * build a list of things to display. first compute total
+ * length of buffer we will need so we can malloc it.
+ */
+ for (ap = pg.gl_pathv, len = m.pagerlen + 1; *ap != NULL; ++ap) {
+ if (**ap == '\0')
+ continue;
+ len += strlen(*ap) + 1;
+ }
+ if ((cmd = malloc(len)) == NULL) {
+ warn("malloc");
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+
+ /* now build the command string... */
+ p = cmd;
+ len = m.pagerlen;
+ memcpy(p, m.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());
+}
+
+static int
+manual_find_literalfile(struct manstate *mp, char **pv)
+{
+ ENTRY *suffix;
+ int found;
+ char buf[MAXPATHLEN];
+ const char *p;
+ int suflen;
+
+ found = 0;
+
+ /*
+ * Expand both '*' and suffix to force an actual
+ * match via fnmatch(3). Since the only match in pg
+ * is the literal file, the match is genuine.
+ */
+
+ TAILQ_FOREACH(suffix, &mp->buildlist->entrylist, q) {
+ for (p = suffix->s, suflen = 0;
+ *p != '\0' && !isspace((unsigned char)*p);
+ ++p)
+ ++suflen;
+ if (*p == '\0')
+ continue;
+
+ (void)snprintf(buf, sizeof(buf), "*%.*s", suflen, suffix->s);
+
+ if (!fnmatch(buf, *pv, 0)) {
+ if (!mp->where)
+ build_page(p + 1, pv, mp);
+ found = 1;
+ break;
+ }
+ }
+
+ return found;
+}
+
+static int
+manual_find_buildkeyword(const char *prefix, const char *escpage,
+ struct manstate *mp, char **pv)
+{
+ ENTRY *suffix;
+ int found;
+ char buf[MAXPATHLEN];
+ const char *p;
+ int suflen;
+
+ found = 0;
+ /* Try the _build keywords next. */
+ TAILQ_FOREACH(suffix, &mp->buildlist->entrylist, q) {
+ for (p = suffix->s, suflen = 0;
+ *p != '\0' && !isspace((unsigned char)*p);
+ ++p)
+ ++suflen;
+ if (*p == '\0')
+ continue;
+
+ (void)snprintf(buf, sizeof(buf), "%s%s%.*s",
+ prefix, escpage, suflen, suffix->s);
+ if (!fnmatch(buf, *pv, 0)) {
+ if (!mp->where)
+ build_page(p + 1, pv, mp);
+ found = 1;
+ break;
+ }
+ }
+
+ return found;
+}
+
+/*
+ * manual --
+ * Search the manuals for the pages.
+ */
+static int
+manual(char *page, struct manstate *mp, glob_t *pg)
+{
+ ENTRY *suffix, *mdir;
+ int anyfound, error, found;
+ size_t cnt;
+ char *p, buf[MAXPATHLEN], *escpage, *eptr;
+ static const char escglob[] = "\\~?*{}[]";
+
+ anyfound = 0;
+
+ /*
+ * Fixup page which may contain glob(3) special characters, e.g.
+ * the famous "No man page for [" FAQ.
+ */
+ if ((escpage = malloc((2 * strlen(page)) + 1)) == NULL) {
+ warn("malloc");
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+
+ p = page;
+ eptr = escpage;
+
+ while (*p) {
+ if (strchr(escglob, *p) != NULL) {
+ *eptr++ = '\\';
+ *eptr++ = *p++;
+ } else
+ *eptr++ = *p++;
+ }
+
+ *eptr = '\0';
+
+ /*
+ * If 'page' is given with an absolute path,
+ * or a relative path explicitly beginning with "./"
+ * or "../", then interpret it as a file specification.
+ */
+ if ((page[0] == '/')
+ || (page[0] == '.' && page[1] == '/')
+ || (page[0] == '.' && page[1] == '.' && page[2] == '/')
+ ) {
+ /* check if file actually exists */
+ (void)strlcpy(buf, escpage, sizeof(buf));
+ error = glob(buf, GLOB_APPEND | GLOB_BRACE | GLOB_NOSORT, NULL, pg);
+ if (error != 0) {
+ if (error == GLOB_NOMATCH) {
+ goto notfound;
+ } else {
+ errx(EXIT_FAILURE, "glob failed");
+ }
+ }
+
+ if (pg->gl_matchc == 0)
+ goto notfound;
+
+ /* literal file only yields one match */
+ cnt = pg->gl_pathc - pg->gl_matchc;
+
+ if (manual_find_literalfile(mp, &pg->gl_pathv[cnt])) {
+ anyfound = 1;
+ } else {
+ /* It's not a man page, forget about it. */
+ *pg->gl_pathv[cnt] = '\0';
+ }
+
+ notfound:
+ if (!anyfound) {
+ if (addentry(mp->missinglist, page, 0) < 0) {
+ warn("malloc");
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+ }
+ free(escpage);
+ return anyfound;
+ }
+
+ /* For each man directory in mymanpath ... */
+ TAILQ_FOREACH(mdir, &mp->mymanpath->entrylist, q) {
+
+ /*
+ * use glob(3) to look in the filesystem for matching files.
+ * match any suffix here, as we will check that later.
+ */
+ (void)snprintf(buf, sizeof(buf), "%s/%s.*", mdir->s, escpage);
+ if ((error = glob(buf,
+ GLOB_APPEND | GLOB_BRACE | GLOB_NOSORT, NULL, pg)) != 0) {
+ if (error == GLOB_NOMATCH)
+ continue;
+ else {
+ warn("globbing");
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+ }
+ if (pg->gl_matchc == 0)
+ continue;
+
+ /*
+ * start going through the matches glob(3) just found and
+ * use m.pathsearch (if present) to filter out pages we
+ * don't want. then verify the suffix is valid, and build
+ * the page if we have a _build suffix.
+ */
+ for (cnt = pg->gl_pathc - pg->gl_matchc;
+ cnt < pg->gl_pathc; ++cnt) {
+
+ /* filter on directory path name */
+ if (mp->pathsearch) {
+ p = strstr(pg->gl_pathv[cnt], mp->pathsearch);
+ if (!p || strchr(p, '/') == NULL) {
+ *pg->gl_pathv[cnt] = '\0'; /* zap! */
+ continue;
+ }
+ }
+
+ /*
+ * Try the _suffix keywords first.
+ *
+ * XXX
+ * Older versions of man.conf didn't have the _suffix
+ * keywords, 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", escpage);
+ if (!fnmatch(buf, pg->gl_pathv[cnt], 0))
+ goto next;
+
+ found = 0;
+ TAILQ_FOREACH(suffix, &mp->suffixlist->entrylist, q) {
+ (void)snprintf(buf,
+ sizeof(buf), "*/%s%s", escpage,
+ suffix->s);
+ if (!fnmatch(buf, pg->gl_pathv[cnt], 0)) {
+ found = 1;
+ break;
+ }
+ }
+ if (found)
+ goto next;
+
+ /* Try the _build keywords next. */
+ found = manual_find_buildkeyword("*/", escpage,
+ mp, &pg->gl_pathv[cnt]);
+ if (found) {
+next: anyfound = 1;
+ if (!mp->all) {
+ /* Delete any other matches. */
+ while (++cnt< pg->gl_pathc)
+ *pg->gl_pathv[cnt] = '\0';
+ break;
+ }
+ continue;
+ }
+
+ /* It's not a man page, forget about it. */
+ *pg->gl_pathv[cnt] = '\0';
+ }
+
+ if (anyfound && !mp->all)
+ break;
+ }
+
+ /* If not found, enter onto the missing list. */
+ if (!anyfound) {
+ if (addentry(mp->missinglist, page, 0) < 0) {
+ warn("malloc");
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ free(escpage);
+ return anyfound;
+}
+
+/*
+ * A do-nothing counterpart to fmtcheck(3) that only supplies the
+ * __format_arg marker. Actual fmtcheck(3) call is done once in
+ * config().
+ */
+__always_inline __format_arg(2)
+static inline const char *
+fmtcheck_ok(const char *userfmt, const char *template)
+{
+ return userfmt;
+}
+
+/*
+ * build_page --
+ * Build a man page for display.
+ */
+static void
+build_page(const char *fmt, char **pathp, struct manstate *mp)
+{
+ static int warned;
+ int olddir, fd, n;
+ size_t tmpdirlen;
+ char *p, *b;
+ char buf[MAXPATHLEN], cmd[MAXPATHLEN], tpath[MAXPATHLEN];
+ const char *tmpdir;
+
+ /* Let the user know this may take awhile. */
+ if (!warned) {
+ warned = 1;
+ warnx("Formatting manual page...");
+ }
+
+ /*
+ * 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] ...
+ * (e.g. buf will be "/usr/share/man" and p will be "man1/man.1")
+ * we also save a pointer to our current directory so that we
+ * can fchdir() back to it. this allows relative MANDIR paths
+ * to work with multiple man pages... e.g. consider:
+ * cd /usr/share && man -M ./man cat ls
+ * when no "cat1" subdir files are present.
+ */
+ olddir = -1;
+ for (--b, --p, n = 2; b != buf; b--, p--)
+ if (*b == '/')
+ if (--n == 0) {
+ *b = '\0';
+ olddir = open(".", O_RDONLY);
+ (void) chdir(buf);
+ p++;
+ break;
+ }
+
+
+ /* advance fmt past the suffix spec to the printf format string */
+ for (; *fmt && isspace((unsigned char)*fmt); ++fmt)
+ continue;
+
+ /*
+ * Get a temporary file and build a version of the file
+ * to display. Replace the old file name with the new one.
+ */
+ if ((tmpdir = getenv("TMPDIR")) == NULL)
+ tmpdir = _PATH_TMP;
+ tmpdirlen = strlen(tmpdir);
+ (void)snprintf(tpath, sizeof (tpath), "%s%s%s", tmpdir,
+ (tmpdirlen > 0 && tmpdir[tmpdirlen-1] == '/') ? "" : "/", TMPFILE);
+ if ((fd = mkstemp(tpath)) == -1) {
+ warn("%s", tpath);
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+ (void)snprintf(buf, sizeof(buf), "%s > %s", fmt, tpath);
+ (void)snprintf(cmd, sizeof(cmd), fmtcheck_ok(buf, "%s"), p);
+ (void)system(cmd);
+ (void)close(fd);
+ if ((*pathp = strdup(tpath)) == NULL) {
+ warn("malloc");
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+
+ /* Link the built file into the remove-when-done list. */
+ if (addentry(mp->intmp, *pathp, 0) < 0) {
+ warn("malloc");
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+
+ /* restore old directory so relative manpaths still work */
+ if (olddir != -1) {
+ fchdir(olddir);
+ close(olddir);
+ }
+}
+
+/*
+ * how --
+ * display how information
+ */
+static void
+how(const char *fname)
+{
+ FILE *fp;
+
+ int lcnt, print;
+ char buf[256];
+ const char *p;
+
+ if (!(fp = fopen(fname, "r"))) {
+ warn("%s", fname);
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+#define S1 "SYNOPSIS"
+#define S2 "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS"
+#define D1 "DESCRIPTION"
+#define D2 "D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN"
+ 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 (!strncmp(buf, D1, sizeof(D1) - 1) ||
+ !strncmp(buf, D2, sizeof(D2) - 1)) {
+ if (fp)
+ (void)fclose(fp);
+ return;
+ }
+ if (!print)
+ continue;
+ if (*buf == '\n')
+ ++lcnt;
+ else {
+ for(; lcnt; --lcnt)
+ (void)putchar('\n');
+ for (p = buf; isspace((unsigned char)*p); ++p)
+ continue;
+ (void)fputs(p, stdout);
+ }
+ }
+ (void)fclose(fp);
+}
+
+/*
+ * cat --
+ * cat out the file
+ */
+static void
+cat(const char *fname)
+{
+ int fd;
+ ssize_t n;
+ char buf[2048];
+
+ if ((fd = open(fname, O_RDONLY, 0)) < 0) {
+ warn("%s", fname);
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+ while ((n = read(fd, buf, sizeof(buf))) > 0)
+ if (write(STDOUT_FILENO, buf, (size_t)n) != n) {
+ warn("write");
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+ if (n == -1) {
+ warn("read");
+ (void)cleanup();
+ exit(EXIT_FAILURE);
+ }
+ (void)close(fd);
+}
+
+/*
+ * check_pager --
+ * check the user supplied page information
+ */
+static const char *
+check_pager(const char *name)
+{
+ const char *p;
+
+ /*
+ * if the user uses "more", we make it "more -s"; watch out for
+ * PAGER = "mypager /usr/ucb/more"
+ */
+ for (p = name; *p && !isspace((unsigned char)*p); ++p)
+ continue;
+ for (; p > name && *p != '/'; --p);
+ if (p != name)
+ ++p;
+
+ /* make sure it's "more", not "morex" */
+ if (!strncmp(p, "more", 4) && (!p[4] || isspace((unsigned char)p[4]))){
+ char *newname;
+ (void)asprintf(&newname, "%s %s", p, "-s");
+ name = newname;
+ }
+
+ return name;
+}
+
+/*
+ * jump --
+ * strip out flag argument and jump
+ */
+static void
+jump(char **argv, const char *flag, const char *name)
+{
+ char **arg;
+
+ argv[0] = __UNCONST(name);
+ for (arg = argv + 1; *arg; ++arg)
+ if (!strcmp(*arg, flag))
+ break;
+ for (; *arg; ++arg)
+ arg[0] = arg[1];
+ execvp(name, argv);
+ err(EXIT_FAILURE, "Cannot execute `%s'", name);
+}
+
+/*
+ * onsig --
+ * If signaled, delete the temporary files.
+ */
+static void
+onsig(int signo)
+{
+
+ (void)cleanup();
+
+ (void)raise_default_signal(signo);
+
+ /* NOTREACHED */
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * cleanup --
+ * Clean up temporary files, show any error messages.
+ */
+static int
+cleanup(void)
+{
+ TAG *intmpp, *missp;
+ ENTRY *ep;
+ int rval;
+
+ rval = EXIT_SUCCESS;
+ /*
+ * note that _missing and _intmp were created by main(), so
+ * gettag() cannot return NULL here.
+ */
+ missp = gettag("_missing", 0); /* missing man pages */
+ intmpp = gettag("_intmp", 0); /* tmp files we need to unlink */
+
+ TAILQ_FOREACH(ep, &missp->entrylist, q) {
+ warnx("no entry for %s in the manual.", ep->s);
+ rval = EXIT_FAILURE;
+ }
+
+ TAILQ_FOREACH(ep, &intmpp->entrylist, q)
+ (void)unlink(ep->s);
+
+ return rval;
+}
+
+static const char *
+getclass(const char *machine)
+{
+ char buf[BUFSIZ];
+ TAG *t;
+ snprintf(buf, sizeof(buf), "_%s", machine);
+ t = gettag(buf, 0);
+ return t != NULL && !TAILQ_EMPTY(&t->entrylist) ?
+ TAILQ_FIRST(&t->entrylist)->s : NULL;
+}
+
+static void
+addpath(struct manstate *m, const char *dir, size_t len, const char *sub)
+{
+ char buf[2 * MAXPATHLEN + 1];
+ (void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,%s%s%s}",
+ dir, (dir[len - 1] == '/') ? "" : "/", sub, m->machine,
+ m->machclass ? "/" : "", m->machclass ? m->machclass : "",
+ m->machclass ? "," : "");
+ if (addentry(m->mymanpath, buf, 0) < 0)
+ errx(EXIT_FAILURE, "malloc failed");
+}
+
+/*
+ * usage --
+ * print usage message and die
+ */
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "Usage: %s [-acw|-h] [-C cfg] [-M path] "
+ "[-m path] [-S srch] [[-s] sect] name ...\n", getprogname());
+ (void)fprintf(stderr, "Usage: %s [-C file] -f command ...\n", getprogname());
+ (void)fprintf(stderr,
+ "Usage: %s [-C file] -k keyword ...\n",
+ getprogname());
+ (void)fprintf(stderr, "Usage: %s -p\n", getprogname());
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * printmanpath --
+ * Prints a list of directories containing man pages.
+ */
+static void
+printmanpath(struct manstate *m)
+{
+ ENTRY *epath;
+ char **ap;
+ glob_t pg;
+ struct stat sb;
+ TAG *path = m->mymanpath;
+
+ /* the tail queue is empty if no _default tag is defined in * man.conf */
+ if (TAILQ_EMPTY(&path->entrylist))
+ errx(EXIT_FAILURE, "Empty manpath");
+
+ TAILQ_FOREACH(epath, &path->entrylist, q) {
+ if (glob(epath->s, GLOB_BRACE | GLOB_NOSORT, NULL, &pg) != 0)
+ err(EXIT_FAILURE, "glob failed");
+
+ if (pg.gl_matchc == 0) {
+ globfree(&pg);
+ continue;
+ }
+
+ for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
+ /* Skip cat page directories */
+ if (strstr(*ap, "/cat") != NULL)
+ continue;
+ /* Skip non-directories. */
+ if (stat(*ap, &sb) == 0 && S_ISDIR(sb.st_mode))
+ printf("%s\n", *ap);
+ }
+ globfree(&pg);
+ }
+}