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