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