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