man.c revision 95c635efb7c3b86efc493e0447eaec7aecca3f0f
/*
* 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);
}