man.c.orig   [plain text]


/*
 * man.c
 *
 * Copyright (c) 1990, 1991, John W. Eaton.
 *
 * You may distribute under the terms of the GNU General Public
 * License as specified in the file COPYING that comes with the man
 * distribution.  
 *
 * John W. Eaton
 * jwe@che.utexas.edu
 * Department of Chemical Engineering
 * The University of Texas at Austin
 * Austin, Texas  78712
 *
 * Some manpath, compression and locale related changes - aeb - 940320
 * Some suid related changes - aeb - 941008
 * Some more fixes, Pauline Middelink & aeb, Oct 1994
 * man -K: aeb, Jul 1995
 * Split off of manfile for man2html, aeb, New Year's Eve 1997
 */

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <sys/file.h>
#include <sys/stat.h>		/* for chmod */
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <locale.h>

#ifndef R_OK
#define R_OK 4
#endif

extern char *index (const char *, int);		/* not always in <string.h> */
extern char *rindex (const char *, int);	/* not always in <string.h> */

#include "defs.h"
#include "gripes.h"
#include "man.h"
#include "manfile.h"
#include "manpath.h"
#include "man-config.h"
#include "man-getopt.h"
#include "man-iconv.h"
#include "to_cat.h"
#include "util.h"
#include "glob.h"
#include "different.h"
#include "man-iconv.h"

#define SIZE(x) (sizeof(x)/sizeof((x)[0]))

const char *progname;
const char *pager;
char *colon_sep_section_list;
char *roff_directive;
char *dohp = 0;
int do_irix;
int do_win32;
int apropos;
int whatis;
int nocats;			/* set by -c option: do not use cat page */
				/* this means that cat pages must not be used,
				   perhaps because the user knows they are
				   old or corrupt or so */
int can_use_cache;		/* output device is a tty, width 80 */
				/* this means that the result may be written
				   in /var/cache, and may be read from there */
int findall;
int print_where;
int one_per_line;
int do_troff;
int preformat;
int debug;
int fhs;
int fsstnd;
int noautopath;
int nocache;
static int is_japanese;
static char *language;
static char **section_list;

#ifdef DO_COMPRESS
int do_compress = 1;
#else
int do_compress = 0;
#endif

#define BUFSIZE 8192

/*
 * Try to determine the line length to use.
 * Preferences: 1. MANWIDTH, 2. ioctl, 3. COLUMNS, 4. 80
 *
 * joey, 950902
 */

#include <sys/ioctl.h>

int line_length = 80;
int ll = 0;

static void
get_line_length(void){
     char *cp;
     int width;

     if (preformat) {
	  line_length = 80;
	  return;
     }
     if ((cp = getenv ("MANWIDTH")) != NULL && (width = atoi(cp)) > 0) {
	  line_length = width;
	  return;
     }
#ifdef TIOCGWINSZ
     if (isatty(0) && isatty(1)) { /* Jon Tombs */
	  struct winsize wsz;

	  if(ioctl(0, TIOCGWINSZ, &wsz))
	       perror("TIOCGWINSZ failed\n");
	  else if(wsz.ws_col) {
	       line_length = wsz.ws_col;
	       return;
	  }
     }
#endif
     if ((cp = getenv ("COLUMNS")) != NULL && (width = atoi(cp)) > 0)
	  line_length = width;
     else
	  line_length = 80;
}

static int
setll(void) {
     /* Short-circuit bogus behavior (3828722). */
     return 0;

     return
	  (!do_troff && (line_length < 66 || line_length > 80)) ?
	  line_length*9/10 : 0;
}

/* People prefer no page headings in their man screen output;
   now ".pl 0" has a bad effect on .SH etc, so we need ".pl N"
   for some large number N, like 1100i (a hundred pages). */
#define VERY_LONG_PAGE	"1100i"

static char *
setpl(void) {
     char *pl;

     /* Short-circuit bogus behavior (3828722). */
     return NULL;

     if (do_troff)
	  return NULL;
     if (preformat)
	  pl = VERY_LONG_PAGE;
     else
     if ((pl = getenv("MANPL")) == 0) {
	  if (isatty(0) && isatty(1))
	       pl = VERY_LONG_PAGE;
	  else
	       pl = "11i";		/* old troff default */
     }
     return pl;
}

/*
 * Check to see if the argument is a valid section number.  If the
 * first character of name is a numeral, or the name matches one of
 * the sections listed in section_list, we'll assume that it's a section.
 * The list of sections in config.h simply allows us to specify oddly
 * named directories like .../man3f.  Yuk. 
 */
