]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - usr.bin/catman/catman.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / usr.bin / catman / catman.c
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
61 static int verbose;             /* -v flag: be verbose with warnings */
62 static int pretend;             /* -n, -p flags: print out what would be done
63                                    instead of actually doing it */
64 static int force;               /* -f flag: force overwriting all cat pages */
65 static int rm_junk;             /* -r flag: remove garbage pages */
66 static char *locale;            /* user's locale if -L is used */
67 static char *lang_locale;       /* short form of locale */
68 static const char *machine, *machine_arch;
69 static int exit_code;           /* exit code to use when finished */
70
71 /*
72  * -T argument for nroff
73  */
74 static const char *nroff_device = "ascii";
75
76 /*
77  * Mapping from locale to nroff device
78  */
79 static 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"
92 enum Ziptype {NONE, BZIP, GZIP};
93
94 static uid_t uid;
95 static int starting_dir;
96 static char tmp_file[MAXPATHLEN];
97 static struct stat test_st;
98
99 /*
100  * A hashtable is an array of chains composed of this entry structure.
101  */
102 struct 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
112 static struct hash_entry *visited[HASHTABLE_ALLOC];
113 static struct hash_entry *links[HASHTABLE_ALLOC];
114
115 /*
116  * Inserts a string into a hashtable keyed by inode & device number.
117  */
118 static void
119 insert_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  */
141 static const char *
142 find_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
158 static void
159 trap_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  */
169 static void
170 junk(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  */
185 static int
186 directory_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  */
218 static int
219 is_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
247 static int
248 is_bzipped(char *name)
249 {
250         int len = strlen(name);
251         return len >= 5 && strcmp(&name[len - 4], BZ2_EXT) == 0;
252 }
253
254 static int
255 is_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  */
264 static char *
265 get_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  */
277 static int
278 already_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  */
306 static int
307 test_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  */
330 static int
331 is_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  */
341 static void
342 check_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  */
352 static int
353 make_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  */
382 static void
383 process_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  */
451 static void
452 scan_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  */
576 static void
577 process_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
591 static int
592 select_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  */
608 static void
609 process_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  */
662 static void
663 process_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
706 static void
707 determine_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
739 static void
740 usage(void)
741 {
742         fprintf(stderr, "usage: %s [-fLnrv] [directories ...]\n",
743             getprogname());
744         exit(1);
745 }
746
747 int
748 main(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 }