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