static char *
is_section (char *name) {
     char **vs;

     /* 3Xt may be a section, but 3DBorder is a man page */
     if (isdigit (name[0]) && !isdigit (name[1]) && strlen(name) < 5)
	  return my_strdup (name);

     for (vs = section_list; *vs != NULL; vs++)
	  if (strcmp (*vs, name) == 0)
	       return my_strdup (name);

     return NULL;
}


static void
remove_file (char *file) {
     int i;

     i = unlink (file);

     if (debug) {
	  if (i)
	       perror(file);
	  else
	       gripe (UNLINKED, file);
     }
}

static void
remove_other_catfiles (const char *catfile) {
     char *pathname;
     char *t;
     char **gf;
     int offset;

     pathname = my_strdup(catfile);
     t = rindex(pathname, '.');
     if (t == NULL || strcmp(t, getval("COMPRESS_EXT")))
	  return;
     offset = t - pathname;
     strcpy(t, "*");
     gf = glob_filename (pathname);

     if (gf != (char **) -1 && gf != NULL) {
	  for ( ; *gf; gf++) {
	       /*
		* Only remove files with a known extension, like .Z
		* (otherwise we might kill a lot when called with
		* catfile = ".gz" ...)
		*/
	       if (strlen (*gf) <= offset) {
		    if (strlen (*gf) == offset)  /* uncompressed version */
			 remove_file (*gf);
		    continue;
	       }

	       if (!strcmp (*gf + offset, getval("COMPRESS_EXT")))
		    continue;

	       if (get_expander (*gf) != NULL)
		    remove_file (*gf);
	  }
     }
}

/*
 * Simply display the preformatted page.
 */
static int
display_cat_file (const char *file) {
     int found;

     if (preformat)
	  return 1;		/* nothing to do - preformat only */

     found = 0;

     if (access (file, R_OK) == 0 && different_cat_file(file)) {
	  char *command = NULL;
	  const char *expander = get_expander (file);

	  if (expander != NULL && expander[0] != 0) {
	       if (isatty(1))
		    command = my_xsprintf("%s %S | %s", expander, file, pager);
	       else
		    command = my_xsprintf("%s %S", expander, file);
	  } else {
	       if (isatty(1)) {
		    command = my_xsprintf("%s %S", pager, file);
	       } else {
		    const char *cat = getval("CAT");
		    command = my_xsprintf("%s %S", cat[0] ? cat : "cat", file);
	       }
	  }
	  found = !do_system_command (command, 0);
     }
     return found;
}

/*
 * Try to find the ultimate source file.  If the first line of the
 * current file is not of the form
 *
 *      .so man3/printf.3s
 *
 * the input file name is returned.
 *
 * For /cd/usr/src/usr.bin/util-linux-1.5/mount/umount.8.gz
 * (which contains `.so man8/mount.8')
 * we return /cd/usr/src/usr.bin/util-linux-1.5/mount/mount.8.gz .
 *
 * For /usr/man/man3/TIFFScanlineSize.3t
 * (which contains `.so TIFFsize.3t')
 * we return /usr/man/man3/TIFFsize.3t .
 */
static const char *
ultimate_source (const char *name0) {
     FILE *fp;
     char *name;
     const char *expander;
     int expfl = 0;
     char *fgr;
     char *beg;
     char *end;
     char *cp;
     char buf[BUFSIZE];
     static char ultname[BUFSIZE];

     if (strlen(name0) >= sizeof(ultname))
	     return name0;
     strcpy(ultname, name0);
     name = ultname;

again:
     expander = get_expander (name);
     if (expander && *expander) {
	  char *command;

	  command = my_xsprintf ("%s %S", expander, name);
	  fp = my_popen (command, "r");
	  if (fp == NULL) {
	       perror("popen");
	       gripe (EXPANSION_FAILED, command);
	       return (NULL);
	  }
	  fgr = fgets (buf, sizeof(buf), fp);
	  pclose (fp);
	  expfl = 1;
     } else {
	  fp = fopen (name, "r");
	  if (fp == NULL && expfl) {
	       char *extp = rindex (name0, '.');
	       if (extp && *extp && strlen(name)+strlen(extp) < BUFSIZE) {
		    strcat(name, extp);
		    fp = fopen (name, "r");
	       }
	  }
	  /*
	   * Some people have compressed man pages, but uncompressed
	   * .so files - we could glob for all possible extensions,
	   * for now: only try .gz
	   */
	  else if (fp == NULL && get_expander(".gz") &&
		   strlen(name)+strlen(".gz") < BUFSIZE) {
	       strcat(name, ".gz");
	       fp = fopen (name, "r");
	  }

	  if (fp == NULL) {
	       perror("fopen");
	       gripe (OPEN_ERROR, name);
	       return (NULL);
	  }
	  fgr = fgets (buf, sizeof(buf), fp);
	  fclose (fp);
     }

     if (fgr == NULL) {
	  perror("fgets");
	  gripe (READ_ERROR, name);
	  return (NULL);
     }

     if (strncmp(buf, ".so", 3))
	  return (my_strdup(name));

     beg = buf+3;
     while (*beg == ' ' || *beg == '\t')
	  beg++;

     end = beg;
     while (*end != ' ' && *end != '\t' && *end != '\n' && *end != '\0')
	  end++;		/* note that buf is NUL-terminated */
     				/* hence the RH patch is superfluous */

     *end = '\0';

     /* If name ends in path/manx/foo.9x then use path, otherwise
	try same directory. */
     if ((cp = rindex(name, '/')) == NULL) /* very strange ... */
	  return 0;
     *cp = 0;

     /* allow "man ./foo.3" where foo.3 contains ".so man2/bar.2" */
     if ((cp = rindex(name, '/')) != NULL && !strcmp(cp+1, "."))
	  *cp = 0;

     /* In all cases, the new name will be something from name
	followed by something from beg. */
     if (strlen(name) + strlen(beg) + 1 >= BUFSIZ)
	  return 0;		/* very long names, ignore */

     if (!index(beg, '/')) {
	  /* strange.. try same directory as the .so file */
	  strcat(name, "/");
	  strcat(name, beg);
     } else if((cp = rindex(name, '/')) != NULL && !strncmp(cp+1, "man", 3)) {
	  strcpy(cp+1, beg);
     } else if((cp = rindex(beg, '/')) != NULL) {
	  strcat(name, cp);
     } else {
	  strcat(name, "/");
	  strcat(name, beg);
     }

     goto again;
}

