1 /* $NetBSD: makewhatis.c,v 1.46 2008/11/16 06:26:12 dholland Exp $ */ 2 3 /*- 4 * Copyright (c) 1999 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Matthias Scheler. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 #if HAVE_NBTOOL_CONFIG_H 33 #include "nbtool_config.h" 34 #endif 35 36 #include <sys/cdefs.h> 37 #if !defined(lint) 38 __COPYRIGHT("@(#) Copyright (c) 1999\ 39 The NetBSD Foundation, Inc. All rights reserved."); 40 __RCSID("$NetBSD: makewhatis.c,v 1.46 2008/11/16 06:26:12 dholland Exp $"); 41 #endif /* not lint */ 42 43 #include <sys/types.h> 44 #include <sys/param.h> 45 #include <sys/queue.h> 46 #include <sys/stat.h> 47 #include <sys/wait.h> 48 49 #include <ctype.h> 50 #include <err.h> 51 #include <errno.h> 52 #include <fcntl.h> 53 #include <fts.h> 54 #include <glob.h> 55 #include <locale.h> 56 #include <paths.h> 57 #include <signal.h> 58 #include <stdio.h> 59 #include <stdlib.h> 60 #include <string.h> 61 #include <unistd.h> 62 #include <zlib.h> 63 #include <util.h> 64 65 #include <man/manconf.h> 66 #include <man/pathnames.h> 67 68 #ifndef NROFF 69 #define NROFF "nroff" 70 #endif 71 72 typedef struct manpagestruct manpage; 73 struct manpagestruct { 74 manpage *mp_left, *mp_right; 75 ino_t mp_inode; 76 size_t mp_sdoff; 77 size_t mp_sdlen; 78 char mp_name[1]; 79 }; 80 81 typedef struct whatisstruct whatis; 82 struct whatisstruct { 83 whatis *wi_left, *wi_right; 84 char *wi_data; 85 char wi_prefix[1]; 86 }; 87 88 int main(int, char * const *); 89 static char *findwhitespace(char *); 90 static char *strmove(char *, char *); 91 static char *GetS(gzFile, char *, size_t); 92 static int pathnamesection(const char *, const char *); 93 static int manpagesection(char *); 94 static char *createsectionstring(char *); 95 static void addmanpage(manpage **, ino_t, char *, size_t, size_t); 96 static void addwhatis(whatis **, char *, char *); 97 static char *makesection(int); 98 static char *makewhatisline(const char *, const char *, const char *); 99 static void catpreprocess(char *); 100 static char *parsecatpage(const char *, gzFile *); 101 static int manpreprocess(char *); 102 static char *nroff(const char *, gzFile *); 103 static char *parsemanpage(const char *, gzFile *, int); 104 static char *getwhatisdata(char *); 105 static void processmanpages(manpage **, whatis **); 106 static void dumpwhatis(FILE *, whatis *); 107 static int makewhatis(char * const *manpath); 108 109 static char * const default_manpath[] = { 110 "/usr/share/man", 111 NULL 112 }; 113 114 static const char *sectionext = "0123456789ln"; 115 static const char *whatisdb = _PATH_WHATIS; 116 static const char *whatisdb_new = _PATH_WHATIS ".new"; 117 static int dowarn = 0; 118 119 #define ISALPHA(c) isalpha((unsigned char)(c)) 120 #define ISDIGIT(c) isdigit((unsigned char)(c)) 121 #define ISSPACE(c) isspace((unsigned char)(c)) 122 123 int 124 main(int argc, char *const *argv) 125 { 126 char * const *manpath; 127 int c, dofork; 128 const char *conffile; 129 ENTRY *ep; 130 TAG *tp; 131 int rv, jobs, status; 132 glob_t pg; 133 char *paths[2], **p, *sl; 134 int retval; 135 136 dofork = 1; 137 conffile = NULL; 138 jobs = 0; 139 retval = EXIT_SUCCESS; 140 141 (void)setlocale(LC_ALL, ""); 142 143 while ((c = getopt(argc, argv, "C:fw")) != -1) { 144 switch (c) { 145 case 'C': 146 conffile = optarg; 147 break; 148 case 'f': 149 /* run all processing on foreground */ 150 dofork = 0; 151 break; 152 case 'w': 153 dowarn++; 154 break; 155 default: 156 fprintf(stderr, "Usage: %s [-fw] [-C file] [manpath ...]\n", 157 getprogname()); 158 exit(EXIT_FAILURE); 159 } 160 } 161 argc -= optind; 162 argv += optind; 163 164 if (argc >= 1) { 165 manpath = &argv[0]; 166 167 mkwhatis: 168 return makewhatis(manpath); 169 } 170 171 /* 172 * Try read config file, fallback to default_manpath[] 173 * if man.conf not available. 174 */ 175 config(conffile); 176 if ((tp = gettag("_whatdb", 0)) == NULL) { 177 manpath = default_manpath; 178 goto mkwhatis; 179 } 180 181 /* Build individual databases */ 182 paths[1] = NULL; 183 TAILQ_FOREACH(ep, &tp->entrylist, q) { 184 if ((rv = glob(ep->s, 185 GLOB_BRACE | GLOB_NOSORT | GLOB_ERR | GLOB_NOCHECK, 186 NULL, &pg)) != 0) 187 err(EXIT_FAILURE, "glob('%s')", ep->s); 188 189 /* We always have something to work with here */ 190 for (p = pg.gl_pathv; *p; p++) { 191 sl = strrchr(*p, '/'); 192 if (sl == NULL) { 193 err(EXIT_FAILURE, "glob: _whatdb entry '%s' " 194 "doesn't contain slash", ep->s); 195 } 196 197 /* 198 * Cut the last component of path, leaving just 199 * the directory. We will use the result as root 200 * for manpage search. 201 * glob malloc()s space for the paths, so it's 202 * okay to change it in-place. 203 */ 204 *sl = '\0'; 205 paths[0] = *p; 206 207 if (!dofork) { 208 /* Do not fork child */ 209 makewhatis(paths); 210 continue; 211 } 212 213 switch (fork()) { 214 case 0: 215 exit(makewhatis(paths)); 216 break; 217 case -1: 218 warn("fork"); 219 makewhatis(paths); 220 break; 221 default: 222 jobs++; 223 break; 224 } 225 226 } 227 228 globfree(&pg); 229 } 230 231 /* Wait for the childern to finish */ 232 while (jobs > 0) { 233 (void)wait(&status); 234 if (!WIFEXITED(status) || WEXITSTATUS(status) != EXIT_SUCCESS) 235 retval = EXIT_FAILURE; 236 jobs--; 237 } 238 239 return retval; 240 } 241 242 static int 243 makewhatis(char * const * manpath) 244 { 245 FTS *fts; 246 FTSENT *fe; 247 manpage *source; 248 whatis *dest; 249 FILE *out; 250 size_t sdoff, sdlen; 251 int outfd; 252 struct stat st_before, st_after; 253 254 if ((fts = fts_open(manpath, FTS_LOGICAL, NULL)) == NULL) 255 err(EXIT_FAILURE, "Cannot open `%s'", *manpath); 256 257 source = NULL; 258 while ((fe = fts_read(fts)) != NULL) { 259 switch (fe->fts_info) { 260 case FTS_F: 261 if (manpagesection(fe->fts_path) >= 0) { 262 /* 263 * Get manpage subdirectory prefix. Most 264 * commonly, this is arch-specific subdirectory. 265 */ 266 if (fe->fts_level >= 3) { 267 int sl; 268 const char *s, *lsl; 269 270 lsl = NULL; 271 s = &fe->fts_path[fe->fts_pathlen - 1]; 272 for(sl = fe->fts_level - 1; sl > 0; 273 sl--) { 274 s--; 275 while (s[0] != '/') 276 s--; 277 if (lsl == NULL) 278 lsl = s; 279 } 280 281 /* 282 * Include trailing '/', so we get 283 * 'arch/'. 284 */ 285 sdoff = s + 1 - fe->fts_path; 286 sdlen = lsl - s + 1; 287 } else { 288 sdoff = 0; 289 sdlen = 0; 290 } 291 292 addmanpage(&source, fe->fts_statp->st_ino, 293 fe->fts_path, sdoff, sdlen); 294 } 295 /*FALLTHROUGH*/ 296 case FTS_D: 297 case FTS_DC: 298 case FTS_DEFAULT: 299 case FTS_DP: 300 case FTS_SL: 301 case FTS_DOT: 302 case FTS_W: 303 case FTS_NSOK: 304 case FTS_INIT: 305 break; 306 case FTS_SLNONE: 307 warnx("Symbolic link with no target: `%s'", 308 fe->fts_path); 309 break; 310 case FTS_DNR: 311 warnx("Unreadable directory: `%s'", fe->fts_path); 312 break; 313 case FTS_NS: 314 errno = fe->fts_errno; 315 warn("Cannot stat `%s'", fe->fts_path); 316 break; 317 case FTS_ERR: 318 errno = fe->fts_errno; 319 warn("Error reading `%s'", fe->fts_path); 320 break; 321 default: 322 errx(EXIT_FAILURE, "Unknown info %d returned from fts " 323 " for path: `%s'", fe->fts_info, fe->fts_path); 324 } 325 } 326 327 (void)fts_close(fts); 328 329 dest = NULL; 330 processmanpages(&source, &dest); 331 332 if (chdir(manpath[0]) == -1) 333 err(EXIT_FAILURE, "Cannot change dir to `%s'", manpath[0]); 334 335 /* 336 * makewhatis runs unattended, so it needs to be able to 337 * recover if the last run crashed out. Therefore, if 338 * whatisdb_new exists and is more than (arbitrarily) sixteen 339 * hours old, nuke it. If it exists but is not so old, refuse 340 * to run until it's cleaned up, in case another makewhatis is 341 * already running. Also, open the output with O_EXCL to make 342 * sure we get our own, in case two copies start exactly at 343 * once. (Unlikely? Maybe, maybe not, if two copies of cron 344 * end up running.) 345 * 346 * Similarly, before renaming the file after we finish writing 347 * to it, make sure it's still the same file we opened. This 348 * can't be completely race-free, but getting caught by it 349 * would require an unexplained sixteen-hour-or-more lag 350 * between the last mtime update when we wrote to it and when 351 * we get to the stat call *and* another makewhatis starting 352 * out to write at exactly the wrong moment. Not impossible, 353 * but not likely enough to worry about. 354 * 355 * This is maybe unnecessarily elaborate, but generating 356 * corrupted output isn't so good either. 357 */ 358 359 if (stat(whatisdb_new, &st_before) == 0) { 360 if (st_before.st_mtime - time(NULL) > 16*60*60) { 361 /* Don't complain if someone else just removed it. */ 362 if (unlink(whatisdb_new) == -1 && errno != ENOENT) { 363 err(EXIT_FAILURE, "Could not remove `%s'", 364 whatisdb_new); 365 } else { 366 warnx("Removed stale `%s'", whatisdb_new); 367 } 368 } else { 369 errx(EXIT_FAILURE, "The file `%s' already exists " 370 "-- am I already running?", whatisdb_new); 371 } 372 } else if (errno != ENOENT) { 373 /* Something unexpected happened. */ 374 err(EXIT_FAILURE, "Cannot stat `%s'", whatisdb_new); 375 } 376 377 outfd = open(whatisdb_new, O_WRONLY|O_CREAT|O_EXCL, 378 S_IRUSR|S_IRGRP|S_IROTH); 379 if (outfd < 0) 380 err(EXIT_FAILURE, "Cannot open `%s'", whatisdb_new); 381 382 if (fstat(outfd, &st_before) == -1) 383 err(EXIT_FAILURE, "Cannot fstat `%s'", whatisdb_new); 384 385 if ((out = fdopen(outfd, "w")) == NULL) 386 err(EXIT_FAILURE, "Cannot fdopen `%s'", whatisdb_new); 387 388 dumpwhatis(out, dest); 389 if (fchmod(fileno(out), S_IRUSR|S_IRGRP|S_IROTH) == -1) 390 err(EXIT_FAILURE, "Cannot chmod `%s'", whatisdb_new); 391 if (fclose(out) != 0) 392 err(EXIT_FAILURE, "Cannot close `%s'", whatisdb_new); 393 394 if (stat(whatisdb_new, &st_after) == -1) 395 err(EXIT_FAILURE, "Cannot stat `%s' (after writing)", 396 whatisdb_new); 397 398 if (st_before.st_dev != st_after.st_dev || 399 st_before.st_ino != st_after.st_ino) { 400 errx(EXIT_FAILURE, "The file `%s' changed under me; giving up", 401 whatisdb_new); 402 } 403 404 if (rename(whatisdb_new, whatisdb) == -1) 405 err(EXIT_FAILURE, "Could not rename `%s' to `%s'", 406 whatisdb_new, whatisdb); 407 408 return EXIT_SUCCESS; 409 } 410 411 static char * 412 findwhitespace(char *str) 413 { 414 while (!ISSPACE(*str)) 415 if (*str++ == '\0') { 416 str = NULL; 417 break; 418 } 419 420 return str; 421 } 422 423 static char * 424 strmove(char *dest, char *src) 425 { 426 return memmove(dest, src, strlen(src) + 1); 427 } 428 429 static char * 430 GetS(gzFile in, char *buffer, size_t length) 431 { 432 char *ptr; 433 434 if (((ptr = gzgets(in, buffer, (int)length)) != NULL) && (*ptr == '\0')) 435 ptr = NULL; 436 437 return ptr; 438 } 439 440 static char * 441 makesection(int s) 442 { 443 char sectionbuffer[24]; 444 if (s == -1) 445 return NULL; 446 (void)snprintf(sectionbuffer, sizeof(sectionbuffer), 447 " (%c) - ", sectionext[s]); 448 return estrdup(sectionbuffer); 449 } 450 451 static int 452 pathnamesection(const char *pat, const char *name) 453 { 454 char *ptr, *ext; 455 size_t len = strlen(pat); 456 457 458 while ((ptr = strstr(name, pat)) != NULL) { 459 if ((ext = strchr(sectionext, ptr[len])) != NULL) { 460 return ext - sectionext; 461 } 462 name = ptr + 1; 463 } 464 return -1; 465 } 466 467 468 static int 469 manpagesection(char *name) 470 { 471 char *ptr; 472 473 if ((ptr = strrchr(name, '/')) != NULL) 474 ptr++; 475 else 476 ptr = name; 477 478 while ((ptr = strchr(ptr, '.')) != NULL) { 479 int section; 480 481 ptr++; 482 section = 0; 483 while (sectionext[section] != '\0') 484 if (sectionext[section] == *ptr) 485 return section; 486 else 487 section++; 488 } 489 return -1; 490 } 491 492 static char * 493 createsectionstring(char *section_id) 494 { 495 char *section; 496 497 if (asprintf(§ion, " (%s) - ", section_id) < 0) 498 err(EXIT_FAILURE, "malloc failed"); 499 return section; 500 } 501 502 static void 503 addmanpage(manpage **tree, ino_t inode, char *name, size_t sdoff, size_t sdlen) 504 { 505 manpage *mp; 506 507 while ((mp = *tree) != NULL) { 508 if (mp->mp_inode == inode) 509 return; 510 tree = inode < mp->mp_inode ? &mp->mp_left : &mp->mp_right; 511 } 512 513 mp = emalloc(sizeof(manpage) + strlen(name)); 514 mp->mp_left = NULL; 515 mp->mp_right = NULL; 516 mp->mp_inode = inode; 517 mp->mp_sdoff = sdoff; 518 mp->mp_sdlen = sdlen; 519 (void)strcpy(mp->mp_name, name); 520 *tree = mp; 521 } 522 523 static void 524 addwhatis(whatis **tree, char *data, char *prefix) 525 { 526 whatis *wi; 527 int result; 528 529 while (ISSPACE(*data)) 530 data++; 531 532 if (*data == '/') { 533 char *ptr; 534 535 ptr = ++data; 536 while ((*ptr != '\0') && !ISSPACE(*ptr)) 537 if (*ptr++ == '/') 538 data = ptr; 539 } 540 541 while ((wi = *tree) != NULL) { 542 result = strcmp(data, wi->wi_data); 543 if (result == 0) return; 544 tree = result < 0 ? &wi->wi_left : &wi->wi_right; 545 } 546 547 wi = emalloc(sizeof(whatis) + strlen(prefix)); 548 549 wi->wi_left = NULL; 550 wi->wi_right = NULL; 551 wi->wi_data = data; 552 if (prefix[0] != '\0') 553 (void) strcpy(wi->wi_prefix, prefix); 554 else 555 wi->wi_prefix[0] = '\0'; 556 *tree = wi; 557 } 558 559 static void 560 catpreprocess(char *from) 561 { 562 char *to; 563 564 to = from; 565 while (ISSPACE(*from)) from++; 566 567 while (*from != '\0') 568 if (ISSPACE(*from)) { 569 while (ISSPACE(*++from)); 570 if (*from != '\0') 571 *to++ = ' '; 572 } 573 else if (*(from + 1) == '\b') 574 from += 2; 575 else 576 *to++ = *from++; 577 578 *to = '\0'; 579 } 580 581 static char * 582 makewhatisline(const char *file, const char *line, const char *section) 583 { 584 static const char *del[] = { 585 " - ", 586 " -- ", 587 "- ", 588 " -", 589 NULL 590 }; 591 size_t i, pos; 592 size_t llen, slen, dlen; 593 char *result, *ptr; 594 595 ptr = NULL; 596 if (section == NULL) { 597 if (dowarn) 598 warnx("%s: No section provided for `%s'", file, line); 599 return estrdup(line); 600 } 601 602 for (i = 0; del[i]; i++) 603 if ((ptr = strstr(line, del[i])) != NULL) 604 break; 605 606 if (del[i] == NULL) { 607 if (dowarn) 608 warnx("%s: Bad format line `%s'", file, line); 609 return estrdup(line); 610 } 611 612 slen = strlen(section); 613 llen = strlen(line); 614 dlen = strlen(del[i]); 615 616 result = emalloc(llen - dlen + slen + 1); 617 pos = ptr - line; 618 619 (void)memcpy(result, line, pos); 620 (void)memcpy(&result[pos], section, slen); 621 (void)strcpy(&result[pos + slen], &line[pos + dlen]); 622 return result; 623 } 624 625 static char * 626 parsecatpage(const char *name, gzFile *in) 627 { 628 char buffer[8192]; 629 char *section, *ptr, *last; 630 size_t size; 631 632 do { 633 if (GetS(in, buffer, sizeof(buffer)) == NULL) 634 return NULL; 635 } 636 while (buffer[0] == '\n'); 637 638 section = NULL; 639 if ((ptr = strchr(buffer, '(')) != NULL) { 640 if ((last = strchr(ptr + 1, ')')) !=NULL) { 641 size_t length; 642 643 length = last - ptr + 1; 644 section = emalloc(length + 5); 645 *section = ' '; 646 (void) memcpy(section + 1, ptr, length); 647 (void) strcpy(section + 1 + length, " - "); 648 } 649 } 650 651 for (;;) { 652 if (GetS(in, buffer, sizeof(buffer)) == NULL) { 653 free(section); 654 return NULL; 655 } 656 catpreprocess(buffer); 657 if (strncmp(buffer, "NAME", 4) == 0) 658 break; 659 } 660 if (section == NULL) 661 section = makesection(pathnamesection("/cat", name)); 662 663 ptr = last = buffer; 664 size = sizeof(buffer) - 1; 665 while ((size > 0) && (GetS(in, ptr, size) != NULL)) { 666 int length; 667 668 catpreprocess(ptr); 669 670 length = strlen(ptr); 671 if (length == 0) { 672 *last = '\0'; 673 674 ptr = makewhatisline(name, buffer, section); 675 free(section); 676 return ptr; 677 } 678 if ((length > 1) && (ptr[length - 1] == '-') && 679 ISALPHA(ptr[length - 2])) 680 last = &ptr[--length]; 681 else { 682 last = &ptr[length++]; 683 *last = ' '; 684 } 685 686 ptr += length; 687 size -= length; 688 } 689 690 free(section); 691 692 return NULL; 693 } 694 695 static int 696 manpreprocess(char *line) 697 { 698 char *from, *to; 699 700 to = from = line; 701 while (ISSPACE(*from)) 702 from++; 703 if (strncmp(from, ".\\\"", 3) == 0) 704 return 1; 705 706 while (*from != '\0') 707 if (ISSPACE(*from)) { 708 while (ISSPACE(*++from)); 709 if ((*from != '\0') && (*from != ',')) 710 *to++ = ' '; 711 } else if (*from == '\\') { 712 switch (*++from) { 713 case '\0': 714 case '-': 715 break; 716 case 'f': 717 case 's': 718 from++; 719 if ((*from=='+') || (*from=='-')) 720 from++; 721 while (ISDIGIT(*from)) 722 from++; 723 break; 724 default: 725 from++; 726 } 727 } else { 728 if (*from == '"') 729 from++; 730 else 731 *to++ = *from++; 732 } 733 734 *to = '\0'; 735 736 if (strncasecmp(line, ".Xr", 3) == 0) { 737 char *sect; 738 739 from = line + 3; 740 if (ISSPACE(*from)) 741 from++; 742 743 if ((sect = findwhitespace(from)) != NULL) { 744 size_t length; 745 char *trail; 746 747 *sect++ = '\0'; 748 if ((trail = findwhitespace(sect)) != NULL) 749 *trail++ = '\0'; 750 length = strlen(from); 751 (void) memmove(line, from, length); 752 line[length++] = '('; 753 to = &line[length]; 754 length = strlen(sect); 755 (void) memmove(to, sect, length); 756 if (trail == NULL) { 757 (void) strcpy(&to[length], ")"); 758 } else { 759 to += length; 760 *to++ = ')'; 761 length = strlen(trail); 762 (void) memmove(to, trail, length + 1); 763 } 764 } 765 } 766 767 return 0; 768 } 769 770 static char * 771 nroff(const char *inname, gzFile *in) 772 { 773 char tempname[MAXPATHLEN], buffer[65536], *data; 774 int tempfd, bytes, pipefd[2], status; 775 static int devnull = -1; 776 pid_t child; 777 778 if (gzrewind(in) < 0) 779 err(EXIT_FAILURE, "Cannot rewind pipe"); 780 781 if ((devnull < 0) && 782 ((devnull = open(_PATH_DEVNULL, O_WRONLY, 0)) < 0)) 783 err(EXIT_FAILURE, "Cannot open `/dev/null'"); 784 785 (void)strlcpy(tempname, _PATH_TMP "makewhatis.XXXXXX", 786 sizeof(tempname)); 787 if ((tempfd = mkstemp(tempname)) == -1) 788 err(EXIT_FAILURE, "Cannot create temp file"); 789 790 while ((bytes = gzread(in, buffer, sizeof(buffer))) > 0) 791 if (write(tempfd, buffer, (size_t)bytes) != bytes) { 792 bytes = -1; 793 break; 794 } 795 796 if (bytes < 0) { 797 (void)close(tempfd); 798 (void)unlink(tempname); 799 err(EXIT_FAILURE, "Read from pipe failed"); 800 } 801 if (lseek(tempfd, (off_t)0, SEEK_SET) == (off_t)-1) { 802 (void)close(tempfd); 803 (void)unlink(tempname); 804 err(EXIT_FAILURE, "Cannot rewind temp file"); 805 } 806 if (pipe(pipefd) == -1) { 807 (void)close(tempfd); 808 (void)unlink(tempname); 809 err(EXIT_FAILURE, "Cannot create pipe"); 810 } 811 812 switch (child = vfork()) { 813 case -1: 814 (void)close(pipefd[1]); 815 (void)close(pipefd[0]); 816 (void)close(tempfd); 817 (void)unlink(tempname); 818 err(EXIT_FAILURE, "Fork failed"); 819 /* NOTREACHED */ 820 case 0: 821 (void)close(pipefd[0]); 822 if (tempfd != STDIN_FILENO) { 823 (void)dup2(tempfd, STDIN_FILENO); 824 (void)close(tempfd); 825 } 826 if (pipefd[1] != STDOUT_FILENO) { 827 (void)dup2(pipefd[1], STDOUT_FILENO); 828 (void)close(pipefd[1]); 829 } 830 if (devnull != STDERR_FILENO) { 831 (void)dup2(devnull, STDERR_FILENO); 832 (void)close(devnull); 833 } 834 (void)execlp(NROFF, NROFF, "-S", "-man", NULL); 835 _exit(EXIT_FAILURE); 836 /*NOTREACHED*/ 837 default: 838 (void)close(pipefd[1]); 839 (void)close(tempfd); 840 break; 841 } 842 843 if ((in = gzdopen(pipefd[0], "r")) == NULL) { 844 if (errno == 0) 845 errno = ENOMEM; 846 (void)close(pipefd[0]); 847 (void)kill(child, SIGTERM); 848 while (waitpid(child, NULL, 0) != child); 849 (void)unlink(tempname); 850 err(EXIT_FAILURE, "Cannot read from pipe"); 851 } 852 853 data = parsecatpage(inname, in); 854 while (gzread(in, buffer, sizeof(buffer)) > 0); 855 (void)gzclose(in); 856 857 while (waitpid(child, &status, 0) != child); 858 if ((data != NULL) && 859 !(WIFEXITED(status) && (WEXITSTATUS(status) == 0))) { 860 free(data); 861 errx(EXIT_FAILURE, NROFF " on `%s' exited with %d status", 862 inname, WEXITSTATUS(status)); 863 } 864 865 (void)unlink(tempname); 866 return data; 867 } 868 869 static char * 870 parsemanpage(const char *name, gzFile *in, int defaultsection) 871 { 872 char *section, buffer[8192], *ptr; 873 874 section = NULL; 875 do { 876 if (GetS(in, buffer, sizeof(buffer) - 1) == NULL) { 877 free(section); 878 return NULL; 879 } 880 if (manpreprocess(buffer)) 881 continue; 882 if (strncasecmp(buffer, ".Dt", 3) == 0) { 883 char *end; 884 885 ptr = &buffer[3]; 886 if (ISSPACE(*ptr)) 887 ptr++; 888 if ((ptr = findwhitespace(ptr)) == NULL) 889 continue; 890 891 if ((end = findwhitespace(++ptr)) != NULL) 892 *end = '\0'; 893 894 free(section); 895 section = createsectionstring(ptr); 896 } 897 else if (strncasecmp(buffer, ".TH", 3) == 0) { 898 ptr = &buffer[3]; 899 while (ISSPACE(*ptr)) 900 ptr++; 901 if ((ptr = findwhitespace(ptr)) != NULL) { 902 char *next; 903 904 while (ISSPACE(*ptr)) 905 ptr++; 906 if ((next = findwhitespace(ptr)) != NULL) 907 *next = '\0'; 908 free(section); 909 section = createsectionstring(ptr); 910 } 911 } 912 else if (strncasecmp(buffer, ".Ds", 3) == 0) { 913 free(section); 914 return NULL; 915 } 916 } while (strncasecmp(buffer, ".Sh NAME", 8) != 0); 917 918 do { 919 if (GetS(in, buffer, sizeof(buffer) - 1) == NULL) { 920 free(section); 921 return NULL; 922 } 923 } while (manpreprocess(buffer)); 924 925 if (strncasecmp(buffer, ".Nm", 3) == 0) { 926 size_t length, offset; 927 928 ptr = &buffer[3]; 929 while (ISSPACE(*ptr)) 930 ptr++; 931 932 length = strlen(ptr); 933 if ((length > 1) && (ptr[length - 1] == ',') && 934 ISSPACE(ptr[length - 2])) { 935 ptr[--length] = '\0'; 936 ptr[length - 1] = ','; 937 } 938 (void) memmove(buffer, ptr, length + 1); 939 940 offset = length + 3; 941 ptr = &buffer[offset]; 942 for (;;) { 943 size_t more; 944 945 if ((sizeof(buffer) == offset) || 946 (GetS(in, ptr, sizeof(buffer) - offset) 947 == NULL)) { 948 free(section); 949 return NULL; 950 } 951 if (manpreprocess(ptr)) 952 continue; 953 954 if (strncasecmp(ptr, ".Nm", 3) != 0) break; 955 956 ptr += 3; 957 if (ISSPACE(*ptr)) 958 ptr++; 959 960 buffer[length++] = ' '; 961 more = strlen(ptr); 962 if ((more > 1) && (ptr[more - 1] == ',') && 963 ISSPACE(ptr[more - 2])) { 964 ptr[--more] = '\0'; 965 ptr[more - 1] = ','; 966 } 967 968 (void) memmove(&buffer[length], ptr, more + 1); 969 length += more; 970 offset = length + 3; 971 972 ptr = &buffer[offset]; 973 } 974 975 if (strncasecmp(ptr, ".Nd", 3) == 0) { 976 (void) strlcpy(&buffer[length], " -", 977 sizeof(buffer) - length); 978 979 while (strncasecmp(ptr, ".Sh", 3) != 0) { 980 int more; 981 982 if (*ptr == '.') { 983 char *space; 984 985 if (strncasecmp(ptr, ".Nd", 3) != 0 || 986 strchr(ptr, '[') != NULL) { 987 free(section); 988 return NULL; 989 } 990 space = findwhitespace(ptr); 991 if (space == NULL) { 992 ptr = ""; 993 } else { 994 space++; 995 (void) strmove(ptr, space); 996 } 997 } 998 999 if (*ptr != '\0') { 1000 buffer[offset - 1] = ' '; 1001 more = strlen(ptr) + 1; 1002 offset += more; 1003 } 1004 ptr = &buffer[offset]; 1005 if ((sizeof(buffer) == offset) || 1006 (GetS(in, ptr, sizeof(buffer) - offset) 1007 == NULL)) { 1008 free(section); 1009 return NULL; 1010 } 1011 if (manpreprocess(ptr)) 1012 *ptr = '\0'; 1013 } 1014 } 1015 } 1016 else { 1017 int offset; 1018 1019 if (*buffer == '.') { 1020 char *space; 1021 1022 if ((space = findwhitespace(&buffer[1])) == NULL) { 1023 free(section); 1024 return NULL; 1025 } 1026 space++; 1027 (void) strmove(buffer, space); 1028 } 1029 1030 offset = strlen(buffer) + 1; 1031 for (;;) { 1032 int more; 1033 1034 ptr = &buffer[offset]; 1035 if ((sizeof(buffer) == offset) || 1036 (GetS(in, ptr, sizeof(buffer) - offset) 1037 == NULL)) { 1038 free(section); 1039 return NULL; 1040 } 1041 if (manpreprocess(ptr) || (*ptr == '\0')) 1042 continue; 1043 1044 if ((strncasecmp(ptr, ".Sh", 3) == 0) || 1045 (strncasecmp(ptr, ".Ss", 3) == 0)) 1046 break; 1047 1048 if (*ptr == '.') { 1049 char *space; 1050 1051 if ((space = findwhitespace(ptr)) == NULL) { 1052 continue; 1053 } 1054 1055 space++; 1056 (void) memmove(ptr, space, strlen(space) + 1); 1057 } 1058 1059 buffer[offset - 1] = ' '; 1060 more = strlen(ptr); 1061 if ((more > 1) && (ptr[more - 1] == ',') && 1062 ISSPACE(ptr[more - 2])) { 1063 ptr[more - 1] = '\0'; 1064 ptr[more - 2] = ','; 1065 } 1066 else more++; 1067 offset += more; 1068 } 1069 } 1070 1071 if (section == NULL) 1072 section = makesection(defaultsection); 1073 1074 ptr = makewhatisline(name, buffer, section); 1075 free(section); 1076 return ptr; 1077 } 1078 1079 static char * 1080 getwhatisdata(char *name) 1081 { 1082 gzFile *in; 1083 char *data; 1084 int section; 1085 1086 if ((in = gzopen(name, "r")) == NULL) { 1087 if (errno == 0) 1088 errno = ENOMEM; 1089 err(EXIT_FAILURE, "Cannot open `%s'", name); 1090 /* NOTREACHED */ 1091 } 1092 1093 section = manpagesection(name); 1094 if (section == 0) { 1095 data = parsecatpage(name, in); 1096 } else { 1097 data = parsemanpage(name, in, section); 1098 if (data == NULL) 1099 data = nroff(name, in); 1100 } 1101 1102 (void) gzclose(in); 1103 return data; 1104 } 1105 1106 static void 1107 processmanpages(manpage **source, whatis **dest) 1108 { 1109 manpage *mp; 1110 char sd[128]; 1111 1112 mp = *source; 1113 *source = NULL; 1114 1115 while (mp != NULL) { 1116 manpage *obsolete; 1117 char *data; 1118 1119 if (mp->mp_left != NULL) 1120 processmanpages(&mp->mp_left, dest); 1121 1122 if ((data = getwhatisdata(mp->mp_name)) != NULL) { 1123 /* Pass eventual directory prefix to addwhatis() */ 1124 if (mp->mp_sdlen > 0 && mp->mp_sdlen < sizeof(sd)-1) 1125 strlcpy(sd, &mp->mp_name[mp->mp_sdoff], 1126 mp->mp_sdlen); 1127 else 1128 sd[0] = '\0'; 1129 1130 addwhatis(dest, data, sd); 1131 } 1132 1133 obsolete = mp; 1134 mp = mp->mp_right; 1135 free(obsolete); 1136 } 1137 } 1138 1139 static void 1140 dumpwhatis(FILE *out, whatis *tree) 1141 { 1142 while (tree != NULL) { 1143 if (tree->wi_left) 1144 dumpwhatis(out, tree->wi_left); 1145 1146 if ((tree->wi_data[0] && fputs(tree->wi_prefix, out) == EOF) || 1147 (fputs(tree->wi_data, out) == EOF) || 1148 (fputc('\n', out) == EOF)) 1149 err(EXIT_FAILURE, "Write failed"); 1150 1151 tree = tree->wi_right; 1152 } 1153 } 1154