xref: /illumos-gate/usr/src/cmd/mandoc/mansearch.c (revision a40ea1a7)
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