static void
add_directive (const char *d, const char *file, char *buf, int buflen) {
     if ((d = getval(d)) != 0 && *d) {
	  if (*buf == 0) {
	       if (strlen(d) + strlen(file) + 2 > buflen)
		    return;
	       strcpy (buf, d);
	       strcat (buf, " ");
	       strcat (buf, file);
	  } else {
	       if (strlen(d) + strlen(buf) + 4 > buflen)
		    return;
	       strcat (buf, " | ");
	       strcat (buf, d);
	  }
     }
}

static int
is_lang_page (char *lang, const char *file) {
	char lang_path[16] = "";

	snprintf(lang_path, sizeof(lang_path), "/%s/", lang);
	if (strstr(file, lang_path))
		return 1;
	if (strlen(lang) > 2) {
		lang_path[3] = '/';
		lang_path[4] = 0;
		if (strstr(file, lang_path))
			return 1;
	}
	return 0;
}

static int
parse_roff_directive (char *cp, const char *file, char *buf, int buflen) {
     char c;
     int tbl_found = 0;
     int use_jroff;

     use_jroff = (is_japanese &&
		   (strstr(file, "/jman/") || is_lang_page(language, file)));

     while ((c = *cp++) != '\0') {
	  switch (c) {
	  case 'e':
	       if (debug)
		    gripe (FOUND_EQN);
	       add_directive((do_troff ? "EQN" : use_jroff ? "JNEQN": "NEQN"),
			     file, buf, buflen);
	       break;

	  case 'g':
	       if (debug)
		    gripe (FOUND_GRAP);
	       add_directive ("GRAP", file, buf, buflen);
	       break;

	  case 'p':
	       if (debug)
		    gripe (FOUND_PIC);
	       add_directive ("PIC", file, buf, buflen);
	       break;

	  case 't':
	       if (debug)
		    gripe (FOUND_TBL);
	       tbl_found++;
	       add_directive ("TBL", file, buf, buflen);
	       break;

	  case 'v':
	       if (debug)
		    gripe (FOUND_VGRIND);
	       add_directive ("VGRIND", file, buf, buflen);
	       break;

	  case 'r':
	       if (debug)
		    gripe (FOUND_REFER);
	       add_directive ("REFER", file, buf, buflen);
	       break;

	  case ' ':
	  case '\t':
	  case '\n':
	       goto done;

	  default:
	       return -1;
	  }
     }

done:
     if (*buf == 0)
	  return 1;

     add_directive (do_troff ? "TROFF" : use_jroff ? "JNROFF" : "NROFF",
		    "", buf, buflen);

     if (tbl_found && !do_troff && *getval("COL"))
	  add_directive ("COL", "", buf, buflen);

     return 0;
}

static char *
eos(char *s) {
     while(*s) s++;
     return s;
}

/*
 * Create command to format FILE, in the directory PATH/manX
 */
