man(1): import man(1) replacement
[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 *) __dead;
118 static int       manual(char *, struct manstate *, glob_t *);
119 static void      onsig(int) __dead;
120 static void      usage(void) __dead;
121 static void      addpath(struct manstate *, const char *, size_t, const char *);
122 static const char *getclass(const char *);
123 static void printmanpath(struct manstate *);
124
125 /*
126  * main function
127  */
128 int
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' is given with a full or relative path
540          * then interpret it as a file specification.
541          */
542         if ((page[0] == '/') || (page[0] == '.')) {
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                 if (p && p[0] == '.' && isdigit((unsigned char)p[1]))
560                         p[0] = '\0';
561
562                 found = 0;
563                 for (cnt = pg->gl_pathc - pg->gl_matchc;
564                     cnt < pg->gl_pathc; ++cnt)
565                 {
566                         found = manual_find_buildkeyword("", escpage,
567                                 mp, pg, cnt);
568                         if (found) {
569                                 anyfound = 1;
570                                 if (!mp->all) {
571                                         /* Delete any other matches. */
572                                         while (++cnt< pg->gl_pathc)
573                                                 *pg->gl_pathv[cnt] = '\0';
574                                         break;
575                                 }
576                                 continue;
577                         }
578
579                         /* It's not a man page, forget about it. */
580                         *pg->gl_pathv[cnt] = '\0';
581                 }
582
583   notfound:
584                 if (!anyfound) {
585                         if (addentry(mp->missinglist, page, 0) < 0) {
586                                 warn("malloc");
587                                 (void)cleanup();
588                                 exit(EXIT_FAILURE);
589                         }
590                 }
591                 free(escpage);
592                 return anyfound;
593         }
594
595         /* For each man directory in mymanpath ... */
596         TAILQ_FOREACH(mdir, &mp->mymanpath->entrylist, q) {
597
598                 /* 
599                  * use glob(3) to look in the filesystem for matching files.
600                  * match any suffix here, as we will check that later.
601                  */
602                 (void)snprintf(buf, sizeof(buf), "%s/%s.*", mdir->s, escpage);
603                 if ((error = glob(buf,
604                     GLOB_APPEND | GLOB_BRACE | GLOB_NOSORT, NULL, pg)) != 0) {
605                         if (error == GLOB_NOMATCH)
606                                 continue;
607                         else {
608                                 warn("globbing");
609                                 (void)cleanup();
610                                 exit(EXIT_FAILURE);
611                         }
612                 }
613                 if (pg->gl_matchc == 0)
614                         continue;
615
616                 /*
617                  * start going through the matches glob(3) just found and 
618                  * use m.pathsearch (if present) to filter out pages we 
619                  * don't want.  then verify the suffix is valid, and build
620                  * the page if we have a _build suffix.
621                  */
622                 for (cnt = pg->gl_pathc - pg->gl_matchc;
623                     cnt < pg->gl_pathc; ++cnt) {
624
625                         /* filter on directory path name */
626                         if (mp->pathsearch) {
627                                 p = strstr(pg->gl_pathv[cnt], mp->pathsearch);
628                                 if (!p || strchr(p, '/') == NULL) {
629                                         *pg->gl_pathv[cnt] = '\0'; /* zap! */
630                                         continue;
631                                 }
632                         }
633
634                         /*
635                          * Try the _suffix keywords first.
636                          *
637                          * XXX
638                          * Older versions of man.conf didn't have the _suffix
639                          * keywords, it was assumed that everything was a .0.
640                          * We just test for .0 first, it's fast and probably
641                          * going to hit.
642                          */
643                         (void)snprintf(buf, sizeof(buf), "*/%s.0", escpage);
644                         if (!fnmatch(buf, pg->gl_pathv[cnt], 0))
645                                 goto next;
646
647                         found = 0;
648                         TAILQ_FOREACH(suffix, &mp->suffixlist->entrylist, q) {
649                                 (void)snprintf(buf,
650                                      sizeof(buf), "*/%s%s", escpage,
651                                      suffix->s);
652                                 if (!fnmatch(buf, pg->gl_pathv[cnt], 0)) {
653                                         found = 1;
654                                         break;
655                                 }
656                         }
657                         if (found)
658                                 goto next;
659
660                         /* Try the _build keywords next. */
661                         found = manual_find_buildkeyword("*/", escpage,
662                                 mp, pg, cnt);
663                         if (found) {
664 next:                           anyfound = 1;
665                                 if (!mp->all) {
666                                         /* Delete any other matches. */
667                                         while (++cnt< pg->gl_pathc)
668                                                 *pg->gl_pathv[cnt] = '\0';
669                                         break;
670                                 }
671                                 continue;
672                         }
673
674                         /* It's not a man page, forget about it. */
675                         *pg->gl_pathv[cnt] = '\0';
676                 }
677
678                 if (anyfound && !mp->all)
679                         break;
680         }
681
682         /* If not found, enter onto the missing list. */
683         if (!anyfound) {
684                 if (addentry(mp->missinglist, page, 0) < 0) {
685                         warn("malloc");
686                         (void)cleanup();
687                         exit(EXIT_FAILURE);
688                 }
689         }
690
691         free(escpage);
692         return anyfound;
693 }
694
695 /* 
696  * build_page --
697  *      Build a man page for display.
698  */
699 static void
700 build_page(const char *fmt, char **pathp, struct manstate *mp)
701 {
702         static int warned;
703         int olddir, fd, n;
704         size_t tmpdirlen;
705         char *p, *b;
706         char buf[MAXPATHLEN], cmd[MAXPATHLEN], tpath[MAXPATHLEN];
707         const char *tmpdir;
708
709         /* Let the user know this may take awhile. */
710         if (!warned) {
711                 warned = 1;
712                 warnx("Formatting manual page...");
713         }
714
715        /*
716         * Historically man chdir'd to the root of the man tree. 
717         * This was used in man pages that contained relative ".so"
718         * directives (including other man pages for command aliases etc.)
719         * It even went one step farther, by examining the first line
720         * of the man page and parsing the .so filename so it would
721         * make hard(?) links to the cat'ted man pages for space savings.
722         * (We don't do that here, but we could).
723         */
724  
725        /* copy and find the end */
726        for (b = buf, p = *pathp; (*b++ = *p++) != '\0';)
727                continue;
728  
729         /* 
730          * skip the last two path components, page name and man[n] ...
731          * (e.g. buf will be "/usr/share/man" and p will be "man1/man.1")
732          * we also save a pointer to our current directory so that we
733          * can fchdir() back to it.  this allows relative MANDIR paths
734          * to work with multiple man pages... e.g. consider:
735          *      cd /usr/share && man -M ./man cat ls
736          * when no "cat1" subdir files are present.
737          */
738         olddir = -1;
739         for (--b, --p, n = 2; b != buf; b--, p--)
740                 if (*b == '/')
741                         if (--n == 0) {
742                                 *b = '\0';
743                                 olddir = open(".", O_RDONLY);
744                                 (void) chdir(buf);
745                                 p++;
746                                 break;
747                         }
748
749
750         /* advance fmt past the suffix spec to the printf format string */
751         for (; *fmt && isspace((unsigned char)*fmt); ++fmt)
752                 continue;
753
754         /*
755          * Get a temporary file and build a version of the file
756          * to display.  Replace the old file name with the new one.
757          */
758         if ((tmpdir = getenv("TMPDIR")) == NULL)
759                 tmpdir = _PATH_TMP;
760         tmpdirlen = strlen(tmpdir);
761         (void)snprintf(tpath, sizeof (tpath), "%s%s%s", tmpdir, 
762             (tmpdirlen > 0 && tmpdir[tmpdirlen-1] == '/') ? "" : "/", TMPFILE);
763         if ((fd = mkstemp(tpath)) == -1) {
764                 warn("%s", tpath);
765                 (void)cleanup();
766                 exit(EXIT_FAILURE);
767         }
768         (void)snprintf(buf, sizeof(buf), "%s > %s", fmt, tpath);
769         (void)snprintf(cmd, sizeof(cmd), buf, p);
770         (void)system(cmd);
771         (void)close(fd);
772         if ((*pathp = strdup(tpath)) == NULL) {
773                 warn("malloc");
774                 (void)cleanup();
775                 exit(EXIT_FAILURE);
776         }
777
778         /* Link the built file into the remove-when-done list. */
779         if (addentry(mp->intmp, *pathp, 0) < 0) {
780                 warn("malloc");
781                 (void)cleanup();
782                 exit(EXIT_FAILURE);
783         }
784
785         /* restore old directory so relative manpaths still work */
786         if (olddir != -1) {
787                 fchdir(olddir);
788                 close(olddir);
789         }
790 }
791
792 /*
793  * how --
794  *      display how information
795  */
796 static void
797 how(const char *fname)
798 {
799         FILE *fp;
800
801         int lcnt, print;
802         char buf[256];
803         const char *p;
804
805         if (!(fp = fopen(fname, "r"))) {
806                 warn("%s", fname);
807                 (void)cleanup();
808                 exit(EXIT_FAILURE);
809         }
810 #define S1      "SYNOPSIS"
811 #define S2      "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS"
812 #define D1      "DESCRIPTION"
813 #define D2      "D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN"
814         for (lcnt = print = 0; fgets(buf, sizeof(buf), fp);) {
815                 if (!strncmp(buf, S1, sizeof(S1) - 1) ||
816                     !strncmp(buf, S2, sizeof(S2) - 1)) {
817                         print = 1;
818                         continue;
819                 } else if (!strncmp(buf, D1, sizeof(D1) - 1) ||
820                     !strncmp(buf, D2, sizeof(D2) - 1)) {
821                         if (fp)
822                                 (void)fclose(fp);
823                         return;
824                 }
825                 if (!print)
826                         continue;
827                 if (*buf == '\n')
828                         ++lcnt;
829                 else {
830                         for(; lcnt; --lcnt)
831                                 (void)putchar('\n');
832                         for (p = buf; isspace((unsigned char)*p); ++p)
833                                 continue;
834                         (void)fputs(p, stdout);
835                 }
836         }
837         (void)fclose(fp);
838 }
839
840 /*
841  * cat --
842  *      cat out the file
843  */
844 static void
845 cat(const char *fname)
846 {
847         int fd;
848         ssize_t n;
849         char buf[2048];
850
851         if ((fd = open(fname, O_RDONLY, 0)) < 0) {
852                 warn("%s", fname);
853                 (void)cleanup();
854                 exit(EXIT_FAILURE);
855         }
856         while ((n = read(fd, buf, sizeof(buf))) > 0)
857                 if (write(STDOUT_FILENO, buf, (size_t)n) != n) {
858                         warn("write");
859                         (void)cleanup();
860                         exit(EXIT_FAILURE);
861                 }
862         if (n == -1) {
863                 warn("read");
864                 (void)cleanup();
865                 exit(EXIT_FAILURE);
866         }
867         (void)close(fd);
868 }
869
870 /*
871  * check_pager --
872  *      check the user supplied page information
873  */
874 static const char *
875 check_pager(const char *name)
876 {
877         const char *p;
878
879         /*
880          * if the user uses "more", we make it "more -s"; watch out for
881          * PAGER = "mypager /usr/ucb/more"
882          */
883         for (p = name; *p && !isspace((unsigned char)*p); ++p)
884                 continue;
885         for (; p > name && *p != '/'; --p);
886         if (p != name)
887                 ++p;
888
889         /* make sure it's "more", not "morex" */
890         if (!strncmp(p, "more", 4) && (!p[4] || isspace((unsigned char)p[4]))){
891                 char *newname;
892                 (void)asprintf(&newname, "%s %s", p, "-s");
893                 name = newname;
894         }
895
896         return name;
897 }
898
899 /*
900  * jump --
901  *      strip out flag argument and jump
902  */
903 static void
904 jump(char **argv, const char *flag, const char *name)
905 {
906         char **arg;
907
908         argv[0] = __UNCONST(name);
909         for (arg = argv + 1; *arg; ++arg)
910                 if (!strcmp(*arg, flag))
911                         break;
912         for (; *arg; ++arg)
913                 arg[0] = arg[1];
914         execvp(name, argv);
915         err(EXIT_FAILURE, "Cannot execute `%s'", name);
916 }
917
918 /* 
919  * onsig --
920  *      If signaled, delete the temporary files.
921  */
922 static void
923 onsig(int signo)
924 {
925
926         (void)cleanup();
927
928         (void)raise_default_signal(signo);
929
930         /* NOTREACHED */
931         exit(EXIT_FAILURE);
932 }
933
934 /*
935  * cleanup --
936  *      Clean up temporary files, show any error messages.
937  */
938 static int
939 cleanup(void)
940 {
941         TAG *intmpp, *missp;
942         ENTRY *ep;
943         int rval;
944
945         rval = EXIT_SUCCESS;
946         /* 
947          * note that _missing and _intmp were created by main(), so
948          * gettag() cannot return NULL here.
949          */
950         missp = gettag("_missing", 0);  /* missing man pages */
951         intmpp = gettag("_intmp", 0);   /* tmp files we need to unlink */
952
953         TAILQ_FOREACH(ep, &missp->entrylist, q) {
954                 warnx("no entry for %s in the manual.", ep->s);
955                 rval = EXIT_FAILURE;
956         }
957
958         TAILQ_FOREACH(ep, &intmpp->entrylist, q)
959                 (void)unlink(ep->s);
960
961         return rval;
962 }
963
964 static const char *
965 getclass(const char *machine)
966 {
967         char buf[BUFSIZ];
968         TAG *t;
969         snprintf(buf, sizeof(buf), "_%s", machine);
970         t = gettag(buf, 0);
971         return t != NULL && !TAILQ_EMPTY(&t->entrylist) ?
972             TAILQ_FIRST(&t->entrylist)->s : NULL;
973 }
974
975 static void
976 addpath(struct manstate *m, const char *dir, size_t len, const char *sub)
977 {
978         char buf[2 * MAXPATHLEN + 1];
979         (void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,%s%s%s}",
980              dir, (dir[len - 1] == '/') ? "" : "/", sub, m->machine,
981              m->machclass ? "/" : "", m->machclass ? m->machclass : "",
982              m->machclass ? "," : "");
983         if (addentry(m->mymanpath, buf, 0) < 0)
984                 errx(EXIT_FAILURE, "malloc failed");
985 }
986
987 /*
988  * usage --
989  *      print usage message and die
990  */
991 static void
992 usage(void)
993 {
994         (void)fprintf(stderr, "Usage: %s [-acw|-h] [-C cfg] [-M path] "
995             "[-m path] [-S srch] [[-s] sect] name ...\n", getprogname());
996         (void)fprintf(stderr, 
997             "Usage: %s -k [-C cfg] [-M path] [-m path] keyword ...\n", 
998             getprogname());
999         (void)fprintf(stderr, "Usage: %s -p\n", getprogname());
1000         exit(EXIT_FAILURE);
1001 }
1002
1003 /*
1004  * printmanpath --
1005  *      Prints a list of directories containing man pages.
1006  */
1007 static void
1008 printmanpath(struct manstate *m)
1009 {
1010         ENTRY *esubd;
1011         char *defaultpath = NULL; /* _default tag value from man.conf. */
1012         char *buf; /* for storing temporary values */
1013         char **ap;
1014         glob_t pg;
1015         struct stat sb;
1016         TAG *path = m->defaultpath;
1017         TAG *subdirs = m->subdirs;
1018         
1019         /* the tail queue is empty if no _default tag is defined in * man.conf */
1020         if (TAILQ_EMPTY(&path->entrylist))
1021                 errx(EXIT_FAILURE, "Empty manpath");
1022                 
1023         defaultpath = TAILQ_LAST(&path->entrylist, tqh)->s;
1024         
1025         if (glob(defaultpath, GLOB_BRACE | GLOB_NOSORT, NULL, &pg) != 0)
1026                 err(EXIT_FAILURE, "glob failed");
1027
1028         if (pg.gl_matchc == 0) {
1029                 warnx("Default path in %s doesn't exist", _PATH_MANCONF);
1030                 globfree(&pg);
1031                 return;
1032         }
1033
1034         TAILQ_FOREACH(esubd, &subdirs->entrylist, q) {
1035                 /* Drop cat page directory, only sources are relevant. */
1036                 if (strncmp(esubd->s, "man", 3))
1037                         continue;
1038
1039                 for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
1040                         if (asprintf(&buf, "%s%s", *ap, esubd->s) == -1) 
1041                                 err(EXIT_FAILURE, "memory allocation error");
1042                         /* Skip non-directories. */
1043                         if (stat(buf, &sb) == 0 && S_ISDIR(sb.st_mode))
1044                                 printf("%s\n", buf);
1045
1046                         free(buf);
1047                 }
1048         }
1049         globfree(&pg);
1050 }