]> git.cameronkatri.com Git - mandoc.git/blob - makewhatis.c
Added search for `Ev' environment variables in ENVIRONMENT section.
[mandoc.git] / makewhatis.c
1 /* $Id: makewhatis.c,v 1.15 2011/07/01 12:02:44 kristaps Exp $ */
2 /*
3 * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20
21 #include <sys/param.h>
22
23 #include <assert.h>
24 #include <fcntl.h>
25 #include <getopt.h>
26 #include <stdio.h>
27 #include <stdint.h>
28 #include <stdlib.h>
29 #include <string.h>
30
31 #ifdef __linux__
32 # include <db_185.h>
33 #else
34 # include <db.h>
35 #endif
36
37 #include "man.h"
38 #include "mdoc.h"
39 #include "mandoc.h"
40
41 #define MANDOC_DB "mandoc.db"
42 #define MANDOC_IDX "mandoc.index"
43 #define MANDOC_BUFSZ BUFSIZ
44 #define MANDOC_FLAGS O_CREAT|O_TRUNC|O_RDWR
45
46 /* Bit-fields. See makewhatis.1. */
47
48 #define TYPE_NAME 0x01
49 #define TYPE_FUNCTION 0x02
50 #define TYPE_UTILITY 0x04
51 #define TYPE_INCLUDES 0x08
52 #define TYPE_VARIABLE 0x10
53 #define TYPE_STANDARD 0x20
54 #define TYPE_AUTHOR 0x40
55 #define TYPE_CONFIG 0x80
56 #define TYPE_DESC 0x100
57 #define TYPE_XREF 0x200
58 #define TYPE_PATH 0x400
59 #define TYPE_ENV 0x800
60
61 /* Buffer for storing growable data. */
62
63 struct buf {
64 char *cp;
65 size_t len;
66 size_t size;
67 };
68
69 #define MAN_ARGS DB *hash, \
70 struct buf *buf, \
71 struct buf *dbuf, \
72 const struct man_node *n
73 #define MDOC_ARGS DB *hash, \
74 struct buf *buf, \
75 struct buf *dbuf, \
76 const struct mdoc_node *n, \
77 const struct mdoc_meta *m
78
79 static void buf_appendmdoc(struct buf *,
80 const struct mdoc_node *, int);
81 static void buf_append(struct buf *, const char *);
82 static void buf_appendb(struct buf *,
83 const void *, size_t);
84 static void dbt_put(DB *, const char *, DBT *, DBT *);
85 static void hash_put(DB *, const struct buf *, int);
86 static int pman_node(MAN_ARGS);
87 static void pmdoc_node(MDOC_ARGS);
88 static void pmdoc_An(MDOC_ARGS);
89 static void pmdoc_Cd(MDOC_ARGS);
90 static void pmdoc_Ev(MDOC_ARGS);
91 static void pmdoc_Fd(MDOC_ARGS);
92 static void pmdoc_In(MDOC_ARGS);
93 static void pmdoc_Fn(MDOC_ARGS);
94 static void pmdoc_Fo(MDOC_ARGS);
95 static void pmdoc_Nd(MDOC_ARGS);
96 static void pmdoc_Nm(MDOC_ARGS);
97 static void pmdoc_Pa(MDOC_ARGS);
98 static void pmdoc_St(MDOC_ARGS);
99 static void pmdoc_Vt(MDOC_ARGS);
100 static void pmdoc_Xr(MDOC_ARGS);
101 static void usage(void);
102
103 typedef void (*pmdoc_nf)(MDOC_ARGS);
104
105 static const pmdoc_nf mdocs[MDOC_MAX] = {
106 NULL, /* Ap */
107 NULL, /* Dd */
108 NULL, /* Dt */
109 NULL, /* Os */
110 NULL, /* Sh */
111 NULL, /* Ss */
112 NULL, /* Pp */
113 NULL, /* D1 */
114 NULL, /* Dl */
115 NULL, /* Bd */
116 NULL, /* Ed */
117 NULL, /* Bl */
118 NULL, /* El */
119 NULL, /* It */
120 NULL, /* Ad */
121 pmdoc_An, /* An */
122 NULL, /* Ar */
123 pmdoc_Cd, /* Cd */
124 NULL, /* Cm */
125 NULL, /* Dv */
126 NULL, /* Er */
127 pmdoc_Ev, /* Ev */
128 NULL, /* Ex */
129 NULL, /* Fa */
130 pmdoc_Fd, /* Fd */
131 NULL, /* Fl */
132 pmdoc_Fn, /* Fn */
133 NULL, /* Ft */
134 NULL, /* Ic */
135 pmdoc_In, /* In */
136 NULL, /* Li */
137 pmdoc_Nd, /* Nd */
138 pmdoc_Nm, /* Nm */
139 NULL, /* Op */
140 NULL, /* Ot */
141 pmdoc_Pa, /* Pa */
142 NULL, /* Rv */
143 pmdoc_St, /* St */
144 pmdoc_Vt, /* Va */
145 pmdoc_Vt, /* Vt */
146 pmdoc_Xr, /* Xr */
147 NULL, /* %A */
148 NULL, /* %B */
149 NULL, /* %D */
150 NULL, /* %I */
151 NULL, /* %J */
152 NULL, /* %N */
153 NULL, /* %O */
154 NULL, /* %P */
155 NULL, /* %R */
156 NULL, /* %T */
157 NULL, /* %V */
158 NULL, /* Ac */
159 NULL, /* Ao */
160 NULL, /* Aq */
161 NULL, /* At */
162 NULL, /* Bc */
163 NULL, /* Bf */
164 NULL, /* Bo */
165 NULL, /* Bq */
166 NULL, /* Bsx */
167 NULL, /* Bx */
168 NULL, /* Db */
169 NULL, /* Dc */
170 NULL, /* Do */
171 NULL, /* Dq */
172 NULL, /* Ec */
173 NULL, /* Ef */
174 NULL, /* Em */
175 NULL, /* Eo */
176 NULL, /* Fx */
177 NULL, /* Ms */
178 NULL, /* No */
179 NULL, /* Ns */
180 NULL, /* Nx */
181 NULL, /* Ox */
182 NULL, /* Pc */
183 NULL, /* Pf */
184 NULL, /* Po */
185 NULL, /* Pq */
186 NULL, /* Qc */
187 NULL, /* Ql */
188 NULL, /* Qo */
189 NULL, /* Qq */
190 NULL, /* Re */
191 NULL, /* Rs */
192 NULL, /* Sc */
193 NULL, /* So */
194 NULL, /* Sq */
195 NULL, /* Sm */
196 NULL, /* Sx */
197 NULL, /* Sy */
198 NULL, /* Tn */
199 NULL, /* Ux */
200 NULL, /* Xc */
201 NULL, /* Xo */
202 pmdoc_Fo, /* Fo */
203 NULL, /* Fc */
204 NULL, /* Oo */
205 NULL, /* Oc */
206 NULL, /* Bk */
207 NULL, /* Ek */
208 NULL, /* Bt */
209 NULL, /* Hf */
210 NULL, /* Fr */
211 NULL, /* Ud */
212 NULL, /* Lb */
213 NULL, /* Lp */
214 NULL, /* Lk */
215 NULL, /* Mt */
216 NULL, /* Brq */
217 NULL, /* Bro */
218 NULL, /* Brc */
219 NULL, /* %C */
220 NULL, /* Es */
221 NULL, /* En */
222 NULL, /* Dx */
223 NULL, /* %Q */
224 NULL, /* br */
225 NULL, /* sp */
226 NULL, /* %U */
227 NULL, /* Ta */
228 };
229
230 static const char *progname;
231
232 int
233 main(int argc, char *argv[])
234 {
235 struct mparse *mp; /* parse sequence */
236 struct mdoc *mdoc; /* resulting mdoc */
237 struct man *man; /* resulting man */
238 char *fn; /* current file being parsed */
239 const char *msec, /* manual section */
240 *mtitle, /* manual title */
241 *arch, /* manual architecture */
242 *dir; /* result dir (default: cwd) */
243 char ibuf[MAXPATHLEN], /* index fname */
244 ibbuf[MAXPATHLEN], /* index backup fname */
245 fbuf[MAXPATHLEN], /* btree fname */
246 fbbuf[MAXPATHLEN], /* btree backup fname */
247 vbuf[8]; /* stringified record number */
248 int ch, seq, verb;
249 DB *idx, /* index database */
250 *db, /* keyword database */
251 *hash; /* temporary keyword hashtable */
252 DBT key, val;
253 size_t sv;
254 BTREEINFO info; /* btree configuration */
255 recno_t rec; /* current record number */
256 struct buf buf, /* keyword buffer */
257 dbuf; /* description buffer */
258 extern int optind;
259 extern char *optarg;
260
261 progname = strrchr(argv[0], '/');
262 if (progname == NULL)
263 progname = argv[0];
264 else
265 ++progname;
266
267 dir = "";
268 verb = 0;
269
270 while (-1 != (ch = getopt(argc, argv, "d:v")))
271 switch (ch) {
272 case ('d'):
273 dir = optarg;
274 break;
275 case ('v'):
276 verb++;
277 break;
278 default:
279 usage();
280 return((int)MANDOCLEVEL_BADARG);
281 }
282
283 argc -= optind;
284 argv += optind;
285
286 /*
287 * Set up temporary file-names into which we're going to write
288 * all of our data (both for the index and database). These
289 * will be securely renamed to the real file-names after we've
290 * written all of our data.
291 */
292
293 ibuf[0] = ibuf[MAXPATHLEN - 2] =
294 ibbuf[0] = ibbuf[MAXPATHLEN - 2] =
295 fbuf[0] = fbuf[MAXPATHLEN - 2] =
296 fbbuf[0] = fbbuf[MAXPATHLEN - 2] = '\0';
297
298 strlcat(fbuf, dir, MAXPATHLEN);
299 strlcat(fbuf, MANDOC_DB, MAXPATHLEN);
300
301 strlcat(fbbuf, fbuf, MAXPATHLEN);
302 strlcat(fbbuf, "~", MAXPATHLEN);
303
304 strlcat(ibuf, dir, MAXPATHLEN);
305 strlcat(ibuf, MANDOC_IDX, MAXPATHLEN);
306
307 strlcat(ibbuf, ibuf, MAXPATHLEN);
308 strlcat(ibbuf, "~", MAXPATHLEN);
309
310 if ('\0' != fbuf[MAXPATHLEN - 2] ||
311 '\0' != fbbuf[MAXPATHLEN - 2] ||
312 '\0' != ibuf[MAXPATHLEN - 2] ||
313 '\0' != ibbuf[MAXPATHLEN - 2]) {
314 fprintf(stderr, "%s: Path too long\n", dir);
315 exit((int)MANDOCLEVEL_SYSERR);
316 }
317
318 /*
319 * For the keyword database, open a BTREE database that allows
320 * duplicates.
321 * For the index database, use a standard RECNO database type.
322 */
323
324 memset(&info, 0, sizeof(BTREEINFO));
325 info.flags = R_DUP;
326 db = dbopen(fbbuf, MANDOC_FLAGS, 0644, DB_BTREE, &info);
327
328 if (NULL == db) {
329 perror(fbbuf);
330 exit((int)MANDOCLEVEL_SYSERR);
331 }
332
333 idx = dbopen(ibbuf, MANDOC_FLAGS, 0644, DB_RECNO, NULL);
334
335 if (NULL == db) {
336 perror(ibbuf);
337 (*db->close)(db);
338 exit((int)MANDOCLEVEL_SYSERR);
339 }
340
341 /*
342 * Try parsing each manual given on the command line.
343 * If we fail, then emit an error and keep on going.
344 * Take resulting trees and push them down into the database code.
345 * Use the auto-parser and don't report any errors.
346 */
347
348 mp = mparse_alloc(MPARSE_AUTO, MANDOCLEVEL_FATAL, NULL, NULL);
349
350 rec = 1;
351 hash = NULL;
352
353 memset(&buf, 0, sizeof(struct buf));
354 memset(&dbuf, 0, sizeof(struct buf));
355
356 buf.size = dbuf.size = MANDOC_BUFSZ;
357
358 buf.cp = mandoc_malloc(buf.size);
359 dbuf.cp = mandoc_malloc(dbuf.size);
360
361 while (NULL != (fn = *argv++)) {
362 mparse_reset(mp);
363
364 /* Initialise the in-memory hash of keywords. */
365
366 if (hash)
367 (*hash->close)(hash);
368
369 hash = dbopen(NULL, MANDOC_FLAGS, 0644, DB_HASH, NULL);
370
371 if (NULL == hash) {
372 perror("hash");
373 exit((int)MANDOCLEVEL_SYSERR);
374 }
375
376 /* Parse and get (non-empty) AST. */
377
378 if (mparse_readfd(mp, -1, fn) >= MANDOCLEVEL_FATAL) {
379 fprintf(stderr, "%s: Parse failure\n", fn);
380 continue;
381 }
382
383 mparse_result(mp, &mdoc, &man);
384
385 if (NULL == mdoc && NULL == man)
386 continue;
387
388 msec = NULL != mdoc ?
389 mdoc_meta(mdoc)->msec :
390 man_meta(man)->msec;
391 mtitle = NULL != mdoc ?
392 mdoc_meta(mdoc)->title :
393 man_meta(man)->title;
394 arch = NULL != mdoc ? mdoc_meta(mdoc)->arch : NULL;
395
396 /*
397 * The index record value consists of a nil-terminated
398 * filename, a nil-terminated manual section, and a
399 * nil-terminated description. Since the description
400 * may not be set, we set a sentinel to see if we're
401 * going to write a nil byte in its place.
402 */
403
404 dbuf.len = 0;
405 buf_appendb(&dbuf, fn, strlen(fn) + 1);
406 buf_appendb(&dbuf, msec, strlen(msec) + 1);
407 buf_appendb(&dbuf, mtitle, strlen(mtitle) + 1);
408 buf_appendb(&dbuf, arch ? arch : "",
409 arch ? strlen(arch) + 1 : 1);
410
411 sv = dbuf.len;
412
413 /* Fix the record number in the btree value. */
414
415 if (mdoc)
416 pmdoc_node(hash, &buf, &dbuf,
417 mdoc_node(mdoc), mdoc_meta(mdoc));
418 else
419 pman_node(hash, &buf, &dbuf, man_node(man));
420
421 /*
422 * Copy from the in-memory hashtable of pending keywords
423 * into the database.
424 */
425
426 memset(vbuf, 0, sizeof(uint32_t));
427 memcpy(vbuf + 4, &rec, sizeof(uint32_t));
428
429 seq = R_FIRST;
430 while (0 == (ch = (*hash->seq)(hash, &key, &val, seq))) {
431 seq = R_NEXT;
432
433 memcpy(vbuf, val.data, sizeof(uint32_t));
434 val.size = sizeof(vbuf);
435 val.data = vbuf;
436
437 if (verb > 1)
438 printf("%s: Keyword %s (%zu): 0x%x\n",
439 fn, (char *)key.data, key.size,
440 *(int *)val.data);
441
442 dbt_put(db, fbbuf, &key, &val);
443
444 }
445
446 if (ch < 0) {
447 perror("hash");
448 exit((int)MANDOCLEVEL_SYSERR);
449 }
450
451 /*
452 * Apply to the index. If we haven't had a description
453 * set, put an empty one in now.
454 */
455
456 if (dbuf.len == sv)
457 buf_appendb(&dbuf, "", 1);
458
459 key.data = &rec;
460 key.size = sizeof(recno_t);
461
462 val.data = dbuf.cp;
463 val.size = dbuf.len;
464
465 if (verb > 0)
466 printf("%s: Indexed\n", fn);
467
468 dbt_put(idx, ibbuf, &key, &val);
469 rec++;
470 }
471
472 (*db->close)(db);
473 (*idx->close)(idx);
474
475 if (hash)
476 (*hash->close)(hash);
477
478 mparse_free(mp);
479
480 free(buf.cp);
481 free(dbuf.cp);
482
483 /* Atomically replace the file with our temporary one. */
484
485 if (-1 == rename(fbbuf, fbuf))
486 perror(fbuf);
487 if (-1 == rename(ibbuf, ibuf))
488 perror(fbuf);
489
490 return((int)MANDOCLEVEL_OK);
491 }
492
493 /*
494 * Grow the buffer (if necessary) and copy in a binary string.
495 */
496 static void
497 buf_appendb(struct buf *buf, const void *cp, size_t sz)
498 {
499
500 /* Overshoot by MANDOC_BUFSZ. */
501
502 while (buf->len + sz >= buf->size) {
503 buf->size = buf->len + sz + MANDOC_BUFSZ;
504 buf->cp = mandoc_realloc(buf->cp, buf->size);
505 }
506
507 memcpy(buf->cp + (int)buf->len, cp, sz);
508 buf->len += sz;
509 }
510
511 /*
512 * Append a nil-terminated string to the buffer.
513 * This can be invoked multiple times.
514 * The buffer string will be nil-terminated.
515 * If invoked multiple times, a space is put between strings.
516 */
517 static void
518 buf_append(struct buf *buf, const char *cp)
519 {
520 size_t sz;
521
522 if (0 == (sz = strlen(cp)))
523 return;
524
525 if (buf->len)
526 buf->cp[(int)buf->len - 1] = ' ';
527
528 buf_appendb(buf, cp, sz + 1);
529 }
530
531 /*
532 * Recursively add all text from a given node.
533 * This is optimised for general mdoc nodes in this context, which do
534 * not consist of subexpressions and having a recursive call for n->next
535 * would be wasteful.
536 * The "f" variable should be 0 unless called from pmdoc_Nd for the
537 * description buffer, which does not start at the beginning of the
538 * buffer.
539 */
540 static void
541 buf_appendmdoc(struct buf *buf, const struct mdoc_node *n, int f)
542 {
543
544 for ( ; n; n = n->next) {
545 if (n->child)
546 buf_appendmdoc(buf, n->child, f);
547
548 if (MDOC_TEXT == n->type && f) {
549 f = 0;
550 buf_appendb(buf, n->string,
551 strlen(n->string) + 1);
552 } else if (MDOC_TEXT == n->type)
553 buf_append(buf, n->string);
554
555 }
556 }
557
558 /* ARGSUSED */
559 static void
560 pmdoc_An(MDOC_ARGS)
561 {
562
563 if (SEC_AUTHORS != n->sec)
564 return;
565
566 buf_appendmdoc(buf, n->child, 0);
567 hash_put(hash, buf, TYPE_AUTHOR);
568 }
569
570 /* ARGSUSED */
571 static void
572 pmdoc_Fd(MDOC_ARGS)
573 {
574 const char *start, *end;
575 size_t sz;
576
577 if (SEC_SYNOPSIS != n->sec)
578 return;
579 if (NULL == (n = n->child) || MDOC_TEXT != n->type)
580 return;
581
582 /*
583 * Only consider those `Fd' macro fields that begin with an
584 * "inclusion" token (versus, e.g., #define).
585 */
586 if (strcmp("#include", n->string))
587 return;
588
589 if (NULL == (n = n->next) || MDOC_TEXT != n->type)
590 return;
591
592 /*
593 * Strip away the enclosing angle brackets and make sure we're
594 * not zero-length.
595 */
596
597 start = n->string;
598 if ('<' == *start || '"' == *start)
599 start++;
600
601 if (0 == (sz = strlen(start)))
602 return;
603
604 end = &start[(int)sz - 1];
605 if ('>' == *end || '"' == *end)
606 end--;
607
608 assert(end >= start);
609
610 buf_appendb(buf, start, (size_t)(end - start + 1));
611 buf_appendb(buf, "", 1);
612
613 hash_put(hash, buf, TYPE_INCLUDES);
614 }
615
616 /* ARGSUSED */
617 static void
618 pmdoc_Cd(MDOC_ARGS)
619 {
620
621 if (SEC_SYNOPSIS != n->sec)
622 return;
623
624 buf_appendmdoc(buf, n->child, 0);
625 hash_put(hash, buf, TYPE_CONFIG);
626 }
627
628 /* ARGSUSED */
629 static void
630 pmdoc_In(MDOC_ARGS)
631 {
632
633 if (SEC_SYNOPSIS != n->sec)
634 return;
635 if (NULL == n->child || MDOC_TEXT != n->child->type)
636 return;
637
638 buf_append(buf, n->child->string);
639 hash_put(hash, buf, TYPE_INCLUDES);
640 }
641
642 /* ARGSUSED */
643 static void
644 pmdoc_Fn(MDOC_ARGS)
645 {
646 const char *cp;
647
648 if (SEC_SYNOPSIS != n->sec)
649 return;
650 if (NULL == n->child || MDOC_TEXT != n->child->type)
651 return;
652
653 /* .Fn "struct type *arg" "foo" */
654
655 cp = strrchr(n->child->string, ' ');
656 if (NULL == cp)
657 cp = n->child->string;
658
659 /* Strip away pointer symbol. */
660
661 while ('*' == *cp)
662 cp++;
663
664 buf_append(buf, cp);
665 hash_put(hash, buf, TYPE_FUNCTION);
666 }
667
668 /* ARGSUSED */
669 static void
670 pmdoc_St(MDOC_ARGS)
671 {
672
673 if (SEC_STANDARDS != n->sec)
674 return;
675 if (NULL == n->child || MDOC_TEXT != n->child->type)
676 return;
677
678 buf_append(buf, n->child->string);
679 hash_put(hash, buf, TYPE_STANDARD);
680 }
681
682 /* ARGSUSED */
683 static void
684 pmdoc_Xr(MDOC_ARGS)
685 {
686
687 if (NULL == (n = n->child))
688 return;
689
690 buf_appendb(buf, n->string, strlen(n->string));
691
692 if (NULL != (n = n->next)) {
693 buf_appendb(buf, ".", 1);
694 buf_appendb(buf, n->string, strlen(n->string) + 1);
695 } else
696 buf_appendb(buf, ".", 2);
697
698 hash_put(hash, buf, TYPE_XREF);
699 }
700
701 /* ARGSUSED */
702 static void
703 pmdoc_Vt(MDOC_ARGS)
704 {
705 const char *start;
706 size_t sz;
707
708 if (SEC_SYNOPSIS != n->sec)
709 return;
710 if (MDOC_Vt == n->tok && MDOC_BODY != n->type)
711 return;
712 if (NULL == n->last || MDOC_TEXT != n->last->type)
713 return;
714
715 /*
716 * Strip away leading pointer symbol '*' and trailing ';'.
717 */
718
719 start = n->last->string;
720
721 while ('*' == *start)
722 start++;
723
724 if (0 == (sz = strlen(start)))
725 return;
726
727 if (';' == start[(int)sz - 1])
728 sz--;
729
730 if (0 == sz)
731 return;
732
733 buf_appendb(buf, start, sz);
734 buf_appendb(buf, "", 1);
735 hash_put(hash, buf, TYPE_VARIABLE);
736 }
737
738 /* ARGSUSED */
739 static void
740 pmdoc_Fo(MDOC_ARGS)
741 {
742
743 if (SEC_SYNOPSIS != n->sec || MDOC_HEAD != n->type)
744 return;
745 if (NULL == n->child || MDOC_TEXT != n->child->type)
746 return;
747
748 buf_append(buf, n->child->string);
749 hash_put(hash, buf, TYPE_FUNCTION);
750 }
751
752
753 /* ARGSUSED */
754 static void
755 pmdoc_Nd(MDOC_ARGS)
756 {
757
758 if (MDOC_BODY != n->type)
759 return;
760
761 buf_appendmdoc(dbuf, n->child, 1);
762 buf_appendmdoc(buf, n->child, 0);
763
764 hash_put(hash, buf, TYPE_DESC);
765 }
766
767 /* ARGSUSED */
768 static void
769 pmdoc_Ev(MDOC_ARGS)
770 {
771
772 if (SEC_ENVIRONMENT != n->sec)
773 return;
774
775 buf_appendmdoc(buf, n->child, 0);
776 hash_put(hash, buf, TYPE_ENV);
777 }
778
779 /* ARGSUSED */
780 static void
781 pmdoc_Pa(MDOC_ARGS)
782 {
783
784 if (SEC_FILES != n->sec)
785 return;
786
787 buf_appendmdoc(buf, n->child, 0);
788 hash_put(hash, buf, TYPE_PATH);
789 }
790
791 /* ARGSUSED */
792 static void
793 pmdoc_Nm(MDOC_ARGS)
794 {
795
796 if (SEC_NAME == n->sec) {
797 buf_appendmdoc(buf, n->child, 0);
798 hash_put(hash, buf, TYPE_NAME);
799 return;
800 } else if (SEC_SYNOPSIS != n->sec || MDOC_HEAD != n->type)
801 return;
802
803 if (NULL == n->child)
804 buf_append(buf, m->name);
805
806 buf_appendmdoc(buf, n->child, 0);
807 hash_put(hash, buf, TYPE_UTILITY);
808 }
809
810 static void
811 hash_put(DB *db, const struct buf *buf, int mask)
812 {
813 DBT key, val;
814 int rc;
815
816 if (buf->len < 2)
817 return;
818
819 key.data = buf->cp;
820 key.size = buf->len;
821
822 if ((rc = (*db->get)(db, &key, &val, 0)) < 0) {
823 perror("hash");
824 exit((int)MANDOCLEVEL_SYSERR);
825 } else if (0 == rc)
826 mask |= *(int *)val.data;
827
828 val.data = &mask;
829 val.size = sizeof(int);
830
831 if ((rc = (*db->put)(db, &key, &val, 0)) < 0) {
832 perror("hash");
833 exit((int)MANDOCLEVEL_SYSERR);
834 }
835 }
836
837 static void
838 dbt_put(DB *db, const char *dbn, DBT *key, DBT *val)
839 {
840
841 assert(key->size);
842 assert(val->size);
843
844 if (0 == (*db->put)(db, key, val, 0))
845 return;
846
847 perror(dbn);
848 exit((int)MANDOCLEVEL_SYSERR);
849 /* NOTREACHED */
850 }
851
852 /*
853 * Call out to per-macro handlers after clearing the persistent database
854 * key. If the macro sets the database key, flush it to the database.
855 */
856 static void
857 pmdoc_node(MDOC_ARGS)
858 {
859
860 if (NULL == n)
861 return;
862
863 switch (n->type) {
864 case (MDOC_HEAD):
865 /* FALLTHROUGH */
866 case (MDOC_BODY):
867 /* FALLTHROUGH */
868 case (MDOC_TAIL):
869 /* FALLTHROUGH */
870 case (MDOC_BLOCK):
871 /* FALLTHROUGH */
872 case (MDOC_ELEM):
873 if (NULL == mdocs[n->tok])
874 break;
875
876 buf->len = 0;
877 (*mdocs[n->tok])(hash, buf, dbuf, n, m);
878 break;
879 default:
880 break;
881 }
882
883 pmdoc_node(hash, buf, dbuf, n->child, m);
884 pmdoc_node(hash, buf, dbuf, n->next, m);
885 }
886
887 static int
888 pman_node(MAN_ARGS)
889 {
890 const struct man_node *head, *body;
891 const char *start, *sv;
892 size_t sz;
893
894 if (NULL == n)
895 return(0);
896
897 /*
898 * We're only searching for one thing: the first text child in
899 * the BODY of a NAME section. Since we don't keep track of
900 * sections in -man, run some hoops to find out whether we're in
901 * the correct section or not.
902 */
903
904 if (MAN_BODY == n->type && MAN_SH == n->tok) {
905 body = n;
906 assert(body->parent);
907 if (NULL != (head = body->parent->head) &&
908 1 == head->nchild &&
909 NULL != (head = (head->child)) &&
910 MAN_TEXT == head->type &&
911 0 == strcmp(head->string, "NAME") &&
912 NULL != (body = body->child) &&
913 MAN_TEXT == body->type) {
914
915 assert(body->string);
916 start = sv = body->string;
917
918 /*
919 * Go through a special heuristic dance here.
920 * This is why -man manuals are great!
921 * (I'm being sarcastic: my eyes are bleeding.)
922 * Conventionally, one or more manual names are
923 * comma-specified prior to a whitespace, then a
924 * dash, then a description. Try to puzzle out
925 * the name parts here.
926 */
927
928 for ( ;; ) {
929 sz = strcspn(start, " ,");
930 if ('\0' == start[(int)sz])
931 break;
932
933 buf->len = 0;
934 buf_appendb(buf, start, sz);
935 buf_appendb(buf, "", 1);
936
937 hash_put(hash, buf, TYPE_NAME);
938
939 if (' ' == start[(int)sz]) {
940 start += (int)sz + 1;
941 break;
942 }
943
944 assert(',' == start[(int)sz]);
945 start += (int)sz + 1;
946 while (' ' == *start)
947 start++;
948 }
949
950 if (sv == start) {
951 buf->len = 0;
952 buf_append(buf, start);
953 return(1);
954 }
955
956 while (' ' == *start)
957 start++;
958
959 if (0 == strncmp(start, "-", 1))
960 start += 1;
961 else if (0 == strncmp(start, "\\-", 2))
962 start += 2;
963 else if (0 == strncmp(start, "\\(en", 4))
964 start += 4;
965 else if (0 == strncmp(start, "\\(em", 4))
966 start += 4;
967
968 while (' ' == *start)
969 start++;
970
971 sz = strlen(start) + 1;
972 buf_appendb(dbuf, start, sz);
973 buf_appendb(buf, start, sz);
974 }
975 }
976
977 if (pman_node(hash, buf, dbuf, n->child))
978 return(1);
979 if (pman_node(hash, buf, dbuf, n->next))
980 return(1);
981
982 return(0);
983 }
984
985 static void
986 usage(void)
987 {
988
989 fprintf(stderr, "usage: %s [-v] [-d path] [file...]\n",
990 progname);
991 }