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