catman.c revision 201512
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: head/usr.bin/catman/catman.c 201512 2010-01-04 15:40:17Z kib $"); 3196845Smarkm 3296845Smarkm#include <sys/types.h> 3396845Smarkm#include <sys/stat.h> 3496845Smarkm#include <sys/param.h> 35153115Sru#include <sys/utsname.h> 3696845Smarkm 3796845Smarkm#include <ctype.h> 3896845Smarkm#include <dirent.h> 3996845Smarkm#include <err.h> 4096845Smarkm#include <fcntl.h> 41116136Sache#include <locale.h> 42116136Sache#include <langinfo.h> 43139182Sru#include <libgen.h> 4496845Smarkm#include <stdio.h> 4596845Smarkm#include <stdlib.h> 4696845Smarkm#include <string.h> 4796845Smarkm#include <unistd.h> 4896845Smarkm 4996845Smarkm#define DEFAULT_MANPATH "/usr/share/man" 5096845Smarkm 5196845Smarkm#define TOP_LEVEL_DIR 0 /* signifies a top-level man directory */ 5296845Smarkm#define MAN_SECTION_DIR 1 /* signifies a man section directory */ 5396845Smarkm#define UNKNOWN 2 /* signifies an unclassifiable directory */ 5496845Smarkm 5596845Smarkm#define TEST_EXISTS 0x01 5696845Smarkm#define TEST_DIR 0x02 5796845Smarkm#define TEST_FILE 0x04 5896845Smarkm#define TEST_READABLE 0x08 5996845Smarkm#define TEST_WRITABLE 0x10 6096845Smarkm 6196845Smarkmstatic int verbose; /* -v flag: be verbose with warnings */ 6296845Smarkmstatic int pretend; /* -n, -p flags: print out what would be done 6396845Smarkm instead of actually doing it */ 6496845Smarkmstatic int force; /* -f flag: force overwriting all cat pages */ 6596845Smarkmstatic int rm_junk; /* -r flag: remove garbage pages */ 6696845Smarkmstatic char *locale; /* user's locale if -L is used */ 6796845Smarkmstatic char *lang_locale; /* short form of locale */ 68153115Srustatic const char *machine, *machine_arch; 6996845Smarkmstatic int exit_code; /* exit code to use when finished */ 7096845Smarkm 7196845Smarkm/* 7296845Smarkm * -T argument for nroff 7396845Smarkm */ 7496845Smarkmstatic const char *nroff_device = "ascii"; 7596845Smarkm 7696845Smarkm/* 7796845Smarkm * Mapping from locale to nroff device 7896845Smarkm */ 7996845Smarkmstatic const char *locale_device[] = { 8096845Smarkm "KOI8-R", "koi8-r", 8196845Smarkm "ISO8859-1", "latin1", 8296845Smarkm "ISO8859-15", "latin1", 8396845Smarkm NULL 8496845Smarkm}; 8596845Smarkm 86106120Sobrien#define BZ2_CMD "bzip2" 87106120Sobrien#define BZ2_EXT ".bz2" 88106120Sobrien#define BZ2CAT_CMD "bz" 89115207Sru#define GZ_CMD "gzip" 90106120Sobrien#define GZ_EXT ".gz" 91106120Sobrien#define GZCAT_CMD "z" 92106120Sobrienenum Ziptype {NONE, BZIP, GZIP}; 93106120Sobrien 94194548Sbrooksstatic uid_t uid; 9596845Smarkmstatic int starting_dir; 9696845Smarkmstatic char tmp_file[MAXPATHLEN]; 9796845Smarkmstruct stat test_st; 9896845Smarkm 9996845Smarkm/* 10096845Smarkm * A hashtable is an array of chains composed of this entry structure. 10196845Smarkm */ 10296845Smarkmstruct hash_entry { 10396845Smarkm ino_t inode_number; 10496845Smarkm dev_t device_number; 10596845Smarkm const char *data; 10696845Smarkm struct hash_entry *next; 10796845Smarkm}; 10896845Smarkm 10996845Smarkm#define HASHTABLE_ALLOC 16384 /* allocation for hashtable (power of 2) */ 11096845Smarkm#define HASH_MASK (HASHTABLE_ALLOC - 1) 11196845Smarkm 11296845Smarkmstatic struct hash_entry *visited[HASHTABLE_ALLOC]; 11396845Smarkmstatic struct hash_entry *links[HASHTABLE_ALLOC]; 11496845Smarkm 11596845Smarkm/* 11696845Smarkm * Inserts a string into a hashtable keyed by inode & device number. 11796845Smarkm */ 11896845Smarkmstatic void 11996845Smarkminsert_hashtable(struct hash_entry **table, 12096845Smarkm ino_t inode_number, 12196845Smarkm dev_t device_number, 12296845Smarkm const char *data) 12396845Smarkm{ 12496845Smarkm struct hash_entry *new_entry; 12596845Smarkm struct hash_entry **chain; 12696845Smarkm 12796845Smarkm new_entry = (struct hash_entry *) malloc(sizeof(struct hash_entry)); 12896845Smarkm if (new_entry == NULL) 12996845Smarkm err(1, "can't insert into hashtable"); 13096845Smarkm chain = &table[inode_number & HASH_MASK]; 13196845Smarkm new_entry->inode_number = inode_number; 13296845Smarkm new_entry->device_number = device_number; 13396845Smarkm new_entry->data = data; 13496845Smarkm new_entry->next = *chain; 13596845Smarkm *chain = new_entry; 13696845Smarkm} 13796845Smarkm 13896845Smarkm/* 13996845Smarkm * Finds a string in a hashtable keyed by inode & device number. 14096845Smarkm */ 14196845Smarkmstatic const char * 14296845Smarkmfind_hashtable(struct hash_entry **table, 14396845Smarkm ino_t inode_number, 14496845Smarkm dev_t device_number) 14596845Smarkm{ 14696845Smarkm struct hash_entry *chain; 14796845Smarkm 14896845Smarkm chain = table[inode_number & HASH_MASK]; 14996845Smarkm while (chain != NULL) { 15096845Smarkm if (chain->inode_number == inode_number && 15196845Smarkm chain->device_number == device_number) 15296845Smarkm return chain->data; 15396845Smarkm chain = chain->next; 15496845Smarkm } 15596845Smarkm return NULL; 15696845Smarkm} 15796845Smarkm 15896845Smarkmstatic void 15996845Smarkmtrap_signal(int sig __unused) 16096845Smarkm{ 16196845Smarkm if (tmp_file[0] != '\0') 16296845Smarkm unlink(tmp_file); 16396845Smarkm exit(1); 16496845Smarkm} 16596845Smarkm 16696845Smarkm/* 16796845Smarkm * Deals with junk files in the man or cat section directories. 16896845Smarkm */ 16996845Smarkmstatic void 17096845Smarkmjunk(const char *mandir, const char *name, const char *reason) 17196845Smarkm{ 17296845Smarkm if (verbose) 17396845Smarkm fprintf(stderr, "%s/%s: %s\n", mandir, name, reason); 17496845Smarkm if (rm_junk) { 17596845Smarkm fprintf(stderr, "rm %s/%s\n", mandir, name); 17696845Smarkm if (!pretend && unlink(name) < 0) 17796845Smarkm warn("%s/%s", mandir, name); 17896845Smarkm } 17996845Smarkm} 18096845Smarkm 18196845Smarkm/* 18296845Smarkm * Returns TOP_LEVEL_DIR for .../man, MAN_SECTION_DIR for .../manXXX, 18396845Smarkm * and UNKNOWN for everything else. 18496845Smarkm */ 18596845Smarkmstatic int 18696845Smarkmdirectory_type(char *dir) 18796845Smarkm{ 18896845Smarkm char *p; 18996845Smarkm 19096845Smarkm for (;;) { 19196845Smarkm p = strrchr(dir, '/'); 19296845Smarkm if (p == NULL || p[1] != '\0') 19396845Smarkm break; 19496845Smarkm *p = '\0'; 19596845Smarkm } 19696845Smarkm if (p == NULL) 19796845Smarkm p = dir; 19896845Smarkm else 19996845Smarkm p++; 20096845Smarkm if (strncmp(p, "man", 3) == 0) { 20196845Smarkm p += 3; 20296845Smarkm if (*p == '\0') 20396845Smarkm return TOP_LEVEL_DIR; 204116137Sache while (isalnum((unsigned char)*p) || *p == '_') { 20596845Smarkm if (*++p == '\0') 20696845Smarkm return MAN_SECTION_DIR; 20796845Smarkm } 20896845Smarkm } 20996845Smarkm return UNKNOWN; 21096845Smarkm} 21196845Smarkm 21296845Smarkm/* 21396845Smarkm * Tests whether the given file name (without a preceding path) 21496845Smarkm * is a proper man page name (like "mk-amd-map.8.gz"). 21596845Smarkm * Only alphanumerics and '_' are allowed after the last '.' and 21696845Smarkm * the last '.' can't be the first or last characters. 21796845Smarkm */ 21896845Smarkmstatic int 21996845Smarkmis_manpage_name(char *name) 22096845Smarkm{ 22196845Smarkm char *lastdot = NULL; 22296845Smarkm char *n = name; 22396845Smarkm 22496845Smarkm while (*n != '\0') { 225116137Sache if (!isalnum((unsigned char)*n)) { 22696845Smarkm switch (*n) { 22796845Smarkm case '_': 22896845Smarkm break; 22996845Smarkm case '-': 23096845Smarkm case '+': 23196845Smarkm case '[': 23296845Smarkm case ':': 23396845Smarkm lastdot = NULL; 23496845Smarkm break; 23596845Smarkm case '.': 23696845Smarkm lastdot = n; 23796845Smarkm break; 23896845Smarkm default: 23996845Smarkm return 0; 24096845Smarkm } 24196845Smarkm } 24296845Smarkm n++; 24396845Smarkm } 24496845Smarkm return lastdot > name && lastdot + 1 < n; 24596845Smarkm} 24696845Smarkm 24796845Smarkmstatic int 248106120Sobrienis_bzipped(char *name) 249106120Sobrien{ 250106120Sobrien int len = strlen(name); 251106120Sobrien return len >= 5 && strcmp(&name[len - 4], BZ2_EXT) == 0; 252106120Sobrien} 253106120Sobrien 254106120Sobrienstatic int 25596845Smarkmis_gzipped(char *name) 25696845Smarkm{ 25796845Smarkm int len = strlen(name); 258106120Sobrien return len >= 4 && strcmp(&name[len - 3], GZ_EXT) == 0; 25996845Smarkm} 26096845Smarkm 26196845Smarkm/* 26296845Smarkm * Converts manXXX to catXXX. 26396845Smarkm */ 26496845Smarkmstatic char * 26596845Smarkmget_cat_section(char *section) 26696845Smarkm{ 26796845Smarkm char *cat_section; 26896845Smarkm 26996845Smarkm cat_section = strdup(section); 27096845Smarkm strncpy(cat_section, "cat", 3); 27196845Smarkm return cat_section; 27296845Smarkm} 27396845Smarkm 27496845Smarkm/* 27596845Smarkm * Tests to see if the given directory has already been visited. 27696845Smarkm */ 27796845Smarkmstatic int 27896845Smarkmalready_visited(char *mandir, char *dir, int count_visit) 27996845Smarkm{ 28096845Smarkm struct stat st; 28196845Smarkm 28296845Smarkm if (stat(dir, &st) < 0) { 28396845Smarkm if (mandir != NULL) 28496845Smarkm warn("%s/%s", mandir, dir); 28596845Smarkm else 28696845Smarkm warn("%s", dir); 28796845Smarkm exit_code = 1; 28896845Smarkm return 1; 28996845Smarkm } 29096845Smarkm if (find_hashtable(visited, st.st_ino, st.st_dev) != NULL) { 29196845Smarkm if (mandir != NULL) 29296845Smarkm warnx("already visited %s/%s", mandir, dir); 29396845Smarkm else 29496845Smarkm warnx("already visited %s", dir); 29596845Smarkm return 1; 29696845Smarkm } 29796845Smarkm if (count_visit) 29896845Smarkm insert_hashtable(visited, st.st_ino, st.st_dev, ""); 29996845Smarkm return 0; 30096845Smarkm} 30196845Smarkm 30296845Smarkm/* 30396845Smarkm * Returns a set of TEST_* bits describing a file's type and permissions. 30496845Smarkm * If mod_time isn't NULL, it will contain the file's modification time. 30596845Smarkm */ 30696845Smarkmstatic int 30796845Smarkmtest_path(char *name, time_t *mod_time) 30896845Smarkm{ 30996845Smarkm int result; 31096845Smarkm 31196845Smarkm if (stat(name, &test_st) < 0) 31296845Smarkm return 0; 31396845Smarkm result = TEST_EXISTS; 31496845Smarkm if (mod_time != NULL) 31596845Smarkm *mod_time = test_st.st_mtime; 31696845Smarkm if (S_ISDIR(test_st.st_mode)) 31796845Smarkm result |= TEST_DIR; 31896845Smarkm else if (S_ISREG(test_st.st_mode)) 31996845Smarkm result |= TEST_FILE; 320194493Sbrooks if (access(name, R_OK)) 32196845Smarkm result |= TEST_READABLE; 322194493Sbrooks if (access(name, W_OK)) 32396845Smarkm result |= TEST_WRITABLE; 32496845Smarkm return result; 32596845Smarkm} 32696845Smarkm 32796845Smarkm/* 32896845Smarkm * Checks whether a file is a symbolic link. 32996845Smarkm */ 33096845Smarkmstatic int 33196845Smarkmis_symlink(char *path) 33296845Smarkm{ 33396845Smarkm struct stat st; 33496845Smarkm 33596845Smarkm return lstat(path, &st) >= 0 && S_ISLNK(st.st_mode); 33696845Smarkm} 33796845Smarkm 33896845Smarkm/* 33996845Smarkm * Tests to see if the given directory can be written to. 34096845Smarkm */ 34196845Smarkmstatic void 34296845Smarkmcheck_writable(char *mandir) 34396845Smarkm{ 34496845Smarkm if (verbose && !(test_path(mandir, NULL) & TEST_WRITABLE)) 34596845Smarkm fprintf(stderr, "%s: not writable - will only be able to write to existing cat directories\n", mandir); 34696845Smarkm} 34796845Smarkm 34896845Smarkm/* 34996845Smarkm * If the directory exists, attempt to make it writable, otherwise 35096845Smarkm * attempt to create it. 35196845Smarkm */ 35296845Smarkmstatic int 35396845Smarkmmake_writable_dir(char *mandir, char *dir) 35496845Smarkm{ 35596845Smarkm int test; 35696845Smarkm 35796845Smarkm if ((test = test_path(dir, NULL)) != 0) { 35896845Smarkm if (!(test & TEST_WRITABLE) && chmod(dir, 0755) < 0) { 35996845Smarkm warn("%s/%s: chmod", mandir, dir); 36096845Smarkm exit_code = 1; 36196845Smarkm return 0; 36296845Smarkm } 36396845Smarkm } else { 36496845Smarkm if (verbose || pretend) 36596845Smarkm fprintf(stderr, "mkdir %s\n", dir); 36696845Smarkm if (!pretend) { 36796845Smarkm unlink(dir); 36896845Smarkm if (mkdir(dir, 0755) < 0) { 36996845Smarkm warn("%s/%s: mkdir", mandir, dir); 37096845Smarkm exit_code = 1; 37196845Smarkm return 0; 37296845Smarkm } 37396845Smarkm } 37496845Smarkm } 37596845Smarkm return 1; 37696845Smarkm} 37796845Smarkm 37896845Smarkm/* 37996845Smarkm * Processes a single man page source by using nroff to create 38096845Smarkm * the preformatted cat page. 38196845Smarkm */ 38296845Smarkmstatic void 383106120Sobrienprocess_page(char *mandir, char *src, char *cat, enum Ziptype zipped) 38496845Smarkm{ 38596845Smarkm int src_test, cat_test; 38696845Smarkm time_t src_mtime, cat_mtime; 38796845Smarkm char cmd[MAXPATHLEN]; 38896845Smarkm dev_t src_dev; 38996845Smarkm ino_t src_ino; 39096845Smarkm const char *link_name; 39196845Smarkm 39296845Smarkm src_test = test_path(src, &src_mtime); 39396845Smarkm if (!(src_test & (TEST_FILE|TEST_READABLE))) { 39496845Smarkm if (!(src_test & TEST_DIR)) { 39596845Smarkm warnx("%s/%s: unreadable", mandir, src); 39696845Smarkm exit_code = 1; 39796845Smarkm if (rm_junk && is_symlink(src)) 39896845Smarkm junk(mandir, src, "bogus symlink"); 39996845Smarkm } 40096845Smarkm return; 40196845Smarkm } 40296845Smarkm src_dev = test_st.st_dev; 40396845Smarkm src_ino = test_st.st_ino; 40496845Smarkm cat_test = test_path(cat, &cat_mtime); 40596845Smarkm if (cat_test & (TEST_FILE|TEST_READABLE)) { 40696845Smarkm if (!force && cat_mtime >= src_mtime) { 40796845Smarkm if (verbose) { 40896845Smarkm fprintf(stderr, "\t%s/%s: up to date\n", 40996845Smarkm mandir, src); 41096845Smarkm } 41196845Smarkm return; 41296845Smarkm } 41396845Smarkm } 41496845Smarkm /* 41596845Smarkm * Is the man page a link to one we've already processed? 41696845Smarkm */ 41796845Smarkm if ((link_name = find_hashtable(links, src_ino, src_dev)) != NULL) { 41896845Smarkm if (verbose || pretend) { 41996845Smarkm fprintf(stderr, "%slink %s -> %s\n", 42096845Smarkm verbose ? "\t" : "", cat, link_name); 42196845Smarkm } 42296845Smarkm if (!pretend) 42396845Smarkm link(link_name, cat); 42496845Smarkm return; 42596845Smarkm } 42696845Smarkm insert_hashtable(links, src_ino, src_dev, strdup(cat)); 42796845Smarkm if (verbose || pretend) { 42896845Smarkm fprintf(stderr, "%sformat %s -> %s\n", 42996845Smarkm verbose ? "\t" : "", src, cat); 43096845Smarkm if (pretend) 43196845Smarkm return; 43296845Smarkm } 43396845Smarkm snprintf(tmp_file, sizeof tmp_file, "%s.tmp", cat); 43496845Smarkm snprintf(cmd, sizeof cmd, 435115207Sru "%scat %s | tbl | nroff -T%s -man | col | %s > %s.tmp", 436106120Sobrien zipped == BZIP ? BZ2CAT_CMD : zipped == GZIP ? GZCAT_CMD : "", 437115207Sru src, nroff_device, 438115207Sru zipped == BZIP ? BZ2_CMD : zipped == GZIP ? GZ_CMD : "cat", 439115207Sru cat); 44096845Smarkm if (system(cmd) != 0) 44196845Smarkm err(1, "formatting pipeline"); 44296845Smarkm if (rename(tmp_file, cat) < 0) 44396845Smarkm warn("%s", cat); 44496845Smarkm tmp_file[0] = '\0'; 44596845Smarkm} 44696845Smarkm 44796845Smarkm/* 44896845Smarkm * Scan the man section directory for pages and process each one, 44996845Smarkm * then check for junk in the corresponding cat section. 45096845Smarkm */ 45196845Smarkmstatic void 45296845Smarkmscan_section(char *mandir, char *section, char *cat_section) 45396845Smarkm{ 45496845Smarkm struct dirent **entries; 45596845Smarkm char **expected = NULL; 45696845Smarkm int npages; 45796845Smarkm int nexpected = 0; 45896845Smarkm int i, e; 459106120Sobrien enum Ziptype zipped; 46096845Smarkm char *page_name; 46196845Smarkm char page_path[MAXPATHLEN]; 46296845Smarkm char cat_path[MAXPATHLEN]; 463106120Sobrien char zip_path[MAXPATHLEN]; 46496845Smarkm 46596845Smarkm /* 46696845Smarkm * scan the man section directory for pages 46796845Smarkm */ 46896845Smarkm npages = scandir(section, &entries, NULL, alphasort); 46996845Smarkm if (npages < 0) { 47096845Smarkm warn("%s/%s", mandir, section); 47196845Smarkm exit_code = 1; 47296845Smarkm return; 47396845Smarkm } 47496845Smarkm if (verbose || rm_junk) { 47596845Smarkm /* 47696845Smarkm * Maintain a list of all cat pages that should exist, 47796845Smarkm * corresponding to existing man pages. 47896845Smarkm */ 47996845Smarkm expected = (char **) calloc(npages, sizeof(char *)); 48096845Smarkm } 48196845Smarkm for (i = 0; i < npages; free(entries[i++])) { 48296845Smarkm page_name = entries[i]->d_name; 48396845Smarkm snprintf(page_path, sizeof page_path, "%s/%s", section, 48496845Smarkm page_name); 48596845Smarkm if (!is_manpage_name(page_name)) { 48696845Smarkm if (!(test_path(page_path, NULL) & TEST_DIR)) { 48796845Smarkm junk(mandir, page_path, 48896845Smarkm "invalid man page name"); 48996845Smarkm } 49096845Smarkm continue; 49196845Smarkm } 492106120Sobrien zipped = is_bzipped(page_name) ? BZIP : 493106120Sobrien is_gzipped(page_name) ? GZIP : NONE; 494106120Sobrien if (zipped != NONE) { 49596845Smarkm snprintf(cat_path, sizeof cat_path, "%s/%s", 49696845Smarkm cat_section, page_name); 49796845Smarkm if (expected != NULL) 49896845Smarkm expected[nexpected++] = strdup(page_name); 499106120Sobrien process_page(mandir, page_path, cat_path, zipped); 50096845Smarkm } else { 50196845Smarkm /* 50296845Smarkm * We've got an uncompressed man page, 50396845Smarkm * check to see if there's a (preferred) 50496845Smarkm * compressed one. 50596845Smarkm */ 506106120Sobrien snprintf(zip_path, sizeof zip_path, "%s%s", 507106120Sobrien page_path, GZ_EXT); 508106120Sobrien if (test_path(zip_path, NULL) != 0) { 50996845Smarkm junk(mandir, page_path, 510106120Sobrien "man page unused due to existing " GZ_EXT); 51196845Smarkm } else { 51296845Smarkm if (verbose) { 51396845Smarkm fprintf(stderr, 51496845Smarkm "warning, %s is uncompressed\n", 51596845Smarkm page_path); 51696845Smarkm } 517115207Sru snprintf(cat_path, sizeof cat_path, "%s/%s", 518115207Sru cat_section, page_name); 51996845Smarkm if (expected != NULL) { 52096845Smarkm asprintf(&expected[nexpected++], 521115207Sru "%s", page_name); 52296845Smarkm } 523106120Sobrien process_page(mandir, page_path, cat_path, NONE); 52496845Smarkm } 52596845Smarkm } 52696845Smarkm } 52796845Smarkm free(entries); 52896845Smarkm if (expected == NULL) 52996845Smarkm return; 53096845Smarkm /* 53196845Smarkm * scan cat sections for junk 53296845Smarkm */ 53396845Smarkm npages = scandir(cat_section, &entries, NULL, alphasort); 53496845Smarkm e = 0; 53596845Smarkm for (i = 0; i < npages; free(entries[i++])) { 53696845Smarkm const char *junk_reason; 53796845Smarkm int cmp = 1; 53896845Smarkm 53996845Smarkm page_name = entries[i]->d_name; 54096845Smarkm if (strcmp(page_name, ".") == 0 || strcmp(page_name, "..") == 0) 54196845Smarkm continue; 54296845Smarkm /* 54396845Smarkm * Keep the index into the expected cat page list 54496845Smarkm * ahead of the name we've found. 54596845Smarkm */ 54696845Smarkm while (e < nexpected && 54796845Smarkm (cmp = strcmp(page_name, expected[e])) > 0) 54896845Smarkm free(expected[e++]); 54996845Smarkm if (cmp == 0) 55096845Smarkm continue; 55196845Smarkm /* we have an unexpected page */ 552139182Sru snprintf(cat_path, sizeof cat_path, "%s/%s", cat_section, 553139182Sru page_name); 55496845Smarkm if (!is_manpage_name(page_name)) { 555139182Sru if (test_path(cat_path, NULL) & TEST_DIR) 556139182Sru continue; 55796845Smarkm junk_reason = "invalid cat page name"; 55896845Smarkm } else if (!is_gzipped(page_name) && e + 1 < nexpected && 55996845Smarkm strncmp(page_name, expected[e + 1], strlen(page_name)) == 0 && 56096845Smarkm strlen(expected[e + 1]) == strlen(page_name) + 3) { 561106120Sobrien junk_reason = "cat page unused due to existing " GZ_EXT; 56296845Smarkm } else 56396845Smarkm junk_reason = "cat page without man page"; 56496845Smarkm junk(mandir, cat_path, junk_reason); 56596845Smarkm } 56696845Smarkm free(entries); 56796845Smarkm while (e < nexpected) 56896845Smarkm free(expected[e++]); 56996845Smarkm free(expected); 57096845Smarkm} 57196845Smarkm 57296845Smarkm 57396845Smarkm/* 57496845Smarkm * Processes a single man section. 57596845Smarkm */ 57696845Smarkmstatic void 57796845Smarkmprocess_section(char *mandir, char *section) 57896845Smarkm{ 57996845Smarkm char *cat_section; 58096845Smarkm 58196845Smarkm if (already_visited(mandir, section, 1)) 58296845Smarkm return; 58396845Smarkm if (verbose) 58496845Smarkm fprintf(stderr, " section %s\n", section); 58596845Smarkm cat_section = get_cat_section(section); 58696845Smarkm if (make_writable_dir(mandir, cat_section)) 58796845Smarkm scan_section(mandir, section, cat_section); 588139182Sru free(cat_section); 58996845Smarkm} 59096845Smarkm 59196845Smarkmstatic int 592201512Skibselect_sections(const struct dirent *entry) 59396845Smarkm{ 594201512Skib char *name; 595201512Skib int ret; 596201512Skib 597201512Skib name = strdup(entry->d_name); 598201512Skib ret = directory_type(name) == MAN_SECTION_DIR; 599201512Skib free(name); 600201512Skib return (ret); 60196845Smarkm} 60296845Smarkm 60396845Smarkm/* 60496845Smarkm * Processes a single top-level man directory. If section isn't NULL, 60596845Smarkm * it will only process that section sub-directory, otherwise it will 60696845Smarkm * process all of them. 60796845Smarkm */ 60896845Smarkmstatic void 60996845Smarkmprocess_mandir(char *dir_name, char *section) 61096845Smarkm{ 61196845Smarkm fchdir(starting_dir); 61296845Smarkm if (already_visited(NULL, dir_name, section == NULL)) 61396845Smarkm return; 61496845Smarkm check_writable(dir_name); 61596845Smarkm if (verbose) 61696845Smarkm fprintf(stderr, "man directory %s\n", dir_name); 61796845Smarkm if (pretend) 61896845Smarkm fprintf(stderr, "cd %s\n", dir_name); 61996845Smarkm if (chdir(dir_name) < 0) { 62096845Smarkm warn("%s: chdir", dir_name); 62196845Smarkm exit_code = 1; 62296845Smarkm return; 62396845Smarkm } 62496845Smarkm if (section != NULL) { 62596845Smarkm process_section(dir_name, section); 62696845Smarkm } else { 62796845Smarkm struct dirent **entries; 628153115Sru char *machine_dir, *arch_dir; 62996845Smarkm int nsections; 63096845Smarkm int i; 63196845Smarkm 63296845Smarkm nsections = scandir(".", &entries, select_sections, alphasort); 63396845Smarkm if (nsections < 0) { 63496845Smarkm warn("%s", dir_name); 63596845Smarkm exit_code = 1; 63696845Smarkm return; 63796845Smarkm } 63896845Smarkm for (i = 0; i < nsections; i++) { 63996845Smarkm process_section(dir_name, entries[i]->d_name); 640139185Sru asprintf(&machine_dir, "%s/%s", entries[i]->d_name, 641139185Sru machine); 642139185Sru if (test_path(machine_dir, NULL) & TEST_DIR) 643139185Sru process_section(dir_name, machine_dir); 644139185Sru free(machine_dir); 645153115Sru if (strcmp(machine_arch, machine) != 0) { 646153115Sru asprintf(&arch_dir, "%s/%s", entries[i]->d_name, 647153115Sru machine_arch); 648153115Sru if (test_path(arch_dir, NULL) & TEST_DIR) 649153115Sru process_section(dir_name, arch_dir); 650153115Sru free(arch_dir); 651153115Sru } 65296845Smarkm free(entries[i]); 65396845Smarkm } 65496845Smarkm free(entries); 65596845Smarkm } 65696845Smarkm} 65796845Smarkm 65896845Smarkm/* 65996845Smarkm * Processes one argument, which may be a colon-separated list of 66096845Smarkm * directories. 66196845Smarkm */ 66296845Smarkmstatic void 66396845Smarkmprocess_argument(const char *arg) 66496845Smarkm{ 66596845Smarkm char *dir; 66696845Smarkm char *mandir; 667139182Sru char *section; 66896845Smarkm char *parg; 66996845Smarkm 67096845Smarkm parg = strdup(arg); 67196845Smarkm if (parg == NULL) 67296845Smarkm err(1, "out of memory"); 67396845Smarkm while ((dir = strsep(&parg, ":")) != NULL) { 67496845Smarkm switch (directory_type(dir)) { 67596845Smarkm case TOP_LEVEL_DIR: 67696845Smarkm if (locale != NULL) { 67796845Smarkm asprintf(&mandir, "%s/%s", dir, locale); 67896845Smarkm process_mandir(mandir, NULL); 67996845Smarkm free(mandir); 68096845Smarkm if (lang_locale != NULL) { 68196845Smarkm asprintf(&mandir, "%s/%s", dir, 68296845Smarkm lang_locale); 68396845Smarkm process_mandir(mandir, NULL); 68496845Smarkm free(mandir); 68596845Smarkm } 68696845Smarkm } else { 68796845Smarkm process_mandir(dir, NULL); 68896845Smarkm } 68996845Smarkm break; 69096845Smarkm case MAN_SECTION_DIR: { 691139182Sru mandir = strdup(dirname(dir)); 692139182Sru section = strdup(basename(dir)); 693139182Sru process_mandir(mandir, section); 694139182Sru free(mandir); 695139182Sru free(section); 69696845Smarkm break; 69796845Smarkm } 69896845Smarkm default: 69996845Smarkm warnx("%s: directory name not in proper man form", dir); 70096845Smarkm exit_code = 1; 70196845Smarkm } 70296845Smarkm } 70396845Smarkm free(parg); 70496845Smarkm} 70596845Smarkm 70696845Smarkmstatic void 70796845Smarkmdetermine_locale(void) 70896845Smarkm{ 70996845Smarkm char *sep; 71096845Smarkm 711116136Sache if ((locale = setlocale(LC_CTYPE, "")) == NULL) { 712116136Sache warnx("-L option used, but no locale found\n"); 71396845Smarkm return; 71496845Smarkm } 71596845Smarkm sep = strchr(locale, '_'); 716116136Sache if (sep != NULL && isupper((unsigned char)sep[1]) 717116136Sache && isupper((unsigned char)sep[2])) { 718139183Sru asprintf(&lang_locale, "%.*s%s", (int)(sep - locale), 719139183Sru locale, &sep[3]); 72096845Smarkm } 721116136Sache sep = nl_langinfo(CODESET); 722116136Sache if (sep != NULL && *sep != '\0' && strcmp(sep, "US-ASCII") != 0) { 72396845Smarkm int i; 72496845Smarkm 72596845Smarkm for (i = 0; locale_device[i] != NULL; i += 2) { 72696845Smarkm if (strcmp(sep, locale_device[i]) == 0) { 72796845Smarkm nroff_device = locale_device[i + 1]; 72896845Smarkm break; 72996845Smarkm } 73096845Smarkm } 73196845Smarkm } 732116136Sache if (verbose) { 733116136Sache if (lang_locale != NULL) 734116136Sache fprintf(stderr, "short locale is %s\n", lang_locale); 73596845Smarkm fprintf(stderr, "nroff device is %s\n", nroff_device); 736116136Sache } 73796845Smarkm} 73896845Smarkm 73996845Smarkmstatic void 74096845Smarkmusage(void) 74196845Smarkm{ 742146466Sru fprintf(stderr, "usage: %s [-fLnrv] [directories ...]\n", 743146466Sru getprogname()); 74496845Smarkm exit(1); 74596845Smarkm} 74696845Smarkm 74796845Smarkmint 74896845Smarkmmain(int argc, char **argv) 74996845Smarkm{ 75096845Smarkm int opt; 75196845Smarkm 752194548Sbrooks if ((uid = getuid()) == 0) { 753194548Sbrooks fprintf(stderr, "don't run %s as root, use:\n echo", argv[0]); 754194548Sbrooks for (optind = 0; optind < argc; optind++) { 755194548Sbrooks fprintf(stderr, " %s", argv[optind]); 756194548Sbrooks } 757194548Sbrooks fprintf(stderr, " | nice -5 su -m man\n"); 758194548Sbrooks exit(1); 759194548Sbrooks } 76096845Smarkm while ((opt = getopt(argc, argv, "vnfLrh")) != -1) { 76196845Smarkm switch (opt) { 76296845Smarkm case 'f': 76396845Smarkm force++; 76496845Smarkm break; 76596845Smarkm case 'L': 76696845Smarkm determine_locale(); 76796845Smarkm break; 76896845Smarkm case 'n': 76996845Smarkm pretend++; 77096845Smarkm break; 77196845Smarkm case 'r': 77296845Smarkm rm_junk++; 77396845Smarkm break; 77496845Smarkm case 'v': 77596845Smarkm verbose++; 77696845Smarkm break; 77796845Smarkm default: 77896845Smarkm usage(); 77996845Smarkm /* NOTREACHED */ 78096845Smarkm } 78196845Smarkm } 78296845Smarkm if ((starting_dir = open(".", 0)) < 0) { 78396845Smarkm err(1, "."); 78496845Smarkm } 78596845Smarkm umask(022); 78696845Smarkm signal(SIGINT, trap_signal); 78796845Smarkm signal(SIGHUP, trap_signal); 78896845Smarkm signal(SIGQUIT, trap_signal); 78996845Smarkm signal(SIGTERM, trap_signal); 790139185Sru 791153115Sru if ((machine = getenv("MACHINE")) == NULL) { 792153115Sru static struct utsname utsname; 793139185Sru 794153115Sru if (uname(&utsname) == -1) 795153115Sru err(1, "uname"); 796153115Sru machine = utsname.machine; 797153115Sru } 798153115Sru 799153115Sru if ((machine_arch = getenv("MACHINE_ARCH")) == NULL) 800153115Sru machine_arch = MACHINE_ARCH; 801153115Sru 80296845Smarkm if (optind == argc) { 80396845Smarkm const char *manpath = getenv("MANPATH"); 80496845Smarkm if (manpath == NULL) 80596845Smarkm manpath = DEFAULT_MANPATH; 80696845Smarkm process_argument(manpath); 80796845Smarkm } else { 80896845Smarkm while (optind < argc) 80996845Smarkm process_argument(argv[optind++]); 81096845Smarkm } 81196845Smarkm exit(exit_code); 81296845Smarkm} 813