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