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