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