Annotation of src/usr.bin/mandoc/mansearch.c, Revision 1.9
1.9 ! schwarze 1: /* $Id: mansearch.c,v 1.8 2014/01/05 04:13:46 schwarze Exp $ */
1.1 schwarze 2: /*
3: * Copyright (c) 2012 Kristaps Dzonsons <kristaps@bsd.lv>
1.4 schwarze 4: * Copyright (c) 2013, 2014 Ingo Schwarze <schwarze@openbsd.org>
1.1 schwarze 5: *
6: * Permission to use, copy, modify, and distribute this software for any
7: * purpose with or without fee is hereby granted, provided that the above
8: * copyright notice and this permission notice appear in all copies.
9: *
10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17: */
18: #include <assert.h>
19: #include <fcntl.h>
20: #include <getopt.h>
21: #include <limits.h>
22: #include <regex.h>
23: #include <stdio.h>
24: #include <stdint.h>
25: #include <stddef.h>
26: #include <stdlib.h>
27: #include <string.h>
28: #include <unistd.h>
29:
30: #include <ohash.h>
31: #include <sqlite3.h>
32:
33: #include "mandoc.h"
34: #include "manpath.h"
35: #include "mansearch.h"
36:
37: #define SQL_BIND_TEXT(_db, _s, _i, _v) \
38: do { if (SQLITE_OK != sqlite3_bind_text \
39: ((_s), (_i)++, (_v), -1, SQLITE_STATIC)) \
40: fprintf(stderr, "%s\n", sqlite3_errmsg((_db))); \
41: } while (0)
42: #define SQL_BIND_INT64(_db, _s, _i, _v) \
43: do { if (SQLITE_OK != sqlite3_bind_int64 \
44: ((_s), (_i)++, (_v))) \
45: fprintf(stderr, "%s\n", sqlite3_errmsg((_db))); \
46: } while (0)
47: #define SQL_BIND_BLOB(_db, _s, _i, _v) \
48: do { if (SQLITE_OK != sqlite3_bind_blob \
49: ((_s), (_i)++, (&_v), sizeof(_v), SQLITE_STATIC)) \
50: fprintf(stderr, "%s\n", sqlite3_errmsg((_db))); \
51: } while (0)
52:
53: struct expr {
54: uint64_t bits; /* type-mask */
55: const char *substr; /* to search for, if applicable */
56: regex_t regexp; /* compiled regexp, if applicable */
1.4 schwarze 57: int open; /* opening parentheses before */
58: int and; /* logical AND before */
59: int close; /* closing parentheses after */
1.1 schwarze 60: struct expr *next; /* next in sequence */
61: };
62:
63: struct match {
64: uint64_t id; /* identifier in database */
65: int form; /* 0 == catpage */
66: };
67:
68: struct type {
69: uint64_t bits;
70: const char *name;
71: };
72:
73: static const struct type types[] = {
74: { TYPE_An, "An" },
75: { TYPE_Ar, "Ar" },
76: { TYPE_At, "At" },
77: { TYPE_Bsx, "Bsx" },
78: { TYPE_Bx, "Bx" },
79: { TYPE_Cd, "Cd" },
80: { TYPE_Cm, "Cm" },
81: { TYPE_Dv, "Dv" },
82: { TYPE_Dx, "Dx" },
83: { TYPE_Em, "Em" },
84: { TYPE_Er, "Er" },
85: { TYPE_Ev, "Ev" },
86: { TYPE_Fa, "Fa" },
87: { TYPE_Fl, "Fl" },
88: { TYPE_Fn, "Fn" },
89: { TYPE_Fn, "Fo" },
90: { TYPE_Ft, "Ft" },
91: { TYPE_Fx, "Fx" },
92: { TYPE_Ic, "Ic" },
93: { TYPE_In, "In" },
94: { TYPE_Lb, "Lb" },
95: { TYPE_Li, "Li" },
96: { TYPE_Lk, "Lk" },
97: { TYPE_Ms, "Ms" },
98: { TYPE_Mt, "Mt" },
99: { TYPE_Nd, "Nd" },
100: { TYPE_Nm, "Nm" },
101: { TYPE_Nx, "Nx" },
102: { TYPE_Ox, "Ox" },
103: { TYPE_Pa, "Pa" },
104: { TYPE_Rs, "Rs" },
105: { TYPE_Sh, "Sh" },
106: { TYPE_Ss, "Ss" },
107: { TYPE_St, "St" },
108: { TYPE_Sy, "Sy" },
109: { TYPE_Tn, "Tn" },
110: { TYPE_Va, "Va" },
111: { TYPE_Va, "Vt" },
112: { TYPE_Xr, "Xr" },
1.5 schwarze 113: { TYPE_sec, "sec" },
114: { TYPE_arch,"arch" },
1.1 schwarze 115: { ~0ULL, "any" },
116: { 0ULL, NULL }
117: };
118:
1.8 schwarze 119: static void buildnames(struct manpage *, sqlite3 *,
120: sqlite3_stmt *, uint64_t, const char *);
1.3 schwarze 121: static char *buildoutput(sqlite3 *, sqlite3_stmt *,
122: uint64_t, uint64_t);
1.1 schwarze 123: static void *hash_alloc(size_t, void *);
124: static void hash_free(void *, size_t, void *);
125: static void *hash_halloc(size_t, void *);
126: static struct expr *exprcomp(const struct mansearch *,
127: int, char *[]);
128: static void exprfree(struct expr *);
1.6 schwarze 129: static struct expr *exprspec(struct expr *, uint64_t,
130: const char *, const char *);
1.1 schwarze 131: static struct expr *exprterm(const struct mansearch *, char *, int);
1.4 schwarze 132: static void sql_append(char **sql, size_t *sz,
133: const char *newstr, int count);
1.1 schwarze 134: static void sql_match(sqlite3_context *context,
135: int argc, sqlite3_value **argv);
136: static void sql_regexp(sqlite3_context *context,
137: int argc, sqlite3_value **argv);
1.6 schwarze 138: static char *sql_statement(const struct expr *);
1.1 schwarze 139:
140: int
141: mansearch(const struct mansearch *search,
1.3 schwarze 142: const struct manpaths *paths,
143: int argc, char *argv[],
144: const char *outkey,
1.1 schwarze 145: struct manpage **res, size_t *sz)
146: {
1.3 schwarze 147: int fd, rc, c, ibit;
1.1 schwarze 148: int64_t id;
1.3 schwarze 149: uint64_t outbit;
1.1 schwarze 150: char buf[PATH_MAX];
1.2 schwarze 151: char *sql;
1.1 schwarze 152: struct manpage *mpage;
153: struct expr *e, *ep;
154: sqlite3 *db;
1.3 schwarze 155: sqlite3_stmt *s, *s2;
1.1 schwarze 156: struct match *mp;
157: struct ohash_info info;
158: struct ohash htab;
159: unsigned int idx;
160: size_t i, j, cur, maxres;
161:
162: memset(&info, 0, sizeof(struct ohash_info));
163:
164: info.halloc = hash_halloc;
165: info.alloc = hash_alloc;
166: info.hfree = hash_free;
167: info.key_offset = offsetof(struct match, id);
168:
169: *sz = cur = maxres = 0;
170: sql = NULL;
171: *res = NULL;
172: fd = -1;
173: e = NULL;
174: rc = 0;
175:
176: if (0 == argc)
177: goto out;
178: if (NULL == (e = exprcomp(search, argc, argv)))
179: goto out;
180:
1.3 schwarze 181: outbit = 0;
182: if (NULL != outkey) {
183: for (ibit = 0; types[ibit].bits; ibit++) {
184: if (0 == strcasecmp(types[ibit].name, outkey)) {
185: outbit = types[ibit].bits;
186: break;
187: }
188: }
189: }
190:
1.1 schwarze 191: /*
192: * Save a descriptor to the current working directory.
193: * Since pathnames in the "paths" variable might be relative,
194: * and we'll be chdir()ing into them, we need to keep a handle
195: * on our current directory from which to start the chdir().
196: */
197:
198: if (NULL == getcwd(buf, PATH_MAX)) {
199: perror(NULL);
200: goto out;
201: } else if (-1 == (fd = open(buf, O_RDONLY, 0))) {
202: perror(buf);
203: goto out;
204: }
205:
1.6 schwarze 206: sql = sql_statement(e);
1.1 schwarze 207:
208: /*
209: * Loop over the directories (containing databases) for us to
210: * search.
211: * Don't let missing/bad databases/directories phase us.
212: * In each, try to open the resident database and, if it opens,
213: * scan it for our match expression.
214: */
215:
216: for (i = 0; i < paths->sz; i++) {
217: if (-1 == fchdir(fd)) {
218: perror(buf);
219: free(*res);
220: break;
221: } else if (-1 == chdir(paths->paths[i])) {
222: perror(paths->paths[i]);
223: continue;
224: }
225:
226: c = sqlite3_open_v2
227: (MANDOC_DB, &db,
228: SQLITE_OPEN_READONLY, NULL);
229:
230: if (SQLITE_OK != c) {
231: perror(MANDOC_DB);
232: sqlite3_close(db);
233: continue;
234: }
235:
236: /*
237: * Define the SQL functions for substring
238: * and regular expression matching.
239: */
240:
241: c = sqlite3_create_function(db, "match", 2,
242: SQLITE_ANY, NULL, sql_match, NULL, NULL);
243: assert(SQLITE_OK == c);
244: c = sqlite3_create_function(db, "regexp", 2,
245: SQLITE_ANY, NULL, sql_regexp, NULL, NULL);
246: assert(SQLITE_OK == c);
247:
248: j = 1;
249: c = sqlite3_prepare_v2(db, sql, -1, &s, NULL);
250: if (SQLITE_OK != c)
251: fprintf(stderr, "%s\n", sqlite3_errmsg(db));
252:
253: for (ep = e; NULL != ep; ep = ep->next) {
254: if (NULL == ep->substr) {
255: SQL_BIND_BLOB(db, s, j, ep->regexp);
256: } else
257: SQL_BIND_TEXT(db, s, j, ep->substr);
258: SQL_BIND_INT64(db, s, j, ep->bits);
259: }
260:
261: memset(&htab, 0, sizeof(struct ohash));
262: ohash_init(&htab, 4, &info);
263:
264: /*
265: * Hash each entry on its [unique] document identifier.
266: * This is a uint64_t.
267: * Instead of using a hash function, simply convert the
268: * uint64_t to a uint32_t, the hash value's type.
269: * This gives good performance and preserves the
270: * distribution of buckets in the table.
271: */
272: while (SQLITE_ROW == (c = sqlite3_step(s))) {
1.9 ! schwarze 273: id = sqlite3_column_int64(s, 1);
1.1 schwarze 274: idx = ohash_lookup_memory
275: (&htab, (char *)&id,
276: sizeof(uint64_t), (uint32_t)id);
277:
278: if (NULL != ohash_find(&htab, idx))
279: continue;
280:
281: mp = mandoc_calloc(1, sizeof(struct match));
282: mp->id = id;
1.9 ! schwarze 283: mp->form = sqlite3_column_int(s, 0);
1.1 schwarze 284: ohash_insert(&htab, idx, mp);
285: }
286:
287: if (SQLITE_DONE != c)
288: fprintf(stderr, "%s\n", sqlite3_errmsg(db));
289:
290: sqlite3_finalize(s);
291:
292: c = sqlite3_prepare_v2(db,
293: "SELECT * FROM mlinks WHERE pageid=?",
294: -1, &s, NULL);
295: if (SQLITE_OK != c)
296: fprintf(stderr, "%s\n", sqlite3_errmsg(db));
297:
1.3 schwarze 298: c = sqlite3_prepare_v2(db,
299: "SELECT * FROM keys WHERE pageid=? AND bits & ?",
300: -1, &s2, NULL);
301: if (SQLITE_OK != c)
302: fprintf(stderr, "%s\n", sqlite3_errmsg(db));
303:
1.1 schwarze 304: for (mp = ohash_first(&htab, &idx);
305: NULL != mp;
306: mp = ohash_next(&htab, &idx)) {
307: if (cur + 1 > maxres) {
308: maxres += 1024;
309: *res = mandoc_realloc
310: (*res, maxres * sizeof(struct manpage));
311: }
312: mpage = *res + cur;
313: mpage->form = mp->form;
1.8 schwarze 314: buildnames(mpage, db, s, mp->id, paths->paths[i]);
1.3 schwarze 315: mpage->output = outbit ?
316: buildoutput(db, s2, mp->id, outbit) : NULL;
1.1 schwarze 317:
318: free(mp);
319: cur++;
320: }
321:
322: sqlite3_finalize(s);
1.3 schwarze 323: sqlite3_finalize(s2);
1.1 schwarze 324: sqlite3_close(db);
325: ohash_delete(&htab);
326: }
327: rc = 1;
328: out:
329: exprfree(e);
330: if (-1 != fd)
331: close(fd);
332: free(sql);
333: *sz = cur;
334: return(rc);
1.2 schwarze 335: }
336:
1.8 schwarze 337: static void
338: buildnames(struct manpage *mpage, sqlite3 *db, sqlite3_stmt *s,
339: uint64_t id, const char *path)
1.2 schwarze 340: {
1.8 schwarze 341: char *newnames;
1.2 schwarze 342: const char *oldnames, *sep1, *name, *sec, *sep2, *arch;
343: size_t i;
344: int c;
345:
1.8 schwarze 346: mpage->names = NULL;
1.2 schwarze 347: i = 1;
348: SQL_BIND_INT64(db, s, i, id);
349: while (SQLITE_ROW == (c = sqlite3_step(s))) {
1.8 schwarze 350:
351: /* Assemble the list of names. */
352:
353: if (NULL == mpage->names) {
1.2 schwarze 354: oldnames = "";
355: sep1 = "";
356: } else {
1.8 schwarze 357: oldnames = mpage->names;
1.2 schwarze 358: sep1 = ", ";
359: }
360: sec = sqlite3_column_text(s, 1);
361: arch = sqlite3_column_text(s, 2);
362: name = sqlite3_column_text(s, 3);
363: sep2 = '\0' == *arch ? "" : "/";
364: if (-1 == asprintf(&newnames, "%s%s%s(%s%s%s)",
365: oldnames, sep1, name, sec, sep2, arch)) {
366: perror(0);
367: exit((int)MANDOCLEVEL_SYSERR);
368: }
1.8 schwarze 369: free(mpage->names);
370: mpage->names = newnames;
371:
372: /* Also save the first file name encountered. */
373:
374: if (NULL != mpage->file)
375: continue;
376:
377: name = sqlite3_column_text(s, 0);
378: if (-1 == asprintf(&mpage->file, "%s/%s", path, name)) {
379: perror(0);
380: exit((int)MANDOCLEVEL_SYSERR);
381: }
1.2 schwarze 382: }
383: if (SQLITE_DONE != c)
384: fprintf(stderr, "%s\n", sqlite3_errmsg(db));
385: sqlite3_reset(s);
1.3 schwarze 386: }
387:
388: static char *
389: buildoutput(sqlite3 *db, sqlite3_stmt *s, uint64_t id, uint64_t outbit)
390: {
391: char *output, *newoutput;
392: const char *oldoutput, *sep1, *data;
393: size_t i;
394: int c;
395:
396: output = NULL;
397: i = 1;
398: SQL_BIND_INT64(db, s, i, id);
399: SQL_BIND_INT64(db, s, i, outbit);
400: while (SQLITE_ROW == (c = sqlite3_step(s))) {
401: if (NULL == output) {
402: oldoutput = "";
403: sep1 = "";
404: } else {
405: oldoutput = output;
406: sep1 = " # ";
407: }
408: data = sqlite3_column_text(s, 1);
409: if (-1 == asprintf(&newoutput, "%s%s%s",
410: oldoutput, sep1, data)) {
411: perror(0);
412: exit((int)MANDOCLEVEL_SYSERR);
413: }
414: free(output);
415: output = newoutput;
416: }
417: if (SQLITE_DONE != c)
418: fprintf(stderr, "%s\n", sqlite3_errmsg(db));
419: sqlite3_reset(s);
420: return(output);
1.1 schwarze 421: }
422:
423: /*
424: * Implement substring match as an application-defined SQL function.
425: * Using the SQL LIKE or GLOB operators instead would be a bad idea
426: * because that would require escaping metacharacters in the string
427: * being searched for.
428: */
429: static void
430: sql_match(sqlite3_context *context, int argc, sqlite3_value **argv)
431: {
432:
433: assert(2 == argc);
434: sqlite3_result_int(context, NULL != strcasestr(
435: (const char *)sqlite3_value_text(argv[1]),
436: (const char *)sqlite3_value_text(argv[0])));
437: }
438:
439: /*
440: * Implement regular expression match
441: * as an application-defined SQL function.
442: */
443: static void
444: sql_regexp(sqlite3_context *context, int argc, sqlite3_value **argv)
445: {
446:
447: assert(2 == argc);
448: sqlite3_result_int(context, !regexec(
449: (regex_t *)sqlite3_value_blob(argv[0]),
450: (const char *)sqlite3_value_text(argv[1]),
451: 0, NULL, 0));
452: }
453:
1.4 schwarze 454: static void
455: sql_append(char **sql, size_t *sz, const char *newstr, int count)
456: {
457: size_t newsz;
458:
459: newsz = 1 < count ? (size_t)count : strlen(newstr);
460: *sql = mandoc_realloc(*sql, *sz + newsz + 1);
461: if (1 < count)
462: memset(*sql + *sz, *newstr, (size_t)count);
463: else
464: memcpy(*sql + *sz, newstr, newsz);
465: *sz += newsz;
466: (*sql)[*sz] = '\0';
467: }
468:
1.1 schwarze 469: /*
470: * Prepare the search SQL statement.
471: */
472: static char *
1.6 schwarze 473: sql_statement(const struct expr *e)
1.1 schwarze 474: {
475: char *sql;
476: size_t sz;
1.4 schwarze 477: int needop;
1.1 schwarze 478:
1.4 schwarze 479: sql = mandoc_strdup("SELECT * FROM mpages WHERE ");
1.1 schwarze 480: sz = strlen(sql);
481:
1.4 schwarze 482: for (needop = 0; NULL != e; e = e->next) {
483: if (e->and)
484: sql_append(&sql, &sz, " AND ", 1);
485: else if (needop)
486: sql_append(&sql, &sz, " OR ", 1);
487: if (e->open)
488: sql_append(&sql, &sz, "(", e->open);
489: sql_append(&sql, &sz, NULL == e->substr ?
490: "id IN (SELECT pageid FROM keys "
491: "WHERE key REGEXP ? AND bits & ?)" :
492: "id IN (SELECT pageid FROM keys "
493: "WHERE key MATCH ? AND bits & ?)", 1);
494: if (e->close)
495: sql_append(&sql, &sz, ")", e->close);
496: needop = 1;
1.1 schwarze 497: }
498:
499: return(sql);
500: }
501:
502: /*
503: * Compile a set of string tokens into an expression.
504: * Tokens in "argv" are assumed to be individual expression atoms (e.g.,
505: * "(", "foo=bar", etc.).
506: */
507: static struct expr *
508: exprcomp(const struct mansearch *search, int argc, char *argv[])
509: {
1.4 schwarze 510: int i, toopen, logic, igncase, toclose;
1.1 schwarze 511: struct expr *first, *next, *cur;
512:
513: first = cur = NULL;
1.6 schwarze 514: logic = igncase = toclose = 0;
515: toopen = 1;
1.1 schwarze 516:
517: for (i = 0; i < argc; i++) {
1.4 schwarze 518: if (0 == strcmp("(", argv[i])) {
519: if (igncase)
520: goto fail;
521: toopen++;
522: toclose++;
523: continue;
524: } else if (0 == strcmp(")", argv[i])) {
525: if (toopen || logic || igncase || NULL == cur)
526: goto fail;
527: cur->close++;
528: if (0 > --toclose)
529: goto fail;
530: continue;
531: } else if (0 == strcmp("-a", argv[i])) {
532: if (toopen || logic || igncase || NULL == cur)
533: goto fail;
534: logic = 1;
535: continue;
536: } else if (0 == strcmp("-o", argv[i])) {
537: if (toopen || logic || igncase || NULL == cur)
538: goto fail;
539: logic = 2;
540: continue;
541: } else if (0 == strcmp("-i", argv[i])) {
542: if (igncase)
543: goto fail;
544: igncase = 1;
545: continue;
1.1 schwarze 546: }
1.4 schwarze 547: next = exprterm(search, argv[i], !igncase);
548: if (NULL == next)
549: goto fail;
550: next->open = toopen;
551: next->and = (1 == logic);
1.1 schwarze 552: if (NULL != first) {
553: cur->next = next;
554: cur = next;
555: } else
556: cur = first = next;
1.4 schwarze 557: toopen = logic = igncase = 0;
1.1 schwarze 558: }
1.6 schwarze 559: if (toopen || logic || igncase || toclose)
560: goto fail;
561:
562: cur->close++;
563: cur = exprspec(cur, TYPE_arch, search->arch, "^(%s|any)$");
564: exprspec(cur, TYPE_sec, search->sec, "^%s$");
565:
566: return(first);
567:
1.4 schwarze 568: fail:
569: if (NULL != first)
570: exprfree(first);
571: return(NULL);
1.1 schwarze 572: }
573:
574: static struct expr *
1.6 schwarze 575: exprspec(struct expr *cur, uint64_t key, const char *value,
576: const char *format)
577: {
578: char errbuf[BUFSIZ];
579: char *cp;
580: int irc;
581:
582: if (NULL == value)
583: return(cur);
584:
585: if (-1 == asprintf(&cp, format, value)) {
586: perror(0);
587: exit((int)MANDOCLEVEL_SYSERR);
588: }
589: cur->next = mandoc_calloc(1, sizeof(struct expr));
590: cur = cur->next;
591: cur->and = 1;
592: cur->bits = key;
593: if (0 != (irc = regcomp(&cur->regexp, cp,
594: REG_EXTENDED | REG_NOSUB | REG_ICASE))) {
595: regerror(irc, &cur->regexp, errbuf, sizeof(errbuf));
596: fprintf(stderr, "regcomp: %s\n", errbuf);
597: cur->substr = value;
598: }
599: free(cp);
600: return(cur);
601: }
602:
603: static struct expr *
1.1 schwarze 604: exprterm(const struct mansearch *search, char *buf, int cs)
605: {
1.6 schwarze 606: char errbuf[BUFSIZ];
1.1 schwarze 607: struct expr *e;
608: char *key, *v;
609: size_t i;
1.6 schwarze 610: int irc;
1.1 schwarze 611:
612: if ('\0' == *buf)
613: return(NULL);
614:
615: e = mandoc_calloc(1, sizeof(struct expr));
616:
617: /*"whatis" mode uses an opaque string and default fields. */
618:
619: if (MANSEARCH_WHATIS & search->flags) {
620: e->substr = buf;
621: e->bits = search->deftype;
622: return(e);
623: }
624:
625: /*
626: * If no =~ is specified, search with equality over names and
627: * descriptions.
628: * If =~ begins the phrase, use name and description fields.
629: */
630:
631: if (NULL == (v = strpbrk(buf, "=~"))) {
632: e->substr = buf;
633: e->bits = search->deftype;
634: return(e);
635: } else if (v == buf)
636: e->bits = search->deftype;
637:
638: if ('~' == *v++) {
1.6 schwarze 639: if (0 != (irc = regcomp(&e->regexp, v,
640: REG_EXTENDED | REG_NOSUB | (cs ? 0 : REG_ICASE)))) {
641: regerror(irc, &e->regexp, errbuf, sizeof(errbuf));
642: fprintf(stderr, "regcomp: %s\n", errbuf);
1.1 schwarze 643: free(e);
644: return(NULL);
645: }
646: } else
647: e->substr = v;
648: v[-1] = '\0';
649:
650: /*
651: * Parse out all possible fields.
652: * If the field doesn't resolve, bail.
653: */
654:
655: while (NULL != (key = strsep(&buf, ","))) {
656: if ('\0' == *key)
657: continue;
658: i = 0;
659: while (types[i].bits &&
660: strcasecmp(types[i].name, key))
661: i++;
662: if (0 == types[i].bits) {
663: free(e);
664: return(NULL);
665: }
666: e->bits |= types[i].bits;
667: }
668:
669: return(e);
670: }
671:
672: static void
673: exprfree(struct expr *p)
674: {
675: struct expr *pp;
676:
677: while (NULL != p) {
678: pp = p->next;
679: free(p);
680: p = pp;
681: }
682: }
683:
684: static void *
685: hash_halloc(size_t sz, void *arg)
686: {
687:
688: return(mandoc_calloc(sz, 1));
689: }
690:
691: static void *
692: hash_alloc(size_t sz, void *arg)
693: {
694:
695: return(mandoc_malloc(sz));
696: }
697:
698: static void
699: hash_free(void *p, size_t sz, void *arg)
700: {
701:
702: free(p);
703: }