man.c

Go to the documentation of this file.
00001 /*      man 2.4 - display online manual pages           Author: Kees J. Bot
00002  *                                                              17 Mar 1993
00003  */
00004 #define nil NULL
00005 #include <sys/types.h>
00006 #include <stdio.h>
00007 #include <stdlib.h>
00008 #include <dirent.h>
00009 #include <string.h>
00010 #include <errno.h>
00011 #include <unistd.h>
00012 #include <stdarg.h>
00013 #include <fcntl.h>
00014 #include <signal.h>
00015 #include <sys/stat.h>
00016 #include <sys/wait.h>
00017 
00018 /* Defaults: */
00019 char MANPATH[]= "/usr/local/man:/usr/man:/usr/gnu/man";
00020 char PAGER[]=   "more";
00021 
00022 /* Comment at the start to let tbl(1) be run before n/troff. */
00023 char TBL_MAGIC[] = ".\\\"t\n";
00024 
00025 #define arraysize(a)    (sizeof(a) / sizeof((a)[0]))
00026 #define arraylimit(a)   ((a) + arraysize(a))
00027 #define between(a, c, z) ((unsigned) ((c) - (a)) <= (unsigned) ((z) - (a)))
00028 
00029 /* Section 9 uses special macros under Minix. */
00030 #if __minix
00031 #define SEC9SPECIAL     1
00032 #else
00033 #define SEC9SPECIAL     0
00034 #endif
00035 
00036 int searchwhatis(FILE *wf, char *title, char **ppage, char **psection)
00037 /* Search a whatis file for the next occurence of "title".  Return the basename
00038  * of the page to read and the section it is in.  Return 0 on failure, 1 on
00039  * success, -1 on EOF or error.
00040  */
00041 {
00042     static char page[256], section[32];
00043     char alias[256];
00044     int found= 0;
00045     int c;
00046 
00047     /* Each whatis line should have the format:
00048      *  page, title, title (section) - descriptive text
00049      */
00050 
00051     /* Search the file for a line with the title. */
00052     do {
00053         int first= 1;
00054         char *pc= section;
00055 
00056         c= fgetc(wf);
00057 
00058         /* Search the line for the title. */
00059         do {
00060             char *pa= alias;
00061 
00062             while (c == ' ' || c == '\t' || c == ',') c= fgetc(wf);
00063 
00064             while (c != ' ' && c != '\t' && c != ','
00065                     && c != '(' && c != '\n' && c != EOF
00066             ) {
00067                 if (pa < arraylimit(alias)-1) *pa++= c;
00068                 c= getc(wf);
00069             }
00070             *pa= 0;
00071             if (first) { strcpy(page, alias); first= 0; }
00072 
00073             if (strcmp(alias, title) == 0) found= 1;
00074         } while (c != '(' && c != '\n' && c != EOF);
00075 
00076         if (c != '(') {
00077             found= 0;
00078         } else {
00079             while ((c= fgetc(wf)) != ')' && c != '\n' && c != EOF) {
00080                 if ('A' <= c && c <= 'Z') c= c - 'A' + 'a';
00081                 if (pc < arraylimit(section)-1) *pc++= c;
00082             }
00083             *pc= 0;
00084             if (c != ')' || pc == section) found= 0;
00085         }
00086         while (c != EOF && c != '\n') c= getc(wf);
00087     } while (!found && c != EOF);
00088 
00089     if (found) {
00090         *ppage= page;
00091         *psection= section;
00092     }
00093     return c == EOF ? -1 : found;
00094 }
00095 
00096 int searchwindex(FILE *wf, char *title, char **ppage, char **psection)
00097 /* Search a windex file for the next occurence of "title".  Return the basename
00098  * of the page to read and the section it is in.  Return 0 on failure, 1 on
00099  * success, -1 on EOF or error.
00100  */
00101 {
00102     static char page[256], section[32];
00103     static long low, high;
00104     long mid0, mid1;
00105     int c;
00106     unsigned char *pt;
00107     char *pc;
00108 
00109     /* Each windex line should have the format:
00110      *  title page (section) - descriptive text
00111      * The file is sorted.
00112      */
00113 
00114     if (ftell(wf) == 0) {
00115         /* First read of this file, initialize. */
00116         low= 0;
00117         fseek(wf, (off_t) 0, SEEK_END);
00118         high= ftell(wf);
00119     }
00120 
00121     /* Binary search for the title. */
00122     while (low <= high) {
00123         pt= (unsigned char *) title;
00124 
00125         mid0= mid1= (low + high) >> 1;
00126         if (mid0 == 0) {
00127             if (fseek(wf, (off_t) 0, SEEK_SET) != 0)
00128                 return -1;
00129         } else {
00130             if (fseek(wf, (off_t) mid0 - 1, SEEK_SET) != 0)
00131                 return -1;
00132 
00133             /* Find the start of a line. */
00134             while ((c= getc(wf)) != EOF && c != '\n')
00135                 mid1++;
00136             if (ferror(wf)) return -1;
00137         }
00138 
00139         /* See if the line has the title we seek. */
00140         for (;;) {
00141             if ((c= getc(wf)) == ' ' || c == '\t') c= 0;
00142             if (c == 0 || c != *pt) break;
00143             pt++;
00144         }
00145 
00146         /* Halve the search range. */
00147         if (c == EOF || *pt <= c) {
00148             high= mid0 - 1;
00149         } else {
00150             low= mid1 + 1;
00151         }
00152     }
00153 
00154     /* Look for the title from 'low' onwards. */
00155     if (fseek(wf, (off_t) low, SEEK_SET) != 0)
00156         return -1;
00157 
00158     do {
00159         if (low != 0) {
00160             /* Find the start of a line. */
00161             while ((c= getc(wf)) != EOF && c != '\n')
00162                 low++;
00163             if (ferror(wf)) return -1;
00164         }
00165 
00166         /* See if the line has the title we seek. */
00167         pt= (unsigned char *) title;
00168 
00169         for (;;) {
00170             if ((c= getc(wf)) == EOF) return 0;
00171             low++;
00172             if (c == ' ' || c == '\t') c= 0;
00173             if (c == 0 || c != *pt) break;
00174             pt++;
00175         }
00176     } while (c < *pt);
00177 
00178     if (*pt != c) return 0;             /* Not found. */
00179 
00180     /* Get page and section. */
00181     while ((c= fgetc(wf)) == ' ' || c == '\t') {}
00182 
00183     pc= page;
00184     while (c != ' ' && c != '\t' && c != '(' && c != '\n' && c != EOF) {
00185         if (pc < arraylimit(page)-1) *pc++= c;
00186         c= getc(wf);
00187     }
00188     if (pc == page) return 0;
00189     *pc= 0;
00190 
00191     while (c == ' ' || c == '\t') c= fgetc(wf);
00192 
00193     if (c != '(') return 0;
00194 
00195     pc= section;
00196     while ((c= fgetc(wf)) != ')' && c != '\n' && c != EOF) {
00197         if ('A' <= c && c <= 'Z') c= c - 'A' + 'a';
00198         if (pc < arraylimit(section)-1) *pc++= c;
00199     }
00200     *pc= 0;
00201     if (c != ')' || pc == section) return 0;
00202 
00203     while (c != EOF && c != '\n') c= getc(wf);
00204     if (c != '\n') return 0;
00205 
00206     *ppage= page;
00207     *psection= section;
00208     return 1;
00209 }
00210 
00211 char ALL[]= "";         /* Magic sequence of all sections. */
00212 
00213 int all= 0;             /* Show all pages with a given title. */
00214 int whatis= 0;          /* man -f word == whatis word. */
00215 int apropos= 0;         /* man -k word == apropos word. */
00216 int quiet= 0;           /* man -q == quietly check. */
00217 enum ROFF { NROFF, TROFF } rofftype= NROFF;
00218 char *roff[] = { "nroff", "troff" };
00219 
00220 int shown;              /* True if something has been shown. */
00221 int tty;                /* True if displaying on a terminal. */
00222 char *manpath;          /* The manual directory path. */
00223 char *pager;            /* The pager to use. */
00224 
00225 char *pipeline[8][8];   /* An 8 command pipeline of 7 arguments each. */
00226 char *(*plast)[8] = pipeline;
00227 
00228 void putinline(char *arg1, ...)
00229 /* Add a command to the pipeline. */
00230 {
00231     va_list ap;
00232     char **argv;
00233 
00234     argv= *plast++;
00235     *argv++= arg1;
00236 
00237     va_start(ap, arg1);
00238     while ((*argv++= va_arg(ap, char *)) != nil) {}
00239     va_end(ap);
00240 }
00241 
00242 void execute(int set_mp, char *file)
00243 /* Execute the pipeline build with putinline().  (This is a lot of work to
00244  * avoid a call to system(), but it so much fun to do it right!)
00245  */
00246 {
00247     char *(*plp)[8], **argv;
00248     char *mp;
00249     int fd0, pfd[2], err[2];
00250     pid_t pid;
00251     int r, status;
00252     int last;
00253     void (*isav)(int sig), (*qsav)(int sig), (*tsav)(int sig);
00254 
00255     if (tty) {
00256         /* Must run this through a pager. */
00257         putinline(pager, (char *) nil);
00258     }
00259     if (plast == pipeline) {
00260         /* No commands at all? */
00261         putinline("cat", (char *) nil);
00262     }
00263 
00264     /* Add the file as argument to the first command. */
00265     argv= pipeline[0];
00266     while (*argv != nil) argv++;
00267     *argv++= file;
00268     *argv= nil;
00269 
00270     /* Start the commands. */
00271     fd0= 0;
00272     for (plp= pipeline; plp < plast; plp++) {
00273         argv= *plp;
00274         last= (plp+1 == plast);
00275 
00276         /* Create an error pipe and pipe between this command and the next. */
00277         if (pipe(err) < 0 || (!last && pipe(pfd) < 0)) {
00278             fprintf(stderr, "man: can't create a pipe: %s\n", strerror(errno));
00279             exit(1);
00280         }
00281 
00282         (void) fcntl(err[1], F_SETFD, fcntl(err[1], F_GETFD) | FD_CLOEXEC);
00283 
00284         if ((pid = fork()) < 0) {
00285             fprintf(stderr, "man: cannot fork: %s\n", strerror(errno));
00286             exit(1);
00287         }
00288         if (pid == 0) {
00289             /* Child. */
00290             if (set_mp) {
00291                 mp= malloc((8 + strlen(manpath) + 1) * sizeof(*mp));
00292                 if (mp != nil) {
00293                     strcpy(mp, "MANPATH=");
00294                     strcat(mp, manpath);
00295                     (void) putenv(mp);
00296                 }
00297             }
00298 
00299             if (fd0 != 0) {
00300                 dup2(fd0, 0);
00301                 close(fd0);
00302             }
00303             if (!last) {
00304                 close(pfd[0]);
00305                 if (pfd[1] != 1) {
00306                     dup2(pfd[1], 1);
00307                     close(pfd[1]);
00308                 }
00309             }
00310             close(err[0]);
00311             execvp(argv[0], argv);
00312             (void) write(err[1], &errno, sizeof(errno));
00313             _exit(1);
00314         }
00315 
00316         close(err[1]);
00317         if (read(err[0], &errno, sizeof(errno)) != 0) {
00318             fprintf(stderr, "man: %s: %s\n", argv[0],
00319                             strerror(errno));
00320             exit(1);
00321         }
00322         close(err[0]);
00323 
00324         if (!last) {
00325             close(pfd[1]);
00326             fd0= pfd[0];
00327         }
00328         set_mp= 0;
00329     }
00330 
00331     /* Wait for the last command to finish. */
00332     isav= signal(SIGINT, SIG_IGN);
00333     qsav= signal(SIGQUIT, SIG_IGN);
00334     tsav= signal(SIGTERM, SIG_IGN);
00335     while ((r= wait(&status)) != pid) {
00336         if (r < 0) {
00337             fprintf(stderr, "man: wait(): %s\n", strerror(errno));
00338             exit(1);
00339         }
00340     }
00341     (void) signal(SIGINT, isav);
00342     (void) signal(SIGQUIT, qsav);
00343     (void) signal(SIGTERM, tsav);
00344     if (status != 0) exit(1);
00345     plast= pipeline;
00346 }
00347 
00348 void keyword(char *keyword)
00349 /* Make an apropos(1) or whatis(1) call. */
00350 {
00351     putinline(apropos ? "apropos" : "whatis",
00352                 all ? "-a" : (char *) nil,
00353                 (char *) nil);
00354 
00355     if (tty) {
00356         printf("Looking for keyword '%s'\n", keyword);
00357         fflush(stdout);
00358     }
00359 
00360     execute(1, keyword);
00361 }
00362 
00363 enum pagetype { CAT, CATZ, MAN, MANZ, SMAN, SMANZ };
00364 
00365 int showpage(char *page, enum pagetype ptype, char *macros)
00366 /* Show a manual page if it exists using the proper decompression and
00367  * formatting tools.
00368  */
00369 {
00370     struct stat st;
00371 
00372     /* We want a normal file without X bits if not a full path. */
00373     if (stat(page, &st) < 0) return 0;
00374 
00375     if (!S_ISREG(st.st_mode)) return 0;
00376     if ((st.st_mode & 0111) && page[0] != '/') return 0;
00377 
00378     /* Do we only care if it exists? */
00379     if (quiet) { shown= 1; return 1; }
00380 
00381     if (ptype == CATZ || ptype == MANZ || ptype == SMANZ) {
00382         putinline("zcat", (char *) nil);
00383     }
00384 
00385     if (ptype == SMAN || ptype == SMANZ) {
00386         /* Change SGML into regular *roff. */
00387         putinline("/usr/lib/sgml/sgml2roff", (char *) nil);
00388         putinline("tbl", (char *) nil);
00389         putinline("eqn", (char *) nil);
00390     }
00391 
00392     if (ptype == MAN) {
00393         /* Do we need tbl? */
00394         FILE *fp;
00395         int c;
00396         char *tp = TBL_MAGIC;
00397 
00398         if ((fp = fopen(page, "r")) == nil) {
00399             fprintf(stderr, "man: %s: %s\n", page, strerror(errno));
00400             exit(1);
00401         }
00402         c= fgetc(fp);
00403         for (;;) {
00404             if (c == *tp || (c == '\'' && *tp == '.')) {
00405                 if (*++tp == 0) {
00406                     /* A match, add tbl. */
00407                     putinline("tbl", (char *) nil);
00408                     break;
00409                 }
00410             } else {
00411                 /* No match. */
00412                 break;
00413             }
00414             while ((c = fgetc(fp)) == ' ' || c == '\t') {}
00415         }
00416         fclose(fp);
00417     }
00418 
00419     if (ptype == MAN || ptype == MANZ || ptype == SMAN || ptype == SMANZ) {
00420         putinline(roff[rofftype], macros, (char *) nil);
00421     }
00422 
00423     if (tty) {
00424         printf("%s %s\n",
00425             ptype == CAT || ptype == CATZ ? "Showing" : "Formatting", page);
00426         fflush(stdout);
00427     }
00428     execute(0, page);
00429 
00430     shown= 1;
00431     return 1;
00432 }
00433 
00434 int member(char *word, char *list)
00435 /* True if word is a member of a comma separated list. */
00436 {
00437     size_t len= strlen(word);
00438 
00439     if (list == ALL) return 1;
00440 
00441     while (*list != 0) {
00442         if (strncmp(word, list, len) == 0
00443                 && (list[len] == 0 || list[len] == ','))
00444             return 1;
00445         while (*list != 0 && *list != ',') list++;
00446         if (*list == ',') list++;
00447     }
00448     return 0;
00449 }
00450 
00451 int trymandir(char *mandir, char *title, char *section)
00452 /* Search the whatis file of the manual directory for a page of the given
00453  * section and display it.
00454  */
00455 {
00456     FILE *wf;
00457     char whatis[1024], pagename[1024], *wpage, *wsection;
00458     int rsw, rsp;
00459     int ntries;
00460     int (*searchidx)(FILE *, char *, char **, char **);
00461     struct searchnames {
00462         enum pagetype   ptype;
00463         char            *pathfmt;
00464     } *sp;
00465     static struct searchnames searchN[] = {
00466         { CAT,  "%s/cat%s/%s.%s"        },      /* SysV */
00467         { CATZ, "%s/cat%s/%s.%s.Z"      },
00468         { MAN,  "%s/man%s/%s.%s"        },
00469         { MANZ, "%s/man%s/%s.%s.Z"      },
00470         { SMAN, "%s/sman%s/%s.%s"       },      /* Solaris */
00471         { SMANZ,"%s/sman%s/%s.%s.Z"     },
00472         { CAT,  "%s/cat%.1s/%s.%s"      },      /* BSD */
00473         { CATZ, "%s/cat%.1s/%s.%s.Z"    },
00474         { MAN,  "%s/man%.1s/%s.%s"      },
00475         { MANZ, "%s/man%.1s/%s.%s.Z"    },
00476     };
00477 
00478     if (strlen(mandir) + 1 + 6 + 1 > arraysize(whatis)) return 0;
00479 
00480     /* Prefer a fast windex database if available. */
00481     sprintf(whatis, "%s/windex", mandir);
00482 
00483     if ((wf= fopen(whatis, "r")) != nil) {
00484         searchidx= searchwindex;
00485     } else {
00486         /* Use a classic whatis database. */
00487         sprintf(whatis, "%s/whatis", mandir);
00488 
00489         if ((wf= fopen(whatis, "r")) == nil) return 0;
00490         searchidx= searchwhatis;
00491     }
00492 
00493     rsp= 0;
00494     while (!rsp && (rsw= (*searchidx)(wf, title, &wpage, &wsection)) == 1) {
00495         if (!member(wsection, section)) continue;
00496 
00497         /* When looking for getc(1S) we try:
00498          *      cat1s/getc.1s
00499          *      cat1s/getc.1s.Z
00500          *      man1s/getc.1s
00501          *      man1s/getc.1s.Z
00502          *      sman1s/getc.1s
00503          *      sman1s/getc.1s.Z
00504          *      cat1/getc.1s
00505          *      cat1/getc.1s.Z
00506          *      man1/getc.1s
00507          *      man1/getc.1s.Z
00508          */
00509 
00510         if (strlen(mandir) + 2 * strlen(wsection) + strlen(wpage)
00511                     + 10 > arraysize(pagename))
00512             continue;
00513 
00514         sp= searchN;
00515         ntries= arraysize(searchN);
00516         do {
00517             if (sp->ptype <= CATZ && rofftype != NROFF)
00518                 continue;
00519 
00520             sprintf(pagename, sp->pathfmt,
00521                 mandir, wsection, wpage, wsection);
00522 
00523             rsp= showpage(pagename, sp->ptype,
00524                 (SEC9SPECIAL && strcmp(wsection, "9") == 0) ? "-mnx" : "-man");
00525         } while (sp++, !rsp && --ntries != 0);
00526 
00527         if (all) rsp= 0;
00528     }
00529     if (rsw < 0 && ferror(wf)) {
00530         fprintf(stderr, "man: %s: %s\n", whatis, strerror(errno));
00531         exit(1);
00532     }
00533     fclose(wf);
00534     return rsp;
00535 }
00536 
00537 int trysubmandir(char *mandir, char *title, char *section)
00538 /* Search the subdirectories of this manual directory for whatis files, they
00539  * may have manual pages that override the ones in the major directory.
00540  */
00541 {
00542     char submandir[1024];
00543     DIR *md;
00544     struct dirent *entry;
00545 
00546     if ((md= opendir(mandir)) == nil) return 0;
00547 
00548     while ((entry= readdir(md)) != nil) {
00549         if (strcmp(entry->d_name, ".") == 0
00550             || strcmp(entry->d_name, "..") == 0) continue;
00551         if ((strncmp(entry->d_name, "man", 3) == 0
00552             || strncmp(entry->d_name, "cat", 3) == 0)
00553             && between('0', entry->d_name[3], '9')) continue;
00554 
00555         if (strlen(mandir) + 1 + strlen(entry->d_name) + 1
00556                     > arraysize(submandir)) continue;
00557 
00558         sprintf(submandir, "%s/%s", mandir, entry->d_name);
00559 
00560         if (trymandir(submandir, title, section) && !all) {
00561             closedir(md);
00562             return 1;
00563         }
00564     }
00565     closedir(md);
00566 
00567     return 0;
00568 }
00569 
00570 void searchmanpath(char *title, char *section)
00571 /* Search the manual path for a manual page describing "title." */
00572 {
00573     char mandir[1024];
00574     char *pp= manpath, *pd;
00575 
00576     for (;;) {
00577         while (*pp != 0 && *pp == ':') pp++;
00578 
00579         if (*pp == 0) break;
00580 
00581         pd= mandir;
00582         while (*pp != 0 && *pp != ':') {
00583             if (pd < arraylimit(mandir)) *pd++= *pp;
00584             pp++;
00585         }
00586         if (pd == arraylimit(mandir)) continue;         /* forget it */
00587 
00588         *pd= 0;
00589         if (trysubmandir(mandir, title, section) && !all) break;
00590         if (trymandir(mandir, title, section) && !all) break;
00591     }
00592 }
00593 
00594 void usage(void)
00595 {
00596     fprintf(stderr, "Usage: man -[antfkq] [-M path] [-s section] title ...\n");
00597     exit(1);
00598 }
00599 
00600 int main(int argc, char **argv)
00601 {
00602     char *title, *section= ALL;
00603     int i;
00604     int nomoreopt= 0;
00605     char *opt;
00606 
00607     if ((pager= getenv("PAGER")) == nil) pager= PAGER;
00608     if ((manpath= getenv("MANPATH")) == nil) manpath= MANPATH;
00609     tty= isatty(1);
00610 
00611     i= 1;
00612     do {
00613         while (i < argc && argv[i][0] == '-' && !nomoreopt) {
00614             opt= argv[i++]+1;
00615             if (opt[0] == '-' && opt[1] == 0) {
00616                 nomoreopt= 1;
00617                 break;
00618             }
00619             while (*opt != 0) {
00620                 switch (*opt++) {
00621                 case 'a':
00622                     all= 1;
00623                     break;
00624                 case 'f':
00625                     whatis= 1;
00626                     break;
00627                 case 'k':
00628                     apropos= 1;
00629                     break;
00630                 case 'q':
00631                     quiet= 1;
00632                     break;
00633                 case 'n':
00634                     rofftype= NROFF;
00635                     apropos= whatis= 0;
00636                     break;
00637                 case 't':
00638                     rofftype= TROFF;
00639                     apropos= whatis= 0;
00640                     break;
00641                 case 's':
00642                     if (*opt == 0) {
00643                         if (i == argc) usage();
00644                         section= argv[i++];
00645                     } else {
00646                         section= opt;
00647                         opt= "";
00648                     }
00649                     break;
00650                 case 'M':
00651                     if (*opt == 0) {
00652                         if (i == argc) usage();
00653                         manpath= argv[i++];
00654                     } else {
00655                         manpath= opt;
00656                         opt= "";
00657                     }
00658                     break;
00659                 default:
00660                     usage();
00661                 }
00662             }
00663         }
00664 
00665         if (i >= argc) usage();
00666 
00667         if (between('0', argv[i][0], '9') && argv[i][1] == 0) {
00668             /* Allow single digit section designations. */
00669             section= argv[i++];
00670         }
00671         if (i == argc) usage();
00672 
00673         title= argv[i++];
00674 
00675         if (whatis || apropos) {
00676             keyword(title);
00677         } else {
00678             shown= 0;
00679             searchmanpath(title, section);
00680 
00681             if (!shown) (void) showpage(title, MAN, "-man");
00682 
00683             if (!shown) {
00684                 if (!quiet) {
00685                     fprintf(stderr,
00686                         "man: no manual on %s\n",
00687                         title);
00688                 }
00689                 exit(1);
00690             }
00691         }
00692     } while (i < argc);
00693 
00694     return 0;
00695 }

Generated on Fri Apr 14 22:57:08 2006 for minix by  doxygen 1.4.6