mansearch.c revision 322249
1/* $OpenBSD: mansearch.c,v 1.50 2016/07/09 15:23:36 schwarze Exp $ */ 2/* 3 * Copyright (c) 2012 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2013-2017 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 AUTHORS DISCLAIM ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS 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 "config.h" 19 20#include <sys/mman.h> 21#include <sys/types.h> 22 23#include <assert.h> 24#if HAVE_ERR 25#include <err.h> 26#endif 27#include <errno.h> 28#include <fcntl.h> 29#include <glob.h> 30#include <limits.h> 31#include <regex.h> 32#include <stdio.h> 33#include <stdint.h> 34#include <stddef.h> 35#include <stdlib.h> 36#include <string.h> 37#include <unistd.h> 38 39#include "mandoc.h" 40#include "mandoc_aux.h" 41#include "mandoc_ohash.h" 42#include "manconf.h" 43#include "mansearch.h" 44#include "dbm.h" 45 46struct expr { 47 /* Used for terms: */ 48 struct dbm_match match; /* Match type and expression. */ 49 uint64_t bits; /* Type mask. */ 50 /* Used for OR and AND groups: */ 51 struct expr *next; /* Next child in the parent group. */ 52 struct expr *child; /* First child in this group. */ 53 enum { EXPR_TERM, EXPR_OR, EXPR_AND } type; 54}; 55 56const char *const mansearch_keynames[KEY_MAX] = { 57 "arch", "sec", "Xr", "Ar", "Fa", "Fl", "Dv", "Fn", 58 "Ic", "Pa", "Cm", "Li", "Em", "Cd", "Va", "Ft", 59 "Tn", "Er", "Ev", "Sy", "Sh", "In", "Ss", "Ox", 60 "An", "Mt", "St", "Bx", "At", "Nx", "Fx", "Lk", 61 "Ms", "Bsx", "Dx", "Rs", "Vt", "Lb", "Nm", "Nd" 62}; 63 64 65static struct ohash *manmerge(struct expr *, struct ohash *); 66static struct ohash *manmerge_term(struct expr *, struct ohash *); 67static struct ohash *manmerge_or(struct expr *, struct ohash *); 68static struct ohash *manmerge_and(struct expr *, struct ohash *); 69static char *buildnames(const struct dbm_page *); 70static char *buildoutput(size_t, struct dbm_page *); 71static size_t lstlen(const char *, size_t); 72static void lstcat(char *, size_t *, const char *, const char *); 73static int lstmatch(const char *, const char *); 74static struct expr *exprcomp(const struct mansearch *, 75 int, char *[], int *); 76static struct expr *expr_and(const struct mansearch *, 77 int, char *[], int *); 78static struct expr *exprterm(const struct mansearch *, 79 int, char *[], int *); 80static void exprfree(struct expr *); 81static int manpage_compare(const void *, const void *); 82 83 84int 85mansearch(const struct mansearch *search, 86 const struct manpaths *paths, 87 int argc, char *argv[], 88 struct manpage **res, size_t *sz) 89{ 90 char buf[PATH_MAX]; 91 struct dbm_res *rp; 92 struct expr *e; 93 struct dbm_page *page; 94 struct manpage *mpage; 95 struct ohash *htab; 96 size_t cur, i, maxres, outkey; 97 unsigned int slot; 98 int argi, chdir_status, getcwd_status, im; 99 100 argi = 0; 101 if ((e = exprcomp(search, argc, argv, &argi)) == NULL) { 102 *sz = 0; 103 return 0; 104 } 105 106 cur = maxres = 0; 107 if (res != NULL) 108 *res = NULL; 109 110 outkey = KEY_Nd; 111 if (search->outkey != NULL) 112 for (im = 0; im < KEY_MAX; im++) 113 if (0 == strcasecmp(search->outkey, 114 mansearch_keynames[im])) { 115 outkey = im; 116 break; 117 } 118 119 /* 120 * Remember the original working directory, if possible. 121 * This will be needed if the second or a later directory 122 * is given as a relative path. 123 * Do not error out if the current directory is not 124 * searchable: Maybe it won't be needed after all. 125 */ 126 127 if (getcwd(buf, PATH_MAX) == NULL) { 128 getcwd_status = 0; 129 (void)strlcpy(buf, strerror(errno), sizeof(buf)); 130 } else 131 getcwd_status = 1; 132 133 /* 134 * Loop over the directories (containing databases) for us to 135 * search. 136 * Don't let missing/bad databases/directories phase us. 137 * In each, try to open the resident database and, if it opens, 138 * scan it for our match expression. 139 */ 140 141 chdir_status = 0; 142 for (i = 0; i < paths->sz; i++) { 143 if (chdir_status && paths->paths[i][0] != '/') { 144 if ( ! getcwd_status) { 145 warnx("%s: getcwd: %s", paths->paths[i], buf); 146 continue; 147 } else if (chdir(buf) == -1) { 148 warn("%s", buf); 149 continue; 150 } 151 } 152 if (chdir(paths->paths[i]) == -1) { 153 warn("%s", paths->paths[i]); 154 continue; 155 } 156 chdir_status = 1; 157 158 if (dbm_open(MANDOC_DB) == -1) { 159 if (errno != ENOENT) 160 warn("%s/%s", paths->paths[i], MANDOC_DB); 161 continue; 162 } 163 164 if ((htab = manmerge(e, NULL)) == NULL) { 165 dbm_close(); 166 continue; 167 } 168 169 for (rp = ohash_first(htab, &slot); rp != NULL; 170 rp = ohash_next(htab, &slot)) { 171 page = dbm_page_get(rp->page); 172 173 if (lstmatch(search->sec, page->sect) == 0 || 174 lstmatch(search->arch, page->arch) == 0) 175 continue; 176 177 if (res == NULL) { 178 cur = 1; 179 break; 180 } 181 if (cur + 1 > maxres) { 182 maxres += 1024; 183 *res = mandoc_reallocarray(*res, 184 maxres, sizeof(**res)); 185 } 186 mpage = *res + cur; 187 mandoc_asprintf(&mpage->file, "%s/%s", 188 paths->paths[i], page->file + 1); 189 mpage->names = buildnames(page); 190 mpage->output = buildoutput(outkey, page); 191 mpage->ipath = i; 192 mpage->bits = rp->bits; 193 mpage->sec = *page->sect - '0'; 194 if (mpage->sec < 0 || mpage->sec > 9) 195 mpage->sec = 10; 196 mpage->form = *page->file; 197 free(rp); 198 cur++; 199 } 200 ohash_delete(htab); 201 free(htab); 202 dbm_close(); 203 204 /* 205 * In man(1) mode, prefer matches in earlier trees 206 * over matches in later trees. 207 */ 208 209 if (cur && search->firstmatch) 210 break; 211 } 212 if (res != NULL) 213 qsort(*res, cur, sizeof(struct manpage), manpage_compare); 214 if (chdir_status && getcwd_status && chdir(buf) == -1) 215 warn("%s", buf); 216 exprfree(e); 217 *sz = cur; 218 return res != NULL || cur; 219} 220 221/* 222 * Merge the results for the expression tree rooted at e 223 * into the the result list htab. 224 */ 225static struct ohash * 226manmerge(struct expr *e, struct ohash *htab) 227{ 228 switch (e->type) { 229 case EXPR_TERM: 230 return manmerge_term(e, htab); 231 case EXPR_OR: 232 return manmerge_or(e->child, htab); 233 case EXPR_AND: 234 return manmerge_and(e->child, htab); 235 default: 236 abort(); 237 } 238} 239 240static struct ohash * 241manmerge_term(struct expr *e, struct ohash *htab) 242{ 243 struct dbm_res res, *rp; 244 uint64_t ib; 245 unsigned int slot; 246 int im; 247 248 if (htab == NULL) { 249 htab = mandoc_malloc(sizeof(*htab)); 250 mandoc_ohash_init(htab, 4, offsetof(struct dbm_res, page)); 251 } 252 253 for (im = 0, ib = 1; im < KEY_MAX; im++, ib <<= 1) { 254 if ((e->bits & ib) == 0) 255 continue; 256 257 switch (ib) { 258 case TYPE_arch: 259 dbm_page_byarch(&e->match); 260 break; 261 case TYPE_sec: 262 dbm_page_bysect(&e->match); 263 break; 264 case TYPE_Nm: 265 dbm_page_byname(&e->match); 266 break; 267 case TYPE_Nd: 268 dbm_page_bydesc(&e->match); 269 break; 270 default: 271 dbm_page_bymacro(im - 2, &e->match); 272 break; 273 } 274 275 /* 276 * When hashing for deduplication, use the unique 277 * page ID itself instead of a hash function; 278 * that is quite efficient. 279 */ 280 281 for (;;) { 282 res = dbm_page_next(); 283 if (res.page == -1) 284 break; 285 slot = ohash_lookup_memory(htab, 286 (char *)&res, sizeof(res.page), res.page); 287 if ((rp = ohash_find(htab, slot)) != NULL) { 288 rp->bits |= res.bits; 289 continue; 290 } 291 rp = mandoc_malloc(sizeof(*rp)); 292 *rp = res; 293 ohash_insert(htab, slot, rp); 294 } 295 } 296 return htab; 297} 298 299static struct ohash * 300manmerge_or(struct expr *e, struct ohash *htab) 301{ 302 while (e != NULL) { 303 htab = manmerge(e, htab); 304 e = e->next; 305 } 306 return htab; 307} 308 309static struct ohash * 310manmerge_and(struct expr *e, struct ohash *htab) 311{ 312 struct ohash *hand, *h1, *h2; 313 struct dbm_res *res; 314 unsigned int slot1, slot2; 315 316 /* Evaluate the first term of the AND clause. */ 317 318 hand = manmerge(e, NULL); 319 320 while ((e = e->next) != NULL) { 321 322 /* Evaluate the next term and prepare for ANDing. */ 323 324 h2 = manmerge(e, NULL); 325 if (ohash_entries(h2) < ohash_entries(hand)) { 326 h1 = h2; 327 h2 = hand; 328 } else 329 h1 = hand; 330 hand = mandoc_malloc(sizeof(*hand)); 331 mandoc_ohash_init(hand, 4, offsetof(struct dbm_res, page)); 332 333 /* Keep all pages that are in both result sets. */ 334 335 for (res = ohash_first(h1, &slot1); res != NULL; 336 res = ohash_next(h1, &slot1)) { 337 if (ohash_find(h2, ohash_lookup_memory(h2, 338 (char *)res, sizeof(res->page), 339 res->page)) == NULL) 340 free(res); 341 else 342 ohash_insert(hand, ohash_lookup_memory(hand, 343 (char *)res, sizeof(res->page), 344 res->page), res); 345 } 346 347 /* Discard the merged results. */ 348 349 for (res = ohash_first(h2, &slot2); res != NULL; 350 res = ohash_next(h2, &slot2)) 351 free(res); 352 ohash_delete(h2); 353 free(h2); 354 ohash_delete(h1); 355 free(h1); 356 } 357 358 /* Merge the result of the AND into htab. */ 359 360 if (htab == NULL) 361 return hand; 362 363 for (res = ohash_first(hand, &slot1); res != NULL; 364 res = ohash_next(hand, &slot1)) { 365 slot2 = ohash_lookup_memory(htab, 366 (char *)res, sizeof(res->page), res->page); 367 if (ohash_find(htab, slot2) == NULL) 368 ohash_insert(htab, slot2, res); 369 else 370 free(res); 371 } 372 373 /* Discard the merged result. */ 374 375 ohash_delete(hand); 376 free(hand); 377 return htab; 378} 379 380void 381mansearch_free(struct manpage *res, size_t sz) 382{ 383 size_t i; 384 385 for (i = 0; i < sz; i++) { 386 free(res[i].file); 387 free(res[i].names); 388 free(res[i].output); 389 } 390 free(res); 391} 392 393static int 394manpage_compare(const void *vp1, const void *vp2) 395{ 396 const struct manpage *mp1, *mp2; 397 const char *cp1, *cp2; 398 size_t sz1, sz2; 399 int diff; 400 401 mp1 = vp1; 402 mp2 = vp2; 403 if ((diff = mp2->bits - mp1->bits) || 404 (diff = mp1->sec - mp2->sec)) 405 return diff; 406 407 /* Fall back to alphabetic ordering of names. */ 408 sz1 = strcspn(mp1->names, "("); 409 sz2 = strcspn(mp2->names, "("); 410 if (sz1 < sz2) 411 sz1 = sz2; 412 if ((diff = strncasecmp(mp1->names, mp2->names, sz1))) 413 return diff; 414 415 /* For identical names and sections, prefer arch-dependent. */ 416 cp1 = strchr(mp1->names + sz1, '/'); 417 cp2 = strchr(mp2->names + sz2, '/'); 418 return cp1 != NULL && cp2 != NULL ? strcasecmp(cp1, cp2) : 419 cp1 != NULL ? -1 : cp2 != NULL ? 1 : 0; 420} 421 422static char * 423buildnames(const struct dbm_page *page) 424{ 425 char *buf; 426 size_t i, sz; 427 428 sz = lstlen(page->name, 2) + 1 + lstlen(page->sect, 2) + 429 (page->arch == NULL ? 0 : 1 + lstlen(page->arch, 2)) + 2; 430 buf = mandoc_malloc(sz); 431 i = 0; 432 lstcat(buf, &i, page->name, ", "); 433 buf[i++] = '('; 434 lstcat(buf, &i, page->sect, ", "); 435 if (page->arch != NULL) { 436 buf[i++] = '/'; 437 lstcat(buf, &i, page->arch, ", "); 438 } 439 buf[i++] = ')'; 440 buf[i++] = '\0'; 441 assert(i == sz); 442 return buf; 443} 444 445/* 446 * Count the buffer space needed to print the NUL-terminated 447 * list of NUL-terminated strings, when printing sep separator 448 * characters between strings. 449 */ 450static size_t 451lstlen(const char *cp, size_t sep) 452{ 453 size_t sz; 454 455 for (sz = 0;; sz++) { 456 if (cp[0] == '\0') { 457 if (cp[1] == '\0') 458 break; 459 sz += sep - 1; 460 } else if (cp[0] < ' ') 461 sz--; 462 cp++; 463 } 464 return sz; 465} 466 467/* 468 * Print the NUL-terminated list of NUL-terminated strings 469 * into the buffer, seperating strings with sep. 470 */ 471static void 472lstcat(char *buf, size_t *i, const char *cp, const char *sep) 473{ 474 const char *s; 475 476 for (;;) { 477 if (cp[0] == '\0') { 478 if (cp[1] == '\0') 479 break; 480 s = sep; 481 while (*s != '\0') 482 buf[(*i)++] = *s++; 483 } else if (cp[0] >= ' ') 484 buf[(*i)++] = cp[0]; 485 cp++; 486 } 487} 488 489/* 490 * Return 1 if the string *want occurs in any of the strings 491 * in the NUL-terminated string list *have, or 0 otherwise. 492 * If either argument is NULL or empty, assume no filtering 493 * is desired and return 1. 494 */ 495static int 496lstmatch(const char *want, const char *have) 497{ 498 if (want == NULL || have == NULL || *have == '\0') 499 return 1; 500 while (*have != '\0') { 501 if (strcasestr(have, want) != NULL) 502 return 1; 503 have = strchr(have, '\0') + 1; 504 } 505 return 0; 506} 507 508/* 509 * Build a list of values taken by the macro im in the manual page. 510 */ 511static char * 512buildoutput(size_t im, struct dbm_page *page) 513{ 514 const char *oldoutput, *sep, *input; 515 char *output, *newoutput, *value; 516 size_t sz, i; 517 518 switch (im) { 519 case KEY_Nd: 520 return mandoc_strdup(page->desc); 521 case KEY_Nm: 522 input = page->name; 523 break; 524 case KEY_sec: 525 input = page->sect; 526 break; 527 case KEY_arch: 528 input = page->arch; 529 if (input == NULL) 530 input = "all\0"; 531 break; 532 default: 533 input = NULL; 534 break; 535 } 536 537 if (input != NULL) { 538 sz = lstlen(input, 3) + 1; 539 output = mandoc_malloc(sz); 540 i = 0; 541 lstcat(output, &i, input, " # "); 542 output[i++] = '\0'; 543 assert(i == sz); 544 return output; 545 } 546 547 output = NULL; 548 dbm_macro_bypage(im - 2, page->addr); 549 while ((value = dbm_macro_next()) != NULL) { 550 if (output == NULL) { 551 oldoutput = ""; 552 sep = ""; 553 } else { 554 oldoutput = output; 555 sep = " # "; 556 } 557 mandoc_asprintf(&newoutput, "%s%s%s", oldoutput, sep, value); 558 free(output); 559 output = newoutput; 560 } 561 return output; 562} 563 564/* 565 * Compile a set of string tokens into an expression. 566 * Tokens in "argv" are assumed to be individual expression atoms (e.g., 567 * "(", "foo=bar", etc.). 568 */ 569static struct expr * 570exprcomp(const struct mansearch *search, int argc, char *argv[], int *argi) 571{ 572 struct expr *parent, *child; 573 int needterm, nested; 574 575 if ((nested = *argi) == argc) 576 return NULL; 577 needterm = 1; 578 parent = child = NULL; 579 while (*argi < argc) { 580 if (strcmp(")", argv[*argi]) == 0) { 581 if (needterm) 582 warnx("missing term " 583 "before closing parenthesis"); 584 needterm = 0; 585 if (nested) 586 break; 587 warnx("ignoring unmatched right parenthesis"); 588 ++*argi; 589 continue; 590 } 591 if (strcmp("-o", argv[*argi]) == 0) { 592 if (needterm) { 593 if (*argi > 0) 594 warnx("ignoring -o after %s", 595 argv[*argi - 1]); 596 else 597 warnx("ignoring initial -o"); 598 } 599 needterm = 1; 600 ++*argi; 601 continue; 602 } 603 needterm = 0; 604 if (child == NULL) { 605 child = expr_and(search, argc, argv, argi); 606 continue; 607 } 608 if (parent == NULL) { 609 parent = mandoc_calloc(1, sizeof(*parent)); 610 parent->type = EXPR_OR; 611 parent->next = NULL; 612 parent->child = child; 613 } 614 child->next = expr_and(search, argc, argv, argi); 615 child = child->next; 616 } 617 if (needterm && *argi) 618 warnx("ignoring trailing %s", argv[*argi - 1]); 619 return parent == NULL ? child : parent; 620} 621 622static struct expr * 623expr_and(const struct mansearch *search, int argc, char *argv[], int *argi) 624{ 625 struct expr *parent, *child; 626 int needterm; 627 628 needterm = 1; 629 parent = child = NULL; 630 while (*argi < argc) { 631 if (strcmp(")", argv[*argi]) == 0) { 632 if (needterm) 633 warnx("missing term " 634 "before closing parenthesis"); 635 needterm = 0; 636 break; 637 } 638 if (strcmp("-o", argv[*argi]) == 0) 639 break; 640 if (strcmp("-a", argv[*argi]) == 0) { 641 if (needterm) { 642 if (*argi > 0) 643 warnx("ignoring -a after %s", 644 argv[*argi - 1]); 645 else 646 warnx("ignoring initial -a"); 647 } 648 needterm = 1; 649 ++*argi; 650 continue; 651 } 652 if (needterm == 0) 653 break; 654 if (child == NULL) { 655 child = exprterm(search, argc, argv, argi); 656 if (child != NULL) 657 needterm = 0; 658 continue; 659 } 660 needterm = 0; 661 if (parent == NULL) { 662 parent = mandoc_calloc(1, sizeof(*parent)); 663 parent->type = EXPR_AND; 664 parent->next = NULL; 665 parent->child = child; 666 } 667 child->next = exprterm(search, argc, argv, argi); 668 if (child->next != NULL) { 669 child = child->next; 670 needterm = 0; 671 } 672 } 673 if (needterm && *argi) 674 warnx("ignoring trailing %s", argv[*argi - 1]); 675 return parent == NULL ? child : parent; 676} 677 678static struct expr * 679exprterm(const struct mansearch *search, int argc, char *argv[], int *argi) 680{ 681 char errbuf[BUFSIZ]; 682 struct expr *e; 683 char *key, *val; 684 uint64_t iterbit; 685 int cs, i, irc; 686 687 if (strcmp("(", argv[*argi]) == 0) { 688 ++*argi; 689 e = exprcomp(search, argc, argv, argi); 690 if (*argi < argc) { 691 assert(strcmp(")", argv[*argi]) == 0); 692 ++*argi; 693 } else 694 warnx("unclosed parenthesis"); 695 return e; 696 } 697 698 if (strcmp("-i", argv[*argi]) == 0 && *argi + 1 < argc) { 699 cs = 0; 700 ++*argi; 701 } else 702 cs = 1; 703 704 e = mandoc_calloc(1, sizeof(*e)); 705 e->type = EXPR_TERM; 706 e->bits = 0; 707 e->next = NULL; 708 e->child = NULL; 709 710 if (search->argmode == ARG_NAME) { 711 e->bits = TYPE_Nm; 712 e->match.type = DBM_EXACT; 713 e->match.str = argv[(*argi)++]; 714 return e; 715 } 716 717 /* 718 * Separate macro keys from search string. 719 * If needed, request regular expression handling. 720 */ 721 722 if (search->argmode == ARG_WORD) { 723 e->bits = TYPE_Nm; 724 e->match.type = DBM_REGEX; 725#if HAVE_REWB_BSD 726 mandoc_asprintf(&val, "[[:<:]]%s[[:>:]]", argv[*argi]); 727#elif HAVE_REWB_SYSV 728 mandoc_asprintf(&val, "\\<%s\\>", argv[*argi]); 729#else 730 mandoc_asprintf(&val, 731 "(^|[^a-zA-Z01-9_])%s([^a-zA-Z01-9_]|$)", argv[*argi]); 732#endif 733 cs = 0; 734 } else if ((val = strpbrk(argv[*argi], "=~")) == NULL) { 735 e->bits = TYPE_Nm | TYPE_Nd; 736 e->match.type = DBM_SUB; 737 e->match.str = argv[*argi]; 738 } else { 739 if (val == argv[*argi]) 740 e->bits = TYPE_Nm | TYPE_Nd; 741 if (*val == '=') { 742 e->match.type = DBM_SUB; 743 e->match.str = val + 1; 744 } else 745 e->match.type = DBM_REGEX; 746 *val++ = '\0'; 747 if (strstr(argv[*argi], "arch") != NULL) 748 cs = 0; 749 } 750 751 /* Compile regular expressions. */ 752 753 if (e->match.type == DBM_REGEX) { 754 e->match.re = mandoc_malloc(sizeof(*e->match.re)); 755 irc = regcomp(e->match.re, val, 756 REG_EXTENDED | REG_NOSUB | (cs ? 0 : REG_ICASE)); 757 if (irc) { 758 regerror(irc, e->match.re, errbuf, sizeof(errbuf)); 759 warnx("regcomp /%s/: %s", val, errbuf); 760 } 761 if (search->argmode == ARG_WORD) 762 free(val); 763 if (irc) { 764 free(e->match.re); 765 free(e); 766 ++*argi; 767 return NULL; 768 } 769 } 770 771 if (e->bits) { 772 ++*argi; 773 return e; 774 } 775 776 /* 777 * Parse out all possible fields. 778 * If the field doesn't resolve, bail. 779 */ 780 781 while (NULL != (key = strsep(&argv[*argi], ","))) { 782 if ('\0' == *key) 783 continue; 784 for (i = 0, iterbit = 1; i < KEY_MAX; i++, iterbit <<= 1) { 785 if (0 == strcasecmp(key, mansearch_keynames[i])) { 786 e->bits |= iterbit; 787 break; 788 } 789 } 790 if (i == KEY_MAX) { 791 if (strcasecmp(key, "any")) 792 warnx("treating unknown key " 793 "\"%s\" as \"any\"", key); 794 e->bits |= ~0ULL; 795 } 796 } 797 798 ++*argi; 799 return e; 800} 801 802static void 803exprfree(struct expr *e) 804{ 805 if (e->next != NULL) 806 exprfree(e->next); 807 if (e->child != NULL) 808 exprfree(e->child); 809 free(e); 810} 811