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$"); 31 32#include <sys/types.h> 33#include <sys/stat.h> 34#include <sys/param.h> 35#include <sys/utsname.h> 36 37#include <ctype.h> 38#include <dirent.h> 39#include <err.h> 40#include <fcntl.h> 41#include <locale.h> 42#include <langinfo.h> 43#include <libgen.h> 44#include <stdio.h> 45#include <stdlib.h> 46#include <string.h> 47#include <unistd.h> 48 49#define DEFAULT_MANPATH "/usr/share/man" 50 51#define TOP_LEVEL_DIR 0 /* signifies a top-level man directory */ 52#define MAN_SECTION_DIR 1 /* signifies a man section directory */ 53#define UNKNOWN 2 /* signifies an unclassifiable directory */ 54 55#define TEST_EXISTS 0x01 56#define TEST_DIR 0x02 57#define TEST_FILE 0x04 58#define TEST_READABLE 0x08 59#define TEST_WRITABLE 0x10 60 61static int verbose; /* -v flag: be verbose with warnings */ 62static int pretend; /* -n, -p flags: print out what would be done 63 instead of actually doing it */ 64static int force; /* -f flag: force overwriting all cat pages */ 65static int rm_junk; /* -r flag: remove garbage pages */ 66static char *locale; /* user's locale if -L is used */ 67static char *lang_locale; /* short form of locale */ 68static const char *machine, *machine_arch; 69static int exit_code; /* exit code to use when finished */ 70 71/* 72 * -T argument for nroff 73 */ 74static const char *nroff_device = "ascii"; 75 76/* 77 * Mapping from locale to nroff device 78 */ 79static const char *locale_device[] = { 80 "KOI8-R", "koi8-r", 81 "ISO8859-1", "latin1", 82 "ISO8859-15", "latin1", 83 NULL 84}; 85 86#define BZ2_CMD "bzip2" 87#define BZ2_EXT ".bz2" 88#define BZ2CAT_CMD "bz" 89#define GZ_CMD "gzip" 90#define GZ_EXT ".gz" 91#define GZCAT_CMD "z" 92enum Ziptype {NONE, BZIP, GZIP}; 93 94static uid_t uid; 95static int starting_dir; 96static char tmp_file[MAXPATHLEN]; 97static struct stat test_st; 98 99/* 100 * A hashtable is an array of chains composed of this entry structure. 101 */ 102struct hash_entry { 103 ino_t inode_number; 104 dev_t device_number; 105 const char *data; 106 struct hash_entry *next; 107}; 108 109#define HASHTABLE_ALLOC 16384 /* allocation for hashtable (power of 2) */ 110#define HASH_MASK (HASHTABLE_ALLOC - 1) 111 112static struct hash_entry *visited[HASHTABLE_ALLOC]; 113static struct hash_entry *links[HASHTABLE_ALLOC]; 114 115/* 116 * Inserts a string into a hashtable keyed by inode & device number. 117 */ 118static void 119insert_hashtable(struct hash_entry **table, 120 ino_t inode_number, 121 dev_t device_number, 122 const char *data) 123{ 124 struct hash_entry *new_entry; 125 struct hash_entry **chain; 126 127 new_entry = (struct hash_entry *) malloc(sizeof(struct hash_entry)); 128 if (new_entry == NULL) 129 err(1, "can't insert into hashtable"); 130 chain = &table[inode_number & HASH_MASK]; 131 new_entry->inode_number = inode_number; 132 new_entry->device_number = device_number; 133 new_entry->data = data; 134 new_entry->next = *chain; 135 *chain = new_entry; 136} 137 138/* 139 * Finds a string in a hashtable keyed by inode & device number. 140 */ 141static const char * 142find_hashtable(struct hash_entry **table, 143 ino_t inode_number, 144 dev_t device_number) 145{ 146 struct hash_entry *chain; 147 148 chain = table[inode_number & HASH_MASK]; 149 while (chain != NULL) { 150 if (chain->inode_number == inode_number && 151 chain->device_number == device_number) 152 return chain->data; 153 chain = chain->next; 154 } 155 return NULL; 156} 157 158static void 159trap_signal(int sig __unused) 160{ 161 if (tmp_file[0] != '\0') 162 unlink(tmp_file); 163 exit(1); 164} 165 166/* 167 * Deals with junk files in the man or cat section directories. 168 */ 169static void 170junk(const char *mandir, const char *name, const char *reason) 171{ 172 if (verbose) 173 fprintf(stderr, "%s/%s: %s\n", mandir, name, reason); 174 if (rm_junk) { 175 fprintf(stderr, "rm %s/%s\n", mandir, name); 176 if (!pretend && unlink(name) < 0) 177 warn("%s/%s", mandir, name); 178 } 179} 180 181/* 182 * Returns TOP_LEVEL_DIR for .../man, MAN_SECTION_DIR for .../manXXX, 183 * and UNKNOWN for everything else. 184 */ 185static int 186directory_type(char *dir) 187{ 188 char *p; 189 190 for (;;) { 191 p = strrchr(dir, '/'); 192 if (p == NULL || p[1] != '\0') 193 break; 194 *p = '\0'; 195 } 196 if (p == NULL) 197 p = dir; 198 else 199 p++; 200 if (strncmp(p, "man", 3) == 0) { 201 p += 3; 202 if (*p == '\0') 203 return TOP_LEVEL_DIR; 204 while (isalnum((unsigned char)*p) || *p == '_') { 205 if (*++p == '\0') 206 return MAN_SECTION_DIR; 207 } 208 } 209 return UNKNOWN; 210} 211 212/* 213 * Tests whether the given file name (without a preceding path) 214 * is a proper man page name (like "mk-amd-map.8.gz"). 215 * Only alphanumerics and '_' are allowed after the last '.' and 216 * the last '.' can't be the first or last characters. 217 */ 218static int 219is_manpage_name(char *name) 220{ 221 char *lastdot = NULL; 222 char *n = name; 223 224 while (*n != '\0') { 225 if (!isalnum((unsigned char)*n)) { 226 switch (*n) { 227 case '_': 228 break; 229 case '-': 230 case '+': 231 case '[': 232 case ':': 233 lastdot = NULL; 234 break; 235 case '.': 236 lastdot = n; 237 break; 238 default: 239 return 0; 240 } 241 } 242 n++; 243 } 244 return lastdot > name && lastdot + 1 < n; 245} 246 247static int 248is_bzipped(char *name) 249{ 250 int len = strlen(name); 251 return len >= 5 && strcmp(&name[len - 4], BZ2_EXT) == 0; 252} 253 254static int 255is_gzipped(char *name) 256{ 257 int len = strlen(name); 258 return len >= 4 && strcmp(&name[len - 3], GZ_EXT) == 0; 259} 260 261/* 262 * Converts manXXX to catXXX. 263 */ 264static char * 265get_cat_section(char *section) 266{ 267 char *cat_section; 268 269 cat_section = strdup(section); 270 strncpy(cat_section, "cat", 3); 271 return cat_section; 272} 273 274/* 275 * Tests to see if the given directory has already been visited. 276 */ 277static int 278already_visited(char *mandir, char *dir, int count_visit) 279{ 280 struct stat st; 281 282 if (stat(dir, &st) < 0) { 283 if (mandir != NULL) 284 warn("%s/%s", mandir, dir); 285 else 286 warn("%s", dir); 287 exit_code = 1; 288 return 1; 289 } 290 if (find_hashtable(visited, st.st_ino, st.st_dev) != NULL) { 291 if (mandir != NULL) 292 warnx("already visited %s/%s", mandir, dir); 293 else 294 warnx("already visited %s", dir); 295 return 1; 296 } 297 if (count_visit) 298 insert_hashtable(visited, st.st_ino, st.st_dev, ""); 299 return 0; 300} 301 302/* 303 * Returns a set of TEST_* bits describing a file's type and permissions. 304 * If mod_time isn't NULL, it will contain the file's modification time. 305 */ 306static int 307test_path(char *name, time_t *mod_time) 308{ 309 int result; 310 311 if (stat(name, &test_st) < 0) 312 return 0; 313 result = TEST_EXISTS; 314 if (mod_time != NULL) 315 *mod_time = test_st.st_mtime; 316 if (S_ISDIR(test_st.st_mode)) 317 result |= TEST_DIR; 318 else if (S_ISREG(test_st.st_mode)) 319 result |= TEST_FILE; 320 if (access(name, R_OK)) 321 result |= TEST_READABLE; 322 if (access(name, W_OK)) 323 result |= TEST_WRITABLE; 324 return result; 325} 326 327/* 328 * Checks whether a file is a symbolic link. 329 */ 330static int 331is_symlink(char *path) 332{ 333 struct stat st; 334 335 return lstat(path, &st) >= 0 && S_ISLNK(st.st_mode); 336} 337 338/* 339 * Tests to see if the given directory can be written to. 340 */ 341static void 342check_writable(char *mandir) 343{ 344 if (verbose && !(test_path(mandir, NULL) & TEST_WRITABLE)) 345 fprintf(stderr, "%s: not writable - will only be able to write to existing cat directories\n", mandir); 346} 347 348/* 349 * If the directory exists, attempt to make it writable, otherwise 350 * attempt to create it. 351 */ 352static int 353make_writable_dir(char *mandir, char *dir) 354{ 355 int test; 356 357 if ((test = test_path(dir, NULL)) != 0) { 358 if (!(test & TEST_WRITABLE) && chmod(dir, 0755) < 0) { 359 warn("%s/%s: chmod", mandir, dir); 360 exit_code = 1; 361 return 0; 362 } 363 } else { 364 if (verbose || pretend) 365 fprintf(stderr, "mkdir %s\n", dir); 366 if (!pretend) { 367 unlink(dir); 368 if (mkdir(dir, 0755) < 0) { 369 warn("%s/%s: mkdir", mandir, dir); 370 exit_code = 1; 371 return 0; 372 } 373 } 374 } 375 return 1; 376} 377 378/* 379 * Processes a single man page source by using nroff to create 380 * the preformatted cat page. 381 */ 382static void 383process_page(char *mandir, char *src, char *cat, enum Ziptype zipped) 384{ 385 int src_test, cat_test; 386 time_t src_mtime, cat_mtime; 387 char cmd[MAXPATHLEN]; 388 dev_t src_dev; 389 ino_t src_ino; 390 const char *link_name; 391 392 src_test = test_path(src, &src_mtime); 393 if (!(src_test & (TEST_FILE|TEST_READABLE))) { 394 if (!(src_test & TEST_DIR)) { 395 warnx("%s/%s: unreadable", mandir, src); 396 exit_code = 1; 397 if (rm_junk && is_symlink(src)) 398 junk(mandir, src, "bogus symlink"); 399 } 400 return; 401 } 402 src_dev = test_st.st_dev; 403 src_ino = test_st.st_ino; 404 cat_test = test_path(cat, &cat_mtime); 405 if (cat_test & (TEST_FILE|TEST_READABLE)) { 406 if (!force && cat_mtime >= src_mtime) { 407 if (verbose) { 408 fprintf(stderr, "\t%s/%s: up to date\n", 409 mandir, src); 410 } 411 return; 412 } 413 } 414 /* 415 * Is the man page a link to one we've already processed? 416 */ 417 if ((link_name = find_hashtable(links, src_ino, src_dev)) != NULL) { 418 if (verbose || pretend) { 419 fprintf(stderr, "%slink %s -> %s\n", 420 verbose ? "\t" : "", cat, link_name); 421 } 422 if (!pretend) 423 link(link_name, cat); 424 return; 425 } 426 insert_hashtable(links, src_ino, src_dev, strdup(cat)); 427 if (verbose || pretend) { 428 fprintf(stderr, "%sformat %s -> %s\n", 429 verbose ? "\t" : "", src, cat); 430 if (pretend) 431 return; 432 } 433 snprintf(tmp_file, sizeof tmp_file, "%s.tmp", cat); 434 snprintf(cmd, sizeof cmd, 435 "%scat %s | tbl | nroff -c -T%s -man | %s > %s.tmp", 436 zipped == BZIP ? BZ2CAT_CMD : zipped == GZIP ? GZCAT_CMD : "", 437 src, nroff_device, 438 zipped == BZIP ? BZ2_CMD : zipped == GZIP ? GZ_CMD : "cat", 439 cat); 440 if (system(cmd) != 0) 441 err(1, "formatting pipeline"); 442 if (rename(tmp_file, cat) < 0) 443 warn("%s", cat); 444 tmp_file[0] = '\0'; 445} 446 447/* 448 * Scan the man section directory for pages and process each one, 449 * then check for junk in the corresponding cat section. 450 */ 451static void 452scan_section(char *mandir, char *section, char *cat_section) 453{ 454 struct dirent **entries; 455 char **expected = NULL; 456 int npages; 457 int nexpected = 0; 458 int i, e; 459 enum Ziptype zipped; 460 char *page_name; 461 char page_path[MAXPATHLEN]; 462 char cat_path[MAXPATHLEN]; 463 char zip_path[MAXPATHLEN]; 464 465 /* 466 * scan the man section directory for pages 467 */ 468 npages = scandir(section, &entries, NULL, alphasort); 469 if (npages < 0) { 470 warn("%s/%s", mandir, section); 471 exit_code = 1; 472 return; 473 } 474 if (verbose || rm_junk) { 475 /* 476 * Maintain a list of all cat pages that should exist, 477 * corresponding to existing man pages. 478 */ 479 expected = (char **) calloc(npages, sizeof(char *)); 480 } 481 for (i = 0; i < npages; free(entries[i++])) { 482 page_name = entries[i]->d_name; 483 snprintf(page_path, sizeof page_path, "%s/%s", section, 484 page_name); 485 if (!is_manpage_name(page_name)) { 486 if (!(test_path(page_path, NULL) & TEST_DIR)) { 487 junk(mandir, page_path, 488 "invalid man page name"); 489 } 490 continue; 491 } 492 zipped = is_bzipped(page_name) ? BZIP : 493 is_gzipped(page_name) ? GZIP : NONE; 494 if (zipped != NONE) { 495 snprintf(cat_path, sizeof cat_path, "%s/%s", 496 cat_section, page_name); 497 if (expected != NULL) 498 expected[nexpected++] = strdup(page_name); 499 process_page(mandir, page_path, cat_path, zipped); 500 } else { 501 /* 502 * We've got an uncompressed man page, 503 * check to see if there's a (preferred) 504 * compressed one. 505 */ 506 snprintf(zip_path, sizeof zip_path, "%s%s", 507 page_path, GZ_EXT); 508 if (test_path(zip_path, NULL) != 0) { 509 junk(mandir, page_path, 510 "man page unused due to existing " GZ_EXT); 511 } else { 512 if (verbose) { 513 fprintf(stderr, 514 "warning, %s is uncompressed\n", 515 page_path); 516 } 517 snprintf(cat_path, sizeof cat_path, "%s/%s", 518 cat_section, page_name); 519 if (expected != NULL) { 520 asprintf(&expected[nexpected++], 521 "%s", page_name); 522 } 523 process_page(mandir, page_path, cat_path, NONE); 524 } 525 } 526 } 527 free(entries); 528 if (expected == NULL) 529 return; 530 /* 531 * scan cat sections for junk 532 */ 533 npages = scandir(cat_section, &entries, NULL, alphasort); 534 e = 0; 535 for (i = 0; i < npages; free(entries[i++])) { 536 const char *junk_reason; 537 int cmp = 1; 538 539 page_name = entries[i]->d_name; 540 if (strcmp(page_name, ".") == 0 || strcmp(page_name, "..") == 0) 541 continue; 542 /* 543 * Keep the index into the expected cat page list 544 * ahead of the name we've found. 545 */ 546 while (e < nexpected && 547 (cmp = strcmp(page_name, expected[e])) > 0) 548 free(expected[e++]); 549 if (cmp == 0) 550 continue; 551 /* we have an unexpected page */ 552 snprintf(cat_path, sizeof cat_path, "%s/%s", cat_section, 553 page_name); 554 if (!is_manpage_name(page_name)) { 555 if (test_path(cat_path, NULL) & TEST_DIR) 556 continue; 557 junk_reason = "invalid cat page name"; 558 } else if (!is_gzipped(page_name) && e + 1 < nexpected && 559 strncmp(page_name, expected[e + 1], strlen(page_name)) == 0 && 560 strlen(expected[e + 1]) == strlen(page_name) + 3) { 561 junk_reason = "cat page unused due to existing " GZ_EXT; 562 } else 563 junk_reason = "cat page without man page"; 564 junk(mandir, cat_path, junk_reason); 565 } 566 free(entries); 567 while (e < nexpected) 568 free(expected[e++]); 569 free(expected); 570} 571 572 573/* 574 * Processes a single man section. 575 */ 576static void 577process_section(char *mandir, char *section) 578{ 579 char *cat_section; 580 581 if (already_visited(mandir, section, 1)) 582 return; 583 if (verbose) 584 fprintf(stderr, " section %s\n", section); 585 cat_section = get_cat_section(section); 586 if (make_writable_dir(mandir, cat_section)) 587 scan_section(mandir, section, cat_section); 588 free(cat_section); 589} 590 591static int 592select_sections(const struct dirent *entry) 593{ 594 char *name; 595 int ret; 596 597 name = strdup(entry->d_name); 598 ret = directory_type(name) == MAN_SECTION_DIR; 599 free(name); 600 return (ret); 601} 602 603/* 604 * Processes a single top-level man directory. If section isn't NULL, 605 * it will only process that section sub-directory, otherwise it will 606 * process all of them. 607 */ 608static void 609process_mandir(char *dir_name, char *section) 610{ 611 fchdir(starting_dir); 612 if (already_visited(NULL, dir_name, section == NULL)) 613 return; 614 check_writable(dir_name); 615 if (verbose) 616 fprintf(stderr, "man directory %s\n", dir_name); 617 if (pretend) 618 fprintf(stderr, "cd %s\n", dir_name); 619 if (chdir(dir_name) < 0) { 620 warn("%s: chdir", dir_name); 621 exit_code = 1; 622 return; 623 } 624 if (section != NULL) { 625 process_section(dir_name, section); 626 } else { 627 struct dirent **entries; 628 char *machine_dir, *arch_dir; 629 int nsections; 630 int i; 631 632 nsections = scandir(".", &entries, select_sections, alphasort); 633 if (nsections < 0) { 634 warn("%s", dir_name); 635 exit_code = 1; 636 return; 637 } 638 for (i = 0; i < nsections; i++) { 639 process_section(dir_name, entries[i]->d_name); 640 asprintf(&machine_dir, "%s/%s", entries[i]->d_name, 641 machine); 642 if (test_path(machine_dir, NULL) & TEST_DIR) 643 process_section(dir_name, machine_dir); 644 free(machine_dir); 645 if (strcmp(machine_arch, machine) != 0) { 646 asprintf(&arch_dir, "%s/%s", entries[i]->d_name, 647 machine_arch); 648 if (test_path(arch_dir, NULL) & TEST_DIR) 649 process_section(dir_name, arch_dir); 650 free(arch_dir); 651 } 652 free(entries[i]); 653 } 654 free(entries); 655 } 656} 657 658/* 659 * Processes one argument, which may be a colon-separated list of 660 * directories. 661 */ 662static void 663process_argument(const char *arg) 664{ 665 char *dir; 666 char *mandir; 667 char *section; 668 char *parg; 669 670 parg = strdup(arg); 671 if (parg == NULL) 672 err(1, "out of memory"); 673 while ((dir = strsep(&parg, ":")) != NULL) { 674 switch (directory_type(dir)) { 675 case TOP_LEVEL_DIR: 676 if (locale != NULL) { 677 asprintf(&mandir, "%s/%s", dir, locale); 678 process_mandir(mandir, NULL); 679 free(mandir); 680 if (lang_locale != NULL) { 681 asprintf(&mandir, "%s/%s", dir, 682 lang_locale); 683 process_mandir(mandir, NULL); 684 free(mandir); 685 } 686 } else { 687 process_mandir(dir, NULL); 688 } 689 break; 690 case MAN_SECTION_DIR: { 691 mandir = strdup(dirname(dir)); 692 section = strdup(basename(dir)); 693 process_mandir(mandir, section); 694 free(mandir); 695 free(section); 696 break; 697 } 698 default: 699 warnx("%s: directory name not in proper man form", dir); 700 exit_code = 1; 701 } 702 } 703 free(parg); 704} 705 706static void 707determine_locale(void) 708{ 709 char *sep; 710 711 if ((locale = setlocale(LC_CTYPE, "")) == NULL) { 712 warnx("-L option used, but no locale found\n"); 713 return; 714 } 715 sep = strchr(locale, '_'); 716 if (sep != NULL && isupper((unsigned char)sep[1]) 717 && isupper((unsigned char)sep[2])) { 718 asprintf(&lang_locale, "%.*s%s", (int)(sep - locale), 719 locale, &sep[3]); 720 } 721 sep = nl_langinfo(CODESET); 722 if (sep != NULL && *sep != '\0' && strcmp(sep, "US-ASCII") != 0) { 723 int i; 724 725 for (i = 0; locale_device[i] != NULL; i += 2) { 726 if (strcmp(sep, locale_device[i]) == 0) { 727 nroff_device = locale_device[i + 1]; 728 break; 729 } 730 } 731 } 732 if (verbose) { 733 if (lang_locale != NULL) 734 fprintf(stderr, "short locale is %s\n", lang_locale); 735 fprintf(stderr, "nroff device is %s\n", nroff_device); 736 } 737} 738 739static void 740usage(void) 741{ 742 fprintf(stderr, "usage: %s [-fLnrv] [directories ...]\n", 743 getprogname()); 744 exit(1); 745} 746 747int 748main(int argc, char **argv) 749{ 750 int opt; 751 752 if ((uid = getuid()) == 0) { 753 fprintf(stderr, "don't run %s as root, use:\n echo", argv[0]); 754 for (optind = 0; optind < argc; optind++) { 755 fprintf(stderr, " %s", argv[optind]); 756 } 757 fprintf(stderr, " | nice -5 su -m man\n"); 758 exit(1); 759 } 760 while ((opt = getopt(argc, argv, "vnfLrh")) != -1) { 761 switch (opt) { 762 case 'f': 763 force++; 764 break; 765 case 'L': 766 determine_locale(); 767 break; 768 case 'n': 769 pretend++; 770 break; 771 case 'r': 772 rm_junk++; 773 break; 774 case 'v': 775 verbose++; 776 break; 777 default: 778 usage(); 779 /* NOTREACHED */ 780 } 781 } 782 if ((starting_dir = open(".", 0)) < 0) { 783 err(1, "."); 784 } 785 umask(022); 786 signal(SIGINT, trap_signal); 787 signal(SIGHUP, trap_signal); 788 signal(SIGQUIT, trap_signal); 789 signal(SIGTERM, trap_signal); 790 791 if ((machine = getenv("MACHINE")) == NULL) { 792 static struct utsname utsname; 793 794 if (uname(&utsname) == -1) 795 err(1, "uname"); 796 machine = utsname.machine; 797 } 798 799 if ((machine_arch = getenv("MACHINE_ARCH")) == NULL) 800 machine_arch = MACHINE_ARCH; 801 802 if (optind == argc) { 803 const char *manpath = getenv("MANPATH"); 804 if (manpath == NULL) 805 manpath = DEFAULT_MANPATH; 806 process_argument(manpath); 807 } else { 808 while (optind < argc) 809 process_argument(argv[optind++]); 810 } 811 exit(exit_code); 812} 813