static char *
make_roff_command (const char *path, const char *file) {
     FILE *fp;
     static char buf [BUFSIZE];
     char line [BUFSIZE], bufh [BUFSIZE], buft [BUFSIZE];
     int status, ll;
     char *cp, *fgr, *pl;
     char *command = "";
     const char *expander;
     const char *converter;

     /* if window size differs much from 80, try to adapt */
     /* (but write only standard formatted files to the cat directory,
	see can_use_cache) */
     ll = setll();
     pl = setpl();
     if (ll && debug)
	  gripe (NO_CAT_FOR_NONSTD_LL);

     expander = get_expander (file);
     converter = get_converter (path);

     /* head */
     bufh[0] = 0;
     if (ll || pl) {
	  /* some versions of echo do not accept the -e flag,
	     so we just use two echo calls when needed */
	  strcat(bufh, "(");
	  if (ll) {
	       /* we should set line length and title line length */
	       /* however, a .lt command here fails, only
		  .ev 1; .lt ...; .ev helps for my version of groff */
	       sprintf(eos(bufh), "echo \".ll %d.%di\"; ", ll/10, ll%10);
#if 0
	       sprintf(eos(bufh), "echo \".lt %d.%di\"; ", ll/10, ll%10);
#endif
	  }
	  if (pl)
	       sprintf(eos(bufh), "echo \".pl %.128s\"; ", pl);
     }

     /* tail */
     buft[0] = 0;
     if (ll || pl) {
	  if (pl && !strcmp(pl, VERY_LONG_PAGE))
	      /* At end of the nroff source, set the page length to
		 the current position plus 10 lines.  This plus setpl()
		 gives us a single page that just contains the whole
		 man page. (William Webber, wew@cs.rmit.edu.au) */
	      strcat(buft, "; echo \".\\\\\\\"\"; echo \".pl \\n(nlu+10\"");
#if 0
	      /* In case this doesnt work for some reason,
		 michaelkjohnson suggests: I've got a simple
		 awk invocation that I throw into the pipeline: */

		 awk 'BEGIN {RS="\n\n\n\n*"} /.*/ {print}'
#endif
	  strcat(buft, ")");
     }

     if (expander && *expander) {
	  if (converter && *converter)
	     command = my_xsprintf("%s%s '%S' | %s%s",
				   bufh, expander, file, converter, buft);
	  else
	     command = my_xsprintf("%s%s '%S'%s",
				   bufh, expander, file, buft);
     } else if (ll || pl) {
	  const char *cat = getval("CAT");
	  if (!cat || !*cat)
		  cat = "cat";

	  if (converter && *converter)
	      command = my_xsprintf("%s%s '%S' | %s%s",
				    bufh, cat, file, converter, buft);
	  else
	      command = my_xsprintf("%s%s '%S'%s",
				    bufh, cat, file, buft);
     }

     if (strlen(command) >= sizeof(buf))
	  exit(1);
     strcpy(buf, command);

     if (roff_directive != NULL) {
	  if (debug)
	       gripe (ROFF_FROM_COMMAND_LINE);

	  status = parse_roff_directive (roff_directive, file,
					 buf, sizeof(buf));

	  if (status == 0)
	       return buf;

	  if (status == -1)
	       gripe (ROFF_CMD_FROM_COMMANDLINE_ERROR);
     }

     if (expander && *expander) {
	  char *cmd = my_xsprintf ("%s %S", expander, file);
	  fp = my_popen (cmd, "r");
	  if (fp == NULL) {
	       perror("popen");
	       gripe (EXPANSION_FAILED, cmd);
	       return (NULL);
	  }
	  fgr = fgets (line, sizeof(line), fp);
	  pclose (fp);
     } else {
	  fp = fopen (file, "r");
	  if (fp == NULL) {
	       perror("fopen");
	       gripe (OPEN_ERROR, file);
	       return (NULL);
	  }
	  fgr = fgets (line, sizeof(line), fp);
	  fclose (fp);
     }

     if (fgr == NULL) {
	  perror("fgets");
	  gripe (READ_ERROR, file);
	  return (NULL);
     }

     cp = &line[0];
     if (*cp++ == '\'' && *cp++ == '\\' && *cp++ == '"' && *cp++ == ' ') {
	  if (debug)
	       gripe (ROFF_FROM_FILE, file);

	  status = parse_roff_directive (cp, file, buf, sizeof(buf));

	  if (status == 0)
	       return buf;

	  if (status == -1)
	       gripe (ROFF_CMD_FROM_FILE_ERROR, file);
     }

     if ((cp = getenv ("MANROFFSEQ")) != NULL) {
	  if (debug)
	       gripe (ROFF_FROM_ENV);

	  status = parse_roff_directive (cp, file, buf, sizeof(buf));

	  if (status == 0)
	       return buf;

	  if (status == -1)
	       gripe (MANROFFSEQ_ERROR);
     }

     if (debug)
	  gripe (USING_DEFAULT);

     (void) parse_roff_directive ("t", file, buf, sizeof(buf));

     return buf;
}

