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