summaryrefslogtreecommitdiff
path: root/usr/src/cmd/man/man.c
diff options
context:
space:
mode:
authorGarrett D'Amore <garrett@damore.org>2014-07-14 20:10:37 -0700
committerGarrett D'Amore <garrett@damore.org>2014-07-21 09:20:34 -0700
commit95c635efb7c3b86efc493e0447eaec7aecca3f0f (patch)
tree3c62b5521fea39ab7bab299052b02576ae802db9 /usr/src/cmd/man/man.c
parent81d43577d1b5e76e6016ba642ecc1a76fde43021 (diff)
downloadillumos-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.c1622
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);
+}