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