From 2278433078e7542c0f49d8cdfe1397a9df4d208b Mon Sep 17 00:00:00 2001 From: Alan Cox Date: Mon, 29 Dec 2014 22:39:22 +0000 Subject: [PATCH] man: add basic man util --- Applications/util/Makefile | 2 + Applications/util/man.c | 716 +++++++++++++++++++++++++++++++++++++ 2 files changed, 718 insertions(+) create mode 100644 Applications/util/man.c diff --git a/Applications/util/Makefile b/Applications/util/Makefile index 4f2d6729..de9e85fe 100644 --- a/Applications/util/Makefile +++ b/Applications/util/Makefile @@ -35,6 +35,7 @@ SRCS = banner.c \ du.c \ echo.c \ ed.c \ + env.c \ false.c \ fdisk.c \ fgrep.c \ @@ -46,6 +47,7 @@ SRCS = banner.c \ ll.c \ ln.c \ ls.c \ + man.c \ mkdir.c \ mkfs.c \ mkfifo.c \ diff --git a/Applications/util/man.c b/Applications/util/man.c new file mode 100644 index 00000000..61ee7367 --- /dev/null +++ b/Applications/util/man.c @@ -0,0 +1,716 @@ +/* + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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); +} -- 2.34.1