1 /* 2 * Copyright (c) 1987, 1993, 1994 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\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.16 (Berkeley) 04/16/94"; 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 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 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 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 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 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 * 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 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 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 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 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