/* * 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 (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2012, Josef 'Jeff' Sipek . All rights reserved. * Copyright 2014 Garrett D'Amore * Copyright 2016 Nexenta Systems, Inc. * Copyright 2019 Joyent, Inc. */ /* 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. */ /* * Find and display reference manual pages. This version includes makewhatis * functionality as well. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "man.h" /* Mapping of old directories to new directories */ static const struct map_entry { char *old_name; char *new_name; } map[] = { { "1m", "8" }, { "3b", "3ucb" }, { "3e", "3elf" }, { "3g", "3gen" }, { "3k", "3kstat" }, { "3n", "3socket" }, { "3r", "3rt" }, { "3s", "3c" }, { "3t", "3thr" }, { "3x", "3curses" }, { "3xc", "3xcurses" }, { "3xn", "3xnet" }, { "4", "5" }, { "5", "7" }, { "7", "4" }, { "7b", "4b" }, { "7d", "4d" }, { "7fs", "4fs" }, { "7i", "4i" }, { "7ipp", "4ipp" }, { "7m", "4m" }, { "7p", "4p" }, { NULL, NULL } }; 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_DEFMANDIR Add DEFMANDIR to the end if it hasn't * already appeared earlier. * BMP_FALLBACK_DEFMANDIR Append /usr/share/man only if no other * manpath (including derived from PATH) * elements are valid. */ #define BMP_ISPATH 1 #define BMP_APPEND_DEFMANDIR 2 #define BMP_FALLBACK_DEFMANDIR 4 /* * When doing equality comparisons of directories, device and inode * comparisons are done. The secnode 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,8,1m", 0, 0 }, { "/usr/sbin", "/usr/share/man,8,1m", 0, 0 }, { "/usr/ucb", "/usr/share/man,1b", 0, 0 }, { "/usr/bin", "/usr/share/man,1,8,1m,1s,1t,1c", 0, 0 }, { "/usr/xpg4/bin", "/usr/share/man,1", 0, 0 }, { "/usr/xpg6/bin", "/usr/share/man,1", 0, 0 }, { NULL, NULL, 0, 0 } }; struct man_node { char *path; /* mandir path */ char **secv; /* submandir suffixes */ int defsrch; /* hint for man -p */ int frompath; /* hint for man -d */ struct man_node *next; }; static int all = 0; static int apropos = 0; static int debug = 0; static int found = 0; static int list = 0; static int makewhatis = 0; static int printmp = 0; static int psoutput = 0; static int lintout = 0; static int whatis = 0; static int makewhatishere = 0; static char *mansec = NULL; static char *pager = NULL; static char *addlocale(char *); static struct man_node *build_manpath(char **, char *, int); static void do_makewhatis(struct man_node *); static char *check_config(char *); static int cmp(const void *, const void *); static int dupcheck(struct man_node *, struct dupnode **); static int format(char *, char *, char *, char *); static void free_dupnode(struct dupnode *); static void free_manp(struct man_node *manp); static void freev(char **); static void fullpaths(struct man_node **); static void get_all_sect(struct man_node *); static int getdirs(char *, char ***, int); static void getpath(struct man_node *, char **); static void getsect(struct man_node *, char **, char *); static void init_bintoman(void); static void lower(char *); static void mandir(char **, char *, char *, int); static int manual(struct man_node *, char *, char *); static char *map_section(char *, char *); static char *path_to_manpath(char *); static void print_manpath(struct man_node *); static void search_whatis(char *, char *); static int searchdir(char *, char *, char *); static void sortdir(DIR *, char ***); static char **split(char *, char); static void usage_man(void); static void usage_whatapro(void); static void usage_catman(void); static void usage_makewhatis(void); static void whatapro(struct man_node *, char *); static char language[MAXPATHLEN]; /* LC_MESSAGES */ static char localedir[MAXPATHLEN]; /* locale specific path component */ static char *newsection = NULL; static int manwidth = 0; extern const char *__progname; int main(int argc, char **argv) { int c, i; char **pathv; char *manpath = NULL; static struct man_node *mandirs = NULL; int bmp_flags = 0; int ret = 0; char *opts; char *mwstr; int catman = 0; (void) setlocale(LC_ALL, ""); (void) strcpy(language, setlocale(LC_MESSAGES, (char *)NULL)); if (strcmp("C", language) != 0) (void) strlcpy(localedir, language, MAXPATHLEN); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); if (strcmp(__progname, "apropos") == 0) { apropos++; opts = "M:ds:"; } else if (strcmp(__progname, "whatis") == 0) { apropos++; whatis++; opts = "M:ds:"; } else if (strcmp(__progname, "catman") == 0) { catman++; makewhatis++; opts = "P:M:w"; } else if (strcmp(__progname, "makewhatis") == 0) { makewhatis++; makewhatishere++; manpath = "."; opts = ""; } else { opts = "FM:P:T:adfklprs:tw"; if (argc > 1 && strcmp(argv[1], "-") == 0) { pager = "cat"; optind++; } } opterr = 0; while ((c = getopt(argc, argv, opts)) != -1) { switch (c) { case 'M': /* Respecify path for man pages */ manpath = optarg; break; case 'a': all++; break; case 'd': debug++; break; case 'f': whatis++; /*FALLTHROUGH*/ case 'k': apropos++; break; case 'l': list++; all++; break; case 'p': printmp++; break; case 's': mansec = optarg; break; case 'r': lintout++; break; case 't': psoutput++; break; case 'T': case 'P': case 'F': /* legacy options, compatibility only and ignored */ break; case 'w': makewhatis++; break; case '?': default: if (apropos) usage_whatapro(); else if (catman) usage_catman(); else if (makewhatishere) usage_makewhatis(); else usage_man(); } } argc -= optind; argv += optind; if (argc == 0) { if (apropos) { (void) fprintf(stderr, gettext("%s what?\n"), __progname); exit(1); } else if (!printmp && !makewhatis) { (void) fprintf(stderr, gettext("What manual page do you want?\n")); exit(1); } } init_bintoman(); if (manpath == NULL && (manpath = getenv("MANPATH")) == NULL) { if ((manpath = getenv("PATH")) != NULL) bmp_flags = BMP_ISPATH | BMP_APPEND_DEFMANDIR; else manpath = DEFMANDIR; } pathv = split(manpath, ':'); mandirs = build_manpath(pathv, mansec, bmp_flags); fullpaths(&mandirs); if (makewhatis) { do_makewhatis(mandirs); exit(0); } if (printmp) { print_manpath(mandirs); exit(0); } /* Collect environment information */ if (isatty(STDOUT_FILENO) && (mwstr = getenv("MANWIDTH")) != NULL && *mwstr != '\0') { if (strcasecmp(mwstr, "tty") == 0) { struct winsize ws; if (ioctl(0, TIOCGWINSZ, &ws) != 0) warn("TIOCGWINSZ"); else manwidth = ws.ws_col; } else { manwidth = (int)strtol(mwstr, (char **)NULL, 10); if (manwidth < 0) manwidth = 0; } } if (manwidth != 0) { DPRINTF("-- Using non-standard page width: %d\n", manwidth); } if (pager == NULL) { if ((pager = getenv("PAGER")) == NULL || *pager == '\0') pager = PAGER; } DPRINTF("-- Using pager: %s\n", pager); for (i = 0; i < argc; i++) { char *cmd; static struct man_node *mp; char *pv[2] = {NULL, NULL}; /* * If full path to command specified, customize * the manpath accordingly. */ if ((cmd = strrchr(argv[i], '/')) != NULL) { *cmd = '\0'; if ((pv[0] = strdup(argv[i])) == NULL) err(1, "strdup"); pv[1] = NULL; *cmd = '/'; mp = build_manpath(pv, mansec, BMP_ISPATH | BMP_FALLBACK_DEFMANDIR); } else { mp = mandirs; } if (apropos) { whatapro(mp, argv[i]); } else { /* * If a page is specified with an embedded section, * such as 'printf.3c' First try to find it literally * (which has historically worked due to the * implementation of mandir() and has come to be * relied upon), if that doesn't work split it at the * right most '.' to separate a hypothetical name and * section, and explicitly search under the specified * section, which will trigger the section name * compatibility logic. * * The error that the page they initially requested * does not exist will still be produced at this * point, and indicate (unless clobbered by the pager) * what has been done. */ int lret = 0; lret = manual(mp, argv[i], NULL); if (lret != 0) { char *sec = NULL; if ((sec = strrchr(argv[i], '.')) != NULL) { char *page = NULL; *sec++ = '\0'; if ((page = strdup(argv[i])) == NULL) err(1, "strdup"); mp = build_manpath(pathv, sec, 0); lret = manual(mp, page, sec); free(page); } } ret += lret; } if (mp != NULL && mp != mandirs) { free(pv[0]); free_manp(mp); } } freev(pathv); return (ret == 0 ? 0 : 1); } /* * This routine builds the manpage structure from MANPATH or PATH, * depending on flags. See BMP_* definitions above for valid * flags. */ static struct man_node * build_manpath(char **pathv, char *sec, 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 = DEFMANDIR; int s; struct dupnode *didup = NULL; struct stat sb; s = sizeof (struct man_node); for (p = pathv; *p != NULL; ) { if (flags & BMP_ISPATH) { if ((mand = path_to_manpath(*p)) == NULL) goto next; free(*p); *p = mand; } q = split(*p, ','); if (stat(q[0], &sb) != 0 || (sb.st_mode & S_IFDIR) == 0) { freev(q); goto next; } if (access(q[0], R_OK | X_OK) == 0) { /* * Some element exists. Do not append DEFMANDIR as a * fallback. */ flags &= ~BMP_FALLBACK_DEFMANDIR; if ((currp = (struct man_node *)calloc(1, s)) == NULL) err(1, "calloc"); currp->frompath = (flags & BMP_ISPATH); if (manpage == NULL) lastp = manpage = currp; getpath(currp, p); getsect(currp, p, sec); /* * 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 DEFMANDIR. After all pathv * elements have been processed, append DEFMANDIR if needed. */ if (p == &mandir) break; p++; if (*p != NULL) continue; if (flags & (BMP_APPEND_DEFMANDIR | BMP_FALLBACK_DEFMANDIR)) { p = &mandir; flags &= ~BMP_ISPATH; } } free_dupnode(didup); return (manpage); } /* * Store the mandir path into the manp structure. */ static void getpath(struct man_node *manp, char **pv) { char *s = *pv; int i = 0; while (*s != '\0' && *s != ',') i++, s++; if ((manp->path = (char *)malloc(i + 1)) == NULL) err(1, "malloc"); (void) strlcpy(manp->path, *pv, i + 1); } /* * Store the mandir's corresponding sections (submandir * directories) into the manp structure. */ static void getsect(struct man_node *manp, char **pv, char *explicit_sec) { char *sections; char **sectp; /* Just store all sections when doing makewhatis or apropos/whatis */ if (makewhatis || apropos) { manp->defsrch = 1; DPRINTF("-- Adding %s\n", manp->path); manp->secv = NULL; get_all_sect(manp); } else if (explicit_sec != NULL) { DPRINTF("-- Adding %s: sections=%s\n", manp->path, explicit_sec); manp->secv = split(explicit_sec, ','); for (sectp = manp->secv; *sectp; sectp++) lower(*sectp); } else if ((sections = strchr(*pv, ',')) != NULL) { sections++; DPRINTF("-- Adding %s: sections=%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; DPRINTF("-- Adding %s: sections=%s (from %s)\n", manp->path, sections, CONFIG); manp->secv = split(sections, ','); for (sectp = manp->secv; *sectp; sectp++) lower(*sectp); if (*manp->secv == NULL) get_all_sect(manp); } else { manp->defsrch = 1; DPRINTF("-- Adding %s: default search order\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 = NULL; char *tmp = NULL; int maxentries = MAXTOKENS; int entries = 0; if ((dp = opendir(manp->path)) == 0) return; sortdir(dp, &dirv); (void) closedir(dp); if (manp->secv == NULL) { if ((manp->secv = malloc(maxentries * sizeof (char *))) == NULL) err(1, "malloc"); } for (dv = dirv, p = manp->secv; *dv; dv++) { if (strcmp(*dv, CONFIG) == 0) { free(*dv); continue; } free(tmp); if ((tmp = strdup(*dv + 3)) == NULL) err(1, "strdup"); if (prev != NULL && strcmp(prev, tmp) == 0) { free(*dv); continue; } free(prev); if ((prev = strdup(*dv + 3)) == NULL) err(1, "strdup"); if ((*p = strdup(*dv + 3)) == NULL) err(1, "strdup"); p++; entries++; if (entries == maxentries) { maxentries += MAXTOKENS; if ((manp->secv = realloc(manp->secv, sizeof (char *) * maxentries)) == NULL) err(1, "realloc"); p = manp->secv + entries; } free(*dv); } free(tmp); free(prev); *p = NULL; free(dirv); } /* * Build whatis databases. */ static void do_makewhatis(struct man_node *manp) { struct man_node *p; char *ldir; for (p = manp; p != NULL; p = p->next) { ldir = addlocale(p->path); if (*localedir != '\0' && getdirs(ldir, NULL, 0) > 0) mwpath(ldir); free(ldir); mwpath(p->path); } } /* * Count mandirs under the given manpath */ static int getdirs(char *path, char ***dirv, int flag) { DIR *dp; struct dirent *d; int n = 0; int maxentries = MAXDIRS; char **dv = NULL; if ((dp = opendir(path)) == NULL) return (0); if (flag) { if ((*dirv = malloc(sizeof (char *) * maxentries)) == NULL) err(1, "malloc"); dv = *dirv; } while ((d = readdir(dp))) { if (strncmp(d->d_name, "man", 3) != 0) continue; n++; if (flag) { if ((*dv = strdup(d->d_name + 3)) == NULL) err(1, "strdup"); dv++; if ((dv - *dirv) == maxentries) { int entries = maxentries; maxentries += MAXTOKENS; if ((*dirv = realloc(*dirv, sizeof (char *) * maxentries)) == NULL) err(1, "realloc"); dv = *dirv + entries; } } } (void) closedir(dp); return (n); } /* * Find matching whatis or apropos entries. */ static void whatapro(struct man_node *manp, char *word) { char whatpath[MAXPATHLEN]; struct man_node *b; char *ldir; for (b = manp; b != NULL; b = b->next) { if (*localedir != '\0') { ldir = addlocale(b->path); if (getdirs(ldir, NULL, 0) != 0) { (void) snprintf(whatpath, sizeof (whatpath), "%s/%s", ldir, WHATIS); search_whatis(whatpath, word); } free(ldir); } (void) snprintf(whatpath, sizeof (whatpath), "%s/%s", b->path, WHATIS); search_whatis(whatpath, word); } } static void search_whatis(char *whatpath, char *word) { FILE *fp; char *line = NULL; size_t linecap = 0; char *pkwd; regex_t preg; char **ss = NULL; char s[MAXNAMELEN]; int i; if ((fp = fopen(whatpath, "r")) == NULL) { perror(whatpath); return; } DPRINTF("-- Found %s: %s\n", WHATIS, whatpath); /* Build keyword regex */ if (asprintf(&pkwd, "%s%s%s", (whatis) ? "\\<" : "", word, (whatis) ? "\\>" : "") == -1) err(1, "asprintf"); if (regcomp(&preg, pkwd, REG_BASIC | REG_ICASE | REG_NOSUB) != 0) err(1, "regcomp"); if (mansec != NULL) ss = split(mansec, ','); while (getline(&line, &linecap, fp) > 0) { if (regexec(&preg, line, 0, NULL, 0) == 0) { if (mansec != NULL) { /* Section-restricted search */ for (i = 0; ss[i] != NULL; i++) { (void) snprintf(s, sizeof (s), "(%s)", ss[i]); if (strstr(line, s) != NULL) { (void) printf("%s", line); break; } } } else { (void) printf("%s", line); } } } if (ss != NULL) freev(ss); free(pkwd); (void) fclose(fp); } /* * Split a string by specified separator. */ static char ** split(char *s1, char sep) { char **tokv, **vp; char *mp = s1, *tp; int maxentries = MAXTOKENS; int entries = 0; if ((tokv = vp = malloc(maxentries * sizeof (char *))) == NULL) err(1, "malloc"); for (; mp && *mp; mp = tp) { tp = strchr(mp, sep); if (mp == tp) { tp++; continue; } if (tp) { size_t len; len = tp - mp; if ((*vp = (char *)malloc(sizeof (char) * len + 1)) == NULL) err(1, "malloc"); (void) strncpy(*vp, mp, len); *(*vp + len) = '\0'; tp++; vp++; } else { if ((*vp = strdup(mp)) == NULL) err(1, "strdup"); vp++; } entries++; if (entries == maxentries) { maxentries += MAXTOKENS; if ((tokv = realloc(tokv, maxentries * sizeof (char *))) == NULL) err(1, "realloc"); vp = tokv + entries; } } *vp = 0; return (tokv); } /* * Free a vector allocated by split() */ static void freev(char **v) { int i; if (v != NULL) { 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; int 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; } if (!cwd_gotten) { cwd = getcwd(NULL, MAXPATHLEN); cwd_gotten = 1; } if (cwd) { /* Relative manpath with cwd: make absolute */ if (asprintf(&p, "%s/%s", cwd, b->path) == -1) err(1, "asprintf"); free(b->path); b->path = p; } else { /* Relative manpath but no cwd: omit path entry */ if (prev) prev->next = b->next; else *manp_head = b->next; free_manp(b); } } 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 function for qsort(). * Sort first by section, then by prefix. */ static int cmp(const void *arg1, const void *arg2) { int n; char **p1 = (char **)arg1; char **p2 = (char **)arg2; /* By section */ if ((n = strcmp(*p1 + 3, *p2 + 3)) != 0) return (n); /* By prefix reversed */ return (strncmp(*p2, *p1, 3)); } /* * Find a manpage. */ static int manual(struct man_node *manp, char *name, char *sec) { 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) { DPRINTF("-- Searching mandir: %s\n", p->path); if (*localedir != '\0') { ldir = addlocale(p->path); ndirs = getdirs(ldir, NULL, 0); if (ndirs != 0) { ldirs[0] = ldir; ldirs[1] = NULL; local = build_manpath(ldirs, mansec, 0); DPRINTF("-- Locale specific subdir: %s\n", ldir); mandir(local->secv, ldir, name, 1); free_manp(local); } free(ldir); } /* * Locale mandir not valid, man page in locale * mandir not found, or -a option present */ if (ndirs == 0 || !found || all) mandir(p->secv, p->path, name, 0); if (found && !all) break; } if (!found) { if (sec != NULL) { (void) fprintf(stderr, gettext( "No manual entry for %s in section(s) %s\n"), fullname, sec); } else { (void) fprintf(stderr, gettext("No manual entry for %s\n"), fullname); } } return (!found); } /* * For a specified manual directory, read, store and sort section subdirs. * For each section specified, find and search matching subdirs. */ static void mandir(char **secv, char *path, char *name, int lspec) { DIR *dp; char **dirv; char **dv, **pdv; int len, dslen; if ((dp = opendir(path)) == NULL) return; if (lspec) DPRINTF("-- Searching mandir: %s\n", path); sortdir(dp, &dirv); /* Search in the order specified by MANSECTS */ for (; *secv; secv++) { len = strlen(*secv); for (dv = dirv; *dv; dv++) { dslen = strlen(*dv + 3); if (dslen > len) len = dslen; if (**secv == '\\') { if (strcmp(*secv + 1, *dv + 3) != 0) continue; } else if (strncasecmp(*secv, *dv + 3, len) != 0) { if (!all && (newsection = map_section(*secv, path)) == NULL) { continue; } if (newsection == NULL) newsection = ""; if (strncmp(newsection, *dv + 3, len) != 0) { continue; } } if (searchdir(path, *dv, name) == 0) continue; if (!all) { pdv = dirv; while (*pdv) { free(*pdv); pdv++; } (void) closedir(dp); free(dirv); return; } if (all && **dv == 'm' && *(dv + 1) && strcmp(*(dv + 1) + 3, *dv + 3) == 0) dv++; } } pdv = dirv; while (*pdv != NULL) { 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; if ((dv = *dirv = malloc(sizeof (char *) * maxentries)) == NULL) err(1, "malloc"); dv = *dirv; while ((d = readdir(dp))) { if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) continue; if (strncmp(d->d_name, "man", 3) == 0 || strncmp(d->d_name, "cat", 3) == 0) { if ((*dv = strdup(d->d_name)) == NULL) err(1, "strdup"); dv++; entries++; if (entries == maxentries) { maxentries += MAXDIRS; if ((*dirv = realloc(*dirv, sizeof (char *) * maxentries)) == NULL) err(1, "realloc"); dv = *dirv + entries; } } } *dv = 0; qsort((void *)*dirv, dv - *dirv, sizeof (char *), cmp); } /* * Search a section subdir for a given manpage. */ static int searchdir(char *path, char *dir, char *name) { DIR *sdp; struct dirent *sd; char sectpath[MAXPATHLEN]; char file[MAXNAMLEN]; char dname[MAXPATHLEN]; char *last; int nlen; (void) snprintf(sectpath, sizeof (sectpath), "%s/%s", path, dir); (void) snprintf(file, sizeof (file), "%s.", name); if ((sdp = opendir(sectpath)) == NULL) return (0); while ((sd = readdir(sdp))) { char *pname, *upper = NULL; if ((pname = strdup(sd->d_name)) == NULL) err(1, "strdup"); if ((last = strrchr(pname, '.')) != NULL && (strcmp(last, ".gz") == 0 || strcmp(last, ".bz2") == 0)) *last = '\0'; last = strrchr(pname, '.'); nlen = last - pname; (void) snprintf(dname, sizeof (dname), "%.*s.", nlen, pname); /* * Check for a case where name has something like foo.3C because * the user reasonably thought that the section name was * capitalized in the file. This relies on the fact that all of * our section names are currently 7-bit ASCII. */ if (last != NULL) { char *c; if ((upper = strdup(pname)) == NULL) { err(1, "strdup"); } c = strrchr(upper, '.'); c++; while (*c != '\0') { *c = toupper(*c); c++; } } if (strcmp(dname, file) == 0 || strcmp(pname, name) == 0 || (upper != NULL && strcmp(upper, name) == 0)) { (void) format(path, dir, name, sd->d_name); (void) closedir(sdp); free(pname); free(upper); return (1); } free(pname); free(upper); } (void) closedir(sdp); return (0); } /* * Check the hash table of old directory names to see if there is a * new directory name. */ static char * map_section(char *section, char *path) { int i; char fullpath[MAXPATHLEN]; if (list) /* -l option fall through */ return (NULL); for (i = 0; map[i].new_name != NULL; i++) { if (strcmp(section, map[i].old_name) == 0) { (void) snprintf(fullpath, sizeof (fullpath), "%s/man%s", path, map[i].new_name); if (!access(fullpath, R_OK | X_OK)) { return (map[i].new_name); } else { return (NULL); } } } return (NULL); } /* * Format the manpage. */ static int format(char *path, char *dir, char *name, char *pg) { char manpname[MAXPATHLEN], catpname[MAXPATHLEN]; char cmdbuf[BUFSIZ], tmpbuf[BUFSIZ]; char *cattool; struct stat sbman, sbcat; found++; if (list) { (void) printf(gettext("%s(%s)\t-M %s\n"), name, dir + 3, path); return (-1); } (void) snprintf(manpname, sizeof (manpname), "%s/man%s/%s", path, dir + 3, pg); (void) snprintf(catpname, sizeof (catpname), "%s/cat%s/%s", path, dir + 3, pg); /* Can't do PS output if manpage doesn't exist */ if (stat(manpname, &sbman) != 0 && (psoutput|lintout)) return (-1); /* * If both manpage and catpage do not exist, manpname is * broken symlink, most likely. */ if (stat(catpname, &sbcat) != 0 && stat(manpname, &sbman) != 0) err(1, "%s", manpname); /* Setup cattool */ if (fnmatch("*.gz", manpname, 0) == 0) cattool = "gzcat"; else if (fnmatch("*.bz2", manpname, 0) == 0) cattool = "bzcat"; else cattool = "cat"; if (psoutput) { (void) snprintf(cmdbuf, BUFSIZ, "cd %s; %s %s | mandoc -Tps | lp -Tpostscript", path, cattool, manpname); DPRINTF("-- Using manpage: %s\n", manpname); goto cmd; } else if (lintout) { (void) snprintf(cmdbuf, BUFSIZ, "cd %s; %s %s | mandoc -Tlint", path, cattool, manpname); DPRINTF("-- Linting manpage: %s\n", manpname); goto cmd; } /* * Output catpage if: * - manpage doesn't exist * - output width is standard and catpage is recent enough */ if (stat(manpname, &sbman) != 0 || (manwidth == 0 && stat(catpname, &sbcat) == 0 && sbcat.st_mtime >= sbman.st_mtime)) { DPRINTF("-- Using catpage: %s\n", catpname); (void) snprintf(cmdbuf, BUFSIZ, "%s %s", pager, catpname); goto cmd; } DPRINTF("-- Using manpage: %s\n", manpname); if (manwidth > 0) (void) snprintf(tmpbuf, BUFSIZ, "-Owidth=%d ", manwidth); (void) snprintf(cmdbuf, BUFSIZ, "cd %s; %s %s | mandoc %s| %s", path, cattool, manpname, (manwidth > 0) ? tmpbuf : "", pager); cmd: DPRINTF("-- Command: %s\n", cmdbuf); if (!debug) return (system(cmdbuf) == 0); else return (0); } /* * Add to the path. */ static char * addlocale(char *path) { char *tmp; if (asprintf(&tmp, "%s/%s", path, localedir) == -1) err(1, "asprintf"); return (tmp); } /* * Get the order of sections from man.cf. */ static char * check_config(char *path) { FILE *fp; char *rc = NULL; char *sect = NULL; char fname[MAXPATHLEN]; char *line = NULL; char *nl; size_t linecap = 0; (void) snprintf(fname, MAXPATHLEN, "%s/%s", path, CONFIG); if ((fp = fopen(fname, "r")) == NULL) return (NULL); while (getline(&line, &linecap, fp) > 0) { if ((rc = strstr(line, "MANSECTS=")) != NULL) break; } (void) fclose(fp); if (rc != NULL) { if ((nl = strchr(rc, '\n')) != NULL) *nl = '\0'; sect = strchr(rc, '=') + 1; } return (sect); } /* * Initialize 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 and * /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) err(1, "calloc"); for (i = 0; mnp->secv[i] != NULL; i++) { if ((cursnp = calloc(1, sizeof (struct secnode))) == NULL) err(1, "calloc"); cursnp->next = curdnp->secl; curdnp->secl = cursnp; if ((cursnp->secp = strdup(mnp->secv[i])) == NULL) err(1, "strdup"); } 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) err(1, "calloc"); cursnp->next = curdnp->secl; curdnp->secl = cursnp; if ((cursnp->secp = strdup(mnp->secv[i])) == NULL) err(1, "strdup"); rv = 0; } return (rv); } /* * Given a bindir, return corresponding mandir. */ 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) err(1, "strdup"); 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`/share/man * and `dirname $bindir`/man */ if ((mand = malloc(MAXPATHLEN)) == NULL) err(1, "malloc"); if (strlcpy(mand, bindir, MAXPATHLEN) >= MAXPATHLEN) { 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, "/share/man", MAXPATHLEN) >= MAXPATHLEN) { free(mand); return (NULL); } if ((stat(mand, &sb) == 0) && S_ISDIR(sb.st_mode)) { return (mand); } /* * Strip the /share/man off and try /man */ *p = '\0'; if (strlcat(mand, "/man", MAXPATHLEN) >= MAXPATHLEN) { 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); } } /* * Print manp linked list to stdout. */ void print_manpath(struct man_node *manp) { char colon[2] = "\0\0"; char **secp; 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"); } static void usage_man(void) { (void) fprintf(stderr, gettext( "usage: man [-alptw] [-M path] [-s section] name ...\n" " man [-M path] [-s section] -k keyword ...\n" " man [-M path] [-s section] -f keyword ...\n")); exit(1); } static void usage_whatapro(void) { (void) fprintf(stderr, gettext( "usage: %s [-M path] [-s section] keyword ...\n"), whatis ? "whatis" : "apropos"); exit(1); } static void usage_catman(void) { (void) fprintf(stderr, gettext( "usage: catman [-M path] [-w]\n")); exit(1); } static void usage_makewhatis(void) { (void) fprintf(stderr, gettext("usage: makewhatis\n")); exit(1); }