catman.c revision 153115
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 153115 2005-12-05 14:22:12Z ru $"); 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#define TEST_EXECUTABLE 0x20 6196845Smarkm 6296845Smarkmstatic int verbose; /* -v flag: be verbose with warnings */ 6396845Smarkmstatic int pretend; /* -n, -p flags: print out what would be done 6496845Smarkm instead of actually doing it */ 6596845Smarkmstatic int force; /* -f flag: force overwriting all cat pages */ 6696845Smarkmstatic int rm_junk; /* -r flag: remove garbage pages */ 6796845Smarkmstatic char *locale; /* user's locale if -L is used */ 6896845Smarkmstatic char *lang_locale; /* short form of locale */ 69153115Srustatic const char *machine, *machine_arch; 7096845Smarkmstatic int exit_code; /* exit code to use when finished */ 7196845Smarkm 7296845Smarkm/* 7396845Smarkm * -T argument for nroff 7496845Smarkm */ 7596845Smarkmstatic const char *nroff_device = "ascii"; 7696845Smarkm 7796845Smarkm/* 7896845Smarkm * Mapping from locale to nroff device 7996845Smarkm */ 8096845Smarkmstatic const char *locale_device[] = { 8196845Smarkm "KOI8-R", "koi8-r", 8296845Smarkm "ISO8859-1", "latin1", 8396845Smarkm "ISO8859-15", "latin1", 8496845Smarkm NULL 8596845Smarkm}; 8696845Smarkm 87106120Sobrien#define BZ2_CMD "bzip2" 88106120Sobrien#define BZ2_EXT ".bz2" 89106120Sobrien#define BZ2CAT_CMD "bz" 90115207Sru#define GZ_CMD "gzip" 91106120Sobrien#define GZ_EXT ".gz" 92106120Sobrien#define GZCAT_CMD "z" 93106120Sobrienenum Ziptype {NONE, BZIP, GZIP}; 94106120Sobrien 9596845Smarkmstatic uid_t uid; 9696845Smarkmstatic gid_t gids[NGROUPS_MAX]; 9796845Smarkmstatic int ngids; 9896845Smarkmstatic int starting_dir; 9996845Smarkmstatic char tmp_file[MAXPATHLEN]; 10096845Smarkmstruct stat test_st; 10196845Smarkm 10296845Smarkm/* 10396845Smarkm * A hashtable is an array of chains composed of this entry structure. 10496845Smarkm */ 10596845Smarkmstruct hash_entry { 10696845Smarkm ino_t inode_number; 10796845Smarkm dev_t device_number; 10896845Smarkm const char *data; 10996845Smarkm struct hash_entry *next; 11096845Smarkm}; 11196845Smarkm 11296845Smarkm#define HASHTABLE_ALLOC 16384 /* allocation for hashtable (power of 2) */ 11396845Smarkm#define HASH_MASK (HASHTABLE_ALLOC - 1) 11496845Smarkm 11596845Smarkmstatic struct hash_entry *visited[HASHTABLE_ALLOC]; 11696845Smarkmstatic struct hash_entry *links[HASHTABLE_ALLOC]; 11796845Smarkm 11896845Smarkm/* 11996845Smarkm * Inserts a string into a hashtable keyed by inode & device number. 12096845Smarkm */ 12196845Smarkmstatic void 12296845Smarkminsert_hashtable(struct hash_entry **table, 12396845Smarkm ino_t inode_number, 12496845Smarkm dev_t device_number, 12596845Smarkm const char *data) 12696845Smarkm{ 12796845Smarkm struct hash_entry *new_entry; 12896845Smarkm struct hash_entry **chain; 12996845Smarkm 13096845Smarkm new_entry = (struct hash_entry *) malloc(sizeof(struct hash_entry)); 13196845Smarkm if (new_entry == NULL) 13296845Smarkm err(1, "can't insert into hashtable"); 13396845Smarkm chain = &table[inode_number & HASH_MASK]; 13496845Smarkm new_entry->inode_number = inode_number; 13596845Smarkm new_entry->device_number = device_number; 13696845Smarkm new_entry->data = data; 13796845Smarkm new_entry->next = *chain; 13896845Smarkm *chain = new_entry; 13996845Smarkm} 14096845Smarkm 14196845Smarkm/* 14296845Smarkm * Finds a string in a hashtable keyed by inode & device number. 14396845Smarkm */ 14496845Smarkmstatic const char * 14596845Smarkmfind_hashtable(struct hash_entry **table, 14696845Smarkm ino_t inode_number, 14796845Smarkm dev_t device_number) 14896845Smarkm{ 14996845Smarkm struct hash_entry *chain; 15096845Smarkm 15196845Smarkm chain = table[inode_number & HASH_MASK]; 15296845Smarkm while (chain != NULL) { 15396845Smarkm if (chain->inode_number == inode_number && 15496845Smarkm chain->device_number == device_number) 15596845Smarkm return chain->data; 15696845Smarkm chain = chain->next; 15796845Smarkm } 15896845Smarkm return NULL; 15996845Smarkm} 16096845Smarkm 16196845Smarkmstatic void 16296845Smarkmtrap_signal(int sig __unused) 16396845Smarkm{ 16496845Smarkm if (tmp_file[0] != '\0') 16596845Smarkm unlink(tmp_file); 16696845Smarkm exit(1); 16796845Smarkm} 16896845Smarkm 16996845Smarkm/* 17096845Smarkm * Deals with junk files in the man or cat section directories. 17196845Smarkm */ 17296845Smarkmstatic void 17396845Smarkmjunk(const char *mandir, const char *name, const char *reason) 17496845Smarkm{ 17596845Smarkm if (verbose) 17696845Smarkm fprintf(stderr, "%s/%s: %s\n", mandir, name, reason); 17796845Smarkm if (rm_junk) { 17896845Smarkm fprintf(stderr, "rm %s/%s\n", mandir, name); 17996845Smarkm if (!pretend && unlink(name) < 0) 18096845Smarkm warn("%s/%s", mandir, name); 18196845Smarkm } 18296845Smarkm} 18396845Smarkm 18496845Smarkm/* 18596845Smarkm * Returns TOP_LEVEL_DIR for .../man, MAN_SECTION_DIR for .../manXXX, 18696845Smarkm * and UNKNOWN for everything else. 18796845Smarkm */ 18896845Smarkmstatic int 18996845Smarkmdirectory_type(char *dir) 19096845Smarkm{ 19196845Smarkm char *p; 19296845Smarkm 19396845Smarkm for (;;) { 19496845Smarkm p = strrchr(dir, '/'); 19596845Smarkm if (p == NULL || p[1] != '\0') 19696845Smarkm break; 19796845Smarkm *p = '\0'; 19896845Smarkm } 19996845Smarkm if (p == NULL) 20096845Smarkm p = dir; 20196845Smarkm else 20296845Smarkm p++; 20396845Smarkm if (strncmp(p, "man", 3) == 0) { 20496845Smarkm p += 3; 20596845Smarkm if (*p == '\0') 20696845Smarkm return TOP_LEVEL_DIR; 207116137Sache while (isalnum((unsigned char)*p) || *p == '_') { 20896845Smarkm if (*++p == '\0') 20996845Smarkm return MAN_SECTION_DIR; 21096845Smarkm } 21196845Smarkm } 21296845Smarkm return UNKNOWN; 21396845Smarkm} 21496845Smarkm 21596845Smarkm/* 21696845Smarkm * Tests whether the given file name (without a preceding path) 21796845Smarkm * is a proper man page name (like "mk-amd-map.8.gz"). 21896845Smarkm * Only alphanumerics and '_' are allowed after the last '.' and 21996845Smarkm * the last '.' can't be the first or last characters. 22096845Smarkm */ 22196845Smarkmstatic int 22296845Smarkmis_manpage_name(char *name) 22396845Smarkm{ 22496845Smarkm char *lastdot = NULL; 22596845Smarkm char *n = name; 22696845Smarkm 22796845Smarkm while (*n != '\0') { 228116137Sache if (!isalnum((unsigned char)*n)) { 22996845Smarkm switch (*n) { 23096845Smarkm case '_': 23196845Smarkm break; 23296845Smarkm case '-': 23396845Smarkm case '+': 23496845Smarkm case '[': 23596845Smarkm case ':': 23696845Smarkm lastdot = NULL; 23796845Smarkm break; 23896845Smarkm case '.': 23996845Smarkm lastdot = n; 24096845Smarkm break; 24196845Smarkm default: 24296845Smarkm return 0; 24396845Smarkm } 24496845Smarkm } 24596845Smarkm n++; 24696845Smarkm } 24796845Smarkm return lastdot > name && lastdot + 1 < n; 24896845Smarkm} 24996845Smarkm 25096845Smarkmstatic int 251106120Sobrienis_bzipped(char *name) 252106120Sobrien{ 253106120Sobrien int len = strlen(name); 254106120Sobrien return len >= 5 && strcmp(&name[len - 4], BZ2_EXT) == 0; 255106120Sobrien} 256106120Sobrien 257106120Sobrienstatic int 25896845Smarkmis_gzipped(char *name) 25996845Smarkm{ 26096845Smarkm int len = strlen(name); 261106120Sobrien return len >= 4 && strcmp(&name[len - 3], GZ_EXT) == 0; 26296845Smarkm} 26396845Smarkm 26496845Smarkm/* 26596845Smarkm * Converts manXXX to catXXX. 26696845Smarkm */ 26796845Smarkmstatic char * 26896845Smarkmget_cat_section(char *section) 26996845Smarkm{ 27096845Smarkm char *cat_section; 27196845Smarkm 27296845Smarkm cat_section = strdup(section); 27396845Smarkm strncpy(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; 32396845Smarkm if (test_st.st_uid == uid) { 32496845Smarkm test_st.st_mode >>= 6; 32596845Smarkm } else { 32696845Smarkm int i; 32796845Smarkm for (i = 0; i < ngids; i++) { 32896845Smarkm if (test_st.st_gid == gids[i]) { 32996845Smarkm test_st.st_mode >>= 3; 33096845Smarkm break; 33196845Smarkm } 33296845Smarkm } 33396845Smarkm } 33496845Smarkm if (test_st.st_mode & S_IROTH) 33596845Smarkm result |= TEST_READABLE; 33696845Smarkm if (test_st.st_mode & S_IWOTH) 33796845Smarkm result |= TEST_WRITABLE; 33896845Smarkm if (test_st.st_mode & S_IXOTH) 33996845Smarkm result |= TEST_EXECUTABLE; 34096845Smarkm return result; 34196845Smarkm} 34296845Smarkm 34396845Smarkm/* 34496845Smarkm * Checks whether a file is a symbolic link. 34596845Smarkm */ 34696845Smarkmstatic int 34796845Smarkmis_symlink(char *path) 34896845Smarkm{ 34996845Smarkm struct stat st; 35096845Smarkm 35196845Smarkm return lstat(path, &st) >= 0 && S_ISLNK(st.st_mode); 35296845Smarkm} 35396845Smarkm 35496845Smarkm/* 35596845Smarkm * Tests to see if the given directory can be written to. 35696845Smarkm */ 35796845Smarkmstatic void 35896845Smarkmcheck_writable(char *mandir) 35996845Smarkm{ 36096845Smarkm if (verbose && !(test_path(mandir, NULL) & TEST_WRITABLE)) 36196845Smarkm fprintf(stderr, "%s: not writable - will only be able to write to existing cat directories\n", mandir); 36296845Smarkm} 36396845Smarkm 36496845Smarkm/* 36596845Smarkm * If the directory exists, attempt to make it writable, otherwise 36696845Smarkm * attempt to create it. 36796845Smarkm */ 36896845Smarkmstatic int 36996845Smarkmmake_writable_dir(char *mandir, char *dir) 37096845Smarkm{ 37196845Smarkm int test; 37296845Smarkm 37396845Smarkm if ((test = test_path(dir, NULL)) != 0) { 37496845Smarkm if (!(test & TEST_WRITABLE) && chmod(dir, 0755) < 0) { 37596845Smarkm warn("%s/%s: chmod", mandir, dir); 37696845Smarkm exit_code = 1; 37796845Smarkm return 0; 37896845Smarkm } 37996845Smarkm } else { 38096845Smarkm if (verbose || pretend) 38196845Smarkm fprintf(stderr, "mkdir %s\n", dir); 38296845Smarkm if (!pretend) { 38396845Smarkm unlink(dir); 38496845Smarkm if (mkdir(dir, 0755) < 0) { 38596845Smarkm warn("%s/%s: mkdir", mandir, dir); 38696845Smarkm exit_code = 1; 38796845Smarkm return 0; 38896845Smarkm } 38996845Smarkm } 39096845Smarkm } 39196845Smarkm return 1; 39296845Smarkm} 39396845Smarkm 39496845Smarkm/* 39596845Smarkm * Processes a single man page source by using nroff to create 39696845Smarkm * the preformatted cat page. 39796845Smarkm */ 39896845Smarkmstatic void 399106120Sobrienprocess_page(char *mandir, char *src, char *cat, enum Ziptype zipped) 40096845Smarkm{ 40196845Smarkm int src_test, cat_test; 40296845Smarkm time_t src_mtime, cat_mtime; 40396845Smarkm char cmd[MAXPATHLEN]; 40496845Smarkm dev_t src_dev; 40596845Smarkm ino_t src_ino; 40696845Smarkm const char *link_name; 40796845Smarkm 40896845Smarkm src_test = test_path(src, &src_mtime); 40996845Smarkm if (!(src_test & (TEST_FILE|TEST_READABLE))) { 41096845Smarkm if (!(src_test & TEST_DIR)) { 41196845Smarkm warnx("%s/%s: unreadable", mandir, src); 41296845Smarkm exit_code = 1; 41396845Smarkm if (rm_junk && is_symlink(src)) 41496845Smarkm junk(mandir, src, "bogus symlink"); 41596845Smarkm } 41696845Smarkm return; 41796845Smarkm } 41896845Smarkm src_dev = test_st.st_dev; 41996845Smarkm src_ino = test_st.st_ino; 42096845Smarkm cat_test = test_path(cat, &cat_mtime); 42196845Smarkm if (cat_test & (TEST_FILE|TEST_READABLE)) { 42296845Smarkm if (!force && cat_mtime >= src_mtime) { 42396845Smarkm if (verbose) { 42496845Smarkm fprintf(stderr, "\t%s/%s: up to date\n", 42596845Smarkm mandir, src); 42696845Smarkm } 42796845Smarkm return; 42896845Smarkm } 42996845Smarkm } 43096845Smarkm /* 43196845Smarkm * Is the man page a link to one we've already processed? 43296845Smarkm */ 43396845Smarkm if ((link_name = find_hashtable(links, src_ino, src_dev)) != NULL) { 43496845Smarkm if (verbose || pretend) { 43596845Smarkm fprintf(stderr, "%slink %s -> %s\n", 43696845Smarkm verbose ? "\t" : "", cat, link_name); 43796845Smarkm } 43896845Smarkm if (!pretend) 43996845Smarkm link(link_name, cat); 44096845Smarkm return; 44196845Smarkm } 44296845Smarkm insert_hashtable(links, src_ino, src_dev, strdup(cat)); 44396845Smarkm if (verbose || pretend) { 44496845Smarkm fprintf(stderr, "%sformat %s -> %s\n", 44596845Smarkm verbose ? "\t" : "", src, cat); 44696845Smarkm if (pretend) 44796845Smarkm return; 44896845Smarkm } 44996845Smarkm snprintf(tmp_file, sizeof tmp_file, "%s.tmp", cat); 45096845Smarkm snprintf(cmd, sizeof cmd, 451115207Sru "%scat %s | tbl | nroff -T%s -man | col | %s > %s.tmp", 452106120Sobrien zipped == BZIP ? BZ2CAT_CMD : zipped == GZIP ? GZCAT_CMD : "", 453115207Sru src, nroff_device, 454115207Sru zipped == BZIP ? BZ2_CMD : zipped == GZIP ? GZ_CMD : "cat", 455115207Sru cat); 45696845Smarkm if (system(cmd) != 0) 45796845Smarkm err(1, "formatting pipeline"); 45896845Smarkm if (rename(tmp_file, cat) < 0) 45996845Smarkm warn("%s", cat); 46096845Smarkm tmp_file[0] = '\0'; 46196845Smarkm} 46296845Smarkm 46396845Smarkm/* 46496845Smarkm * Scan the man section directory for pages and process each one, 46596845Smarkm * then check for junk in the corresponding cat section. 46696845Smarkm */ 46796845Smarkmstatic void 46896845Smarkmscan_section(char *mandir, char *section, char *cat_section) 46996845Smarkm{ 47096845Smarkm struct dirent **entries; 47196845Smarkm char **expected = NULL; 47296845Smarkm int npages; 47396845Smarkm int nexpected = 0; 47496845Smarkm int i, e; 475106120Sobrien enum Ziptype zipped; 47696845Smarkm char *page_name; 47796845Smarkm char page_path[MAXPATHLEN]; 47896845Smarkm char cat_path[MAXPATHLEN]; 479106120Sobrien char zip_path[MAXPATHLEN]; 48096845Smarkm 48196845Smarkm /* 48296845Smarkm * scan the man section directory for pages 48396845Smarkm */ 48496845Smarkm npages = scandir(section, &entries, NULL, alphasort); 48596845Smarkm if (npages < 0) { 48696845Smarkm warn("%s/%s", mandir, section); 48796845Smarkm exit_code = 1; 48896845Smarkm return; 48996845Smarkm } 49096845Smarkm if (verbose || rm_junk) { 49196845Smarkm /* 49296845Smarkm * Maintain a list of all cat pages that should exist, 49396845Smarkm * corresponding to existing man pages. 49496845Smarkm */ 49596845Smarkm expected = (char **) calloc(npages, sizeof(char *)); 49696845Smarkm } 49796845Smarkm for (i = 0; i < npages; free(entries[i++])) { 49896845Smarkm page_name = entries[i]->d_name; 49996845Smarkm snprintf(page_path, sizeof page_path, "%s/%s", section, 50096845Smarkm page_name); 50196845Smarkm if (!is_manpage_name(page_name)) { 50296845Smarkm if (!(test_path(page_path, NULL) & TEST_DIR)) { 50396845Smarkm junk(mandir, page_path, 50496845Smarkm "invalid man page name"); 50596845Smarkm } 50696845Smarkm continue; 50796845Smarkm } 508106120Sobrien zipped = is_bzipped(page_name) ? BZIP : 509106120Sobrien is_gzipped(page_name) ? GZIP : NONE; 510106120Sobrien if (zipped != NONE) { 51196845Smarkm snprintf(cat_path, sizeof cat_path, "%s/%s", 51296845Smarkm cat_section, page_name); 51396845Smarkm if (expected != NULL) 51496845Smarkm expected[nexpected++] = strdup(page_name); 515106120Sobrien process_page(mandir, page_path, cat_path, zipped); 51696845Smarkm } else { 51796845Smarkm /* 51896845Smarkm * We've got an uncompressed man page, 51996845Smarkm * check to see if there's a (preferred) 52096845Smarkm * compressed one. 52196845Smarkm */ 522106120Sobrien snprintf(zip_path, sizeof zip_path, "%s%s", 523106120Sobrien page_path, GZ_EXT); 524106120Sobrien if (test_path(zip_path, NULL) != 0) { 52596845Smarkm junk(mandir, page_path, 526106120Sobrien "man page unused due to existing " GZ_EXT); 52796845Smarkm } else { 52896845Smarkm if (verbose) { 52996845Smarkm fprintf(stderr, 53096845Smarkm "warning, %s is uncompressed\n", 53196845Smarkm page_path); 53296845Smarkm } 533115207Sru snprintf(cat_path, sizeof cat_path, "%s/%s", 534115207Sru cat_section, page_name); 53596845Smarkm if (expected != NULL) { 53696845Smarkm asprintf(&expected[nexpected++], 537115207Sru "%s", page_name); 53896845Smarkm } 539106120Sobrien process_page(mandir, page_path, cat_path, NONE); 54096845Smarkm } 54196845Smarkm } 54296845Smarkm } 54396845Smarkm free(entries); 54496845Smarkm if (expected == NULL) 54596845Smarkm return; 54696845Smarkm /* 54796845Smarkm * scan cat sections for junk 54896845Smarkm */ 54996845Smarkm npages = scandir(cat_section, &entries, NULL, alphasort); 55096845Smarkm e = 0; 55196845Smarkm for (i = 0; i < npages; free(entries[i++])) { 55296845Smarkm const char *junk_reason; 55396845Smarkm int cmp = 1; 55496845Smarkm 55596845Smarkm page_name = entries[i]->d_name; 55696845Smarkm if (strcmp(page_name, ".") == 0 || strcmp(page_name, "..") == 0) 55796845Smarkm continue; 55896845Smarkm /* 55996845Smarkm * Keep the index into the expected cat page list 56096845Smarkm * ahead of the name we've found. 56196845Smarkm */ 56296845Smarkm while (e < nexpected && 56396845Smarkm (cmp = strcmp(page_name, expected[e])) > 0) 56496845Smarkm free(expected[e++]); 56596845Smarkm if (cmp == 0) 56696845Smarkm continue; 56796845Smarkm /* we have an unexpected page */ 568139182Sru snprintf(cat_path, sizeof cat_path, "%s/%s", cat_section, 569139182Sru page_name); 57096845Smarkm if (!is_manpage_name(page_name)) { 571139182Sru if (test_path(cat_path, NULL) & TEST_DIR) 572139182Sru continue; 57396845Smarkm junk_reason = "invalid cat page name"; 57496845Smarkm } else if (!is_gzipped(page_name) && e + 1 < nexpected && 57596845Smarkm strncmp(page_name, expected[e + 1], strlen(page_name)) == 0 && 57696845Smarkm strlen(expected[e + 1]) == strlen(page_name) + 3) { 577106120Sobrien junk_reason = "cat page unused due to existing " GZ_EXT; 57896845Smarkm } else 57996845Smarkm junk_reason = "cat page without man page"; 58096845Smarkm junk(mandir, cat_path, junk_reason); 58196845Smarkm } 58296845Smarkm free(entries); 58396845Smarkm while (e < nexpected) 58496845Smarkm free(expected[e++]); 58596845Smarkm free(expected); 58696845Smarkm} 58796845Smarkm 58896845Smarkm 58996845Smarkm/* 59096845Smarkm * Processes a single man section. 59196845Smarkm */ 59296845Smarkmstatic void 59396845Smarkmprocess_section(char *mandir, char *section) 59496845Smarkm{ 59596845Smarkm char *cat_section; 59696845Smarkm 59796845Smarkm if (already_visited(mandir, section, 1)) 59896845Smarkm return; 59996845Smarkm if (verbose) 60096845Smarkm fprintf(stderr, " section %s\n", section); 60196845Smarkm cat_section = get_cat_section(section); 60296845Smarkm if (make_writable_dir(mandir, cat_section)) 60396845Smarkm scan_section(mandir, section, cat_section); 604139182Sru free(cat_section); 60596845Smarkm} 60696845Smarkm 60796845Smarkmstatic int 60896845Smarkmselect_sections(struct dirent *entry) 60996845Smarkm{ 61096845Smarkm return directory_type(entry->d_name) == MAN_SECTION_DIR; 61196845Smarkm} 61296845Smarkm 61396845Smarkm/* 61496845Smarkm * Processes a single top-level man directory. If section isn't NULL, 61596845Smarkm * it will only process that section sub-directory, otherwise it will 61696845Smarkm * process all of them. 61796845Smarkm */ 61896845Smarkmstatic void 61996845Smarkmprocess_mandir(char *dir_name, char *section) 62096845Smarkm{ 62196845Smarkm fchdir(starting_dir); 62296845Smarkm if (already_visited(NULL, dir_name, section == NULL)) 62396845Smarkm return; 62496845Smarkm check_writable(dir_name); 62596845Smarkm if (verbose) 62696845Smarkm fprintf(stderr, "man directory %s\n", dir_name); 62796845Smarkm if (pretend) 62896845Smarkm fprintf(stderr, "cd %s\n", dir_name); 62996845Smarkm if (chdir(dir_name) < 0) { 63096845Smarkm warn("%s: chdir", dir_name); 63196845Smarkm exit_code = 1; 63296845Smarkm return; 63396845Smarkm } 63496845Smarkm if (section != NULL) { 63596845Smarkm process_section(dir_name, section); 63696845Smarkm } else { 63796845Smarkm struct dirent **entries; 638153115Sru char *machine_dir, *arch_dir; 63996845Smarkm int nsections; 64096845Smarkm int i; 64196845Smarkm 64296845Smarkm nsections = scandir(".", &entries, select_sections, alphasort); 64396845Smarkm if (nsections < 0) { 64496845Smarkm warn("%s", dir_name); 64596845Smarkm exit_code = 1; 64696845Smarkm return; 64796845Smarkm } 64896845Smarkm for (i = 0; i < nsections; i++) { 64996845Smarkm process_section(dir_name, entries[i]->d_name); 650139185Sru asprintf(&machine_dir, "%s/%s", entries[i]->d_name, 651139185Sru machine); 652139185Sru if (test_path(machine_dir, NULL) & TEST_DIR) 653139185Sru process_section(dir_name, machine_dir); 654139185Sru free(machine_dir); 655153115Sru if (strcmp(machine_arch, machine) != 0) { 656153115Sru asprintf(&arch_dir, "%s/%s", entries[i]->d_name, 657153115Sru machine_arch); 658153115Sru if (test_path(arch_dir, NULL) & TEST_DIR) 659153115Sru process_section(dir_name, arch_dir); 660153115Sru free(arch_dir); 661153115Sru } 66296845Smarkm free(entries[i]); 66396845Smarkm } 66496845Smarkm free(entries); 66596845Smarkm } 66696845Smarkm} 66796845Smarkm 66896845Smarkm/* 66996845Smarkm * Processes one argument, which may be a colon-separated list of 67096845Smarkm * directories. 67196845Smarkm */ 67296845Smarkmstatic void 67396845Smarkmprocess_argument(const char *arg) 67496845Smarkm{ 67596845Smarkm char *dir; 67696845Smarkm char *mandir; 677139182Sru char *section; 67896845Smarkm char *parg; 67996845Smarkm 68096845Smarkm parg = strdup(arg); 68196845Smarkm if (parg == NULL) 68296845Smarkm err(1, "out of memory"); 68396845Smarkm while ((dir = strsep(&parg, ":")) != NULL) { 68496845Smarkm switch (directory_type(dir)) { 68596845Smarkm case TOP_LEVEL_DIR: 68696845Smarkm if (locale != NULL) { 68796845Smarkm asprintf(&mandir, "%s/%s", dir, locale); 68896845Smarkm process_mandir(mandir, NULL); 68996845Smarkm free(mandir); 69096845Smarkm if (lang_locale != NULL) { 69196845Smarkm asprintf(&mandir, "%s/%s", dir, 69296845Smarkm lang_locale); 69396845Smarkm process_mandir(mandir, NULL); 69496845Smarkm free(mandir); 69596845Smarkm } 69696845Smarkm } else { 69796845Smarkm process_mandir(dir, NULL); 69896845Smarkm } 69996845Smarkm break; 70096845Smarkm case MAN_SECTION_DIR: { 701139182Sru mandir = strdup(dirname(dir)); 702139182Sru section = strdup(basename(dir)); 703139182Sru process_mandir(mandir, section); 704139182Sru free(mandir); 705139182Sru free(section); 70696845Smarkm break; 70796845Smarkm } 70896845Smarkm default: 70996845Smarkm warnx("%s: directory name not in proper man form", dir); 71096845Smarkm exit_code = 1; 71196845Smarkm } 71296845Smarkm } 71396845Smarkm free(parg); 71496845Smarkm} 71596845Smarkm 71696845Smarkmstatic void 71796845Smarkmdetermine_locale(void) 71896845Smarkm{ 71996845Smarkm char *sep; 72096845Smarkm 721116136Sache if ((locale = setlocale(LC_CTYPE, "")) == NULL) { 722116136Sache warnx("-L option used, but no locale found\n"); 72396845Smarkm return; 72496845Smarkm } 72596845Smarkm sep = strchr(locale, '_'); 726116136Sache if (sep != NULL && isupper((unsigned char)sep[1]) 727116136Sache && isupper((unsigned char)sep[2])) { 728139183Sru asprintf(&lang_locale, "%.*s%s", (int)(sep - locale), 729139183Sru locale, &sep[3]); 73096845Smarkm } 731116136Sache sep = nl_langinfo(CODESET); 732116136Sache if (sep != NULL && *sep != '\0' && strcmp(sep, "US-ASCII") != 0) { 73396845Smarkm int i; 73496845Smarkm 73596845Smarkm for (i = 0; locale_device[i] != NULL; i += 2) { 73696845Smarkm if (strcmp(sep, locale_device[i]) == 0) { 73796845Smarkm nroff_device = locale_device[i + 1]; 73896845Smarkm break; 73996845Smarkm } 74096845Smarkm } 74196845Smarkm } 742116136Sache if (verbose) { 743116136Sache if (lang_locale != NULL) 744116136Sache fprintf(stderr, "short locale is %s\n", lang_locale); 74596845Smarkm fprintf(stderr, "nroff device is %s\n", nroff_device); 746116136Sache } 74796845Smarkm} 74896845Smarkm 74996845Smarkmstatic void 75096845Smarkmusage(void) 75196845Smarkm{ 752146466Sru fprintf(stderr, "usage: %s [-fLnrv] [directories ...]\n", 753146466Sru getprogname()); 75496845Smarkm exit(1); 75596845Smarkm} 75696845Smarkm 75796845Smarkmint 75896845Smarkmmain(int argc, char **argv) 75996845Smarkm{ 76096845Smarkm int opt; 76196845Smarkm 76296845Smarkm if ((uid = getuid()) == 0) { 76396845Smarkm fprintf(stderr, "don't run %s as root, use:\n echo", argv[0]); 76496845Smarkm for (optind = 0; optind < argc; optind++) { 76596845Smarkm fprintf(stderr, " %s", argv[optind]); 76696845Smarkm } 76796845Smarkm fprintf(stderr, " | nice -5 su -m man\n"); 76896845Smarkm exit(1); 76996845Smarkm } 77096845Smarkm while ((opt = getopt(argc, argv, "vnfLrh")) != -1) { 77196845Smarkm switch (opt) { 77296845Smarkm case 'f': 77396845Smarkm force++; 77496845Smarkm break; 77596845Smarkm case 'L': 77696845Smarkm determine_locale(); 77796845Smarkm break; 77896845Smarkm case 'n': 77996845Smarkm pretend++; 78096845Smarkm break; 78196845Smarkm case 'r': 78296845Smarkm rm_junk++; 78396845Smarkm break; 78496845Smarkm case 'v': 78596845Smarkm verbose++; 78696845Smarkm break; 78796845Smarkm default: 78896845Smarkm usage(); 78996845Smarkm /* NOTREACHED */ 79096845Smarkm } 79196845Smarkm } 79296845Smarkm ngids = getgroups(NGROUPS_MAX, gids); 79396845Smarkm if ((starting_dir = open(".", 0)) < 0) { 79496845Smarkm err(1, "."); 79596845Smarkm } 79696845Smarkm umask(022); 79796845Smarkm signal(SIGINT, trap_signal); 79896845Smarkm signal(SIGHUP, trap_signal); 79996845Smarkm signal(SIGQUIT, trap_signal); 80096845Smarkm signal(SIGTERM, trap_signal); 801139185Sru 802153115Sru if ((machine = getenv("MACHINE")) == NULL) { 803153115Sru static struct utsname utsname; 804139185Sru 805153115Sru if (uname(&utsname) == -1) 806153115Sru err(1, "uname"); 807153115Sru machine = utsname.machine; 808153115Sru } 809153115Sru 810153115Sru if ((machine_arch = getenv("MACHINE_ARCH")) == NULL) 811153115Sru machine_arch = MACHINE_ARCH; 812153115Sru 81396845Smarkm if (optind == argc) { 81496845Smarkm const char *manpath = getenv("MANPATH"); 81596845Smarkm if (manpath == NULL) 81696845Smarkm manpath = DEFAULT_MANPATH; 81796845Smarkm process_argument(manpath); 81896845Smarkm } else { 81996845Smarkm while (optind < argc) 82096845Smarkm process_argument(argv[optind++]); 82196845Smarkm } 82296845Smarkm exit(exit_code); 82396845Smarkm} 824