source: trunk/minix/commands/simple/man.c@ 20

Last change on this file since 20 was 9, checked in by Mattia Monga, 14 years ago

Minix 3.1.2a

File size: 15.5 KB
RevLine 
[9]1/* man 2.4 - display online manual pages Author: Kees J. Bot
2 * 17 Mar 1993
3 */
4#define nil NULL
5#include <sys/types.h>
6#include <stdio.h>
7#include <stdlib.h>
8#include <dirent.h>
9#include <string.h>
10#include <errno.h>
11#include <unistd.h>
12#include <stdarg.h>
13#include <fcntl.h>
14#include <signal.h>
15#include <sys/stat.h>
16#include <sys/wait.h>
17
18/* Defaults: */
19char MANPATH[]= "/usr/local/man:/usr/man:/usr/gnu/man";
20char PAGER[]= "more";
21
22/* Comment at the start to let tbl(1) be run before n/troff. */
23char TBL_MAGIC[] = ".\\\"t\n";
24
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)))
28
29/* Section 9 uses special macros under Minix. */
30#if __minix
31#define SEC9SPECIAL 1
32#else
33#define SEC9SPECIAL 0
34#endif
35
36int 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.
40 */
41{
42 static char page[256], section[32];
43 char alias[256];
44 int found= 0;
45 int c;
46
47 /* Each whatis line should have the format:
48 * page, title, title (section) - descriptive text
49 */
50
51 /* Search the file for a line with the title. */
52 do {
53 int first= 1;
54 char *pc= section;
55
56 c= fgetc(wf);
57
58 /* Search the line for the title. */
59 do {
60 char *pa= alias;
61
62 while (c == ' ' || c == '\t' || c == ',') c= fgetc(wf);
63
64 while (c != ' ' && c != '\t' && c != ','
65 && c != '(' && c != '\n' && c != EOF
66 ) {
67 if (pa < arraylimit(alias)-1) *pa++= c;
68 c= getc(wf);
69 }
70 *pa= 0;
71 if (first) { strcpy(page, alias); first= 0; }
72
73 if (strcmp(alias, title) == 0) found= 1;
74 } while (c != '(' && c != '\n' && c != EOF);
75
76 if (c != '(') {
77 found= 0;
78 } else {
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;
82 }
83 *pc= 0;
84 if (c != ')' || pc == section) found= 0;
85 }
86 while (c != EOF && c != '\n') c= getc(wf);
87 } while (!found && c != EOF);
88
89 if (found) {
90 *ppage= page;
91 *psection= section;
92 }
93 return c == EOF ? -1 : found;
94}
95
96int 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.
100 */
101{
102 static char page[256], section[32];
103 static long low, high;
104 long mid0, mid1;
105 int c;
106 unsigned char *pt;
107 char *pc;
108
109 /* Each windex line should have the format:
110 * title page (section) - descriptive text
111 * The file is sorted.
112 */
113
114 if (ftell(wf) == 0) {
115 /* First read of this file, initialize. */
116 low= 0;
117 fseek(wf, (off_t) 0, SEEK_END);
118 high= ftell(wf);
119 }
120
121 /* Binary search for the title. */
122 while (low <= high) {
123 pt= (unsigned char *) title;
124
125 mid0= mid1= (low + high) >> 1;
126 if (mid0 == 0) {
127 if (fseek(wf, (off_t) 0, SEEK_SET) != 0)
128 return -1;
129 } else {
130 if (fseek(wf, (off_t) mid0 - 1, SEEK_SET) != 0)
131 return -1;
132
133 /* Find the start of a line. */
134 while ((c= getc(wf)) != EOF && c != '\n')
135 mid1++;
136 if (ferror(wf)) return -1;
137 }
138
139 /* See if the line has the title we seek. */
140 for (;;) {
141 if ((c= getc(wf)) == ' ' || c == '\t') c= 0;
142 if (c == 0 || c != *pt) break;
143 pt++;
144 }
145
146 /* Halve the search range. */
147 if (c == EOF || *pt <= c) {
148 high= mid0 - 1;
149 } else {
150 low= mid1 + 1;
151 }
152 }
153
154 /* Look for the title from 'low' onwards. */
155 if (fseek(wf, (off_t) low, SEEK_SET) != 0)
156 return -1;
157
158 do {
159 if (low != 0) {
160 /* Find the start of a line. */
161 while ((c= getc(wf)) != EOF && c != '\n')
162 low++;
163 if (ferror(wf)) return -1;
164 }
165
166 /* See if the line has the title we seek. */
167 pt= (unsigned char *) title;
168
169 for (;;) {
170 if ((c= getc(wf)) == EOF) return 0;
171 low++;
172 if (c == ' ' || c == '\t') c= 0;
173 if (c == 0 || c != *pt) break;
174 pt++;
175 }
176 } while (c < *pt);
177
178 if (*pt != c) return 0; /* Not found. */
179
180 /* Get page and section. */
181 while ((c= fgetc(wf)) == ' ' || c == '\t') {}
182
183 pc= page;
184 while (c != ' ' && c != '\t' && c != '(' && c != '\n' && c != EOF) {
185 if (pc < arraylimit(page)-1) *pc++= c;
186 c= getc(wf);
187 }
188 if (pc == page) return 0;
189 *pc= 0;
190
191 while (c == ' ' || c == '\t') c= fgetc(wf);
192
193 if (c != '(') return 0;
194
195 pc= section;
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;
199 }
200 *pc= 0;
201 if (c != ')' || pc == section) return 0;
202
203 while (c != EOF && c != '\n') c= getc(wf);
204 if (c != '\n') return 0;
205
206 *ppage= page;
207 *psection= section;
208 return 1;
209}
210
211char ALL[]= ""; /* Magic sequence of all sections. */
212
213int all= 0; /* Show all pages with a given title. */
214int whatis= 0; /* man -f word == whatis word. */
215int apropos= 0; /* man -k word == apropos word. */
216int quiet= 0; /* man -q == quietly check. */
217enum ROFF { NROFF, TROFF } rofftype= NROFF;
218char *roff[] = { "nroff", "troff" };
219
220int shown; /* True if something has been shown. */
221int tty; /* True if displaying on a terminal. */
222char *manpath; /* The manual directory path. */
223char *pager; /* The pager to use. */
224
225char *pipeline[8][8]; /* An 8 command pipeline of 7 arguments each. */
226char *(*plast)[8] = pipeline;
227
228void putinline(char *arg1, ...)
229/* Add a command to the pipeline. */
230{
231 va_list ap;
232 char **argv;
233
234 argv= *plast++;
235 *argv++= arg1;
236
237 va_start(ap, arg1);
238 while ((*argv++= va_arg(ap, char *)) != nil) {}
239 va_end(ap);
240}
241
242void 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!)
245 */
246{
247 char *(*plp)[8], **argv;
248 char *mp;
249 int fd0, pfd[2], err[2];
250 pid_t pid;
251 int r, status;
252 int last;
253 void (*isav)(int sig), (*qsav)(int sig), (*tsav)(int sig);
254
255 if (tty) {
256 /* Must run this through a pager. */
257 putinline(pager, (char *) nil);
258 }
259 if (plast == pipeline) {
260 /* No commands at all? */
261 putinline("cat", (char *) nil);
262 }
263
264 /* Add the file as argument to the first command. */
265 argv= pipeline[0];
266 while (*argv != nil) argv++;
267 *argv++= file;
268 *argv= nil;
269
270 /* Start the commands. */
271 fd0= 0;
272 for (plp= pipeline; plp < plast; plp++) {
273 argv= *plp;
274 last= (plp+1 == plast);
275
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));
279 exit(1);
280 }
281
282 (void) fcntl(err[1], F_SETFD, fcntl(err[1], F_GETFD) | FD_CLOEXEC);
283
284 if ((pid = fork()) < 0) {
285 fprintf(stderr, "man: cannot fork: %s\n", strerror(errno));
286 exit(1);
287 }
288 if (pid == 0) {
289 /* Child. */
290 if (set_mp) {
291 mp= malloc((8 + strlen(manpath) + 1) * sizeof(*mp));
292 if (mp != nil) {
293 strcpy(mp, "MANPATH=");
294 strcat(mp, manpath);
295 (void) putenv(mp);
296 }
297 }
298
299 if (fd0 != 0) {
300 dup2(fd0, 0);
301 close(fd0);
302 }
303 if (!last) {
304 close(pfd[0]);
305 if (pfd[1] != 1) {
306 dup2(pfd[1], 1);
307 close(pfd[1]);
308 }
309 }
310 close(err[0]);
311 execvp(argv[0], argv);
312 (void) write(err[1], &errno, sizeof(errno));
313 _exit(1);
314 }
315
316 close(err[1]);
317 if (read(err[0], &errno, sizeof(errno)) != 0) {
318 fprintf(stderr, "man: %s: %s\n", argv[0],
319 strerror(errno));
320 exit(1);
321 }
322 close(err[0]);
323
324 if (!last) {
325 close(pfd[1]);
326 fd0= pfd[0];
327 }
328 set_mp= 0;
329 }
330
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) {
336 if (r < 0) {
337 fprintf(stderr, "man: wait(): %s\n", strerror(errno));
338 exit(1);
339 }
340 }
341 (void) signal(SIGINT, isav);
342 (void) signal(SIGQUIT, qsav);
343 (void) signal(SIGTERM, tsav);
344 if (status != 0) exit(1);
345 plast= pipeline;
346}
347
348void keyword(char *keyword)
349/* Make an apropos(1) or whatis(1) call. */
350{
351 putinline(apropos ? "apropos" : "whatis",
352 all ? "-a" : (char *) nil,
353 (char *) nil);
354
355 if (tty) {
356 printf("Looking for keyword '%s'\n", keyword);
357 fflush(stdout);
358 }
359
360 execute(1, keyword);
361}
362
363enum pagetype { CAT, CATZ, MAN, MANZ, SMAN, SMANZ };
364
365int showpage(char *page, enum pagetype ptype, char *macros)
366/* Show a manual page if it exists using the proper decompression and
367 * formatting tools.
368 */
369{
370 struct stat st;
371
372 /* We want a normal file without X bits if not a full path. */
373 if (stat(page, &st) < 0) return 0;
374
375 if (!S_ISREG(st.st_mode)) return 0;
376 if ((st.st_mode & 0111) && page[0] != '/') return 0;
377
378 /* Do we only care if it exists? */
379 if (quiet) { shown= 1; return 1; }
380
381 if (ptype == CATZ || ptype == MANZ || ptype == SMANZ) {
382 putinline("zcat", (char *) nil);
383 }
384
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);
390 }
391
392 if (ptype == MAN) {
393 /* Do we need tbl? */
394 FILE *fp;
395 int c;
396 char *tp = TBL_MAGIC;
397
398 if ((fp = fopen(page, "r")) == nil) {
399 fprintf(stderr, "man: %s: %s\n", page, strerror(errno));
400 exit(1);
401 }
402 c= fgetc(fp);
403 for (;;) {
404 if (c == *tp || (c == '\'' && *tp == '.')) {
405 if (*++tp == 0) {
406 /* A match, add tbl. */
407 putinline("tbl", (char *) nil);
408 break;
409 }
410 } else {
411 /* No match. */
412 break;
413 }
414 while ((c = fgetc(fp)) == ' ' || c == '\t') {}
415 }
416 fclose(fp);
417 }
418
419 if (ptype == MAN || ptype == MANZ || ptype == SMAN || ptype == SMANZ) {
420 putinline(roff[rofftype], macros, (char *) nil);
421 }
422
423 if (tty) {
424 printf("%s %s\n",
425 ptype == CAT || ptype == CATZ ? "Showing" : "Formatting", page);
426 fflush(stdout);
427 }
428 execute(0, page);
429
430 shown= 1;
431 return 1;
432}
433
434int member(char *word, char *list)
435/* True if word is a member of a comma separated list. */
436{
437 size_t len= strlen(word);
438
439 if (list == ALL) return 1;
440
441 while (*list != 0) {
442 if (strncmp(word, list, len) == 0
443 && (list[len] == 0 || list[len] == ','))
444 return 1;
445 while (*list != 0 && *list != ',') list++;
446 if (*list == ',') list++;
447 }
448 return 0;
449}
450
451int 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.
454 */
455{
456 FILE *wf;
457 char whatis[1024], pagename[1024], *wpage, *wsection;
458 int rsw, rsp;
459 int ntries;
460 int (*searchidx)(FILE *, char *, char **, char **);
461 struct searchnames {
462 enum pagetype ptype;
463 char *pathfmt;
464 } *sp;
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" },
476 };
477
478 if (strlen(mandir) + 1 + 6 + 1 > arraysize(whatis)) return 0;
479
480 /* Prefer a fast windex database if available. */
481 sprintf(whatis, "%s/windex", mandir);
482
483 if ((wf= fopen(whatis, "r")) != nil) {
484 searchidx= searchwindex;
485 } else {
486 /* Use a classic whatis database. */
487 sprintf(whatis, "%s/whatis", mandir);
488
489 if ((wf= fopen(whatis, "r")) == nil) return 0;
490 searchidx= searchwhatis;
491 }
492
493 rsp= 0;
494 while (!rsp && (rsw= (*searchidx)(wf, title, &wpage, &wsection)) == 1) {
495 if (!member(wsection, section)) continue;
496
497 /* When looking for getc(1S) we try:
498 * cat1s/getc.1s
499 * cat1s/getc.1s.Z
500 * man1s/getc.1s
501 * man1s/getc.1s.Z
502 * sman1s/getc.1s
503 * sman1s/getc.1s.Z
504 * cat1/getc.1s
505 * cat1/getc.1s.Z
506 * man1/getc.1s
507 * man1/getc.1s.Z
508 */
509
510 if (strlen(mandir) + 2 * strlen(wsection) + strlen(wpage)
511 + 10 > arraysize(pagename))
512 continue;
513
514 sp= searchN;
515 ntries= arraysize(searchN);
516 do {
517 if (sp->ptype <= CATZ && rofftype != NROFF)
518 continue;
519
520 sprintf(pagename, sp->pathfmt,
521 mandir, wsection, wpage, wsection);
522
523 rsp= showpage(pagename, sp->ptype,
524 (SEC9SPECIAL && strcmp(wsection, "9") == 0) ? "-mnx" : "-man");
525 } while (sp++, !rsp && --ntries != 0);
526
527 if (all) rsp= 0;
528 }
529 if (rsw < 0 && ferror(wf)) {
530 fprintf(stderr, "man: %s: %s\n", whatis, strerror(errno));
531 exit(1);
532 }
533 fclose(wf);
534 return rsp;
535}
536
537int 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.
540 */
541{
542 char submandir[1024];
543 DIR *md;
544 struct dirent *entry;
545
546 if ((md= opendir(mandir)) == nil) return 0;
547
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;
554
555 if (strlen(mandir) + 1 + strlen(entry->d_name) + 1
556 > arraysize(submandir)) continue;
557
558 sprintf(submandir, "%s/%s", mandir, entry->d_name);
559
560 if (trymandir(submandir, title, section) && !all) {
561 closedir(md);
562 return 1;
563 }
564 }
565 closedir(md);
566
567 return 0;
568}
569
570void searchmanpath(char *title, char *section)
571/* Search the manual path for a manual page describing "title." */
572{
573 char mandir[1024];
574 char *pp= manpath, *pd;
575
576 for (;;) {
577 while (*pp != 0 && *pp == ':') pp++;
578
579 if (*pp == 0) break;
580
581 pd= mandir;
582 while (*pp != 0 && *pp != ':') {
583 if (pd < arraylimit(mandir)) *pd++= *pp;
584 pp++;
585 }
586 if (pd == arraylimit(mandir)) continue; /* forget it */
587
588 *pd= 0;
589 if (trysubmandir(mandir, title, section) && !all) break;
590 if (trymandir(mandir, title, section) && !all) break;
591 }
592}
593
594void usage(void)
595{
596 fprintf(stderr, "Usage: man -[antfkq] [-M path] [-s section] title ...\n");
597 exit(1);
598}
599
600int main(int argc, char **argv)
601{
602 char *title, *section= ALL;
603 int i;
604 int nomoreopt= 0;
605 char *opt;
606
607 if ((pager= getenv("PAGER")) == nil) pager= PAGER;
608 if ((manpath= getenv("MANPATH")) == nil) manpath= MANPATH;
609 tty= isatty(1);
610
611 i= 1;
612 do {
613 while (i < argc && argv[i][0] == '-' && !nomoreopt) {
614 opt= argv[i++]+1;
615 if (opt[0] == '-' && opt[1] == 0) {
616 nomoreopt= 1;
617 break;
618 }
619 while (*opt != 0) {
620 switch (*opt++) {
621 case 'a':
622 all= 1;
623 break;
624 case 'f':
625 whatis= 1;
626 break;
627 case 'k':
628 apropos= 1;
629 break;
630 case 'q':
631 quiet= 1;
632 break;
633 case 'n':
634 rofftype= NROFF;
635 apropos= whatis= 0;
636 break;
637 case 't':
638 rofftype= TROFF;
639 apropos= whatis= 0;
640 break;
641 case 's':
642 if (*opt == 0) {
643 if (i == argc) usage();
644 section= argv[i++];
645 } else {
646 section= opt;
647 opt= "";
648 }
649 break;
650 case 'M':
651 if (*opt == 0) {
652 if (i == argc) usage();
653 manpath= argv[i++];
654 } else {
655 manpath= opt;
656 opt= "";
657 }
658 break;
659 default:
660 usage();
661 }
662 }
663 }
664
665 if (i >= argc) usage();
666
667 if (between('0', argv[i][0], '9') && argv[i][1] == 0) {
668 /* Allow single digit section designations. */
669 section= argv[i++];
670 }
671 if (i == argc) usage();
672
673 title= argv[i++];
674
675 if (whatis || apropos) {
676 keyword(title);
677 } else {
678 shown= 0;
679 searchmanpath(title, section);
680
681 if (!shown) (void) showpage(title, MAN, "-man");
682
683 if (!shown) {
684 if (!quiet) {
685 fprintf(stderr,
686 "man: no manual on %s\n",
687 title);
688 }
689 exit(1);
690 }
691 }
692 } while (i < argc);
693
694 return 0;
695}
Note: See TracBrowser for help on using the repository browser.