1 /*
2 * Copyright (c) 1987, 1993, 1994, 1995
3 * The Regents of the University of California. All rights reserved.
4 *
5 * %sccs.include.redist.c%
6 */
7
8 #ifndef lint
9 static char copyright[] =
10 "@(#) Copyright (c) 1987, 1993, 1994, 1995\n\
11 The Regents of the University of California. All rights reserved.\n";
12 #endif /* not lint */
13
14 #ifndef lint
15 static char sccsid[] = "@(#)man.c 8.17 (Berkeley) 01/31/95";
16 #endif /* not lint */
17
18 #include <sys/param.h>
19 #include <sys/queue.h>
20
21 #include <ctype.h>
22 #include <err.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <fnmatch.h>
26 #include <glob.h>
27 #include <signal.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32
33 #include "config.h"
34 #include "pathnames.h"
35
36 int f_all, f_where;
37
38 static void build_page __P((char *, char **));
39 static void cat __P((char *));
40 static char *check_pager __P((char *));
41 static int cleanup __P((void));
42 static void how __P((char *));
43 static void jump __P((char **, char *, char *));
44 static int manual __P((char *, TAG *, glob_t *));
45 static void onsig __P((int));
46 static void usage __P((void));
47
48 int
main(argc,argv)49 main(argc, argv)
50 int argc;
51 char *argv[];
52 {
53 extern char *optarg;
54 extern int optind;
55 TAG *defp, *defnewp, *section, *sectnewp, *subp;
56 ENTRY *e_defp, *e_sectp, *e_subp, *ep;
57 glob_t pg;
58 size_t len;
59 int ch, f_cat, f_how, found;
60 char **ap, *cmd, *machine, *p, *p_add, *p_path, *pager, *slashp;
61 char *conffile, buf[MAXPATHLEN * 2];
62
63 f_cat = f_how = 0;
64 conffile = p_add = p_path = NULL;
65 while ((ch = getopt(argc, argv, "-aC:cfhkM:m:P:w")) != EOF)
66 switch (ch) {
67 case 'a':
68 f_all = 1;
69 break;
70 case 'C':
71 conffile = optarg;
72 break;
73 case 'c':
74 case '-': /* Deprecated. */
75 f_cat = 1;
76 break;
77 case 'h':
78 f_how = 1;
79 break;
80 case 'm':
81 p_add = optarg;
82 break;
83 case 'M':
84 case 'P': /* Backward compatibility. */
85 p_path = optarg;
86 break;
87 /*
88 * The -f and -k options are backward compatible,
89 * undocumented ways of calling whatis(1) and apropos(1).
90 */
91 case 'f':
92 jump(argv, "-f", "whatis");
93 /* NOTREACHED */
94 case 'k':
95 jump(argv, "-k", "apropos");
96 /* NOTREACHED */
97 case 'w':
98 f_all = f_where = 1;
99 break;
100 case '?':
101 default:
102 usage();
103 }
104 argc -= optind;
105 argv += optind;
106
107 if (!*argv)
108 usage();
109
110 if (!f_cat && !f_how && !f_where)
111 if (!isatty(1))
112 f_cat = 1;
113 else if ((pager = getenv("PAGER")) != NULL)
114 pager = check_pager(pager);
115 else
116 pager = _PATH_PAGER;
117
118 /* Read the configuration file. */
119 config(conffile);
120
121 /* Get the machine type. */
122 if ((machine = getenv("MACHINE")) == NULL)
123 machine = MACHINE;
124
125 /* If there's no _default list, create an empty one. */
126 if ((defp = getlist("_default")) == NULL)
127 defp = addlist("_default");
128
129 /*
130 * 1: If the user specified a MANPATH variable, or set the -M
131 * option, we replace the _default list with the user's list,
132 * appending the entries in the _subdir list and the machine.
133 */
134 if (p_path == NULL)
135 p_path = getenv("MANPATH");
136 if (p_path != NULL) {
137 while ((e_defp = defp->list.tqh_first) != NULL) {
138 free(e_defp->s);
139 TAILQ_REMOVE(&defp->list, e_defp, q);
140 }
141 for (p = strtok(p_path, ":");
142 p != NULL; p = strtok(NULL, ":")) {
143 slashp = p[strlen(p) - 1] == '/' ? "" : "/";
144 e_subp = (subp = getlist("_subdir")) == NULL ?
145 NULL : subp->list.tqh_first;
146 for (; e_subp != NULL; e_subp = e_subp->q.tqe_next) {
147 (void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,}",
148 p, slashp, e_subp->s, machine);
149 if ((ep = malloc(sizeof(ENTRY))) == NULL ||
150 (ep->s = strdup(buf)) == NULL)
151 err(1, NULL);
152 TAILQ_INSERT_TAIL(&defp->list, ep, q);
153 }
154 }
155 }
156
157 /*
158 * 2: If the user did not specify MANPATH, -M or a section, rewrite
159 * the _default list to include the _subdir list and the machine.
160 */
161 if (argv[1] == NULL)
162 section = NULL;
163 else if ((section = getlist(*argv)) != NULL)
164 ++argv;
165 if (p_path == NULL && section == NULL) {
166 defnewp = addlist("_default_new");
167 e_defp =
168 defp->list.tqh_first == NULL ? NULL : defp->list.tqh_first;
169 for (; e_defp != NULL; e_defp = e_defp->q.tqe_next) {
170 slashp =
171 e_defp->s[strlen(e_defp->s) - 1] == '/' ? "" : "/";
172 e_subp = (subp = getlist("_subdir")) == NULL ?
173 NULL : subp->list.tqh_first;
174 for (; e_subp != NULL; e_subp = e_subp->q.tqe_next) {
175 (void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,}",
176 e_defp->s, slashp, e_subp->s, machine);
177 if ((ep = malloc(sizeof(ENTRY))) == NULL ||
178 (ep->s = strdup(buf)) == NULL)
179 err(1, NULL);
180 TAILQ_INSERT_TAIL(&defnewp->list, ep, q);
181 }
182 }
183 defp = getlist("_default");
184 while ((e_defp = defp->list.tqh_first) != NULL) {
185 free(e_defp->s);
186 TAILQ_REMOVE(&defp->list, e_defp, q);
187 }
188 free(defp->s);
189 TAILQ_REMOVE(&head, defp, q);
190 defnewp = getlist("_default_new");
191 free(defnewp->s);
192 defnewp->s = "_default";
193 defp = defnewp;
194 }
195
196 /*
197 * 3: If the user set the -m option, insert the user's list before
198 * whatever list we have, again appending the _subdir list and
199 * the machine.
200 */
201 if (p_add != NULL)
202 for (p = strtok(p_add, ":"); p != NULL; p = strtok(NULL, ":")) {
203 slashp = p[strlen(p) - 1] == '/' ? "" : "/";
204 e_subp = (subp = getlist("_subdir")) == NULL ?
205 NULL : subp->list.tqh_first;
206 for (; e_subp != NULL; e_subp = e_subp->q.tqe_next) {
207 (void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,}",
208 p, slashp, e_subp->s, machine);
209 if ((ep = malloc(sizeof(ENTRY))) == NULL ||
210 (ep->s = strdup(buf)) == NULL)
211 err(1, NULL);
212 TAILQ_INSERT_HEAD(&defp->list, ep, q);
213 }
214 }
215
216 /*
217 * 4: If none of MANPATH, -M, or -m were specified, and a section was,
218 * rewrite the section's paths (if they have a trailing slash) to
219 * append the _subdir list and the machine. This then becomes the
220 * _default list.
221 */
222 if (p_path == NULL && p_add == NULL && section != NULL) {
223 sectnewp = addlist("_section_new");
224 for (e_sectp = section->list.tqh_first;
225 e_sectp != NULL; e_sectp = e_sectp->q.tqe_next) {
226 if (e_sectp->s[strlen(e_sectp->s) - 1] != '/') {
227 (void)snprintf(buf, sizeof(buf),
228 "%s{/%s,}", e_sectp->s, machine);
229 if ((ep = malloc(sizeof(ENTRY))) == NULL ||
230 (ep->s = strdup(buf)) == NULL)
231 err(1, NULL);
232 TAILQ_INSERT_TAIL(§newp->list, ep, q);
233 continue;
234 }
235 e_subp = (subp = getlist("_subdir")) == NULL ?
236 NULL : subp->list.tqh_first;
237 for (; e_subp != NULL; e_subp = e_subp->q.tqe_next) {
238 (void)snprintf(buf, sizeof(buf), "%s%s{/%s,}",
239 e_sectp->s, e_subp->s, machine);
240 if ((ep = malloc(sizeof(ENTRY))) == NULL ||
241 (ep->s = strdup(buf)) == NULL)
242 err(1, NULL);
243 TAILQ_INSERT_TAIL(§newp->list, ep, q);
244 }
245 }
246 sectnewp->s = section->s;
247 defp = sectnewp;
248 TAILQ_REMOVE(&head, section, q);
249 }
250
251 /*
252 * 5: Search for the files. Set up an interrupt handler, so the
253 * temporary files go away.
254 */
255 (void)signal(SIGINT, onsig);
256 (void)signal(SIGHUP, onsig);
257
258 memset(&pg, 0, sizeof(pg));
259 for (found = 0; *argv; ++argv)
260 if (manual(*argv, defp, &pg))
261 found = 1;
262
263 /* 6: If nothing found, we're done. */
264 if (!found) {
265 (void)cleanup();
266 exit (1);
267 }
268
269 /* 7: If it's simple, display it fast. */
270 if (f_cat) {
271 for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
272 if (**ap == '\0')
273 continue;
274 cat(*ap);
275 }
276 exit (cleanup());
277 }
278 if (f_how) {
279 for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
280 if (**ap == '\0')
281 continue;
282 how(*ap);
283 }
284 exit(cleanup());
285 }
286 if (f_where) {
287 for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
288 if (**ap == '\0')
289 continue;
290 (void)printf("%s\n", *ap);
291 }
292 exit(cleanup());
293 }
294
295 /*
296 * 8: We display things in a single command; build a list of things
297 * to display.
298 */
299 for (ap = pg.gl_pathv, len = strlen(pager) + 1; *ap != NULL; ++ap) {
300 if (**ap == '\0')
301 continue;
302 len += strlen(*ap) + 1;
303 }
304 if ((cmd = malloc(len)) == NULL) {
305 warn(NULL);
306 (void)cleanup();
307 exit(1);
308 }
309 p = cmd;
310 len = strlen(pager);
311 memmove(p, pager, len);
312 p += len;
313 *p++ = ' ';
314 for (ap = pg.gl_pathv; *ap != NULL; ++ap) {
315 if (**ap == '\0')
316 continue;
317 len = strlen(*ap);
318 memmove(p, *ap, len);
319 p += len;
320 *p++ = ' ';
321 }
322 *p = '\0';
323
324 /* Use system(3) in case someone's pager is "pager arg1 arg2". */
325 (void)system(cmd);
326
327 exit(cleanup());
328 }
329
330 /*
331 * manual --
332 * Search the manuals for the pages.
333 */
334 static int
manual(page,tag,pg)335 manual(page, tag, pg)
336 char *page;
337 TAG *tag;
338 glob_t *pg;
339 {
340 ENTRY *ep, *e_sufp, *e_tag;
341 TAG *missp, *sufp;
342 int anyfound, cnt, found;
343 char *p, buf[128];
344
345 anyfound = 0;
346 buf[0] = '*';
347
348 /* For each element in the list... */
349 e_tag = tag == NULL ? NULL : tag->list.tqh_first;
350 for (; e_tag != NULL; e_tag = e_tag->q.tqe_next) {
351 (void)snprintf(buf, sizeof(buf), "%s/%s.*", e_tag->s, page);
352 if (glob(buf,
353 GLOB_APPEND | GLOB_BRACE | GLOB_NOSORT | GLOB_QUOTE,
354 NULL, pg)) {
355 warn("globbing");
356 (void)cleanup();
357 exit(1);
358 }
359 if (pg->gl_matchc == 0)
360 continue;
361
362 /* Find out if it's really a man page. */
363 for (cnt = pg->gl_pathc - pg->gl_matchc;
364 cnt < pg->gl_pathc; ++cnt) {
365
366 /*
367 * Try the _suffix key words first.
368 *
369 * XXX
370 * Older versions of man.conf didn't have the suffix
371 * key words, it was assumed that everything was a .0.
372 * We just test for .0 first, it's fast and probably
373 * going to hit.
374 */
375 (void)snprintf(buf, sizeof(buf), "*/%s.0", page);
376 if (!fnmatch(buf, pg->gl_pathv[cnt], 0))
377 goto next;
378
379 e_sufp = (sufp = getlist("_suffix")) == NULL ?
380 NULL : sufp->list.tqh_first;
381 for (found = 0;
382 e_sufp != NULL; e_sufp = e_sufp->q.tqe_next) {
383 (void)snprintf(buf,
384 sizeof(buf), "*/%s%s", page, e_sufp->s);
385 if (!fnmatch(buf, pg->gl_pathv[cnt], 0)) {
386 found = 1;
387 break;
388 }
389 }
390 if (found)
391 goto next;
392
393 /* Try the _build key words next. */
394 e_sufp = (sufp = getlist("_build")) == NULL ?
395 NULL : sufp->list.tqh_first;
396 for (found = 0;
397 e_sufp != NULL; e_sufp = e_sufp->q.tqe_next) {
398 for (p = e_sufp->s;
399 *p != '\0' && !isspace(*p); ++p);
400 if (*p == '\0')
401 continue;
402 *p = '\0';
403 (void)snprintf(buf,
404 sizeof(buf), "*/%s%s", page, e_sufp->s);
405 if (!fnmatch(buf, pg->gl_pathv[cnt], 0)) {
406 if (!f_where)
407 build_page(p + 1,
408 &pg->gl_pathv[cnt]);
409 *p = ' ';
410 found = 1;
411 break;
412 }
413 *p = ' ';
414 }
415 if (found) {
416 next: anyfound = 1;
417 if (!f_all) {
418 /* Delete any other matches. */
419 while (++cnt< pg->gl_pathc)
420 pg->gl_pathv[cnt] = "";
421 break;
422 }
423 continue;
424 }
425
426 /* It's not a man page, forget about it. */
427 pg->gl_pathv[cnt] = "";
428 }
429
430 if (anyfound && !f_all)
431 break;
432 }
433
434 /* If not found, enter onto the missing list. */
435 if (!anyfound) {
436 if ((missp = getlist("_missing")) == NULL)
437 missp = addlist("_missing");
438 if ((ep = malloc(sizeof(ENTRY))) == NULL ||
439 (ep->s = strdup(page)) == NULL) {
440 warn(NULL);
441 (void)cleanup();
442 exit(1);
443 }
444 TAILQ_INSERT_TAIL(&missp->list, ep, q);
445 }
446 return (anyfound);
447 }
448
449 /*
450 * build_page --
451 * Build a man page for display.
452 */
453 static void
build_page(fmt,pathp)454 build_page(fmt, pathp)
455 char *fmt, **pathp;
456 {
457 static int warned;
458 ENTRY *ep;
459 TAG *intmpp;
460 int fd;
461 char buf[MAXPATHLEN], cmd[MAXPATHLEN], tpath[sizeof(_PATH_TMP)];
462
463 /* Let the user know this may take awhile. */
464 if (!warned) {
465 warned = 1;
466 warnx("Formatting manual page...");
467 }
468
469 /* Add a remove-when-done list. */
470 if ((intmpp = getlist("_intmp")) == NULL)
471 intmpp = addlist("_intmp");
472
473 /* Move to the printf(3) format string. */
474 for (; *fmt && isspace(*fmt); ++fmt);
475
476 /*
477 * Get a temporary file and build a version of the file
478 * to display. Replace the old file name with the new one.
479 */
480 (void)strcpy(tpath, _PATH_TMP);
481 if ((fd = mkstemp(tpath)) == -1) {
482 warn("%s", tpath);
483 (void)cleanup();
484 exit(1);
485 }
486 (void)snprintf(buf, sizeof(buf), "%s > %s", fmt, tpath);
487 (void)snprintf(cmd, sizeof(cmd), buf, *pathp);
488 (void)system(cmd);
489 (void)close(fd);
490 if ((*pathp = strdup(tpath)) == NULL) {
491 warn(NULL);
492 (void)cleanup();
493 exit(1);
494 }
495
496 /* Link the built file into the remove-when-done list. */
497 if ((ep = malloc(sizeof(ENTRY))) == NULL) {
498 warn(NULL);
499 (void)cleanup();
500 exit(1);
501 }
502 ep->s = *pathp;
503 TAILQ_INSERT_TAIL(&intmpp->list, ep, q);
504 }
505
506 /*
507 * how --
508 * display how information
509 */
510 static void
how(fname)511 how(fname)
512 char *fname;
513 {
514 FILE *fp;
515
516 int lcnt, print;
517 char *p, buf[256];
518
519 if (!(fp = fopen(fname, "r"))) {
520 warn("%s", fname);
521 (void)cleanup();
522 exit (1);
523 }
524 #define S1 "SYNOPSIS"
525 #define S2 "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS"
526 #define D1 "DESCRIPTION"
527 #define D2 "D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN"
528 for (lcnt = print = 0; fgets(buf, sizeof(buf), fp);) {
529 if (!strncmp(buf, S1, sizeof(S1) - 1) ||
530 !strncmp(buf, S2, sizeof(S2) - 1)) {
531 print = 1;
532 continue;
533 } else if (!strncmp(buf, D1, sizeof(D1) - 1) ||
534 !strncmp(buf, D2, sizeof(D2) - 1))
535 return;
536 if (!print)
537 continue;
538 if (*buf == '\n')
539 ++lcnt;
540 else {
541 for(; lcnt; --lcnt)
542 (void)putchar('\n');
543 for (p = buf; isspace(*p); ++p);
544 (void)fputs(p, stdout);
545 }
546 }
547 (void)fclose(fp);
548 }
549
550 /*
551 * cat --
552 * cat out the file
553 */
554 static void
cat(fname)555 cat(fname)
556 char *fname;
557 {
558 int fd, n;
559 char buf[2048];
560
561 if ((fd = open(fname, O_RDONLY, 0)) < 0) {
562 warn("%s", fname);
563 (void)cleanup();
564 exit(1);
565 }
566 while ((n = read(fd, buf, sizeof(buf))) > 0)
567 if (write(STDOUT_FILENO, buf, n) != n) {
568 warn("write");
569 (void)cleanup();
570 exit (1);
571 }
572 if (n == -1) {
573 warn("read");
574 (void)cleanup();
575 exit(1);
576 }
577 (void)close(fd);
578 }
579
580 /*
581 * check_pager --
582 * check the user supplied page information
583 */
584 static char *
check_pager(name)585 check_pager(name)
586 char *name;
587 {
588 char *p, *save;
589
590 /*
591 * if the user uses "more", we make it "more -s"; watch out for
592 * PAGER = "mypager /usr/ucb/more"
593 */
594 for (p = name; *p && !isspace(*p); ++p);
595 for (; p > name && *p != '/'; --p);
596 if (p != name)
597 ++p;
598
599 /* make sure it's "more", not "morex" */
600 if (!strncmp(p, "more", 4) && (!p[4] || isspace(p[4]))){
601 save = name;
602 /* allocate space to add the "-s" */
603 if (!(name =
604 malloc((u_int)(strlen(save) + sizeof("-s") + 1))))
605 err(1, NULL);
606 (void)sprintf(name, "%s %s", save, "-s");
607 }
608 return(name);
609 }
610
611 /*
612 * jump --
613 * strip out flag argument and jump
614 */
615 static void
jump(argv,flag,name)616 jump(argv, flag, name)
617 char **argv, *flag, *name;
618 {
619 char **arg;
620
621 argv[0] = name;
622 for (arg = argv + 1; *arg; ++arg)
623 if (!strcmp(*arg, flag))
624 break;
625 for (; *arg; ++arg)
626 arg[0] = arg[1];
627 execvp(name, argv);
628 (void)fprintf(stderr, "%s: Command not found.\n", name);
629 exit(1);
630 }
631
632 /*
633 * onsig --
634 * If signaled, delete the temporary files.
635 */
636 static void
onsig(signo)637 onsig(signo)
638 int signo;
639 {
640 (void)cleanup();
641
642 (void)signal(signo, SIG_DFL);
643 (void)kill(getpid(), signo);
644
645 /* NOTREACHED */
646 exit (1);
647 }
648
649 /*
650 * cleanup --
651 * Clean up temporary files, show any error messages.
652 */
653 static int
cleanup()654 cleanup()
655 {
656 TAG *intmpp, *missp;
657 ENTRY *ep;
658 int rval;
659
660 rval = 0;
661 ep = (missp = getlist("_missing")) == NULL ?
662 NULL : missp->list.tqh_first;
663 if (ep != NULL)
664 for (; ep != NULL; ep = ep->q.tqe_next) {
665 warnx("no entry for %s in the manual.", ep->s);
666 rval = 1;
667 }
668
669 ep = (intmpp = getlist("_intmp")) == NULL ?
670 NULL : intmpp->list.tqh_first;
671 for (; ep != NULL; ep = ep->q.tqe_next)
672 (void)unlink(ep->s);
673 return (rval);
674 }
675
676 /*
677 * usage --
678 * print usage message and die
679 */
680 static void
usage()681 usage()
682 {
683 (void)fprintf(stderr,
684 "usage: man [-achw] [-C file] [-M path] [-m path] [section] title ...\n");
685 exit(1);
686 }
687