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