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