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