1/*- 2 * Copyright (c) 2002 John Rochester 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer, 10 * in this position and unchanged. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. The name of the author may not be used to endorse or promote products 15 * derived from this software without specific prior written permission 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29#include <sys/cdefs.h> 30__FBSDID("$FreeBSD: src/usr.bin/makewhatis/makewhatis.c,v 1.9 2002/09/04 23:29:04 dwmalone Exp $"); 31 32#include <sys/types.h> 33#include <sys/stat.h> 34#include <sys/param.h> 35#include <sys/queue.h> 36 37/* Workaround for missing #define in sys/queue.h (3806865) */ 38#ifndef SLIST_HEAD_INITIALIZER 39#define SLIST_HEAD_INITIALIZER(head) { NULL } 40#endif 41 42#include <ctype.h> 43#include <dirent.h> 44#include <err.h> 45#include <stdio.h> 46#include <stdlib.h> 47#include <string.h> 48#include <stringlist.h> 49#include <unistd.h> 50#include <zlib.h> 51 52#define DEFAULT_MANPATH "/usr/share/man" 53#define LINE_ALLOC 4096 54 55static char blank[] = ""; 56 57/* 58 * Information collected about each man page in a section. 59 */ 60struct page_info { 61 char * filename; 62 char * name; 63 char * suffix; 64 int gzipped; 65 ino_t inode; 66}; 67 68/* 69 * An entry kept for each visited directory. 70 */ 71struct visited_dir { 72 dev_t device; 73 ino_t inode; 74 SLIST_ENTRY(visited_dir) next; 75}; 76 77/* 78 * an expanding string 79 */ 80struct sbuf { 81 char * content; /* the start of the buffer */ 82 char * end; /* just past the end of the content */ 83 char * last; /* the last allocated character */ 84}; 85 86/* 87 * Removes the last amount characters from the sbuf. 88 */ 89#define sbuf_retract(sbuf, amount) \ 90 ((sbuf)->end -= (amount)) 91/* 92 * Returns the length of the sbuf content. 93 */ 94#define sbuf_length(sbuf) \ 95 ((sbuf)->end - (sbuf)->content) 96 97typedef char *edited_copy(char *from, char *to, int length); 98 99static int append; /* -a flag: append to existing whatis */ 100static int verbose; /* -v flag: be verbose with warnings */ 101static int indent = 24; /* -i option: description indentation */ 102static const char *whatis_name="whatis";/* -n option: the name */ 103static char *common_output; /* -o option: the single output file */ 104static char *locale; /* user's locale if -L is used */ 105static char *lang_locale; /* short form of locale */ 106#ifndef __APPLE__ 107static const char *machine; 108#endif /* !__APPLE__ */ 109 110static int exit_code; /* exit code to use when finished */ 111static SLIST_HEAD(, visited_dir) visited_dirs = 112 SLIST_HEAD_INITIALIZER(visited_dirs); 113 114/* 115 * While the whatis line is being formed, it is stored in whatis_proto. 116 * When finished, it is reformatted into whatis_final and then appended 117 * to whatis_lines. 118 */ 119static struct sbuf *whatis_proto; 120static struct sbuf *whatis_final; 121static StringList *whatis_lines; /* collected output lines */ 122 123static char tmp_file[MAXPATHLEN]; /* path of temporary file, if any */ 124 125/* A set of possible names for the NAME man page section */ 126static const char *name_section_titles[] = { 127 "NAME", "Name", "NAMN", "BEZEICHNUNG", "\xcc\xbe\xbe\xce", 128 "\xee\xe1\xfa\xf7\xe1\xee\xe9\xe5", NULL 129}; 130 131/* A subset of the mdoc(7) commands to ignore */ 132static char mdoc_commands[] = "ArDvErEvFlLiNmPa"; 133 134/* 135 * Frees a struct page_info and its content. 136 */ 137static void 138free_page_info(struct page_info *info) 139{ 140 free(info->filename); 141 free(info->name); 142 free(info->suffix); 143 free(info); 144} 145 146/* 147 * Allocates and fills in a new struct page_info given the 148 * name of the man section directory and the dirent of the file. 149 * If the file is not a man page, returns NULL. 150 */ 151static struct page_info * 152new_page_info(char *dir, struct dirent *dirent) 153{ 154 struct page_info *info; 155 int basename_length; 156 char *suffix; 157 struct stat st; 158 159 info = (struct page_info *) malloc(sizeof(struct page_info)); 160 if (info == NULL) 161 err(1, "malloc"); 162 basename_length = strlen(dirent->d_name); 163 suffix = &dirent->d_name[basename_length]; 164 asprintf(&info->filename, "%s/%s", dir, dirent->d_name); 165 if ((info->gzipped = basename_length >= 4 && strcmp(&dirent->d_name[basename_length - 3], ".gz") == 0)) { 166 suffix -= 3; 167 *suffix = '\0'; 168 } 169 for (;;) { 170 if (--suffix == dirent->d_name || !isalnum(*suffix)) { 171 if (*suffix == '.') 172 break; 173 if (verbose) 174 warnx("%s: invalid man page name", info->filename); 175 free(info->filename); 176 free(info); 177 return NULL; 178 } 179 } 180 *suffix++ = '\0'; 181 info->name = strdup(dirent->d_name); 182 info->suffix = strdup(suffix); 183 if (stat(info->filename, &st) < 0) { 184 warn("%s", info->filename); 185 free_page_info(info); 186 return NULL; 187 } 188 if (!S_ISREG(st.st_mode)) { 189 if (verbose && !S_ISDIR(st.st_mode)) 190 warnx("%s: not a regular file", info->filename); 191 free_page_info(info); 192 return NULL; 193 } 194 info->inode = st.st_ino; 195 return info; 196} 197 198/* 199 * Reset an sbuf's length to 0. 200 */ 201static void 202sbuf_clear(struct sbuf *sbuf) 203{ 204 sbuf->end = sbuf->content; 205} 206 207/* 208 * Allocate a new sbuf. 209 */ 210static struct sbuf * 211new_sbuf(void) 212{ 213 struct sbuf *sbuf = (struct sbuf *) malloc(sizeof(struct sbuf)); 214 sbuf->content = (char *) malloc(LINE_ALLOC); 215 sbuf->last = sbuf->content + LINE_ALLOC - 1; 216 sbuf_clear(sbuf); 217 return sbuf; 218} 219 220/* 221 * Ensure that there is enough room in the sbuf for nchars more characters. 222 */ 223static void 224sbuf_need(struct sbuf *sbuf, int nchars) 225{ 226 char *new_content; 227 size_t size, cntsize; 228 229 /* double the size of the allocation until the buffer is big enough */ 230 while (sbuf->end + nchars > sbuf->last) { 231 size = sbuf->last + 1 - sbuf->content; 232 size *= 2; 233 cntsize = sbuf->end - sbuf->content; 234 235 new_content = (char *)malloc(size); 236 memcpy(new_content, sbuf->content, cntsize); 237 free(sbuf->content); 238 sbuf->content = new_content; 239 sbuf->end = new_content + cntsize; 240 sbuf->last = new_content + size - 1; 241 } 242} 243 244/* 245 * Appends a string of a given length to the sbuf. 246 */ 247static void 248sbuf_append(struct sbuf *sbuf, const char *text, int length) 249{ 250 if (length > 0) { 251 sbuf_need(sbuf, length); 252 memcpy(sbuf->end, text, length); 253 sbuf->end += length; 254 } 255} 256 257/* 258 * Appends a null-terminated string to the sbuf. 259 */ 260static void 261sbuf_append_str(struct sbuf *sbuf, char *text) 262{ 263 sbuf_append(sbuf, text, strlen(text)); 264} 265 266/* 267 * Appends an edited null-terminated string to the sbuf. 268 */ 269static void 270sbuf_append_edited(struct sbuf *sbuf, char *text, edited_copy copy) 271{ 272 int length = strlen(text); 273 if (length > 0) { 274 sbuf_need(sbuf, length); 275 sbuf->end = copy(text, sbuf->end, length); 276 } 277} 278 279/* 280 * Strips any of a set of chars from the end of the sbuf. 281 */ 282static void 283sbuf_strip(struct sbuf *sbuf, const char *set) 284{ 285 while (sbuf->end > sbuf->content && strchr(set, sbuf->end[-1]) != NULL) 286 sbuf->end--; 287} 288 289/* 290 * Returns the null-terminated string built by the sbuf. 291 */ 292static char * 293sbuf_content(struct sbuf *sbuf) 294{ 295 *sbuf->end = '\0'; 296 return sbuf->content; 297} 298 299/* 300 * Returns true if no man page exists in the directory with 301 * any of the names in the StringList. 302 */ 303static int 304no_page_exists(char *dir, StringList *names, char *suffix) 305{ 306 char path[MAXPATHLEN]; 307 size_t i; 308 309 for (i = 0; i < names->sl_cur; i++) { 310 snprintf(path, sizeof path, "%s/%s.%s.gz", dir, names->sl_str[i], suffix); 311 if (access(path, F_OK) < 0) { 312 path[strlen(path) - 3] = '\0'; 313 if (access(path, F_OK) < 0) 314 continue; 315 } 316 return 0; 317 } 318 return 1; 319} 320 321static void 322trap_signal(int sig __unused) 323{ 324 if (tmp_file[0] != '\0') 325 unlink(tmp_file); 326 exit(1); 327} 328 329/* 330 * Attempts to open an output file. Returns NULL if unsuccessful. 331 */ 332static FILE * 333open_output(char *name) 334{ 335 FILE *output; 336 337 whatis_lines = sl_init(); 338 if (append) { 339 char line[LINE_ALLOC]; 340 341 output = fopen(name, "r"); 342 if (output == NULL) { 343 warn("%s", name); 344 exit_code = 1; 345 return NULL; 346 } 347 while (fgets(line, sizeof line, output) != NULL) { 348 line[strlen(line) - 1] = '\0'; 349 sl_add(whatis_lines, strdup(line)); 350 } 351 } 352 if (common_output == NULL) { 353 snprintf(tmp_file, sizeof tmp_file, "%s.tmp", name); 354 name = tmp_file; 355 } 356 output = fopen(name, "w"); 357 if (output == NULL) { 358 warn("%s", name); 359 exit_code = 1; 360 return NULL; 361 } 362 return output; 363} 364 365static int 366linesort(const void *a, const void *b) 367{ 368 return strcmp((*(const char * const *)a), (*(const char * const *)b)); 369} 370 371/* 372 * Writes the unique sorted lines to the output file. 373 */ 374static void 375finish_output(FILE *output, char *name) 376{ 377 size_t i; 378 char *prev = NULL; 379 380 qsort(whatis_lines->sl_str, whatis_lines->sl_cur, sizeof(char *), linesort); 381 for (i = 0; i < whatis_lines->sl_cur; i++) { 382 char *line = whatis_lines->sl_str[i]; 383 if (i > 0 && strcmp(line, prev) == 0) 384 continue; 385 prev = line; 386 fputs(line, output); 387 putc('\n', output); 388 } 389 fclose(output); 390 sl_free(whatis_lines, 1); 391 if (common_output == NULL) { 392 rename(tmp_file, name); 393 unlink(tmp_file); 394 } 395} 396 397static FILE * 398open_whatis(char *mandir) 399{ 400 char filename[MAXPATHLEN]; 401 402 snprintf(filename, sizeof filename, "%s/%s", mandir, whatis_name); 403 return open_output(filename); 404} 405 406static void 407finish_whatis(FILE *output, char *mandir) 408{ 409 char filename[MAXPATHLEN]; 410 411 snprintf(filename, sizeof filename, "%s/%s", mandir, whatis_name); 412 finish_output(output, filename); 413} 414 415/* 416 * Tests to see if the given directory has already been visited. 417 */ 418static int 419already_visited(char *dir) 420{ 421 struct stat st; 422 struct visited_dir *visit; 423 424 if (stat(dir, &st) < 0) { 425 warn("%s", dir); 426 exit_code = 1; 427 return 1; 428 } 429 SLIST_FOREACH(visit, &visited_dirs, next) { 430 if (visit->inode == st.st_ino && 431 visit->device == st.st_dev) { 432 warnx("already visited %s", dir); 433 return 1; 434 } 435 } 436 visit = (struct visited_dir *) malloc(sizeof(struct visited_dir)); 437 visit->device = st.st_dev; 438 visit->inode = st.st_ino; 439 SLIST_INSERT_HEAD(&visited_dirs, visit, next); 440 return 0; 441} 442 443/* 444 * Removes trailing spaces from a string, returning a pointer to just 445 * beyond the new last character. 446 */ 447static char * 448trim_rhs(char *str) 449{ 450 char *rhs = &str[strlen(str)]; 451 while (--rhs > str && isspace(*rhs)) 452 ; 453 *++rhs = '\0'; 454 return rhs; 455} 456 457/* 458 * Returns a pointer to the next non-space character in the string. 459 */ 460static char * 461skip_spaces(char *s) 462{ 463 while (*s != '\0' && isspace(*s)) 464 s++; 465 return s; 466} 467 468/* 469 * Returns whether the string contains only digits. 470 */ 471static int 472only_digits(char *line) 473{ 474 if (!isdigit(*line++)) 475 return 0; 476 while (isdigit(*line)) 477 line++; 478 return *line == '\0'; 479} 480 481/* 482 * Returns whether the line is of one of the forms: 483 * .Sh NAME 484 * .Sh "NAME" 485 * etc. 486 * assuming that section_start is ".Sh". 487 */ 488static int 489name_section_line(char *line, const char *section_start) 490{ 491 char *rhs; 492 const char **title; 493 494 if (strncmp(line, section_start, 3) != 0) 495 return 0; 496 line = skip_spaces(line + 3); 497 rhs = trim_rhs(line); 498 if (*line == '"') { 499 line++; 500 if (*--rhs == '"') 501 *rhs = '\0'; 502 } 503 for (title = name_section_titles; *title != NULL; title++) 504 if (strcmp(*title, line) == 0) 505 return 1; 506 return 0; 507} 508 509/* 510 * Copies characters while removing the most common nroff/troff 511 * markup: 512 * \(em, \(mi, \s[+-N], \& 513 * \fF, \f(fo, \f[font] 514 * \*s, \*(st, \*[stringvar] 515 */ 516static char * 517de_nroff_copy(char *from, char *to, int fromlen) 518{ 519 char *from_end = &from[fromlen]; 520 while (from < from_end) { 521 switch (*from) { 522 case '\\': 523 switch (*++from) { 524 case '(': 525 if (strncmp(&from[1], "em", 2) == 0 || 526 strncmp(&from[1], "mi", 2) == 0) { 527 from += 3; 528 continue; 529 } 530 break; 531 case 's': 532 if (*++from == '-') 533 from++; 534 while (isdigit(*from)) 535 from++; 536 continue; 537 case 'f': 538 case '*': 539 if (*++from == '(') 540 from += 3; 541 else if (*from == '[') { 542 while (*++from != ']' && from < from_end); 543 from++; 544 } else 545 from++; 546 continue; 547 case '&': 548 from++; 549 continue; 550 } 551 break; 552 } 553 *to++ = *from++; 554 } 555 return to; 556} 557 558/* 559 * Appends a string with the nroff formatting removed. 560 */ 561static void 562add_nroff(char *text) 563{ 564 sbuf_append_edited(whatis_proto, text, de_nroff_copy); 565} 566 567/* 568 * Appends "name(suffix), " to whatis_final. 569 */ 570static void 571add_whatis_name(char *name, char *suffix) 572{ 573 if (*name != '\0') { 574 sbuf_append_str(whatis_final, name); 575 sbuf_append(whatis_final, "(", 1); 576 sbuf_append_str(whatis_final, suffix); 577 sbuf_append(whatis_final, "), ", 3); 578 } 579} 580 581/* 582 * Processes an old-style man(7) line. This ignores commands with only 583 * a single number argument. 584 */ 585static void 586process_man_line(char *line) 587{ 588 if (*line == '.') { 589 while (isalpha(*++line)) 590 ; 591 line = skip_spaces(line); 592 if (only_digits(line)) 593 return; 594 } else 595 line = skip_spaces(line); 596#ifdef __APPLE__ 597 /* 4454557 */ 598 if (*line == '"') 599 ++line; 600#endif /* __APPLE__ */ 601 if (*line != '\0') { 602 add_nroff(line); 603 sbuf_append(whatis_proto, " ", 1); 604 } 605} 606 607/* 608 * Processes a new-style mdoc(7) line. 609 */ 610static void 611process_mdoc_line(char *line) 612{ 613 int xref; 614 int arg = 0; 615 char *line_end = &line[strlen(line)]; 616 int orig_length = sbuf_length(whatis_proto); 617 char *next; 618 619 if (*line == '\0') 620 return; 621 if (line[0] != '.' || !isupper(line[1]) || !islower(line[2])) { 622 add_nroff(skip_spaces(line)); 623 sbuf_append(whatis_proto, " ", 1); 624 return; 625 } 626 xref = strncmp(line, ".Xr", 3) == 0; 627 line += 3; 628 while ((line = skip_spaces(line)) < line_end) { 629 if (*line == '"') { 630 next = ++line; 631 for (;;) { 632 next = strchr(next, '"'); 633 if (next == NULL) 634 break; 635 memmove(next, next + 1, strlen(next)); 636 line_end--; 637 if (*next != '"') 638 break; 639 next++; 640 } 641 } else 642 next = strpbrk(line, " \t"); 643 if (next != NULL) 644 *next++ = '\0'; 645 else 646 next = line_end; 647 if (isupper(*line) && islower(line[1]) && line[2] == '\0') { 648 if (strcmp(line, "Ns") == 0) { 649 arg = 0; 650 line = next; 651 continue; 652 } 653 if (strstr(mdoc_commands, line) != NULL) { 654 line = next; 655 continue; 656 } 657 } 658 if (arg > 0 && strchr(",.:;?!)]", *line) == 0) { 659 if (xref) { 660 sbuf_append(whatis_proto, "(", 1); 661 add_nroff(line); 662 sbuf_append(whatis_proto, ")", 1); 663 xref = 0; 664 line = blank; 665 } else 666 sbuf_append(whatis_proto, " ", 1); 667 } 668 add_nroff(line); 669 arg++; 670 line = next; 671 } 672 if (sbuf_length(whatis_proto) > orig_length) 673 sbuf_append(whatis_proto, " ", 1); 674} 675 676/* 677 * Collects a list of comma-separated names from the text. 678 */ 679static void 680collect_names(StringList *names, char *text) 681{ 682 char *arg; 683 684 for (;;) { 685 arg = text; 686 text = strchr(text, ','); 687 if (text != NULL) 688 *text++ = '\0'; 689 sl_add(names, arg); 690 if (text == NULL) 691 return; 692 if (*text == ' ') 693 text++; 694 } 695} 696 697enum { STATE_UNKNOWN, STATE_MANSTYLE, STATE_MDOCNAME, STATE_MDOCDESC }; 698 699/* 700 * Processes a man page source into a single whatis line and adds it 701 * to whatis_lines. 702 */ 703static void 704process_page(struct page_info *page, char *section_dir) 705{ 706 gzFile *in; 707 char buffer[4096]; 708 char *line; 709 StringList *names; 710 char *descr; 711 int state = STATE_UNKNOWN; 712 size_t i; 713 714 sbuf_clear(whatis_proto); 715 if ((in = gzopen(page->filename, "r")) == NULL) { 716 warn("%s", page->filename); 717 exit_code = 1; 718 return; 719 } 720 while (gzgets(in, buffer, sizeof buffer) != NULL) { 721 line = buffer; 722 if (strncmp(line, ".\\\"", 3) == 0) /* ignore comments */ 723 continue; 724 switch (state) { 725 /* 726 * haven't reached the NAME section yet. 727 */ 728 case STATE_UNKNOWN: 729 if (name_section_line(line, ".SH")) 730 state = STATE_MANSTYLE; 731 else if (name_section_line(line, ".Sh")) 732 state = STATE_MDOCNAME; 733 continue; 734 /* 735 * Inside an old-style .SH NAME section. 736 */ 737 case STATE_MANSTYLE: 738 if ((strncmp(line, ".SH", 3) == 0) || (strncmp(line, ".SS", 3) == 0)) 739 break; 740 trim_rhs(line); 741 if (strcmp(line, ".") == 0) 742 continue; 743 if (strncmp(line, ".IX", 3) == 0) { 744 line += 3; 745 line = skip_spaces(line); 746 } 747 process_man_line(line); 748 continue; 749 /* 750 * Inside a new-style .Sh NAME section (the .Nm part). 751 */ 752 case STATE_MDOCNAME: 753 trim_rhs(line); 754 if (strncmp(line, ".Nm", 3) == 0) { 755 process_mdoc_line(line); 756 continue; 757 } else { 758 if (strcmp(line, ".") == 0) 759 continue; 760 sbuf_append(whatis_proto, "- ", 2); 761 state = STATE_MDOCDESC; 762 } 763 /* fall through */ 764 /* 765 * Inside a new-style .Sh NAME section (after the .Nm-s). 766 */ 767 case STATE_MDOCDESC: 768 if (strncmp(line, ".Sh", 3) == 0) 769 break; 770 trim_rhs(line); 771 if (strcmp(line, ".") == 0) 772 continue; 773 process_mdoc_line(line); 774 continue; 775 } 776 break; 777 } 778 gzclose(in); 779 sbuf_strip(whatis_proto, " \t.-"); 780 line = sbuf_content(whatis_proto); 781 /* 782 * line now contains the appropriate data, but without 783 * the proper indentation or the section appended to each name. 784 */ 785 descr = strstr(line, " - "); 786 if (descr == NULL) { 787 descr = strchr(line, ' '); 788 if (descr == NULL) { 789 if (verbose) 790 fprintf(stderr, " ignoring junk description \"%s\"\n", line); 791 return; 792 } 793 *descr++ = '\0'; 794 } else { 795 *descr = '\0'; 796 descr += 3; 797 } 798 names = sl_init(); 799 collect_names(names, line); 800 sbuf_clear(whatis_final); 801 if (!sl_find(names, page->name) && no_page_exists(section_dir, names, page->suffix)) { 802 /* 803 * Add the page name since that's the only thing that 804 * man(1) will find. 805 */ 806 add_whatis_name(page->name, page->suffix); 807 } 808 for (i = 0; i < names->sl_cur; i++) 809 add_whatis_name(names->sl_str[i], page->suffix); 810 sl_free(names, 0); 811 sbuf_retract(whatis_final, 2); /* remove last ", " */ 812 while (sbuf_length(whatis_final) < indent) 813 sbuf_append(whatis_final, " ", 1); 814 sbuf_append(whatis_final, " - ", 3); 815 sbuf_append_str(whatis_final, skip_spaces(descr)); 816 sl_add(whatis_lines, strdup(sbuf_content(whatis_final))); 817} 818 819/* 820 * Sorts pages first by inode number, then by name. 821 */ 822static int 823pagesort(const void *a, const void *b) 824{ 825 const struct page_info *p1 = *(struct page_info * const *) a; 826 const struct page_info *p2 = *(struct page_info * const *) b; 827 if (p1->inode == p2->inode) 828 return strcmp(p1->name, p2->name); 829 return p1->inode - p2->inode; 830} 831 832/* 833 * Processes a single man section. 834 */ 835static void 836process_section(char *section_dir) 837{ 838 struct dirent **entries; 839 int nentries; 840 struct page_info **pages; 841 int npages = 0; 842 int i; 843 ino_t prev_inode = 0; 844 845 if (verbose) 846 fprintf(stderr, " %s\n", section_dir); 847 848 /* 849 * scan the man section directory for pages 850 */ 851 nentries = scandir(section_dir, &entries, NULL, alphasort); 852 if (nentries < 0) { 853 warn("%s", section_dir); 854 exit_code = 1; 855 return; 856 } 857 /* 858 * collect information about man pages 859 */ 860 pages = (struct page_info **) calloc(nentries, sizeof(struct page_info *)); 861 for (i = 0; i < nentries; i++) { 862 struct page_info *info = new_page_info(section_dir, entries[i]); 863 if (info != NULL) 864 pages[npages++] = info; 865 free(entries[i]); 866 } 867 free(entries); 868 qsort(pages, npages, sizeof(struct page_info *), pagesort); 869 /* 870 * process each unique page 871 */ 872 for (i = 0; i < npages; i++) { 873 struct page_info *page = pages[i]; 874 if (page->inode != prev_inode) { 875 prev_inode = page->inode; 876 if (verbose) 877 fprintf(stderr, " reading %s\n", page->filename); 878 process_page(page, section_dir); 879 } else if (verbose) 880 fprintf(stderr, " skipping %s, duplicate\n", page->filename); 881 free_page_info(page); 882 } 883 free(pages); 884} 885 886/* 887 * Returns whether the directory entry is a man page section. 888 */ 889static int 890select_sections(struct dirent *entry) 891{ 892 char *p = &entry->d_name[3]; 893 894 if (strncmp(entry->d_name, "man", 3) != 0) 895 return 0; 896 while (*p != '\0') { 897 if (!isalnum(*p++)) 898 return 0; 899 } 900 return 1; 901} 902 903/* 904 * Processes a single top-level man directory by finding all the 905 * sub-directories named man* and processing each one in turn. 906 */ 907static void 908process_mandir(char *dir_name) 909{ 910 struct dirent **entries; 911 int nsections; 912 FILE *fp = NULL; 913 int i; 914 struct stat st; 915 916 if (already_visited(dir_name)) 917 return; 918 if (verbose) 919 fprintf(stderr, "man directory %s\n", dir_name); 920 nsections = scandir(dir_name, &entries, select_sections, alphasort); 921 if (nsections < 0) { 922 warn("%s", dir_name); 923 exit_code = 1; 924 return; 925 } 926 if (common_output == NULL && (fp = open_whatis(dir_name)) == NULL) 927 return; 928 for (i = 0; i < nsections; i++) { 929 char section_dir[MAXPATHLEN]; 930 snprintf(section_dir, sizeof section_dir, "%s/%s", dir_name, entries[i]->d_name); 931 process_section(section_dir); 932#ifndef __APPLE__ 933 snprintf(section_dir, sizeof section_dir, "%s/%s/%s", dir_name, 934 entries[i]->d_name, machine); 935 if (stat(section_dir, &st) == 0 && S_ISDIR(st.st_mode)) 936 process_section(section_dir); 937#endif /* !__APPLE__ */ 938 free(entries[i]); 939 } 940 free(entries); 941 if (common_output == NULL) 942 finish_whatis(fp, dir_name); 943} 944 945/* 946 * Processes one argument, which may be a colon-separated list of 947 * directories. 948 */ 949static void 950process_argument(const char *arg) 951{ 952 char *dir; 953 char *mandir; 954 char *parg; 955 956 parg = strdup(arg); 957 if (parg == NULL) 958 err(1, "out of memory"); 959 while ((dir = strsep(&parg, ":")) != NULL) { 960 if (locale != NULL) { 961 asprintf(&mandir, "%s/%s", dir, locale); 962 process_mandir(mandir); 963 free(mandir); 964 if (lang_locale != NULL) { 965 asprintf(&mandir, "%s/%s", dir, lang_locale); 966 process_mandir(mandir); 967 free(mandir); 968 } 969 } else { 970 process_mandir(dir); 971 } 972 } 973 free(parg); 974} 975 976 977int 978main(int argc, char **argv) 979{ 980 int opt; 981 FILE *fp = NULL; 982 983 while ((opt = getopt(argc, argv, "ai:n:o:vL")) != -1) { 984 switch (opt) { 985 case 'a': 986 append++; 987 break; 988 case 'i': 989 indent = atoi(optarg); 990 break; 991 case 'n': 992 whatis_name = optarg; 993 break; 994 case 'o': 995 common_output = optarg; 996 break; 997 case 'v': 998 verbose++; 999 break; 1000 case 'L': 1001 locale = getenv("LC_ALL"); 1002 if (locale == NULL) 1003 locale = getenv("LC_CTYPE"); 1004 if (locale == NULL) 1005 locale = getenv("LANG"); 1006 if (locale != NULL) { 1007 char *sep = strchr(locale, '_'); 1008 if (sep != NULL && isupper(sep[1]) && 1009 isupper(sep[2])) { 1010 asprintf(&lang_locale, "%.*s%s", sep - locale, locale, &sep[3]); 1011 } 1012 } 1013 break; 1014 default: 1015 fprintf(stderr, "usage: %s [-a] [-i indent] [-n name] [-o output_file] [-v] [-L] [directories...]\n", argv[0]); 1016 exit(1); 1017 } 1018 } 1019 1020 signal(SIGINT, trap_signal); 1021 signal(SIGHUP, trap_signal); 1022 signal(SIGQUIT, trap_signal); 1023 signal(SIGTERM, trap_signal); 1024 SLIST_INIT(&visited_dirs); 1025 whatis_proto = new_sbuf(); 1026 whatis_final = new_sbuf(); 1027 1028#ifndef __APPLE__ 1029 if ((machine = getenv("MACHINE")) == NULL) 1030 machine = MACHINE; 1031#endif /* !__APPLE__ */ 1032 1033 if (common_output != NULL && (fp = open_output(common_output)) == NULL) 1034 err(1, "%s", common_output); 1035 if (optind == argc) { 1036 const char *manpath = getenv("MANPATH"); 1037 if (manpath == NULL) 1038 manpath = DEFAULT_MANPATH; 1039 process_argument(manpath); 1040 } else { 1041 while (optind < argc) 1042 process_argument(argv[optind++]); 1043 } 1044 if (common_output != NULL) 1045 finish_output(fp, common_output); 1046 exit(exit_code); 1047} 1048