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