/*
 * Try to format the man page and create a new formatted file.  Return
 * 1 for success and 0 for failure.
 */
static int
make_cat_file (const char *path, const char *man_file, const char *cat_file) {
     int mode;
     FILE *fp;
     char *roff_command;
     char *command = NULL;
     struct stat statbuf;

     /* _Before_ first, make sure we will write to a regular file. */
     if (stat(cat_file, &statbuf) == 0) {
	  if(!S_ISREG(statbuf.st_mode)) {
	       if (debug)
		    gripe (CAT_OPEN_ERROR, cat_file);
	       return 0;
	  }
     }

     /* First make sure we can write the file; create an empty file. */
     /* If we are suid it must get mode 0666. */
     if ((fp = fopen (cat_file, "w")) == NULL) {
	  if (errno == ENOENT)		/* directory does not exist */
	       return 0;

	  /* If we cannot write the file, maybe we can delete it */
	  if(unlink (cat_file) != 0 || (fp = fopen (cat_file, "w")) == NULL) {
	       if (errno == EROFS) 	/* possibly a CDROM */
		    return 0;
	       if (debug)
		    gripe (CAT_OPEN_ERROR, cat_file);
	       if (!suid)
		    return 0;

	       /* maybe the real user can write it */
	       /* note: just doing "> %s" gives the wrong exit status */
	       command = my_xsprintf("cp /dev/null %S 2>/dev/null", cat_file);
	       if (do_system_command(command, 1)) {
		    if (debug)
			 gripe (USER_CANNOT_OPEN_CAT);
		    return 0;
	       }
	       if (debug)
		    gripe (USER_CAN_OPEN_CAT);
	  }
     } else {
	  /* we can write it - good */
	  fclose (fp);

	  /* but maybe the real user cannot - let's allow everybody */
	  /* the mode is reset below */
	  if (suid) {
	       if (chmod (cat_file, 0666)) {
		    /* probably we are sgid but not owner;
		       just delete the file and create it again */
		    if(unlink(cat_file) != 0) {
			 command = my_xsprintf("rm %S", cat_file);
			 (void) do_system_command (command, 1);
		    }
		    if ((fp = fopen (cat_file, "w")) != NULL)
			 fclose (fp);
	       }
          }
     }

     roff_command = make_roff_command (path, man_file);
     if (roff_command == NULL)
	  return 0;
     if (do_compress)
	  /* The cd is necessary, because of .so commands,
	     like .so man1/bash.1 in bash_builtins.1.
	     But it changes the meaning of man_file and cat_file,
	     if these are not absolute. */
	
	  command = my_xsprintf("(cd %S && %s | %S > %S)", path,
		   roff_command, getval("COMPRESS"), cat_file);
     else
	  command = my_xsprintf ("(cd %S && %s > %S)", path,
		   roff_command, cat_file);

     /*
      * Don't let the user interrupt the system () call and screw up
      * the formatted man page if we're not done yet.
      */
     signal (SIGINT, SIG_IGN);

     gripe (PLEASE_WAIT);

     if (!do_system_command (command, 0)) {
	  /* success */
	  mode = ((ruid != euid) ? 0644 : (rgid != egid) ? 0464 : 0444);
	  if(chmod (cat_file, mode) != 0 && suid) {
	       command = my_xsprintf ("chmod 0%o %S", mode, cat_file);
	       (void) do_system_command (command, 1);
	  }
	  /* be silent about the success of chmod - it is not important */
	  if (debug)
	       gripe (CHANGED_MODE, cat_file, mode);
     } else {
	  /* something went wrong - remove garbage */
	  if(unlink(cat_file) != 0 && suid) {
	       command = my_xsprintf ("rm %S", cat_file);
	       (void) do_system_command (command, 1);
	  }
     }

     signal (SIGINT, SIG_DFL);

     return 1;
}

static int
display_man_file(const char *path, const char *man_file) {
     char *roff_command;
     char *command;

     if (!different_man_file (man_file))
	  return 0;
     roff_command = make_roff_command (path, man_file);
     if (roff_command == NULL)
	  return 0;
     if (do_troff)
	  command = my_xsprintf ("(cd %S && %s)", path, roff_command);
     else
	  command = my_xsprintf ("(cd %S && %s | %s)", path,
		   roff_command, pager);

     return !do_system_command (command, 0);
}

/*
 * make and display the cat file - return 0 if something went wrong
 */
