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