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