/*
* 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, *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;
/* 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;
}
/*
* 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);
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 (!isascii(buf[0]))
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 (beg[0] == '/') {
strcpy(name, beg);
} else
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 || true))", 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 = 1;
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);
}
}
}
return status ? EXIT_SUCCESS : EXIT_FAILURE;
}