man(1): import man(1) replacement
authorFranco Fichtner <franco@lastsummer.de>
Sat, 5 Oct 2013 01:04:20 +0000 (03:04 +0200)
committerFranco Fichtner <franco@lastsummer.de>
Sun, 6 Oct 2013 11:51:28 +0000 (13:51 +0200)
Vanilla sources; not yet hooked up to the build.

Taken-from: NetBSD

etc/man.conf [new file with mode: 0644]
usr.bin/man/Makefile [new file with mode: 0644]
usr.bin/man/man.1 [new file with mode: 0644]
usr.bin/man/man.c [new file with mode: 0644]
usr.bin/man/man.conf.5 [new file with mode: 0644]
usr.bin/man/manconf.c [new file with mode: 0644]
usr.bin/man/manconf.h [new file with mode: 0644]
usr.bin/man/pathnames.h [new file with mode: 0644]

diff --git a/etc/man.conf b/etc/man.conf
new file mode 100644 (file)
index 0000000..8c8ab92
--- /dev/null
@@ -0,0 +1,68 @@
+#      $NetBSD: man.conf,v 1.32 2013/07/18 13:10:50 christos Exp $
+
+# Sheer, raging paranoia...
+_version       BSD.2
+
+# The whatis/apropos database.
+_mandb /var/db/man.db
+
+# Subdirectories for paths ending in '/', IN SEARCH ORDER.
+_subdir                cat1 man1 cat8 man8 cat6 man6 cat2 man2 cat3 man3 cat4 man4 cat5 man5 cat7 man7 man3f cat9 man9 catn mann catl manl
+
+# Files typed by suffix and their commands.
+# Note the order, .Z must come after .[1-9].Z, or it will match first.
+_suffix                .0
+_build         .0.Z            /usr/bin/zcat %s
+_build         .0.bz2          /usr/bin/bzcat %s
+_build         .0.gz           /usr/bin/zcat %s
+_build         .0.xz           /usr/bin/xzcat %s
+_build         .[1-9ln]        /usr/bin/mandoc %s
+_build         .[1-9ln].Z      /usr/bin/zcat %s | /usr/bin/mandoc
+_build         .[1-9ln].bz2    /usr/bin/bzcat %s | /usr/bin/mandoc
+_build         .[1-9ln].gz     /usr/bin/zcat %s | /usr/bin/mandoc
+_build         .[1-9ln].xz     /usr/bin/xzcat %s | /usr/bin/mandoc
+_build         .tbl            /usr/bin/mandoc %s
+_build         .tbl.Z          /usr/bin/zcat %s | /usr/bin/mandoc
+_build         .tbl.bz2        /usr/bin/bzcat %s | /usr/bin/mandoc
+_build         .tbl.gz         /usr/bin/zcat %s | /usr/bin/mandoc
+_build         .tbl.xz         /usr/bin/xzcat %s | /usr/bin/mandoc
+
+_build         .me             /usr/bin/nroff -msafer -me %s 2>/dev/null | cat -s
+
+_crunch                .Z              /usr/bin/zcat > %s
+_crunch                .bz2            /usr/bin/bzcat > %s
+_crunch                .gz             /usr/bin/zcat > %s
+_crunch                .xz             /usr/bin/xzcat > %s
+
+# Sections and their directories.
+# All paths ending in '/' are the equivalent of entries specifying that
+# directory with all of the subdirectories listed for the keyword _subdir.
+
+# default
+_default       /usr/{share,X11R7,X11R6,pkg,local}/man/
+
+# Other sections that represent complete man subdirectories.
+X11R7          /usr/X11R7/man/
+X11R6          /usr/X11R6/man/
+packages       /usr/pkg/man/
+local          /usr/local/man/
+
+# Specific section/directory combinations.
+1              {cat,man}1
+2              {cat,man}2
+3              {cat,man}3
+3F             {cat,man}3f
+3f             {cat,man}3f
+4              {cat,man}4
+5              {cat,man}5
+6              {cat,man}6
+7              {cat,man}7
+8              {cat,man}8
+9              {cat,man}9
+l              {cat,man}l
+n              {cat,man}n
+
+# machine classes per machine
+_i386          x86
+_amd64         x86
+_sparc64       sparc
diff --git a/usr.bin/man/Makefile b/usr.bin/man/Makefile
new file mode 100644 (file)
index 0000000..8219957
--- /dev/null
@@ -0,0 +1,15 @@
+#      $NetBSD: Makefile,v 1.14 2013/07/19 04:17:02 uwe Exp $
+#      @(#)Makefile    8.1 (Berkeley) 6/6/93
+
+WARNS?=        6
+
+PROG=  man
+SRCS=  man.c manconf.c
+MAN=   man.1 man.conf.5
+
+COPTS.man.c += -Wno-format-nonliteral
+
+DPADD+=        ${LIBUTIL}
+LDADD+=        -lutil
+
+.include <bsd.prog.mk>
diff --git a/usr.bin/man/man.1 b/usr.bin/man/man.1
new file mode 100644 (file)
index 0000000..3433fed
--- /dev/null
@@ -0,0 +1,243 @@
+.\"    $NetBSD: man.1,v 1.26 2013/07/18 03:40:31 uwe Exp $
+.\"
+.\" Copyright (c) 1989, 1990, 1993
+.\"    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.
+.\"
+.\"     @(#)man.1      8.2 (Berkeley) 1/2/94
+.\"
+.Dd October 7, 2011
+.Dt MAN 1
+.Os
+.Sh NAME
+.Nm man
+.Nd display the on-line manual pages
+.Pq aka Dq Em man pages
+.Sh SYNOPSIS
+.Nm
+.Oo Fl acw Ns \&| Ns Fl h Oc
+.Op Fl C Ar file
+.Op Fl M Ar path
+.Op Fl m Ar path
+.Op Fl S Ar srch
+.Oo
+.Op Fl s
+.Ar section
+.Oc
+.Ar name Ar ...
+.Nm
+.Fl k
+.Op Fl C Ar file
+.Op Fl M Ar path
+.Op Fl m Ar path
+.Ar keyword Ar ...
+.Nm
+.Fl p
+.Sh DESCRIPTION
+The
+.Nm
+utility displays the manual pages named on the command line.
+Its options are as follows:
+.Bl -tag -width indent
+.It Fl a
+Display all of the man pages for a specified
+.Ar section
+and
+.Ar name
+combination.
+(Normally, only the first man page found is displayed.)
+.It Fl C
+Use the specified
+.Ar file
+instead of the default configuration file.
+This permits users to configure their own man environment.
+See
+.Xr man.conf 5
+for a description of the contents of this file.
+.It Fl c
+Copy the man page to the standard output instead of using
+.Xr more 1
+to paginate it.
+This is done by default if the standard output is not a terminal device.
+.It Fl h
+Display only the
+.Dq Tn SYNOPSIS
+lines of the requested man pages.
+For commands, this is typically the command line usage information.
+For library functions, this usually contains the required include
+files and function prototypes.
+.It Fl k
+Display the header lines for any man pages matching
+.Ar keyword Ns Pq s ,
+in the same manner as
+.Xr apropos 1 .
+.It Fl M
+Override the list of standard directories which
+.Nm
+searches for man pages.
+The supplied
+.Ar path
+must be a colon
+.Pq Dq \&:
+separated list of directories.
+This search path may also be set using the environment variable
+.Ev MANPATH .
+The subdirectories to be searched, and their search order,
+is specified by the
+.Dq _subdir
+line in the
+.Nm
+configuration file.
+.It Fl m
+Augment the list of standard directories which
+.Nm
+searches for man pages.
+The supplied
+.Ar path
+must be a colon
+.Pq Dq \&:
+separated list of directories.
+These directories will be searched before the standard directories or
+the directories specified using the
+.Fl M
+option or the
+.Ev MANPATH
+environment variable.
+The subdirectories to be searched, and their search order,
+is specified by the
+.Dq _subdir
+line in the
+.Nm
+configuration file.
+.It Fl p
+Print the search path for the manual pages.
+.It Fl s
+Restrict the directories that
+.Nm
+will search to the specified section.
+The
+.Nm
+configuration file (see
+.Xr man.conf 5 )
+specifies the possible
+.Ar section
+values that are currently available.
+.It Fl S
+Display only man pages that have the specified string in the directory
+part of their filenames.
+This allows the man page search process criteria to be
+narrowed without having to change the MANPATH or
+.Dq _default
+variables.
+.It Fl w
+List the pathnames of the man pages which
+.Nm
+would display for the specified
+.Ar section
+and
+.Ar name
+combination.
+.El
+.Pp
+If the
+.Ql Fl s
+option is not specified,
+there is more than one argument,
+the
+.Ql Fl k
+option is not used, and the first argument is a valid section, then that
+argument will be used as if specified by the
+.Ql Fl s
+option.
+.Pp
+If
+.Ar name
+is given with a full or relative path then
+.Nm
+interprets it as a file specification, so that you can do
+.Nm
+.Cm ./foo.5
+or even
+.Nm
+.Cm /cd/foo/bar.1.gz .
+.Sh ENVIRONMENT
+.Bl -tag -width MANPATHX
+.It Ev MACHINE
+As some man pages are intended only for specific architectures,
+.Nm
+searches any subdirectories,
+with the same name as the current architecture,
+in every directory which it searches.
+Machine specific areas are checked before general areas.
+The current machine type may be overridden by setting the environment
+variable
+.Ev MACHINE
+to the name of a specific architecture.
+.It Ev MANPATH
+The standard search path used by
+.Nm
+may be overridden by specifying a path in the
+.Ev MANPATH
+environment variable.
+The format of the path is a colon
+.Pq Dq \&:
+separated list of directories.
+The subdirectories to be searched as well as their search order
+is specified by the
+.Dq _subdir
+line in the
+.Nm
+configuration file.
+.It Ev PAGER
+The pagination command used for writing the output.
+If the
+.Ev PAGER
+environment variable is null or not set, the standard pagination program
+.Xr more 1
+will be used.
+.El
+.Sh FILES
+.Bl -hang -width /etc/man.conf -compact
+.It Pa /etc/man.conf
+default man configuration file.
+.El
+.Sh SEE ALSO
+.Xr apropos 1 ,
+.Xr whatis 1 ,
+.Xr whereis 1 ,
+.Xr man.conf 5 ,
+.Xr mdoc 7 ,
+.Xr mdoc.samples 7
+.Sh STANDARDS
+.Nm
+conforms to
+.St -xcu5 .
+.\"and is expected to conform to
+.\".St -p1003.2-?? .
+.Sh BUGS
+The on-line man pages are, by necessity, forgiving toward stupid
+display devices, causing a few man pages to be not as nicely formatted
+as their typeset counterparts.
diff --git a/usr.bin/man/man.c b/usr.bin/man/man.c
new file mode 100644 (file)
index 0000000..bc83a0b
--- /dev/null
@@ -0,0 +1,1050 @@
+/*     $NetBSD: man.c,v 1.56 2013/07/30 15:10:04 joerg 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.56 2013/07/30 15:10:04 joerg 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 <errno.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;
+       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 */
+                       m.manpath = strdup(optarg);
+                       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) {
+               struct utsname utsname;
+
+               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_buildkeyword(const char *prefix, const char *escpage,
+       struct manstate *mp, glob_t *pg, size_t cnt)
+{
+       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, pg->gl_pathv[cnt], 0)) {
+                       if (!mp->where)
+                               build_page(p + 1, &pg->gl_pathv[cnt], 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 a full or relative path
+        * then interpret it as a file specification.
+        */
+       if ((page[0] == '/') || (page[0] == '.')) {
+               /* 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;
+
+               /* clip suffix for the suffix check below */
+               p = strrchr(escpage, '.');
+               if (p && p[0] == '.' && isdigit((unsigned char)p[1]))
+                       p[0] = '\0';
+
+               found = 0;
+               for (cnt = pg->gl_pathc - pg->gl_matchc;
+                   cnt < pg->gl_pathc; ++cnt)
+               {
+                       found = manual_find_buildkeyword("", escpage,
+                               mp, pg, cnt);
+                       if (found) {
+                               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';
+               }
+
+  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, 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;
+}
+
+/* 
+ * 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), buf, 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 -k [-C cfg] [-M path] [-m path] 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 *esubd;
+       char *defaultpath = NULL; /* _default tag value from man.conf. */
+       char *buf; /* for storing temporary values */
+       char **ap;
+       glob_t pg;
+       struct stat sb;
+       TAG *path = m->defaultpath;
+       TAG *subdirs = m->subdirs;
+       
+       /* the tail queue is empty if no _default tag is defined in * man.conf */
+       if (TAILQ_EMPTY(&path->entrylist))
+               errx(EXIT_FAILURE, "Empty manpath");
+               
+       defaultpath = TAILQ_LAST(&path->entrylist, tqh)->s;
+       
+       if (glob(defaultpath, GLOB_BRACE | GLOB_NOSORT, NULL, &pg) != 0)
+               err(EXIT_FAILURE, "glob failed");
+
+       if (pg.gl_matchc == 0) {
+               warnx("Default path in %s doesn't exist", _PATH_MANCONF);
+               globfree(&pg);
+               return;
+       }
+
+       TAILQ_FOREACH(esubd, &subdirs->entrylist, q) {
+               /* Drop cat page directory, only sources are relevant. */
+               if (strncmp(esubd->s, "man", 3))
+                       continue;
+
+               for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
+                       if (asprintf(&buf, "%s%s", *ap, esubd->s) == -1) 
+                               err(EXIT_FAILURE, "memory allocation error");
+                       /* Skip non-directories. */
+                       if (stat(buf, &sb) == 0 && S_ISDIR(sb.st_mode))
+                               printf("%s\n", buf);
+
+                       free(buf);
+               }
+       }
+       globfree(&pg);
+}
diff --git a/usr.bin/man/man.conf.5 b/usr.bin/man/man.conf.5
new file mode 100644 (file)
index 0000000..4d5f803
--- /dev/null
@@ -0,0 +1,286 @@
+.\"    $NetBSD: man.conf.5,v 1.24 2013/06/28 10:13:18 wiz Exp $
+.\"
+.\" Copyright (c) 1989, 1991, 1993
+.\"    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.
+.\"
+.\"    @(#)man.conf.5  8.5 (Berkeley) 1/2/94
+.\"
+.Dd April 28, 2012
+.Dt MAN.CONF 5
+.Os
+.Sh NAME
+.Nm man.conf
+.Nd configuration file for manual pages
+.Sh DESCRIPTION
+The
+.Nm
+file contains the default configuration used by
+.Xr man 1 ,
+.Xr apropos 1 ,
+.Xr whatis 1 ,
+.Xr catman 8 ,
+and
+.Xr makewhatis 8
+to find manual pages and information about manual pages (e.g. the
+whatis database).
+.Pp
+Manual pages are located by searching an ordered set of directories
+called the
+.Dq man path
+for a file that matches the name of the requested page.
+Each directory in the search path usually has a set of subdirectories
+in it (though this is not required).
+When subdirectories are used, there are normally two subdirectories
+for each section of the manual.
+One subdirectory contains formatted copies of that section's manual
+pages that can be directly displayed to a terminal, while the other
+section subdirectory contains unformatted copies of the pages (see
+.Xr nroff 1
+and
+.Xr mdoc 7 ) .
+Formatted manual pages are normally named with a trailing
+.Dq \.0
+suffix.
+.Pp
+The
+.Nm
+file contains comment and configuration lines.
+Comment lines start with the
+.Dq #
+character.
+Blank lines are also treated as comment lines.
+Configuration lines consist of a configuration keyword followed by a
+configuration string.
+There are two types of configuration keywords: control keywords and
+section keywords.
+Control keywords must start with the
+.Dq _
+character.
+The following control keywords are currently defined:
+.Bl -tag -width XXmachineX
+.It _build
+Identifies the set of suffixes used for manual pages that must be
+formatted for display and the command that should be used to format
+them.
+Manual file names, regardless of their format, are expected to end in a
+.Dq \.*
+pattern, i.e. a
+.Dq \&\.
+followed by some suffix.
+The first field of a _build line contains a man page suffix specification.
+The suffix specification may contain the normal shell globbing characters
+(NOT including curly braces
+.Pq Dq {} ) .
+The rest of the _build line is a shell command line whose standard
+output is a formatted manual page that can be directly displayed to
+the user.
+There should be exactly one occurrence of the string
+.Dq %s
+in the shell command line, and it will
+be replaced by the name of the file which is being formatted.
+.It _crunch
+Used by
+.Xr catman 8
+to determine how to crunch formatted pages
+which originally were compressed man pages: The first field lists a suffix
+which indicates what kind of compression were used to compress the man page.
+The rest of the line must be a shell command line, used to compress the
+formatted pages.
+There should be exactly one occurrence of the string
+.Dq %s
+in the shell command line, and it will
+be replaced by the name of the output file.
+.It _default
+Contains the system-wide default man path used to search for man pages.
+.It _subdir
+Contains the list (in search order) of section subdirectories which will
+be searched in any man path directory named with a trailing slash
+.Pq Dq /
+character.
+This list is also used, even if there is no trailing slash character,
+when a path is specified to the
+.Xr man 1
+utility by the user, by the
+.Ev MANPATH
+environment variable, or by the
+.Fl M
+and
+.Fl m
+options.
+.It _suffix
+identifies the set of suffixes used for formatted man pages
+(the
+.Dq \.0
+suffix is normally used here).
+Formatted man pages can be directly displayed to the user.
+Each suffix may contain the normal shell globbing characters (NOT
+including curly braces
+.Pq Dq {} ) .
+.It _version
+Contains the version of the configuration file.
+.It _whatdb
+Defines the full pathname (not just a directory path) for a database to
+be used
+by the
+.Xr apropos 1
+and
+.Xr whatis 1
+commands.
+The pathname may contain the normal shell globbing characters,
+including curly braces
+.Pq Dq {} ;
+to escape a shell globbing character,
+precede it with a backslash
+.Pq Dq \e .
+.It _ Ns Aq machine
+Defines additional paths to be searched for the particular
+.Dv machine
+whose literal value is taken from
+.Xr uname 1
+.Fl m .
+For example on an
+.Dv amd64 ,
+.Dv _amd64
+is used.
+.El
+.Pp
+Section configuration lines in
+.Nm
+consist of a section keyword naming the section and a configuration
+string that defines the directory or subdirectory path that the section's
+manual pages are located in.
+The path may contain the normal shell globbing characters,
+including curly braces
+.Pq Dq {} ;
+to escape a shell globbing character,
+precede it with a backslash
+.Pq Dq \e .
+Section keywords must not start with the
+.Dq _
+character.
+.Pp
+A section path may contain either a list of absolute directories or
+a list of or relative directories (but not both).
+Relative directory paths are treated as a list of subdirectories that
+are appended to the current man path directory being searched.
+Section configuration lines with absolute directory paths (starting with
+.Dq / )
+completely replace the current man search path directory with their
+content.
+.Pp
+Section configuration lines with absolute directory paths ending
+with a trailing slash character are expected to contain subdirectories
+of manual pages, (see the keyword
+.Dq _subdir
+above).
+The
+.Dq _subdir
+subdirectory list is not applied to absolute section directories
+if there is no trailing slash.
+.Pp
+In addition to the above rules, the
+.Xr man 1
+command also always checks in each directory that it searches for
+a subdirectory with the same name as the current machine type.
+If the machine-specific directory is found, it is also searched.
+This allows the manual to contain machine-specific man pages.
+Note that the machine subdirectory does not need to be specified
+in the
+.Nm
+file.
+.Pp
+Multiple specifications for all types of
+.Nm
+configuration lines are
+cumulative and the entries are used in the order listed in the file;
+multiple entries may be listed per line, as well.
+.Sh FILES
+.Bl -tag -width /etc/man.conf -compact
+.It Pa /etc/man.conf
+Standard manual configuration file.
+.El
+.Sh EXAMPLES
+Given the following
+.Nm
+file:
+.Bd -literal -offset indent
+_version       BSD.2
+_subdir                cat[123]
+_suffix                .0
+_build         .[1-9]  nroff -man %s
+_build         .tbl    tbl %s | nroff -man
+_i386          x86
+_default       /usr/share/man/
+sect3          /usr/share/man/{old/,}cat3
+.Ed
+.Pp
+By default, the command
+.Dq Li man mktemp
+will search for
+.Dq mktemp. Ns Aq any_digit
+and
+.Dq mktemp.tbl
+in the directories
+.Dq Pa /usr/share/man/cat1 ,
+.Dq Pa /usr/share/man/cat2 ,
+and
+.Dq Pa /usr/share/man/cat3 .
+If on a machine of type
+.Dq vax ,
+the subdirectory
+.Dq vax
+in each
+directory would be searched as well, before the directory was
+searched.
+.Pp
+If
+.Dq mktemp.tbl
+was found first, the command
+.Dq Li tbl Ao manual page Ac | nroff -man
+would be run to build a man page for display to the user.
+.Pp
+The command
+.Dq Li man sect3 mktemp
+would search the directories
+.Dq Pa /usr/share/man/old/cat3
+and
+.Dq Pa /usr/share/man/cat3 ,
+in that order, for
+the mktemp manual page.
+If a subdirectory with the same name as the current machine type
+existed in any of them, it would be searched as well, before each
+of them were searched.
+.Sh SEE ALSO
+.Xr apropos 1 ,
+.Xr machine 1 ,
+.Xr man 1 ,
+.Xr whatis 1 ,
+.Xr whereis 1 ,
+.Xr fnmatch 3 ,
+.Xr glob 3 ,
+.Xr catman 8 ,
+.Xr makewhatis 8
diff --git a/usr.bin/man/manconf.c b/usr.bin/man/manconf.c
new file mode 100644 (file)
index 0000000..2a386a6
--- /dev/null
@@ -0,0 +1,257 @@
+/*     $NetBSD: manconf.c,v 1.7 2013/07/18 15:39:08 christos Exp $     */
+
+/*
+ * Copyright (c) 1989, 1993, 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.
+ */
+
+/*
+ * manconf.c: provides interface for reading man.conf files
+ *
+ * note that this code is shared across all programs that read man.conf.
+ * (currently: apropos, catman, makewhatis, man, and whatis...)
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)config.c   8.8 (Berkeley) 1/31/95";
+#else
+__RCSID("$NetBSD: manconf.c,v 1.7 2013/07/18 15:39:08 christos Exp $");
+#endif
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "manconf.h"
+#include "pathnames.h"
+
+TAILQ_HEAD(_head, _tag);
+static struct _head head;      /* 'head' -- top level data structure */
+
+/*
+ * xstrdup: like strdup, but also returns length of string in lenp
+ */
+static char *
+xstrdup(const char *str, size_t *lenp)
+{
+       size_t len;
+       char *copy;
+
+       len = strlen(str) + 1;
+       copy = malloc(len);
+       if (!copy)
+               return NULL;
+       (void)memcpy(copy, str, len);
+       if (lenp)
+               *lenp = len - 1;        /* subtract out the null */
+       return copy;
+}
+
+/*
+ * config --
+ *
+ * Read the configuration file and build a doubly linked
+ * list off of "head" that looks like:
+ *
+ *     tag1 <-> entry <-> entry <-> entry
+ *     |
+ *     tag2 <-> entry <-> entry <-> entry
+ *
+ * note: will err/errx out on error (fopen or malloc failure)
+ */
+void
+config(const char *fname)
+{
+       TAG *tp;
+       FILE *cfp;
+       size_t len;
+       int lcnt;
+       char *p, *t, type;
+
+       if (fname == NULL)
+               fname = _PATH_MANCONF;
+       if ((cfp = fopen(fname, "r")) == NULL)
+               err(EXIT_FAILURE, "%s", fname);
+       TAILQ_INIT(&head);
+       for (lcnt = 1; (p = fgetln(cfp, &len)) != NULL; ++lcnt) {
+               if (len == 1)                   /* Skip empty lines. */
+                       continue;
+               if (p[len - 1] != '\n') {       /* Skip corrupted lines. */
+                       warnx("%s: line %d corrupted", fname, lcnt);
+                       continue;
+               }
+               p[len - 1] = '\0';              /* Terminate the line. */
+
+                                               /* Skip leading space. */
+               for (/*EMPTY*/; *p != '\0' && isspace((unsigned char)*p); ++p)
+                       continue;
+                                               /* Skip empty/comment lines. */
+               if (*p == '\0' || *p == '#')
+                       continue;
+                                               /* Find first token. */
+               for (t = p; *t && !isspace((unsigned char)*t); ++t)
+                       continue;
+               if (*t == '\0')                 /* Need more than one token.*/
+                       continue;
+               *t = '\0';
+
+               tp = gettag(p, 1);
+               if (!tp)
+                       errx(EXIT_FAILURE, "gettag: malloc failed");
+
+               /*
+                * Attach new records. Check to see if it is a
+                * section record or not.
+                */
+
+               if (*p == '_') {                /* not a section record */
+                       /*
+                        * Special cases: _build and _crunch take the
+                        * rest of the line as a single entry.
+                        */
+                       if (!strcmp(p, "_build") || !strcmp(p, "_crunch")) {
+                               /*
+                                * The reason we're not just using
+                                * strtok(3) for all of the parsing is
+                                * so we don't get caught if a line
+                                * has only a single token on it.
+                                */
+                               while (*++t && isspace((unsigned char)*t));
+                               if (addentry(tp, t, 0) == -1)
+                                       errx(EXIT_FAILURE,
+                                           "addentry: malloc failed");
+                       } else {
+                               for (++t; (p = strtok(t, " \t\n")) != NULL;
+                                    t = NULL) {
+                                       if (addentry(tp, p, 0) == -1)
+                                               errx(EXIT_FAILURE,
+                                                  "addentry: malloc failed");
+                               }
+                       }
+
+               } else {                        /* section record */
+
+                       /*
+                        * section entries can either be all absolute
+                        * paths or all relative paths, but not both.
+                        */
+                       type = (char)((TAILQ_FIRST(&tp->entrylist) != NULL) ?
+                           *(TAILQ_FIRST(&tp->entrylist)->s) : '\0');
+
+                       for (++t; (p = strtok(t, " \t\n")) != NULL; t = NULL) {
+
+                               /* ensure an assigned type */
+                               if (type == 0)
+                                       type = *p;
+
+                               /* check for illegal mix */
+                               if (*p != type) {
+       warnx("section %s: %s: invalid entry, does not match previous types",
+             tp->s, p);
+       warnx("man.conf cannot mix absolute and relative paths in an entry");
+                                       continue;
+                               }
+                               if (addentry(tp, p, 0) == -1)
+                                       errx(EXIT_FAILURE,
+                                           "addentry: malloc failed");
+                       }
+               }
+       }
+       (void)fclose(cfp);
+}
+
+/*
+ * gettag --
+ *     if (!create) return tag for given name if it exists, or NULL otherwise
+ *
+ *     if (create) return tag for given name if it exists, try and create
+ *     a new tag if it does not exist.  return NULL if unable to create new
+ *     tag.
+ */
+TAG *
+gettag(const char *name, int create)
+{
+       TAG *tp;
+
+       TAILQ_FOREACH(tp, &head, q)
+               if (!strcmp(name, tp->s))
+                       return tp;
+       if (!create)
+               return NULL;
+
+       /* try and add it in */
+       tp = malloc(sizeof(*tp));
+       if (tp)
+               tp->s = xstrdup(name, &tp->len);
+       if (!tp || !tp->s) {
+               if (tp)
+                       free(tp);
+               return NULL;
+       }
+       TAILQ_INIT(&tp->entrylist);
+       TAILQ_INSERT_TAIL(&head, tp, q);
+       return tp;
+}
+
+/*
+ * addentry --
+ *     add an entry to a list.
+ *     returns -1 if malloc failed, otherwise 0.
+ */
+int
+addentry(TAG *tp, const char *newent, int ishead)
+{
+       ENTRY *ep;
+
+       ep = malloc(sizeof(*ep));
+       if (ep)
+               ep->s = xstrdup(newent, &ep->len);
+       if (!ep || !ep->s) {
+               if (ep)
+                       free(ep);
+               return -1;
+       }
+       if (ishead)
+               TAILQ_INSERT_HEAD(&tp->entrylist, ep, q);
+       else
+               TAILQ_INSERT_TAIL(&tp->entrylist, ep, q);
+
+       return 0;
+}
diff --git a/usr.bin/man/manconf.h b/usr.bin/man/manconf.h
new file mode 100644 (file)
index 0000000..ab87728
--- /dev/null
@@ -0,0 +1,60 @@
+/*     $NetBSD: manconf.h,v 1.3 2006/04/10 14:39:06 chuck Exp $        */
+
+/*-
+ * Copyright (c) 1993
+ *     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.
+ *
+ *     @(#)config.h    8.4 (Berkeley) 12/18/93
+ */
+
+/*
+ * manconf.h: common data structures and APIs shared across all programs 
+ * that access man.conf (currently: apropos, catman, makewhatis, man, and
+ * whatis).
+ */
+
+/* TAG: top-level structure (one per section/reserved word) */
+typedef struct _tag {
+       TAILQ_ENTRY(_tag) q;                    /* Queue of tags */
+
+       TAILQ_HEAD(tqh, _entry) entrylist;      /* Queue of entries */
+       char *s;                                /* Associated string */
+       size_t len;                             /* Length of 's' */
+} TAG;
+
+/* ENTRY: each TAG has one or more ENTRY strings linked off of it */
+typedef struct _entry {
+       TAILQ_ENTRY(_entry) q;                  /* Queue of entries */
+
+       char *s;                                /* Associated string */
+       size_t len;                             /* Length of 's' */
+} ENTRY;
+
+int     addentry(TAG *, const char *, int);
+void    config(const char *);
+TAG    *gettag(const char *, int);
diff --git a/usr.bin/man/pathnames.h b/usr.bin/man/pathnames.h
new file mode 100644 (file)
index 0000000..95c2280
--- /dev/null
@@ -0,0 +1,39 @@
+/*     $NetBSD: pathnames.h,v 1.5 2003/08/07 11:15:11 agc Exp $        */
+
+/*
+ * Copyright (c) 1989, 1993
+ *     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.
+ *
+ *     @(#)pathnames.h 8.3 (Berkeley) 1/2/94
+ */
+
+#include <paths.h>
+
+#define        _PATH_MANCONF   "/etc/man.conf"
+#define        _PATH_PAGER     "/usr/bin/more -s"
+#define        _PATH_WHATIS    "whatis.db"
+#define        TMPFILE         "man.XXXXXX"