Annotation of src/usr.bin/mandoc/mansearch.c, Revision 1.28
1.28 ! schwarze 1: /* $Id: mansearch.c,v 1.27 2014/05/12 19:11:20 espie 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: */
1.19 schwarze 18: #include <sys/mman.h>
1.1 schwarze 19: #include <assert.h>
20: #include <fcntl.h>
21: #include <getopt.h>
22: #include <limits.h>
23: #include <regex.h>
24: #include <stdio.h>
25: #include <stdint.h>
26: #include <stddef.h>
27: #include <stdlib.h>
28: #include <string.h>
29: #include <unistd.h>
30:
31: #include <ohash.h>
32: #include <sqlite3.h>
33:
34: #include "mandoc.h"
1.14 schwarze 35: #include "mandoc_aux.h"
1.1 schwarze 36: #include "manpath.h"
37: #include "mansearch.h"
38:
1.11 schwarze 39: extern int mansearch_keymax;
40: extern const char *const mansearch_keynames[];
41:
1.1 schwarze 42: #define SQL_BIND_TEXT(_db, _s, _i, _v) \
43: do { if (SQLITE_OK != sqlite3_bind_text \
44: ((_s), (_i)++, (_v), -1, SQLITE_STATIC)) \
45: fprintf(stderr, "%s\n", sqlite3_errmsg((_db))); \
46: } while (0)
47: #define SQL_BIND_INT64(_db, _s, _i, _v) \
48: do { if (SQLITE_OK != sqlite3_bind_int64 \
49: ((_s), (_i)++, (_v))) \
50: fprintf(stderr, "%s\n", sqlite3_errmsg((_db))); \
51: } while (0)
52: #define SQL_BIND_BLOB(_db, _s, _i, _v) \
53: do { if (SQLITE_OK != sqlite3_bind_blob \
54: ((_s), (_i)++, (&_v), sizeof(_v), SQLITE_STATIC)) \
55: fprintf(stderr, "%s\n", sqlite3_errmsg((_db))); \
56: } while (0)
57:
58: struct expr {
1.28 ! schwarze 59: regex_t regexp; /* compiled regexp, if applicable */
! 60: const char *substr; /* to search for, if applicable */
! 61: struct expr *next; /* next in sequence */
1.24 schwarze 62: uint64_t bits; /* type-mask */
1.28 ! schwarze 63: int equal; /* equality, not subsring match */
1.4 schwarze 64: int open; /* opening parentheses before */
65: int and; /* logical AND before */
66: int close; /* closing parentheses after */
1.1 schwarze 67: };
68:
69: struct match {
1.22 schwarze 70: uint64_t pageid; /* identifier in database */
1.17 schwarze 71: char *desc; /* manual page description */
1.1 schwarze 72: int form; /* 0 == catpage */
73: };
74:
1.8 schwarze 75: static void buildnames(struct manpage *, sqlite3 *,
1.10 schwarze 76: sqlite3_stmt *, uint64_t,
77: const char *, int form);
1.3 schwarze 78: static char *buildoutput(sqlite3 *, sqlite3_stmt *,
79: uint64_t, uint64_t);
1.1 schwarze 80: static void *hash_alloc(size_t, void *);
1.27 espie 81: static void hash_free(void *, void *);
82: static void *hash_calloc(size_t, size_t, void *);
1.24 schwarze 83: static struct expr *exprcomp(const struct mansearch *,
1.1 schwarze 84: int, char *[]);
85: static void exprfree(struct expr *);
1.6 schwarze 86: static struct expr *exprspec(struct expr *, uint64_t,
87: const char *, const char *);
1.1 schwarze 88: static struct expr *exprterm(const struct mansearch *, char *, int);
1.4 schwarze 89: static void sql_append(char **sql, size_t *sz,
90: const char *newstr, int count);
1.1 schwarze 91: static void sql_match(sqlite3_context *context,
92: int argc, sqlite3_value **argv);
93: static void sql_regexp(sqlite3_context *context,
94: int argc, sqlite3_value **argv);
1.6 schwarze 95: static char *sql_statement(const struct expr *);
1.19 schwarze 96:
1.24 schwarze 97:
1.19 schwarze 98: int
99: mansearch_setup(int start)
100: {
101: static void *pagecache;
102: int c;
103:
104: #define PC_PAGESIZE 1280
105: #define PC_NUMPAGES 256
106:
107: if (start) {
108: if (NULL != pagecache) {
109: fprintf(stderr, "pagecache already enabled\n");
110: return((int)MANDOCLEVEL_BADARG);
111: }
112:
113: pagecache = mmap(NULL, PC_PAGESIZE * PC_NUMPAGES,
114: PROT_READ | PROT_WRITE, MAP_ANON, -1, 0);
115:
116: if (MAP_FAILED == pagecache) {
117: perror("mmap");
118: pagecache = NULL;
119: return((int)MANDOCLEVEL_SYSERR);
120: }
121:
122: c = sqlite3_config(SQLITE_CONFIG_PAGECACHE,
123: pagecache, PC_PAGESIZE, PC_NUMPAGES);
124:
125: if (SQLITE_OK == c)
126: return((int)MANDOCLEVEL_OK);
127:
128: fprintf(stderr, "pagecache: %s\n", sqlite3_errstr(c));
129:
130: } else if (NULL == pagecache) {
131: fprintf(stderr, "pagecache missing\n");
132: return((int)MANDOCLEVEL_BADARG);
133: }
134:
135: if (-1 == munmap(pagecache, PC_PAGESIZE * PC_NUMPAGES)) {
136: perror("munmap");
137: pagecache = NULL;
138: return((int)MANDOCLEVEL_SYSERR);
139: }
140:
141: pagecache = NULL;
142: return((int)MANDOCLEVEL_OK);
143: }
1.1 schwarze 144:
145: int
146: mansearch(const struct mansearch *search,
1.3 schwarze 147: const struct manpaths *paths,
148: int argc, char *argv[],
149: const char *outkey,
1.1 schwarze 150: struct manpage **res, size_t *sz)
151: {
1.11 schwarze 152: int fd, rc, c, indexbit;
1.22 schwarze 153: int64_t pageid;
1.11 schwarze 154: uint64_t outbit, iterbit;
1.1 schwarze 155: char buf[PATH_MAX];
1.2 schwarze 156: char *sql;
1.1 schwarze 157: struct manpage *mpage;
158: struct expr *e, *ep;
159: sqlite3 *db;
1.3 schwarze 160: sqlite3_stmt *s, *s2;
1.1 schwarze 161: struct match *mp;
162: struct ohash_info info;
163: struct ohash htab;
164: unsigned int idx;
165: size_t i, j, cur, maxres;
166:
1.27 espie 167: info.calloc = hash_calloc;
1.1 schwarze 168: info.alloc = hash_alloc;
1.27 espie 169: info.free = hash_free;
1.22 schwarze 170: info.key_offset = offsetof(struct match, pageid);
1.1 schwarze 171:
172: *sz = cur = maxres = 0;
173: sql = NULL;
174: *res = NULL;
175: fd = -1;
176: e = NULL;
177: rc = 0;
178:
179: if (0 == argc)
180: goto out;
181: if (NULL == (e = exprcomp(search, argc, argv)))
182: goto out;
183:
1.3 schwarze 184: outbit = 0;
185: if (NULL != outkey) {
1.11 schwarze 186: for (indexbit = 0, iterbit = 1;
187: indexbit < mansearch_keymax;
188: indexbit++, iterbit <<= 1) {
189: if (0 == strcasecmp(outkey,
190: mansearch_keynames[indexbit])) {
191: outbit = iterbit;
1.3 schwarze 192: break;
193: }
194: }
195: }
196:
1.1 schwarze 197: /*
198: * Save a descriptor to the current working directory.
199: * Since pathnames in the "paths" variable might be relative,
200: * and we'll be chdir()ing into them, we need to keep a handle
201: * on our current directory from which to start the chdir().
202: */
203:
204: if (NULL == getcwd(buf, PATH_MAX)) {
1.20 schwarze 205: perror("getcwd");
1.1 schwarze 206: goto out;
207: } else if (-1 == (fd = open(buf, O_RDONLY, 0))) {
208: perror(buf);
209: goto out;
210: }
211:
1.6 schwarze 212: sql = sql_statement(e);
1.1 schwarze 213:
214: /*
215: * Loop over the directories (containing databases) for us to
216: * search.
217: * Don't let missing/bad databases/directories phase us.
218: * In each, try to open the resident database and, if it opens,
219: * scan it for our match expression.
220: */
221:
222: for (i = 0; i < paths->sz; i++) {
223: if (-1 == fchdir(fd)) {
224: perror(buf);
225: free(*res);
226: break;
227: } else if (-1 == chdir(paths->paths[i])) {
228: perror(paths->paths[i]);
229: continue;
1.24 schwarze 230: }
1.1 schwarze 231:
1.24 schwarze 232: c = sqlite3_open_v2(MANDOC_DB, &db,
233: SQLITE_OPEN_READONLY, NULL);
1.1 schwarze 234:
235: if (SQLITE_OK != c) {
236: perror(MANDOC_DB);
237: sqlite3_close(db);
238: continue;
239: }
240:
241: /*
242: * Define the SQL functions for substring
243: * and regular expression matching.
244: */
245:
246: c = sqlite3_create_function(db, "match", 2,
1.21 schwarze 247: SQLITE_UTF8 | SQLITE_DETERMINISTIC,
248: NULL, sql_match, NULL, NULL);
1.1 schwarze 249: assert(SQLITE_OK == c);
250: c = sqlite3_create_function(db, "regexp", 2,
1.21 schwarze 251: SQLITE_UTF8 | SQLITE_DETERMINISTIC,
252: NULL, sql_regexp, NULL, NULL);
1.1 schwarze 253: assert(SQLITE_OK == c);
254:
255: j = 1;
256: c = sqlite3_prepare_v2(db, sql, -1, &s, NULL);
257: if (SQLITE_OK != c)
258: fprintf(stderr, "%s\n", sqlite3_errmsg(db));
259:
260: for (ep = e; NULL != ep; ep = ep->next) {
261: if (NULL == ep->substr) {
262: SQL_BIND_BLOB(db, s, j, ep->regexp);
263: } else
264: SQL_BIND_TEXT(db, s, j, ep->substr);
1.18 schwarze 265: if (0 == ((TYPE_Nd | TYPE_Nm) & ep->bits))
1.17 schwarze 266: SQL_BIND_INT64(db, s, j, ep->bits);
1.1 schwarze 267: }
268:
269: memset(&htab, 0, sizeof(struct ohash));
270: ohash_init(&htab, 4, &info);
271:
272: /*
273: * Hash each entry on its [unique] document identifier.
274: * This is a uint64_t.
275: * Instead of using a hash function, simply convert the
276: * uint64_t to a uint32_t, the hash value's type.
277: * This gives good performance and preserves the
278: * distribution of buckets in the table.
279: */
280: while (SQLITE_ROW == (c = sqlite3_step(s))) {
1.22 schwarze 281: pageid = sqlite3_column_int64(s, 2);
1.24 schwarze 282: idx = ohash_lookup_memory(&htab,
283: (char *)&pageid, sizeof(uint64_t),
284: (uint32_t)pageid);
1.1 schwarze 285:
286: if (NULL != ohash_find(&htab, idx))
287: continue;
288:
289: mp = mandoc_calloc(1, sizeof(struct match));
1.22 schwarze 290: mp->pageid = pageid;
1.17 schwarze 291: mp->form = sqlite3_column_int(s, 1);
292: if (TYPE_Nd == outbit)
293: mp->desc = mandoc_strdup(
294: sqlite3_column_text(s, 0));
1.1 schwarze 295: ohash_insert(&htab, idx, mp);
296: }
297:
298: if (SQLITE_DONE != c)
299: fprintf(stderr, "%s\n", sqlite3_errmsg(db));
300:
301: sqlite3_finalize(s);
302:
1.24 schwarze 303: c = sqlite3_prepare_v2(db,
1.25 schwarze 304: "SELECT sec, arch, name, pageid FROM mlinks "
305: "WHERE pageid=? ORDER BY sec, arch, name",
1.1 schwarze 306: -1, &s, NULL);
307: if (SQLITE_OK != c)
308: fprintf(stderr, "%s\n", sqlite3_errmsg(db));
309:
1.3 schwarze 310: c = sqlite3_prepare_v2(db,
1.25 schwarze 311: "SELECT bits, key, pageid FROM keys "
312: "WHERE pageid=? AND bits & ?",
1.3 schwarze 313: -1, &s2, NULL);
314: if (SQLITE_OK != c)
315: fprintf(stderr, "%s\n", sqlite3_errmsg(db));
316:
1.1 schwarze 317: for (mp = ohash_first(&htab, &idx);
318: NULL != mp;
319: mp = ohash_next(&htab, &idx)) {
320: if (cur + 1 > maxres) {
321: maxres += 1024;
1.26 schwarze 322: *res = mandoc_reallocarray(*res,
323: maxres, sizeof(struct manpage));
1.1 schwarze 324: }
325: mpage = *res + cur;
326: mpage->form = mp->form;
1.22 schwarze 327: buildnames(mpage, db, s, mp->pageid,
1.10 schwarze 328: paths->paths[i], mp->form);
1.17 schwarze 329: mpage->output = TYPE_Nd & outbit ?
330: mp->desc : outbit ?
1.22 schwarze 331: buildoutput(db, s2, mp->pageid, outbit) : NULL;
1.1 schwarze 332:
333: free(mp);
334: cur++;
335: }
336:
337: sqlite3_finalize(s);
1.3 schwarze 338: sqlite3_finalize(s2);
1.1 schwarze 339: sqlite3_close(db);
340: ohash_delete(&htab);
341: }
342: rc = 1;
343: out:
1.20 schwarze 344: if (-1 != fd) {
345: if (-1 == fchdir(fd))
346: perror(buf);
347: close(fd);
348: }
1.1 schwarze 349: exprfree(e);
350: free(sql);
351: *sz = cur;
352: return(rc);
1.2 schwarze 353: }
354:
1.8 schwarze 355: static void
356: buildnames(struct manpage *mpage, sqlite3 *db, sqlite3_stmt *s,
1.22 schwarze 357: uint64_t pageid, const char *path, int form)
1.2 schwarze 358: {
1.13 schwarze 359: char *newnames, *prevsec, *prevarch;
1.10 schwarze 360: const char *oldnames, *sep1, *name, *sec, *sep2, *arch, *fsec;
1.2 schwarze 361: size_t i;
362: int c;
363:
1.16 schwarze 364: mpage->file = NULL;
1.8 schwarze 365: mpage->names = NULL;
1.13 schwarze 366: prevsec = prevarch = NULL;
1.2 schwarze 367: i = 1;
1.22 schwarze 368: SQL_BIND_INT64(db, s, i, pageid);
1.2 schwarze 369: while (SQLITE_ROW == (c = sqlite3_step(s))) {
1.8 schwarze 370:
1.13 schwarze 371: /* Decide whether we already have some names. */
1.8 schwarze 372:
373: if (NULL == mpage->names) {
1.2 schwarze 374: oldnames = "";
375: sep1 = "";
376: } else {
1.8 schwarze 377: oldnames = mpage->names;
1.2 schwarze 378: sep1 = ", ";
379: }
1.13 schwarze 380:
381: /* Fetch the next name. */
382:
1.10 schwarze 383: sec = sqlite3_column_text(s, 0);
384: arch = sqlite3_column_text(s, 1);
385: name = sqlite3_column_text(s, 2);
1.13 schwarze 386:
387: /* If the section changed, append the old one. */
388:
389: if (NULL != prevsec &&
390: (strcmp(sec, prevsec) ||
391: strcmp(arch, prevarch))) {
392: sep2 = '\0' == *prevarch ? "" : "/";
1.15 schwarze 393: mandoc_asprintf(&newnames, "%s(%s%s%s)",
394: oldnames, prevsec, sep2, prevarch);
1.13 schwarze 395: free(mpage->names);
396: oldnames = mpage->names = newnames;
397: free(prevsec);
398: free(prevarch);
399: prevsec = prevarch = NULL;
400: }
401:
402: /* Save the new section, to append it later. */
403:
404: if (NULL == prevsec) {
405: prevsec = mandoc_strdup(sec);
406: prevarch = mandoc_strdup(arch);
407: }
408:
409: /* Append the new name. */
410:
1.15 schwarze 411: mandoc_asprintf(&newnames, "%s%s%s",
412: oldnames, sep1, name);
1.8 schwarze 413: free(mpage->names);
414: mpage->names = newnames;
415:
416: /* Also save the first file name encountered. */
417:
418: if (NULL != mpage->file)
419: continue;
420:
1.10 schwarze 421: if (form) {
422: sep1 = "man";
423: fsec = sec;
424: } else {
425: sep1 = "cat";
426: fsec = "0";
427: }
1.13 schwarze 428: sep2 = '\0' == *arch ? "" : "/";
1.15 schwarze 429: mandoc_asprintf(&mpage->file, "%s/%s%s%s%s/%s.%s",
430: path, sep1, sec, sep2, arch, name, fsec);
1.2 schwarze 431: }
432: if (SQLITE_DONE != c)
433: fprintf(stderr, "%s\n", sqlite3_errmsg(db));
434: sqlite3_reset(s);
1.13 schwarze 435:
436: /* Append one final section to the names. */
437:
438: if (NULL != prevsec) {
439: sep2 = '\0' == *prevarch ? "" : "/";
1.15 schwarze 440: mandoc_asprintf(&newnames, "%s(%s%s%s)",
441: mpage->names, prevsec, sep2, prevarch);
1.13 schwarze 442: free(mpage->names);
443: mpage->names = newnames;
444: free(prevsec);
445: free(prevarch);
446: }
1.3 schwarze 447: }
448:
449: static char *
1.22 schwarze 450: buildoutput(sqlite3 *db, sqlite3_stmt *s, uint64_t pageid, uint64_t outbit)
1.3 schwarze 451: {
452: char *output, *newoutput;
453: const char *oldoutput, *sep1, *data;
454: size_t i;
455: int c;
456:
457: output = NULL;
458: i = 1;
1.22 schwarze 459: SQL_BIND_INT64(db, s, i, pageid);
1.3 schwarze 460: SQL_BIND_INT64(db, s, i, outbit);
461: while (SQLITE_ROW == (c = sqlite3_step(s))) {
462: if (NULL == output) {
463: oldoutput = "";
464: sep1 = "";
465: } else {
466: oldoutput = output;
467: sep1 = " # ";
468: }
469: data = sqlite3_column_text(s, 1);
1.15 schwarze 470: mandoc_asprintf(&newoutput, "%s%s%s",
471: oldoutput, sep1, data);
1.3 schwarze 472: free(output);
473: output = newoutput;
474: }
475: if (SQLITE_DONE != c)
476: fprintf(stderr, "%s\n", sqlite3_errmsg(db));
477: sqlite3_reset(s);
478: return(output);
1.1 schwarze 479: }
480:
481: /*
482: * Implement substring match as an application-defined SQL function.
483: * Using the SQL LIKE or GLOB operators instead would be a bad idea
484: * because that would require escaping metacharacters in the string
485: * being searched for.
486: */
487: static void
488: sql_match(sqlite3_context *context, int argc, sqlite3_value **argv)
489: {
490:
491: assert(2 == argc);
492: sqlite3_result_int(context, NULL != strcasestr(
493: (const char *)sqlite3_value_text(argv[1]),
494: (const char *)sqlite3_value_text(argv[0])));
495: }
496:
497: /*
498: * Implement regular expression match
499: * as an application-defined SQL function.
500: */
501: static void
502: sql_regexp(sqlite3_context *context, int argc, sqlite3_value **argv)
503: {
504:
505: assert(2 == argc);
506: sqlite3_result_int(context, !regexec(
507: (regex_t *)sqlite3_value_blob(argv[0]),
508: (const char *)sqlite3_value_text(argv[1]),
509: 0, NULL, 0));
510: }
511:
1.4 schwarze 512: static void
513: sql_append(char **sql, size_t *sz, const char *newstr, int count)
514: {
515: size_t newsz;
516:
517: newsz = 1 < count ? (size_t)count : strlen(newstr);
518: *sql = mandoc_realloc(*sql, *sz + newsz + 1);
519: if (1 < count)
520: memset(*sql + *sz, *newstr, (size_t)count);
521: else
522: memcpy(*sql + *sz, newstr, newsz);
523: *sz += newsz;
524: (*sql)[*sz] = '\0';
525: }
526:
1.1 schwarze 527: /*
528: * Prepare the search SQL statement.
529: */
530: static char *
1.6 schwarze 531: sql_statement(const struct expr *e)
1.1 schwarze 532: {
533: char *sql;
534: size_t sz;
1.4 schwarze 535: int needop;
1.1 schwarze 536:
1.25 schwarze 537: sql = mandoc_strdup(
538: "SELECT desc, form, pageid FROM mpages WHERE ");
1.1 schwarze 539: sz = strlen(sql);
540:
1.4 schwarze 541: for (needop = 0; NULL != e; e = e->next) {
542: if (e->and)
543: sql_append(&sql, &sz, " AND ", 1);
544: else if (needop)
545: sql_append(&sql, &sz, " OR ", 1);
546: if (e->open)
547: sql_append(&sql, &sz, "(", e->open);
1.17 schwarze 548: sql_append(&sql, &sz,
549: TYPE_Nd & e->bits
550: ? (NULL == e->substr
551: ? "desc REGEXP ?"
552: : "desc MATCH ?")
1.18 schwarze 553: : TYPE_Nm == e->bits
554: ? (NULL == e->substr
1.22 schwarze 555: ? "pageid IN (SELECT pageid FROM names "
1.18 schwarze 556: "WHERE name REGEXP ?)"
1.28 ! schwarze 557: : e->equal
! 558: ? "pageid IN (SELECT pageid FROM names "
! 559: "WHERE name = ?)"
1.22 schwarze 560: : "pageid IN (SELECT pageid FROM names "
1.18 schwarze 561: "WHERE name MATCH ?)")
1.17 schwarze 562: : (NULL == e->substr
1.22 schwarze 563: ? "pageid IN (SELECT pageid FROM keys "
1.17 schwarze 564: "WHERE key REGEXP ? AND bits & ?)"
1.22 schwarze 565: : "pageid IN (SELECT pageid FROM keys "
1.17 schwarze 566: "WHERE key MATCH ? AND bits & ?)"), 1);
1.4 schwarze 567: if (e->close)
568: sql_append(&sql, &sz, ")", e->close);
569: needop = 1;
1.1 schwarze 570: }
571:
572: return(sql);
573: }
574:
575: /*
576: * Compile a set of string tokens into an expression.
577: * Tokens in "argv" are assumed to be individual expression atoms (e.g.,
578: * "(", "foo=bar", etc.).
579: */
580: static struct expr *
581: exprcomp(const struct mansearch *search, int argc, char *argv[])
582: {
1.18 schwarze 583: uint64_t mask;
1.4 schwarze 584: int i, toopen, logic, igncase, toclose;
1.18 schwarze 585: struct expr *first, *prev, *cur, *next;
1.1 schwarze 586:
587: first = cur = NULL;
1.6 schwarze 588: logic = igncase = toclose = 0;
1.23 schwarze 589: toopen = NULL != search->sec || NULL != search->arch;
1.1 schwarze 590:
591: for (i = 0; i < argc; i++) {
1.4 schwarze 592: if (0 == strcmp("(", argv[i])) {
593: if (igncase)
594: goto fail;
595: toopen++;
596: toclose++;
597: continue;
598: } else if (0 == strcmp(")", argv[i])) {
599: if (toopen || logic || igncase || NULL == cur)
600: goto fail;
601: cur->close++;
602: if (0 > --toclose)
603: goto fail;
604: continue;
605: } else if (0 == strcmp("-a", argv[i])) {
606: if (toopen || logic || igncase || NULL == cur)
607: goto fail;
608: logic = 1;
609: continue;
610: } else if (0 == strcmp("-o", argv[i])) {
611: if (toopen || logic || igncase || NULL == cur)
612: goto fail;
613: logic = 2;
614: continue;
615: } else if (0 == strcmp("-i", argv[i])) {
616: if (igncase)
617: goto fail;
618: igncase = 1;
619: continue;
1.1 schwarze 620: }
1.4 schwarze 621: next = exprterm(search, argv[i], !igncase);
622: if (NULL == next)
623: goto fail;
1.17 schwarze 624: if (NULL == first)
625: first = next;
626: else
1.1 schwarze 627: cur->next = next;
1.18 schwarze 628: prev = cur = next;
1.17 schwarze 629:
630: /*
631: * Searching for descriptions must be split out
632: * because they are stored in the mpages table,
633: * not in the keys table.
634: */
635:
1.18 schwarze 636: for (mask = TYPE_Nm; mask <= TYPE_Nd; mask <<= 1) {
637: if (mask & cur->bits && ~mask & cur->bits) {
638: next = mandoc_calloc(1,
639: sizeof(struct expr));
640: memcpy(next, cur, sizeof(struct expr));
641: prev->open = 1;
642: cur->bits = mask;
643: cur->next = next;
644: cur = next;
645: cur->bits &= ~mask;
646: }
647: }
648: prev->and = (1 == logic);
649: prev->open += toopen;
650: if (cur != prev)
1.17 schwarze 651: cur->close = 1;
1.18 schwarze 652:
1.4 schwarze 653: toopen = logic = igncase = 0;
1.1 schwarze 654: }
1.6 schwarze 655: if (toopen || logic || igncase || toclose)
656: goto fail;
657:
1.23 schwarze 658: if (NULL != search->sec || NULL != search->arch)
659: cur->close++;
660: if (NULL != search->arch)
661: cur = exprspec(cur, TYPE_arch, search->arch, "^(%s|any)$");
662: if (NULL != search->sec)
663: exprspec(cur, TYPE_sec, search->sec, "^%s$");
1.6 schwarze 664:
665: return(first);
666:
1.4 schwarze 667: fail:
668: if (NULL != first)
669: exprfree(first);
670: return(NULL);
1.1 schwarze 671: }
672:
673: static struct expr *
1.6 schwarze 674: exprspec(struct expr *cur, uint64_t key, const char *value,
675: const char *format)
676: {
677: char errbuf[BUFSIZ];
678: char *cp;
679: int irc;
680:
1.15 schwarze 681: mandoc_asprintf(&cp, format, value);
1.6 schwarze 682: cur->next = mandoc_calloc(1, sizeof(struct expr));
683: cur = cur->next;
684: cur->and = 1;
685: cur->bits = key;
686: if (0 != (irc = regcomp(&cur->regexp, cp,
687: REG_EXTENDED | REG_NOSUB | REG_ICASE))) {
688: regerror(irc, &cur->regexp, errbuf, sizeof(errbuf));
689: fprintf(stderr, "regcomp: %s\n", errbuf);
690: cur->substr = value;
691: }
692: free(cp);
693: return(cur);
694: }
695:
696: static struct expr *
1.1 schwarze 697: exprterm(const struct mansearch *search, char *buf, int cs)
698: {
1.6 schwarze 699: char errbuf[BUFSIZ];
1.1 schwarze 700: struct expr *e;
1.28 ! schwarze 701: char *key, *val;
1.11 schwarze 702: uint64_t iterbit;
703: int i, irc;
1.1 schwarze 704:
705: if ('\0' == *buf)
706: return(NULL);
707:
708: e = mandoc_calloc(1, sizeof(struct expr));
709:
1.28 ! schwarze 710: if (MANSEARCH_MAN & search->flags) {
! 711: e->bits = search->deftype;
1.1 schwarze 712: e->substr = buf;
1.28 ! schwarze 713: e->equal = 1;
1.1 schwarze 714: return(e);
715: }
716:
717: /*
1.28 ! schwarze 718: * Look for an '=' or '~' operator,
! 719: * unless forced to some fixed macro keys.
1.1 schwarze 720: */
721:
1.28 ! schwarze 722: if (MANSEARCH_WHATIS & search->flags)
! 723: val = NULL;
! 724: else
! 725: val = strpbrk(buf, "=~");
! 726:
! 727: if (NULL == val) {
! 728: e->bits = search->deftype;
1.1 schwarze 729: e->substr = buf;
730:
1.28 ! schwarze 731: /*
! 732: * Found an operator.
! 733: * Regexp search is requested by !e->substr.
! 734: */
! 735:
! 736: } else {
! 737: if (val == buf)
! 738: e->bits = search->deftype;
! 739: if ('=' == *val)
! 740: e->substr = val + 1;
! 741: *val++ = '\0';
1.12 schwarze 742: if (NULL != strstr(buf, "arch"))
743: cs = 0;
1.28 ! schwarze 744: }
! 745:
! 746: /* Compile regular expressions. */
! 747:
! 748: if (MANSEARCH_WHATIS & search->flags) {
! 749: e->substr = NULL;
! 750: mandoc_asprintf(&val, "[[:<:]]%s[[:>:]]", buf);
! 751: }
! 752:
! 753: if (NULL == e->substr) {
! 754: irc = regcomp(&e->regexp, val,
! 755: REG_EXTENDED | REG_NOSUB | (cs ? 0 : REG_ICASE));
! 756: if (MANSEARCH_WHATIS & search->flags)
! 757: free(val);
! 758: if (irc) {
1.6 schwarze 759: regerror(irc, &e->regexp, errbuf, sizeof(errbuf));
760: fprintf(stderr, "regcomp: %s\n", errbuf);
1.1 schwarze 761: free(e);
762: return(NULL);
763: }
1.28 ! schwarze 764: }
! 765:
! 766: if (e->bits)
! 767: return(e);
1.1 schwarze 768:
769: /*
770: * Parse out all possible fields.
771: * If the field doesn't resolve, bail.
772: */
773:
774: while (NULL != (key = strsep(&buf, ","))) {
775: if ('\0' == *key)
776: continue;
1.11 schwarze 777: for (i = 0, iterbit = 1;
778: i < mansearch_keymax;
779: i++, iterbit <<= 1) {
780: if (0 == strcasecmp(key,
781: mansearch_keynames[i])) {
782: e->bits |= iterbit;
783: break;
784: }
785: }
786: if (i == mansearch_keymax) {
787: if (strcasecmp(key, "any")) {
788: free(e);
789: return(NULL);
790: }
791: e->bits |= ~0ULL;
1.1 schwarze 792: }
793: }
794:
795: return(e);
796: }
797:
798: static void
799: exprfree(struct expr *p)
800: {
801: struct expr *pp;
802:
803: while (NULL != p) {
804: pp = p->next;
805: free(p);
806: p = pp;
807: }
808: }
809:
810: static void *
1.27 espie 811: hash_calloc(size_t nmemb, size_t sz, void *arg)
1.1 schwarze 812: {
813:
1.27 espie 814: return(mandoc_calloc(nmemb, sz));
1.1 schwarze 815: }
816:
817: static void *
818: hash_alloc(size_t sz, void *arg)
819: {
820:
821: return(mandoc_malloc(sz));
822: }
823:
824: static void
1.27 espie 825: hash_free(void *p, void *arg)
1.1 schwarze 826: {
827:
828: free(p);
829: }