--- /dev/null
+/*
+
+ Copyright 1999 by Philip Homburg and Kees Bot.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
+ OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ Kees J. Bot (kjb@cs.vu.nl)
+ Philip Homburg (philip@cs.vu.nl)
+*/
+/* man 2.2 - display online manual pages Author: Kees J. Bot
+ * 17 Mar 1993
+ */
+#define nil NULL
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+/* Defaults: */
+char MANPATH[]= "/usr/local/man:/usr/man";
+char PAGER[]= "more";
+
+/* Comment at the start to let tbl(1) be run before n/troff. */
+char TBL_MAGIC[] = ".\\\"t\n";
+
+#define arraysize(a) (sizeof(a) / sizeof((a)[0]))
+#define arraylimit(a) ((a) + arraysize(a))
+#define between(a, c, z) ((unsigned) ((c) - (a)) <= (unsigned) ((z) - (a)))
+
+int searchwhatis(FILE *wf, char *title, char **ppage, char **psection)
+/* Search a whatis file for the next occurence of "title". Return the basename
+ * of the page to read and the section it is in. Return 0 on failure, 1 on
+ * success, -1 on EOF or error.
+ */
+{
+ static char page[256], section[32];
+ char alias[256];
+ int found= 0;
+ int c;
+
+ /* Each whatis line should have the format:
+ * page, title, title (section) - descriptive text
+ */
+
+ /* Search the file for a line with the title. */
+ do {
+ int first= 1;
+ char *pc= section;
+
+ c= fgetc(wf);
+
+ /* Search the line for the title. */
+ do {
+ char *pa= alias;
+
+ while (c == ' ' || c == '\t' || c == ',') c= fgetc(wf);
+
+ while (c != ' ' && c != '\t' && c != ','
+ && c != '(' && c != '\n' && c != EOF) {
+
+ if (pa < arraylimit(alias)-1) *pa++= c;
+ c= getc(wf);
+ }
+ *pa= 0;
+ if (first) { strcpy(page, alias); first= 0; }
+
+ if (strcmp(alias, title) == 0) found= 1;
+ } while (c != '(' && c != '\n' && c != EOF);
+
+ if (c != '(') {
+ found= 0;
+ } else {
+ while ((c= fgetc(wf)) != ')' && c != '\n' && c != EOF) {
+ if ('A' <= c && c <= 'Z') c= c - 'A' + 'a';
+ if (pc < arraylimit(section)-1) *pc++= c;
+ }
+ *pc= 0;
+ if (c != ')' || pc == section) found= 0;
+ }
+ while (c != EOF && c != '\n') c= getc(wf);
+ } while (!found && c != EOF);
+
+ if (found) {
+ *ppage= page;
+ *psection= section;
+ }
+ return c == EOF ? -1 : found;
+}
+
+int searchwindex(FILE *wf, char *title, char **ppage, char **psection)
+/* Search a windex file for the next occurence of "title". Return the basename
+ * of the page to read and the section it is in. Return 0 on failure, 1 on
+ * success, -1 on EOF or error.
+ */
+{
+ static char page[256], section[32];
+ static long low, high;
+ long mid0, mid1;
+ int c;
+ unsigned char *pt;
+ char *pc;
+
+ /* Each windex line should have the format:
+ * title page (section) - descriptive text
+ * The file is sorted.
+ */
+
+ if (ftell(wf) == 0) {
+ /* First read of this file, initialize. */
+ low= 0;
+ fseek(wf, (off_t) 0, SEEK_END);
+ high= ftell(wf);
+ }
+
+ /* Binary search for the title. */
+ while (low <= high) {
+ pt= (unsigned char *) title;
+
+ mid0= mid1= (low + high) >> 1;
+ if (mid0 == 0) {
+ if (fseek(wf, (off_t) 0, SEEK_SET) != 0)
+ return -1;
+ } else {
+ if (fseek(wf, (off_t) mid0 - 1, SEEK_SET) != 0)
+ return -1;
+
+ /* Find the start of a line. */
+ while ((c= getc(wf)) != EOF && c != '\n')
+ mid1++;
+ if (ferror(wf)) return -1;
+ }
+
+ /* See if the line has the title we seek. */
+ for (;;) {
+ if ((c= getc(wf)) == ' ' || c == '\t') c= 0;
+ if (c == 0 || c != *pt) break;
+ pt++;
+ }
+
+ /* Halve the search range. */
+ if (c == EOF || *pt <= c) {
+ high= mid0 - 1;
+ } else {
+ low= mid1 + 1;
+ }
+ }
+
+ /* Look for the title from 'low' onwards. */
+ if (fseek(wf, (off_t) low, SEEK_SET) != 0)
+ return -1;
+
+ do {
+ if (low != 0) {
+ /* Find the start of a line. */
+ while ((c= getc(wf)) != EOF && c != '\n')
+ low++;
+ if (ferror(wf)) return -1;
+ }
+
+ /* See if the line has the title we seek. */
+ pt= (unsigned char *) title;
+
+ for (;;) {
+ if ((c= getc(wf)) == EOF) return 0;
+ low++;
+ if (c == ' ' || c == '\t') c= 0;
+ if (c == 0 || c != *pt) break;
+ pt++;
+ }
+ } while (c < *pt);
+
+ if (*pt != c) return 0; /* Not found. */
+
+ /* Get page and section. */
+ while ((c= fgetc(wf)) == ' ' || c == '\t') {}
+
+ pc= page;
+ while (c != ' ' && c != '\t' && c != '(' && c != '\n' && c != EOF) {
+ if (pc < arraylimit(page)-1) *pc++= c;
+ c= getc(wf);
+ }
+ if (pc == page) return 0;
+ *pc= 0;
+
+ while (c == ' ' || c == '\t') c= fgetc(wf);
+
+ if (c != '(') return 0;
+
+ pc= section;
+ while ((c= fgetc(wf)) != ')' && c != '\n' && c != EOF) {
+ if ('A' <= c && c <= 'Z') c= c - 'A' + 'a';
+ if (pc < arraylimit(section)-1) *pc++= c;
+ }
+ *pc= 0;
+ if (c != ')' || pc == section) return 0;
+
+ while (c != EOF && c != '\n') c= getc(wf);
+ if (c != '\n') return 0;
+
+ *ppage= page;
+ *psection= section;
+ return 1;
+}
+
+char ALL[]= ""; /* Magic sequence of all sections. */
+
+int all= 0; /* Show all pages with a given title. */
+int whatis= 0; /* man -f word == whatis word. */
+int apropos= 0; /* man -k word == apropos word. */
+int quiet= 0; /* man -q == quietly check. */
+enum ROFF { NROFF, TROFF } rofftype= NROFF;
+char *roff[] = { "nroff", "troff" };
+
+int shown; /* True if something has been shown. */
+int tty; /* True if displaying on a terminal. */
+char *manpath; /* The manual directory path. */
+char *pager; /* The pager to use. */
+
+char *pipeline[8][8]; /* An 8 command pipeline of 7 arguments each. */
+char *(*plast)[8] = pipeline;
+
+void putinline(char *arg1, ...)
+/* Add a command to the pipeline. */
+{
+ va_list ap;
+ char **argv;
+
+ argv= *plast++;
+ *argv++= arg1;
+
+ va_start(ap, arg1);
+ while ((*argv++= va_arg(ap, char *)) != nil) {}
+ va_end(ap);
+}
+
+void execute(int set_mp, char *file)
+/* Execute the pipeline build with putinline(). (This is a lot of work to
+ * avoid a call to system(), but it so much fun to do it right!)
+ */
+{
+ char *(*plp)[8], **argv;
+ char *mp;
+ int fd0, pfd[2], err[2];
+ pid_t pid;
+ int r, status;
+ int last;
+ void (*isav)(signal_t sig), (*qsav)(signal_t sig), (*tsav)(signal_t sig);
+
+ if (tty) {
+ /* Must run this through a pager. */
+ putinline(pager, (char *) nil);
+ }
+ if (plast == pipeline) {
+ /* No commands at all? */
+ putinline("cat", (char *) nil);
+ }
+
+ /* Add the file as argument to the first command. */
+ argv= pipeline[0];
+ while (*argv != nil) argv++;
+ *argv++= file;
+ *argv= nil;
+
+ /* Start the commands. */
+ fd0= 0;
+ for (plp= pipeline; plp < plast; plp++) {
+ argv= *plp;
+ last= (plp+1 == plast);
+
+ /* Create an error pipe and pipe between this command and the
+ * next.
+ */
+ if (pipe(err) < 0 || (!last && pipe(pfd) < 0)) {
+ fprintf(stderr,
+ "man: can't create a pipe: %s\n",
+ strerror(errno));
+ exit(1);
+ }
+
+ (void) fcntl(err[1], F_SETFD,
+ fcntl(err[1], F_GETFD) | FD_CLOEXEC);
+
+ if ((pid = fork()) < 0) {
+ fprintf(stderr, "man: cannot fork: %s\n",
+ strerror(errno));
+ exit(1);
+ }
+ if (pid == 0) {
+ /* Child. */
+ if (set_mp) {
+ mp= malloc((8 + strlen(manpath) + 1)
+ * sizeof(*mp));
+ if (mp != nil) {
+ strcpy(mp, "MANPATH=");
+ strcat(mp, manpath);
+ (void) putenv(mp);
+ }
+ }
+
+ if (fd0 != 0) {
+ dup2(fd0, 0);
+ close(fd0);
+ }
+ if (!last) {
+ close(pfd[0]);
+ if (pfd[1] != 1) {
+ dup2(pfd[1], 1);
+ close(pfd[1]);
+ }
+ }
+ close(err[0]);
+ execvp(argv[0], argv);
+ (void) write(err[1], (void *)&errno, sizeof(errno));
+ _exit(1);
+ }
+
+ close(err[1]);
+ if (read(err[0], (void *)&errno, sizeof(errno)) != 0) {
+ fprintf(stderr, "man: %s: %s\n", argv[0],
+ strerror(errno));
+ exit(1);
+ }
+ close(err[0]);
+
+ if (!last) {
+ close(pfd[1]);
+ fd0= pfd[0];
+ }
+ set_mp= 0;
+ }
+
+ /* Wait for the last command to finish. */
+ isav= signal(SIGINT, SIG_IGN);
+ qsav= signal(SIGQUIT, SIG_IGN);
+ tsav= signal(SIGTERM, SIG_IGN);
+ while ((r= wait(&status)) != pid) {
+ if (r < 0) {
+ fprintf(stderr, "man: wait(): %s\n", strerror(errno));
+ exit(1);
+ }
+ }
+ (void) signal(SIGINT, isav);
+ (void) signal(SIGQUIT, qsav);
+ (void) signal(SIGTERM, tsav);
+ if (status != 0) exit(1);
+ plast= pipeline;
+}
+
+void keyword(char *keyword)
+/* Make an apropos(1) or whatis(1) call. */
+{
+ putinline(apropos ? "apropos" : "whatis",
+ all ? "-a" : (char *) nil,
+ (char *) nil);
+
+ if (tty) {
+ printf("Looking for keyword '%s'\n", keyword);
+ fflush(stdout);
+ }
+
+ execute(1, keyword);
+}
+
+enum pagetype { CAT, CATZ, MAN, MANZ };
+
+int showpage(char *page, enum pagetype ptype, char *macros)
+/* Show a manual page if it exists using the proper decompression and
+ * formatting tools.
+ */
+{
+ struct stat st;
+
+ /* We want a normal file without X bits if not a full path. */
+ if (stat(page, &st) < 0) return 0;
+
+ if (!S_ISREG(st.st_mode)) return 0;
+ if ((st.st_mode & 0111) && page[0] != '/') return 0;
+
+ /* Do we only care if it exists? */
+ if (quiet) { shown= 1; return 1; }
+
+ if (ptype == CATZ || ptype == MANZ) {
+ putinline("zcat", (char *) nil);
+ }
+
+ if (ptype == MAN) {
+ /* Do we need tbl? */
+ FILE *fp;
+ int c;
+ char *tp = TBL_MAGIC;
+
+ if ((fp = fopen(page, "r")) == nil) {
+ fprintf(stderr, "man: %s: %s\n", page, strerror(errno));
+ exit(1);
+ }
+ c= fgetc(fp);
+ for (;;) {
+ if (c == *tp || (c == '\'' && *tp == '.')) {
+ if (*++tp == 0) {
+ /* A match, add tbl. */
+ putinline("tbl", (char *) nil);
+ break;
+ }
+ } else {
+ /* No match. */
+ break;
+ }
+ while ((c = fgetc(fp)) == ' ' || c == '\t') {}
+ }
+ fclose(fp);
+ }
+
+ if (ptype == MAN || ptype == MANZ) {
+ putinline(roff[rofftype], macros, (char *) nil);
+ }
+
+ if (tty) {
+ printf("%s %s\n",
+ ptype >= MAN ? "Formatting" : "Showing", page);
+ fflush(stdout);
+ }
+ execute(0, page);
+
+ shown= 1;
+ return 1;
+}
+
+int member(char *word, char *list)
+/* True if word is a member of a comma separated list. */
+{
+ size_t len= strlen(word);
+
+ if (list == ALL) return 1;
+
+ while (*list != 0) {
+ if (strncmp(word, list, len) == 0
+ && (list[len] == 0 || list[len] == ','))
+ return 1;
+ while (*list != 0 && *list != ',') list++;
+ if (*list == ',') list++;
+ }
+ return 0;
+}
+
+int trymandir(char *mandir, char *title, char *section)
+/* Search the whatis file of the manual directory for a page of the given
+ * section and display it.
+ */
+{
+ FILE *wf;
+ char whatis[1024], pagename[1024], *wpage, *wsection;
+ int rsw, rsp;
+ int ntries;
+ int (*searchidx)(FILE *, char *, char **, char **);
+ struct searchnames {
+ enum pagetype ptype;
+ char *pathfmt;
+ } *sp;
+ static struct searchnames searchN[] = {
+ { CAT, "%s/cat%s/%s.%s" }, /* SysV */
+ { CATZ, "%s/cat%s/%s.%s.Z" },
+ { MAN, "%s/man%s/%s.%s" },
+ { MANZ, "%s/man%s/%s.%s.Z" },
+ { CAT, "%s/cat%.1s/%s.%s" }, /* BSD */
+ { CATZ, "%s/cat%.1s/%s.%s.Z" },
+ { MAN, "%s/man%.1s/%s.%s" },
+ { MANZ, "%s/man%.1s/%s.%s.Z" },
+ };
+
+ if (strlen(mandir) + 1 + 6 + 1 > arraysize(whatis)) return 0;
+
+ /* Prefer a fast windex database if available. */
+ sprintf(whatis, "%s/windex", mandir);
+
+ if ((wf= fopen(whatis, "r")) != nil) {
+ searchidx= searchwindex;
+ } else {
+ /* Use a classic whatis database. */
+ sprintf(whatis, "%s/whatis", mandir);
+
+ if ((wf= fopen(whatis, "r")) == nil) return 0;
+ searchidx= searchwhatis;
+ }
+
+ rsp= 0;
+ while (!rsp && (rsw= (*searchidx)(wf, title, &wpage, &wsection)) == 1) {
+ if (!member(wsection, section)) continue;
+
+ /* When looking for getc(1S) we try:
+ * cat1s/getc.1s
+ * cat1s/getc.1s.Z
+ * man1s/getc.1s
+ * man1s/getc.1s.Z
+ * cat1/getc.1s
+ * cat1/getc.1s.Z
+ * man1/getc.1s
+ * man1/getc.1s.Z
+ */
+
+ if (strlen(mandir) + 2 * strlen(wsection) + strlen(wpage)
+ + 10 > arraysize(pagename))
+ continue;
+
+ sp= searchN;
+ ntries= arraysize(searchN);
+ do {
+ if (sp->ptype <= CATZ && rofftype != NROFF)
+ continue;
+
+ sprintf(pagename, sp->pathfmt,
+ mandir, wsection, wpage, wsection);
+
+ rsp= showpage(pagename, sp->ptype,
+ strcmp(wsection, "9") == 0 ? "-mnx" : "-man");
+ } while (sp++, !rsp && --ntries != 0);
+
+ if (all) rsp= 0;
+ }
+ if (rsw < 0 && ferror(wf)) {
+ fprintf(stderr, "man: %s: %s\n", whatis, strerror(errno));
+ exit(1);
+ }
+ fclose(wf);
+ return rsp;
+}
+
+int trysubmandir(char *mandir, char *title, char *section)
+/* Search the subdirectories of this manual directory for whatis files, they
+ * may have manual pages that override the ones in the major directory.
+ */
+{
+ char submandir[1024];
+ DIR *md;
+ struct dirent *entry;
+
+ if ((md= opendir(mandir)) == nil) return 0;
+
+ while ((entry= readdir(md)) != nil) {
+ if (strcmp(entry->d_name, ".") == 0
+ || strcmp(entry->d_name, "..") == 0) continue;
+ if ((strncmp(entry->d_name, "man", 3) == 0
+ || strncmp(entry->d_name, "cat", 3) == 0)
+ && between('0', entry->d_name[3], '9')) continue;
+
+ if (strlen(mandir) + 1 + strlen(entry->d_name) + 1
+ > arraysize(submandir)) continue;
+
+ sprintf(submandir, "%s/%s", mandir, entry->d_name);
+
+ if (trymandir(submandir, title, section) && !all) {
+ closedir(md);
+ return 1;
+ }
+ }
+ closedir(md);
+
+ return 0;
+}
+
+void searchmanpath(char *title, char *section)
+/* Search the manual path for a manual page describing "title." */
+{
+ char mandir[1024];
+ char *pp= manpath, *pd;
+
+ for (;;) {
+ while (*pp != 0 && *pp == ':') pp++;
+
+ if (*pp == 0) break;
+
+ pd= mandir;
+ while (*pp != 0 && *pp != ':') {
+ if (pd < arraylimit(mandir)) *pd++= *pp;
+ pp++;
+ }
+ if (pd == arraylimit(mandir)) continue; /* forget it */
+
+ *pd= 0;
+ if (trysubmandir(mandir, title, section) && !all) break;
+ if (trymandir(mandir, title, section) && !all) break;
+ }
+}
+
+void usage(void)
+{
+ fprintf(stderr,
+ "Usage: man -[antfkq] [-M path] [-s section] title ...\n");
+ exit(1);
+}
+
+int main(int argc, char **argv)
+{
+ char *title, *section= ALL;
+ int i;
+ int nomoreopt= 0;
+ char *opt;
+
+ if ((pager= getenv("PAGER")) == nil) pager= PAGER;
+ if ((manpath= getenv("MANPATH")) == nil) manpath= MANPATH;
+ tty= isatty(1);
+
+ i= 1;
+ do {
+ while (i < argc && argv[i][0] == '-' && !nomoreopt) {
+ opt= argv[i++]+1;
+ if (opt[0] == '-' && opt[1] == 0) {
+ nomoreopt= 1;
+ break;
+ }
+ while (*opt != 0) {
+ switch (*opt++) {
+ case 'a':
+ all= 1;
+ break;
+ case 'f':
+ whatis= 1;
+ break;
+ case 'k':
+ apropos= 1;
+ break;
+ case 'q':
+ quiet= 1;
+ break;
+ case 'n':
+ rofftype= NROFF;
+ apropos= whatis= 0;
+ break;
+ case 't':
+ rofftype= TROFF;
+ apropos= whatis= 0;
+ break;
+ case 's':
+ if (*opt == 0) {
+ if (i == argc) usage();
+ section= argv[i++];
+ } else {
+ section= opt;
+ opt= "";
+ }
+ break;
+ case 'M':
+ if (*opt == 0) {
+ if (i == argc) usage();
+ manpath= argv[i++];
+ } else {
+ manpath= opt;
+ opt= "";
+ }
+ break;
+ default:
+ usage();
+ }
+ }
+ }
+
+ if (i >= argc) usage();
+
+ if (between('0', argv[i][0], '9') && argv[i][1] == 0) {
+ /* Allow single digit section designations. */
+ section= argv[i++];
+ }
+ if (i == argc) usage();
+
+ title= argv[i++];
+
+ if (whatis || apropos) {
+ keyword(title);
+ } else {
+ shown= 0;
+ searchmanpath(title, section);
+
+ if (!shown) (void) showpage(title, MAN, "-man");
+
+ if (!shown) {
+ if (!quiet) {
+ fprintf(stderr,
+ "man: no manual on %s\n",
+ title);
+ }
+ exit(1);
+ }
+ }
+ } while (i < argc);
+
+ exit(0);
+}