/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T. */ /* All rights reserved. */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ /* * man * links to apropos, whatis, and catman * This version uses more for underlining and paging. */ #include <stdio.h> #include <ctype.h> #include <sgtty.h> #include <sys/param.h> #include <sys/types.h> #include <sys/stat.h> #include <signal.h> #include <string.h> #include <malloc.h> #include <dirent.h> #include <errno.h> #include <fcntl.h> #include <locale.h> #include <stdlib.h> #include <unistd.h> #include <memory.h> #include <limits.h> #include <wchar.h> #define MACROF "tmac.an" /* name of <locale> macro file */ #define TMAC_AN "-man" /* default macro file */ /* * The default search path for man subtrees. */ #define MANDIR "/usr/share/man" /* default mandir */ #define MAKEWHATIS "/usr/lib/makewhatis" #define WHATIS "windex" #define TEMPLATE "/tmp/mpXXXXXX" #define CONFIG "man.cf" /* * Names for formatting and display programs. The values given * below are reasonable defaults, but sites with source may * wish to modify them to match the local environment. The * value for TCAT is particularly problematic as there's no * accepted standard value available for it. (The definition * below assumes C.A.T. troff output and prints it). */ #define MORE "more -s" /* default paging filter */ #define CAT_S "/usr/bin/cat -s" /* for '-' opt (no more) */ #define CAT_ "/usr/bin/cat" /* for when output is not a tty */ #define TROFF "troff" /* local name for troff */ #define TCAT "lp -c -T troff" /* command to "display" troff output */ #define SOLIMIT 10 /* maximum allowed .so chain length */ #define MAXDIRS 128 /* max # of subdirs per manpath */ #define MAXPAGES 32 /* max # for multiple pages */ #define PLEN 3 /* prefix length {man, cat, fmt} */ #define TMPLEN 7 /* length of tmpfile prefix */ #define MAXTOKENS 64 #define MAXSUFFIX 20 /* length of section suffix */ #define DOT_SO ".so " #define PREPROC_SPEC "'\\\" " #define DPRINTF if (debug && !catmando) \ (void) printf #define sys(s) (debug ? ((void)puts(s), 0) : system(s)) #define eq(a, b) (strcmp(a, b) == 0) #define match(a, b, c) (strncmp(a, b, c) == 0) #define ISDIR(A) ((A.st_mode & S_IFMT) == S_IFDIR) #define SROFF_CMD "/usr/lib/sgml/sgml2roff" /* sgml converter */ #define MANDIRNAME "man" /* man directory */ #define SGMLDIR "sman" /* sman directory */ #define SGML_SYMBOL "<!DOCTYPE" /* a sgml file should contain this */ #define SGML_SYMBOL_LEN 9 /* length of SGML_SYMBOL */ /* * Directory mapping of old directories to new directories */ typedef struct { char *old_name; char *new_name; } map_entry; static const map_entry map[] = { { "3b", "3ucb" }, { "3e", "3elf" }, { "3g", "3gen" }, { "3k", "3kstat" }, { "3n", "3socket" }, { "3r", "3rt" }, { "3s", "3c" }, { "3t", "3thr" }, { "3x", "3curses" }, { "3xc", "3xcurses" }, { "3xn", "3xnet" } }; /* * A list of known preprocessors to precede the formatter itself * in the formatting pipeline. Preprocessors are specified by * starting a manual page with a line of the form: * '\" X * where X is a string consisting of letters from the p_tag fields * below. */ static const struct preprocessor { char p_tag; char *p_nroff, *p_troff; } preprocessors [] = { {'c', "cw", "cw"}, {'e', "neqn /usr/share/lib/pub/eqnchar", "eqn /usr/share/lib/pub/eqnchar"}, {'p', "pic", "pic"}, {'r', "refer", "refer"}, {'t', "tbl", "tbl"}, {'v', "vgrind -f", "vgrind -f"}, {0, 0, 0} }; struct suffix { char *ds; char *fs; }; /* * Flags that control behavior of build_manpath() * * BMP_ISPATH pathv is a vector constructed from PATH. * Perform appropriate path translations for * manpath. * BMP_APPEND_MANDIR Add /usr/share/man to the end if it * hasn't already appeared earlier. * BMP_FALLBACK_MANDIR Append /usr/share/man only if no other * manpath (including derived from PATH) * elements are valid. */ #define BMP_ISPATH 1 #define BMP_APPEND_MANDIR 2 #define BMP_FALLBACK_MANDIR 4 /* * When doing equality comparisons of directories, device and inode * comparisons are done. The dupsec and dupnode structures are used * to form a list of lists for this processing. */ struct secnode { char *secp; struct secnode *next; }; struct dupnode { dev_t dev; /* from struct stat st_dev */ ino_t ino; /* from struct stat st_ino */ struct secnode *secl; /* sections already considered */ struct dupnode *next; }; /* * Map directories that may appear in PATH to the corresponding * man directory */ static struct pathmap { char *bindir; char *mandir; dev_t dev; ino_t ino; } bintoman[] = { {"/sbin", "/usr/share/man,1m", 0, 0}, {"/usr/sbin", "/usr/share/man,1m", 0, 0}, {"/usr/ucb", "/usr/share/man,1b", 0, 0}, {"/usr/bin/X11", "/usr/X11/share/man", 0, 0}, /* * Restrict to section 1 so that whatis /usr/{,xpg4,xpg6}/bin/ls * does not confuse users with section 1 and 1b */ {"/usr/bin", "/usr/share/man,1,1m,1s,1t,1c,1f", 0, 0}, {"/usr/xpg4/bin", "/usr/share/man,1", 0, 0}, {"/usr/xpg6/bin", "/usr/share/man,1", 0, 0}, {NULL, NULL, 0, 0} }; /* * Subdirectories to search for unformatted/formatted man page * versions, in nroff and troff variations. The searching * code in manual() is structured to expect there to be two * subdirectories apiece, the first for unformatted files * and the second for formatted ones. */ static char *nroffdirs[] = { "man", "cat", 0 }; static char *troffdirs[] = { "man", "fmt", 0 }; #define MAN_USAGE "\ usage:\tman [-] [-adFlprt] [-M path] [-T macro-package ] [ -s section ] \ name ...\n\ \tman [-M path] -k keyword ...\n\tman [-M path] -f file ..." #define CATMAN_USAGE "\ usage:\tcatman [-p] [-c|-ntw] [-M path] [-T macro-package ] [sections]" static char *opts[] = { "FfkrpP:M:T:ts:lad", /* man */ "wpnP:M:T:tc" /* catman */ }; struct man_node { char *path; /* mandir path */ char **secv; /* submandir suffices */ int defsrch; /* hint for man -p to avoid section list */ int frompath; /* hint for man -d and catman -p */ struct man_node *next; }; static char *pages[MAXPAGES]; static char **endp = pages; /* * flags (options) */ static int nomore; static int troffit; static int debug; static int Tflag; static int sargs; static int margs; static int force; static int found; static int list; static int all; static int whatis; static int apropos; static int catmando; static int nowhatis; static int whatonly; static int compargs; /* -c option for catman */ static int printmp; static char *CAT = CAT_; static char macros[MAXPATHLEN]; static char *mansec; static char *pager; static char *troffcmd; static char *troffcat; static char **subdirs; static char *check_config(char *); static struct man_node *build_manpath(char **, int); static void getpath(struct man_node *, char **); static void getsect(struct man_node *, char **); static void get_all_sect(struct man_node *); static void catman(struct man_node *, char **, int); static int makecat(char *, char **, int); static int getdirs(char *, char ***, short); static void whatapro(struct man_node *, char *, int); static void lookup_windex(char *, char *, char **); static int icmp(wchar_t *, wchar_t *); static void more(char **, int); static void cleanup(char **); static void bye(int); static char **split(char *, char); static void freev(char **); static void fullpaths(struct man_node **); static void lower(char *); static int cmp(const void *, const void *); static void manual(struct man_node *, char *); static void mandir(char **, char *, char *); static void sortdir(DIR *, char ***); static int searchdir(char *, char *, char *); static int windex(char **, char *, char *); static void section(struct suffix *, char *); static int bfsearch(FILE *, char **, char *, char **); static int compare(char *, char *, char **); static int format(char *, char *, char *, char *); static char *addlocale(char *); static int get_manconfig(FILE *, char *); static void malloc_error(void); static int sgmlcheck(const char *); static char *map_section(char *, char *); static void free_manp(struct man_node *manp); static void init_bintoman(void); static char *path_to_manpath(char *); static int dupcheck(struct man_node *, struct dupnode **); static void free_dupnode(struct dupnode *); static void print_manpath(struct man_node *, char *); /* * This flag is used when the SGML-to-troff converter * is absent - all the SGML searches are bypassed. */ static int no_sroff = 0; /* * This flag is used to describe the case where we've found * an SGML formatted manpage in the sman directory, we haven't * found a troff formatted manpage, and we don't have the SGML to troff * conversion utility on the system. */ static int sman_no_man_no_sroff; static char language[PATH_MAX + 1]; /* LC_MESSAGES */ static char localedir[PATH_MAX + 1]; /* locale specific path component */ static int defaultmandir = 1; /* if processing default mandir, 1 */ static char *newsection = NULL; int main(int argc, char *argv[]) { int badopts = 0; int c; char **pathv; char *cmdname; char *manpath = NULL; static struct man_node *manpage = NULL; int bmp_flags = 0; if (access(SROFF_CMD, F_OK | X_OK) != 0) no_sroff = 1; (void) setlocale(LC_ALL, ""); (void) strcpy(language, setlocale(LC_MESSAGES, (char *)0)); if (strcmp("C", language) != 0) (void) sprintf(localedir, "%s", language); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); (void) strcpy(macros, TMAC_AN); /* * get base part of command name */ if ((cmdname = strrchr(argv[0], '/')) != NULL) cmdname++; else cmdname = argv[0]; if (eq(cmdname, "apropos") || eq(cmdname, "whatis")) { whatis++; apropos = (*cmdname == 'a'); if ((optind = 1) == argc) { (void) fprintf(stderr, gettext("%s what?\n"), cmdname); exit(2); } goto doargs; } else if (eq(cmdname, "catman")) catmando++; opterr = 0; while ((c = getopt(argc, argv, opts[catmando])) != -1) switch (c) { /* * man specific options */ case 'k': apropos++; /*FALLTHROUGH*/ case 'f': whatis++; break; case 'F': force++; /* do lookups the hard way */ break; case 's': mansec = optarg; sargs++; break; case 'r': nomore++, troffit++; break; case 'l': list++; /* implies all */ /*FALLTHROUGH*/ case 'a': all++; break; case 'd': debug++; break; /* * man and catman use -p differently. In catman it * enables debug mode and in man it prints the (possibly * derived from PATH or name operand) MANPATH. */ case 'p': if (catmando == 0) { printmp++; } else { debug++; } break; case 'n': nowhatis++; break; case 'w': whatonly++; break; case 'c': /* n|troff compatibility */ if (no_sroff) (void) fprintf(stderr, gettext( "catman: SGML conversion not " "available -- -c flag ignored\n")); else compargs++; continue; /* * shared options */ case 'P': /* Backwards compatibility */ case 'M': /* Respecify path for man pages. */ manpath = optarg; margs++; break; case 'T': /* Respecify man macros */ (void) strcpy(macros, optarg); Tflag++; break; case 't': troffit++; break; case '?': badopts++; } /* * Bad options or no args? * (man -p and catman don't need args) */ if (badopts || (!catmando && !printmp && optind == argc)) { (void) fprintf(stderr, "%s\n", catmando ? gettext(CATMAN_USAGE) : gettext(MAN_USAGE)); exit(2); } if (compargs && (nowhatis || whatonly || troffit)) { (void) fprintf(stderr, "%s\n", gettext(CATMAN_USAGE)); (void) fprintf(stderr, gettext( "-c option cannot be used with [-w][-n][-t]\n")); exit(2); } if (sargs && margs && catmando) { (void) fprintf(stderr, "%s\n", gettext(CATMAN_USAGE)); exit(2); } if (troffit == 0 && nomore == 0 && !isatty(fileno(stdout))) nomore++; /* * Collect environment information. */ if (troffit) { if ((troffcmd = getenv("TROFF")) == NULL) troffcmd = TROFF; if ((troffcat = getenv("TCAT")) == NULL) troffcat = TCAT; } else { if (((pager = getenv("PAGER")) == NULL) || (*pager == NULL)) pager = MORE; } doargs: subdirs = troffit ? troffdirs : nroffdirs; init_bintoman(); if (manpath == NULL && (manpath = getenv("MANPATH")) == NULL) { if ((manpath = getenv("PATH")) != NULL) { bmp_flags = BMP_ISPATH | BMP_APPEND_MANDIR; } else { manpath = MANDIR; } } pathv = split(manpath, ':'); manpage = build_manpath(pathv, bmp_flags); /* release pathv allocated by split() */ freev(pathv); fullpaths(&manpage); if (catmando) { catman(manpage, argv+optind, argc-optind); exit(0); } /* * The manual routine contains windows during which * termination would leave a temp file behind. Thus * we blanket the whole thing with a clean-up routine. */ if (signal(SIGINT, SIG_IGN) == SIG_DFL) { (void) signal(SIGINT, bye); (void) signal(SIGQUIT, bye); (void) signal(SIGTERM, bye); } /* * "man -p" without operands */ if ((printmp != 0) && (optind == argc)) { print_manpath(manpage, NULL); exit(0); } for (; optind < argc; optind++) { if (strcmp(argv[optind], "-") == 0) { nomore++; CAT = CAT_S; } else { char *cmd; static struct man_node *mp; char *pv[2]; /* * If full path to command specified, customize * manpath accordingly */ if ((cmd = strrchr(argv[optind], '/')) != NULL) { *cmd = '\0'; if ((pv[0] = strdup(argv[optind])) == NULL) { malloc_error(); } pv[1] = NULL; *cmd = '/'; mp = build_manpath(pv, BMP_ISPATH|BMP_FALLBACK_MANDIR); } else { mp = manpage; } if (whatis) { whatapro(mp, argv[optind], apropos); } else if (printmp != 0) { print_manpath(mp, argv[optind]); } else { manual(mp, argv[optind]); } if (mp != NULL && mp != manpage) { free(pv[0]); free_manp(mp); } } } return (0); /*NOTREACHED*/ } /* * This routine builds the manpage structure from MANPATH or PATH, * depending on flags. See BMP_* definitions above for valid * flags. * * Assumes pathv elements were malloc'd, as done by split(). * Elements may be freed and reallocated to have different contents. */ static struct man_node * build_manpath(char **pathv, int flags) { struct man_node *manpage = NULL; struct man_node *currp = NULL; struct man_node *lastp = NULL; char **p; char **q; char *mand = NULL; char *mandir = MANDIR; int s; struct dupnode *didup = NULL; s = sizeof (struct man_node); for (p = pathv; *p; ) { if (flags & BMP_ISPATH) { if ((mand = path_to_manpath(*p)) == NULL) { goto next; } free(*p); *p = mand; } q = split(*p, ','); if (access(q[0], R_OK|X_OK) != 0) { if (catmando) { (void) fprintf(stderr, gettext("%s is not accessible.\n"), q[0]); (void) fflush(stderr); } } else { /* * Some element exists. Do not append MANDIR as a * fallback. */ flags &= ~BMP_FALLBACK_MANDIR; if ((currp = (struct man_node *)calloc(1, s)) == NULL) { malloc_error(); } currp->frompath = (flags & BMP_ISPATH); if (manpage == NULL) { lastp = manpage = currp; } getpath(currp, p); getsect(currp, p); /* * If there are no new elements in this path, * do not add it to the manpage list */ if (dupcheck(currp, &didup) != 0) { freev(currp->secv); free(currp); } else { currp->next = NULL; if (currp != manpage) { lastp->next = currp; } lastp = currp; } } freev(q); next: /* * Special handling of appending MANDIR. * After all pathv elements have been processed, append MANDIR * if needed. */ if (p == &mandir) { break; } p++; if (*p != NULL) { continue; } if (flags & (BMP_APPEND_MANDIR|BMP_FALLBACK_MANDIR)) { p = &mandir; flags &= ~BMP_ISPATH; } } free_dupnode(didup); return (manpage); } /* * Stores the mandir path into the manp structure. */ static void getpath(struct man_node *manp, char **pv) { char *s; int i = 0; s = *pv; while (*s != NULL && *s != ',') i++, s++; manp->path = (char *)malloc(i+1); if (manp->path == NULL) malloc_error(); (void) strncpy(manp->path, *pv, i); *(manp->path + i) = '\0'; } /* * Stores the mandir's corresponding sections (submandir * directories) into the manp structure. */ static void getsect(struct man_node *manp, char **pv) { char *sections; char **sectp; if (sargs) { manp->secv = split(mansec, ','); for (sectp = manp->secv; *sectp; sectp++) lower(*sectp); } else if ((sections = strchr(*pv, ',')) != NULL) { if (debug) { if (manp->frompath != 0) { /* * TRANSLATION_NOTE - message for man -d or catman -p * ex. /usr/share/man: derived from PATH, MANSECTS=,1b */ (void) printf(gettext( "%s: derived from PATH, MANSECTS=%s\n"), manp->path, sections); } else { /* * TRANSLATION_NOTE - message for man -d or catman -p * ex. /usr/share/man: from -M option, MANSECTS=,1,2,3c */ (void) fprintf(stdout, gettext( "%s: from -M option, MANSECTS=%s\n"), manp->path, sections); } } manp->secv = split(++sections, ','); for (sectp = manp->secv; *sectp; sectp++) lower(*sectp); if (*manp->secv == NULL) get_all_sect(manp); } else if ((sections = check_config(*pv)) != NULL) { manp->defsrch = 1; /* * TRANSLATION_NOTE - message for man -d or catman -p * ex. /usr/share/man: from man.cf, MANSECTS=1,1m,1c,1f */ if (debug) (void) fprintf(stdout, gettext( "%s: from %s, MANSECTS=%s\n"), manp->path, CONFIG, sections); manp->secv = split(sections, ','); for (sectp = manp->secv; *sectp; sectp++) lower(*sectp); if (*manp->secv == NULL) get_all_sect(manp); } else { manp->defsrch = 1; /* * TRANSLATION_NOTE - message for man -d or catman -p * if man.cf has not been found or sections has not been specified * man/catman searches the sections lexicographically. */ if (debug) (void) fprintf(stdout, gettext( "%s: search the sections lexicographically\n"), manp->path); manp->secv = NULL; get_all_sect(manp); } } /* * Get suffices of all sub-mandir directories in a mandir. */ static void get_all_sect(struct man_node *manp) { DIR *dp; char **dirv; char **dv; char **p; char prev[MAXSUFFIX]; char tmp[MAXSUFFIX]; int plen; int maxentries = MAXTOKENS; int entries = 0; if ((dp = opendir(manp->path)) == 0) return; /* * sortdir() allocates memory for dirv and dirv[]. */ sortdir(dp, &dirv); (void) closedir(dp); if (manp->secv == NULL) { /* * allocates memory for manp->secv only if it's NULL */ manp->secv = (char **)malloc(maxentries * sizeof (char *)); if (manp->secv == NULL) malloc_error(); } (void) memset(tmp, 0, MAXSUFFIX); (void) memset(prev, 0, MAXSUFFIX); for (dv = dirv, p = manp->secv; *dv; dv++) { plen = PLEN; if (match(*dv, SGMLDIR, PLEN+1)) ++plen; if (strcmp(*dv, CONFIG) == 0) { /* release memory allocated by sortdir */ free(*dv); continue; } (void) sprintf(tmp, "%s", *dv + plen); if (strcmp(prev, tmp) == 0) { /* release memory allocated by sortdir */ free(*dv); continue; } (void) sprintf(prev, "%s", *dv + plen); /* * copy the string in (*dv + plen) to *p */ *p = strdup(*dv + plen); if (*p == NULL) malloc_error(); p++; entries++; if (entries == maxentries) { maxentries += MAXTOKENS; manp->secv = (char **)realloc(manp->secv, sizeof (char *) * maxentries); if (manp->secv == NULL) malloc_error(); p = manp->secv + entries; } /* release memory allocated by sortdir */ free(*dv); } *p = 0; /* release memory allocated by sortdir */ free(dirv); } /* * Format man pages (build cat pages); if no * sections are specified, build all of them. * When building cat pages: * catman() tries to build cat pages for locale specific * man dirs first. Then, catman() tries to build cat pages * for the default man dir (for C locale like /usr/share/man) * regardless of the locale. * When building windex file: * catman() tries to build windex file for locale specific * man dirs first. Then, catman() tries to build windex file * for the default man dir (for C locale like /usr/share/man) * regardless of the locale. */ static void catman(struct man_node *manp, char **argv, int argc) { char cmdbuf[BUFSIZ]; char **dv; int changed; struct man_node *p; int ndirs = 0; char *ldir; int i; struct dupnode *dnp = NULL; char **realsecv; char *fakesecv[2] = { " catman ", NULL }; for (p = manp; p != NULL; p = p->next) { /* * prevent catman from doing very heavy lifting multiple * times on some directory */ realsecv = p->secv; p->secv = fakesecv; if (dupcheck(p, &dnp) != 0) { p->secv = realsecv; continue; } /* * TRANSLATION_NOTE - message for catman -p * ex. mandir path = /usr/share/man */ if (debug) (void) fprintf(stdout, gettext( "\nmandir path = %s\n"), p->path); ndirs = 0; /* * Build cat pages * addlocale() allocates memory and returns it */ ldir = addlocale(p->path); if (!whatonly) { if (*localedir != '\0') { if (defaultmandir) defaultmandir = 0; /* getdirs allocate memory for dv */ ndirs = getdirs(ldir, &dv, 1); if (ndirs != 0) { changed = argc ? makecat(ldir, argv, argc) : makecat(ldir, dv, ndirs); /* release memory by getdirs */ for (i = 0; i < ndirs; i++) { free(dv[i]); } free(dv); } } /* default man dir is always processed */ defaultmandir = 1; ndirs = getdirs(p->path, &dv, 1); changed = argc ? makecat(p->path, argv, argc) : makecat(p->path, dv, ndirs); /* release memory allocated by getdirs */ for (i = 0; i < ndirs; i++) { free(dv[i]); } free(dv); } /* * Build whatis database * print error message if locale is set and man dir not found * won't build it at all if -c option is on */ if (!compargs && (whatonly || (!nowhatis && changed))) { if (*localedir != '\0') { /* just count the number of ndirs */ if ((ndirs = getdirs(ldir, NULL, 0)) != 0) { (void) sprintf(cmdbuf, "/usr/bin/sh %s %s", MAKEWHATIS, ldir); (void) sys(cmdbuf); } } /* whatis database of the default man dir */ /* will be always built in C locale. */ (void) sprintf(cmdbuf, "/usr/bin/sh %s %s", MAKEWHATIS, p->path); (void) sys(cmdbuf); } /* release memory allocated by addlocale() */ free(ldir); } free_dupnode(dnp); } /* * Build cat pages for given sections */ static int makecat(char *path, char **dv, int ndirs) { DIR *dp, *sdp; struct dirent *d; struct stat sbuf; char mandir[MAXPATHLEN+1]; char smandir[MAXPATHLEN+1]; char catdir[MAXPATHLEN+1]; char *dirp, *sdirp; int i, fmt; int manflag, smanflag; for (i = fmt = 0; i < ndirs; i++) { (void) snprintf(mandir, MAXPATHLEN, "%s/%s%s", path, MANDIRNAME, dv[i]); (void) snprintf(smandir, MAXPATHLEN, "%s/%s%s", path, SGMLDIR, dv[i]); (void) snprintf(catdir, MAXPATHLEN, "%s/%s%s", path, subdirs[1], dv[i]); dirp = strrchr(mandir, '/') + 1; sdirp = strrchr(smandir, '/') + 1; manflag = smanflag = 0; if ((dp = opendir(mandir)) != NULL) manflag = 1; if (!no_sroff && (sdp = opendir(smandir)) != NULL) smanflag = 1; if (dp == 0 && sdp == 0) { if (strcmp(mandir, CONFIG) == 0) perror(mandir); continue; } /* * TRANSLATION_NOTE - message for catman -p * ex. Building cat pages for mandir = /usr/share/man/ja */ if (debug) (void) fprintf(stdout, gettext( "Building cat pages for mandir = %s\n"), path); if (!compargs && stat(catdir, &sbuf) < 0) { (void) umask(02); /* * TRANSLATION_NOTE - message for catman -p * ex. mkdir /usr/share/man/ja/cat3c */ if (debug) (void) fprintf(stdout, gettext("mkdir %s\n"), catdir); else { if (mkdir(catdir, 0755) < 0) { perror(catdir); continue; } (void) chmod(catdir, 0755); } } /* * if it is -c option of catman, if there is no * coresponding man dir for sman files to go to, * make the man dir */ if (compargs && !manflag) { if (mkdir(mandir, 0755) < 0) { perror(mandir); continue; } (void) chmod(mandir, 0755); } if (smanflag) { while ((d = readdir(sdp))) { if (eq(".", d->d_name) || eq("..", d->d_name)) continue; if (format(path, sdirp, (char *)0, d->d_name) > 0) fmt++; } } if (manflag && !compargs) { while ((d = readdir(dp))) { if (eq(".", d->d_name) || eq("..", d->d_name)) continue; if (format(path, dirp, (char *)0, d->d_name) > 0) fmt++; } } if (manflag) (void) closedir(dp); if (smanflag) (void) closedir(sdp); } return (fmt); } /* * Get all "man" and "sman" dirs under a given manpath * and return the number found * If -c option is on, only count sman dirs */ static int getdirs(char *path, char ***dirv, short flag) { DIR *dp; struct dirent *d; int n = 0; int plen, sgml_flag, man_flag; int i = 0; int maxentries = MAXDIRS; char **dv; if ((dp = opendir(path)) == 0) { if (debug) { if (*localedir != '\0') (void) printf(gettext("\ locale is %s, search in %s\n"), localedir, path); perror(path); } return (0); } if (flag) { /* allocate memory for dirv */ *dirv = (char **)malloc(sizeof (char *) * maxentries); if (*dirv == NULL) malloc_error(); dv = *dirv; } while ((d = readdir(dp))) { plen = PLEN; man_flag = sgml_flag = 0; if (match(d->d_name, SGMLDIR, PLEN+1)) { plen = PLEN + 1; sgml_flag = 1; i++; } if (match(subdirs[0], d->d_name, PLEN)) man_flag = 1; if (compargs && sgml_flag) { if (flag) { *dv = strdup(d->d_name+plen); if (*dv == NULL) malloc_error(); dv++; n = i; } } else if (!compargs && (sgml_flag || man_flag)) { if (flag) { *dv = strdup(d->d_name+plen); if (*dv == NULL) malloc_error(); dv++; } n++; } if (flag) { if ((dv - *dirv) == maxentries) { int entries = maxentries; maxentries += MAXTOKENS; *dirv = (char **)realloc(*dirv, sizeof (char *) * maxentries); if (*dirv == NULL) malloc_error(); dv = *dirv + entries; } } } (void) closedir(dp); return (n); } /* * Find matching whatis or apropos entries * whatapro() tries to handle the windex file of the locale specific * man dirs first, then tries to handle the windex file of the default * man dir (of C locale like /usr/share/man). */ static void whatapro(struct man_node *manp, char *word, int apropos) { char whatpath[MAXPATHLEN+1]; char *p; struct man_node *b; int ndirs = 0; char *ldir; /* * TRANSLATION_NOTE - message for man -d * %s takes a parameter to -k option. */ DPRINTF(gettext("word = %s \n"), word); /* * get base part of name */ if (!apropos) { if ((p = strrchr(word, '/')) == NULL) p = word; else p++; } else { p = word; } for (b = manp; b != NULL; b = b->next) { if (*localedir != '\0') { /* addlocale() allocates memory and returns it */ ldir = addlocale(b->path); if (defaultmandir) defaultmandir = 0; ndirs = getdirs(ldir, NULL, 0); if (ndirs != 0) { (void) sprintf(whatpath, "%s/%s", ldir, WHATIS); /* * TRANSLATION_NOTE - message for man -d * ex. mandir path = /usr/share/man/ja */ DPRINTF(gettext("\nmandir path = %s\n"), ldir); lookup_windex(whatpath, p, b->secv); } /* release memory allocated by addlocale() */ free(ldir); } defaultmandir = 1; (void) sprintf(whatpath, "%s/%s", b->path, WHATIS); /* * TRANSLATION_NOTE - message for man -d * ex. mandir path = /usr/share/man */ DPRINTF(gettext("\nmandir path = %s\n"), b->path); lookup_windex(whatpath, p, b->secv); } } static void lookup_windex(char *whatpath, char *word, char **secv) { FILE *fp; char *matches[MAXPAGES]; char **pp; wchar_t wbuf[BUFSIZ]; wchar_t *word_wchar = NULL; wchar_t *ws; size_t word_len, ret; if ((fp = fopen(whatpath, "r")) == NULL) { perror(whatpath); return; } if (apropos) { word_len = strlen(word) + 1; if ((word_wchar = (wchar_t *)malloc(sizeof (wchar_t) * word_len)) == NULL) { malloc_error(); } ret = mbstowcs(word_wchar, (const char *)word, word_len); if (ret == (size_t)-1) { (void) fprintf(stderr, gettext( "Invalid character in keyword\n")); exit(1); } while (fgetws(wbuf, BUFSIZ, fp) != NULL) for (ws = wbuf; *ws; ws++) if (icmp(word_wchar, ws) == 0) { (void) printf("%ws", wbuf); break; } } else { if (bfsearch(fp, matches, word, secv)) for (pp = matches; *pp; pp++) { (void) printf("%s", *pp); /* * release memory allocated by * strdup() in bfsearch() */ free(*pp); } } (void) fclose(fp); if (word_wchar) free(word_wchar); } /* * case-insensitive compare unless upper case is used * ie) "mount" matches mount, Mount, MOUNT * "Mount" matches Mount, MOUNT * "MOUNT" matches MOUNT only * If matched return 0. Otherwise, return 1. */ static int icmp(wchar_t *ws, wchar_t *wt) { for (; (*ws == 0) || (*ws == (iswupper(*ws) ? *wt: towlower(*wt))); ws++, wt++) if (*ws == 0) return (0); return (1); } /* * Invoke PAGER with all matching man pages */ static void more(char **pages, int plain) { char cmdbuf[BUFSIZ]; char **vp; /* * Dont bother. */ if (list || (*pages == 0)) return; if (plain && troffit) { cleanup(pages); return; } (void) sprintf(cmdbuf, "%s", troffit ? troffcat : plain ? CAT : pager); /* * Build arg list */ for (vp = pages; vp < endp; vp++) { (void) strcat(cmdbuf, " "); (void) strcat(cmdbuf, *vp); } (void) sys(cmdbuf); cleanup(pages); } /* * Get rid of dregs. */ static void cleanup(char **pages) { char **vp; for (vp = pages; vp < endp; vp++) { if (match(TEMPLATE, *vp, TMPLEN)) (void) unlink(*vp); free(*vp); } endp = pages; /* reset */ } /* * Clean things up after receiving a signal. */ /*ARGSUSED*/ static void bye(int sig) { cleanup(pages); exit(1); /*NOTREACHED*/ } /* * Split a string by specified separator. * ignore empty components/adjacent separators. * returns vector to all tokens */ static char ** split(char *s1, char sep) { char **tokv, **vp; char *mp, *tp; int maxentries = MAXTOKENS; int entries = 0; tokv = vp = (char **)malloc(maxentries * sizeof (char *)); if (tokv == NULL) malloc_error(); mp = s1; for (; mp && *mp; mp = tp) { tp = strchr(mp, sep); if (mp == tp) { /* empty component */ tp++; /* ignore */ continue; } if (tp) { /* a component found */ size_t len; len = tp - mp; *vp = (char *)malloc(sizeof (char) * len + 1); if (*vp == NULL) malloc_error(); (void) strncpy(*vp, mp, len); *(*vp + len) = '\0'; tp++; vp++; } else { /* the last component */ *vp = strdup(mp); if (*vp == NULL) malloc_error(); vp++; } entries++; if (entries == maxentries) { maxentries += MAXTOKENS; tokv = (char **)realloc(tokv, maxentries * sizeof (char *)); if (tokv == NULL) malloc_error(); vp = tokv + entries; } } *vp = 0; return (tokv); } /* * Free a vector allocated by split(); */ static void freev(char **v) { int i; for (i = 0; v[i] != NULL; i++) { free(v[i]); } free(v); } /* * Convert paths to full paths if necessary * */ static void fullpaths(struct man_node **manp_head) { char *cwd = NULL; char *p; char cwd_gotten = 0; struct man_node *manp = *manp_head; struct man_node *b; struct man_node *prev = NULL; for (b = manp; b != NULL; b = b->next) { if (*(b->path) == '/') { prev = b; continue; } /* try to get cwd if haven't already */ if (!cwd_gotten) { cwd = getcwd(NULL, MAXPATHLEN+1); cwd_gotten = 1; } if (cwd) { /* case: relative manpath with cwd: make absolute */ if ((p = malloc(strlen(b->path)+strlen(cwd)+2)) == NULL) { malloc_error(); } (void) sprintf(p, "%s/%s", cwd, b->path); /* * resetting b->path */ free(b->path); b->path = p; } else { /* case: relative manpath but no cwd: omit path entry */ if (prev) prev->next = b->next; else *manp_head = b->next; free_manp(b); } } /* * release memory allocated by getcwd() */ free(cwd); } /* * Free a man_node structure and its contents */ static void free_manp(struct man_node *manp) { char **p; free(manp->path); p = manp->secv; while ((p != NULL) && (*p != NULL)) { free(*p); p++; } free(manp->secv); free(manp); } /* * Map (in place) to lower case */ static void lower(char *s) { if (s == 0) return; while (*s) { if (isupper(*s)) *s = tolower(*s); s++; } } /* * compare for sort() * sort first by section-spec, then by prefix {sman, man, cat, fmt} * note: prefix is reverse sorted so that "sman" and "man" always * comes before {cat, fmt} */ static int cmp(const void *arg1, const void *arg2) { int n; char **p1 = (char **)arg1; char **p2 = (char **)arg2; /* by section; sman always before man dirs */ if ((n = strcmp(*p1 + PLEN + (**p1 == 's' ? 1 : 0), *p2 + PLEN + (**p2 == 's' ? 1 : 0)))) return (n); /* by prefix reversed */ return (strncmp(*p2, *p1, PLEN)); } /* * Find a man page ... * Loop through each path specified, * first try the lookup method (whatis database), * and if it doesn't exist, do the hard way. */ static void manual(struct man_node *manp, char *name) { struct man_node *p; struct man_node *local; int ndirs = 0; char *ldir; char *ldirs[2]; char *fullname = name; char *slash; if ((slash = strrchr(name, '/')) != NULL) { name = slash + 1; } /* * for each path in MANPATH */ found = 0; for (p = manp; p != NULL; p = p->next) { /* * TRANSLATION_NOTE - message for man -d * ex. mandir path = /usr/share/man */ DPRINTF(gettext("\nmandir path = %s\n"), p->path); if (*localedir != '\0') { /* addlocale() allocates memory and returns it */ ldir = addlocale(p->path); if (defaultmandir) defaultmandir = 0; /* * TRANSLATION_NOTE - message for man -d * ex. localedir = ja, ldir = /usr/share/man/ja */ if (debug) (void) printf(gettext( "localedir = %s, ldir = %s\n"), localedir, ldir); ndirs = getdirs(ldir, NULL, 0); if (ndirs != 0) { ldirs[0] = ldir; ldirs[1] = NULL; local = build_manpath(ldirs, 0); if (force || windex(local->secv, ldir, name) < 0) mandir(local->secv, ldir, name); free_manp(local); } /* release memory allocated by addlocale() */ free(ldir); } defaultmandir = 1; /* * locale mandir not valid, man page in locale * mandir not found, or -a option present */ if (ndirs == 0 || !found || all) { if (force || windex(p->secv, p->path, name) < 0) mandir(p->secv, p->path, name); } if (found && !all) break; } if (found) { more(pages, nomore); } else { if (sargs) { (void) printf(gettext("No entry for %s in section(s) " "%s of the manual.\n"), fullname, mansec); } else { (void) printf(gettext( "No manual entry for %s.\n"), fullname, mansec); } if (sman_no_man_no_sroff) (void) printf(gettext("(An SGML manpage was found " "for '%s' but it cannot be displayed.)\n"), fullname, mansec); } sman_no_man_no_sroff = 0; } /* * For a specified manual directory, * read, store, & sort section subdirs, * for each section specified * find and search matching subdirs */ static void mandir(char **secv, char *path, char *name) { DIR *dp; char **dirv; char **dv, **pdv; int len, dslen, plen = PLEN; if ((dp = opendir(path)) == 0) { /* * TRANSLATION_NOTE - message for man -d or catman -p * opendir(%s) returned 0 */ if (debug) (void) fprintf(stdout, gettext( " opendir on %s failed\n"), path); return; } /* * TRANSLATION_NOTE - message for man -d or catman -p * ex. mandir path = /usr/share/man/ja */ if (debug) (void) printf(gettext("mandir path = %s\n"), path); /* * sordir() allocates memory for dirv and dirv[]. */ sortdir(dp, &dirv); /* * Search in the order specified by MANSECTS */ for (; *secv; secv++) { /* * TRANSLATION_NOTE - message for man -d or catman -p * ex. section = 3c */ DPRINTF(gettext(" section = %s\n"), *secv); len = strlen(*secv); for (dv = dirv; *dv; dv++) { plen = PLEN; if (*dv[0] == 's') plen++; dslen = strlen(*dv+plen); if (dslen > len) len = dslen; if (**secv == '\\') { if (!eq(*secv + 1, *dv+plen)) continue; } else if (!match(*secv, *dv+plen, len)) { /* check to see if directory name changed */ if (!all && (newsection = map_section(*secv, path)) == NULL) { continue; } if (newsection == NULL) newsection = ""; if (!match(newsection, *dv+plen, len)) { continue; } } if (searchdir(path, *dv, name) == 0) continue; if (!all) { /* release memory allocated by sortdir() */ pdv = dirv; while (*pdv) { free(*pdv); pdv++; } (void) closedir(dp); /* release memory allocated by sortdir() */ free(dirv); return; } /* * if we found a match in the man dir skip * the corresponding cat dir if it exists */ if (all && **dv == 'm' && *(dv+1) && eq(*(dv+1)+plen, *dv+plen)) dv++; } } /* release memory allocated by sortdir() */ pdv = dirv; while (*pdv) { free(*pdv); pdv++; } free(dirv); (void) closedir(dp); } /* * Sort directories. */ static void sortdir(DIR *dp, char ***dirv) { struct dirent *d; char **dv; int maxentries = MAXDIRS; int entries = 0; *dirv = (char **)malloc(sizeof (char *) * maxentries); dv = *dirv; while ((d = readdir(dp))) { /* store dirs */ if (eq(d->d_name, ".") || eq(d->d_name, "..")) /* ignore */ continue; /* check if it matches sman, man, cat format */ if (match(d->d_name, SGMLDIR, PLEN+1) || match(d->d_name, subdirs[0], PLEN) || match(d->d_name, subdirs[1], PLEN)) { *dv = malloc(strlen(d->d_name) + 1); if (*dv == NULL) malloc_error(); (void) strcpy(*dv, d->d_name); dv++; entries++; if (entries == maxentries) { maxentries += MAXDIRS; *dirv = (char **)realloc(*dirv, sizeof (char *) * maxentries); if (*dirv == NULL) malloc_error(); dv = *dirv + entries; } } } *dv = 0; qsort((void *)*dirv, dv - *dirv, sizeof (char *), cmp); } /* * Search a section subdirectory for a * given man page, return 1 for success */ static int searchdir(char *path, char *dir, char *name) { DIR *sdp; struct dirent *sd; char sectpath[MAXPATHLEN+1]; char file[MAXNAMLEN+1]; char dname[MAXPATHLEN+1]; char *last; int nlen; /* * TRANSLATION_NOTE - message for man -d or catman -p * ex. scanning = man3c */ DPRINTF(gettext(" scanning = %s\n"), dir); (void) sprintf(sectpath, "%s/%s", path, dir); (void) snprintf(file, MAXPATHLEN, "%s.", name); if ((sdp = opendir(sectpath)) == 0) { if (errno != ENOTDIR) /* ignore matching cruft */ perror(sectpath); return (0); } while ((sd = readdir(sdp))) { last = strrchr(sd->d_name, '.'); nlen = last - sd->d_name; (void) sprintf(dname, "%.*s.", nlen, sd->d_name); if (eq(dname, file) || eq(sd->d_name, name)) { if (no_sroff && *dir == 's') { sman_no_man_no_sroff = 1; return (0); } (void) format(path, dir, name, sd->d_name); (void) closedir(sdp); return (1); } } (void) closedir(sdp); return (0); } /* * Check the hash table of old directory names to see if there is a * new directory name. * Returns new directory name if a match; after checking to be sure * directory exists. * Otherwise returns NULL */ static char * map_section(char *section, char *path) { int i; int len; char fullpath[MAXPATHLEN]; if (list) /* -l option fall through */ return (NULL); for (i = 0; i <= ((sizeof (map)/sizeof (map[0]) - 1)); i++) { if (strlen(section) > strlen(map[i].new_name)) { len = strlen(section); } else { len = strlen(map[i].new_name); } if (match(section, map[i].old_name, len)) { (void) sprintf(fullpath, "%s/sman%s", path, map[i].new_name); if (!access(fullpath, R_OK | X_OK)) { return (map[i].new_name); } else { return (NULL); } } } return (NULL); } /* * Use windex database for quick lookup of man pages * instead of mandir() (brute force search) */ static int windex(char **secv, char *path, char *name) { FILE *fp; struct stat sbuf; struct suffix *sp; struct suffix psecs[MAXPAGES]; char whatfile[MAXPATHLEN+1]; char page[MAXPATHLEN+1]; char *matches[MAXPAGES]; char *file, *dir; char **sv, **vp; int len, dslen, exist, i; int found_in_windex = 0; char *tmp[] = {0, 0, 0, 0}; (void) sprintf(whatfile, "%s/%s", path, WHATIS); if ((fp = fopen(whatfile, "r")) == NULL) { if (errno == ENOENT) return (-1); return (0); } /* * TRANSLATION_NOTE - message for man -d or catman -p * ex. search in = /usr/share/man/ja/windex file */ if (debug) (void) fprintf(stdout, gettext( " search in = %s file\n"), whatfile); if (bfsearch(fp, matches, name, NULL) == 0) { (void) fclose(fp); return (-1); /* force search in mandir */ } (void) fclose(fp); /* * Save and split sections * section() allocates memory for sp->ds */ for (sp = psecs, vp = matches; *vp; vp++, sp++) section(sp, *vp); sp->ds = 0; /* * Search in the order specified * by MANSECTS */ for (; *secv; secv++) { len = strlen(*secv); /* * TRANSLATION_NOTE - message for man -d or catman -p * ex. search an entry to match printf.3c */ if (debug) (void) fprintf(stdout, gettext( " search an entry to match %s.%s\n"), name, *secv); /* * For every whatis entry that * was matched */ for (sp = psecs; sp->ds; sp++) { dslen = strlen(sp->ds); if (dslen > len) len = dslen; if (**secv == '\\') { if (!eq(*secv + 1, sp->ds)) continue; } else if (!match(*secv, sp->ds, len)) { /* check to see if directory name changed */ if (!all && (newsection = map_section(*secv, path)) == NULL) { continue; } if (newsection == NULL) newsection = ""; if (!match(newsection, sp->ds, len)) { continue; } } /* * here to form "sman", "man", "cat"|"fmt" in * order */ if (!no_sroff) { tmp[0] = SGMLDIR; for (i = 1; i < 4; i++) tmp[i] = subdirs[i-1]; } else { for (i = 0; i < 3; i++) tmp[i] = subdirs[i]; } for (sv = tmp; *sv; sv++) { (void) sprintf(page, "%s/%s%s/%s%s%s", path, *sv, sp->ds, name, *sp->fs ? "." : "", sp->fs); exist = (stat(page, &sbuf) == 0); if (exist) break; } if (!exist) { (void) fprintf(stderr, gettext( "%s entry incorrect: %s(%s) not found.\n"), WHATIS, name, sp->ds); continue; } file = strrchr(page, '/'), *file = 0; dir = strrchr(page, '/'); /* * By now we have a match */ found_in_windex = 1; (void) format(path, ++dir, name, ++file); if (!all) goto finish; } } finish: /* * release memory allocated by section() */ sp = psecs; while (sp->ds) { free(sp->ds); sp->ds = NULL; sp++; } /* * If we didn't find a match, return failure as if we didn't find * the windex at all. Why? Well, if you create a windex, then upgrade * to a later release that contains new man pages, and forget to * recreate the windex (since we don't do that automatically), you * won't see any new man pages since they aren't in the windex. * Pretending we didn't see a windex at all if there are no matches * forces a search of the underlying directory. After all, the * goal of the windex is to enable searches (man -k) and speed things * up, not to _prevent_ you from seeing new man pages, so this seems * ok. The only problem is when there are multiple entries (different * sections), and some are in and some are out. Say you do 'man ls', * and ls(1) isn't in the windex, but ls(1B) is. In that case, we * will find a match in ls(1B), and you'll see that man page. * That doesn't seem bad since if you specify the section the search * will be restricted too. So in the example above, if you do * 'man -s 1 ls' you'll get ls(1). */ if (found_in_windex) return (0); else return (-1); } /* * Return pointers to the section-spec * and file-suffix of a whatis entry */ static void section(struct suffix *sp, char *s) { char *lp, *p; lp = strchr(s, '('); p = strchr(s, ')'); if (++lp == 0 || p == 0 || lp == p) { (void) fprintf(stderr, gettext("mangled windex entry:\n\t%s\n"), s); return; } *p = 0; /* * copy the string pointed to by lp */ lp = strdup(lp); if (lp == NULL) malloc_error(); /* * release memory in s * s has been allocated memory in bfsearch() */ free(s); lower(lp); /* * split section-specifier if file-name * suffix differs from section-suffix */ sp->ds = lp; if ((p = strchr(lp, '/'))) { *p++ = 0; sp->fs = p; } else sp->fs = lp; } /* * Binary file search to find matching man * pages in whatis database. */ static int bfsearch(FILE *fp, char **matchv, char *key, char **secv) { char entry[BUFSIZ]; char **vp; long top, bot, mid; int c; vp = matchv; bot = 0; (void) fseek(fp, 0L, 2); top = ftell(fp); for (;;) { mid = (top+bot)/2; (void) fseek(fp, mid, 0); do { c = getc(fp); mid++; } while (c != EOF && c != '\n'); if (fgets(entry, sizeof (entry), fp) == NULL) break; switch (compare(key, entry, secv)) { case -2: case -1: case 0: if (top <= mid) break; top = mid; continue; case 1: case 2: bot = mid; continue; } break; } (void) fseek(fp, bot, 0); while (ftell(fp) < top) { if (fgets(entry, sizeof (entry), fp) == NULL) { *matchv = 0; return (matchv - vp); } switch (compare(key, entry, secv)) { case -2: *matchv = 0; return (matchv - vp); case -1: case 0: *matchv = strdup(entry); if (*matchv == NULL) malloc_error(); else matchv++; break; case 1: case 2: continue; } break; } while (fgets(entry, sizeof (entry), fp)) { switch (compare(key, entry, secv)) { case -1: case 0: *matchv = strdup(entry); if (*matchv == NULL) malloc_error(); else matchv++; continue; } break; } *matchv = 0; return (matchv - vp); } static int compare(char *key, char *entry, char **secv) { char *entbuf; char *s; int comp, mlen; int mbcurmax = MB_CUR_MAX; char *secp = NULL; int rv; int eblen; entbuf = strdup(entry); if (entbuf == NULL) { malloc_error(); } eblen = strlen(entbuf); s = entbuf; while (*s) { if (*s == '\t' || *s == ' ') { *s = '\0'; break; } mlen = mblen(s, mbcurmax); if (mlen == -1) { (void) fprintf(stderr, gettext( "Invalid character in windex file.\n")); exit(1); } s += mlen; } /* * Find the section within parantheses */ if (secv != NULL && (s - entbuf) < eblen) { if ((secp = strchr(s + 1, ')')) != NULL) { *secp = '\0'; if ((secp = strchr(s + 1, '(')) != NULL) { secp++; } } } comp = strcmp(key, entbuf); if (comp == 0) { if (secp == NULL) { rv = 0; } else { while (*secv != NULL) { if ((strcmp(*secv, secp)) == 0) { rv = 0; break; } secv++; } } } else if (comp < 0) { rv = -2; } else { rv = 2; } free(entbuf); return (rv); } /* * Format a man page and follow .so references * if necessary. */ static int format(char *path, char *dir, char *name, char *pg) { char manpname[MAXPATHLEN+1], catpname[MAXPATHLEN+1]; char manpname_sgml[MAXPATHLEN+1], smantmpname[MAXPATHLEN+1]; char soed[MAXPATHLEN+1], soref[MAXPATHLEN+1]; char manbuf[BUFSIZ], cmdbuf[BUFSIZ], tmpbuf[BUFSIZ]; char tmpdir[MAXPATHLEN+1]; int socount, updatedcat, regencat; struct stat mansb, catsb, smansb; char *tmpname; int catonly = 0; struct stat statb; int plen = PLEN; FILE *md; int tempfd; ssize_t count; int temp, sgml_flag = 0, check_flag = 0; char prntbuf[BUFSIZ + 1]; char *ptr; char *new_m; char *tmpsubdir; found++; if (*dir != 'm' && *dir != 's') catonly++; if (*dir == 's') { tmpsubdir = SGMLDIR; ++plen; (void) sprintf(manpname_sgml, "%s/man%s/%s", path, dir+plen, pg); } else tmpsubdir = MANDIRNAME; if (list) { (void) printf(gettext("%s (%s)\t-M %s\n"), name, dir+plen, path); return (-1); } (void) sprintf(manpname, "%s/%s%s/%s", path, tmpsubdir, dir+plen, pg); (void) sprintf(catpname, "%s/%s%s/%s", path, subdirs[1], dir+plen, pg); (void) sprintf(smantmpname, "%s/%s%s/%s", path, SGMLDIR, dir+plen, pg); /* * TRANSLATION_NOTE - message for man -d or catman -p * ex. unformatted = /usr/share/man/ja/man3s/printf.3s */ DPRINTF(gettext( " unformatted = %s\n"), catonly ? "" : manpname); /* * TRANSLATION_NOTE - message for man -d or catman -p * ex. formatted = /usr/share/man/ja/cat3s/printf.3s */ DPRINTF(gettext( " formatted = %s\n"), catpname); /* * Take care of indirect references to other man pages; * i.e., resolve files containing only ".so manx/file.x". * We follow .so chains, replacing title with the .so'ed * file at each stage, and keeping track of how many times * we've done so, so that we can avoid looping. */ *soed = 0; socount = 0; for (;;) { FILE *md; char *cp; char *s; char *new_s; if (catonly) break; /* * Grab manpname's first line, stashing it in manbuf. */ if ((md = fopen(manpname, "r")) == NULL) { if (*soed && errno == ENOENT) { (void) fprintf(stderr, gettext("Can't find referent of " ".so in %s\n"), soed); (void) fflush(stderr); return (-1); } perror(manpname); return (-1); } if (fgets(manbuf, BUFSIZ-1, md) == NULL) { (void) fclose(md); (void) fprintf(stderr, gettext("%s: null file\n"), manpname); (void) fflush(stderr); return (-1); } (void) fclose(md); if (strncmp(manbuf, DOT_SO, sizeof (DOT_SO) - 1)) break; so_again: if (++socount > SOLIMIT) { (void) fprintf(stderr, gettext(".so chain too long\n")); (void) fflush(stderr); return (-1); } s = manbuf + sizeof (DOT_SO) - 1; if ((check_flag == 1) && ((new_s = strrchr(s, '/')) != NULL)) { new_s++; (void) sprintf(s, "%s%s/%s", tmpsubdir, dir+plen, new_s); } cp = strrchr(s, '\n'); if (cp) *cp = '\0'; /* * Compensate for sloppy typists by stripping * trailing white space. */ cp = s + strlen(s); while (--cp >= s && (*cp == ' ' || *cp == '\t')) *cp = '\0'; /* * Go off and find the next link in the chain. */ (void) strcpy(soed, manpname); (void) strcpy(soref, s); (void) sprintf(manpname, "%s/%s", path, s); /* * TRANSLATION_NOTE - message for man -d or catman -p * ex. .so ref = man3c/string.3c */ DPRINTF(gettext(".so ref = %s\n"), s); } /* * Make symlinks if so'ed and cattin' */ if (socount && catmando) { (void) sprintf(cmdbuf, "cd %s; rm -f %s; ln -s ../%s%s %s", path, catpname, subdirs[1], soref+plen, catpname); (void) sys(cmdbuf); return (1); } /* * Obtain the cat page that corresponds to the man page. * If it already exists, is up to date, and if we haven't * been told not to use it, use it as it stands. */ regencat = updatedcat = 0; if (compargs || (!catonly && stat(manpname, &mansb) >= 0 && (stat(catpname, &catsb) < 0 || catsb.st_mtime < mansb.st_mtime)) || (access(catpname, R_OK) != 0)) { /* * Construct a shell command line for formatting manpname. * The resulting file goes initially into /tmp. If possible, * it will later be moved to catpname. */ int pipestage = 0; int needcol = 0; char *cbp = cmdbuf; regencat = updatedcat = 1; if (!catmando && !debug && !check_flag) { (void) fprintf(stderr, gettext( "Reformatting page. Please Wait...")); if (sargs && (newsection != NULL) && (*newsection != '\0')) { (void) fprintf(stderr, gettext( "\nThe directory name has been changed " "to %s\n"), newsection); } (void) fflush(stderr); } /* * in catman command, if the file exists in sman dir already, * don't need to convert the file in man dir to cat dir */ if (!no_sroff && catmando && match(tmpsubdir, MANDIRNAME, PLEN) && stat(smantmpname, &smansb) >= 0) return (1); /* * cd to path so that relative .so commands will work * correctly */ (void) sprintf(cbp, "cd %s; ", path); cbp += strlen(cbp); /* * check to see whether it is a sgml file * assume sgml symbol(>!DOCTYPE) can be found in the first * BUFSIZ bytes */ if ((temp = open(manpname, 0)) == -1) { perror(manpname); return (-1); } if ((count = read(temp, prntbuf, BUFSIZ)) <= 0) { perror(manpname); return (-1); } prntbuf[count] = '\0'; /* null terminate */ ptr = prntbuf; if (sgmlcheck((const char *)ptr) == 1) { sgml_flag = 1; if (defaultmandir && *localedir) { (void) sprintf(cbp, "LC_MESSAGES=C %s %s ", SROFF_CMD, manpname); } else { (void) sprintf(cbp, "%s %s ", SROFF_CMD, manpname); } cbp += strlen(cbp); } else if (*dir == 's') { (void) close(temp); return (-1); } (void) close(temp); /* * Check for special formatting requirements by examining * manpname's first line preprocessor specifications. */ if (strncmp(manbuf, PREPROC_SPEC, sizeof (PREPROC_SPEC) - 1) == 0) { char *ptp; ptp = manbuf + sizeof (PREPROC_SPEC) - 1; while (*ptp && *ptp != '\n') { const struct preprocessor *pp; /* * Check for a preprocessor we know about. */ for (pp = preprocessors; pp->p_tag; pp++) { if (pp->p_tag == *ptp) break; } if (pp->p_tag == 0) { (void) fprintf(stderr, gettext("unknown preprocessor " "specifier %c\n"), *ptp); (void) fflush(stderr); return (-1); } /* * Add it to the pipeline. */ (void) sprintf(cbp, "%s %s |", troffit ? pp->p_troff : pp->p_nroff, pipestage++ == 0 ? manpname : "-"); cbp += strlen(cbp); /* * Special treatment: if tbl is among the * preprocessors and we'll process with * nroff, we have to pass things through * col at the end of the pipeline. */ if (pp->p_tag == 't' && !troffit) needcol++; ptp++; } } /* * if catman, use the cat page name * otherwise, dup template and create another * (needed for multiple pages) */ if (catmando) tmpname = catpname; else { tmpname = strdup(TEMPLATE); if (tmpname == NULL) malloc_error(); (void) close(mkstemp(tmpname)); } if (! Tflag) { if (*localedir != '\0') { (void) sprintf(macros, "%s/%s", path, MACROF); /* * TRANSLATION_NOTE - message for man -d or catman -p * ex. locale macros = /usr/share/man/ja/tmac.an */ if (debug) (void) printf(gettext( "\nlocale macros = %s "), macros); if (stat(macros, &statb) < 0) (void) strcpy(macros, TMAC_AN); /* * TRANSLATION_NOTE - message for man -d or catman -p * ex. macros = /usr/share/man/ja/tman.an */ if (debug) (void) printf(gettext( "\nmacros = %s\n"), macros); } } tmpdir[0] = '\0'; if (sgml_flag == 1) { if (check_flag == 0) { strcpy(tmpdir, "/tmp/sman_XXXXXX"); if ((tempfd = mkstemp(tmpdir)) == -1) { (void) fprintf(stderr, gettext( "%s: null file\n"), tmpdir); (void) fflush(stderr); return (-1); } if (debug) close(tempfd); (void) sprintf(tmpbuf, "%s > %s", cmdbuf, tmpdir); if (sys(tmpbuf)) { /* * TRANSLATION_NOTE - message for man -d or catman -p * Error message if sys(%s) failed */ (void) fprintf(stderr, gettext( "sys(%s) fail!\n"), tmpbuf); (void) fprintf(stderr, gettext(" aborted (sorry)\n")); (void) fflush(stderr); /* release memory for tmpname */ if (!catmando) { (void) unlink(tmpdir); (void) unlink(tmpname); free(tmpname); } return (-1); } else if (debug == 0) { if ((md = fdopen(tempfd, "r")) == NULL) { (void) fprintf(stderr, gettext( "%s: null file\n"), tmpdir); (void) fflush(stderr); close(tempfd); /* release memory for tmpname */ if (!catmando) free(tmpname); return (-1); } /* if the file is empty, */ /* it's a fragment, do nothing */ if (fgets(manbuf, BUFSIZ-1, md) == NULL) { (void) fclose(md); /* release memory for tmpname */ if (!catmando) free(tmpname); return (1); } (void) fclose(md); if (strncmp(manbuf, DOT_SO, sizeof (DOT_SO) - 1) == 0) { if (!compargs) { check_flag = 1; (void) unlink(tmpdir); (void) unlink(tmpname); /* release memory for tmpname */ if (!catmando) free(tmpname); goto so_again; } else { (void) unlink(tmpdir); strcpy(tmpdir, "/tmp/sman_XXXXXX"); tempfd = mkstemp(tmpdir); if ((tempfd == -1) || (md = fdopen(tempfd, "w")) == NULL) { (void) fprintf(stderr, gettext( "%s: null file\n"), tmpdir); (void) fflush(stderr); if (tempfd != -1) close(tempfd); /* release memory for tmpname */ if (!catmando) free(tmpname); return (-1); } if ((new_m = strrchr(manbuf, '/')) != NULL) { (void) fprintf(md, ".so man%s%s\n", dir+plen, new_m); } else { /* * TRANSLATION_NOTE - message for catman -c * Error message if unable to get file name */ (void) fprintf(stderr, gettext("file not found\n")); (void) fflush(stderr); return (-1); } (void) fclose(md); } } } if (catmando && compargs) (void) sprintf(cmdbuf, "cat %s > %s", tmpdir, manpname_sgml); else (void) sprintf(cmdbuf, " cat %s | tbl | eqn | %s %s - %s > %s", tmpdir, troffit ? troffcmd : "nroff -u0 -Tlp", macros, troffit ? "" : " | col -x", tmpname); } else if (catmando && compargs) (void) sprintf(cbp, " > %s", manpname_sgml); else (void) sprintf(cbp, " | tbl | eqn | %s %s - %s > %s", troffit ? troffcmd : "nroff -u0 -Tlp", macros, troffit ? "" : " | col -x", tmpname); } else (void) sprintf(cbp, "%s %s %s%s > %s", troffit ? troffcmd : "nroff -u0 -Tlp", macros, pipestage == 0 ? manpname : "-", troffit ? "" : " | col -x", tmpname); /* Reformat the page. */ if (sys(cmdbuf)) { /* * TRANSLATION_NOTE - message for man -d or catman -p * Error message if sys(%s) failed */ (void) fprintf(stderr, gettext( "sys(%s) fail!\n"), cmdbuf); (void) fprintf(stderr, gettext(" aborted (sorry)\n")); (void) fflush(stderr); (void) unlink(tmpname); /* release memory for tmpname */ if (!catmando) free(tmpname); return (-1); } if (tmpdir[0] != '\0') (void) unlink(tmpdir); if (catmando) return (1); /* * Attempt to move the cat page to its proper home. */ (void) sprintf(cmdbuf, "trap '' 1 15; /usr/bin/mv -f %s %s 2> /dev/null", tmpname, catpname); if (sys(cmdbuf)) updatedcat = 0; else if (debug == 0) (void) chmod(catpname, 0644); if (debug) { /* release memory for tmpname */ if (!catmando) free(tmpname); (void) unlink(tmpname); return (1); } (void) fprintf(stderr, gettext(" done\n")); (void) fflush(stderr); } /* * Save file name (dup if necessary) * to view later * fix for 1123802 - don't save names if we are invoked as catman */ if (!catmando) { char **tmpp; int dup; char *newpage; if (regencat && !updatedcat) newpage = tmpname; else { newpage = strdup(catpname); if (newpage == NULL) malloc_error(); } /* make sure we don't add a dup */ dup = 0; for (tmpp = pages; tmpp < endp; tmpp++) { if (strcmp(*tmpp, newpage) == 0) { dup = 1; break; } } if (!dup) *endp++ = newpage; if (endp >= &pages[MAXPAGES]) { fprintf(stderr, gettext("Internal pages array overflow!\n")); exit(1); } } return (regencat); } /* * Add <localedir> to the path. */ static char * addlocale(char *path) { char *tmp; tmp = malloc(strlen(path) + strlen(localedir) + 2); if (tmp == NULL) malloc_error(); (void) sprintf(tmp, "%s/%s", path, localedir); return (tmp); } /* * From the configuration file "man.cf", get the order of suffices of * sub-mandirs to be used in the search path for a given mandir. */ static char * check_config(char *path) { FILE *fp; static char submandir[BUFSIZ]; char *sect; char fname[MAXPATHLEN]; (void) sprintf(fname, "%s/%s", path, CONFIG); if ((fp = fopen(fname, "r")) == NULL) return (NULL); else { if (get_manconfig(fp, submandir) == -1) { (void) fclose(fp); return (NULL); } (void) fclose(fp); sect = strchr(submandir, '='); if (sect != NULL) return (++sect); else return (NULL); } } /* * This routine is for getting the MANSECTS entry from man.cf. * It sets submandir to the line in man.cf that contains * MANSECTS=sections[,sections]... */ static int get_manconfig(FILE *fp, char *submandir) { char *s, *t, *rc; char buf[BUFSIZ]; while ((rc = fgets(buf, sizeof (buf), fp)) != NULL) { /* * skip leading blanks */ for (t = buf; *t != '\0'; t++) { if (!isspace(*t)) break; } /* * skip line that starts with '#' or empty line */ if (*t == '#' || *t == '\0') continue; if (strstr(buf, "MANSECTS") != NULL) break; } /* * the man.cf file doesn't have a MANSECTS entry */ if (rc == NULL) return (-1); s = strchr(buf, '\n'); *s = '\0'; /* replace '\n' with '\0' */ (void) strcpy(submandir, buf); return (0); } static void malloc_error(void) { (void) fprintf(stderr, gettext( "Memory allocation failed.\n")); exit(1); } static int sgmlcheck(const char *s1) { const char *s2 = SGML_SYMBOL; int len; while (*s1) { /* * Assume the first character of SGML_SYMBOL(*s2) is '<'. * Therefore, not necessary to do toupper(*s1) here. */ if (*s1 == *s2) { /* * *s1 is '<'. Check the following substring matches * with "!DOCTYPE". */ s1++; if (strncasecmp(s1, s2 + 1, SGML_SYMBOL_LEN - 1) == 0) { /* * SGML_SYMBOL found */ return (1); } continue; } else if (isascii(*s1)) { /* * *s1 is an ASCII char * Skip one character */ s1++; continue; } else { /* * *s1 is a non-ASCII char or * the first byte of the multibyte char. * Skip one character */ len = mblen(s1, MB_CUR_MAX); if (len == -1) len = 1; s1 += len; continue; } } /* * SGML_SYMBOL not found */ return (0); } /* * Initializes the bintoman array with appropriate device and inode info */ static void init_bintoman(void) { int i; struct stat sb; for (i = 0; bintoman[i].bindir != NULL; i++) { if (stat(bintoman[i].bindir, &sb) == 0) { bintoman[i].dev = sb.st_dev; bintoman[i].ino = sb.st_ino; } else { bintoman[i].dev = NODEV; } } } /* * If a duplicate is found, return 1 * If a duplicate is not found, add it to the dupnode list and return 0 */ static int dupcheck(struct man_node *mnp, struct dupnode **dnp) { struct dupnode *curdnp; struct secnode *cursnp; struct stat sb; int i; int rv = 1; int dupfound; /* * If the path doesn't exist, treat it as a duplicate */ if (stat(mnp->path, &sb) != 0) { return (1); } /* * If no sections were found in the man dir, treat it as duplicate */ if (mnp->secv == NULL) { return (1); } /* * Find the dupnode structure for the previous time this directory * was looked at. Device and inode numbers are compared so that * directories that are reached via different paths (e.g. /usr/man vs. * /usr/share/man) are treated as equivalent. */ for (curdnp = *dnp; curdnp != NULL; curdnp = curdnp->next) { if (curdnp->dev == sb.st_dev && curdnp->ino == sb.st_ino) { break; } } /* * First time this directory has been seen. Add a new node to the * head of the list. Since all entries are guaranteed to be unique * copy all sections to new node. */ if (curdnp == NULL) { if ((curdnp = calloc(1, sizeof (struct dupnode))) == NULL) { malloc_error(); } for (i = 0; mnp->secv[i] != NULL; i++) { if ((cursnp = calloc(1, sizeof (struct secnode))) == NULL) { malloc_error(); } cursnp->next = curdnp->secl; curdnp->secl = cursnp; if ((cursnp->secp = strdup(mnp->secv[i])) == NULL) { malloc_error(); } } curdnp->dev = sb.st_dev; curdnp->ino = sb.st_ino; curdnp->next = *dnp; *dnp = curdnp; return (0); } /* * Traverse the section vector in the man_node and the section list * in dupnode cache to eliminate all duplicates from man_node */ for (i = 0; mnp->secv[i] != NULL; i++) { dupfound = 0; for (cursnp = curdnp->secl; cursnp != NULL; cursnp = cursnp->next) { if (strcmp(mnp->secv[i], cursnp->secp) == 0) { dupfound = 1; break; } } if (dupfound) { mnp->secv[i][0] = '\0'; continue; } /* * Update curdnp and set return value to indicate that this * was not all duplicates. */ if ((cursnp = calloc(1, sizeof (struct secnode))) == NULL) { malloc_error(); } cursnp->next = curdnp->secl; curdnp->secl = cursnp; if ((cursnp->secp = strdup(mnp->secv[i])) == NULL) { malloc_error(); } rv = 0; } return (rv); } /* * Given a bin directory, return the corresponding man directory. * Return string must be free()d by the caller. * * NULL will be returned if no matching man directory can be found. */ static char * path_to_manpath(char *bindir) { char *mand, *p; int i; struct stat sb; /* * First look for known translations for specific bin paths */ if (stat(bindir, &sb) != 0) { return (NULL); } for (i = 0; bintoman[i].bindir != NULL; i++) { if (sb.st_dev == bintoman[i].dev && sb.st_ino == bintoman[i].ino) { if ((mand = strdup(bintoman[i].mandir)) == NULL) { malloc_error(); } if ((p = strchr(mand, ',')) != NULL) { *p = '\0'; } if (stat(mand, &sb) != 0) { free(mand); return (NULL); } if (p != NULL) { *p = ','; } return (mand); } } /* * No specific translation found. Try `dirname $bindir`/man * and `dirname $bindir`/share/man */ if ((mand = malloc(PATH_MAX)) == NULL) { malloc_error(); } if (strlcpy(mand, bindir, PATH_MAX) >= PATH_MAX) { free(mand); return (NULL); } /* * Advance to end of buffer, strip trailing /'s then remove last * directory component. */ for (p = mand; *p != '\0'; p++) ; for (; p > mand && *p == '/'; p--) ; for (; p > mand && *p != '/'; p--) ; if (p == mand && *p == '.') { if (realpath("..", mand) == NULL) { free(mand); return (NULL); } for (; *p != '\0'; p++) ; } else { *p = '\0'; } if (strlcat(mand, "/man", PATH_MAX) >= PATH_MAX) { free(mand); return (NULL); } if ((stat(mand, &sb) == 0) && S_ISDIR(sb.st_mode)) { return (mand); } /* * Strip the /man off and try /share/man */ *p = '\0'; if (strlcat(mand, "/share/man", PATH_MAX) >= PATH_MAX) { free(mand); return (NULL); } if ((stat(mand, &sb) == 0) && S_ISDIR(sb.st_mode)) { return (mand); } /* * No man or share/man directory found */ free(mand); return (NULL); } /* * Free a linked list of dupnode structs */ void free_dupnode(struct dupnode *dnp) { struct dupnode *dnp2; struct secnode *snp; while (dnp != NULL) { dnp2 = dnp; dnp = dnp->next; while (dnp2->secl != NULL) { snp = dnp2->secl; dnp2->secl = dnp2->secl->next; free(snp->secp); free(snp); } free(dnp2); } } /* * prints manp linked list to stdout. * * If namep is NULL, output can be used for setting MANPATH. * * If namep is not NULL output is two columns. First column is the string * pointed to by namep. Second column is a MANPATH-compatible representation * of manp linked list. */ void print_manpath(struct man_node *manp, char *namep) { char colon[2]; char **secp; if (namep != NULL) { (void) printf("%s ", namep); } colon[0] = '\0'; colon[1] = '\0'; for (; manp != NULL; manp = manp->next) { (void) printf("%s%s", colon, manp->path); colon[0] = ':'; /* * If man.cf or a directory scan was used to create section * list, do not print section list again. If the output of * man -p is used to set MANPATH, subsequent runs of man * will re-read man.cf and/or scan man directories as * required. */ if (manp->defsrch != 0) { continue; } for (secp = manp->secv; *secp != NULL; secp++) { /* * Section deduplication may have eliminated some * sections from the vector. Avoid displaying this * detail which would appear as ",," in output */ if ((*secp)[0] != '\0') { (void) printf(",%s", *secp); } } } (void) printf("\n"); }