/* * 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 #include #include #include #include #include /* for chmod */ #include #include #include #include #ifdef TERMIOS_HEADER #include #endif #ifndef R_OK #define R_OK 4 #endif extern char *index (const char *, int); /* not always in */ extern char *rindex (const char *, int); /* not always in */ #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 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; }