1/*-
2 * Copyright (c) 2002 John Rochester
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer,
10 *    in this position and unchanged.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. The name of the author may not be used to endorse or promote products
15 *    derived from this software without specific prior written permission
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__FBSDID("$FreeBSD$");
31
32#include <sys/types.h>
33#include <sys/stat.h>
34#include <sys/param.h>
35#include <sys/utsname.h>
36
37#include <assert.h>
38#include <ctype.h>
39#include <dirent.h>
40#include <err.h>
41#include <errno.h>
42#include <fcntl.h>
43#include <locale.h>
44#include <langinfo.h>
45#include <libgen.h>
46#include <stdio.h>
47#include <stdlib.h>
48#include <string.h>
49#include <unistd.h>
50
51#define DEFAULT_MANPATH		"/usr/share/man"
52
53#define TOP_LEVEL_DIR	0	/* signifies a top-level man directory */
54#define MAN_SECTION_DIR	1	/* signifies a man section directory */
55#define UNKNOWN		2	/* signifies an unclassifiable directory */
56
57#define TEST_EXISTS	0x01
58#define TEST_DIR	0x02
59#define TEST_FILE	0x04
60#define TEST_READABLE	0x08
61#define TEST_WRITABLE	0x10
62
63static int verbose;		/* -v flag: be verbose with warnings */
64static int pretend;		/* -n, -p flags: print out what would be done
65				   instead of actually doing it */
66static int force;		/* -f flag: force overwriting all cat pages */
67static int rm_junk;		/* -r flag: remove garbage pages */
68static char *locale;		/* user's locale if -L is used */
69static char *lang_locale;	/* short form of locale */
70static const char *machine, *machine_arch;
71static int exit_code;		/* exit code to use when finished */
72
73/*
74 * -T argument for nroff
75 */
76static const char *nroff_device = "ascii";
77
78/*
79 * Mapping from locale to nroff device
80 */
81static const char *locale_device[] = {
82	"KOI8-R",	"koi8-r",
83	"ISO8859-1",	"latin1",
84	"ISO8859-15",	"latin1",
85	NULL
86};
87
88#define	BZ2_CMD		"bzip2"
89#define	BZ2_EXT		".bz2"
90#define	BZ2CAT_CMD	"bz"
91#define	GZ_CMD		"gzip"
92#define	GZ_EXT		".gz"
93#define	GZCAT_CMD	"z"
94enum Ziptype {NONE, BZIP, GZIP};
95
96static uid_t uid;
97static int starting_dir;
98static char tmp_file[MAXPATHLEN];
99static struct stat test_st;
100
101/*
102 * A hashtable is an array of chains composed of this entry structure.
103 */
104struct hash_entry {
105	ino_t		inode_number;
106	dev_t		device_number;
107	const char	*data;
108	struct hash_entry *next;
109};
110
111#define HASHTABLE_ALLOC	16384	/* allocation for hashtable (power of 2) */
112#define HASH_MASK	(HASHTABLE_ALLOC - 1)
113
114static struct hash_entry *visited[HASHTABLE_ALLOC];
115static struct hash_entry *links[HASHTABLE_ALLOC];
116
117/*
118 * Inserts a string into a hashtable keyed by inode & device number.
119 */
120static void
121insert_hashtable(struct hash_entry **table,
122    ino_t inode_number,
123    dev_t device_number,
124    const char *data)
125{
126	struct hash_entry *new_entry;
127	struct hash_entry **chain;
128
129	new_entry = (struct hash_entry *) malloc(sizeof(struct hash_entry));
130	if (new_entry == NULL)
131		err(1, "can't insert into hashtable");
132	chain = &table[inode_number & HASH_MASK];
133	new_entry->inode_number = inode_number;
134	new_entry->device_number = device_number;
135	new_entry->data = data;
136	new_entry->next = *chain;
137	*chain = new_entry;
138}
139
140/*
141 * Finds a string in a hashtable keyed by inode & device number.
142 */
143static const char *
144find_hashtable(struct hash_entry **table,
145    ino_t inode_number,
146    dev_t device_number)
147{
148	struct hash_entry *chain;
149
150	chain = table[inode_number & HASH_MASK];
151	while (chain != NULL) {
152		if (chain->inode_number == inode_number &&
153		    chain->device_number == device_number)
154			return chain->data;
155		chain = chain->next;
156	}
157	return NULL;
158}
159
160static void
161trap_signal(int sig __unused)
162{
163	if (tmp_file[0] != '\0')
164		unlink(tmp_file);
165	exit(1);
166}
167
168/*
169 * Deals with junk files in the man or cat section directories.
170 */
171static void
172junk(const char *mandir, const char *name, const char *reason)
173{
174	if (verbose)
175		fprintf(stderr, "%s/%s: %s\n", mandir, name, reason);
176	if (rm_junk) {
177		fprintf(stderr, "rm %s/%s\n", mandir, name);
178		if (!pretend && unlink(name) < 0)
179			warn("%s/%s", mandir, name);
180	}
181}
182
183/*
184 * Returns TOP_LEVEL_DIR for .../man, MAN_SECTION_DIR for .../manXXX,
185 * and UNKNOWN for everything else.
186 */
187static int
188directory_type(char *dir)
189{
190	char *p;
191
192	for (;;) {
193		p = strrchr(dir, '/');
194		if (p == NULL || p[1] != '\0')
195			break;
196		*p = '\0';
197	}
198	if (p == NULL)
199		p = dir;
200	else
201		p++;
202	if (strncmp(p, "man", 3) == 0) {
203		p += 3;
204		if (*p == '\0')
205			return TOP_LEVEL_DIR;
206		while (isalnum((unsigned char)*p) || *p == '_') {
207			if (*++p == '\0')
208				return MAN_SECTION_DIR;
209		}
210	}
211	return UNKNOWN;
212}
213
214/*
215 * Tests whether the given file name (without a preceding path)
216 * is a proper man page name (like "mk-amd-map.8.gz").
217 * Only alphanumerics and '_' are allowed after the last '.' and
218 * the last '.' can't be the first or last characters.
219 */
220static int
221is_manpage_name(char *name)
222{
223	char *lastdot = NULL;
224	char *n = name;
225
226	while (*n != '\0') {
227		if (!isalnum((unsigned char)*n)) {
228			switch (*n) {
229			case '_':
230				break;
231			case '-':
232			case '+':
233			case '[':
234			case ':':
235				lastdot = NULL;
236				break;
237			case '.':
238				lastdot = n;
239				break;
240			default:
241				return 0;
242			}
243		}
244		n++;
245	}
246	return lastdot > name && lastdot + 1 < n;
247}
248
249static int
250is_bzipped(char *name)
251{
252	int len = strlen(name);
253	return len >= 5 && strcmp(&name[len - 4], BZ2_EXT) == 0;
254}
255
256static int
257is_gzipped(char *name)
258{
259	int len = strlen(name);
260	return len >= 4 && strcmp(&name[len - 3], GZ_EXT) == 0;
261}
262
263/*
264 * Converts manXXX to catXXX.
265 */
266static char *
267get_cat_section(char *section)
268{
269	char *cat_section;
270
271	cat_section = strdup(section);
272	assert(strlen(section) > 3 && strncmp(section, "man", 3) == 0);
273	memcpy(cat_section, "cat", 3);
274	return cat_section;
275}
276
277/*
278 * Tests to see if the given directory has already been visited.
279 */
280static int
281already_visited(char *mandir, char *dir, int count_visit)
282{
283	struct stat st;
284
285	if (stat(dir, &st) < 0) {
286		if (mandir != NULL)
287			warn("%s/%s", mandir, dir);
288		else
289			warn("%s", dir);
290		exit_code = 1;
291		return 1;
292	}
293	if (find_hashtable(visited, st.st_ino, st.st_dev) != NULL) {
294		if (mandir != NULL)
295			warnx("already visited %s/%s", mandir, dir);
296		else
297			warnx("already visited %s", dir);
298		return 1;
299	}
300	if (count_visit)
301		insert_hashtable(visited, st.st_ino, st.st_dev, "");
302	return 0;
303}
304
305/*
306 * Returns a set of TEST_* bits describing a file's type and permissions.
307 * If mod_time isn't NULL, it will contain the file's modification time.
308 */
309static int
310test_path(char *name, time_t *mod_time)
311{
312	int result;
313
314	if (stat(name, &test_st) < 0)
315		return 0;
316	result = TEST_EXISTS;
317	if (mod_time != NULL)
318		*mod_time = test_st.st_mtime;
319	if (S_ISDIR(test_st.st_mode))
320		result |= TEST_DIR;
321	else if (S_ISREG(test_st.st_mode))
322		result |= TEST_FILE;
323	if (access(name, R_OK))
324		result |= TEST_READABLE;
325	if (access(name, W_OK))
326		result |= TEST_WRITABLE;
327	return result;
328}
329
330/*
331 * Checks whether a file is a symbolic link.
332 */
333static int
334is_symlink(char *path)
335{
336	struct stat st;
337
338	return lstat(path, &st) >= 0 && S_ISLNK(st.st_mode);
339}
340
341/*
342 * Tests to see if the given directory can be written to.
343 */
344static void
345check_writable(char *mandir)
346{
347	if (verbose && !(test_path(mandir, NULL) & TEST_WRITABLE))
348		fprintf(stderr, "%s: not writable - will only be able to write to existing cat directories\n", mandir);
349}
350
351/*
352 * If the directory exists, attempt to make it writable, otherwise
353 * attempt to create it.
354 */
355static int
356make_writable_dir(char *mandir, char *dir)
357{
358	int test;
359
360	if ((test = test_path(dir, NULL)) != 0) {
361		if (!(test & TEST_WRITABLE) && chmod(dir, 0755) < 0) {
362			warn("%s/%s: chmod", mandir, dir);
363			exit_code = 1;
364			return 0;
365		}
366	} else {
367		if (verbose || pretend)
368			fprintf(stderr, "mkdir %s\n", dir);
369		if (!pretend) {
370			unlink(dir);
371			if (mkdir(dir, 0755) < 0) {
372				warn("%s/%s: mkdir", mandir, dir);
373				exit_code = 1;
374				return 0;
375			}
376		}
377	}
378	return 1;
379}
380
381/*
382 * Processes a single man page source by using nroff to create
383 * the preformatted cat page.
384 */
385static void
386process_page(char *mandir, char *src, char *cat, enum Ziptype zipped)
387{
388	int src_test, cat_test;
389	time_t src_mtime, cat_mtime;
390	char cmd[MAXPATHLEN];
391	dev_t src_dev;
392	ino_t src_ino;
393	const char *link_name;
394
395	src_test = test_path(src, &src_mtime);
396	if (!(src_test & (TEST_FILE|TEST_READABLE))) {
397		if (!(src_test & TEST_DIR)) {
398			warnx("%s/%s: unreadable", mandir, src);
399			exit_code = 1;
400			if (rm_junk && is_symlink(src))
401				junk(mandir, src, "bogus symlink");
402		}
403		return;
404	}
405	src_dev = test_st.st_dev;
406	src_ino = test_st.st_ino;
407	cat_test = test_path(cat, &cat_mtime);
408	if (cat_test & (TEST_FILE|TEST_READABLE)) {
409		if (!force && cat_mtime >= src_mtime) {
410			if (verbose) {
411				fprintf(stderr, "\t%s/%s: up to date\n",
412				    mandir, src);
413			}
414			return;
415		}
416	}
417	/*
418	 * Is the man page a link to one we've already processed?
419	 */
420	if ((link_name = find_hashtable(links, src_ino, src_dev)) != NULL) {
421		if (verbose || pretend) {
422			fprintf(stderr, "%slink %s -> %s\n",
423			    verbose ? "\t" : "", cat, link_name);
424		}
425		if (!pretend) {
426			(void) unlink(cat);
427			if (link(link_name, cat) < 0)
428				warn("%s %s: link", link_name, cat);
429		}
430		return;
431	}
432	insert_hashtable(links, src_ino, src_dev, strdup(cat));
433	if (verbose || pretend) {
434		fprintf(stderr, "%sformat %s -> %s\n",
435		    verbose ? "\t" : "", src, cat);
436		if (pretend)
437			return;
438	}
439	snprintf(tmp_file, sizeof tmp_file, "%s.tmp", cat);
440	snprintf(cmd, sizeof cmd,
441	    "%scat %s | tbl | nroff -c -T%s -man | %s > %s.tmp",
442	    zipped == BZIP ? BZ2CAT_CMD : zipped == GZIP ? GZCAT_CMD : "",
443	    src, nroff_device,
444	    zipped == BZIP ? BZ2_CMD : zipped == GZIP ? GZ_CMD : "cat",
445	    cat);
446	if (system(cmd) != 0)
447		err(1, "formatting pipeline");
448	if (rename(tmp_file, cat) < 0)
449		warn("%s", cat);
450	tmp_file[0] = '\0';
451}
452
453/*
454 * Scan the man section directory for pages and process each one,
455 * then check for junk in the corresponding cat section.
456 */
457static void
458scan_section(char *mandir, char *section, char *cat_section)
459{
460	struct dirent **entries;
461	char **expected = NULL;
462	int npages;
463	int nexpected = 0;
464	int i, e;
465	enum Ziptype zipped;
466	char *page_name;
467	char page_path[MAXPATHLEN];
468	char cat_path[MAXPATHLEN];
469	char zip_path[MAXPATHLEN];
470
471	/*
472	 * scan the man section directory for pages
473	 */
474	npages = scandir(section, &entries, NULL, alphasort);
475	if (npages < 0) {
476		warn("%s/%s", mandir, section);
477		exit_code = 1;
478		return;
479	}
480	if (verbose || rm_junk) {
481		/*
482		 * Maintain a list of all cat pages that should exist,
483		 * corresponding to existing man pages.
484		 */
485		expected = (char **) calloc(npages, sizeof(char *));
486	}
487	for (i = 0; i < npages; free(entries[i++])) {
488		page_name = entries[i]->d_name;
489		snprintf(page_path, sizeof page_path, "%s/%s", section,
490		    page_name);
491		if (!is_manpage_name(page_name)) {
492			if (!(test_path(page_path, NULL) & TEST_DIR)) {
493				junk(mandir, page_path,
494				    "invalid man page name");
495			}
496			continue;
497		}
498		zipped = is_bzipped(page_name) ? BZIP :
499		    is_gzipped(page_name) ? GZIP : NONE;
500		if (zipped != NONE) {
501			snprintf(cat_path, sizeof cat_path, "%s/%s",
502			    cat_section, page_name);
503			if (expected != NULL)
504				expected[nexpected++] = strdup(page_name);
505			process_page(mandir, page_path, cat_path, zipped);
506		} else {
507			/*
508			 * We've got an uncompressed man page,
509			 * check to see if there's a (preferred)
510			 * compressed one.
511			 */
512			snprintf(zip_path, sizeof zip_path, "%s%s",
513			    page_path, GZ_EXT);
514			if (test_path(zip_path, NULL) != 0) {
515				junk(mandir, page_path,
516				    "man page unused due to existing " GZ_EXT);
517			} else {
518				if (verbose) {
519					fprintf(stderr,
520						"warning, %s is uncompressed\n",
521						page_path);
522				}
523				snprintf(cat_path, sizeof cat_path, "%s/%s",
524				    cat_section, page_name);
525				if (expected != NULL) {
526					asprintf(&expected[nexpected++],
527					    "%s", page_name);
528				}
529				process_page(mandir, page_path, cat_path, NONE);
530			}
531		}
532	}
533	free(entries);
534	if (expected == NULL)
535	    return;
536	/*
537	 * scan cat sections for junk
538	 */
539	npages = scandir(cat_section, &entries, NULL, alphasort);
540	e = 0;
541	for (i = 0; i < npages; free(entries[i++])) {
542		const char *junk_reason;
543		int cmp = 1;
544
545		page_name = entries[i]->d_name;
546		if (strcmp(page_name, ".") == 0 || strcmp(page_name, "..") == 0)
547			continue;
548		/*
549		 * Keep the index into the expected cat page list
550		 * ahead of the name we've found.
551		 */
552		while (e < nexpected &&
553		    (cmp = strcmp(page_name, expected[e])) > 0)
554			free(expected[e++]);
555		if (cmp == 0)
556			continue;
557		/* we have an unexpected page */
558		snprintf(cat_path, sizeof cat_path, "%s/%s", cat_section,
559		    page_name);
560		if (!is_manpage_name(page_name)) {
561			if (test_path(cat_path, NULL) & TEST_DIR)
562				continue;
563			junk_reason = "invalid cat page name";
564		} else if (!is_gzipped(page_name) && e + 1 < nexpected &&
565		    strncmp(page_name, expected[e + 1], strlen(page_name)) == 0 &&
566		    strlen(expected[e + 1]) == strlen(page_name) + 3) {
567			junk_reason = "cat page unused due to existing " GZ_EXT;
568		} else
569			junk_reason = "cat page without man page";
570		junk(mandir, cat_path, junk_reason);
571	}
572	free(entries);
573	while (e < nexpected)
574		free(expected[e++]);
575	free(expected);
576}
577
578
579/*
580 * Processes a single man section.
581 */
582static void
583process_section(char *mandir, char *section)
584{
585	char *cat_section;
586
587	if (already_visited(mandir, section, 1))
588		return;
589	if (verbose)
590		fprintf(stderr, "  section %s\n", section);
591	cat_section = get_cat_section(section);
592	if (make_writable_dir(mandir, cat_section))
593		scan_section(mandir, section, cat_section);
594	free(cat_section);
595}
596
597static int
598select_sections(const struct dirent *entry)
599{
600	char *name;
601	int ret;
602
603	name = strdup(entry->d_name);
604	ret = directory_type(name) == MAN_SECTION_DIR;
605	free(name);
606	return (ret);
607}
608
609/*
610 * Processes a single top-level man directory.  If section isn't NULL,
611 * it will only process that section sub-directory, otherwise it will
612 * process all of them.
613 */
614static void
615process_mandir(char *dir_name, char *section)
616{
617	if (fchdir(starting_dir) < 0)
618		err(1, "fchdir");
619	if (already_visited(NULL, dir_name, section == NULL))
620		return;
621	check_writable(dir_name);
622	if (verbose)
623		fprintf(stderr, "man directory %s\n", dir_name);
624	if (pretend)
625		fprintf(stderr, "cd %s\n", dir_name);
626	if (chdir(dir_name) < 0) {
627		warn("%s: chdir", dir_name);
628		exit_code = 1;
629		return;
630	}
631	if (section != NULL) {
632		process_section(dir_name, section);
633	} else {
634		struct dirent **entries;
635		char *machine_dir, *arch_dir;
636		int nsections;
637		int i;
638
639		nsections = scandir(".", &entries, select_sections, alphasort);
640		if (nsections < 0) {
641			warn("%s", dir_name);
642			exit_code = 1;
643			return;
644		}
645		for (i = 0; i < nsections; i++) {
646			process_section(dir_name, entries[i]->d_name);
647			asprintf(&machine_dir, "%s/%s", entries[i]->d_name,
648			    machine);
649			if (test_path(machine_dir, NULL) & TEST_DIR)
650				process_section(dir_name, machine_dir);
651			free(machine_dir);
652			if (strcmp(machine_arch, machine) != 0) {
653				asprintf(&arch_dir, "%s/%s", entries[i]->d_name,
654				    machine_arch);
655				if (test_path(arch_dir, NULL) & TEST_DIR)
656					process_section(dir_name, arch_dir);
657				free(arch_dir);
658			}
659			free(entries[i]);
660		}
661		free(entries);
662	}
663}
664
665/*
666 * Processes one argument, which may be a colon-separated list of
667 * directories.
668 */
669static void
670process_argument(const char *arg)
671{
672	char *dir;
673	char *mandir;
674	char *section;
675	char *parg;
676
677	parg = strdup(arg);
678	if (parg == NULL)
679		err(1, "out of memory");
680	while ((dir = strsep(&parg, ":")) != NULL) {
681		switch (directory_type(dir)) {
682		case TOP_LEVEL_DIR:
683			if (locale != NULL) {
684				asprintf(&mandir, "%s/%s", dir, locale);
685				process_mandir(mandir, NULL);
686				free(mandir);
687				if (lang_locale != NULL) {
688					asprintf(&mandir, "%s/%s", dir,
689					    lang_locale);
690					process_mandir(mandir, NULL);
691					free(mandir);
692				}
693			} else {
694				process_mandir(dir, NULL);
695			}
696			break;
697		case MAN_SECTION_DIR: {
698			mandir = strdup(dirname(dir));
699			section = strdup(basename(dir));
700			process_mandir(mandir, section);
701			free(mandir);
702			free(section);
703			break;
704			}
705		default:
706			warnx("%s: directory name not in proper man form", dir);
707			exit_code = 1;
708		}
709	}
710	free(parg);
711}
712
713static void
714determine_locale(void)
715{
716	char *sep;
717
718	if ((locale = setlocale(LC_CTYPE, "")) == NULL) {
719		warnx("-L option used, but no locale found\n");
720		return;
721	}
722	sep = strchr(locale, '_');
723	if (sep != NULL && isupper((unsigned char)sep[1])
724			&& isupper((unsigned char)sep[2])) {
725		asprintf(&lang_locale, "%.*s%s", (int)(sep - locale),
726		    locale, &sep[3]);
727	}
728	sep = nl_langinfo(CODESET);
729	if (sep != NULL && *sep != '\0' && strcmp(sep, "US-ASCII") != 0) {
730		int i;
731
732		for (i = 0; locale_device[i] != NULL; i += 2) {
733			if (strcmp(sep, locale_device[i]) == 0) {
734				nroff_device = locale_device[i + 1];
735				break;
736			}
737		}
738	}
739	if (verbose) {
740		if (lang_locale != NULL)
741			fprintf(stderr, "short locale is %s\n", lang_locale);
742		fprintf(stderr, "nroff device is %s\n", nroff_device);
743	}
744}
745
746static void
747usage(void)
748{
749	fprintf(stderr, "usage: %s [-fLnrv] [directories ...]\n",
750	    getprogname());
751	exit(1);
752}
753
754int
755main(int argc, char **argv)
756{
757	int opt;
758
759	if ((uid = getuid()) == 0) {
760		fprintf(stderr, "don't run %s as root, use:\n   echo", argv[0]);
761		for (optind = 0; optind < argc; optind++) {
762			fprintf(stderr, " %s", argv[optind]);
763		}
764		fprintf(stderr, " | nice -5 su -m man\n");
765		exit(1);
766	}
767	while ((opt = getopt(argc, argv, "vnfLrh")) != -1) {
768		switch (opt) {
769		case 'f':
770			force++;
771			break;
772		case 'L':
773			determine_locale();
774			break;
775		case 'n':
776			pretend++;
777			break;
778		case 'r':
779			rm_junk++;
780			break;
781		case 'v':
782			verbose++;
783			break;
784		default:
785			usage();
786			/* NOTREACHED */
787		}
788	}
789	if ((starting_dir = open(".", 0)) < 0) {
790		err(1, ".");
791	}
792	umask(022);
793	signal(SIGINT, trap_signal);
794	signal(SIGHUP, trap_signal);
795	signal(SIGQUIT, trap_signal);
796	signal(SIGTERM, trap_signal);
797
798	if ((machine = getenv("MACHINE")) == NULL) {
799		static struct utsname utsname;
800
801		if (uname(&utsname) == -1)
802			err(1, "uname");
803		machine = utsname.machine;
804	}
805
806	if ((machine_arch = getenv("MACHINE_ARCH")) == NULL)
807		machine_arch = MACHINE_ARCH;
808
809	if (optind == argc) {
810		const char *manpath = getenv("MANPATH");
811		if (manpath == NULL)
812			manpath = DEFAULT_MANPATH;
813		process_argument(manpath);
814	} else {
815		while (optind < argc)
816			process_argument(argv[optind++]);
817	}
818	exit(exit_code);
819}
820