static int
make_and_display_cat_file (const char *path, const char *man_file) {
     const char *cat_file;
     const char *ext;
     int status;
     int standards;

     ext = (do_compress ? getval("COMPRESS_EXT") : 0);

     standards = (fhs ? FHS : 0) | (fsstnd ? FSSTND : 0) | (dohp ? DO_HP : 0);

     if ((cat_file = convert_to_cat(man_file, ext, standards)) == NULL)
	  return 0;

     if (debug)
	  gripe (PROPOSED_CATFILE, cat_file);

     /*
      * If cat_file exists, check whether it is more recent.
      * Otherwise, check for other cat files (maybe there are
      * old .Z files that should be removed).
      */

     status = ((nocats | preformat) ? -2 : is_newer (man_file, cat_file));
     if (debug)
	  gripe (IS_NEWER_RESULT, status);
     if (status == -1 || status == -3) {
	  /* what? man_file does not exist anymore? */
	  gripe (CANNOT_STAT, man_file);
	  return(0);
     }

     if (status != 0 || access (cat_file, R_OK) != 0) {
	  /*
	   * Cat file is out of date (status = 1) or does not exist or is
	   * empty or is to be rewritten (status = -2) or is unreadable.
	   * Try to format and save it.
	   */
	  if (print_where) {
	       printf ("%s\n", man_file);
	       return 1;
	  }

	  if (!make_cat_file (path, man_file, cat_file))
	       return 0;

	  /*
	   * If we just created this cat file, unlink any others.
	   */
	  if (status == -2 && do_compress)
	       remove_other_catfiles(cat_file);
     } else {
	  /*
	   * Formatting not necessary.  Cat file is newer than source
	   * file, or source file is not present but cat file is.
	   */
	  if (print_where) {
	       if (one_per_line) {
		    /* addition by marty leisner - leisner@sdsp.mc.xerox.com */
		    printf("%s\n", cat_file);
		    printf("%s\n", man_file);
	       } else
		    printf ("%s (<-- %s)\n", cat_file, man_file);
	       return 1;
	  }
     }
     (void) display_cat_file (cat_file);
     return 1;
}

/*
 * Try to format the man page source and save it, then display it.  If
 * that's not possible, try to format the man page source and display
 * it directly.
 */
static int
format_and_display (const char *man_file) {
     const char *path;

     if (access (man_file, R_OK) != 0)
	  return 0;

     path = mandir_of(man_file);
     if (path == NULL)
	  return 0;

     /* first test for contents  .so man1/xyzzy.1  */
     /* (in that case we do not want to make a cat file identical
	to cat1/xyzzy.1) */
     man_file = ultimate_source (man_file);
     if (man_file == NULL)
	  return 0;

     if (do_troff) {
	  char *command;
	  char *roff_command = make_roff_command (path, man_file);

	  if (roff_command == NULL)
	       return 0;

	  command = my_xsprintf("(cd %S && %s)", path, roff_command);
	  return !do_system_command (command, 0);
     }

     if (can_use_cache && make_and_display_cat_file (path, man_file))
	  return 1;

     /* line length was wrong or could not display cat_file */
     if (print_where) {
	  printf ("%s\n", man_file);
	  return 1;
     }

     return display_man_file (path, man_file);
}

/*
 * Search for manual pages.
 *
 * If preformatted manual pages are supported, look for the formatted
 * file first, then the man page source file.  If they both exist and
 * the man page source file is newer, or only the source file exists,
 * try to reformat it and write the results in the cat directory.  If
 * it is not possible to write the cat file, simply format and display
 * the man file.
 *
 * If preformatted pages are not supported, or the troff option is
 * being used, only look for the man page source file.
 *
 * Note that globbing is necessary also if the section is given,
 * since a preformatted man page might be compressed.
 *
 */
