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 * Some manpath, compression and locale related changes - aeb - 940320
17 * Some suid related changes - aeb - 941008
18 * Some more fixes, Pauline Middelink & aeb, Oct 1994
19 * man -K: aeb, Jul 1995
20 * Split off of manfile for man2html, aeb, New Year's Eve 1997
21 */
22
23#include <stdio.h>
24#include <ctype.h>
25#include <string.h>
26#include <stdlib.h>
27#include <sys/file.h>
28#include <sys/stat.h>		/* for chmod */
29#include <signal.h>
30#include <errno.h>
31#include <unistd.h>
32#include <locale.h>
33
34#ifndef R_OK
35#define R_OK 4
36#endif
37
38extern char *index (const char *, int);		/* not always in <string.h> */
39extern char *rindex (const char *, int);	/* not always in <string.h> */
40
41#include "defs.h"
42#include "gripes.h"
43#include "man.h"
44#include "manfile.h"
45#include "manpath.h"
46#include "man-config.h"
47#include "man-getopt.h"
48#include "man-iconv.h"
49#include "to_cat.h"
50#include "util.h"
51#include "glob.h"
52#include "different.h"
53#include "man-iconv.h"
54
55#define SIZE(x) (sizeof(x)/sizeof((x)[0]))
56
57const char *progname;
58const char *pager, *browser, *htmlpager;
59char *colon_sep_section_list;
60char *roff_directive;
61char *dohp = 0;
62int do_irix;
63int do_win32;
64int apropos;
65int whatis;
66int nocats;			/* set by -c option: do not use cat page */
67				/* this means that cat pages must not be used,
68				   perhaps because the user knows they are
69				   old or corrupt or so */
70int can_use_cache;		/* output device is a tty, width 80 */
71				/* this means that the result may be written
72				   in /var/cache, and may be read from there */
73int findall;
74int print_where;
75int one_per_line;
76int do_troff;
77int preformat;
78int debug;
79int fhs;
80int fsstnd;
81int noautopath;
82int nocache;
83static int is_japanese;
84static char *language;
85static char **section_list;
86
87#ifdef DO_COMPRESS
88int do_compress = 1;
89#else
90int do_compress = 0;
91#endif
92
93#define BUFSIZE 8192
94
95/*
96 * Try to determine the line length to use.
97 * Preferences: 1. MANWIDTH, 2. ioctl, 3. COLUMNS, 4. 80
98 *
99 * joey, 950902
100 */
101
102#include <sys/ioctl.h>
103
104int line_length = 80;
105int ll = 0;
106
107static void
108get_line_length(void){
109     char *cp;
110     int width;
111
112     if (preformat) {
113	  line_length = 80;
114	  return;
115     }
116     if ((cp = getenv ("MANWIDTH")) != NULL && (width = atoi(cp)) > 0) {
117	  line_length = width;
118	  return;
119     }
120#ifdef TIOCGWINSZ
121     if (isatty(0) && isatty(1)) { /* Jon Tombs */
122	  struct winsize wsz;
123
124	  if(ioctl(0, TIOCGWINSZ, &wsz))
125	       perror("TIOCGWINSZ failed\n");
126	  else if(wsz.ws_col) {
127	       line_length = wsz.ws_col;
128	       return;
129	  }
130     }
131#endif
132     if ((cp = getenv ("COLUMNS")) != NULL && (width = atoi(cp)) > 0)
133	  line_length = width;
134     else
135	  line_length = 80;
136}
137
138static int
139setll(void) {
140     return
141	  (!do_troff && (line_length < 66 || line_length > 80)) ?
142	  line_length*9/10 : 0;
143}
144
145/* People prefer no page headings in their man screen output;
146   now ".pl 0" has a bad effect on .SH etc, so we need ".pl N"
147   for some large number N, like 1100i (a hundred pages). */
148#define VERY_LONG_PAGE	"1100i"
149
150static char *
151setpl(void) {
152     char *pl;
153
154     /* Short-circuit bogus behavior (3828722). */
155     return NULL;
156
157     if (do_troff)
158	  return NULL;
159     if (preformat)
160	  pl = VERY_LONG_PAGE;
161     else
162     if ((pl = getenv("MANPL")) == 0) {
163	  if (isatty(0) && isatty(1))
164	       pl = VERY_LONG_PAGE;
165	  else
166	       pl = "11i";		/* old troff default */
167     }
168     return pl;
169}
170
171/*
172 * Check to see if the argument is a valid section number.  If the
173 * first character of name is a numeral, or the name matches one of
174 * the sections listed in section_list, we'll assume that it's a section.
175 * The list of sections in config.h simply allows us to specify oddly
176 * named directories like .../man3f.  Yuk.
177 */
178static char *
179is_section (char *name) {
180     char **vs;
181
182     /* 3Xt may be a section, but 3DBorder is a man page */
183     if (isdigit (name[0]) && !isdigit (name[1]) && strlen(name) < 5)
184	  return my_strdup (name);
185
186     for (vs = section_list; *vs != NULL; vs++)
187	  if (strcmp (*vs, name) == 0)
188	       return my_strdup (name);
189
190     return NULL;
191}
192
193
194static void
195remove_file (char *file) {
196     int i;
197
198     i = unlink (file);
199
200     if (debug) {
201	  if (i)
202	       perror(file);
203	  else
204	       gripe (UNLINKED, file);
205     }
206}
207
208static void
209remove_other_catfiles (const char *catfile) {
210     char *pathname;
211     char *t;
212     char **gf;
213     int offset;
214
215     pathname = my_strdup(catfile);
216     t = rindex(pathname, '.');
217     if (t == NULL || strcmp(t, getval("COMPRESS_EXT")))
218	  return;
219     offset = t - pathname;
220     strcpy(t, "*");
221     gf = glob_filename (pathname);
222
223     if (gf != (char **) -1 && gf != NULL) {
224	  for ( ; *gf; gf++) {
225	       /*
226		* Only remove files with a known extension, like .Z
227		* (otherwise we might kill a lot when called with
228		* catfile = ".gz" ...)
229		*/
230	       if (strlen (*gf) <= offset) {
231		    if (strlen (*gf) == offset)  /* uncompressed version */
232			 remove_file (*gf);
233		    continue;
234	       }
235
236	       if (!strcmp (*gf + offset, getval("COMPRESS_EXT")))
237		    continue;
238
239	       if (get_expander (*gf) != NULL)
240		    remove_file (*gf);
241	  }
242     }
243}
244
245/*
246 * Simply display the preformatted page.
247 */
248static int
249display_cat_file (const char *file) {
250     int found;
251
252     if (preformat)
253	  return 1;		/* nothing to do - preformat only */
254
255     found = 0;
256
257     if (access (file, R_OK) == 0 && different_cat_file(file)) {
258	  char *command = NULL;
259	  const char *expander = get_expander (file);
260
261	  if (expander != NULL && expander[0] != 0) {
262	       if (isatty(1))
263		    command = my_xsprintf("%s %S | %s", expander, file, pager);
264	       else
265		    command = my_xsprintf("%s %S", expander, file);
266	  } else {
267	       if (isatty(1)) {
268		    command = my_xsprintf("%s %S", pager, file);
269	       } else {
270		    const char *cat = getval("CAT");
271		    command = my_xsprintf("%s %S", cat[0] ? cat : "cat", file);
272	       }
273	  }
274	  found = !do_system_command (command, 0);
275     }
276     return found;
277}
278
279/*
280 * Simply display the preformatted page.
281 */
282static int
283display_html_file (const char *file) {
284     int found;
285
286     found = 0;
287
288     if (access (file, R_OK) == 0 && different_cat_file(file)) {
289	  char *command = NULL;
290
291	  if (isatty(1)) {
292	       command = my_xsprintf("%s %S", browser, file);
293	  } else {
294	       command = my_xsprintf("%s %S", htmlpager, file);
295	  }
296	  found = !do_system_command (command, 0);
297     }
298     return found;
299
300     return 1;
301}
302
303/*
304 * Try to find the ultimate source file.  If the first line of the
305 * current file is not of the form
306 *
307 *      .so man3/printf.3s
308 *
309 * the input file name is returned.
310 *
311 * For /cd/usr/src/usr.bin/util-linux-1.5/mount/umount.8.gz
312 * (which contains `.so man8/mount.8')
313 * we return /cd/usr/src/usr.bin/util-linux-1.5/mount/mount.8.gz .
314 *
315 * For /usr/man/man3/TIFFScanlineSize.3t
316 * (which contains `.so TIFFsize.3t')
317 * we return /usr/man/man3/TIFFsize.3t .
318 */
319static const char *
320ultimate_source (const char *name0) {
321     FILE *fp;
322     char *name;
323     const char *expander;
324     int expfl = 0;
325     char *fgr;
326     char *beg;
327     char *end;
328     char *cp;
329     char buf[BUFSIZE];
330     static char ultname[BUFSIZE];
331
332     if (strlen(name0) >= sizeof(ultname))
333	     return name0;
334     strcpy(ultname, name0);
335     name = ultname;
336
337again:
338     expander = get_expander (name);
339     if (expander && *expander) {
340	  char *command;
341
342	  command = my_xsprintf ("%s '%Q'", expander, name);
343	  fp = my_popen (command, "r");
344	  if (fp == NULL) {
345	       perror("popen");
346	       gripe (EXPANSION_FAILED, command);
347	       return (NULL);
348	  }
349	  fgr = fgets (buf, sizeof(buf), fp);
350	  pclose (fp);
351	  expfl = 1;
352     } else {
353	  fp = fopen (name, "r");
354	  if (fp == NULL && expfl) {
355	       char *extp = rindex (name0, '.');
356	       if (extp && *extp && strlen(name)+strlen(extp) < BUFSIZE) {
357		    strcat(name, extp);
358		    fp = fopen (name, "r");
359	       }
360	  }
361	  /*
362	   * Some people have compressed man pages, but uncompressed
363	   * .so files - we could glob for all possible extensions,
364	   * for now: only try .gz
365	   */
366	  else if (fp == NULL && get_expander(".gz") &&
367		   strlen(name)+strlen(".gz") < BUFSIZE) {
368	       strcat(name, ".gz");
369	       fp = fopen (name, "r");
370	  }
371
372	  if (fp == NULL) {
373	       perror("fopen");
374	       gripe (OPEN_ERROR, name);
375	       return (NULL);
376	  }
377	  fgr = fgets (buf, sizeof(buf), fp);
378	  fclose (fp);
379     }
380
381     if (fgr == NULL) {
382	  perror("fgets");
383	  gripe (READ_ERROR, name);
384	  return (NULL);
385     }
386
387     if (!isascii(buf[0]))
388	  return (NULL);
389
390     if (strncmp(buf, ".so", 3))
391	  return (my_strdup(name));
392
393     beg = buf+3;
394     while (*beg == ' ' || *beg == '\t')
395	  beg++;
396
397     end = beg;
398     while (*end != ' ' && *end != '\t' && *end != '\n' && *end != '\0')
399	  end++;		/* note that buf is NUL-terminated */
400     *end = '\0';
401
402     /* If name ends in path/manx/foo.9x then use path, otherwise
403	try same directory. */
404     if ((cp = rindex(name, '/')) == NULL) /* very strange ... */
405	  return 0;
406     *cp = 0;
407
408     /* allow "man ./foo.3" where foo.3 contains ".so man2/bar.2" */
409     if ((cp = rindex(name, '/')) != NULL && !strcmp(cp+1, "."))
410	  *cp = 0;
411
412     /* In all cases, the new name will be something from name
413	followed by something from beg. */
414     if (strlen(name) + strlen(beg) + 1 >= BUFSIZ)
415	  return 0;		/* very long names, ignore */
416
417     if (beg[0] == '/') {
418	  strcpy(name, beg);
419     } else
420     if (!index(beg, '/')) {
421	  /* strange.. try same directory as the .so file */
422	  strcat(name, "/");
423	  strcat(name, beg);
424     } else if((cp = rindex(name, '/')) != NULL && !strncmp(cp+1, "man", 3)) {
425	  strcpy(cp+1, beg);
426     } else if((cp = rindex(beg, '/')) != NULL) {
427	  strcat(name, cp);
428     } else {
429	  strcat(name, "/");
430	  strcat(name, beg);
431     }
432
433     goto again;
434}
435
436static void
437add_directive (const char *d, const char *file, char *buf, int buflen) {
438     if ((d = getval(d)) != 0 && *d) {
439	  if (*buf == 0) {
440	       if (strlen(d) + strlen(file) + 2 + 2 > buflen) // 2 extra for the single quotes
441		    return;
442	       strcpy (buf, d);
443	       strcat (buf, " ");
444	       char *fileq = my_xsprintf("'%Q'", file);
445	       strcat (buf, fileq);
446	       free(fileq);
447	  } else {
448	       if (strlen(d) + strlen(buf) + 4 > buflen)
449		    return;
450	       strcat (buf, " | ");
451	       strcat (buf, d);
452	  }
453     }
454}
455
456static int
457is_lang_page (char *lang, const char *file) {
458	char lang_path[16] = "";
459
460	snprintf(lang_path, sizeof(lang_path), "/%s/", lang);
461	if (strstr(file, lang_path))
462		return 1;
463	if (strlen(lang) > 2) {
464		lang_path[3] = '/';
465		lang_path[4] = 0;
466		if (strstr(file, lang_path))
467			return 1;
468	}
469	return 0;
470}
471
472static int
473parse_roff_directive (char *cp, const char *file, char *buf, int buflen) {
474     char c;
475     int tbl_found = 0;
476     int use_jroff;
477
478     use_jroff = (is_japanese &&
479		   (strstr(file, "/jman/") || is_lang_page(language, file)));
480
481     while ((c = *cp++) != '\0') {
482	  switch (c) {
483	  case 'e':
484	       if (debug)
485		    gripe (FOUND_EQN);
486	       add_directive((do_troff ? "EQN" : use_jroff ? "JNEQN": "NEQN"),
487			     file, buf, buflen);
488	       break;
489
490	  case 'g':
491	       if (debug)
492		    gripe (FOUND_GRAP);
493	       add_directive ("GRAP", file, buf, buflen);
494	       break;
495
496	  case 'p':
497	       if (debug)
498		    gripe (FOUND_PIC);
499	       add_directive ("PIC", file, buf, buflen);
500	       break;
501
502	  case 't':
503	       if (debug)
504		    gripe (FOUND_TBL);
505	       tbl_found++;
506	       add_directive ("TBL", file, buf, buflen);
507	       break;
508
509	  case 'v':
510	       if (debug)
511		    gripe (FOUND_VGRIND);
512	       add_directive ("VGRIND", file, buf, buflen);
513	       break;
514
515	  case 'r':
516	       if (debug)
517		    gripe (FOUND_REFER);
518	       add_directive ("REFER", file, buf, buflen);
519	       break;
520
521	  case ' ':
522	  case '\t':
523	  case '\n':
524	       goto done;
525
526	  default:
527	       return -1;
528	  }
529     }
530
531done:
532     if (*buf == 0)
533	  return 1;
534
535     add_directive (do_troff ? "TROFF" : use_jroff ? "JNROFF" : "NROFF",
536		    "", buf, buflen);
537
538     if (tbl_found && !do_troff && *getval("COL"))
539	  add_directive ("COL", "", buf, buflen);
540
541     return 0;
542}
543
544static char *
545eos(char *s) {
546     while(*s) s++;
547     return s;
548}
549
550/*
551 * Create command to format FILE, in the directory PATH/manX
552 */
553static char *
554make_roff_command (const char *path, const char *file) {
555     FILE *fp;
556     static char buf [BUFSIZE];
557     char line [BUFSIZE], bufh [BUFSIZE], buft [BUFSIZE];
558     int status, ll;
559     char *cp, *fgr, *pl;
560     char *command = "";
561     const char *expander;
562     const char *converter;
563
564     /* if window size differs much from 80, try to adapt */
565     /* (but write only standard formatted files to the cat directory,
566	see can_use_cache) */
567     ll = setll();
568     pl = setpl();
569     if (ll && debug)
570	  gripe (NO_CAT_FOR_NONSTD_LL);
571
572     expander = get_expander (file);
573     converter = get_converter (path);
574
575     /* head */
576     bufh[0] = 0;
577     if (ll || pl) {
578	  /* some versions of echo do not accept the -e flag,
579	     so we just use two echo calls when needed */
580	  strcat(bufh, "(");
581	  if (ll) {
582	       /*
583		* We should set line length and title line length.
584		* However, a .lt command here fails, only
585		*  .ev 1; .lt ...; .ev helps for my version of groff.
586		* The LL assignment is needed by the mandoc macros.
587		*/
588	       sprintf(eos(bufh), "echo \".ll %d.%di\"; ", ll/10, ll%10);
589	       sprintf(eos(bufh), "echo \".nr LL %d.%di\"; ", ll/10, ll%10);
590#if 0
591	       sprintf(eos(bufh), "echo \".lt %d.%di\"; ", ll/10, ll%10);
592#endif
593	  }
594	  if (pl)
595	       sprintf(eos(bufh), "echo \".pl %.128s\"; ", pl);
596     }
597
598     /* tail */
599     buft[0] = 0;
600     if (ll || pl) {
601	  if (pl && !strcmp(pl, VERY_LONG_PAGE))
602	      /* At end of the nroff source, set the page length to
603		 the current position plus 10 lines.  This plus setpl()
604		 gives us a single page that just contains the whole
605		 man page. (William Webber, wew@cs.rmit.edu.au) */
606	      strcat(buft, "; echo \".\\\\\\\"\"; echo \".pl \\n(nlu+10\"");
607#if 0
608	      /* In case this doesnt work for some reason,
609		 michaelkjohnson suggests: I've got a simple
610		 awk invocation that I throw into the pipeline: */
611
612		 awk 'BEGIN {RS="\n\n\n\n*"} /.*/ {print}'
613#endif
614	  strcat(buft, ")");
615     }
616
617     if (expander && *expander) {
618	  if (converter && *converter)
619	     command = my_xsprintf("%s%s '%Q' | %s%s",
620				   bufh, expander, file, converter, buft);
621	  else
622	     command = my_xsprintf("%s%s '%Q'%s",
623				   bufh, expander, file, buft);
624     } else if (ll || pl) {
625	  const char *cat = getval("CAT");
626	  if (!cat || !*cat)
627		  cat = "cat";
628
629	  if (converter && *converter)
630	      command = my_xsprintf("%s%s '%Q' | %s%s",
631				    bufh, cat, file, converter, buft);
632	  else
633	      command = my_xsprintf("%s%s '%Q'%s",
634				    bufh, cat, file, buft);
635     }
636
637     if (strlen(command) >= sizeof(buf))
638	  exit(1);
639     strcpy(buf, command);
640
641     if (roff_directive != NULL) {
642	  if (debug)
643	       gripe (ROFF_FROM_COMMAND_LINE);
644
645	  status = parse_roff_directive (roff_directive, file,
646					 buf, sizeof(buf));
647
648	  if (status == 0)
649	       return buf;
650
651	  if (status == -1)
652	       gripe (ROFF_CMD_FROM_COMMANDLINE_ERROR);
653     }
654
655     if (expander && *expander) {
656	  char *cmd = my_xsprintf ("%s '%Q'", expander, file);
657	  fp = my_popen (cmd, "r");
658	  if (fp == NULL) {
659	       perror("popen");
660	       gripe (EXPANSION_FAILED, cmd);
661	       return (NULL);
662	  }
663	  fgr = fgets (line, sizeof(line), fp);
664	  pclose (fp);
665     } else {
666	  fp = fopen (file, "r");
667	  if (fp == NULL) {
668	       perror("fopen");
669	       gripe (OPEN_ERROR, file);
670	       return (NULL);
671	  }
672	  fgr = fgets (line, sizeof(line), fp);
673	  fclose (fp);
674     }
675
676     if (fgr == NULL) {
677	  perror("fgets");
678	  gripe (READ_ERROR, file);
679	  return (NULL);
680     }
681
682     cp = &line[0];
683     if (*cp++ == '\'' && *cp++ == '\\' && *cp++ == '"' && *cp++ == ' ') {
684	  if (debug)
685	       gripe (ROFF_FROM_FILE, file);
686
687	  status = parse_roff_directive (cp, file, buf, sizeof(buf));
688
689	  if (status == 0)
690	       return buf;
691
692	  if (status == -1)
693	       gripe (ROFF_CMD_FROM_FILE_ERROR, file);
694     }
695
696     if ((cp = getenv ("MANROFFSEQ")) != NULL) {
697	  if (debug)
698	       gripe (ROFF_FROM_ENV);
699
700	  status = parse_roff_directive (cp, file, buf, sizeof(buf));
701
702	  if (status == 0)
703	       return buf;
704
705	  if (status == -1)
706	       gripe (MANROFFSEQ_ERROR);
707     }
708
709     if (debug)
710	  gripe (USING_DEFAULT);
711
712     (void) parse_roff_directive ("t", file, buf, sizeof(buf));
713
714     return buf;
715}
716
717/*
718 * Try to format the man page and create a new formatted file.  Return
719 * 1 for success and 0 for failure.
720 */
721static int
722make_cat_file (const char *path, const char *man_file, const char *cat_file) {
723     int mode;
724     FILE *fp;
725     char *roff_command;
726     char *command = NULL;
727     struct stat statbuf;
728
729     /* _Before_ first, make sure we will write to a regular file. */
730     if (stat(cat_file, &statbuf) == 0) {
731	  if(!S_ISREG(statbuf.st_mode)) {
732	       if (debug)
733		    gripe (CAT_OPEN_ERROR, cat_file);
734	       return 0;
735	  }
736     }
737
738     /* First make sure we can write the file; create an empty file. */
739     /* If we are suid it must get mode 0666. */
740     if ((fp = fopen (cat_file, "w")) == NULL) {
741	  if (errno == ENOENT)		/* directory does not exist */
742	       return 0;
743
744	  /* If we cannot write the file, maybe we can delete it */
745	  if(unlink (cat_file) != 0 || (fp = fopen (cat_file, "w")) == NULL) {
746	       if (errno == EROFS) 	/* possibly a CDROM */
747		    return 0;
748	       if (debug)
749		    gripe (CAT_OPEN_ERROR, cat_file);
750	       if (!suid)
751		    return 0;
752
753	       /* maybe the real user can write it */
754	       /* note: just doing "> %s" gives the wrong exit status */
755	       command = my_xsprintf("cp /dev/null %S 2>/dev/null", cat_file);
756	       if (do_system_command(command, 1)) {
757		    if (debug)
758			 gripe (USER_CANNOT_OPEN_CAT);
759		    return 0;
760	       }
761	       if (debug)
762		    gripe (USER_CAN_OPEN_CAT);
763	  }
764     } else {
765	  /* we can write it - good */
766	  fclose (fp);
767
768	  /* but maybe the real user cannot - let's allow everybody */
769	  /* the mode is reset below */
770	  if (suid) {
771	       if (chmod (cat_file, 0666)) {
772		    /* probably we are sgid but not owner;
773		       just delete the file and create it again */
774		    if(unlink(cat_file) != 0) {
775			 command = my_xsprintf("rm %S", cat_file);
776			 (void) do_system_command (command, 1);
777		    }
778		    if ((fp = fopen (cat_file, "w")) != NULL)
779			 fclose (fp);
780	       }
781          }
782     }
783
784     roff_command = make_roff_command (path, man_file);
785     if (roff_command == NULL)
786	  return 0;
787     if (do_compress)
788	  /* The cd is necessary, because of .so commands,
789	     like .so man1/bash.1 in bash_builtins.1.
790	     But it changes the meaning of man_file and cat_file,
791	     if these are not absolute. */
792
793	  command = my_xsprintf("(cd %S && %s | %S > %S)", path,
794		   roff_command, getval("COMPRESS"), cat_file);
795     else
796	  command = my_xsprintf ("(cd %S && %s > %S)", path,
797		   roff_command, cat_file);
798
799     /*
800      * Don't let the user interrupt the system () call and screw up
801      * the formatted man page if we're not done yet.
802      */
803     signal (SIGINT, SIG_IGN);
804
805     gripe (PLEASE_WAIT);
806
807     if (!do_system_command (command, 0)) {
808	  /* success */
809	  mode = ((ruid != euid) ? 0644 : (rgid != egid) ? 0464 : 0444);
810	  if(chmod (cat_file, mode) != 0 && suid) {
811	       command = my_xsprintf ("chmod 0%o %S", mode, cat_file);
812	       (void) do_system_command (command, 1);
813	  }
814	  /* be silent about the success of chmod - it is not important */
815	  if (debug)
816	       gripe (CHANGED_MODE, cat_file, mode);
817     } else {
818	  /* something went wrong - remove garbage */
819	  if(unlink(cat_file) != 0 && suid) {
820	       command = my_xsprintf ("rm %S", cat_file);
821	       (void) do_system_command (command, 1);
822	  }
823     }
824
825     signal (SIGINT, SIG_DFL);
826
827     return 1;
828}
829
830static int
831display_man_file(const char *path, const char *man_file) {
832     char *roff_command;
833     char *command;
834
835     if (!different_man_file (man_file))
836	  return 0;
837     roff_command = make_roff_command (path, man_file);
838     if (roff_command == NULL)
839	  return 0;
840     if (do_troff)
841	  command = my_xsprintf ("(cd '%Q' && %s)", path, roff_command);
842     else
843	  command = my_xsprintf ("(cd '%Q' && %s | (%s || true))", path,
844		   roff_command, pager);
845
846     return !do_system_command (command, 0);
847}
848
849/*
850 * make and display the cat file - return 0 if something went wrong
851 */
852static int
853make_and_display_cat_file (const char *path, const char *man_file) {
854     const char *cat_file;
855     const char *ext;
856     int status;
857     int standards;
858
859     ext = (do_compress ? getval("COMPRESS_EXT") : 0);
860
861     standards = (fhs ? FHS : 0) | (fsstnd ? FSSTND : 0) | (dohp ? DO_HP : 0);
862
863     if ((cat_file = convert_to_cat(man_file, ext, standards)) == NULL)
864	  return 0;
865
866     if (debug)
867	  gripe (PROPOSED_CATFILE, cat_file);
868
869     /*
870      * If cat_file exists, check whether it is more recent.
871      * Otherwise, check for other cat files (maybe there are
872      * old .Z files that should be removed).
873      */
874
875     status = ((nocats | preformat) ? -2 : is_newer (man_file, cat_file));
876     if (debug)
877	  gripe (IS_NEWER_RESULT, status);
878     if (status == -1 || status == -3) {
879	  /* what? man_file does not exist anymore? */
880	  gripe (CANNOT_STAT, man_file);
881	  return(0);
882     }
883
884     if (status != 0 || access (cat_file, R_OK) != 0) {
885	  /*
886	   * Cat file is out of date (status = 1) or does not exist or is
887	   * empty or is to be rewritten (status = -2) or is unreadable.
888	   * Try to format and save it.
889	   */
890	  if (print_where) {
891	       printf ("%s\n", man_file);
892	       return 1;
893	  }
894
895	  if (!make_cat_file (path, man_file, cat_file))
896	       return 0;
897
898	  /*
899	   * If we just created this cat file, unlink any others.
900	   */
901	  if (status == -2 && do_compress)
902	       remove_other_catfiles(cat_file);
903     } else {
904	  /*
905	   * Formatting not necessary.  Cat file is newer than source
906	   * file, or source file is not present but cat file is.
907	   */
908	  if (print_where) {
909	       if (one_per_line) {
910		    /* addition by marty leisner - leisner@sdsp.mc.xerox.com */
911		    printf("%s\n", cat_file);
912		    printf("%s\n", man_file);
913	       } else
914		    printf ("%s (<-- %s)\n", cat_file, man_file);
915	       return 1;
916	  }
917     }
918     (void) display_cat_file (cat_file);
919     return 1;
920}
921
922/*
923 * Try to format the man page source and save it, then display it.  If
924 * that's not possible, try to format the man page source and display
925 * it directly.
926 */
927static int
928format_and_display (const char *man_file) {
929     const char *path;
930
931     if (access (man_file, R_OK) != 0)
932	  return 0;
933
934     path = mandir_of(man_file);
935     if (path == NULL)
936	  return 0;
937
938     /* first test for contents  .so man1/xyzzy.1  */
939     /* (in that case we do not want to make a cat file identical
940	to cat1/xyzzy.1) */
941     man_file = ultimate_source (man_file);
942     if (man_file == NULL)
943	  return 0;
944
945     if (do_troff) {
946	  char *command;
947	  char *roff_command = make_roff_command (path, man_file);
948
949	  if (roff_command == NULL)
950	       return 0;
951
952	  command = my_xsprintf("(cd %S && %s)", path, roff_command);
953	  return !do_system_command (command, 0);
954     }
955
956     if (can_use_cache && make_and_display_cat_file (path, man_file))
957	  return 1;
958
959     /* line length was wrong or could not display cat_file */
960     if (print_where) {
961	  printf ("%s\n", man_file);
962	  return 1;
963     }
964
965     return display_man_file (path, man_file);
966}
967
968/*
969 * Search for manual pages.
970 *
971 * If preformatted manual pages are supported, look for the formatted
972 * file first, then the man page source file.  If they both exist and
973 * the man page source file is newer, or only the source file exists,
974 * try to reformat it and write the results in the cat directory.  If
975 * it is not possible to write the cat file, simply format and display
976 * the man file.
977 *
978 * If preformatted pages are not supported, or the troff option is
979 * being used, only look for the man page source file.
980 *
981 * Note that globbing is necessary also if the section is given,
982 * since a preformatted man page might be compressed.
983 *
984 */
985static int
986man (const char *name, const char *section) {
987     int found, type, flags;
988     struct manpage *mp;
989
990     found = 0;
991
992     /* allow  man ./manpage  for formatting explicitly given man pages */
993     if (index(name, '/')) {
994	  char fullname[BUFSIZE];
995	  char fullpath[BUFSIZE];
996	  char *path;
997	  char *cp;
998	  FILE *fp = fopen(name, "r");
999
1000	  if (!fp) {
1001	       perror(name);
1002	       return 0;
1003	  }
1004	  fclose (fp);
1005	  if (*name != '/' && getcwd(fullname, sizeof(fullname))
1006	      && strlen(fullname) + strlen(name) + 3 < sizeof(fullname)) {
1007	       strcat (fullname, "/");
1008	       strcat (fullname, name);
1009	  } else if (strlen(name) + 2 < sizeof(fullname)) {
1010	       strcpy (fullname, name);
1011	  } else {
1012	       fprintf(stderr, "%s: name too long\n", name);
1013	       return 0;
1014	  }
1015
1016	  strcpy (fullpath, fullname);
1017	  if ((cp = rindex(fullpath, '/')) != NULL
1018	      && cp-fullpath+4 < sizeof(fullpath)) {
1019	       strcpy(cp+1, "..");
1020	       path = fullpath;
1021	  } else
1022	       path = ".";
1023
1024	  name = ultimate_source (fullname);
1025	  if (!name)
1026	       return 0;
1027
1028	  if (print_where) {
1029	       printf("%s\n", name);
1030	       return 1;
1031	  }
1032	  return display_man_file (path, name);
1033     }
1034
1035     fflush (stdout);
1036     init_manpath();
1037
1038     can_use_cache = nocache ? 0 : (preformat || print_where ||
1039		      (isatty(0) && isatty(1) && !setll()));
1040
1041     if (do_troff) {
1042	  const char *t = getval("TROFF");
1043	  if (!t || !*t)
1044	       return 0;	/* don't know how to format */
1045	  type = TYPE_MAN;
1046     } else {
1047	  const char *n = getval("NROFF");
1048	  type = 0;
1049	  if (can_use_cache)
1050	       type |= TYPE_CAT;
1051	  if (n && *n)
1052	       type |= TYPE_MAN;
1053	  if (fhs || fsstnd)
1054	       type |= TYPE_SCAT;
1055
1056	  n = getval("BROWSER");
1057	  if (n && *n)
1058	      type |= TYPE_HTML;
1059     }
1060
1061     flags = type;
1062     if (!findall)
1063	  flags |= ONLY_ONE;
1064     if (fsstnd)
1065	  flags |= FSSTND;
1066     else if (fhs)
1067	  flags |= FHS;
1068     if (dohp)
1069	  flags |= DO_HP;
1070     if (do_irix)
1071	  flags |= DO_IRIX;
1072     if (do_win32)
1073	  flags |= DO_WIN32;
1074
1075     mp = manfile(name, section, flags, section_list, mandirlist,
1076		  convert_to_cat);
1077     found = 0;
1078     while (mp) {
1079          if (mp->type == TYPE_MAN) {
1080	       found = format_and_display(mp->filename);
1081	  } else if (mp->type == TYPE_CAT || mp->type == TYPE_SCAT) {
1082               if (print_where) {
1083                    printf ("%s\n", mp->filename);
1084                    found = 1;
1085               } else
1086	            found = display_cat_file(mp->filename);
1087	  } else if (mp->type == TYPE_HTML) {
1088               if (print_where) {
1089                    printf ("%s\n", mp->filename);
1090                    found = 1;
1091               } else
1092	            found = display_html_file(mp->filename);
1093	  } else
1094	       /* internal error */
1095	       break;
1096	  if (found && !findall)
1097	       break;
1098	  mp = mp->next;
1099     }
1100     return found;
1101}
1102
1103static char **
1104get_section_list (void) {
1105     int i;
1106     const char *p;
1107     char *end;
1108     static char *tmp_section_list[100];
1109
1110     if (colon_sep_section_list == NULL) {
1111	  if ((p = getenv ("MANSECT")) == NULL)
1112	       p = getval ("MANSECT");
1113	  colon_sep_section_list = my_strdup (p);
1114     }
1115
1116     i = 0;
1117     for (p = colon_sep_section_list; ; p = end+1) {
1118	  if ((end = strchr (p, ':')) != NULL)
1119	       *end = '\0';
1120
1121	  tmp_section_list[i++] = my_strdup (p);
1122
1123	  if (end == NULL || i+1 == SIZE(tmp_section_list))
1124	       break;
1125     }
1126
1127     tmp_section_list [i] = NULL;
1128     return tmp_section_list;
1129}
1130
1131/* return 0 when all was OK */
1132static int
1133do_global_apropos (char *name, char *section) {
1134     char **dp, **gf;
1135     char *pathname;
1136     char *command;
1137     int status, res;
1138
1139     status = 0;
1140     init_manpath();
1141     if (mandirlist)
1142	for (dp = mandirlist; *dp; dp++) {
1143	  if (debug)
1144	       gripe(SEARCHING, *dp);
1145	  pathname = my_xsprintf("%s/man%s/*", *dp, section ? section : "*");
1146	  gf = glob_filename (pathname);
1147	  free(pathname);
1148
1149	  if (gf != (char **) -1 && gf != NULL) {
1150	       for( ; *gf; gf++) {
1151		    const char *expander = get_expander (*gf);
1152		    if (expander)
1153			 command = my_xsprintf("%s %S | grep '%Q'"
1154					       "> /dev/null 2> /dev/null",
1155				 expander, *gf, name);
1156		    else
1157			 command = my_xsprintf("grep '%Q' %S"
1158					       "> /dev/null 2> /dev/null",
1159				 name, *gf);
1160		    res = do_system_command (command, 1);
1161		    status |= res;
1162		    free (command);
1163		    if (res == 0) {
1164			 if (print_where)
1165			      printf("%s\n", *gf);
1166			 else {
1167			      /* should read LOCALE, but libc 4.6.27 doesn't
1168				 seem to handle LC_RESPONSE yet */
1169			      int answer, c;
1170			      char path[BUFSIZE];
1171
1172			      printf("%s? [ynq] ", *gf);
1173			      fflush(stdout);
1174			      answer = c = getchar();
1175			      while (c != '\n' && c != EOF)
1176				   c = getchar();
1177			      if(index("QqXx", answer))
1178				   exit(0);
1179			      if(index("YyJj", answer)) {
1180				   char *ri;
1181
1182				   strcpy(path, *gf);
1183				   ri = rindex(path, '/');
1184				   if (ri)
1185					*ri = 0;
1186				   format_and_display(*gf);
1187			      }
1188			 }
1189		    }
1190	       }
1191	  }
1192     }
1193     return status;
1194}
1195
1196/* Special code for Japanese (to pick jnroff instead of nroff, etc.) */
1197static void
1198setlang(void) {
1199	char *lang;
1200
1201	/* We use getenv() instead of setlocale(), because of
1202	   glibc 2.1.x security policy for SetUID/SetGID binary. */
1203	if ((lang = getenv("LANG")) == NULL &&
1204	    (lang = getenv("LC_ALL")) == NULL &&
1205	    (lang = getenv("LC_CTYPE")) == NULL)
1206		/* nothing */;
1207
1208	language = lang;
1209	is_japanese = (lang && strncmp(lang, "ja", 2) == 0);
1210}
1211
1212/*
1213 * Handle the apropos option.  Cheat by using another program.
1214 */
1215static int
1216do_apropos (char *name) {
1217	char *command;
1218
1219	command = my_xsprintf("'%s' '%Q'", getval("APROPOS"), name);
1220	return do_system_command (command, 0);
1221}
1222
1223/*
1224 * Handle the whatis option.  Cheat by using another program.
1225 */
1226static int
1227do_whatis (char *name) {
1228	char *command;
1229
1230	command = my_xsprintf("'%s' '%Q'", getval("WHATIS"), name);
1231	return do_system_command (command, 0);
1232}
1233
1234int
1235main (int argc, char **argv) {
1236     int status = 1;
1237     char *nextarg;
1238     char *tmp;
1239     char *section = 0;
1240
1241#ifdef __CYGWIN__
1242     extern int optind;
1243#endif
1244
1245
1246#if 0
1247     {
1248	/* There are no known cases of buffer overflow caused by
1249	   excessively long environment variables. In case you find one,
1250	   the simplistic way to fix is to enable this stopgap. */
1251	char *s;
1252#define CHECK(p,l) s=getenv(p); if(s && strlen(s)>(l)) { fprintf(stderr, "ERROR: Environment variable %s too long!\n", p); exit(1); }
1253	CHECK("LANG", 32);
1254	CHECK("LANGUAGE", 128);
1255	CHECK("LC_MESSAGES", 128);
1256	CHECK("MANPAGER", 128);
1257	CHECK("MANPL", 128);
1258	CHECK("MANROFFSEQ", 128);
1259	CHECK("MANSECT", 128);
1260	CHECK("MAN_HP_DIREXT", 128);
1261	CHECK("PAGER", 128);
1262	CHECK("SYSTEM", 64);
1263	CHECK("BROWSER", 64);
1264	CHECK("HTMLPAGER", 64);
1265	/* COLUMNS, LC_ALL, LC_CTYPE, MANPATH, MANWIDTH, MAN_IRIX_CATNAMES,
1266	   MAN_ICONV_PATH, MAN_ICONV_OPT, MAN_ICONV_INPUT_CHARSET,
1267	   MAN_ICONV_OUTPUT_CHARSET, NLSPATH, PATH */
1268     }
1269#endif
1270
1271
1272#ifndef __FreeBSD__
1273     /* Slaven Rezif: FreeBSD-2.2-SNAP does not recognize LC_MESSAGES. */
1274     setlocale(LC_CTYPE, "");	/* used anywhere? maybe only isdigit()? */
1275     setlocale(LC_MESSAGES, "");
1276#endif
1277
1278     /* No doubt we'll need some generic language code here later.
1279	For the moment only Japanese support. */
1280     setlang();
1281
1282     /* Handle /usr/man/man1.Z/name.1 nonsense from HP */
1283     dohp = getenv("MAN_HP_DIREXT");		/* .Z */
1284
1285     /* Handle ls.z (instead of ls.1.z) cat page naming from IRIX */
1286     if (getenv("MAN_IRIX_CATNAMES"))
1287	  do_irix = 1;
1288
1289     /* Handle lack of ':' in NTFS file names */
1290#if defined(_WIN32) || defined(__CYGWIN__)
1291     do_win32 = 1;
1292#endif
1293
1294     progname = mkprogname (argv[0]);
1295
1296     get_permissions ();
1297     get_line_length();
1298
1299     /*
1300      * read command line options and man.conf
1301      */
1302     man_getopt (argc, argv);
1303
1304     /*
1305      * manpath  or  man --path  or  man -w  will only print the manpath
1306      */
1307     if (!strcmp (progname, "manpath") || (optind == argc && print_where)) {
1308	  init_manpath();
1309	  prmanpath();
1310	  exit(0);
1311     }
1312
1313     if (optind == argc)
1314	  gripe(NO_NAME_NO_SECTION);
1315
1316     section_list = get_section_list ();
1317
1318     while (optind < argc) {
1319	  nextarg = argv[optind++];
1320
1321	  /* is_section correctly accepts 3Xt as section, but also 9wm,
1322	     so we should not believe is_section() for the last arg. */
1323	  tmp = is_section (nextarg);
1324	  if (tmp && optind < argc) {
1325		  section = tmp;
1326		  if (debug)
1327			  gripe (SECTION, section);
1328		  continue;
1329	  }
1330
1331	  if (global_apropos)
1332	       status = !do_global_apropos (nextarg, section);
1333	  else if (apropos)
1334	       status = !do_apropos (nextarg);
1335	  else if (whatis)
1336	       status = !do_whatis (nextarg);
1337	  else {
1338	       status = man (nextarg, section);
1339
1340	       if (status == 0) {
1341		    if (section)
1342			 gripe (NO_SUCH_ENTRY_IN_SECTION, nextarg, section);
1343		    else
1344			 gripe (NO_SUCH_ENTRY, nextarg);
1345	       }
1346	  }
1347     }
1348     return status ? EXIT_SUCCESS : EXIT_FAILURE;
1349}
1350