Initial import from FreeBSD RELENG_4:
[dragonfly.git] / gnu / usr.bin / man / man / man.c
1 /*
2  * man.c
3  *
4  * Copyright (c) 1990, 1991, John W. Eaton.
5  *
6  * You may distribute under the terms of the GNU General Public
7  * License as specified in the file COPYING that comes with the man
8  * distribution.
9  *
10  * John W. Eaton
11  * jwe@che.utexas.edu
12  * Department of Chemical Engineering
13  * The University of Texas at Austin
14  * Austin, Texas  78712
15  */
16
17 #ifndef lint
18 static const char rcsid[] =
19   "$FreeBSD: src/gnu/usr.bin/man/man/man.c,v 1.37.2.10 2003/02/14 15:38:51 ru Exp $";
20 #endif /* not lint */
21
22 #define MAN_MAIN
23
24 #include <sys/file.h>
25 #include <sys/stat.h>
26 #include <sys/param.h>
27 #include <ctype.h>
28 #include <errno.h>
29 #ifdef __FreeBSD__
30 #include <locale.h>
31 #include <langinfo.h>
32 #endif
33 #include <stdio.h>
34 #include <string.h>
35 #include <signal.h>
36 #if HAVE_LIBZ > 0
37 #include <zlib.h>
38 #endif
39 #include "config.h"
40 #include "gripes.h"
41 #include "version.h"
42
43 #ifdef POSIX
44 #include <unistd.h>
45 #else
46 #ifndef R_OK
47 #define R_OK 4
48 #endif
49 #endif
50
51 #ifdef SECURE_MAN_UID
52 extern uid_t getuid ();
53 extern int setuid ();
54 #endif
55
56 #ifdef STDC_HEADERS
57 #include <stdlib.h>
58 #else
59 extern char *malloc ();
60 extern char *getenv ();
61 extern void free ();
62 extern int system ();
63 extern int strcmp ();
64 extern int strncmp ();
65 extern int exit ();
66 extern int fflush ();
67 extern int printf ();
68 extern int fprintf ();
69 extern FILE *fopen ();
70 extern int fclose ();
71 extern char *sprintf ();
72 #endif
73
74 extern char **glob_filename ();
75 extern int is_newer ();
76 extern int is_directory ();
77 extern int do_system_command ();
78
79 char *prognam;
80 static char *pager;
81 static char *machine;
82 static char *manp;
83 static char *manpathlist[MAXDIRS];
84 static char *shortsec;
85 static char *longsec;
86 static char *colon_sep_section_list;
87 static char **section_list;
88 static char *roff_directive;
89 static int apropos;
90 static int whatis;
91 static int findall;
92 static int print_where;
93
94 #ifdef __FreeBSD__
95 static char *locale, *locale_opts, *locale_nroff, *locale_codeset;
96 static char locale_terr[3], locale_lang[3];
97 static char *man_locale;
98 static int use_man_locale;
99 static int use_original;
100 struct ltable {
101         char *lcode;
102         char *nroff;
103 };
104 static struct ltable ltable[] = {
105         {"KOI8-R", "koi8-r"},
106         {"ISO8859-1", "latin1"},
107         {"ISO8859-15", "latin1"},
108         {NULL}
109 };
110 #endif
111
112 static int troff = 0;
113
114 int debug;
115
116 #ifdef HAS_TROFF
117 #ifdef __FreeBSD__
118 static char args[] = "M:P:S:adfhkm:op:tw?";
119 #else
120 static char args[] = "M:P:S:adfhkm:p:tw?";
121 #endif
122 #else
123 #ifdef __FreeBSD__
124 static char args[] = "M:P:S:adfhkm:op:w?";
125 #else
126 static char args[] = "M:P:S:adfhkm:p:w?";
127 #endif
128 #endif
129
130 #ifdef SETREUID
131 uid_t ruid;
132 uid_t euid;
133 gid_t rgid;
134 gid_t egid;
135 #endif
136
137 int
138 main (argc, argv)
139      int argc;
140      char **argv;
141 {
142   int status = 0;
143   char *nextarg;
144   char *tmp;
145   extern char *mkprogname ();
146   char *is_section ();
147   char **get_section_list ();
148   void man_getopt ();
149   void do_apropos ();
150   void do_whatis ();
151   int man ();
152
153   prognam = mkprogname (argv[0]);
154   longsec = NULL;
155
156   unsetenv("IFS");
157 #ifdef __FreeBSD__
158   (void) setlocale(LC_ALL, "");
159 #endif
160   man_getopt (argc, argv);
161
162   if (optind == argc)
163     gripe_no_name ((char *)NULL);
164
165   section_list = get_section_list ();
166
167   if (optind == argc - 1)
168     {
169       tmp = is_section (argv[optind], manp);
170
171       if (tmp != NULL)
172         gripe_no_name (tmp);
173     }
174
175 #ifdef SETREUID
176   ruid = getuid();
177   rgid = getgid();
178   euid = geteuid();
179   egid = getegid();
180   setreuid(-1, ruid);
181   setregid(-1, rgid);
182 #endif
183
184   while (optind < argc)
185     {
186       nextarg = argv[optind++];
187
188       /*
189        * See if this argument is a valid section name.  If not,
190        * is_section returns NULL.
191        */
192       tmp = is_section (nextarg, manp);
193
194       if (tmp != NULL)
195         {
196           shortsec = tmp;
197
198           if (debug)
199             fprintf (stderr, "\nsection: %s\n", shortsec);
200
201           continue;
202         }
203
204       if (apropos) {
205         do_apropos (nextarg);
206         status = (status ? 0 : 1); /* reverts status, see below */
207       }
208       else if (whatis) {
209         do_whatis (nextarg);
210         status = (status ? 0 : 1); /* reverts status, see below */
211       }
212       else
213         {
214           status = man (nextarg);
215
216           if (status == 0)
217             gripe_not_found (nextarg, longsec);
218         }
219     }
220   return (status==0);         /* status==1 --> exit(0),
221                                  status==0 --> exit(1) */
222 }
223
224 void
225 usage ()
226 {
227   static char usage_string[1024] = "%s, version %s\n\n";
228
229 #ifdef HAS_TROFF
230 #ifdef __FreeBSD__
231   static char s1[] =
232     "usage: %s [-adfhkotw] [section] [-M path] [-P pager] [-S list]\n\
233            [-m machine] [-p string] name ...\n\n";
234 #else
235   static char s1[] =
236     "usage: %s [-adfhktw] [section] [-M path] [-P pager] [-S list]\n\
237            [-m machine] [-p string] name ...\n\n";
238 #endif
239 #else
240 #ifdef __FreeBSD__
241   static char s1[] =
242     "usage: %s [-adfhkow] [section] [-M path] [-P pager] [-S list]\n\
243            [-m machine] [-p string] name ...\n\n";
244 #else
245   static char s1[] =
246     "usage: %s [-adfhkw] [section] [-M path] [-P pager] [-S list]\n\
247            [-m machine] [-p string] name ...\n\n";
248 #endif
249 #endif
250
251 static char s2[] = "  a : find all matching entries\n\
252   d : print gobs of debugging information\n\
253   f : same as whatis(1)\n\
254   h : print this help message\n\
255   k : same as apropos(1)\n";
256
257 #ifdef __FreeBSD__
258   static char s3[] = "  o : use original, non-localized manpages\n";
259 #endif
260
261 #ifdef HAS_TROFF
262   static char s4[] = "  t : use troff to format pages for printing\n";
263 #endif
264
265   static char s5[] = "  w : print location of man page(s) that would be displayed\n\n\
266   M path    : set search path for manual pages to `path'\n\
267   P pager   : use program `pager' to display pages\n\
268   S list    : colon separated section list\n\
269   m machine : search for alternate architecture man pages\n";
270
271   static char s6[] = "  p string : string tells which preprocessors to run\n\
272                e - [n]eqn(1)   p - pic(1)    t - tbl(1)\n\
273                g - grap(1)     r - refer(1)  v - vgrind(1)\n";
274
275   strcat (usage_string, s1);
276   strcat (usage_string, s2);
277 #ifdef __FreeBSD__
278   strcat (usage_string, s3);
279 #endif
280
281 #ifdef HAS_TROFF
282   strcat (usage_string, s4);
283 #endif
284
285   strcat (usage_string, s5);
286
287   strcat (usage_string, s6);
288
289   fprintf (stderr, usage_string, prognam, version, prognam);
290   exit(1);
291 }
292
293 char **
294 add_dir_to_mpath_list (mp, p)
295      char **mp;
296      char *p;
297 {
298   int status;
299
300   status = is_directory (p);
301
302   if (status < 0 && debug)
303     {
304       fprintf (stderr, "Warning: couldn't stat file %s!\n", p);
305     }
306   else if (status == 0 && debug)
307     {
308       fprintf (stderr, "Warning: %s isn't a directory!\n", p);
309     }
310   else if (status == 1)
311     {
312       if (debug)
313         fprintf (stderr, "adding %s to manpathlist\n", p);
314
315       *mp++ = strdup (p);
316     }
317   return mp;
318 }
319
320 /*
321  * Get options from the command line and user environment.
322  */
323 void
324 man_getopt (argc, argv)
325      register int argc;
326      register char **argv;
327 {
328   register int c;
329   register char *p;
330   register char *end;
331   register char **mp;
332   extern void downcase ();
333   extern char *manpath ();
334
335   while ((c = getopt (argc, argv, args)) != EOF)
336     {
337       switch (c)
338         {
339         case 'M':
340           manp = strdup (optarg);
341           break;
342         case 'P':
343           pager = strdup (optarg);
344           if (setenv("PAGER", pager, 1) != 0)
345                   (void)fprintf(stderr, "setenv PAGER=%s\n", pager);
346           break;
347         case 'S':
348           colon_sep_section_list = strdup (optarg);
349           break;
350         case 'a':
351           findall++;
352           break;
353         case 'd':
354           debug++;
355           break;
356         case 'f':
357           if (troff)
358             gripe_incompatible ("-f and -t");
359           if (apropos)
360             gripe_incompatible ("-f and -k");
361           if (print_where)
362             gripe_incompatible ("-f and -w");
363           whatis++;
364           break;
365         case 'k':
366           if (troff)
367             gripe_incompatible ("-k and -t");
368           if (whatis)
369             gripe_incompatible ("-k and -f");
370           if (print_where)
371             gripe_incompatible ("-k and -w");
372           apropos++;
373           break;
374         case 'm':
375           machine = optarg;
376           break;
377 #ifdef __FreeBSD__
378         case 'o':
379           use_original++;
380           break;
381 #endif
382         case 'p':
383           roff_directive = strdup (optarg);
384           break;
385 #ifdef HAS_TROFF
386         case 't':
387           if (apropos)
388             gripe_incompatible ("-t and -k");
389           if (whatis)
390             gripe_incompatible ("-t and -f");
391           if (print_where)
392             gripe_incompatible ("-t and -w");
393           troff++;
394           break;
395 #endif
396         case 'w':
397           if (apropos)
398             gripe_incompatible ("-w and -k");
399           if (whatis)
400             gripe_incompatible ("-w and -f");
401           if (troff)
402             gripe_incompatible ("-w and -t");
403           print_where++;
404           break;
405         case 'h':
406         case '?':
407         default:
408           usage();
409           break;
410         }
411     }
412
413 #ifdef __FreeBSD__
414   /* "" intentionally used to catch error */
415   if ((locale = setlocale(LC_CTYPE, "")) != NULL)
416         locale_codeset = nl_langinfo(CODESET);
417   if (!use_original && locale != NULL && *locale_codeset != '\0' &&
418       strcmp(locale_codeset, "US-ASCII") != 0
419      ) {
420         char *tmp, *short_locale;
421         struct ltable *pltable;
422
423         *locale_lang = '\0';
424         *locale_terr = '\0';
425
426         if ((short_locale = strdup(locale)) == NULL) {
427                 perror ("ctype locale strdup");
428                 exit (1);
429         }
430         if ((tmp = strchr(short_locale, '.')) != NULL)
431                 *tmp = '\0';
432
433         if (strlen(short_locale) == 2)
434                 strcpy(locale_lang, short_locale);
435         else if ((tmp = strchr(short_locale, '_')) == NULL ||
436                  tmp != short_locale + 2 ||
437                  strlen(tmp + 1) != 2
438                 ) {
439                 errno = EINVAL;
440                 perror ("ctype locale format");
441                 locale = NULL;
442         } else {
443                 strncpy(locale_terr, short_locale + 3, 2);
444                 locale_terr[2] = '\0';
445                 strncpy(locale_lang, short_locale, 2);
446                 locale_lang[2] = '\0';
447         }
448
449         free(short_locale);
450
451         if (locale != NULL) {
452                 for (pltable = ltable; pltable->lcode != NULL; pltable++) {
453                         if (strcmp(pltable->lcode, locale_codeset) == 0) {
454                                 locale_nroff = pltable->nroff;
455                                 break;
456                         }
457                 }
458                 asprintf(&man_locale, "%s.%s", locale_lang, locale_codeset);
459         }
460   } else {
461         if (locale == NULL) {
462                 errno = EINVAL;
463                 perror ("ctype locale");
464         } else {
465                 locale = NULL;
466                 if (*locale_codeset == '\0') {
467                         errno = EINVAL;
468                         perror ("ctype codeset");
469                 }
470         }
471   }
472 #endif /* __FreeBSD__ */
473
474   if (pager == NULL || *pager == '\0')
475     if ((pager = getenv ("PAGER")) == NULL || *pager == '\0')
476       pager = strdup (PAGER);
477
478   if (debug)
479     fprintf (stderr, "\nusing %s as pager\n", pager);
480
481   if (machine == NULL && (machine = getenv ("MACHINE")) == NULL)
482     machine = MACHINE;
483
484   if (debug)
485     fprintf (stderr, "\nusing %s architecture\n", machine);
486
487   if (manp == NULL)
488     {
489       if ((manp = manpath (0)) == NULL)
490         gripe_manpath ();
491
492       if (debug)
493         fprintf (stderr,
494                  "\nsearch path for pages determined by manpath is\n%s\n\n",
495                  manp);
496     }
497
498   /*
499    * Expand the manpath into a list for easier handling.
500    */
501   mp = manpathlist;
502   for (p = manp; ; p = end+1)
503     {
504       if (mp == manpathlist + MAXDIRS - 1) {
505         fprintf (stderr, "Warning: too many directories in manpath, truncated!\n");
506         break;
507       }
508       if ((end = strchr (p, ':')) != NULL)
509         *end = '\0';
510
511       mp = add_dir_to_mpath_list (mp, p);
512       if (end == NULL)
513         break;
514
515       *end = ':';
516     }
517   *mp = NULL;
518 }
519
520 /*
521  * Check to see if the argument is a valid section number.  If the
522  * first character of name is a numeral, or the name matches one of
523  * the sections listed in section_list, we'll assume that it's a section.
524  * The list of sections in config.h simply allows us to specify oddly
525  * named directories like .../man3f.  Yuk.
526  */
527 char *
528 is_section (name, path)
529      char *name;
530      char *path;
531 {
532   register char **vs;
533   char *temp, *end, *loc;
534   char **plist;
535   int x;
536
537   for (vs = section_list; *vs != NULL; vs++)
538     if ((strcmp (*vs, name) == 0)
539         || (isdigit ((unsigned char)name[0]) && strlen(name) == 1))
540       return (longsec = strdup (name));
541
542   plist = manpathlist;
543   if (isdigit ((unsigned char)name[0]))
544     {
545       while (*plist != NULL)
546         {
547           asprintf(&temp, "%s/man%c/*", *plist, name[0]);
548           plist++;
549
550           x = 0;
551           vs = glob_filename (temp);
552           if ((int)vs == -1)
553             {
554               free (temp);
555               return NULL;
556             }
557           for ( ; *vs != NULL; vs++)
558             {
559               end = strrchr (*vs, '/');
560               if ((loc = strstr (end, name)) != NULL && loc - end > 2
561                   && *(loc-1) == '.'
562                   && (*(loc+strlen(name)) == '\0' || *(loc+strlen(name)) == '.'))
563                 {
564                   x = 1;
565                   break;
566                 }
567             }
568           free (temp);
569           if (x == 1)
570             {
571               asprintf(&temp, "%c", name[0]);
572               longsec = strdup (name);
573               return (temp);
574             }
575         }
576     }
577   return NULL;
578 }
579
580 /*
581  * Handle the apropos option.  Cheat by using another program.
582  */
583 void
584 do_apropos (name)
585      register char *name;
586 {
587   register int len;
588   register char *command;
589
590   len = strlen (APROPOS) + strlen (name) + 4;
591
592   if ((command = (char *) malloc(len)) == NULL)
593     gripe_alloc (len, "command");
594
595   sprintf (command, "%s \"%s\"", APROPOS, name);
596
597   (void) do_system_command (command);
598
599   free (command);
600 }
601
602 /*
603  * Handle the whatis option.  Cheat by using another program.
604  */
605 void
606 do_whatis (name)
607      register char *name;
608 {
609   register int len;
610   register char *command;
611
612   len = strlen (WHATIS) + strlen (name) + 4;
613
614   if ((command = (char *) malloc(len)) == NULL)
615     gripe_alloc (len, "command");
616
617   sprintf (command, "%s \"%s\"", WHATIS, name);
618
619   (void) do_system_command (command);
620
621   free (command);
622 }
623
624 /*
625  * Change a name of the form ...man/man1/name.1 to ...man/cat1/name.1
626  * or a name of the form ...man/cat1/name.1 to ...man/man1/name.1
627  */
628 char *
629 convert_name (name, to_cat)
630      register char *name;
631      register int to_cat;
632 {
633   register char *to_name;
634   register char *t1;
635   register char *t2 = NULL;
636
637 #ifdef DO_COMPRESS
638   if (to_cat)
639     {
640       int olen = strlen(name);
641       int cextlen = strlen(COMPRESS_EXT);
642       int len = olen + cextlen;
643
644       to_name = malloc (len+1);
645       if (to_name == NULL)
646         gripe_alloc (len+1, "to_name");
647       strcpy (to_name, name);
648       olen -= cextlen;
649       /* Avoid tacking it on twice */
650       if (olen >= 1 && strcmp(name + olen, COMPRESS_EXT) != 0)
651         strcat (to_name, COMPRESS_EXT);
652     }
653   else
654     to_name = strdup (name);
655 #else
656   to_name = strdup (name);
657 #endif
658
659   t1 = strrchr (to_name, '/');
660   if (t1 != NULL)
661     {
662       *t1 = '\0';
663       t2 = strrchr (to_name, '/');
664       *t1 = '/';
665
666       /* Skip architecture part (if present). */
667       if (t2 != NULL && (t1 - t2 < 5 || *(t2 + 1) != 'm' || *(t2 + 3) != 'n'))
668         {
669           t1 = t2;
670           *t1 = '\0';
671           t2 = strrchr (to_name, '/');
672           *t1 = '/';
673         }
674     }
675
676   if (t2 == NULL)
677     gripe_converting_name (name, to_cat);
678
679   if (to_cat)
680     {
681       *(++t2) = 'c';
682       *(t2+2) = 't';
683     }
684   else
685     {
686       *(++t2) = 'm';
687       *(t2+2) = 'n';
688     }
689
690   if (debug)
691     fprintf (stderr, "to_name in convert_name () is: %s\n", to_name);
692
693   return to_name;
694 }
695
696 /*
697  * Try to find the man page corresponding to the given name.  The
698  * reason we do this with globbing is because some systems have man
699  * page directories named man3 which contain files with names like
700  * XtPopup.3Xt.  Rather than requiring that this program know about
701  * all those possible names, we simply try to match things like
702  * .../man[sect]/name[sect]*.  This is *much* easier.
703  *
704  * Note that globbing is only done when the section is unspecified.
705  */
706 char **
707 glob_for_file (path, section, longsec, name, cat)
708      char *path;
709      char *section;
710      char *longsec;
711      char *name;
712      int cat;
713 {
714   char pathname[FILENAME_MAX];
715   char **gf;
716
717   if (longsec == NULL)
718     longsec = section;
719
720   if (cat)
721     snprintf (pathname, sizeof(pathname), "%s/cat%s/%s.%s*", path, section,
722        name, longsec);
723   else
724     snprintf (pathname, sizeof(pathname), "%s/man%s/%s.%s*", path, section,
725        name, longsec);
726
727   if (debug)
728     fprintf (stderr, "globbing %s\n", pathname);
729
730   gf = glob_filename (pathname);
731
732   if ((gf == (char **) -1 || *gf == NULL) && isdigit ((unsigned char)*section)
733       && strlen (longsec) == 1)
734     {
735       if (cat)
736         snprintf (pathname, sizeof(pathname), "%s/cat%s/%s.%c*", path, section, name, *section);
737       else
738         snprintf (pathname, sizeof(pathname), "%s/man%s/%s.%c*", path, section, name, *section);
739
740       gf = glob_filename (pathname);
741     }
742   if ((gf == (char **) -1 || *gf == NULL) && isdigit ((unsigned char)*section)
743       && strlen (longsec) == 1)
744     {
745       if (cat)
746         snprintf (pathname, sizeof(pathname), "%s/cat%s/%s.0*", path, section, name);
747       else
748         snprintf (pathname, sizeof(pathname), "%s/man%s/%s.0*", path, section, name);
749       if (debug)
750         fprintf (stderr, "globbing %s\n", pathname);
751       gf = glob_filename (pathname);
752     }
753   return gf;
754 }
755
756 /*
757  * Return an un-globbed name in the same form as if we were doing
758  * globbing.
759  */
760 char **
761 make_name (path, section, longsec, name, cat)
762      char *path;
763      char *section;
764      char *longsec;
765      char *name;
766      int cat;
767 {
768   register int i = 0;
769   static char *names[3];
770   char buf[FILENAME_MAX];
771
772   if (cat)
773     snprintf (buf, sizeof(buf), "%s/cat%s/%s.%s", path, section, name, longsec);
774   else
775     snprintf (buf, sizeof(buf), "%s/man%s/%s.%s", path, section, name, longsec);
776
777   if (access (buf, R_OK) == 0)
778     names[i++] = strdup (buf);
779
780   /*
781    * If we're given a section that looks like `3f', we may want to try
782    * file names like .../man3/foo.3f as well.  This seems a bit
783    * kludgey to me, but what the hey...
784    */
785   if (section[1] != '\0')
786     {
787       if (cat)
788         snprintf (buf, sizeof(buf), "%s/cat%c/%s.%s", path, section[0], name, section);
789       else
790         snprintf (buf, sizeof(buf), "%s/man%c/%s.%s", path, section[0], name, section);
791
792       if (access (buf, R_OK) == 0)
793         names[i++] = strdup (buf);
794     }
795
796   names[i] = NULL;
797
798   return &names[0];
799 }
800
801 char *
802 get_expander (file)
803      char *file;
804 {
805   char *end = file + (strlen (file) - 1);
806
807   while (end > file && end[-1] != '.')
808     --end;
809   if (end == file)
810     return NULL;
811 #ifdef FCAT
812   if (*end == 'F')
813     return FCAT;
814 #endif  /* FCAT */
815 #ifdef YCAT
816   if (*end == 'Y')
817     return YCAT;
818 #endif  /* YCAT */
819 #ifdef ZCAT
820   if (*end == 'Z' || !strcmp(end, "gz"))
821     return ZCAT;
822 #endif  /* ZCAT */
823   return NULL;
824 }
825
826 /*
827  * Simply display the preformatted page.
828  */
829 int
830 display_cat_file (file)
831      register char *file;
832 {
833   register int found;
834   char command[FILENAME_MAX];
835
836   found = 0;
837
838   if (access (file, R_OK) == 0)
839     {
840       char *expander = get_expander (file);
841
842       if (expander != NULL)
843         snprintf (command, sizeof(command), "%s %s | %s", expander, file, pager);
844       else
845         snprintf (command, sizeof(command), "%s %s", pager, file);
846
847       found = do_system_command (command);
848     }
849   return found;
850 }
851
852 /*
853  * Try to find the ultimate source file.  If the first line of the
854  * current file is not of the form
855  *
856  *      .so man3/printf.3s
857  *
858  * the input file name is returned.
859  */
860 char *
861 ultimate_source (name, path)
862      char *name;
863      char *path;
864 {
865   static  char buf[BUFSIZ];
866   static  char ult[FILENAME_MAX];
867
868   FILE *fp;
869   char *beg;
870   char *end;
871
872   strncpy (ult, name, sizeof(ult)-1);
873   ult[sizeof(ult)-1] = '\0';
874   strncpy (buf, name, sizeof(buf)-1);
875   ult[sizeof(buf)-1] = '\0';
876
877  next:
878
879   if ((fp = fopen (ult, "r")) == NULL)
880     return ult;
881
882   end = fgets (buf, BUFSIZ, fp);
883   fclose(fp);
884
885   if (!end || strlen (buf) < 5)
886     return ult;
887
888   beg = buf;
889   if (*beg++ == '.' && *beg++ == 's' && *beg++ == 'o')
890     {
891       while ((*beg == ' ' || *beg == '\t') && *beg != '\0')
892         beg++;
893
894       end = beg;
895       while (*end != ' ' && *end != '\t' && *end != '\n' && *end != '\0')
896         end++;
897
898       *end = '\0';
899
900       snprintf(ult, sizeof(ult), "%s/%s", path, beg);
901       snprintf(buf, sizeof(buf), "%s", ult);
902
903       goto next;
904     }
905
906   if (debug)
907     fprintf (stderr, "found ultimate source file %s\n", ult);
908
909   return ult;
910 }
911
912 void
913 add_directive (first, d, file, buf, bufsize)
914      int *first;
915      char *d;
916      char *file;
917      char *buf;
918      int bufsize;
919 {
920   if (strcmp (d, "") != 0)
921     {
922       if (*first)
923         {
924           *first = 0;
925           snprintf(buf, bufsize, "%s %s", d, file);
926         }
927       else
928         {
929           strncat (buf, " | ", bufsize-strlen(buf)-1);
930           strncat (buf, d, bufsize-strlen(buf)-1);
931         }
932     }
933 }
934
935 int
936 parse_roff_directive (cp, file, buf, bufsize)
937   char *cp;
938   char *file;
939   char *buf;
940   int bufsize;
941 {
942   char c;
943   char *exp;
944   int first = 1;
945   int preproc_found = 0;
946   int use_col = 0;
947
948   if ((exp = get_expander(file)) != NULL)
949         add_directive (&first, exp, file, buf, bufsize);
950
951   while ((c = *cp++) != '\0')
952     {
953       switch (c)
954         {
955         case 'e':
956
957           if (debug)
958             fprintf (stderr, "found eqn(1) directive\n");
959
960           preproc_found++;
961           if (troff)
962             add_directive (&first, EQN, file, buf, bufsize);
963           else {
964 #ifdef __FreeBSD__
965             char lbuf[FILENAME_MAX];
966
967             snprintf(lbuf, sizeof(lbuf), "%s -T%s", NEQN,
968                      locale_opts == NULL ? "ascii" : locale_opts);
969             add_directive (&first, lbuf, file, buf, bufsize);
970 #else
971             add_directive (&first, NEQN, file, buf, bufsize);
972 #endif
973           }
974
975           break;
976
977         case 'g':
978
979           if (debug)
980             fprintf (stderr, "found grap(1) directive\n");
981
982           preproc_found++;
983           add_directive (&first, GRAP, file, buf, bufsize);
984
985           break;
986
987         case 'p':
988
989           if (debug)
990             fprintf (stderr, "found pic(1) directive\n");
991
992           preproc_found++;
993           add_directive (&first, PIC, file, buf, bufsize);
994
995           break;
996
997         case 't':
998
999           if (debug)
1000             fprintf (stderr, "found tbl(1) directive\n");
1001
1002           preproc_found++;
1003           use_col++;
1004           add_directive (&first, TBL, file, buf, bufsize);
1005           break;
1006
1007         case 'v':
1008
1009           if (debug)
1010             fprintf (stderr, "found vgrind(1) directive\n");
1011
1012           add_directive (&first, VGRIND, file, buf, bufsize);
1013           break;
1014
1015         case 'r':
1016
1017           if (debug)
1018             fprintf (stderr, "found refer(1) directive\n");
1019
1020           add_directive (&first, REFER, file, buf, bufsize);
1021           break;
1022
1023         case ' ':
1024         case '\t':
1025         case '\n':
1026
1027           goto done;
1028
1029         default:
1030
1031           return -1;
1032         }
1033     }
1034
1035  done:
1036
1037 #ifdef HAS_TROFF
1038   if (troff)
1039     add_directive (&first, TROFF, file, buf, bufsize);
1040   else
1041 #endif
1042     {
1043 #ifdef __FreeBSD__
1044       char lbuf[FILENAME_MAX];
1045
1046       snprintf(lbuf, sizeof(lbuf), "%s -T%s%s%s", NROFF,
1047                locale_opts == NULL ? "ascii" : locale_opts,
1048                use_man_locale ? " -dlocale=" : "",
1049                use_man_locale ? man_locale : "");
1050             add_directive (&first, lbuf, file, buf, bufsize);
1051 #else
1052       add_directive (&first, NROFF " -Tascii", file, buf, bufsize);
1053 #endif
1054     }
1055   if (use_col && !troff)
1056       add_directive (&first, COL, file, buf, bufsize);
1057
1058   if (preproc_found)
1059     return 0;
1060   else
1061     return 1;
1062 }
1063
1064 char *
1065 make_roff_command (file)
1066      char *file;
1067 {
1068 #if HAVE_LIBZ > 0
1069   gzFile fp;
1070 #else
1071   FILE *fp;
1072 #endif
1073   char line [BUFSIZ];
1074   static char buf [BUFSIZ];
1075   int status;
1076   char *cp;
1077
1078   if (roff_directive != NULL)
1079     {
1080       if (debug)
1081         fprintf (stderr, "parsing directive from command line\n");
1082
1083       status = parse_roff_directive (roff_directive, file, buf, sizeof(buf));
1084
1085       if (status == 0)
1086         return buf;
1087
1088       if (status == -1)
1089         gripe_roff_command_from_command_line (file);
1090     }
1091
1092 #if HAVE_LIBZ > 0
1093   if ((fp = gzopen (file, "r")) != NULL)
1094 #else
1095   if ((fp = fopen (file, "r")) != NULL)
1096 #endif
1097     {
1098       cp = line;
1099 #if HAVE_LIBZ > 0
1100       gzgets (fp, line, BUFSIZ);
1101       gzclose(fp);
1102 #else
1103       fgets (line, BUFSIZ, fp);
1104       fclose(fp);
1105 #endif
1106       if (*cp++ == '\'' && *cp++ == '\\' && *cp++ == '"' && *cp++ == ' ')
1107         {
1108           if (debug)
1109             fprintf (stderr, "parsing directive from file\n");
1110
1111           status = parse_roff_directive (cp, file, buf, sizeof(buf));
1112
1113           if (status == 0)
1114             return buf;
1115
1116           if (status == -1)
1117             gripe_roff_command_from_file (file);
1118         }
1119     }
1120   else
1121     {
1122       /*
1123        * Is there really any point in continuing to look for
1124        * preprocessor options if we can't even read the man page source?
1125        */
1126       gripe_reading_man_file (file);
1127       return NULL;
1128     }
1129
1130   if ((cp = getenv ("MANROFFSEQ")) != NULL)
1131     {
1132       if (debug)
1133         fprintf (stderr, "parsing directive from environment\n");
1134
1135       status = parse_roff_directive (cp, file, buf, sizeof(buf));
1136
1137       if (status == 0)
1138         return buf;
1139
1140       if (status == -1)
1141         gripe_roff_command_from_env ();
1142     }
1143
1144   if (debug)
1145     fprintf (stderr, "using default preprocessor sequence\n");
1146
1147   status = parse_roff_directive ("t", file, buf, sizeof(buf));
1148   if (status >= 0)
1149     return buf;
1150   else          /* can't happen */
1151     return NULL;
1152 }
1153
1154 sig_t ohup, oint, oquit, oterm;
1155 static char temp[FILENAME_MAX];
1156
1157 void cleantmp()
1158 {
1159         unlink(temp);
1160         exit(1);
1161 }
1162
1163 void
1164 set_sigs()
1165 {
1166   ohup = signal(SIGHUP, cleantmp);
1167   oint = signal(SIGINT, cleantmp);
1168   oquit = signal(SIGQUIT, cleantmp);
1169   oterm = signal(SIGTERM, cleantmp);
1170 }
1171
1172 void
1173 restore_sigs()
1174 {
1175   signal(SIGHUP, ohup);
1176   signal(SIGINT, oint);
1177   signal(SIGQUIT, oquit);
1178   signal(SIGTERM, oterm);
1179 }
1180
1181 /*
1182  * Try to format the man page and create a new formatted file.  Return
1183  * 1 for success and 0 for failure.
1184  */
1185 int
1186 make_cat_file (path, man_file, cat_file, manid)
1187      register char *path;
1188      register char *man_file;
1189      register char *cat_file;
1190 {
1191   int s, f;
1192   FILE *fp, *pp;
1193   char *roff_command;
1194   char command[FILENAME_MAX];
1195
1196   roff_command = make_roff_command (man_file);
1197   if (roff_command == NULL)
1198       return 0;
1199
1200   snprintf(temp, sizeof(temp), "%s.tmpXXXXXX", cat_file);
1201   if ((f = mkstemp(temp)) >= 0 && (fp = fdopen(f, "w")) != NULL)
1202     {
1203       set_sigs();
1204
1205       if (fchmod (f, CATMODE) < 0) {
1206         perror("fchmod");
1207         unlink(temp);
1208         restore_sigs();
1209         fclose(fp);
1210         return 0;
1211       } else if (debug)
1212         fprintf (stderr, "mode of %s is now %o\n", temp, CATMODE);
1213
1214 #ifdef DO_COMPRESS
1215       snprintf (command, sizeof(command), "(cd %s ; %s | %s)", path,
1216                 roff_command, COMPRESSOR);
1217 #else
1218       snprintf (command, sizeof(command), "(cd %s ; %s)", path,
1219                 roff_command);
1220 #endif
1221       fprintf (stderr, "Formatting page, please wait...");
1222       fflush(stderr);
1223
1224       if (debug)
1225         fprintf (stderr, "\ntrying command: %s\n", command);
1226       else {
1227
1228 #ifdef SETREUID
1229         if (manid) {
1230           setreuid(-1, ruid);
1231           setregid(-1, rgid);
1232         }
1233 #endif
1234         if ((pp = popen(command, "r")) == NULL) {
1235           s = errno;
1236           fprintf(stderr, "Failed.\n");
1237           errno = s;
1238           perror("popen");
1239 #ifdef SETREUID
1240           if (manid) {
1241             setreuid(-1, euid);
1242             setregid(-1, egid);
1243           }
1244 #endif
1245           unlink(temp);
1246           restore_sigs();
1247           fclose(fp);
1248           return 0;
1249         }
1250 #ifdef SETREUID
1251         if (manid) {
1252           setreuid(-1, euid);
1253           setregid(-1, egid);
1254         }
1255 #endif
1256
1257         f = 0;
1258         while ((s = getc(pp)) != EOF) {
1259           putc(s, fp); f++;
1260         }
1261
1262         if (!f || ((s = pclose(pp)) == -1)) {
1263           s = errno;
1264           fprintf(stderr, "Failed.\n");
1265           errno = s;
1266           perror("pclose");
1267           unlink(temp);
1268           restore_sigs();
1269           fclose(fp);
1270           return 0;
1271         }
1272
1273         if (s != 0) {
1274           fprintf(stderr, "Failed.\n");
1275           gripe_system_command(s);
1276           unlink(temp);
1277           restore_sigs();
1278           fclose(fp);
1279           return 0;
1280         }
1281       }
1282
1283       if (debug)
1284         unlink(temp);
1285       else if (rename(temp, cat_file) == -1) {
1286         s = errno;
1287         fprintf(stderr,
1288                  "\nHmm!  Can't seem to rename %s to %s, check permissions on man dir!\n",
1289                  temp, cat_file);
1290         errno = s;
1291         perror("rename");
1292         unlink(temp);
1293         restore_sigs();
1294         fclose(fp);
1295         return 0;
1296       }
1297       restore_sigs();
1298
1299       if (fclose(fp)) {
1300         s = errno;
1301         if (!debug)
1302           unlink(cat_file);
1303         fprintf(stderr, "Failed.\n");
1304         errno = s;
1305         perror("fclose");
1306         return 0;
1307       }
1308
1309       if (debug) {
1310         fprintf(stderr, "No output, debug mode.\n");
1311         return 0;
1312       }
1313
1314       fprintf(stderr, "Done.\n");
1315
1316       return 1;
1317     }
1318   else
1319     {
1320       if (f >= 0) {
1321         s = errno;
1322         unlink(temp);
1323         errno = s;
1324       }
1325       if (debug) {
1326         s = errno;
1327         fprintf (stderr, "Couldn't open %s for writing.\n", temp);
1328         errno = s;
1329       }
1330       if (f >= 0) {
1331         perror("fdopen");
1332         close(f);
1333       }
1334
1335       return 0;
1336     }
1337 }
1338
1339 /*
1340  * Try to format the man page source and save it, then display it.  If
1341  * that's not possible, try to format the man page source and display
1342  * it directly.
1343  *
1344  * Note that we've already been handed the name of the ultimate source
1345  * file at this point.
1346  */
1347 int
1348 format_and_display (path, man_file, cat_file)
1349      register char *path;
1350      register char *man_file;
1351      register char *cat_file;
1352 {
1353   int status;
1354   register int found;
1355   char *roff_command;
1356   char command[FILENAME_MAX];
1357
1358   found = 0;
1359
1360   if (access (man_file, R_OK) != 0)
1361     return 0;
1362
1363   if (troff)
1364     {
1365       roff_command = make_roff_command (man_file);
1366       if (roff_command == NULL)
1367         return 0;
1368       else
1369         snprintf (command, sizeof(command), "(cd %s ; %s)", path, roff_command);
1370
1371       found = do_system_command (command);
1372     }
1373   else
1374     {
1375       status = is_newer (man_file, cat_file);
1376       if (debug)
1377         fprintf (stderr, "status from is_newer() = %d\n", status);
1378
1379       if (status == 1 || status == -2)
1380         {
1381           /*
1382            * Cat file is out of date.  Try to format and save it.
1383            */
1384           if (print_where)
1385             {
1386               printf ("%s\n", man_file);
1387               found++;
1388             }
1389           else
1390             {
1391
1392 #ifdef SETREUID
1393               setreuid(-1, euid);
1394               setregid(-1, egid);
1395               found = make_cat_file (path, man_file, cat_file, 1);
1396 #else
1397               found = make_cat_file (path, man_file, cat_file, 0);
1398 #endif
1399 #ifdef SETREUID
1400               setreuid(-1, ruid);
1401               setregid(-1, rgid);
1402
1403               if (!found)
1404                 {
1405                   /* Try again as real user - see note below.
1406                      By running with
1407                        effective group (user) ID == real group (user) ID
1408                      except for the call above, I believe the problems
1409                      of reading private man pages is avoided.  */
1410                   found = make_cat_file (path, man_file, cat_file, 0);
1411                 }
1412 #endif
1413 #ifdef SECURE_MAN_UID
1414               if (!found)
1415                 {
1416                   /*
1417                    * Try again as real user.  Note that for private
1418                    * man pages, we won't even get this far unless the
1419                    * effective user can read the real user's man page
1420                    * source.  Also, if we are trying to find all the
1421                    * man pages, this will probably make it impossible
1422                    * to make cat files in the system directories if
1423                    * the real user's man directories are searched
1424                    * first, because there's no way to undo this (is
1425                    * there?).  Yikes, am I missing something obvious?
1426                    */
1427                   setuid (getuid ());
1428
1429                   found = make_cat_file (path, man_file, cat_file, 0);
1430                 }
1431 #endif
1432               if (found)
1433                 {
1434                   /*
1435                    * Creating the cat file worked.  Now just display it.
1436                    */
1437                   (void) display_cat_file (cat_file);
1438                 }
1439               else
1440                 {
1441                   /*
1442                    * Couldn't create cat file.  Just format it and
1443                    * display it through the pager.
1444                    */
1445                   roff_command = make_roff_command (man_file);
1446                   if (roff_command == NULL)
1447                     return 0;
1448                   else
1449                     snprintf (command, sizeof(command), "(cd %s ; %s | %s)", path,
1450                              roff_command, pager);
1451
1452                   found = do_system_command (command);
1453                 }
1454             }
1455         }
1456       else if (access (cat_file, R_OK) == 0)
1457         {
1458           /*
1459            * Formatting not necessary.  Cat file is newer than source
1460            * file, or source file is not present but cat file is.
1461            */
1462           if (print_where)
1463             {
1464               printf ("%s (source: %s)\n", cat_file, man_file);
1465               found++;
1466             }
1467           else
1468             {
1469               found = display_cat_file (cat_file);
1470             }
1471         }
1472     }
1473   return found;
1474 }
1475
1476 /*
1477  * See if the preformatted man page or the source exists in the given
1478  * section.
1479  */
1480 int
1481 try_section (path, section, longsec, name, glob)
1482      char *path;
1483      char *section;
1484      char *longsec;
1485      char *name;
1486      int glob;
1487 {
1488   register int found = 0;
1489   register int to_cat;
1490   register int cat;
1491   register char **names;
1492   register char **np;
1493   static int arch_search;
1494   char buf[FILENAME_MAX];
1495
1496   if (!arch_search)
1497     {
1498       snprintf(buf, sizeof(buf), "%s/man%s/%s", path, section, machine);
1499       if (is_directory (buf) == 1)
1500         {
1501           snprintf(buf, sizeof(buf), "%s/%s", machine, name);
1502           arch_search++;
1503           found = try_section (path, section, longsec, buf, glob);
1504           arch_search--;
1505           if (found && !findall)   /* only do this architecture... */
1506             return found;
1507         }
1508     }
1509
1510   if (debug)
1511     {
1512       if (glob)
1513         fprintf (stderr, "trying section %s with globbing\n", section);
1514       else
1515         fprintf (stderr, "trying section %s without globbing\n", section);
1516     }
1517
1518 #ifndef NROFF_MISSING
1519   /*
1520    * Look for man page source files.
1521    */
1522   cat = 0;
1523   if (glob)
1524     names = glob_for_file (path, section, longsec, name, cat);
1525   else
1526     names = make_name (path, section, longsec, name, cat);
1527
1528   if (names == (char **) -1 || *names == NULL)
1529     /*
1530      * No files match.  See if there's a preformatted page around that
1531      * we can display.
1532      */
1533 #endif /* NROFF_MISSING */
1534     {
1535       if (!troff)
1536         {
1537           cat = 1;
1538           if (glob)
1539             names = glob_for_file (path, section, longsec, name, cat);
1540           else
1541             names = make_name (path, section, longsec, name, cat);
1542
1543           if (names != (char **) -1 && *names != NULL)
1544             {
1545               for (np = names; *np != NULL; np++)
1546                 {
1547                   if (print_where)
1548                     {
1549                       printf ("%s\n", *np);
1550                       found++;
1551                     }
1552                   else
1553                     {
1554                       found += display_cat_file (*np);
1555                     }
1556                 }
1557             }
1558         }
1559     }
1560 #ifndef NROFF_MISSING
1561   else
1562     {
1563       for (np = names; *np != NULL; np++)
1564         {
1565           register char *cat_file = NULL;
1566           register char *man_file;
1567
1568           man_file = ultimate_source (*np, path);
1569
1570           if (!troff)
1571             {
1572               to_cat = 1;
1573
1574               cat_file = convert_name (man_file, to_cat);
1575
1576               if (debug)
1577                 fprintf (stderr, "will try to write %s if needed\n", cat_file);
1578             }
1579
1580           found += format_and_display (path, man_file, cat_file);
1581         }
1582     }
1583 #endif /* NROFF_MISSING */
1584   return found;
1585 }
1586
1587 /*
1588  * Search for manual pages.
1589  *
1590  * If preformatted manual pages are supported, look for the formatted
1591  * file first, then the man page source file.  If they both exist and
1592  * the man page source file is newer, or only the source file exists,
1593  * try to reformat it and write the results in the cat directory.  If
1594  * it is not possible to write the cat file, simply format and display
1595  * the man file.
1596  *
1597  * If preformatted pages are not supported, or the troff option is
1598  * being used, only look for the man page source file.
1599  *
1600  */
1601 int
1602 man (name)
1603      char *name;
1604 {
1605   register int found;
1606   register int glob;
1607   register char **mp;
1608   register char **sp;
1609 #ifdef __FreeBSD__
1610   int l_found;
1611   char buf[FILENAME_MAX];
1612 #endif
1613
1614   found = 0;
1615
1616   fflush (stdout);
1617   if (shortsec != NULL)
1618     {
1619       for (mp = manpathlist; *mp != NULL; mp++)
1620         {
1621           if (debug)
1622             fprintf (stderr, "\nsearching in %s\n", *mp);
1623
1624           glob = 1;
1625
1626 #ifdef __FreeBSD__
1627           l_found = 0;
1628           if (locale != NULL) {
1629             locale_opts = locale_nroff;
1630             use_man_locale = 1;
1631             if (*locale_lang != '\0' && *locale_terr != '\0') {
1632               snprintf(buf, sizeof(buf), "%s/%s_%s.%s", *mp,
1633                        locale_lang, locale_terr, locale_codeset);
1634               if (is_directory (buf) == 1)
1635                 l_found = try_section (buf, shortsec, longsec, name, glob);
1636             }
1637             if (!l_found) {
1638               if (*locale_lang != '\0') {
1639                 snprintf(buf, sizeof(buf), "%s/%s.%s", *mp,
1640                          locale_lang, locale_codeset);
1641                 if (is_directory (buf) == 1)
1642                   l_found = try_section (buf, shortsec, longsec, name, glob);
1643               }
1644               use_man_locale = 0;
1645               if (!l_found && strcmp(locale_lang, "en") != 0) {
1646                 snprintf(buf, sizeof(buf), "%s/en.%s", *mp,
1647                          locale_codeset);
1648                 if (is_directory (buf) == 1)
1649                   l_found = try_section (buf, shortsec, longsec, name, glob);
1650               }
1651             }
1652             locale_opts = NULL;
1653             use_man_locale = 0;
1654           }
1655           if (!l_found) {
1656 #endif
1657           found += try_section (*mp, shortsec, longsec, name, glob);
1658 #ifdef __FreeBSD__
1659           } else
1660             found += l_found;
1661 #endif
1662
1663           if (found && !findall)   /* i.e. only do this section... */
1664             return found;
1665         }
1666     }
1667   else
1668     {
1669       for (sp = section_list; *sp != NULL; sp++)
1670         {
1671           for (mp = manpathlist; *mp != NULL; mp++)
1672             {
1673               if (debug)
1674                 fprintf (stderr, "\nsearching in %s\n", *mp);
1675
1676               glob = 1;
1677
1678 #ifdef __FreeBSD__
1679               l_found = 0;
1680               if (locale != NULL) {
1681                 locale_opts = locale_nroff;
1682                 use_man_locale = 1;
1683                 if (*locale_lang != '\0' && *locale_terr != '\0') {
1684                   snprintf(buf, sizeof(buf), "%s/%s_%s.%s", *mp,
1685                            locale_lang, locale_terr, locale_codeset);
1686                   if (is_directory (buf) == 1)
1687                     l_found = try_section (buf, *sp, longsec, name, glob);
1688                 }
1689                 if (!l_found) {
1690                   if (*locale_lang != '\0') {
1691                     snprintf(buf, sizeof(buf), "%s/%s.%s", *mp,
1692                              locale_lang, locale_codeset);
1693                     if (is_directory (buf) == 1)
1694                       l_found = try_section (buf, *sp, longsec, name, glob);
1695                   }
1696                   use_man_locale = 0;
1697                   if (!l_found && strcmp(locale_lang, "en") != 0) {
1698                     snprintf(buf, sizeof(buf), "%s/en.%s", *mp,
1699                              locale_codeset);
1700                     if (is_directory (buf) == 1)
1701                       l_found = try_section (buf, *sp, longsec, name, glob);
1702                   }
1703                 }
1704                 locale_opts = NULL;
1705                 use_man_locale = 0;
1706               }
1707               if (!l_found) {
1708 #endif
1709               found += try_section (*mp, *sp, longsec, name, glob);
1710 #ifdef __FreeBSD__
1711               } else
1712                 found += l_found;
1713 #endif
1714
1715               if (found && !findall)   /* i.e. only do this section... */
1716                 return found;
1717             }
1718         }
1719     }
1720   return found;
1721 }
1722
1723 char **
1724 get_section_list ()
1725 {
1726   int i;
1727   char *p;
1728   char *end;
1729 #define TMP_SECTION_LIST_SIZE 100
1730   static char *tmp_section_list[TMP_SECTION_LIST_SIZE];
1731
1732   if (colon_sep_section_list == NULL)
1733     {
1734       if ((p = getenv ("MANSECT")) == NULL)
1735         {
1736           return std_sections;
1737         }
1738       else
1739         {
1740           colon_sep_section_list = strdup (p);
1741         }
1742     }
1743
1744   i = 0;
1745   for (p = colon_sep_section_list; i < TMP_SECTION_LIST_SIZE ; p = end+1) 
1746     {
1747       if ((end = strchr (p, ':')) != NULL)
1748         *end = '\0';
1749
1750       tmp_section_list[i++] = strdup (p);
1751
1752       if (end == NULL)
1753         break;
1754     }
1755
1756   tmp_section_list [i] = NULL;
1757   return tmp_section_list;
1758 }