Index: stable/7/gnu/usr.bin/man/TODO =================================================================== --- stable/7/gnu/usr.bin/man/TODO (revision 179196) +++ stable/7/gnu/usr.bin/man/TODO (revision 179197) @@ -1,123 +1,125 @@ +$FreeBSD$ + Things that would be nice but aren't really necessary: 0. Update the documentation. XX Come up with an easier way to install this thing. There are now lots of options and dependent flags to set. Should I worry too much about this? XX Properly handle commands like `man 3f intro' when the name of the file we want is something like .../man3/intro.3f. The way this is done right now seems sort of kludgey but it mostly works. See man.c for details. 2. Malloc everything instead of having fixed limits... Or at least check the limits everywhere. If you're paranoid about this, make the limits big (famous last words: really, there aren't that many things that could go wrong :-). 3. Try to do a little better job of memory management. There are a lot of little temporary strings that are malloc'd and never freed. This is probably ok for a standalone program but not so good if you wanted to call man() from another program. XX Come up with a clear view of the cat directory file permissions problem. What's a good solution, other than having man run setuid to some special user? (Make directories writable by all, cat files 666.) XX Allow a compile time option that makes man run setuid to some other user that owns all the cat pages, so that they don't have to be world writable. XX Allow man to deal with compressed (.Z) frozen (.F) and yabba (.Y) cat files. Frozen files are compressed files using freeze/melt, some combination of LZW and tree coding. Sources for it came out on comp.sources.misc or alt.sources or ... a few months ago. Yabba files are compressed using yabba/unyabba, a data compression scheme posted to alt.sources by Dan Bernstein. XX Choose a more reasonable default for the search order. Perhaps this: 1, n, l, 6, 8, 2, 3, 4, 5, 7, p, o XX Fix glob.c so it doesn't need alloca, and/or fix it so that it can work on a Sun: #ifdef __GNUC__ #define alloca __builtin_alloca #else /* !__GNUC__ */ #ifdef sparc #include #endif /* sparc */ #endif /* __GNUC__ */ XX Add some way to automatically to run preprocessors. The Sun man program has a convention that the first line of the man page can indicate which preprocessors should be run. Here's an excerpt from its man page: Preprocessing Manual Pages If the first line is a string of the form: '\" X where X is separated from the the `"' by a single SPACE and consists of any combination of characters in the following list, man pipes its input to troff(1) or nroff(1) through the corresponding preprocessors. e eqn(1), or neqn for nroff r refer(1) t tbl(1), and col(1V) for nroff v vgrind(1) If eqn or neqn is invoked, it will automatically read the file /usr/pub/eqnchar (see eqnchar(7)). XX Have manpath stat() the directories in MANPATH to avoid including directories that don't exist. Some versions of man and whatis complain when the directories (like /usr/new/man) don't exist. XX Pipe the output of apropos and whatis through a pager. XX I've been using your man(1) package for a while now and I ran into a problem with the X man pages that use tbl commands. Is it possible to configure your man(1) package to use a general command string. For example, a user could set an environment variable: setenv ROFFLINE 'pic $* | tbl | nroff -man' 13. Fix makewhatis so that it can handle stuff like this (from the Motif 1.1 man pages): .TH XmRowColumn 3X "" "" "" "" .SH NAME .mc | \fBXmRowColumn \(em the RowColumn widget class.\fP .mc .iX "XmRowColumn" .iX "widget class" "RowColumn" .sp 1 .SH SYNOPSIS 14. Consider changing the format of the awk command's printf to use "%s" instead of the standard 20.20s to accomodate the extra long file names used by Motif. Maybe there's a better way to handle this? -15. Add ability to run man on a local file +XX Add ability to run man on a local file 16. Handle per-tree tmac macros XX Allow user-definable section search order via -S or $MANSECT. Thus programmers can get stty(3) before stty(1). XX Show all the places you would find a man page (-w option) and in what order. 19. Support for multi-char sections like man1m/*.1m or manavs/*.avs (can I have a section that doesn't start with a numeral?) 20. Implement man -K for regexp apropos 21. An option to grep through all the man pages in $MANPATH Index: stable/7/gnu/usr.bin/man/lib/util.c =================================================================== --- stable/7/gnu/usr.bin/man/lib/util.c (revision 179196) +++ stable/7/gnu/usr.bin/man/lib/util.c (revision 179197) @@ -1,160 +1,180 @@ /* * util.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 */ +/* $FreeBSD$ */ + #include #include #include #include #include #include #ifdef STDC_HEADERS #include #else extern int fprintf (); extern int tolower (); #endif extern char *strdup (); extern int system (); #include "gripes.h" /* * Extract last element of a name like /foo/bar/baz. */ char * mkprogname (s) register char *s; { char *t; t = strrchr (s, '/'); if (t == (char *)NULL) t = s; else t++; return strdup (t); } void downcase (s) unsigned char *s; { register unsigned char c; while ((c = *s) != '\0') { if (isalpha (c)) *s = tolower (c); s++; } } /* * Is file a newer than file b? * * case: * * a newer than b returns 1 * a older than b returns 0 * stat on a fails returns -1 * stat on b fails returns -2 * stat on a and b fails returns -3 */ int is_newer (fa, fb) register char *fa; register char *fb; { struct stat fa_sb; struct stat fb_sb; register int fa_stat; register int fb_stat; register int status = 0; fa_stat = stat (fa, &fa_sb); if (fa_stat != 0) status = 1; fb_stat = stat (fb, &fb_sb); if (fb_stat != 0) status |= 2; if (status != 0) return -status; return (fa_sb.st_mtime > fb_sb.st_mtime); } /* * Is path a directory? */ int is_directory (path) char *path; { struct stat sb; register int status; status = stat (path, &sb); if (status != 0) return -1; return ((sb.st_mode & S_IFDIR) == S_IFDIR); +} + +/* + * Is path a regular file? + */ +int +is_file (path) + char *path; +{ + struct stat sb; + register int status; + + status = stat (path, &sb); + + if (status != 0) + return -1; + + return ((sb.st_mode & S_IFREG) == S_IFREG); } /* * Attempt a system () call. Return 1 for success and 0 for failure * (handy for counting successes :-). */ int do_system_command (command) char *command; { int status = 0; extern int debug; /* * If we're debugging, don't really execute the command -- you never * know what might be in that mangled string :-O. */ if (debug) fprintf (stderr, "\ntrying command: %s\n", command); else status = system (command); /* check return value from system() function first */ if (status == -1) { fprintf(stderr, "wait() for exit status of shell failed in function system()\n"); return 0; } else if (status == 127 || status == (127 << 8)) { fprintf(stderr, "execution of the shell failed in function system()\n"); return 0; } if (WIFSIGNALED(status)) return -1; else if (WEXITSTATUS(status)) { gripe_system_command (status); return 0; } else return 1; } Index: stable/7/gnu/usr.bin/man/man/man.c =================================================================== --- stable/7/gnu/usr.bin/man/man/man.c (revision 179196) +++ stable/7/gnu/usr.bin/man/man/man.c (revision 179197) @@ -1,1797 +1,1807 @@ /* * 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 */ #ifndef lint static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ #define MAN_MAIN #include #include #include #include #include #include #ifdef __FreeBSD__ #include #include +#include #endif #include #include #include #if HAVE_LIBZ > 0 #include #endif #include "config.h" #include "gripes.h" #include "version.h" #ifdef POSIX #include #else #ifndef R_OK #define R_OK 4 #endif #endif #ifdef STDC_HEADERS #include #else extern char *malloc (); extern char *getenv (); extern void free (); extern int system (); extern int strcmp (); extern int strncmp (); extern int exit (); extern int fflush (); extern int printf (); extern int fprintf (); extern FILE *fopen (); extern int fclose (); extern char *sprintf (); #endif extern char **glob_filename (); extern int is_newer (); extern int is_directory (); +extern int is_file (); extern int do_system_command (); char *prognam; static char *pager; static char *machine_arch; static char *machine; static char *manp; static char *manpathlist[MAXDIRS]; static char *shortsec; static char *longsec; static char *colon_sep_section_list; static char **section_list; static char *roff_directive; static int apropos; static int whatis; static int findall; static int print_where; +static char *ultimate_source (); #ifdef __FreeBSD__ static char *locale, *locale_opts, *locale_nroff, *locale_codeset; static char locale_terr[3], locale_lang[3]; static char *man_locale; static int use_man_locale; static int use_original; struct ltable { char *lcode; char *nroff; }; static struct ltable ltable[] = { {"KOI8-R", "koi8-r"}, {"ISO8859-1", "latin1"}, {"ISO8859-15", "latin1"}, {NULL} }; #endif static int troff = 0; int debug; #ifdef HAS_TROFF #ifdef __FreeBSD__ static char args[] = "M:P:S:adfhkm:op:tw?"; #else static char args[] = "M:P:S:adfhkm:p:tw?"; #endif #else #ifdef __FreeBSD__ static char args[] = "M:P:S:adfhkm:op:w?"; #else static char args[] = "M:P:S:adfhkm:p:w?"; #endif #endif #ifdef SETUID uid_t ruid; uid_t euid; #endif int main (argc, argv) int argc; char **argv; { int status = 0; char *nextarg; char *tmp; extern char *mkprogname (); char *is_section (); char **get_section_list (); void man_getopt (); void do_apropos (); void do_whatis (); int man (); prognam = mkprogname (argv[0]); longsec = NULL; unsetenv("IFS"); #ifdef __FreeBSD__ (void) setlocale(LC_ALL, ""); #endif man_getopt (argc, argv); if (optind == argc) gripe_no_name ((char *)NULL); section_list = get_section_list (); if (optind == argc - 1) { tmp = is_section (argv[optind], manp); if (tmp != NULL) gripe_no_name (tmp); } #ifdef SETUID ruid = getuid(); euid = geteuid(); seteuid(ruid); #endif while (optind < argc) { nextarg = argv[optind++]; /* * See if this argument is a valid section name. If not, * is_section returns NULL. */ tmp = is_section (nextarg, manp); if (tmp != NULL) { shortsec = tmp; if (debug) fprintf (stderr, "\nsection: %s\n", shortsec); continue; } if (apropos) { do_apropos (nextarg); status = (status ? 0 : 1); /* reverts status, see below */ } else if (whatis) { do_whatis (nextarg); status = (status ? 0 : 1); /* reverts status, see below */ } + else if (strchr (nextarg, '/') != NULL && is_file (nextarg) == 1) + { + format_and_display (NULL, ultimate_source(nextarg, dirname(nextarg)), + NULL); + } else { status = man (nextarg); if (status == 0) gripe_not_found (nextarg, longsec); } } return (status==0); /* status==1 --> exit(0), status==0 --> exit(1) */ } void usage () { static char usage_string[1024] = "%s, version %s\n\n"; #ifdef HAS_TROFF #ifdef __FreeBSD__ static char s1[] = "usage: %s [-adfhkotw] [section] [-M path] [-P pager] [-S list]\n\ [-m machine] [-p string] name ...\n\n"; #else static char s1[] = "usage: %s [-adfhktw] [section] [-M path] [-P pager] [-S list]\n\ [-m machine] [-p string] name ...\n\n"; #endif #else #ifdef __FreeBSD__ static char s1[] = "usage: %s [-adfhkow] [section] [-M path] [-P pager] [-S list]\n\ [-m machine] [-p string] name ...\n\n"; #else static char s1[] = "usage: %s [-adfhkw] [section] [-M path] [-P pager] [-S list]\n\ [-m machine] [-p string] name ...\n\n"; #endif #endif static char s2[] = " a : find all matching entries\n\ d : print gobs of debugging information\n\ f : same as whatis(1)\n\ h : print this help message\n\ k : same as apropos(1)\n"; #ifdef __FreeBSD__ static char s3[] = " o : use original, non-localized manpages\n"; #endif #ifdef HAS_TROFF static char s4[] = " t : use troff to format pages for printing\n"; #endif static char s5[] = " w : print location of man page(s) that would be displayed\n\n\ M path : set search path for manual pages to `path'\n\ P pager : use program `pager' to display pages\n\ S list : colon separated section list\n\ m machine : search for alternate architecture man pages\n"; static char s6[] = " p string : string tells which preprocessors to run\n\ e - [n]eqn(1) p - pic(1) t - tbl(1)\n\ g - grap(1) r - refer(1) v - vgrind(1)\n"; strcat (usage_string, s1); strcat (usage_string, s2); #ifdef __FreeBSD__ strcat (usage_string, s3); #endif #ifdef HAS_TROFF strcat (usage_string, s4); #endif strcat (usage_string, s5); strcat (usage_string, s6); fprintf (stderr, usage_string, prognam, version, prognam); exit(1); } char ** add_dir_to_mpath_list (mp, p) char **mp; char *p; { int status; status = is_directory (p); if (status < 0 && debug) { fprintf (stderr, "Warning: couldn't stat file %s!\n", p); } else if (status == 0 && debug) { fprintf (stderr, "Warning: %s isn't a directory!\n", p); } else if (status == 1) { if (debug) fprintf (stderr, "adding %s to manpathlist\n", p); *mp++ = strdup (p); } return mp; } /* * Get options from the command line and user environment. */ void man_getopt (argc, argv) register int argc; register char **argv; { register int c; register char *p; register char *end; register char **mp; extern void downcase (); extern char *manpath (); while ((c = getopt (argc, argv, args)) != EOF) { switch (c) { case 'M': manp = strdup (optarg); break; case 'P': pager = strdup (optarg); if (setenv("PAGER", pager, 1) != 0) (void)fprintf(stderr, "setenv PAGER=%s\n", pager); break; case 'S': colon_sep_section_list = strdup (optarg); break; case 'a': findall++; break; case 'd': debug++; break; case 'f': if (troff) gripe_incompatible ("-f and -t"); if (apropos) gripe_incompatible ("-f and -k"); if (print_where) gripe_incompatible ("-f and -w"); whatis++; break; case 'k': if (troff) gripe_incompatible ("-k and -t"); if (whatis) gripe_incompatible ("-k and -f"); if (print_where) gripe_incompatible ("-k and -w"); apropos++; break; case 'm': machine_arch = optarg; if ((machine = strchr(optarg, ':')) != NULL) *machine++ = '\0'; else machine = optarg; break; #ifdef __FreeBSD__ case 'o': use_original++; break; #endif case 'p': roff_directive = strdup (optarg); break; #ifdef HAS_TROFF case 't': if (apropos) gripe_incompatible ("-t and -k"); if (whatis) gripe_incompatible ("-t and -f"); if (print_where) gripe_incompatible ("-t and -w"); troff++; break; #endif case 'w': if (apropos) gripe_incompatible ("-w and -k"); if (whatis) gripe_incompatible ("-w and -f"); if (troff) gripe_incompatible ("-w and -t"); print_where++; break; case 'h': case '?': default: usage(); break; } } #ifdef __FreeBSD__ /* "" intentionally used to catch error */ if ((locale = setlocale(LC_CTYPE, "")) != NULL) locale_codeset = nl_langinfo(CODESET); if (!use_original && locale != NULL && *locale_codeset != '\0' && strcmp(locale_codeset, "US-ASCII") != 0 ) { char *tmp, *short_locale; struct ltable *pltable; *locale_lang = '\0'; *locale_terr = '\0'; if ((short_locale = strdup(locale)) == NULL) { perror ("ctype locale strdup"); exit (1); } if ((tmp = strchr(short_locale, '.')) != NULL) *tmp = '\0'; if (strlen(short_locale) == 2) strcpy(locale_lang, short_locale); else if ((tmp = strchr(short_locale, '_')) == NULL || tmp != short_locale + 2 || strlen(tmp + 1) != 2 ) { errno = EINVAL; perror ("ctype locale format"); locale = NULL; } else { strncpy(locale_terr, short_locale + 3, 2); locale_terr[2] = '\0'; strncpy(locale_lang, short_locale, 2); locale_lang[2] = '\0'; } free(short_locale); if (locale != NULL) { for (pltable = ltable; pltable->lcode != NULL; pltable++) { if (strcmp(pltable->lcode, locale_codeset) == 0) { locale_nroff = pltable->nroff; break; } } asprintf(&man_locale, "%s.%s", locale_lang, locale_codeset); } } else { if (locale == NULL) { errno = EINVAL; perror ("ctype locale"); } else { locale = NULL; if (*locale_codeset == '\0') { errno = EINVAL; perror ("ctype codeset"); } } } #endif /* __FreeBSD__ */ if (pager == NULL || *pager == '\0') if ((pager = getenv ("PAGER")) == NULL || *pager == '\0') pager = strdup (PAGER); if (debug) fprintf (stderr, "\nusing %s as pager\n", pager); if (machine_arch == NULL && (machine_arch = getenv ("MACHINE_ARCH")) == NULL) machine_arch = MACHINE_ARCH; if (machine == NULL && (machine = getenv ("MACHINE")) == NULL) { static struct utsname utsname; if (uname(&utsname) == -1) { perror ("uname"); exit (1); } machine = utsname.machine; } if (debug) fprintf (stderr, "\nusing %s:%s architecture\n", machine_arch, machine); if (manp == NULL) { if ((manp = manpath (0)) == NULL) gripe_manpath (); if (debug) fprintf (stderr, "\nsearch path for pages determined by manpath is\n%s\n\n", manp); } /* * Expand the manpath into a list for easier handling. */ mp = manpathlist; for (p = manp; ; p = end+1) { if (mp == manpathlist + MAXDIRS - 1) { fprintf (stderr, "Warning: too many directories in manpath, truncated!\n"); break; } if ((end = strchr (p, ':')) != NULL) *end = '\0'; mp = add_dir_to_mpath_list (mp, p); if (end == NULL) break; *end = ':'; } *mp = NULL; } /* * 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. */ char * is_section (name, path) char *name; char *path; { register char **vs; char *temp, *end, *loc; char **plist; int x; for (vs = section_list; *vs != NULL; vs++) if ((strcmp (*vs, name) == 0) || (isdigit ((unsigned char)name[0]) && strlen(name) == 1)) return (longsec = strdup(name)); plist = manpathlist; if (isdigit ((unsigned char)name[0])) { while (*plist != NULL) { asprintf (&temp, "%s/man%c/*", *plist, name[0]); plist++; x = 0; vs = glob_filename (temp); if (vs == (char **) -1) { free (temp); return NULL; } for ( ; *vs != NULL; vs++) { end = strrchr (*vs, '/'); if ((loc = strstr (end, name)) != NULL && loc - end > 2 && *(loc-1) == '.' && (*(loc+strlen(name)) == '\0' || *(loc+strlen(name)) == '.')) { x = 1; break; } } free (temp); if (x == 1) { asprintf (&temp, "%c", name[0]); longsec = strdup (name); return (temp); } } } return NULL; } /* * Handle the apropos option. Cheat by using another program. */ void do_apropos (name) register char *name; { register int len; register char *command; len = strlen (APROPOS) + strlen (name) + 4; if ((command = (char *) malloc(len)) == NULL) gripe_alloc (len, "command"); sprintf (command, "%s \"%s\"", APROPOS, name); (void) do_system_command (command); free (command); } /* * Handle the whatis option. Cheat by using another program. */ void do_whatis (name) register char *name; { register int len; register char *command; len = strlen (WHATIS) + strlen (name) + 4; if ((command = (char *) malloc(len)) == NULL) gripe_alloc (len, "command"); sprintf (command, "%s \"%s\"", WHATIS, name); (void) do_system_command (command); free (command); } /* * Change a name of the form ...man/man1/name.1 to ...man/cat1/name.1 * or a name of the form ...man/cat1/name.1 to ...man/man1/name.1 */ char * convert_name (name, to_cat) register char *name; register int to_cat; { register char *to_name; register char *t1; register char *t2 = NULL; #ifdef DO_COMPRESS if (to_cat) { int olen = strlen(name); int cextlen = strlen(COMPRESS_EXT); int len = olen + cextlen; to_name = malloc (len+1); if (to_name == NULL) gripe_alloc (len+1, "to_name"); strcpy (to_name, name); olen -= cextlen; /* Avoid tacking it on twice */ if (olen >= 1 && strcmp(name + olen, COMPRESS_EXT) != 0) strcat (to_name, COMPRESS_EXT); } else to_name = strdup (name); #else to_name = strdup (name); #endif t1 = strrchr (to_name, '/'); if (t1 != NULL) { *t1 = '\0'; t2 = strrchr (to_name, '/'); *t1 = '/'; /* Skip architecture part (if present). */ if (t2 != NULL && (t1 - t2 < 5 || *(t2 + 1) != 'm' || *(t2 + 3) != 'n')) { t1 = t2; *t1 = '\0'; t2 = strrchr (to_name, '/'); *t1 = '/'; } } if (t2 == NULL) gripe_converting_name (name, to_cat); if (to_cat) { *(++t2) = 'c'; *(t2+2) = 't'; } else { *(++t2) = 'm'; *(t2+2) = 'n'; } if (debug) fprintf (stderr, "to_name in convert_name () is: %s\n", to_name); return to_name; } /* * Try to find the man page corresponding to the given name. The * reason we do this with globbing is because some systems have man * page directories named man3 which contain files with names like * XtPopup.3Xt. Rather than requiring that this program know about * all those possible names, we simply try to match things like * .../man[sect]/name[sect]*. This is *much* easier. * * Note that globbing is only done when the section is unspecified. */ char ** glob_for_file (path, section, longsec, name, cat) char *path; char *section; char *longsec; char *name; int cat; { char pathname[FILENAME_MAX]; char **gf; if (longsec == NULL) longsec = section; if (cat) snprintf (pathname, sizeof(pathname), "%s/cat%s/%s.%s*", path, section, name, longsec); else snprintf (pathname, sizeof(pathname), "%s/man%s/%s.%s*", path, section, name, longsec); if (debug) fprintf (stderr, "globbing %s\n", pathname); gf = glob_filename (pathname); if ((gf == (char **) -1 || *gf == NULL) && isdigit ((unsigned char)*section) && strlen (longsec) == 1) { if (cat) snprintf (pathname, sizeof(pathname), "%s/cat%s/%s.%c*", path, section, name, *section); else snprintf (pathname, sizeof(pathname), "%s/man%s/%s.%c*", path, section, name, *section); gf = glob_filename (pathname); } if ((gf == (char **) -1 || *gf == NULL) && isdigit ((unsigned char)*section) && strlen (longsec) == 1) { if (cat) snprintf (pathname, sizeof(pathname), "%s/cat%s/%s.0*", path, section, name); else snprintf (pathname, sizeof(pathname), "%s/man%s/%s.0*", path, section, name); if (debug) fprintf (stderr, "globbing %s\n", pathname); gf = glob_filename (pathname); } return gf; } /* * Return an un-globbed name in the same form as if we were doing * globbing. */ char ** make_name (path, section, longsec, name, cat) char *path; char *section; char *longsec; char *name; int cat; { register int i = 0; static char *names[3]; char buf[FILENAME_MAX]; if (cat) snprintf (buf, sizeof(buf), "%s/cat%s/%s.%s", path, section, name, longsec); else snprintf (buf, sizeof(buf), "%s/man%s/%s.%s", path, section, name, longsec); if (access (buf, R_OK) == 0) names[i++] = strdup (buf); /* * If we're given a section that looks like `3f', we may want to try * file names like .../man3/foo.3f as well. This seems a bit * kludgey to me, but what the hey... */ if (section[1] != '\0') { if (cat) snprintf (buf, sizeof(buf), "%s/cat%c/%s.%s", path, section[0], name, section); else snprintf (buf, sizeof(buf), "%s/man%c/%s.%s", path, section[0], name, section); if (access (buf, R_OK) == 0) names[i++] = strdup (buf); } names[i] = NULL; return &names[0]; } char * get_expander (file) char *file; { char *end = file + (strlen (file) - 1); while (end > file && end[-1] != '.') --end; if (end == file) return NULL; #ifdef FCAT if (*end == 'F') return FCAT; #endif /* FCAT */ #ifdef YCAT if (*end == 'Y') return YCAT; #endif /* YCAT */ #ifdef ZCAT - if (*end == 'Z' || !strcmp(end, "gz")) + if (*end == 'Z' || !strcmp(end, "gz") || !strcmp(end, "bz2")) return ZCAT; #endif /* ZCAT */ return NULL; } /* * Simply display the preformatted page. */ int display_cat_file (file) register char *file; { register int found; char command[FILENAME_MAX]; found = 0; if (access (file, R_OK) == 0) { char *expander = get_expander (file); if (expander != NULL) snprintf (command, sizeof(command), "%s %s | %s", expander, file, pager); else snprintf (command, sizeof(command), "%s %s", pager, file); found = do_system_command (command); } 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. */ char * ultimate_source (name, path) char *name; char *path; { static char buf[BUFSIZ]; static char ult[FILENAME_MAX]; FILE *fp; char *beg; char *end; strncpy (ult, name, sizeof(ult)-1); ult[sizeof(ult)-1] = '\0'; strncpy (buf, name, sizeof(buf)-1); ult[sizeof(buf)-1] = '\0'; next: #if HAVE_LIBZ > 0 if ((fp = gzopen (ult, "r")) == NULL) { /* check for the compressed version too */ strlcat(ult, ".gz", FILENAME_MAX); if ((fp = gzopen (ult, "r")) == NULL) return ult; /* we munged it, but it doesn't exist anyway */ } #else if ((fp = fopen (ult, "r")) == NULL) return ult; #endif #if HAVE_LIBZ > 0 end = gzgets (fp, buf, BUFSIZ); gzclose(fp); #else end = fgets (buf, BUFSIZ, fp); fclose(fp); #endif if (!end || strlen (buf) < 5) return ult; beg = buf; if (*beg++ == '.' && *beg++ == 's' && *beg++ == 'o') { while ((*beg == ' ' || *beg == '\t') && *beg != '\0') beg++; end = beg; while (*end != ' ' && *end != '\t' && *end != '\n' && *end != '\0') end++; *end = '\0'; snprintf(ult, sizeof(ult), "%s/%s", path, beg); snprintf(buf, sizeof(buf), "%s", ult); goto next; } if (debug) fprintf (stderr, "found ultimate source file %s\n", ult); return ult; } void add_directive (first, d, file, buf, bufsize) int *first; char *d; char *file; char *buf; int bufsize; { if (strcmp (d, "") != 0) { if (*first) { *first = 0; snprintf(buf, bufsize, "%s %s", d, file); } else { strncat (buf, " | ", bufsize-strlen(buf)-1); strncat (buf, d, bufsize-strlen(buf)-1); } } } int parse_roff_directive (cp, file, buf, bufsize) char *cp; char *file; char *buf; int bufsize; { char c; char *exp; int first = 1; int preproc_found = 0; int use_col = 0; if ((exp = get_expander(file)) != NULL) add_directive (&first, exp, file, buf, bufsize); while ((c = *cp++) != '\0') { switch (c) { case 'e': if (debug) fprintf (stderr, "found eqn(1) directive\n"); preproc_found++; if (troff) add_directive (&first, EQN, file, buf, bufsize); else { #ifdef __FreeBSD__ char lbuf[FILENAME_MAX]; snprintf(lbuf, sizeof(lbuf), "%s -T%s", NEQN, locale_opts == NULL ? "ascii" : locale_opts); add_directive (&first, lbuf, file, buf, bufsize); #else add_directive (&first, NEQN, file, buf, bufsize); #endif } break; case 'g': if (debug) fprintf (stderr, "found grap(1) directive\n"); preproc_found++; add_directive (&first, GRAP, file, buf, bufsize); break; case 'p': if (debug) fprintf (stderr, "found pic(1) directive\n"); preproc_found++; add_directive (&first, PIC, file, buf, bufsize); break; case 't': if (debug) fprintf (stderr, "found tbl(1) directive\n"); preproc_found++; use_col++; add_directive (&first, TBL, file, buf, bufsize); break; case 'v': if (debug) fprintf (stderr, "found vgrind(1) directive\n"); add_directive (&first, VGRIND, file, buf, bufsize); break; case 'r': if (debug) fprintf (stderr, "found refer(1) directive\n"); add_directive (&first, REFER, file, buf, bufsize); break; case ' ': case '\t': case '\n': goto done; default: return -1; } } done: #ifdef HAS_TROFF if (troff) add_directive (&first, TROFF, file, buf, bufsize); else #endif { #ifdef __FreeBSD__ char lbuf[FILENAME_MAX]; snprintf(lbuf, sizeof(lbuf), "%s -T%s%s%s", NROFF, locale_opts == NULL ? "ascii" : locale_opts, use_man_locale ? " -dlocale=" : "", use_man_locale ? man_locale : ""); add_directive (&first, lbuf, file, buf, bufsize); #else add_directive (&first, NROFF " -Tascii", file, buf, bufsize); #endif } if (use_col && !troff) add_directive (&first, COL, file, buf, bufsize); if (preproc_found) return 0; else return 1; } char * make_roff_command (file) char *file; { #if HAVE_LIBZ > 0 gzFile fp; #else FILE *fp; #endif char line [BUFSIZ]; static char buf [BUFSIZ]; int status; char *cp; if (roff_directive != NULL) { if (debug) fprintf (stderr, "parsing directive from command line\n"); status = parse_roff_directive (roff_directive, file, buf, sizeof(buf)); if (status == 0) return buf; if (status == -1) gripe_roff_command_from_command_line (file); } #if HAVE_LIBZ > 0 if ((fp = gzopen (file, "r")) != NULL) #else if ((fp = fopen (file, "r")) != NULL) #endif { cp = line; #if HAVE_LIBZ > 0 gzgets (fp, line, BUFSIZ); gzclose(fp); #else fgets (line, BUFSIZ, fp); fclose(fp); #endif if (*cp++ == '\'' && *cp++ == '\\' && *cp++ == '"' && *cp++ == ' ') { if (debug) fprintf (stderr, "parsing directive from file\n"); status = parse_roff_directive (cp, file, buf, sizeof(buf)); if (status == 0) return buf; if (status == -1) gripe_roff_command_from_file (file); } } else { /* * Is there really any point in continuing to look for * preprocessor options if we can't even read the man page source? */ gripe_reading_man_file (file); return NULL; } if ((cp = getenv ("MANROFFSEQ")) != NULL) { if (debug) fprintf (stderr, "parsing directive from environment\n"); status = parse_roff_directive (cp, file, buf, sizeof(buf)); if (status == 0) return buf; if (status == -1) gripe_roff_command_from_env (); } if (debug) fprintf (stderr, "using default preprocessor sequence\n"); status = parse_roff_directive ("t", file, buf, sizeof(buf)); if (status >= 0) return buf; else /* can't happen */ return NULL; } sig_t ohup, oint, oquit, oterm; static char temp[FILENAME_MAX]; void cleantmp() { unlink(temp); exit(1); } void set_sigs() { ohup = signal(SIGHUP, cleantmp); oint = signal(SIGINT, cleantmp); oquit = signal(SIGQUIT, cleantmp); oterm = signal(SIGTERM, cleantmp); } void restore_sigs() { signal(SIGHUP, ohup); signal(SIGINT, oint); signal(SIGQUIT, oquit); signal(SIGTERM, oterm); } /* * Try to format the man page and create a new formatted file. Return * 1 for success and 0 for failure. */ int make_cat_file (path, man_file, cat_file, manid) register char *path; register char *man_file; register char *cat_file; { int s, f; FILE *pp; #if defined(HAVE_LIBZ) && defined(DO_COMPRESS) gzFile fp; #else FILE *fp; #endif char *roff_command; char command[FILENAME_MAX]; roff_command = make_roff_command (man_file); if (roff_command == NULL) return 0; snprintf(temp, sizeof(temp), "%s.tmpXXXXXX", cat_file); if ((f = mkstemp(temp)) >= 0 && #if defined(HAVE_LIBZ) && defined(DO_COMPRESS) (fp = gzdopen(f, "w")) != NULL) #else (fp = fdopen(f, "w")) != NULL) #endif { set_sigs(); if (fchmod (f, CATMODE) < 0) { perror("fchmod"); unlink(temp); restore_sigs(); fclose(fp); return 0; } else if (debug) fprintf (stderr, "mode of %s is now %o\n", temp, CATMODE); snprintf (command, sizeof(command), "(cd %s ; %s)", path, roff_command); fprintf (stderr, "Formatting page, please wait..."); fflush(stderr); if (debug) fprintf (stderr, "\ntrying command: %s\n", command); else { #ifdef SETUID if (manid) seteuid(ruid); #endif if ((pp = popen(command, "r")) == NULL) { s = errno; fprintf(stderr, "Failed.\n"); errno = s; perror("popen"); #ifdef SETUID if (manid) seteuid(euid); #endif unlink(temp); restore_sigs(); #if defined(HAVE_LIBZ) && defined(DO_COMPRESS) gzclose(fp); #else fclose(fp); #endif return 0; } #ifdef SETUID if (manid) seteuid(euid); #endif f = 0; while ((s = getc(pp)) != EOF) { #if defined(HAVE_LIBZ) && defined(DO_COMPRESS) gzputc(fp, s); #else putc(s, fp); #endif f++; } if (!f || ((s = pclose(pp)) == -1)) { s = errno; fprintf(stderr, "Failed.\n"); errno = s; if (f) perror("pclose"); unlink(temp); restore_sigs(); #if defined(HAVE_LIBZ) && defined(DO_COMPRESS) gzclose(fp); #else fclose(fp); #endif return 0; } if (s != 0) { fprintf(stderr, "Failed.\n"); gripe_system_command(s); unlink(temp); restore_sigs(); #if defined(HAVE_LIBZ) && defined(DO_COMPRESS) gzclose(fp); #else fclose(fp); #endif return 0; } } if (debug) unlink(temp); else if (rename(temp, cat_file) == -1) { s = errno; fprintf(stderr, "\nHmm! Can't seem to rename %s to %s, check permissions on man dir!\n", temp, cat_file); errno = s; perror("rename"); unlink(temp); restore_sigs(); #if defined(HAVE_LIBZ) && defined(DO_COMPRESS) gzclose(fp); #else fclose(fp); #endif return 0; } restore_sigs(); #if defined(HAVE_LIBZ) && defined(DO_COMPRESS) if (gzclose(fp)) { #else if (fclose(fp)) { #endif s = errno; if (!debug) unlink(cat_file); fprintf(stderr, "Failed.\n"); errno = s; perror("fclose"); return 0; } if (debug) { fprintf(stderr, "No output, debug mode.\n"); return 0; } fprintf(stderr, "Done.\n"); return 1; } else { if (f >= 0) { s = errno; unlink(temp); errno = s; } if (debug) { s = errno; fprintf (stderr, "Couldn't open %s for writing.\n", temp); errno = s; } if (f >= 0) { perror("fdopen"); close(f); } return 0; } } /* * 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. * * Note that we've already been handed the name of the ultimate source * file at this point. */ int format_and_display (path, man_file, cat_file) register char *path; register char *man_file; register char *cat_file; { int status; register int found; char *roff_command; char command[FILENAME_MAX]; found = 0; if (access (man_file, R_OK) != 0) return 0; - if (troff) + if (troff || path == NULL) { roff_command = make_roff_command (man_file); if (roff_command == NULL) return 0; - else + if (troff) snprintf (command, sizeof(command), "(cd %s ; %s)", path, roff_command); + else + snprintf (command, sizeof(command), "%s | %s", roff_command, pager); found = do_system_command (command); } else { status = is_newer (man_file, cat_file); if (debug) fprintf (stderr, "status from is_newer() = %d\n", status); if (status == 1 || status == -2) { /* * Cat file is out of date. Try to format and save it. */ if (print_where) { printf ("%s\n", man_file); found++; } else { #ifdef SETUID seteuid(euid); found = make_cat_file (path, man_file, cat_file, 1); seteuid(ruid); if (!found) { /* Try again as real user - see note below. By running with effective group (user) ID == real group (user) ID except for the call above, I believe the problems of reading private man pages is avoided. */ found = make_cat_file (path, man_file, cat_file, 0); } #else found = make_cat_file (path, man_file, cat_file, 0); #endif if (found) { /* * Creating the cat file worked. Now just display it. */ (void) display_cat_file (cat_file); } else { /* * Couldn't create cat file. Just format it and * display it through the pager. */ roff_command = make_roff_command (man_file); if (roff_command == NULL) return 0; else snprintf (command, sizeof(command), "(cd %s ; %s | %s)", path, roff_command, pager); found = do_system_command (command); } } } else if (access (cat_file, R_OK) == 0) { /* * Formatting not necessary. Cat file is newer than source * file, or source file is not present but cat file is. */ if (print_where) { printf ("%s (source: %s)\n", cat_file, man_file); found++; } else { found = display_cat_file (cat_file); } } } return found; } /* * See if the preformatted man page or the source exists in the given * section. */ int try_section (path, section, longsec, name, glob) char *path; char *section; char *longsec; char *name; int glob; { register int found = 0; register int to_cat; register int cat; register char **names; register char **np; static int arch_search; char buf[FILENAME_MAX]; if (!arch_search) { snprintf(buf, sizeof(buf), "%s/man%s/%s", path, section, machine); if (is_directory (buf) == 1) { snprintf(buf, sizeof(buf), "%s/%s", machine, name); arch_search++; found = try_section (path, section, longsec, buf, glob); arch_search--; if (found && !findall) /* only do this architecture... */ return found; } if (strcmp(machine_arch, machine) != 0) { snprintf(buf, sizeof(buf), "%s/man%s/%s", path, section, machine_arch); if (is_directory (buf) == 1) { snprintf(buf, sizeof(buf), "%s/%s", machine_arch, name); arch_search++; found = try_section (path, section, longsec, buf, glob); arch_search--; if (found && !findall) /* only do this architecture... */ return found; } } } if (debug) { if (glob) fprintf (stderr, "trying section %s with globbing\n", section); else fprintf (stderr, "trying section %s without globbing\n", section); } #ifndef NROFF_MISSING /* * Look for man page source files. */ cat = 0; if (glob) names = glob_for_file (path, section, longsec, name, cat); else names = make_name (path, section, longsec, name, cat); if (names == (char **) -1 || *names == NULL) /* * No files match. See if there's a preformatted page around that * we can display. */ #endif /* NROFF_MISSING */ { if (!troff) { cat = 1; if (glob) names = glob_for_file (path, section, longsec, name, cat); else names = make_name (path, section, longsec, name, cat); if (names != (char **) -1 && *names != NULL) { for (np = names; *np != NULL; np++) { if (print_where) { printf ("%s\n", *np); found++; } else { found += display_cat_file (*np); } } } } } #ifndef NROFF_MISSING else { for (np = names; *np != NULL; np++) { register char *cat_file = NULL; register char *man_file; man_file = ultimate_source (*np, path); if (!troff) { to_cat = 1; cat_file = convert_name (man_file, to_cat); if (debug) fprintf (stderr, "will try to write %s if needed\n", cat_file); } found += format_and_display (path, man_file, cat_file); } } #endif /* NROFF_MISSING */ return found; } /* * 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. * */ int man (name) char *name; { register int found; register int glob; register char **mp; register char **sp; #ifdef __FreeBSD__ int l_found; char buf[FILENAME_MAX]; #endif found = 0; fflush (stdout); if (shortsec != NULL) { for (mp = manpathlist; *mp != NULL; mp++) { if (debug) fprintf (stderr, "\nsearching in %s\n", *mp); glob = 1; #ifdef __FreeBSD__ l_found = 0; if (locale != NULL) { locale_opts = locale_nroff; use_man_locale = 1; if (*locale_lang != '\0' && *locale_terr != '\0') { snprintf(buf, sizeof(buf), "%s/%s_%s.%s", *mp, locale_lang, locale_terr, locale_codeset); if (is_directory (buf) == 1) l_found = try_section (buf, shortsec, longsec, name, glob); } if (!l_found) { if (*locale_lang != '\0') { snprintf(buf, sizeof(buf), "%s/%s.%s", *mp, locale_lang, locale_codeset); if (is_directory (buf) == 1) l_found = try_section (buf, shortsec, longsec, name, glob); } use_man_locale = 0; if (!l_found && strcmp(locale_lang, "en") != 0) { snprintf(buf, sizeof(buf), "%s/en.%s", *mp, locale_codeset); if (is_directory (buf) == 1) l_found = try_section (buf, shortsec, longsec, name, glob); } } locale_opts = NULL; use_man_locale = 0; } if (!l_found) { #endif found += try_section (*mp, shortsec, longsec, name, glob); #ifdef __FreeBSD__ } else found += l_found; #endif if (found && !findall) /* i.e. only do this section... */ return found; } } else { for (sp = section_list; *sp != NULL; sp++) { for (mp = manpathlist; *mp != NULL; mp++) { if (debug) fprintf (stderr, "\nsearching in %s\n", *mp); glob = 1; #ifdef __FreeBSD__ l_found = 0; if (locale != NULL) { locale_opts = locale_nroff; use_man_locale = 1; if (*locale_lang != '\0' && *locale_terr != '\0') { snprintf(buf, sizeof(buf), "%s/%s_%s.%s", *mp, locale_lang, locale_terr, locale_codeset); if (is_directory (buf) == 1) l_found = try_section (buf, *sp, longsec, name, glob); } if (!l_found) { if (*locale_lang != '\0') { snprintf(buf, sizeof(buf), "%s/%s.%s", *mp, locale_lang, locale_codeset); if (is_directory (buf) == 1) l_found = try_section (buf, *sp, longsec, name, glob); } use_man_locale = 0; if (!l_found && strcmp(locale_lang, "en") != 0) { snprintf(buf, sizeof(buf), "%s/en.%s", *mp, locale_codeset); if (is_directory (buf) == 1) l_found = try_section (buf, *sp, longsec, name, glob); } } locale_opts = NULL; use_man_locale = 0; } if (!l_found) { #endif found += try_section (*mp, *sp, longsec, name, glob); #ifdef __FreeBSD__ } else found += l_found; #endif if (found && !findall) /* i.e. only do this section... */ return found; } } } return found; } char ** get_section_list () { int i; char *p; char *end; #define TMP_SECTION_LIST_SIZE 100 static char *tmp_section_list[TMP_SECTION_LIST_SIZE]; if (colon_sep_section_list == NULL) { if ((p = getenv ("MANSECT")) == NULL) { return std_sections; } else { colon_sep_section_list = strdup (p); } } i = 0; for (p = colon_sep_section_list; i < TMP_SECTION_LIST_SIZE ; p = end+1) { if ((end = strchr (p, ':')) != NULL) *end = '\0'; tmp_section_list[i++] = strdup (p); if (end == NULL) break; } tmp_section_list [i] = NULL; return tmp_section_list; }