static int
man (const char *name, const char *section) {
     int found, type, flags;
     struct manpage *mp;

     found = 0;

     /* allow  man ./manpage  for formatting explicitly given man pages */
     if (index(name, '/')) {
	  char fullname[BUFSIZE];
	  char fullpath[BUFSIZE];
	  char *path;
	  char *cp;
	  FILE *fp = fopen(name, "r");

	  if (!fp) {
	       perror(name);
	       return 0;
	  }
	  fclose (fp);
	  if (*name != '/' && getcwd(fullname, sizeof(fullname))
	      && strlen(fullname) + strlen(name) + 3 < sizeof(fullname)) {
	       strcat (fullname, "/");
	       strcat (fullname, name);
	  } else if (strlen(name) + 2 < sizeof(fullname)) {
	       strcpy (fullname, name);
	  } else {
	       fprintf(stderr, "%s: name too long\n", name);
	       return 0;
	  }

	  strcpy (fullpath, fullname);
	  if ((cp = rindex(fullpath, '/')) != NULL
	      && cp-fullpath+4 < sizeof(fullpath)) {
	       strcpy(cp+1, "..");
	       path = fullpath;
	  } else
	       path = ".";

	  name = ultimate_source (fullname);
	  if (!name)
	       return 0;

	  if (print_where) {
	       printf("%s\n", name);
	       return 1;
	  }
	  return display_man_file (path, name);
     }

     fflush (stdout);
     init_manpath();

     can_use_cache = nocache ? 0 : (preformat || print_where ||
		      (isatty(0) && isatty(1) && !setll()));

     if (do_troff) {
	  const char *t = getval("TROFF");
	  if (!t || !*t)
	       return 0;	/* don't know how to format */
	  type = TYPE_MAN;
     } else {
	  const char *n = getval("NROFF");
	  type = 0;
	  if (can_use_cache)
	       type |= TYPE_CAT;
	  if (n && *n)
	       type |= TYPE_MAN;
	  if (fhs || fsstnd)
	       type |= TYPE_SCAT;
     }

     flags = type;
     if (!findall)
	  flags |= ONLY_ONE;
     if (fsstnd)
	  flags |= FSSTND;
     else if (fhs)
	  flags |= FHS;
     if (dohp)
	  flags |= DO_HP;
     if (do_irix)
	  flags |= DO_IRIX;
     if (do_win32)
	  flags |= DO_WIN32;

     mp = manfile(name, section, flags, section_list, mandirlist,
		  convert_to_cat);
     found = 0;
     while (mp) {
          if (mp->type == TYPE_MAN) {
	       found = format_and_display(mp->filename);
	  } else if (mp->type == TYPE_CAT || mp->type == TYPE_SCAT) {
               if (print_where) {
                    printf ("%s\n", mp->filename);
                    found = 1;
               } else
	            found = display_cat_file(mp->filename);
	  } else
	       /* internal error */
	       break;
	  if (found && !findall)
	       break;
	  mp = mp->next;
     }
     return found;
}

static char **
get_section_list (void) {
     int i;
     const char *p;
     char *end;
     static char *tmp_section_list[100];

     if (colon_sep_section_list == NULL) {
	  if ((p = getenv ("MANSECT")) == NULL)
	       p = getval ("MANSECT");
	  colon_sep_section_list = my_strdup (p);
     }

     i = 0;
     for (p = colon_sep_section_list; ; p = end+1) {
	  if ((end = strchr (p, ':')) != NULL)
	       *end = '\0';

	  tmp_section_list[i++] = my_strdup (p);

	  if (end == NULL || i+1 == SIZE(tmp_section_list))
	       break;
     }

     tmp_section_list [i] = NULL;
     return tmp_section_list;
}

static void
do_global_apropos (char *name, char *section) {
     char **dp, **gf;
     char *pathname;
     char *command;
     int res;

     /* do_global_apropos() produces a long stream of `system' commands,
	and during the system() call SIGINT and SIGQUIT are being ignored,
	so `man -K' is difficult to interrupt.
	However, ^Z still works, and can be followed by `kill %1'. */

     init_manpath();
     if (mandirlist)
	for (dp = mandirlist; *dp; dp++) {
	  if (debug)
	       gripe(SEARCHING, *dp);
	  pathname = my_xsprintf("%s/man%s/*", *dp, section ? section : "*");
	  gf = glob_filename (pathname);
	  free(pathname);

	  if (gf != (char **) -1 && gf != NULL) {
	       for( ; *gf; gf++) {
		    const char *expander = get_expander (*gf);
		    if (expander)
			 command = my_xsprintf("%s %S | grep '%Q'"
					       "> /dev/null 2> /dev/null",
				 expander, *gf, name);
		    else
			 command = my_xsprintf("grep '%Q' %S"
					       "> /dev/null 2> /dev/null",
				 name, *gf);
		    res = do_system_command (command, 1);
		    free (command);
		    if (res == 0) {
			 if (print_where)
			      printf("%s\n", *gf);
			 else {
			      /* should read LOCALE, but libc 4.6.27 doesn't
				 seem to handle LC_RESPONSE yet */
			      int answer, c;
			      char path[BUFSIZE];

			      printf("%s? [ynq] ", *gf);
			      fflush(stdout);
			      answer = c = getchar();
			      while (c != '\n' && c != EOF)
				   c = getchar();
			      if(index("QqXx", answer))
				   exit(0);
			      if(index("YyJj", answer)) {
				   char *ri;

				   strcpy(path, *gf);
				   ri = rindex(path, '/');
				   if (ri)
					*ri = 0;
				   format_and_display(*gf);
			      }
			 }
		    }
	       }
	  }
     }
}

