1169689Skan/*- 2169689Skan * Copyright (c) 2002 John Rochester 3169689Skan * All rights reserved. 4169689Skan * 5169689Skan * Redistribution and use in source and binary forms, with or without 6169689Skan * modification, are permitted provided that the following conditions 7169689Skan * are met: 8169689Skan * 1. Redistributions of source code must retain the above copyright 9169689Skan * notice, this list of conditions and the following disclaimer, 10169689Skan * in this position and unchanged. 11169689Skan * 2. Redistributions in binary form must reproduce the above copyright 12169689Skan * notice, this list of conditions and the following disclaimer in the 13169689Skan * documentation and/or other materials provided with the distribution. 14169689Skan * 3. The name of the author may not be used to endorse or promote products 15169689Skan * derived from this software without specific prior written permission 16169689Skan * 17169689Skan * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18169689Skan * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19169689Skan * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20169689Skan * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21169689Skan * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22169689Skan * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23169689Skan * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24169689Skan * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25169689Skan * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26169689Skan * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27169689Skan */ 28169689Skan 29169689Skan#include <sys/cdefs.h> 30169689Skan__FBSDID("$FreeBSD: releng/11.0/usr.bin/catman/catman.c 299589 2016-05-13 05:49:02Z truckman $"); 31169689Skan 32169689Skan#include <sys/types.h> 33169689Skan#include <sys/stat.h> 34169689Skan#include <sys/param.h> 35169689Skan#include <sys/utsname.h> 36169689Skan 37169689Skan#include <assert.h> 38169689Skan#include <ctype.h> 39169689Skan#include <dirent.h> 40169689Skan#include <err.h> 41169689Skan#include <errno.h> 42169689Skan#include <fcntl.h> 43169689Skan#include <locale.h> 44169689Skan#include <langinfo.h> 45169689Skan#include <libgen.h> 46169689Skan#include <stdio.h> 47169689Skan#include <stdlib.h> 48169689Skan#include <string.h> 49169689Skan#include <unistd.h> 50169689Skan 51169689Skan#define DEFAULT_MANPATH "/usr/share/man" 52169689Skan 53169689Skan#define TOP_LEVEL_DIR 0 /* signifies a top-level man directory */ 54169689Skan#define MAN_SECTION_DIR 1 /* signifies a man section directory */ 55169689Skan#define UNKNOWN 2 /* signifies an unclassifiable directory */ 56169689Skan 57169689Skan#define TEST_EXISTS 0x01 58169689Skan#define TEST_DIR 0x02 59169689Skan#define TEST_FILE 0x04 60169689Skan#define TEST_READABLE 0x08 61169689Skan#define TEST_WRITABLE 0x10 62169689Skan 63169689Skanstatic int verbose; /* -v flag: be verbose with warnings */ 64169689Skanstatic int pretend; /* -n, -p flags: print out what would be done 65169689Skan instead of actually doing it */ 66169689Skanstatic int force; /* -f flag: force overwriting all cat pages */ 67169689Skanstatic int rm_junk; /* -r flag: remove garbage pages */ 68169689Skanstatic char *locale; /* user's locale if -L is used */ 69169689Skanstatic char *lang_locale; /* short form of locale */ 70169689Skanstatic const char *machine, *machine_arch; 71169689Skanstatic int exit_code; /* exit code to use when finished */ 72169689Skan 73169689Skan/* 74169689Skan * -T argument for nroff 75169689Skan */ 76169689Skanstatic const char *nroff_device = "ascii"; 77169689Skan 78169689Skan/* 79169689Skan * Mapping from locale to nroff device 80169689Skan */ 81169689Skanstatic const char *locale_device[] = { 82169689Skan "KOI8-R", "koi8-r", 83169689Skan "ISO8859-1", "latin1", 84169689Skan "ISO8859-15", "latin1", 85169689Skan NULL 86169689Skan}; 87169689Skan 88169689Skan#define BZ2_CMD "bzip2" 89169689Skan#define BZ2_EXT ".bz2" 90169689Skan#define BZ2CAT_CMD "bz" 91169689Skan#define GZ_CMD "gzip" 92169689Skan#define GZ_EXT ".gz" 93169689Skan#define GZCAT_CMD "z" 94169689Skanenum Ziptype {NONE, BZIP, GZIP}; 95169689Skan 96169689Skanstatic uid_t uid; 97169689Skanstatic int starting_dir; 98169689Skanstatic char tmp_file[MAXPATHLEN]; 99169689Skanstatic struct stat test_st; 100169689Skan 101169689Skan/* 102169689Skan * A hashtable is an array of chains composed of this entry structure. 103169689Skan */ 104169689Skanstruct hash_entry { 105169689Skan ino_t inode_number; 106169689Skan dev_t device_number; 107169689Skan const char *data; 108169689Skan struct hash_entry *next; 109169689Skan}; 110169689Skan 111169689Skan#define HASHTABLE_ALLOC 16384 /* allocation for hashtable (power of 2) */ 112169689Skan#define HASH_MASK (HASHTABLE_ALLOC - 1) 113169689Skan 114169689Skanstatic struct hash_entry *visited[HASHTABLE_ALLOC]; 115169689Skanstatic struct hash_entry *links[HASHTABLE_ALLOC]; 116169689Skan 117169689Skan/* 118169689Skan * Inserts a string into a hashtable keyed by inode & device number. 119169689Skan */ 120169689Skanstatic void 121169689Skaninsert_hashtable(struct hash_entry **table, 122169689Skan ino_t inode_number, 123169689Skan dev_t device_number, 124169689Skan const char *data) 125169689Skan{ 126169689Skan struct hash_entry *new_entry; 127169689Skan struct hash_entry **chain; 128169689Skan 129169689Skan new_entry = (struct hash_entry *) malloc(sizeof(struct hash_entry)); 130169689Skan if (new_entry == NULL) 131169689Skan err(1, "can't insert into hashtable"); 132169689Skan chain = &table[inode_number & HASH_MASK]; 133169689Skan new_entry->inode_number = inode_number; 134169689Skan new_entry->device_number = device_number; 135169689Skan new_entry->data = data; 136169689Skan new_entry->next = *chain; 137169689Skan *chain = new_entry; 138169689Skan} 139169689Skan 140169689Skan/* 141169689Skan * Finds a string in a hashtable keyed by inode & device number. 142169689Skan */ 143169689Skanstatic const char * 144169689Skanfind_hashtable(struct hash_entry **table, 145169689Skan ino_t inode_number, 146169689Skan dev_t device_number) 147169689Skan{ 148169689Skan struct hash_entry *chain; 149169689Skan 150169689Skan chain = table[inode_number & HASH_MASK]; 151169689Skan while (chain != NULL) { 152169689Skan if (chain->inode_number == inode_number && 153169689Skan chain->device_number == device_number) 154169689Skan return chain->data; 155169689Skan chain = chain->next; 156169689Skan } 157169689Skan return NULL; 158169689Skan} 159169689Skan 160169689Skanstatic void 161169689Skantrap_signal(int sig __unused) 162169689Skan{ 163169689Skan if (tmp_file[0] != '\0') 164169689Skan unlink(tmp_file); 165169689Skan exit(1); 166169689Skan} 167169689Skan 168169689Skan/* 169169689Skan * Deals with junk files in the man or cat section directories. 170169689Skan */ 171169689Skanstatic void 172169689Skanjunk(const char *mandir, const char *name, const char *reason) 173169689Skan{ 174169689Skan if (verbose) 175169689Skan fprintf(stderr, "%s/%s: %s\n", mandir, name, reason); 176169689Skan if (rm_junk) { 177169689Skan fprintf(stderr, "rm %s/%s\n", mandir, name); 178169689Skan if (!pretend && unlink(name) < 0) 179169689Skan warn("%s/%s", mandir, name); 180169689Skan } 181169689Skan} 182169689Skan 183169689Skan/* 184169689Skan * Returns TOP_LEVEL_DIR for .../man, MAN_SECTION_DIR for .../manXXX, 185169689Skan * and UNKNOWN for everything else. 186169689Skan */ 187169689Skanstatic int 188169689Skandirectory_type(char *dir) 189169689Skan{ 190169689Skan char *p; 191169689Skan 192169689Skan for (;;) { 193169689Skan p = strrchr(dir, '/'); 194169689Skan if (p == NULL || p[1] != '\0') 195169689Skan break; 196169689Skan *p = '\0'; 197169689Skan } 198169689Skan if (p == NULL) 199169689Skan p = dir; 200169689Skan else 201169689Skan p++; 202169689Skan if (strncmp(p, "man", 3) == 0) { 203169689Skan p += 3; 204169689Skan if (*p == '\0') 205169689Skan return TOP_LEVEL_DIR; 206169689Skan while (isalnum((unsigned char)*p) || *p == '_') { 207169689Skan if (*++p == '\0') 208169689Skan return MAN_SECTION_DIR; 209169689Skan } 210169689Skan } 211169689Skan return UNKNOWN; 212169689Skan} 213169689Skan 214169689Skan/* 215169689Skan * Tests whether the given file name (without a preceding path) 216169689Skan * is a proper man page name (like "mk-amd-map.8.gz"). 217169689Skan * Only alphanumerics and '_' are allowed after the last '.' and 218169689Skan * the last '.' can't be the first or last characters. 219169689Skan */ 220169689Skanstatic int 221169689Skanis_manpage_name(char *name) 222169689Skan{ 223169689Skan char *lastdot = NULL; 224169689Skan char *n = name; 225169689Skan 226169689Skan while (*n != '\0') { 227169689Skan if (!isalnum((unsigned char)*n)) { 228169689Skan switch (*n) { 229169689Skan case '_': 230169689Skan break; 231169689Skan case '-': 232169689Skan case '+': 233169689Skan case '[': 234169689Skan case ':': 235169689Skan lastdot = NULL; 236169689Skan break; 237169689Skan case '.': 238169689Skan lastdot = n; 239169689Skan break; 240169689Skan default: 241169689Skan return 0; 242169689Skan } 243169689Skan } 244169689Skan n++; 245169689Skan } 246169689Skan return lastdot > name && lastdot + 1 < n; 247169689Skan} 248169689Skan 249169689Skanstatic int 250169689Skanis_bzipped(char *name) 251169689Skan{ 252169689Skan int len = strlen(name); 253169689Skan return len >= 5 && strcmp(&name[len - 4], BZ2_EXT) == 0; 254169689Skan} 255169689Skan 256169689Skanstatic int 257169689Skanis_gzipped(char *name) 258169689Skan{ 259169689Skan int len = strlen(name); 260169689Skan return len >= 4 && strcmp(&name[len - 3], GZ_EXT) == 0; 261169689Skan} 262169689Skan 263169689Skan/* 264169689Skan * Converts manXXX to catXXX. 265169689Skan */ 266169689Skanstatic char * 267169689Skanget_cat_section(char *section) 268169689Skan{ 269169689Skan char *cat_section; 270169689Skan 271169689Skan cat_section = strdup(section); 272169689Skan assert(strlen(section) > 3 && strncmp(section, "man", 3) == 0); 273169689Skan memcpy(cat_section, "cat", 3); 274169689Skan return cat_section; 275169689Skan} 276169689Skan 277169689Skan/* 278169689Skan * Tests to see if the given directory has already been visited. 279169689Skan */ 280169689Skanstatic int 281169689Skanalready_visited(char *mandir, char *dir, int count_visit) 282169689Skan{ 283169689Skan struct stat st; 284169689Skan 285169689Skan if (stat(dir, &st) < 0) { 286169689Skan if (mandir != NULL) 287169689Skan warn("%s/%s", mandir, dir); 288169689Skan else 289169689Skan warn("%s", dir); 290169689Skan exit_code = 1; 291169689Skan return 1; 292169689Skan } 293169689Skan if (find_hashtable(visited, st.st_ino, st.st_dev) != NULL) { 294169689Skan if (mandir != NULL) 295169689Skan warnx("already visited %s/%s", mandir, dir); 296169689Skan else 297169689Skan warnx("already visited %s", dir); 298169689Skan return 1; 299169689Skan } 300169689Skan if (count_visit) 301169689Skan insert_hashtable(visited, st.st_ino, st.st_dev, ""); 302169689Skan return 0; 303169689Skan} 304169689Skan 305169689Skan/* 306169689Skan * Returns a set of TEST_* bits describing a file's type and permissions. 307169689Skan * If mod_time isn't NULL, it will contain the file's modification time. 308169689Skan */ 309169689Skanstatic int 310169689Skantest_path(char *name, time_t *mod_time) 311169689Skan{ 312169689Skan int result; 313169689Skan 314169689Skan if (stat(name, &test_st) < 0) 315169689Skan return 0; 316169689Skan result = TEST_EXISTS; 317169689Skan if (mod_time != NULL) 318169689Skan *mod_time = test_st.st_mtime; 319169689Skan if (S_ISDIR(test_st.st_mode)) 320169689Skan result |= TEST_DIR; 321169689Skan else if (S_ISREG(test_st.st_mode)) 322169689Skan result |= TEST_FILE; 323169689Skan if (access(name, R_OK)) 324169689Skan result |= TEST_READABLE; 325169689Skan if (access(name, W_OK)) 326169689Skan result |= TEST_WRITABLE; 327169689Skan return result; 328169689Skan} 329169689Skan 330169689Skan/* 331169689Skan * Checks whether a file is a symbolic link. 332169689Skan */ 333169689Skanstatic int 334169689Skanis_symlink(char *path) 335169689Skan{ 336169689Skan struct stat st; 337169689Skan 338169689Skan return lstat(path, &st) >= 0 && S_ISLNK(st.st_mode); 339169689Skan} 340169689Skan 341169689Skan/* 342169689Skan * Tests to see if the given directory can be written to. 343169689Skan */ 344169689Skanstatic void 345169689Skancheck_writable(char *mandir) 346169689Skan{ 347169689Skan if (verbose && !(test_path(mandir, NULL) & TEST_WRITABLE)) 348169689Skan fprintf(stderr, "%s: not writable - will only be able to write to existing cat directories\n", mandir); 349169689Skan} 350169689Skan 351169689Skan/* 352169689Skan * If the directory exists, attempt to make it writable, otherwise 353169689Skan * attempt to create it. 354169689Skan */ 355169689Skanstatic int 356169689Skanmake_writable_dir(char *mandir, char *dir) 357169689Skan{ 358169689Skan int test; 359169689Skan 360169689Skan if ((test = test_path(dir, NULL)) != 0) { 361169689Skan if (!(test & TEST_WRITABLE) && chmod(dir, 0755) < 0) { 362169689Skan warn("%s/%s: chmod", mandir, dir); 363169689Skan exit_code = 1; 364169689Skan return 0; 365169689Skan } 366169689Skan } else { 367169689Skan if (verbose || pretend) 368169689Skan fprintf(stderr, "mkdir %s\n", dir); 369169689Skan if (!pretend) { 370169689Skan unlink(dir); 371169689Skan if (mkdir(dir, 0755) < 0) { 372169689Skan warn("%s/%s: mkdir", mandir, dir); 373169689Skan exit_code = 1; 374169689Skan return 0; 375169689Skan } 376169689Skan } 377169689Skan } 378169689Skan return 1; 379169689Skan} 380169689Skan 381169689Skan/* 382169689Skan * Processes a single man page source by using nroff to create 383169689Skan * the preformatted cat page. 384169689Skan */ 385169689Skanstatic void 386169689Skanprocess_page(char *mandir, char *src, char *cat, enum Ziptype zipped) 387169689Skan{ 388169689Skan int src_test, cat_test; 389169689Skan time_t src_mtime, cat_mtime; 390169689Skan char cmd[MAXPATHLEN]; 391169689Skan dev_t src_dev; 392169689Skan ino_t src_ino; 393169689Skan const char *link_name; 394169689Skan 395169689Skan src_test = test_path(src, &src_mtime); 396169689Skan if (!(src_test & (TEST_FILE|TEST_READABLE))) { 397169689Skan if (!(src_test & TEST_DIR)) { 398169689Skan warnx("%s/%s: unreadable", mandir, src); 399169689Skan exit_code = 1; 400169689Skan if (rm_junk && is_symlink(src)) 401169689Skan junk(mandir, src, "bogus symlink"); 402169689Skan } 403169689Skan return; 404169689Skan } 405169689Skan src_dev = test_st.st_dev; 406169689Skan src_ino = test_st.st_ino; 407169689Skan cat_test = test_path(cat, &cat_mtime); 408169689Skan if (cat_test & (TEST_FILE|TEST_READABLE)) { 409169689Skan if (!force && cat_mtime >= src_mtime) { 410169689Skan if (verbose) { 411169689Skan fprintf(stderr, "\t%s/%s: up to date\n", 412169689Skan mandir, src); 413169689Skan } 414169689Skan return; 415169689Skan } 416169689Skan } 417169689Skan /* 418169689Skan * Is the man page a link to one we've already processed? 419169689Skan */ 420169689Skan if ((link_name = find_hashtable(links, src_ino, src_dev)) != NULL) { 421169689Skan if (verbose || pretend) { 422169689Skan fprintf(stderr, "%slink %s -> %s\n", 423169689Skan verbose ? "\t" : "", cat, link_name); 424169689Skan } 425169689Skan if (!pretend) { 426169689Skan (void) unlink(cat); 427169689Skan if (link(link_name, cat) < 0) 428169689Skan warn("%s %s: link", link_name, cat); 429169689Skan } 430169689Skan return; 431169689Skan } 432169689Skan insert_hashtable(links, src_ino, src_dev, strdup(cat)); 433169689Skan if (verbose || pretend) { 434169689Skan fprintf(stderr, "%sformat %s -> %s\n", 435169689Skan verbose ? "\t" : "", src, cat); 436169689Skan if (pretend) 437169689Skan return; 438169689Skan } 439169689Skan snprintf(tmp_file, sizeof tmp_file, "%s.tmp", cat); 440169689Skan snprintf(cmd, sizeof cmd, 441169689Skan "%scat %s | tbl | nroff -c -T%s -man | %s > %s.tmp", 442169689Skan zipped == BZIP ? BZ2CAT_CMD : zipped == GZIP ? GZCAT_CMD : "", 443169689Skan src, nroff_device, 444169689Skan zipped == BZIP ? BZ2_CMD : zipped == GZIP ? GZ_CMD : "cat", 445169689Skan cat); 446169689Skan if (system(cmd) != 0) 447169689Skan err(1, "formatting pipeline"); 448169689Skan if (rename(tmp_file, cat) < 0) 449169689Skan warn("%s", cat); 450169689Skan tmp_file[0] = '\0'; 451169689Skan} 452169689Skan 453169689Skan/* 454169689Skan * Scan the man section directory for pages and process each one, 455169689Skan * then check for junk in the corresponding cat section. 456169689Skan */ 457169689Skanstatic void 458169689Skanscan_section(char *mandir, char *section, char *cat_section) 459169689Skan{ 460169689Skan struct dirent **entries; 461169689Skan char **expected = NULL; 462169689Skan int npages; 463169689Skan int nexpected = 0; 464169689Skan int i, e; 465169689Skan enum Ziptype zipped; 466169689Skan char *page_name; 467169689Skan char page_path[MAXPATHLEN]; 468169689Skan char cat_path[MAXPATHLEN]; 469169689Skan char zip_path[MAXPATHLEN]; 470169689Skan 471169689Skan /* 472169689Skan * scan the man section directory for pages 473169689Skan */ 474169689Skan npages = scandir(section, &entries, NULL, alphasort); 475169689Skan if (npages < 0) { 476169689Skan warn("%s/%s", mandir, section); 477169689Skan exit_code = 1; 478169689Skan return; 479169689Skan } 480169689Skan if (verbose || rm_junk) { 481169689Skan /* 482169689Skan * Maintain a list of all cat pages that should exist, 483169689Skan * corresponding to existing man pages. 484169689Skan */ 485169689Skan expected = (char **) calloc(npages, sizeof(char *)); 486169689Skan } 487169689Skan for (i = 0; i < npages; free(entries[i++])) { 488169689Skan page_name = entries[i]->d_name; 489169689Skan snprintf(page_path, sizeof page_path, "%s/%s", section, 490169689Skan page_name); 491169689Skan if (!is_manpage_name(page_name)) { 492169689Skan if (!(test_path(page_path, NULL) & TEST_DIR)) { 493169689Skan junk(mandir, page_path, 494169689Skan "invalid man page name"); 495169689Skan } 496169689Skan continue; 497169689Skan } 498169689Skan zipped = is_bzipped(page_name) ? BZIP : 499169689Skan is_gzipped(page_name) ? GZIP : NONE; 500169689Skan if (zipped != NONE) { 501169689Skan snprintf(cat_path, sizeof cat_path, "%s/%s", 502169689Skan cat_section, page_name); 503169689Skan if (expected != NULL) 504169689Skan expected[nexpected++] = strdup(page_name); 505169689Skan process_page(mandir, page_path, cat_path, zipped); 506169689Skan } else { 507169689Skan /* 508169689Skan * We've got an uncompressed man page, 509169689Skan * check to see if there's a (preferred) 510169689Skan * compressed one. 511169689Skan */ 512169689Skan snprintf(zip_path, sizeof zip_path, "%s%s", 513169689Skan page_path, GZ_EXT); 514169689Skan if (test_path(zip_path, NULL) != 0) { 515169689Skan junk(mandir, page_path, 516169689Skan "man page unused due to existing " GZ_EXT); 517169689Skan } else { 518169689Skan if (verbose) { 519169689Skan fprintf(stderr, 520169689Skan "warning, %s is uncompressed\n", 521169689Skan page_path); 522169689Skan } 523169689Skan snprintf(cat_path, sizeof cat_path, "%s/%s", 524169689Skan cat_section, page_name); 525169689Skan if (expected != NULL) { 526169689Skan asprintf(&expected[nexpected++], 527169689Skan "%s", page_name); 528169689Skan } 529169689Skan process_page(mandir, page_path, cat_path, NONE); 530169689Skan } 531169689Skan } 532169689Skan } 533169689Skan free(entries); 534169689Skan if (expected == NULL) 535169689Skan return; 536169689Skan /* 537169689Skan * scan cat sections for junk 538169689Skan */ 539169689Skan npages = scandir(cat_section, &entries, NULL, alphasort); 540169689Skan e = 0; 541169689Skan for (i = 0; i < npages; free(entries[i++])) { 542169689Skan const char *junk_reason; 543169689Skan int cmp = 1; 544169689Skan 545169689Skan page_name = entries[i]->d_name; 546169689Skan if (strcmp(page_name, ".") == 0 || strcmp(page_name, "..") == 0) 547169689Skan continue; 548169689Skan /* 549169689Skan * Keep the index into the expected cat page list 550169689Skan * ahead of the name we've found. 551169689Skan */ 552169689Skan while (e < nexpected && 553169689Skan (cmp = strcmp(page_name, expected[e])) > 0) 554169689Skan free(expected[e++]); 555169689Skan if (cmp == 0) 556169689Skan continue; 557169689Skan /* we have an unexpected page */ 558169689Skan snprintf(cat_path, sizeof cat_path, "%s/%s", cat_section, 559169689Skan page_name); 560169689Skan if (!is_manpage_name(page_name)) { 561169689Skan if (test_path(cat_path, NULL) & TEST_DIR) 562169689Skan continue; 563169689Skan junk_reason = "invalid cat page name"; 564169689Skan } else if (!is_gzipped(page_name) && e + 1 < nexpected && 565169689Skan strncmp(page_name, expected[e + 1], strlen(page_name)) == 0 && 566169689Skan strlen(expected[e + 1]) == strlen(page_name) + 3) { 567169689Skan junk_reason = "cat page unused due to existing " GZ_EXT; 568169689Skan } else 569169689Skan junk_reason = "cat page without man page"; 570169689Skan junk(mandir, cat_path, junk_reason); 571169689Skan } 572169689Skan free(entries); 573169689Skan while (e < nexpected) 574169689Skan free(expected[e++]); 575169689Skan free(expected); 576169689Skan} 577169689Skan 578169689Skan 579169689Skan/* 580169689Skan * Processes a single man section. 581169689Skan */ 582169689Skanstatic void 583169689Skanprocess_section(char *mandir, char *section) 584169689Skan{ 585169689Skan char *cat_section; 586169689Skan 587169689Skan if (already_visited(mandir, section, 1)) 588169689Skan return; 589169689Skan if (verbose) 590169689Skan fprintf(stderr, " section %s\n", section); 591169689Skan cat_section = get_cat_section(section); 592169689Skan if (make_writable_dir(mandir, cat_section)) 593169689Skan scan_section(mandir, section, cat_section); 594169689Skan free(cat_section); 595169689Skan} 596169689Skan 597169689Skanstatic int 598169689Skanselect_sections(const struct dirent *entry) 599169689Skan{ 600169689Skan char *name; 601169689Skan int ret; 602169689Skan 603169689Skan name = strdup(entry->d_name); 604169689Skan ret = directory_type(name) == MAN_SECTION_DIR; 605169689Skan free(name); 606169689Skan return (ret); 607169689Skan} 608169689Skan 609169689Skan/* 610169689Skan * Processes a single top-level man directory. If section isn't NULL, 611169689Skan * it will only process that section sub-directory, otherwise it will 612169689Skan * process all of them. 613169689Skan */ 614169689Skanstatic void 615169689Skanprocess_mandir(char *dir_name, char *section) 616169689Skan{ 617169689Skan if (fchdir(starting_dir) < 0) 618169689Skan err(1, "fchdir"); 619169689Skan if (already_visited(NULL, dir_name, section == NULL)) 620169689Skan return; 621169689Skan check_writable(dir_name); 622169689Skan if (verbose) 623169689Skan fprintf(stderr, "man directory %s\n", dir_name); 624169689Skan if (pretend) 625169689Skan fprintf(stderr, "cd %s\n", dir_name); 626169689Skan if (chdir(dir_name) < 0) { 627169689Skan warn("%s: chdir", dir_name); 628169689Skan exit_code = 1; 629169689Skan return; 630169689Skan } 631169689Skan if (section != NULL) { 632169689Skan process_section(dir_name, section); 633169689Skan } else { 634169689Skan struct dirent **entries; 635169689Skan char *machine_dir, *arch_dir; 636169689Skan int nsections; 637169689Skan int i; 638169689Skan 639169689Skan nsections = scandir(".", &entries, select_sections, alphasort); 640169689Skan if (nsections < 0) { 641169689Skan 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