summaryrefslogtreecommitdiffstats
path: root/src/man.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/man.c')
-rw-r--r--src/man.c1366
1 files changed, 1366 insertions, 0 deletions
diff --git a/src/man.c b/src/man.c
new file mode 100644
index 0000000..62eaee4
--- /dev/null
+++ b/src/man.c
@@ -0,0 +1,1366 @@
+/*
+ * 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>
+#ifdef TERMIOS_HEADER
+#include <sys/termios.h>
+#endif
+
+#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, *browser, *htmlpager;
+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) {
+ 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;
+ 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;
+}
+
+/*
+ * Simply display the preformatted page.
+ */
+static int
+display_html_file (const char *file) {
+ int found;
+
+ found = 0;
+
+ if (access (file, R_OK) == 0 && different_cat_file(file)) {
+ char *command = NULL;
+
+ if (isatty(1)) {
+ command = my_xsprintf("%s %S", browser, file);
+ } else {
+ command = my_xsprintf("%s %S", htmlpager, file);
+ }
+ found = !do_system_command (command, 0);
+ }
+ return found;
+
+ return 1;
+}
+
+/*
+ * 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);
+
+ #ifdef __APPLE__
+ /* Man 1.5x randomly freezes under Mac OS X 10.4.7 when the
+ man page is compressed (with either gzip or bzip2), and
+ only with large pages.
+ The freeze occurs at the pclose function, and a ps shows
+ that gunzip is still running.
+
+ The problem is the specification of pclose(): The pclose()
+ function waits for the associated process to terminate
+ and returns the exit status of the command as returned by
+ wait4().
+
+ So, if gunzip is started to look at the start of a file and
+ the file is larger than the buffer used by stdio then the
+ first read does not read everything, and the pclose hangs. */
+
+ /* Reading loop insures lockup cannot occur */
+ char dummy[BUFSIZE];
+ while (fgets (dummy,sizeof(dummy),fp) );
+ #endif // __APPLE__
+
+ 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 */
+ *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.
+ * The LL assignment is needed by the mandoc macros.
+ */
+ sprintf(eos(bufh), "echo \".ll %d.%di\"; ", ll/10, ll%10);
+ sprintf(eos(bufh), "echo \".nr 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;
+
+ n = getval("BROWSER");
+ if (n && *n)
+ type |= TYPE_HTML;
+ }
+
+ 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 if (mp->type == TYPE_HTML) {
+ if (print_where) {
+ printf ("%s\n", mp->filename);
+ found = 1;
+ } else
+ found = display_html_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;
+}
+
+/* return 0 when all was OK */
+static int
+do_global_apropos (char *name, char *section) {
+ char **dp, **gf;
+ char *pathname;
+ char *command;
+ int status, res;
+
+ status = 0;
+ 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);
+ status |= res;
+ 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);
+ }
+ }
+ }
+ }
+ }
+ }
+ return status;
+}
+
+/* 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 int
+do_apropos (char *name) {
+ char *command;
+
+ command = my_xsprintf("'%s' '%Q'", getval("APROPOS"), name);
+ return do_system_command (command, 0);
+}
+
+/*
+ * Handle the whatis option. Cheat by using another program.
+ */
+static int
+do_whatis (char *name) {
+ char *command;
+
+ command = my_xsprintf("'%s' '%Q'", getval("WHATIS"), name);
+ return do_system_command (command, 0);
+}
+
+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);
+ CHECK("BROWSER", 64);
+ CHECK("HTMLPAGER", 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)
+ status = !do_global_apropos (nextarg, section);
+ else if (apropos)
+ status = !do_apropos (nextarg);
+ else if (whatis)
+ status = !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);
+ }
+ }
+
+ /* reset duplicate search -
+ fixes Fedora#542852 "man cut cut throws an error" */
+ free_catman_filelists ();
+ }
+ return status ? EXIT_SUCCESS : EXIT_FAILURE;
+}