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