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