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