1 /* man 2.5 - display online manual pages Author: Kees J. Bot
19 char MANPATH
[]= "/usr/local/man:/usr/man:/usr/gnu/man";
22 /* Comment at the start to let tbl(1) be run before n/troff. */
23 char TBL_MAGIC
[] = ".\\\"t\n";
25 #define arraysize(a) (sizeof(a) / sizeof((a)[0]))
26 #define arraylimit(a) ((a) + arraysize(a))
27 #define between(a, c, z) ((unsigned) ((c) - (a)) <= (unsigned) ((z) - (a)))
29 /* Section 1x uses special macros under Minix. */
31 #define SEC1xSPECIAL 1
33 #define SEC1xSPECIAL 0
36 int searchwhatis(FILE *wf
, char *title
, char **ppage
, char **psection
)
37 /* Search a whatis file for the next occurence of "title". Return the basename
38 * of the page to read and the section it is in. Return 0 on failure, 1 on
39 * success, -1 on EOF or error.
42 static char page
[256], section
[32];
47 /* Each whatis line should have the format:
48 * page, title, title (section) - descriptive text
51 /* Search the file for a line with the title. */
58 /* Search the line for the title. */
62 while (c
== ' ' || c
== '\t' || c
== ',') c
= fgetc(wf
);
64 while (c
!= ' ' && c
!= '\t' && c
!= ','
65 && c
!= '(' && c
!= '\n' && c
!= EOF
67 if (pa
< arraylimit(alias
)-1) *pa
++= c
;
71 if (first
) { strcpy(page
, alias
); first
= 0; }
73 if (strcmp(alias
, title
) == 0) found
= 1;
74 } while (c
!= '(' && c
!= '\n' && c
!= EOF
);
79 while ((c
= fgetc(wf
)) != ')' && c
!= '\n' && c
!= EOF
) {
80 if ('A' <= c
&& c
<= 'Z') c
= c
- 'A' + 'a';
81 if (pc
< arraylimit(section
)-1) *pc
++= c
;
84 if (c
!= ')' || pc
== section
) found
= 0;
86 while (c
!= EOF
&& c
!= '\n') c
= getc(wf
);
87 } while (!found
&& c
!= EOF
);
93 return c
== EOF
? -1 : found
;
96 int searchwindex(FILE *wf
, char *title
, char **ppage
, char **psection
)
97 /* Search a windex file for the next occurence of "title". Return the basename
98 * of the page to read and the section it is in. Return 0 on failure, 1 on
99 * success, -1 on EOF or error.
102 static char page
[256], section
[32];
103 static long low
, high
;
109 /* Each windex line should have the format:
110 * title page (section) - descriptive text
111 * The file is sorted.
114 if (ftell(wf
) == 0) {
115 /* First read of this file, initialize. */
117 fseek(wf
, (off_t
) 0, SEEK_END
);
121 /* Binary search for the title. */
122 while (low
<= high
) {
123 pt
= (unsigned char *) title
;
125 mid0
= mid1
= (low
+ high
) >> 1;
127 if (fseek(wf
, (off_t
) 0, SEEK_SET
) != 0)
130 if (fseek(wf
, (off_t
) mid0
- 1, SEEK_SET
) != 0)
133 /* Find the start of a line. */
134 while ((c
= getc(wf
)) != EOF
&& c
!= '\n')
136 if (ferror(wf
)) return -1;
139 /* See if the line has the title we seek. */
141 if ((c
= getc(wf
)) == ' ' || c
== '\t') c
= 0;
142 if (c
== 0 || c
!= *pt
) break;
146 /* Halve the search range. */
147 if (c
== EOF
|| *pt
<= c
) {
154 /* Look for the title from 'low' onwards. */
155 if (fseek(wf
, (off_t
) low
, SEEK_SET
) != 0)
160 /* Find the start of a line. */
161 while ((c
= getc(wf
)) != EOF
&& c
!= '\n')
163 if (ferror(wf
)) return -1;
166 /* See if the line has the title we seek. */
167 pt
= (unsigned char *) title
;
170 if ((c
= getc(wf
)) == EOF
) return 0;
172 if (c
== ' ' || c
== '\t') c
= 0;
173 if (c
== 0 || c
!= *pt
) break;
178 if (*pt
!= c
) return 0; /* Not found. */
180 /* Get page and section. */
181 while ((c
= fgetc(wf
)) == ' ' || c
== '\t') {}
184 while (c
!= ' ' && c
!= '\t' && c
!= '(' && c
!= '\n' && c
!= EOF
) {
185 if (pc
< arraylimit(page
)-1) *pc
++= c
;
188 if (pc
== page
) return 0;
191 while (c
== ' ' || c
== '\t') c
= fgetc(wf
);
193 if (c
!= '(') return 0;
196 while ((c
= fgetc(wf
)) != ')' && c
!= '\n' && c
!= EOF
) {
197 if ('A' <= c
&& c
<= 'Z') c
= c
- 'A' + 'a';
198 if (pc
< arraylimit(section
)-1) *pc
++= c
;
201 if (c
!= ')' || pc
== section
) return 0;
203 while (c
!= EOF
&& c
!= '\n') c
= getc(wf
);
204 if (c
!= '\n') return 0;
211 char ALL
[]= ""; /* Magic sequence of all sections. */
213 int all
= 0; /* Show all pages with a given title. */
214 int whatis
= 0; /* man -f word == whatis word. */
215 int apropos
= 0; /* man -k word == apropos word. */
216 int quiet
= 0; /* man -q == quietly check. */
217 enum ROFF
{ NROFF
, TROFF
} rofftype
= NROFF
;
218 char *roff
[] = { "nroff", "troff" };
220 int shown
; /* True if something has been shown. */
221 int tty
; /* True if displaying on a terminal. */
222 char *manpath
; /* The manual directory path. */
223 char *pager
; /* The pager to use. */
225 char *pipeline
[8][8]; /* An 8 command pipeline of 7 arguments each. */
226 char *(*plast
)[8] = pipeline
;
228 void putinline(char *arg1
, ...)
229 /* Add a command to the pipeline. */
238 while ((*argv
++= va_arg(ap
, char *)) != nil
) {}
242 void execute(int set_mp
, char *file
)
243 /* Execute the pipeline build with putinline(). (This is a lot of work to
244 * avoid a call to system(), but it so much fun to do it right!)
247 char *(*plp
)[8], **argv
;
249 int fd0
, pfd
[2], err
[2];
253 void (*isav
)(int sig
), (*qsav
)(int sig
), (*tsav
)(int sig
);
256 /* Must run this through a pager. */
257 putinline(pager
, (char *) nil
);
259 if (plast
== pipeline
) {
260 /* No commands at all? */
261 putinline("cat", (char *) nil
);
264 /* Add the file as argument to the first command. */
266 while (*argv
!= nil
) argv
++;
270 /* Start the commands. */
272 for (plp
= pipeline
; plp
< plast
; plp
++) {
274 last
= (plp
+1 == plast
);
276 /* Create an error pipe and pipe between this command and the next. */
277 if (pipe(err
) < 0 || (!last
&& pipe(pfd
) < 0)) {
278 fprintf(stderr
, "man: can't create a pipe: %s\n", strerror(errno
));
282 (void) fcntl(err
[1], F_SETFD
, fcntl(err
[1], F_GETFD
) | FD_CLOEXEC
);
284 if ((pid
= fork()) < 0) {
285 fprintf(stderr
, "man: cannot fork: %s\n", strerror(errno
));
291 mp
= malloc((8 + strlen(manpath
) + 1) * sizeof(*mp
));
293 strcpy(mp
, "MANPATH=");
311 execvp(argv
[0], argv
);
312 (void) write(err
[1], &errno
, sizeof(errno
));
317 if (read(err
[0], &errno
, sizeof(errno
)) != 0) {
318 fprintf(stderr
, "man: %s: %s\n", argv
[0],
331 /* Wait for the last command to finish. */
332 isav
= signal(SIGINT
, SIG_IGN
);
333 qsav
= signal(SIGQUIT
, SIG_IGN
);
334 tsav
= signal(SIGTERM
, SIG_IGN
);
335 while ((r
= wait(&status
)) != pid
) {
337 fprintf(stderr
, "man: wait(): %s\n", strerror(errno
));
341 (void) signal(SIGINT
, isav
);
342 (void) signal(SIGQUIT
, qsav
);
343 (void) signal(SIGTERM
, tsav
);
344 if (status
!= 0) exit(1);
348 void keyword(char *keyword
)
349 /* Make an apropos(1) or whatis(1) call. */
351 putinline(apropos
? "apropos" : "whatis",
352 all
? "-a" : (char *) nil
,
356 printf("Looking for keyword '%s'\n", keyword
);
363 enum pagetype
{ CAT
, CATZ
, MAN
, MANZ
, SMAN
, SMANZ
};
365 int showpage(char *page
, enum pagetype ptype
, char *macros
)
366 /* Show a manual page if it exists using the proper decompression and
372 /* We want a normal file without X bits if not a full path. */
373 if (stat(page
, &st
) < 0) return 0;
375 if (!S_ISREG(st
.st_mode
)) return 0;
376 if ((st
.st_mode
& 0111) && page
[0] != '/') return 0;
378 /* Do we only care if it exists? */
379 if (quiet
) { shown
= 1; return 1; }
381 if (ptype
== CATZ
|| ptype
== MANZ
|| ptype
== SMANZ
) {
382 putinline("zcat", (char *) nil
);
385 if (ptype
== SMAN
|| ptype
== SMANZ
) {
386 /* Change SGML into regular *roff. */
387 putinline("/usr/lib/sgml/sgml2roff", (char *) nil
);
388 putinline("tbl", (char *) nil
);
389 putinline("eqn", (char *) nil
);
393 /* Do we need tbl? */
396 char *tp
= TBL_MAGIC
;
398 if ((fp
= fopen(page
, "r")) == nil
) {
399 fprintf(stderr
, "man: %s: %s\n", page
, strerror(errno
));
404 if (c
== *tp
|| (c
== '\'' && *tp
== '.')) {
406 /* A match, add tbl. */
407 putinline("tbl", (char *) nil
);
414 while ((c
= fgetc(fp
)) == ' ' || c
== '\t') {}
419 if (ptype
== MAN
|| ptype
== MANZ
|| ptype
== SMAN
|| ptype
== SMANZ
) {
420 putinline(roff
[rofftype
], macros
, (char *) nil
);
425 ptype
== CAT
|| ptype
== CATZ
? "Showing" : "Formatting", page
);
434 int member(char *word
, char *list
)
435 /* True if word is a member of a comma separated list. */
437 size_t len
= strlen(word
);
439 if (list
== ALL
) return 1;
442 if (strncmp(word
, list
, len
) == 0
443 && (list
[len
] == 0 || list
[len
] == ','))
445 while (*list
!= 0 && *list
!= ',') list
++;
446 if (*list
== ',') list
++;
451 int trymandir(char *mandir
, char *title
, char *section
)
452 /* Search the whatis file of the manual directory for a page of the given
453 * section and display it.
457 char whatis
[1024], pagename
[1024], *wpage
, *wsection
;
460 int (*searchidx
)(FILE *, char *, char **, char **);
465 static struct searchnames searchN
[] = {
466 { CAT
, "%s/cat%s/%s.%s" }, /* SysV */
467 { CATZ
, "%s/cat%s/%s.%s.Z" },
468 { MAN
, "%s/man%s/%s.%s" },
469 { MANZ
, "%s/man%s/%s.%s.Z" },
470 { SMAN
, "%s/sman%s/%s.%s" }, /* Solaris */
471 { SMANZ
,"%s/sman%s/%s.%s.Z" },
472 { CAT
, "%s/cat%.1s/%s.%s" }, /* BSD */
473 { CATZ
, "%s/cat%.1s/%s.%s.Z" },
474 { MAN
, "%s/man%.1s/%s.%s" },
475 { MANZ
, "%s/man%.1s/%s.%s.Z" },
478 if (strlen(mandir
) + 1 + 6 + 1 > arraysize(whatis
)) return 0;
480 /* Prefer a fast windex database if available. */
481 sprintf(whatis
, "%s/windex", mandir
);
483 if ((wf
= fopen(whatis
, "r")) != nil
) {
484 searchidx
= searchwindex
;
486 /* Use a classic whatis database. */
487 sprintf(whatis
, "%s/whatis", mandir
);
489 if ((wf
= fopen(whatis
, "r")) == nil
) return 0;
490 searchidx
= searchwhatis
;
494 while (!rsp
&& (rsw
= (*searchidx
)(wf
, title
, &wpage
, &wsection
)) == 1) {
495 if (!member(wsection
, section
)) continue;
497 /* When looking for getc(1S) we try:
510 if (strlen(mandir
) + 2 * strlen(wsection
) + strlen(wpage
)
511 + 10 > arraysize(pagename
))
515 ntries
= arraysize(searchN
);
517 if (sp
->ptype
<= CATZ
&& rofftype
!= NROFF
)
520 sprintf(pagename
, sp
->pathfmt
,
521 mandir
, wsection
, wpage
, wsection
);
523 rsp
= showpage(pagename
, sp
->ptype
,
524 (SEC1xSPECIAL
&& strcmp(wsection
, "1x") == 0) ? "-mnx" : "-man");
525 } while (sp
++, !rsp
&& --ntries
!= 0);
529 if (rsw
< 0 && ferror(wf
)) {
530 fprintf(stderr
, "man: %s: %s\n", whatis
, strerror(errno
));
537 int trysubmandir(char *mandir
, char *title
, char *section
)
538 /* Search the subdirectories of this manual directory for whatis files, they
539 * may have manual pages that override the ones in the major directory.
542 char submandir
[1024];
544 struct dirent
*entry
;
546 if ((md
= opendir(mandir
)) == nil
) return 0;
548 while ((entry
= readdir(md
)) != nil
) {
549 if (strcmp(entry
->d_name
, ".") == 0
550 || strcmp(entry
->d_name
, "..") == 0) continue;
551 if ((strncmp(entry
->d_name
, "man", 3) == 0
552 || strncmp(entry
->d_name
, "cat", 3) == 0)
553 && between('0', entry
->d_name
[3], '9')) continue;
555 if (strlen(mandir
) + 1 + strlen(entry
->d_name
) + 1
556 > arraysize(submandir
)) continue;
558 sprintf(submandir
, "%s/%s", mandir
, entry
->d_name
);
560 if (trymandir(submandir
, title
, section
) && !all
) {
570 void searchmanpath(char *title
, char *section
)
571 /* Search the manual path for a manual page describing "title." */
574 char *pp
= manpath
, *pd
;
577 while (*pp
!= 0 && *pp
== ':') pp
++;
582 while (*pp
!= 0 && *pp
!= ':') {
583 if (pd
< arraylimit(mandir
)) *pd
++= *pp
;
586 if (pd
== arraylimit(mandir
)) continue; /* forget it */
589 if (trysubmandir(mandir
, title
, section
) && !all
) break;
590 if (trymandir(mandir
, title
, section
) && !all
) break;
596 fprintf(stderr
, "Usage: man -[antfkq] [-M path] [-s section] title ...\n");
600 int main(int argc
, char **argv
)
602 char *title
, *section
= ALL
;
607 if ((pager
= getenv("PAGER")) == nil
) pager
= PAGER
;
608 if ((manpath
= getenv("MANPATH")) == nil
) manpath
= MANPATH
;
613 while (i
< argc
&& argv
[i
][0] == '-' && !nomoreopt
) {
615 if (opt
[0] == '-' && opt
[1] == 0) {
643 if (i
== argc
) usage();
652 if (i
== argc
) usage();
665 if (i
>= argc
) usage();
667 if (between('0', argv
[i
][0], '9') && i
+1 < argc
) {
668 /* Old BSD style section designation? */
671 if (i
== argc
) usage();
675 if (whatis
|| apropos
) {
679 searchmanpath(title
, section
);
681 if (!shown
) (void) showpage(title
, MAN
, "-man");
686 "man: no manual on %s\n",