diff options
author | Garrett D'Amore <garrett@damore.org> | 2014-07-14 20:10:37 -0700 |
---|---|---|
committer | Garrett D'Amore <garrett@damore.org> | 2014-07-21 09:20:34 -0700 |
commit | 95c635efb7c3b86efc493e0447eaec7aecca3f0f (patch) | |
tree | 3c62b5521fea39ab7bab299052b02576ae802db9 /usr/src/cmd/man/man.c | |
parent | 81d43577d1b5e76e6016ba642ecc1a76fde43021 (diff) | |
download | illumos-joyent-95c635efb7c3b86efc493e0447eaec7aecca3f0f.tar.gz |
5025 import and use mandoc
Reviewed by: Hans Rosenfeld <hans.rosenfeld@nexenta.com>
Reviewed by: Igor Kozhukhov <ikozhukhov@gmail.com>
Reviewed by: Robert Mustacchi <rm@joyent.com>
Reviewed by: Albert Lee <trisk@nexenta.com>
Approved by: Dan McDonald <danmcd@omniti.com>
Diffstat (limited to 'usr/src/cmd/man/man.c')
-rw-r--r-- | usr/src/cmd/man/man.c | 1622 |
1 files changed, 1622 insertions, 0 deletions
diff --git a/usr/src/cmd/man/man.c b/usr/src/cmd/man/man.c new file mode 100644 index 0000000000..8038cabbac --- /dev/null +++ b/usr/src/cmd/man/man.c @@ -0,0 +1,1622 @@ +/* + * 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 <jeffpc@31bits.net>. All rights reserved. + * Copyright 2013 Nexenta Systems, Inc. All rights reserved. + * Copyright 2014 Garrett D'Amore <garrett@damore.org> + */ + +/* 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 <sys/param.h> +#include <sys/stat.h> +#include <sys/termios.h> +#include <sys/types.h> + +#include <ctype.h> +#include <dirent.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <fnmatch.h> +#include <limits.h> +#include <locale.h> +#include <malloc.h> +#include <memory.h> +#include <regex.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "man.h" + + +/* Mapping of old directories to new directories */ +static const struct map_entry { + char *old_name; + char *new_name; +} map[] = { + { "3b", "3ucb" }, + { "3e", "3elf" }, + { "3g", "3gen" }, + { "3k", "3kstat" }, + { "3n", "3socket" }, + { "3r", "3rt" }, + { "3s", "3c" }, + { "3t", "3thr" }, + { "3x", "3curses" }, + { "3xc", "3xcurses" }, + { "3xn", "3xnet" }, + { 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,1m", 0, 0 }, + { "/usr/sbin", "/usr/share/man,1m", 0, 0 }, + { "/usr/ucb", "/usr/share/man,1b", 0, 0 }, + { "/usr/bin", "/usr/share/man,1,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 suffices */ + 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 sargs = 0; +static int psoutput = 0; +static int lintout = 0; +static int whatis = 0; +static int makewhatishere = 0; + +static char *mansec; +static char *pager = NULL; + +static char *addlocale(char *); +static struct man_node *build_manpath(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 **); +static void init_bintoman(void); +static void lower(char *); +static void mandir(char **, char *, char *, int); +static int manual(struct man_node *, 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; + sargs++; + 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, bmp_flags); + freev(pathv); + 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]; + + /* + * 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, + BMP_ISPATH | BMP_FALLBACK_DEFMANDIR); + } else { + mp = mandirs; + } + + if (apropos) + whatapro(mp, argv[i]); + else + ret += manual(mp, argv[i]); + + if (mp != NULL && mp != mandirs) { + free(pv[0]); + free_manp(mp); + } + } + + 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, 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); + + /* + * 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 *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 (sargs) { + manp->secv = split(mansec, ','); + for (sectp = manp->secv; *sectp; sectp++) + lower(*sectp); + } else if ((sections = strchr(*pv, ',')) != NULL) { + DPRINTF("-- Adding %s: 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; + DPRINTF("-- Adding %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; + DPRINTF("-- Adding %s: default sort 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 (sargs) + ss = split(mansec, ','); + + while (getline(&line, &linecap, fp) > 0) { + if (regexec(&preg, line, 0, NULL, 0) == 0) { + if (sargs) { + /* 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) +{ + 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, 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 (sargs) { + (void) fprintf(stderr, gettext( + "No manual entry for %s in section(s) %s\n"), + fullname, mansec); + } 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; + + 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); + if (strcmp(dname, file) == 0 || + strcmp(pname, name) == 0) { + (void) format(path, dir, name, sd->d_name); + (void) closedir(sdp); + free(pname); + return (1); + } + free(pname); + } + (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; + int utf8 = 0; + 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"; + + /* Preprocess UTF-8 input with preconv (could be smarter) */ + if (strstr(path, "UTF-8") != NULL) + utf8 = 1; + + if (psoutput) { + (void) snprintf(cmdbuf, BUFSIZ, + "cd %s; %s %s%s | mandoc -Tps | lp -Tpostscript", + path, cattool, manpname, + utf8 ? " | " PRECONV " -e UTF-8" : ""); + DPRINTF("-- Using manpage: %s\n", manpname); + goto cmd; + } else if (lintout) { + (void) snprintf(cmdbuf, BUFSIZ, + "cd %s; %s %s%s | mandoc -Tlint", + path, cattool, manpname, + utf8 ? " | " PRECONV " -e UTF-8" : ""); + 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%s | mandoc -T%s %s| %s", + path, cattool, manpname, + utf8 ? " | " PRECONV " -e UTF-8 " : "", + utf8 ? "utf8" : "ascii", (manwidth > 0) ? tmpbuf : "", pager); + +cmd: + DPRINTF("-- Command: %s\n", cmdbuf); + + if (!debug) + return (system(cmdbuf) == 0); + else + return (0); +} + +/* + * Add <localedir> 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; + char fname[MAXPATHLEN]; + char *line = NULL; + 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 || (sect = strchr(line, '=')) == NULL) + return (NULL); + else + 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); +} |