196845Smarkm/*- 296845Smarkm * Copyright (c) 2002 John Rochester 396845Smarkm * All rights reserved. 496845Smarkm * 596845Smarkm * Redistribution and use in source and binary forms, with or without 696845Smarkm * modification, are permitted provided that the following conditions 796845Smarkm * are met: 896845Smarkm * 1. Redistributions of source code must retain the above copyright 996845Smarkm * notice, this list of conditions and the following disclaimer, 1096845Smarkm * in this position and unchanged. 1196845Smarkm * 2. Redistributions in binary form must reproduce the above copyright 1296845Smarkm * notice, this list of conditions and the following disclaimer in the 1396845Smarkm * documentation and/or other materials provided with the distribution. 1496845Smarkm * 3. The name of the author may not be used to endorse or promote products 1596845Smarkm * derived from this software without specific prior written permission 1696845Smarkm * 1796845Smarkm * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 1896845Smarkm * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 1996845Smarkm * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 2096845Smarkm * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 2196845Smarkm * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 2296845Smarkm * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 2396845Smarkm * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 2496845Smarkm * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 2596845Smarkm * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 2696845Smarkm * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2796845Smarkm */ 2896845Smarkm 2996845Smarkm#include <sys/cdefs.h> 3096845Smarkm__FBSDID("$FreeBSD$"); 3196845Smarkm 3296845Smarkm#include <sys/types.h> 3396845Smarkm#include <sys/stat.h> 3496845Smarkm#include <sys/param.h> 35153115Sru#include <sys/utsname.h> 3696845Smarkm 37300269Struckman#include <assert.h> 3896845Smarkm#include <ctype.h> 3996845Smarkm#include <dirent.h> 4096845Smarkm#include <err.h> 41300269Struckman#include <errno.h> 4296845Smarkm#include <fcntl.h> 43116136Sache#include <locale.h> 44116136Sache#include <langinfo.h> 45139182Sru#include <libgen.h> 4696845Smarkm#include <stdio.h> 4796845Smarkm#include <stdlib.h> 4896845Smarkm#include <string.h> 4996845Smarkm#include <unistd.h> 5096845Smarkm 5196845Smarkm#define DEFAULT_MANPATH "/usr/share/man" 5296845Smarkm 5396845Smarkm#define TOP_LEVEL_DIR 0 /* signifies a top-level man directory */ 5496845Smarkm#define MAN_SECTION_DIR 1 /* signifies a man section directory */ 5596845Smarkm#define UNKNOWN 2 /* signifies an unclassifiable directory */ 5696845Smarkm 5796845Smarkm#define TEST_EXISTS 0x01 5896845Smarkm#define TEST_DIR 0x02 5996845Smarkm#define TEST_FILE 0x04 6096845Smarkm#define TEST_READABLE 0x08 6196845Smarkm#define TEST_WRITABLE 0x10 6296845Smarkm 6396845Smarkmstatic int verbose; /* -v flag: be verbose with warnings */ 6496845Smarkmstatic int pretend; /* -n, -p flags: print out what would be done 6596845Smarkm instead of actually doing it */ 6696845Smarkmstatic int force; /* -f flag: force overwriting all cat pages */ 6796845Smarkmstatic int rm_junk; /* -r flag: remove garbage pages */ 6896845Smarkmstatic char *locale; /* user's locale if -L is used */ 6996845Smarkmstatic char *lang_locale; /* short form of locale */ 70153115Srustatic const char *machine, *machine_arch; 7196845Smarkmstatic int exit_code; /* exit code to use when finished */ 7296845Smarkm 7396845Smarkm/* 7496845Smarkm * -T argument for nroff 7596845Smarkm */ 7696845Smarkmstatic const char *nroff_device = "ascii"; 7796845Smarkm 7896845Smarkm/* 7996845Smarkm * Mapping from locale to nroff device 8096845Smarkm */ 8196845Smarkmstatic const char *locale_device[] = { 8296845Smarkm "KOI8-R", "koi8-r", 8396845Smarkm "ISO8859-1", "latin1", 8496845Smarkm "ISO8859-15", "latin1", 8596845Smarkm NULL 8696845Smarkm}; 8796845Smarkm 88106120Sobrien#define BZ2_CMD "bzip2" 89106120Sobrien#define BZ2_EXT ".bz2" 90106120Sobrien#define BZ2CAT_CMD "bz" 91115207Sru#define GZ_CMD "gzip" 92106120Sobrien#define GZ_EXT ".gz" 93106120Sobrien#define GZCAT_CMD "z" 94106120Sobrienenum Ziptype {NONE, BZIP, GZIP}; 95106120Sobrien 96194548Sbrooksstatic uid_t uid; 9796845Smarkmstatic int starting_dir; 9896845Smarkmstatic char tmp_file[MAXPATHLEN]; 99227155Sedstatic struct stat test_st; 10096845Smarkm 10196845Smarkm/* 10296845Smarkm * A hashtable is an array of chains composed of this entry structure. 10396845Smarkm */ 10496845Smarkmstruct hash_entry { 10596845Smarkm ino_t inode_number; 10696845Smarkm dev_t device_number; 10796845Smarkm const char *data; 10896845Smarkm struct hash_entry *next; 10996845Smarkm}; 11096845Smarkm 11196845Smarkm#define HASHTABLE_ALLOC 16384 /* allocation for hashtable (power of 2) */ 11296845Smarkm#define HASH_MASK (HASHTABLE_ALLOC - 1) 11396845Smarkm 11496845Smarkmstatic struct hash_entry *visited[HASHTABLE_ALLOC]; 11596845Smarkmstatic struct hash_entry *links[HASHTABLE_ALLOC]; 11696845Smarkm 11796845Smarkm/* 11896845Smarkm * Inserts a string into a hashtable keyed by inode & device number. 11996845Smarkm */ 12096845Smarkmstatic void 12196845Smarkminsert_hashtable(struct hash_entry **table, 12296845Smarkm ino_t inode_number, 12396845Smarkm dev_t device_number, 12496845Smarkm const char *data) 12596845Smarkm{ 12696845Smarkm struct hash_entry *new_entry; 12796845Smarkm struct hash_entry **chain; 12896845Smarkm 12996845Smarkm new_entry = (struct hash_entry *) malloc(sizeof(struct hash_entry)); 13096845Smarkm if (new_entry == NULL) 13196845Smarkm err(1, "can't insert into hashtable"); 13296845Smarkm chain = &table[inode_number & HASH_MASK]; 13396845Smarkm new_entry->inode_number = inode_number; 13496845Smarkm new_entry->device_number = device_number; 13596845Smarkm new_entry->data = data; 13696845Smarkm new_entry->next = *chain; 13796845Smarkm *chain = new_entry; 13896845Smarkm} 13996845Smarkm 14096845Smarkm/* 14196845Smarkm * Finds a string in a hashtable keyed by inode & device number. 14296845Smarkm */ 14396845Smarkmstatic const char * 14496845Smarkmfind_hashtable(struct hash_entry **table, 14596845Smarkm ino_t inode_number, 14696845Smarkm dev_t device_number) 14796845Smarkm{ 14896845Smarkm struct hash_entry *chain; 14996845Smarkm 15096845Smarkm chain = table[inode_number & HASH_MASK]; 15196845Smarkm while (chain != NULL) { 15296845Smarkm if (chain->inode_number == inode_number && 15396845Smarkm chain->device_number == device_number) 15496845Smarkm return chain->data; 15596845Smarkm chain = chain->next; 15696845Smarkm } 15796845Smarkm return NULL; 15896845Smarkm} 15996845Smarkm 16096845Smarkmstatic void 16196845Smarkmtrap_signal(int sig __unused) 16296845Smarkm{ 16396845Smarkm if (tmp_file[0] != '\0') 16496845Smarkm unlink(tmp_file); 16596845Smarkm exit(1); 16696845Smarkm} 16796845Smarkm 16896845Smarkm/* 16996845Smarkm * Deals with junk files in the man or cat section directories. 17096845Smarkm */ 17196845Smarkmstatic void 17296845Smarkmjunk(const char *mandir, const char *name, const char *reason) 17396845Smarkm{ 17496845Smarkm if (verbose) 17596845Smarkm fprintf(stderr, "%s/%s: %s\n", mandir, name, reason); 17696845Smarkm if (rm_junk) { 17796845Smarkm fprintf(stderr, "rm %s/%s\n", mandir, name); 17896845Smarkm if (!pretend && unlink(name) < 0) 17996845Smarkm warn("%s/%s", mandir, name); 18096845Smarkm } 18196845Smarkm} 18296845Smarkm 18396845Smarkm/* 18496845Smarkm * Returns TOP_LEVEL_DIR for .../man, MAN_SECTION_DIR for .../manXXX, 18596845Smarkm * and UNKNOWN for everything else. 18696845Smarkm */ 18796845Smarkmstatic int 18896845Smarkmdirectory_type(char *dir) 18996845Smarkm{ 19096845Smarkm char *p; 19196845Smarkm 19296845Smarkm for (;;) { 19396845Smarkm p = strrchr(dir, '/'); 19496845Smarkm if (p == NULL || p[1] != '\0') 19596845Smarkm break; 19696845Smarkm *p = '\0'; 19796845Smarkm } 19896845Smarkm if (p == NULL) 19996845Smarkm p = dir; 20096845Smarkm else 20196845Smarkm p++; 20296845Smarkm if (strncmp(p, "man", 3) == 0) { 20396845Smarkm p += 3; 20496845Smarkm if (*p == '\0') 20596845Smarkm return TOP_LEVEL_DIR; 206116137Sache while (isalnum((unsigned char)*p) || *p == '_') { 20796845Smarkm if (*++p == '\0') 20896845Smarkm return MAN_SECTION_DIR; 20996845Smarkm } 21096845Smarkm } 21196845Smarkm return UNKNOWN; 21296845Smarkm} 21396845Smarkm 21496845Smarkm/* 21596845Smarkm * Tests whether the given file name (without a preceding path) 21696845Smarkm * is a proper man page name (like "mk-amd-map.8.gz"). 21796845Smarkm * Only alphanumerics and '_' are allowed after the last '.' and 21896845Smarkm * the last '.' can't be the first or last characters. 21996845Smarkm */ 22096845Smarkmstatic int 22196845Smarkmis_manpage_name(char *name) 22296845Smarkm{ 22396845Smarkm char *lastdot = NULL; 22496845Smarkm char *n = name; 22596845Smarkm 22696845Smarkm while (*n != '\0') { 227116137Sache if (!isalnum((unsigned char)*n)) { 22896845Smarkm switch (*n) { 22996845Smarkm case '_': 23096845Smarkm break; 23196845Smarkm case '-': 23296845Smarkm case '+': 23396845Smarkm case '[': 23496845Smarkm case ':': 23596845Smarkm lastdot = NULL; 23696845Smarkm break; 23796845Smarkm case '.': 23896845Smarkm lastdot = n; 23996845Smarkm break; 24096845Smarkm default: 24196845Smarkm return 0; 24296845Smarkm } 24396845Smarkm } 24496845Smarkm n++; 24596845Smarkm } 24696845Smarkm return lastdot > name && lastdot + 1 < n; 24796845Smarkm} 24896845Smarkm 24996845Smarkmstatic int 250106120Sobrienis_bzipped(char *name) 251106120Sobrien{ 252106120Sobrien int len = strlen(name); 253106120Sobrien return len >= 5 && strcmp(&name[len - 4], BZ2_EXT) == 0; 254106120Sobrien} 255106120Sobrien 256106120Sobrienstatic int 25796845Smarkmis_gzipped(char *name) 25896845Smarkm{ 25996845Smarkm int len = strlen(name); 260106120Sobrien return len >= 4 && strcmp(&name[len - 3], GZ_EXT) == 0; 26196845Smarkm} 26296845Smarkm 26396845Smarkm/* 26496845Smarkm * Converts manXXX to catXXX. 26596845Smarkm */ 26696845Smarkmstatic char * 26796845Smarkmget_cat_section(char *section) 26896845Smarkm{ 26996845Smarkm char *cat_section; 27096845Smarkm 27196845Smarkm cat_section = strdup(section); 272300269Struckman assert(strlen(section) > 3 && strncmp(section, "man", 3) == 0); 273300269Struckman memcpy(cat_section, "cat", 3); 27496845Smarkm return cat_section; 27596845Smarkm} 27696845Smarkm 27796845Smarkm/* 27896845Smarkm * Tests to see if the given directory has already been visited. 27996845Smarkm */ 28096845Smarkmstatic int 28196845Smarkmalready_visited(char *mandir, char *dir, int count_visit) 28296845Smarkm{ 28396845Smarkm struct stat st; 28496845Smarkm 28596845Smarkm if (stat(dir, &st) < 0) { 28696845Smarkm if (mandir != NULL) 28796845Smarkm warn("%s/%s", mandir, dir); 28896845Smarkm else 28996845Smarkm warn("%s", dir); 29096845Smarkm exit_code = 1; 29196845Smarkm return 1; 29296845Smarkm } 29396845Smarkm if (find_hashtable(visited, st.st_ino, st.st_dev) != NULL) { 29496845Smarkm if (mandir != NULL) 29596845Smarkm warnx("already visited %s/%s", mandir, dir); 29696845Smarkm else 29796845Smarkm warnx("already visited %s", dir); 29896845Smarkm return 1; 29996845Smarkm } 30096845Smarkm if (count_visit) 30196845Smarkm insert_hashtable(visited, st.st_ino, st.st_dev, ""); 30296845Smarkm return 0; 30396845Smarkm} 30496845Smarkm 30596845Smarkm/* 30696845Smarkm * Returns a set of TEST_* bits describing a file's type and permissions. 30796845Smarkm * If mod_time isn't NULL, it will contain the file's modification time. 30896845Smarkm */ 30996845Smarkmstatic int 31096845Smarkmtest_path(char *name, time_t *mod_time) 31196845Smarkm{ 31296845Smarkm int result; 31396845Smarkm 31496845Smarkm if (stat(name, &test_st) < 0) 31596845Smarkm return 0; 31696845Smarkm result = TEST_EXISTS; 31796845Smarkm if (mod_time != NULL) 31896845Smarkm *mod_time = test_st.st_mtime; 31996845Smarkm if (S_ISDIR(test_st.st_mode)) 32096845Smarkm result |= TEST_DIR; 32196845Smarkm else if (S_ISREG(test_st.st_mode)) 32296845Smarkm result |= TEST_FILE; 323194493Sbrooks if (access(name, R_OK)) 32496845Smarkm result |= TEST_READABLE; 325194493Sbrooks if (access(name, W_OK)) 32696845Smarkm result |= TEST_WRITABLE; 32796845Smarkm return result; 32896845Smarkm} 32996845Smarkm 33096845Smarkm/* 33196845Smarkm * Checks whether a file is a symbolic link. 33296845Smarkm */ 33396845Smarkmstatic int 33496845Smarkmis_symlink(char *path) 33596845Smarkm{ 33696845Smarkm struct stat st; 33796845Smarkm 33896845Smarkm return lstat(path, &st) >= 0 && S_ISLNK(st.st_mode); 33996845Smarkm} 34096845Smarkm 34196845Smarkm/* 34296845Smarkm * Tests to see if the given directory can be written to. 34396845Smarkm */ 34496845Smarkmstatic void 34596845Smarkmcheck_writable(char *mandir) 34696845Smarkm{ 34796845Smarkm if (verbose && !(test_path(mandir, NULL) & TEST_WRITABLE)) 34896845Smarkm fprintf(stderr, "%s: not writable - will only be able to write to existing cat directories\n", mandir); 34996845Smarkm} 35096845Smarkm 35196845Smarkm/* 35296845Smarkm * If the directory exists, attempt to make it writable, otherwise 35396845Smarkm * attempt to create it. 35496845Smarkm */ 35596845Smarkmstatic int 35696845Smarkmmake_writable_dir(char *mandir, char *dir) 35796845Smarkm{ 35896845Smarkm int test; 35996845Smarkm 36096845Smarkm if ((test = test_path(dir, NULL)) != 0) { 36196845Smarkm if (!(test & TEST_WRITABLE) && chmod(dir, 0755) < 0) { 36296845Smarkm warn("%s/%s: chmod", mandir, dir); 36396845Smarkm exit_code = 1; 36496845Smarkm return 0; 36596845Smarkm } 36696845Smarkm } else { 36796845Smarkm if (verbose || pretend) 36896845Smarkm fprintf(stderr, "mkdir %s\n", dir); 36996845Smarkm if (!pretend) { 37096845Smarkm unlink(dir); 37196845Smarkm if (mkdir(dir, 0755) < 0) { 37296845Smarkm warn("%s/%s: mkdir", mandir, dir); 37396845Smarkm exit_code = 1; 37496845Smarkm return 0; 37596845Smarkm } 37696845Smarkm } 37796845Smarkm } 37896845Smarkm return 1; 37996845Smarkm} 38096845Smarkm 38196845Smarkm/* 38296845Smarkm * Processes a single man page source by using nroff to create 38396845Smarkm * the preformatted cat page. 38496845Smarkm */ 38596845Smarkmstatic void 386106120Sobrienprocess_page(char *mandir, char *src, char *cat, enum Ziptype zipped) 38796845Smarkm{ 38896845Smarkm int src_test, cat_test; 38996845Smarkm time_t src_mtime, cat_mtime; 39096845Smarkm char cmd[MAXPATHLEN]; 39196845Smarkm dev_t src_dev; 39296845Smarkm ino_t src_ino; 39396845Smarkm const char *link_name; 39496845Smarkm 39596845Smarkm src_test = test_path(src, &src_mtime); 39696845Smarkm if (!(src_test & (TEST_FILE|TEST_READABLE))) { 39796845Smarkm if (!(src_test & TEST_DIR)) { 39896845Smarkm warnx("%s/%s: unreadable", mandir, src); 39996845Smarkm exit_code = 1; 40096845Smarkm if (rm_junk && is_symlink(src)) 40196845Smarkm junk(mandir, src, "bogus symlink"); 40296845Smarkm } 40396845Smarkm return; 40496845Smarkm } 40596845Smarkm src_dev = test_st.st_dev; 40696845Smarkm src_ino = test_st.st_ino; 40796845Smarkm cat_test = test_path(cat, &cat_mtime); 40896845Smarkm if (cat_test & (TEST_FILE|TEST_READABLE)) { 40996845Smarkm if (!force && cat_mtime >= src_mtime) { 41096845Smarkm if (verbose) { 41196845Smarkm fprintf(stderr, "\t%s/%s: up to date\n", 41296845Smarkm mandir, src); 41396845Smarkm } 41496845Smarkm return; 41596845Smarkm } 41696845Smarkm } 41796845Smarkm /* 41896845Smarkm * Is the man page a link to one we've already processed? 41996845Smarkm */ 42096845Smarkm if ((link_name = find_hashtable(links, src_ino, src_dev)) != NULL) { 42196845Smarkm if (verbose || pretend) { 42296845Smarkm fprintf(stderr, "%slink %s -> %s\n", 42396845Smarkm verbose ? "\t" : "", cat, link_name); 42496845Smarkm } 425300269Struckman if (!pretend) { 426300269Struckman (void) unlink(cat); 427300269Struckman if (link(link_name, cat) < 0) 428300269Struckman warn("%s %s: link", link_name, cat); 429300269Struckman } 43096845Smarkm return; 43196845Smarkm } 43296845Smarkm insert_hashtable(links, src_ino, src_dev, strdup(cat)); 43396845Smarkm if (verbose || pretend) { 43496845Smarkm fprintf(stderr, "%sformat %s -> %s\n", 43596845Smarkm verbose ? "\t" : "", src, cat); 43696845Smarkm if (pretend) 43796845Smarkm return; 43896845Smarkm } 43996845Smarkm snprintf(tmp_file, sizeof tmp_file, "%s.tmp", cat); 44096845Smarkm snprintf(cmd, sizeof cmd, 441224657Suqs "%scat %s | tbl | nroff -c -T%s -man | %s > %s.tmp", 442106120Sobrien zipped == BZIP ? BZ2CAT_CMD : zipped == GZIP ? GZCAT_CMD : "", 443115207Sru src, nroff_device, 444115207Sru zipped == BZIP ? BZ2_CMD : zipped == GZIP ? GZ_CMD : "cat", 445115207Sru cat); 44696845Smarkm if (system(cmd) != 0) 44796845Smarkm err(1, "formatting pipeline"); 44896845Smarkm if (rename(tmp_file, cat) < 0) 44996845Smarkm warn("%s", cat); 45096845Smarkm tmp_file[0] = '\0'; 45196845Smarkm} 45296845Smarkm 45396845Smarkm/* 45496845Smarkm * Scan the man section directory for pages and process each one, 45596845Smarkm * then check for junk in the corresponding cat section. 45696845Smarkm */ 45796845Smarkmstatic void 45896845Smarkmscan_section(char *mandir, char *section, char *cat_section) 45996845Smarkm{ 46096845Smarkm struct dirent **entries; 46196845Smarkm char **expected = NULL; 46296845Smarkm int npages; 46396845Smarkm int nexpected = 0; 46496845Smarkm int i, e; 465106120Sobrien enum Ziptype zipped; 46696845Smarkm char *page_name; 46796845Smarkm char page_path[MAXPATHLEN]; 46896845Smarkm char cat_path[MAXPATHLEN]; 469106120Sobrien char zip_path[MAXPATHLEN]; 47096845Smarkm 47196845Smarkm /* 47296845Smarkm * scan the man section directory for pages 47396845Smarkm */ 47496845Smarkm npages = scandir(section, &entries, NULL, alphasort); 47596845Smarkm if (npages < 0) { 47696845Smarkm warn("%s/%s", mandir, section); 47796845Smarkm exit_code = 1; 47896845Smarkm return; 47996845Smarkm } 48096845Smarkm if (verbose || rm_junk) { 48196845Smarkm /* 48296845Smarkm * Maintain a list of all cat pages that should exist, 48396845Smarkm * corresponding to existing man pages. 48496845Smarkm */ 48596845Smarkm expected = (char **) calloc(npages, sizeof(char *)); 48696845Smarkm } 48796845Smarkm for (i = 0; i < npages; free(entries[i++])) { 48896845Smarkm page_name = entries[i]->d_name; 48996845Smarkm snprintf(page_path, sizeof page_path, "%s/%s", section, 49096845Smarkm page_name); 49196845Smarkm if (!is_manpage_name(page_name)) { 49296845Smarkm if (!(test_path(page_path, NULL) & TEST_DIR)) { 49396845Smarkm junk(mandir, page_path, 49496845Smarkm "invalid man page name"); 49596845Smarkm } 49696845Smarkm continue; 49796845Smarkm } 498106120Sobrien zipped = is_bzipped(page_name) ? BZIP : 499106120Sobrien is_gzipped(page_name) ? GZIP : NONE; 500106120Sobrien if (zipped != NONE) { 50196845Smarkm snprintf(cat_path, sizeof cat_path, "%s/%s", 50296845Smarkm cat_section, page_name); 50396845Smarkm if (expected != NULL) 50496845Smarkm expected[nexpected++] = strdup(page_name); 505106120Sobrien process_page(mandir, page_path, cat_path, zipped); 50696845Smarkm } else { 50796845Smarkm /* 50896845Smarkm * We've got an uncompressed man page, 50996845Smarkm * check to see if there's a (preferred) 51096845Smarkm * compressed one. 51196845Smarkm */ 512106120Sobrien snprintf(zip_path, sizeof zip_path, "%s%s", 513106120Sobrien page_path, GZ_EXT); 514106120Sobrien if (test_path(zip_path, NULL) != 0) { 51596845Smarkm junk(mandir, page_path, 516106120Sobrien "man page unused due to existing " GZ_EXT); 51796845Smarkm } else { 51896845Smarkm if (verbose) { 51996845Smarkm fprintf(stderr, 52096845Smarkm "warning, %s is uncompressed\n", 52196845Smarkm page_path); 52296845Smarkm } 523115207Sru snprintf(cat_path, sizeof cat_path, "%s/%s", 524115207Sru cat_section, page_name); 52596845Smarkm if (expected != NULL) { 52696845Smarkm asprintf(&expected[nexpected++], 527115207Sru "%s", page_name); 52896845Smarkm } 529106120Sobrien process_page(mandir, page_path, cat_path, NONE); 53096845Smarkm } 53196845Smarkm } 53296845Smarkm } 53396845Smarkm free(entries); 53496845Smarkm if (expected == NULL) 53596845Smarkm return; 53696845Smarkm /* 53796845Smarkm * scan cat sections for junk 53896845Smarkm */ 53996845Smarkm npages = scandir(cat_section, &entries, NULL, alphasort); 54096845Smarkm e = 0; 54196845Smarkm for (i = 0; i < npages; free(entries[i++])) { 54296845Smarkm const char *junk_reason; 54396845Smarkm int cmp = 1; 54496845Smarkm 54596845Smarkm page_name = entries[i]->d_name; 54696845Smarkm if (strcmp(page_name, ".") == 0 || strcmp(page_name, "..") == 0) 54796845Smarkm continue; 54896845Smarkm /* 54996845Smarkm * Keep the index into the expected cat page list 55096845Smarkm * ahead of the name we've found. 55196845Smarkm */ 55296845Smarkm while (e < nexpected && 55396845Smarkm (cmp = strcmp(page_name, expected[e])) > 0) 55496845Smarkm free(expected[e++]); 55596845Smarkm if (cmp == 0) 55696845Smarkm continue; 55796845Smarkm /* we have an unexpected page */ 558139182Sru snprintf(cat_path, sizeof cat_path, "%s/%s", cat_section, 559139182Sru page_name); 56096845Smarkm if (!is_manpage_name(page_name)) { 561139182Sru if (test_path(cat_path, NULL) & TEST_DIR) 562139182Sru continue; 56396845Smarkm junk_reason = "invalid cat page name"; 56496845Smarkm } else if (!is_gzipped(page_name) && e + 1 < nexpected && 56596845Smarkm strncmp(page_name, expected[e + 1], strlen(page_name)) == 0 && 56696845Smarkm strlen(expected[e + 1]) == strlen(page_name) + 3) { 567106120Sobrien junk_reason = "cat page unused due to existing " GZ_EXT; 56896845Smarkm } else 56996845Smarkm junk_reason = "cat page without man page"; 57096845Smarkm junk(mandir, cat_path, junk_reason); 57196845Smarkm } 57296845Smarkm free(entries); 57396845Smarkm while (e < nexpected) 57496845Smarkm free(expected[e++]); 57596845Smarkm free(expected); 57696845Smarkm} 57796845Smarkm 57896845Smarkm 57996845Smarkm/* 58096845Smarkm * Processes a single man section. 58196845Smarkm */ 58296845Smarkmstatic void 58396845Smarkmprocess_section(char *mandir, char *section) 58496845Smarkm{ 58596845Smarkm char *cat_section; 58696845Smarkm 58796845Smarkm if (already_visited(mandir, section, 1)) 58896845Smarkm return; 58996845Smarkm if (verbose) 59096845Smarkm fprintf(stderr, " section %s\n", section); 59196845Smarkm cat_section = get_cat_section(section); 59296845Smarkm if (make_writable_dir(mandir, cat_section)) 59396845Smarkm scan_section(mandir, section, cat_section); 594139182Sru free(cat_section); 59596845Smarkm} 59696845Smarkm 59796845Smarkmstatic int 598201512Skibselect_sections(const struct dirent *entry) 59996845Smarkm{ 600201512Skib char *name; 601201512Skib int ret; 602201512Skib 603201512Skib name = strdup(entry->d_name); 604201512Skib ret = directory_type(name) == MAN_SECTION_DIR; 605201512Skib free(name); 606201512Skib return (ret); 60796845Smarkm} 60896845Smarkm 60996845Smarkm/* 61096845Smarkm * Processes a single top-level man directory. If section isn't NULL, 61196845Smarkm * it will only process that section sub-directory, otherwise it will 61296845Smarkm * process all of them. 61396845Smarkm */ 61496845Smarkmstatic void 61596845Smarkmprocess_mandir(char *dir_name, char *section) 61696845Smarkm{ 617300269Struckman if (fchdir(starting_dir) < 0) 618300269Struckman err(1, "fchdir"); 61996845Smarkm if (already_visited(NULL, dir_name, section == NULL)) 62096845Smarkm return; 62196845Smarkm check_writable(dir_name); 62296845Smarkm if (verbose) 62396845Smarkm fprintf(stderr, "man directory %s\n", dir_name); 62496845Smarkm if (pretend) 62596845Smarkm fprintf(stderr, "cd %s\n", dir_name); 62696845Smarkm if (chdir(dir_name) < 0) { 62796845Smarkm warn("%s: chdir", dir_name); 62896845Smarkm exit_code = 1; 62996845Smarkm return; 63096845Smarkm } 63196845Smarkm if (section != NULL) { 63296845Smarkm process_section(dir_name, section); 63396845Smarkm } else { 63496845Smarkm struct dirent **entries; 635153115Sru char *machine_dir, *arch_dir; 63696845Smarkm int nsections; 63796845Smarkm int i; 63896845Smarkm 63996845Smarkm nsections = scandir(".", &entries, select_sections, alphasort); 64096845Smarkm if (nsections < 0) { 64196845Smarkm warn("%s", dir_name); 64296845Smarkm exit_code = 1; 64396845Smarkm return; 64496845Smarkm } 64596845Smarkm for (i = 0; i < nsections; i++) { 64696845Smarkm process_section(dir_name, entries[i]->d_name); 647139185Sru asprintf(&machine_dir, "%s/%s", entries[i]->d_name, 648139185Sru machine); 649139185Sru if (test_path(machine_dir, NULL) & TEST_DIR) 650139185Sru process_section(dir_name, machine_dir); 651139185Sru free(machine_dir); 652153115Sru if (strcmp(machine_arch, machine) != 0) { 653153115Sru asprintf(&arch_dir, "%s/%s", entries[i]->d_name, 654153115Sru machine_arch); 655153115Sru if (test_path(arch_dir, NULL) & TEST_DIR) 656153115Sru process_section(dir_name, arch_dir); 657153115Sru free(arch_dir); 658153115Sru } 65996845Smarkm free(entries[i]); 66096845Smarkm } 66196845Smarkm free(entries); 66296845Smarkm } 66396845Smarkm} 66496845Smarkm 66596845Smarkm/* 66696845Smarkm * Processes one argument, which may be a colon-separated list of 66796845Smarkm * directories. 66896845Smarkm */ 66996845Smarkmstatic void 67096845Smarkmprocess_argument(const char *arg) 67196845Smarkm{ 67296845Smarkm char *dir; 67396845Smarkm char *mandir; 674139182Sru char *section; 67596845Smarkm char *parg; 67696845Smarkm 67796845Smarkm parg = strdup(arg); 67896845Smarkm if (parg == NULL) 67996845Smarkm err(1, "out of memory"); 68096845Smarkm while ((dir = strsep(&parg, ":")) != NULL) { 68196845Smarkm switch (directory_type(dir)) { 68296845Smarkm case TOP_LEVEL_DIR: 68396845Smarkm if (locale != NULL) { 68496845Smarkm asprintf(&mandir, "%s/%s", dir, locale); 68596845Smarkm process_mandir(mandir, NULL); 68696845Smarkm free(mandir); 68796845Smarkm if (lang_locale != NULL) { 68896845Smarkm asprintf(&mandir, "%s/%s", dir, 68996845Smarkm lang_locale); 69096845Smarkm process_mandir(mandir, NULL); 69196845Smarkm free(mandir); 69296845Smarkm } 69396845Smarkm } else { 69496845Smarkm process_mandir(dir, NULL); 69596845Smarkm } 69696845Smarkm break; 69796845Smarkm case MAN_SECTION_DIR: { 698139182Sru mandir = strdup(dirname(dir)); 699139182Sru section = strdup(basename(dir)); 700139182Sru process_mandir(mandir, section); 701139182Sru free(mandir); 702139182Sru free(section); 70396845Smarkm break; 70496845Smarkm } 70596845Smarkm default: 70696845Smarkm warnx("%s: directory name not in proper man form", dir); 70796845Smarkm exit_code = 1; 70896845Smarkm } 70996845Smarkm } 71096845Smarkm free(parg); 71196845Smarkm} 71296845Smarkm 71396845Smarkmstatic void 71496845Smarkmdetermine_locale(void) 71596845Smarkm{ 71696845Smarkm char *sep; 71796845Smarkm 718116136Sache if ((locale = setlocale(LC_CTYPE, "")) == NULL) { 719116136Sache warnx("-L option used, but no locale found\n"); 72096845Smarkm return; 72196845Smarkm } 72296845Smarkm sep = strchr(locale, '_'); 723116136Sache if (sep != NULL && isupper((unsigned char)sep[1]) 724116136Sache && isupper((unsigned char)sep[2])) { 725139183Sru asprintf(&lang_locale, "%.*s%s", (int)(sep - locale), 726139183Sru locale, &sep[3]); 72796845Smarkm } 728116136Sache sep = nl_langinfo(CODESET); 729116136Sache if (sep != NULL && *sep != '\0' && strcmp(sep, "US-ASCII") != 0) { 73096845Smarkm int i; 73196845Smarkm 73296845Smarkm for (i = 0; locale_device[i] != NULL; i += 2) { 73396845Smarkm if (strcmp(sep, locale_device[i]) == 0) { 73496845Smarkm nroff_device = locale_device[i + 1]; 73596845Smarkm break; 73696845Smarkm } 73796845Smarkm } 73896845Smarkm } 739116136Sache if (verbose) { 740116136Sache if (lang_locale != NULL) 741116136Sache fprintf(stderr, "short locale is %s\n", lang_locale); 74296845Smarkm fprintf(stderr, "nroff device is %s\n", nroff_device); 743116136Sache } 74496845Smarkm} 74596845Smarkm 74696845Smarkmstatic void 74796845Smarkmusage(void) 74896845Smarkm{ 749146466Sru fprintf(stderr, "usage: %s [-fLnrv] [directories ...]\n", 750146466Sru getprogname()); 75196845Smarkm exit(1); 75296845Smarkm} 75396845Smarkm 75496845Smarkmint 75596845Smarkmmain(int argc, char **argv) 75696845Smarkm{ 75796845Smarkm int opt; 75896845Smarkm 759194548Sbrooks if ((uid = getuid()) == 0) { 760194548Sbrooks fprintf(stderr, "don't run %s as root, use:\n echo", argv[0]); 761194548Sbrooks for (optind = 0; optind < argc; optind++) { 762194548Sbrooks fprintf(stderr, " %s", argv[optind]); 763194548Sbrooks } 764194548Sbrooks fprintf(stderr, " | nice -5 su -m man\n"); 765194548Sbrooks exit(1); 766194548Sbrooks } 76796845Smarkm while ((opt = getopt(argc, argv, "vnfLrh")) != -1) { 76896845Smarkm switch (opt) { 76996845Smarkm case 'f': 77096845Smarkm force++; 77196845Smarkm break; 77296845Smarkm case 'L': 77396845Smarkm determine_locale(); 77496845Smarkm break; 77596845Smarkm case 'n': 77696845Smarkm pretend++; 77796845Smarkm break; 77896845Smarkm case 'r': 77996845Smarkm rm_junk++; 78096845Smarkm break; 78196845Smarkm case 'v': 78296845Smarkm verbose++; 78396845Smarkm break; 78496845Smarkm default: 78596845Smarkm usage(); 78696845Smarkm /* NOTREACHED */ 78796845Smarkm } 78896845Smarkm } 78996845Smarkm if ((starting_dir = open(".", 0)) < 0) { 79096845Smarkm err(1, "."); 79196845Smarkm } 79296845Smarkm umask(022); 79396845Smarkm signal(SIGINT, trap_signal); 79496845Smarkm signal(SIGHUP, trap_signal); 79596845Smarkm signal(SIGQUIT, trap_signal); 79696845Smarkm signal(SIGTERM, trap_signal); 797139185Sru 798153115Sru if ((machine = getenv("MACHINE")) == NULL) { 799153115Sru static struct utsname utsname; 800139185Sru 801153115Sru if (uname(&utsname) == -1) 802153115Sru err(1, "uname"); 803153115Sru machine = utsname.machine; 804153115Sru } 805153115Sru 806153115Sru if ((machine_arch = getenv("MACHINE_ARCH")) == NULL) 807153115Sru machine_arch = MACHINE_ARCH; 808153115Sru 80996845Smarkm if (optind == argc) { 81096845Smarkm const char *manpath = getenv("MANPATH"); 81196845Smarkm if (manpath == NULL) 81296845Smarkm manpath = DEFAULT_MANPATH; 81396845Smarkm process_argument(manpath); 81496845Smarkm } else { 81596845Smarkm while (optind < argc) 81696845Smarkm process_argument(argv[optind++]); 81796845Smarkm } 81896845Smarkm exit(exit_code); 81996845Smarkm} 820