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