/* Special code for Japanese (to pick jnroff instead of nroff, etc.) */
static void
setlang(void) {
	char *lang;

	/* We use getenv() instead of setlocale(), because of
	   glibc 2.1.x security policy for SetUID/SetGID binary. */
	if ((lang = getenv("LANG")) == NULL &&
	    (lang = getenv("LC_ALL")) == NULL &&
	    (lang = getenv("LC_CTYPE")) == NULL)
		/* nothing */;

	language = lang;
	is_japanese = (lang && strncmp(lang, "ja", 2) == 0);
}

/*
 * Handle the apropos option.  Cheat by using another program.
 */
static void
do_apropos (char *name) {
     char *command;

     command = my_xsprintf("%s %Q", getval("APROPOS"), name);
     (void) do_system_command (command, 0);
     free (command);
}

/*
 * Handle the whatis option.  Cheat by using another program.
 */
static void
do_whatis (char *name) {
     char *command;

     command = my_xsprintf("%s %Q", getval("WHATIS"), name);
     (void) do_system_command (command, 0);
     free (command);
}

int
main (int argc, char **argv) {
     int status = 0;
     char *nextarg;
     char *tmp;
     char *section = 0;

#ifdef __CYGWIN__
     extern int optind;
#endif


#if 0
     {
	/* There are no known cases of buffer overflow caused by
	   excessively long environment variables. In case you find one,
	   the simplistic way to fix is to enable this stopgap. */
	char *s;
#define CHECK(p,l) s=getenv(p); if(s && strlen(s)>(l)) { fprintf(stderr, "ERROR: Environment variable %s too long!\n", p); exit(1); }
	CHECK("LANG", 32);
	CHECK("LANGUAGE", 128);
	CHECK("LC_MESSAGES", 128);
	CHECK("MANPAGER", 128);
	CHECK("MANPL", 128);
	CHECK("MANROFFSEQ", 128);
	CHECK("MANSECT", 128);
	CHECK("MAN_HP_DIREXT", 128);
	CHECK("PAGER", 128);
	CHECK("SYSTEM", 64);
	/* COLUMNS, LC_ALL, LC_CTYPE, MANPATH, MANWIDTH, MAN_IRIX_CATNAMES,
	   MAN_ICONV_PATH, MAN_ICONV_OPT, MAN_ICONV_INPUT_CHARSET,
	   MAN_ICONV_OUTPUT_CHARSET, NLSPATH, PATH */
     }
#endif


#ifndef __FreeBSD__ 
     /* Slaven Rezif: FreeBSD-2.2-SNAP does not recognize LC_MESSAGES. */
     setlocale(LC_CTYPE, "");	/* used anywhere? maybe only isdigit()? */
     setlocale(LC_MESSAGES, "");
#endif

     /* No doubt we'll need some generic language code here later.
	For the moment only Japanese support. */
     setlang();

     /* Handle /usr/man/man1.Z/name.1 nonsense from HP */
     dohp = getenv("MAN_HP_DIREXT");		/* .Z */

     /* Handle ls.z (instead of ls.1.z) cat page naming from IRIX */
     if (getenv("MAN_IRIX_CATNAMES"))
	  do_irix = 1;

     /* Handle lack of ':' in NTFS file names */
#if defined(_WIN32) || defined(__CYGWIN__)
     do_win32 = 1;
#endif

     progname = mkprogname (argv[0]);

     get_permissions ();
     get_line_length();

     /*
      * read command line options and man.conf
      */
     man_getopt (argc, argv);

     /*
      * manpath  or  man --path  or  man -w  will only print the manpath
      */
     if (!strcmp (progname, "manpath") || (optind == argc && print_where)) {
	  init_manpath();
	  prmanpath();
	  exit(0);
     }

     if (optind == argc)
	  gripe(NO_NAME_NO_SECTION);

     section_list = get_section_list ();

     while (optind < argc) {
	  nextarg = argv[optind++];

	  /* is_section correctly accepts 3Xt as section, but also 9wm,
	     so we should not believe is_section() for the last arg. */
	  tmp = is_section (nextarg);
	  if (tmp && optind < argc) {
		  section = tmp;
		  if (debug)
			  gripe (SECTION, section);
		  continue;
	  }

	  if (global_apropos)
	       do_global_apropos (nextarg, section);
	  else if (apropos)
	       do_apropos (nextarg);
	  else if (whatis)
	       do_whatis (nextarg);
	  else {
	       status = man (nextarg, section);

	       if (status == 0) {
		    if (section)
			 gripe (NO_SUCH_ENTRY_IN_SECTION, nextarg, section);
		    else
			 gripe (NO_SUCH_ENTRY, nextarg);
	       }
	  }
     }
     return !status;
}