xref: /illumos-gate/usr/src/cmd/mandoc/mansearch.c (revision 6640c13b)
1*6640c13bSYuri Pankov /*	$Id: mansearch.c,v 1.77 2017/08/22 17:50:11 schwarze Exp $ */
2a40ea1a7SYuri Pankov /*
3a40ea1a7SYuri Pankov  * Copyright (c) 2012 Kristaps Dzonsons <kristaps@bsd.lv>
4c66b8046SYuri 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 *);
70c66b8046SYuri Pankov static	char		*buildoutput(size_t, struct dbm_page *);
71c66b8046SYuri Pankov static	size_t		 lstlen(const char *, size_t);
72c66b8046SYuri 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;
107c66b8046SYuri 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) {
159c66b8046SYuri 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 ||
174c66b8046SYuri Pankov 			    lstmatch(search->arch, page->arch) == 0 ||
175c66b8046SYuri Pankov 			    (search->argmode == ARG_NAME &&
176c66b8046SYuri Pankov 			     rp->bits <= (int32_t)(NAME_SYN & NAME_MASK)))
177a40ea1a7SYuri Pankov 				continue;
178a40ea1a7SYuri Pankov 
179c66b8046SYuri Pankov 			if (res == NULL) {
180c66b8046SYuri Pankov 				cur = 1;
181c66b8046SYuri Pankov 				break;
182c66b8046SYuri 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);
191*6640c13bSYuri Pankov 			if (access(chdir_status ? page->file + 1 :
192*6640c13bSYuri Pankov 			    mpage->file, R_OK) == -1) {
193*6640c13bSYuri Pankov 				warn("%s", mpage->file);
194*6640c13bSYuri Pankov 				warnx("outdated mandoc.db contains "
195*6640c13bSYuri Pankov 				    "bogus %s entry, run makewhatis %s",
196*6640c13bSYuri Pankov 				    page->file + 1, paths->paths[i]);
197*6640c13bSYuri Pankov 				free(mpage->file);
198*6640c13bSYuri Pankov 				free(rp);
199*6640c13bSYuri Pankov 				continue;
200*6640c13bSYuri Pankov 			}
201a40ea1a7SYuri Pankov 			mpage->names = buildnames(page);
202c66b8046SYuri Pankov 			mpage->output = buildoutput(outkey, page);
203a40ea1a7SYuri Pankov 			mpage->ipath = i;
204a40ea1a7SYuri Pankov 			mpage->bits = rp->bits;
205a40ea1a7SYuri Pankov 			mpage->sec = *page->sect - '0';
206a40ea1a7SYuri Pankov 			if (mpage->sec < 0 || mpage->sec > 9)
207a40ea1a7SYuri Pankov 				mpage->sec = 10;
208a40ea1a7SYuri Pankov 			mpage->form = *page->file;
209a40ea1a7SYuri Pankov 			free(rp);
210a40ea1a7SYuri Pankov 			cur++;
211a40ea1a7SYuri Pankov 		}
212a40ea1a7SYuri Pankov 		ohash_delete(htab);
213a40ea1a7SYuri Pankov 		free(htab);
214a40ea1a7SYuri Pankov 		dbm_close();
215a40ea1a7SYuri Pankov 
216a40ea1a7SYuri Pankov 		/*
217a40ea1a7SYuri Pankov 		 * In man(1) mode, prefer matches in earlier trees
218a40ea1a7SYuri Pankov 		 * over matches in later trees.
219a40ea1a7SYuri Pankov 		 */
220a40ea1a7SYuri Pankov 
221a40ea1a7SYuri Pankov 		if (cur && search->firstmatch)
222a40ea1a7SYuri Pankov 			break;
223a40ea1a7SYuri Pankov 	}
224c66b8046SYuri Pankov 	if (res != NULL)
225a40ea1a7SYuri Pankov 		qsort(*res, cur, sizeof(struct manpage), manpage_compare);
226a40ea1a7SYuri Pankov 	if (chdir_status && getcwd_status && chdir(buf) == -1)
227a40ea1a7SYuri Pankov 		warn("%s", buf);
228a40ea1a7SYuri Pankov 	exprfree(e);
229a40ea1a7SYuri Pankov 	*sz = cur;
230c66b8046SYuri Pankov 	return res != NULL || cur;
231a40ea1a7SYuri Pankov }
232a40ea1a7SYuri Pankov 
233a40ea1a7SYuri Pankov /*
234a40ea1a7SYuri Pankov  * Merge the results for the expression tree rooted at e
235a40ea1a7SYuri Pankov  * into the the result list htab.
236a40ea1a7SYuri Pankov  */
237a40ea1a7SYuri Pankov static struct ohash *
238a40ea1a7SYuri Pankov manmerge(struct expr *e, struct ohash *htab)
239a40ea1a7SYuri Pankov {
240a40ea1a7SYuri Pankov 	switch (e->type) {
241a40ea1a7SYuri Pankov 	case EXPR_TERM:
242a40ea1a7SYuri Pankov 		return manmerge_term(e, htab);
243a40ea1a7SYuri Pankov 	case EXPR_OR:
244a40ea1a7SYuri Pankov 		return manmerge_or(e->child, htab);
245a40ea1a7SYuri Pankov 	case EXPR_AND:
246a40ea1a7SYuri Pankov 		return manmerge_and(e->child, htab);
247a40ea1a7SYuri Pankov 	default:
248a40ea1a7SYuri Pankov 		abort();
249a40ea1a7SYuri Pankov 	}
250a40ea1a7SYuri Pankov }
251a40ea1a7SYuri Pankov 
252a40ea1a7SYuri Pankov static struct ohash *
253a40ea1a7SYuri Pankov manmerge_term(struct expr *e, struct ohash *htab)
254a40ea1a7SYuri Pankov {
255a40ea1a7SYuri Pankov 	struct dbm_res	 res, *rp;
256a40ea1a7SYuri Pankov 	uint64_t	 ib;
257a40ea1a7SYuri Pankov 	unsigned int	 slot;
258a40ea1a7SYuri Pankov 	int		 im;
259a40ea1a7SYuri Pankov 
260a40ea1a7SYuri Pankov 	if (htab == NULL) {
261a40ea1a7SYuri Pankov 		htab = mandoc_malloc(sizeof(*htab));
262a40ea1a7SYuri Pankov 		mandoc_ohash_init(htab, 4, offsetof(struct dbm_res, page));
263a40ea1a7SYuri Pankov 	}
264a40ea1a7SYuri Pankov 
265a40ea1a7SYuri Pankov 	for (im = 0, ib = 1; im < KEY_MAX; im++, ib <<= 1) {
266a40ea1a7SYuri Pankov 		if ((e->bits & ib) == 0)
267a40ea1a7SYuri Pankov 			continue;
268a40ea1a7SYuri Pankov 
269a40ea1a7SYuri Pankov 		switch (ib) {
270a40ea1a7SYuri Pankov 		case TYPE_arch:
271a40ea1a7SYuri Pankov 			dbm_page_byarch(&e->match);
272a40ea1a7SYuri Pankov 			break;
273a40ea1a7SYuri Pankov 		case TYPE_sec:
274a40ea1a7SYuri Pankov 			dbm_page_bysect(&e->match);
275a40ea1a7SYuri Pankov 			break;
276a40ea1a7SYuri Pankov 		case TYPE_Nm:
277a40ea1a7SYuri Pankov 			dbm_page_byname(&e->match);
278a40ea1a7SYuri Pankov 			break;
279a40ea1a7SYuri Pankov 		case TYPE_Nd:
280a40ea1a7SYuri Pankov 			dbm_page_bydesc(&e->match);
281a40ea1a7SYuri Pankov 			break;
282a40ea1a7SYuri Pankov 		default:
283a40ea1a7SYuri Pankov 			dbm_page_bymacro(im - 2, &e->match);
284a40ea1a7SYuri Pankov 			break;
285a40ea1a7SYuri Pankov 		}
286a40ea1a7SYuri Pankov 
287a40ea1a7SYuri Pankov 		/*
288a40ea1a7SYuri Pankov 		 * When hashing for deduplication, use the unique
289a40ea1a7SYuri Pankov 		 * page ID itself instead of a hash function;
290a40ea1a7SYuri Pankov 		 * that is quite efficient.
291a40ea1a7SYuri Pankov 		 */
292a40ea1a7SYuri Pankov 
293a40ea1a7SYuri Pankov 		for (;;) {
294a40ea1a7SYuri Pankov 			res = dbm_page_next();
295a40ea1a7SYuri Pankov 			if (res.page == -1)
296a40ea1a7SYuri Pankov 				break;
297a40ea1a7SYuri Pankov 			slot = ohash_lookup_memory(htab,
298a40ea1a7SYuri Pankov 			    (char *)&res, sizeof(res.page), res.page);
299a40ea1a7SYuri Pankov 			if ((rp = ohash_find(htab, slot)) != NULL) {
300a40ea1a7SYuri Pankov 				rp->bits |= res.bits;
301a40ea1a7SYuri Pankov 				continue;
302a40ea1a7SYuri Pankov 			}
303a40ea1a7SYuri Pankov 			rp = mandoc_malloc(sizeof(*rp));
304a40ea1a7SYuri Pankov 			*rp = res;
305a40ea1a7SYuri Pankov 			ohash_insert(htab, slot, rp);
306a40ea1a7SYuri Pankov 		}
307a40ea1a7SYuri Pankov 	}
308a40ea1a7SYuri Pankov 	return htab;
309a40ea1a7SYuri Pankov }
310a40ea1a7SYuri Pankov 
311a40ea1a7SYuri Pankov static struct ohash *
312a40ea1a7SYuri Pankov manmerge_or(struct expr *e, struct ohash *htab)
313a40ea1a7SYuri Pankov {
314a40ea1a7SYuri Pankov 	while (e != NULL) {
315a40ea1a7SYuri Pankov 		htab = manmerge(e, htab);
316a40ea1a7SYuri Pankov 		e = e->next;
317a40ea1a7SYuri Pankov 	}
318a40ea1a7SYuri Pankov 	return htab;
319a40ea1a7SYuri Pankov }
320a40ea1a7SYuri Pankov 
321a40ea1a7SYuri Pankov static struct ohash *
322a40ea1a7SYuri Pankov manmerge_and(struct expr *e, struct ohash *htab)
323a40ea1a7SYuri Pankov {
324a40ea1a7SYuri Pankov 	struct ohash	*hand, *h1, *h2;
325a40ea1a7SYuri Pankov 	struct dbm_res	*res;
326a40ea1a7SYuri Pankov 	unsigned int	 slot1, slot2;
327a40ea1a7SYuri Pankov 
328a40ea1a7SYuri Pankov 	/* Evaluate the first term of the AND clause. */
329a40ea1a7SYuri Pankov 
330a40ea1a7SYuri Pankov 	hand = manmerge(e, NULL);
331a40ea1a7SYuri Pankov 
332a40ea1a7SYuri Pankov 	while ((e = e->next) != NULL) {
333a40ea1a7SYuri Pankov 
334a40ea1a7SYuri Pankov 		/* Evaluate the next term and prepare for ANDing. */
335a40ea1a7SYuri Pankov 
336a40ea1a7SYuri Pankov 		h2 = manmerge(e, NULL);
337a40ea1a7SYuri Pankov 		if (ohash_entries(h2) < ohash_entries(hand)) {
338a40ea1a7SYuri Pankov 			h1 = h2;
339a40ea1a7SYuri Pankov 			h2 = hand;
340a40ea1a7SYuri Pankov 		} else
341a40ea1a7SYuri Pankov 			h1 = hand;
342a40ea1a7SYuri Pankov 		hand = mandoc_malloc(sizeof(*hand));
343a40ea1a7SYuri Pankov 		mandoc_ohash_init(hand, 4, offsetof(struct dbm_res, page));
344a40ea1a7SYuri Pankov 
345a40ea1a7SYuri Pankov 		/* Keep all pages that are in both result sets. */
346a40ea1a7SYuri Pankov 
347a40ea1a7SYuri Pankov 		for (res = ohash_first(h1, &slot1); res != NULL;
348a40ea1a7SYuri Pankov 		    res = ohash_next(h1, &slot1)) {
349a40ea1a7SYuri Pankov 			if (ohash_find(h2, ohash_lookup_memory(h2,
350a40ea1a7SYuri Pankov 			    (char *)res, sizeof(res->page),
351a40ea1a7SYuri Pankov 			    res->page)) == NULL)
352a40ea1a7SYuri Pankov 				free(res);
353a40ea1a7SYuri Pankov 			else
354a40ea1a7SYuri Pankov 				ohash_insert(hand, ohash_lookup_memory(hand,
355a40ea1a7SYuri Pankov 				    (char *)res, sizeof(res->page),
356a40ea1a7SYuri Pankov 				    res->page), res);
357a40ea1a7SYuri Pankov 		}
358a40ea1a7SYuri Pankov 
359a40ea1a7SYuri Pankov 		/* Discard the merged results. */
360a40ea1a7SYuri Pankov 
361a40ea1a7SYuri Pankov 		for (res = ohash_first(h2, &slot2); res != NULL;
362a40ea1a7SYuri Pankov 		    res = ohash_next(h2, &slot2))
363a40ea1a7SYuri Pankov 			free(res);
364a40ea1a7SYuri Pankov 		ohash_delete(h2);
365a40ea1a7SYuri Pankov 		free(h2);
366a40ea1a7SYuri Pankov 		ohash_delete(h1);
367a40ea1a7SYuri Pankov 		free(h1);
368a40ea1a7SYuri Pankov 	}
369a40ea1a7SYuri Pankov 
370a40ea1a7SYuri Pankov 	/* Merge the result of the AND into htab. */
371a40ea1a7SYuri Pankov 
372a40ea1a7SYuri Pankov 	if (htab == NULL)
373a40ea1a7SYuri Pankov 		return hand;
374a40ea1a7SYuri Pankov 
375a40ea1a7SYuri Pankov 	for (res = ohash_first(hand, &slot1); res != NULL;
376a40ea1a7SYuri Pankov 	    res = ohash_next(hand, &slot1)) {
377a40ea1a7SYuri Pankov 		slot2 = ohash_lookup_memory(htab,
378a40ea1a7SYuri Pankov 		    (char *)res, sizeof(res->page), res->page);
379a40ea1a7SYuri Pankov 		if (ohash_find(htab, slot2) == NULL)
380a40ea1a7SYuri Pankov 			ohash_insert(htab, slot2, res);
381a40ea1a7SYuri Pankov 		else
382a40ea1a7SYuri Pankov 			free(res);
383a40ea1a7SYuri Pankov 	}
384a40ea1a7SYuri Pankov 
385a40ea1a7SYuri Pankov 	/* Discard the merged result. */
386a40ea1a7SYuri Pankov 
387a40ea1a7SYuri Pankov 	ohash_delete(hand);
388a40ea1a7SYuri Pankov 	free(hand);
389a40ea1a7SYuri Pankov 	return htab;
390a40ea1a7SYuri Pankov }
391a40ea1a7SYuri Pankov 
392a40ea1a7SYuri Pankov void
393a40ea1a7SYuri Pankov mansearch_free(struct manpage *res, size_t sz)
394a40ea1a7SYuri Pankov {
395a40ea1a7SYuri Pankov 	size_t	 i;
396a40ea1a7SYuri Pankov 
397a40ea1a7SYuri Pankov 	for (i = 0; i < sz; i++) {
398a40ea1a7SYuri Pankov 		free(res[i].file);
399a40ea1a7SYuri Pankov 		free(res[i].names);
400a40ea1a7SYuri Pankov 		free(res[i].output);
401a40ea1a7SYuri Pankov 	}
402a40ea1a7SYuri Pankov 	free(res);
403a40ea1a7SYuri Pankov }
404a40ea1a7SYuri Pankov 
405a40ea1a7SYuri Pankov static int
406a40ea1a7SYuri Pankov manpage_compare(const void *vp1, const void *vp2)
407a40ea1a7SYuri Pankov {
408a40ea1a7SYuri Pankov 	const struct manpage	*mp1, *mp2;
409c66b8046SYuri Pankov 	const char		*cp1, *cp2;
410c66b8046SYuri Pankov 	size_t			 sz1, sz2;
411a40ea1a7SYuri Pankov 	int			 diff;
412a40ea1a7SYuri Pankov 
413a40ea1a7SYuri Pankov 	mp1 = vp1;
414a40ea1a7SYuri Pankov 	mp2 = vp2;
415c66b8046SYuri Pankov 	if ((diff = mp2->bits - mp1->bits) ||
416c66b8046SYuri Pankov 	    (diff = mp1->sec - mp2->sec))
417c66b8046SYuri Pankov 		return diff;
418c66b8046SYuri Pankov 
419c66b8046SYuri Pankov 	/* Fall back to alphabetic ordering of names. */
420c66b8046SYuri Pankov 	sz1 = strcspn(mp1->names, "(");
421c66b8046SYuri Pankov 	sz2 = strcspn(mp2->names, "(");
422c66b8046SYuri Pankov 	if (sz1 < sz2)
423c66b8046SYuri Pankov 		sz1 = sz2;
424c66b8046SYuri Pankov 	if ((diff = strncasecmp(mp1->names, mp2->names, sz1)))
425c66b8046SYuri Pankov 		return diff;
426c66b8046SYuri Pankov 
427c66b8046SYuri Pankov 	/* For identical names and sections, prefer arch-dependent. */
428c66b8046SYuri Pankov 	cp1 = strchr(mp1->names + sz1, '/');
429c66b8046SYuri Pankov 	cp2 = strchr(mp2->names + sz2, '/');
430c66b8046SYuri Pankov 	return cp1 != NULL && cp2 != NULL ? strcasecmp(cp1, cp2) :
431c66b8046SYuri Pankov 	    cp1 != NULL ? -1 : cp2 != NULL ? 1 : 0;
432a40ea1a7SYuri Pankov }
433a40ea1a7SYuri Pankov 
434a40ea1a7SYuri Pankov static char *
435a40ea1a7SYuri Pankov buildnames(const struct dbm_page *page)
436a40ea1a7SYuri Pankov {
437a40ea1a7SYuri Pankov 	char	*buf;
438a40ea1a7SYuri Pankov 	size_t	 i, sz;
439a40ea1a7SYuri Pankov 
440c66b8046SYuri Pankov 	sz = lstlen(page->name, 2) + 1 + lstlen(page->sect, 2) +
441c66b8046SYuri Pankov 	    (page->arch == NULL ? 0 : 1 + lstlen(page->arch, 2)) + 2;
442a40ea1a7SYuri Pankov 	buf = mandoc_malloc(sz);
443a40ea1a7SYuri Pankov 	i = 0;
444c66b8046SYuri Pankov 	lstcat(buf, &i, page->name, ", ");
445a40ea1a7SYuri Pankov 	buf[i++] = '(';
446c66b8046SYuri Pankov 	lstcat(buf, &i, page->sect, ", ");
447a40ea1a7SYuri Pankov 	if (page->arch != NULL) {
448a40ea1a7SYuri Pankov 		buf[i++] = '/';
449c66b8046SYuri Pankov 		lstcat(buf, &i, page->arch, ", ");
450a40ea1a7SYuri Pankov 	}
451a40ea1a7SYuri Pankov 	buf[i++] = ')';
452a40ea1a7SYuri Pankov 	buf[i++] = '\0';
453a40ea1a7SYuri Pankov 	assert(i == sz);
454a40ea1a7SYuri Pankov 	return buf;
455a40ea1a7SYuri Pankov }
456a40ea1a7SYuri Pankov 
457a40ea1a7SYuri Pankov /*
458a40ea1a7SYuri Pankov  * Count the buffer space needed to print the NUL-terminated
459c66b8046SYuri Pankov  * list of NUL-terminated strings, when printing sep separator
460a40ea1a7SYuri Pankov  * characters between strings.
461a40ea1a7SYuri Pankov  */
462a40ea1a7SYuri Pankov static size_t
463c66b8046SYuri Pankov lstlen(const char *cp, size_t sep)
464a40ea1a7SYuri Pankov {
465a40ea1a7SYuri Pankov 	size_t	 sz;
466a40ea1a7SYuri Pankov 
467c66b8046SYuri Pankov 	for (sz = 0; *cp != '\0'; cp++) {
468c66b8046SYuri Pankov 
469c66b8046SYuri Pankov 		/* Skip names appearing only in the SYNOPSIS. */
470c66b8046SYuri Pankov 		if (*cp <= (char)(NAME_SYN & NAME_MASK)) {
471c66b8046SYuri Pankov 			while (*cp != '\0')
472a40ea1a7SYuri Pankov 				cp++;
473c66b8046SYuri Pankov 			continue;
474c66b8046SYuri Pankov 		}
475c66b8046SYuri Pankov 
476c66b8046SYuri Pankov 		/* Skip name class markers. */
477c66b8046SYuri Pankov 		if (*cp < ' ')
478c66b8046SYuri Pankov 			cp++;
479c66b8046SYuri Pankov 
480c66b8046SYuri Pankov 		/* Print a separator before each but the first string. */
481c66b8046SYuri Pankov 		if (sz)
482c66b8046SYuri Pankov 			sz += sep;
483c66b8046SYuri Pankov 
484c66b8046SYuri Pankov 		/* Copy one string. */
485c66b8046SYuri Pankov 		while (*cp != '\0') {
486c66b8046SYuri Pankov 			sz++;
487c66b8046SYuri Pankov 			cp++;
488c66b8046SYuri Pankov 		}
489a40ea1a7SYuri Pankov 	}
490a40ea1a7SYuri Pankov 	return sz;
491a40ea1a7SYuri Pankov }
492a40ea1a7SYuri Pankov 
493a40ea1a7SYuri Pankov /*
494a40ea1a7SYuri Pankov  * Print the NUL-terminated list of NUL-terminated strings
495c66b8046SYuri Pankov  * into the buffer, seperating strings with sep.
496a40ea1a7SYuri Pankov  */
497a40ea1a7SYuri Pankov static void
498c66b8046SYuri Pankov lstcat(char *buf, size_t *i, const char *cp, const char *sep)
499a40ea1a7SYuri Pankov {
500c66b8046SYuri Pankov 	const char	*s;
501c66b8046SYuri Pankov 	size_t		 i_start;
502c66b8046SYuri Pankov 
503c66b8046SYuri Pankov 	for (i_start = *i; *cp != '\0'; cp++) {
504c66b8046SYuri Pankov 
505c66b8046SYuri Pankov 		/* Skip names appearing only in the SYNOPSIS. */
506c66b8046SYuri Pankov 		if (*cp <= (char)(NAME_SYN & NAME_MASK)) {
507c66b8046SYuri Pankov 			while (*cp != '\0')
508a40ea1a7SYuri Pankov 				cp++;
509c66b8046SYuri Pankov 			continue;
510a40ea1a7SYuri Pankov 		}
511c66b8046SYuri Pankov 
512c66b8046SYuri Pankov 		/* Skip name class markers. */
513c66b8046SYuri Pankov 		if (*cp < ' ')
514c66b8046SYuri Pankov 			cp++;
515c66b8046SYuri Pankov 
516c66b8046SYuri Pankov 		/* Print a separator before each but the first string. */
517c66b8046SYuri Pankov 		if (*i > i_start) {
518c66b8046SYuri Pankov 			s = sep;
519c66b8046SYuri Pankov 			while (*s != '\0')
520c66b8046SYuri Pankov 				buf[(*i)++] = *s++;
521c66b8046SYuri Pankov 		}
522c66b8046SYuri Pankov 
523c66b8046SYuri Pankov 		/* Copy one string. */
524c66b8046SYuri Pankov 		while (*cp != '\0')
525c66b8046SYuri Pankov 			buf[(*i)++] = *cp++;
526c66b8046SYuri Pankov 	}
527c66b8046SYuri Pankov 
528a40ea1a7SYuri Pankov }
529a40ea1a7SYuri Pankov 
530a40ea1a7SYuri Pankov /*
531a40ea1a7SYuri Pankov  * Return 1 if the string *want occurs in any of the strings
532a40ea1a7SYuri Pankov  * in the NUL-terminated string list *have, or 0 otherwise.
533a40ea1a7SYuri Pankov  * If either argument is NULL or empty, assume no filtering
534a40ea1a7SYuri Pankov  * is desired and return 1.
535a40ea1a7SYuri Pankov  */
536a40ea1a7SYuri Pankov static int
537a40ea1a7SYuri Pankov lstmatch(const char *want, const char *have)
538a40ea1a7SYuri Pankov {
539a40ea1a7SYuri Pankov         if (want == NULL || have == NULL || *have == '\0')
540a40ea1a7SYuri Pankov                 return 1;
541a40ea1a7SYuri Pankov         while (*have != '\0') {
542a40ea1a7SYuri Pankov                 if (strcasestr(have, want) != NULL)
543a40ea1a7SYuri Pankov                         return 1;
544a40ea1a7SYuri Pankov                 have = strchr(have, '\0') + 1;
545a40ea1a7SYuri Pankov         }
546a40ea1a7SYuri Pankov         return 0;
547a40ea1a7SYuri Pankov }
548a40ea1a7SYuri Pankov 
549a40ea1a7SYuri Pankov /*
550c66b8046SYuri Pankov  * Build a list of values taken by the macro im in the manual page.
551a40ea1a7SYuri Pankov  */
552a40ea1a7SYuri Pankov static char *
553c66b8046SYuri Pankov buildoutput(size_t im, struct dbm_page *page)
554a40ea1a7SYuri Pankov {
555c66b8046SYuri Pankov 	const char	*oldoutput, *sep, *input;
556a40ea1a7SYuri Pankov 	char		*output, *newoutput, *value;
557c66b8046SYuri Pankov 	size_t		 sz, i;
558c66b8046SYuri Pankov 
559c66b8046SYuri Pankov 	switch (im) {
560c66b8046SYuri Pankov 	case KEY_Nd:
561c66b8046SYuri Pankov 		return mandoc_strdup(page->desc);
562c66b8046SYuri Pankov 	case KEY_Nm:
563c66b8046SYuri Pankov 		input = page->name;
564c66b8046SYuri Pankov 		break;
565c66b8046SYuri Pankov 	case KEY_sec:
566c66b8046SYuri Pankov 		input = page->sect;
567c66b8046SYuri Pankov 		break;
568c66b8046SYuri Pankov 	case KEY_arch:
569c66b8046SYuri Pankov 		input = page->arch;
570c66b8046SYuri Pankov 		if (input == NULL)
571c66b8046SYuri Pankov 			input = "all\0";
572c66b8046SYuri Pankov 		break;
573c66b8046SYuri Pankov 	default:
574c66b8046SYuri Pankov 		input = NULL;
575c66b8046SYuri Pankov 		break;
576c66b8046SYuri Pankov 	}
577c66b8046SYuri Pankov 
578c66b8046SYuri Pankov 	if (input != NULL) {
579c66b8046SYuri Pankov 		sz = lstlen(input, 3) + 1;
580c66b8046SYuri Pankov 		output = mandoc_malloc(sz);
581c66b8046SYuri Pankov 		i = 0;
582c66b8046SYuri Pankov 		lstcat(output, &i, input, " # ");
583c66b8046SYuri Pankov 		output[i++] = '\0';
584c66b8046SYuri Pankov 		assert(i == sz);
585c66b8046SYuri Pankov 		return output;
586c66b8046SYuri Pankov 	}
587a40ea1a7SYuri Pankov 
588a40ea1a7SYuri Pankov 	output = NULL;
589c66b8046SYuri Pankov 	dbm_macro_bypage(im - 2, page->addr);
590a40ea1a7SYuri Pankov 	while ((value = dbm_macro_next()) != NULL) {
591a40ea1a7SYuri Pankov 		if (output == NULL) {
592a40ea1a7SYuri Pankov 			oldoutput = "";
593a40ea1a7SYuri Pankov 			sep = "";
594a40ea1a7SYuri Pankov 		} else {
595a40ea1a7SYuri Pankov 			oldoutput = output;
596a40ea1a7SYuri Pankov 			sep = " # ";
597a40ea1a7SYuri Pankov 		}
598a40ea1a7SYuri Pankov 		mandoc_asprintf(&newoutput, "%s%s%s", oldoutput, sep, value);
599a40ea1a7SYuri Pankov 		free(output);
600a40ea1a7SYuri Pankov 		output = newoutput;
601a40ea1a7SYuri Pankov 	}
602a40ea1a7SYuri Pankov 	return output;
603a40ea1a7SYuri Pankov }
604a40ea1a7SYuri Pankov 
605a40ea1a7SYuri Pankov /*
606a40ea1a7SYuri Pankov  * Compile a set of string tokens into an expression.
607a40ea1a7SYuri Pankov  * Tokens in "argv" are assumed to be individual expression atoms (e.g.,
608a40ea1a7SYuri Pankov  * "(", "foo=bar", etc.).
609a40ea1a7SYuri Pankov  */
610a40ea1a7SYuri Pankov static struct expr *
611a40ea1a7SYuri Pankov exprcomp(const struct mansearch *search, int argc, char *argv[], int *argi)
612a40ea1a7SYuri Pankov {
613a40ea1a7SYuri Pankov 	struct expr	*parent, *child;
614a40ea1a7SYuri Pankov 	int		 needterm, nested;
615a40ea1a7SYuri Pankov 
616a40ea1a7SYuri Pankov 	if ((nested = *argi) == argc)
617a40ea1a7SYuri Pankov 		return NULL;
618a40ea1a7SYuri Pankov 	needterm = 1;
619a40ea1a7SYuri Pankov 	parent = child = NULL;
620a40ea1a7SYuri Pankov 	while (*argi < argc) {
621a40ea1a7SYuri Pankov 		if (strcmp(")", argv[*argi]) == 0) {
622a40ea1a7SYuri Pankov 			if (needterm)
623a40ea1a7SYuri Pankov 				warnx("missing term "
624a40ea1a7SYuri Pankov 				    "before closing parenthesis");
625a40ea1a7SYuri Pankov 			needterm = 0;
626a40ea1a7SYuri Pankov 			if (nested)
627a40ea1a7SYuri Pankov 				break;
628a40ea1a7SYuri Pankov 			warnx("ignoring unmatched right parenthesis");
629a40ea1a7SYuri Pankov 			++*argi;
630a40ea1a7SYuri Pankov 			continue;
631a40ea1a7SYuri Pankov 		}
632a40ea1a7SYuri Pankov 		if (strcmp("-o", argv[*argi]) == 0) {
633a40ea1a7SYuri Pankov 			if (needterm) {
634a40ea1a7SYuri Pankov 				if (*argi > 0)
635a40ea1a7SYuri Pankov 					warnx("ignoring -o after %s",
636a40ea1a7SYuri Pankov 					    argv[*argi - 1]);
637a40ea1a7SYuri Pankov 				else
638a40ea1a7SYuri Pankov 					warnx("ignoring initial -o");
639a40ea1a7SYuri Pankov 			}
640a40ea1a7SYuri Pankov 			needterm = 1;
641a40ea1a7SYuri Pankov 			++*argi;
642a40ea1a7SYuri Pankov 			continue;
643a40ea1a7SYuri Pankov 		}
644a40ea1a7SYuri Pankov 		needterm = 0;
645a40ea1a7SYuri Pankov 		if (child == NULL) {
646a40ea1a7SYuri Pankov 			child = expr_and(search, argc, argv, argi);
647a40ea1a7SYuri Pankov 			continue;
648a40ea1a7SYuri Pankov 		}
649a40ea1a7SYuri Pankov 		if (parent == NULL) {
650a40ea1a7SYuri Pankov 			parent = mandoc_calloc(1, sizeof(*parent));
651a40ea1a7SYuri Pankov 			parent->type = EXPR_OR;
652a40ea1a7SYuri Pankov 			parent->next = NULL;
653a40ea1a7SYuri Pankov 			parent->child = child;
654a40ea1a7SYuri Pankov 		}
655a40ea1a7SYuri Pankov 		child->next = expr_and(search, argc, argv, argi);
656a40ea1a7SYuri Pankov 		child = child->next;
657a40ea1a7SYuri Pankov 	}
658a40ea1a7SYuri Pankov 	if (needterm && *argi)
659a40ea1a7SYuri Pankov 		warnx("ignoring trailing %s", argv[*argi - 1]);
660a40ea1a7SYuri Pankov 	return parent == NULL ? child : parent;
661a40ea1a7SYuri Pankov }
662a40ea1a7SYuri Pankov 
663a40ea1a7SYuri Pankov static struct expr *
664a40ea1a7SYuri Pankov expr_and(const struct mansearch *search, int argc, char *argv[], int *argi)
665a40ea1a7SYuri Pankov {
666a40ea1a7SYuri Pankov 	struct expr	*parent, *child;
667a40ea1a7SYuri Pankov 	int		 needterm;
668a40ea1a7SYuri Pankov 
669a40ea1a7SYuri Pankov 	needterm = 1;
670a40ea1a7SYuri Pankov 	parent = child = NULL;
671a40ea1a7SYuri Pankov 	while (*argi < argc) {
672a40ea1a7SYuri Pankov 		if (strcmp(")", argv[*argi]) == 0) {
673a40ea1a7SYuri Pankov 			if (needterm)
674a40ea1a7SYuri Pankov 				warnx("missing term "
675a40ea1a7SYuri Pankov 				    "before closing parenthesis");
676a40ea1a7SYuri Pankov 			needterm = 0;
677a40ea1a7SYuri Pankov 			break;
678a40ea1a7SYuri Pankov 		}
679a40ea1a7SYuri Pankov 		if (strcmp("-o", argv[*argi]) == 0)
680a40ea1a7SYuri Pankov 			break;
681a40ea1a7SYuri Pankov 		if (strcmp("-a", argv[*argi]) == 0) {
682a40ea1a7SYuri Pankov 			if (needterm) {
683a40ea1a7SYuri Pankov 				if (*argi > 0)
684a40ea1a7SYuri Pankov 					warnx("ignoring -a after %s",
685a40ea1a7SYuri Pankov 					    argv[*argi - 1]);
686a40ea1a7SYuri Pankov 				else
687a40ea1a7SYuri Pankov 					warnx("ignoring initial -a");
688a40ea1a7SYuri Pankov 			}
689a40ea1a7SYuri Pankov 			needterm = 1;
690a40ea1a7SYuri Pankov 			++*argi;
691a40ea1a7SYuri Pankov 			continue;
692a40ea1a7SYuri Pankov 		}
693a40ea1a7SYuri Pankov 		if (needterm == 0)
694a40ea1a7SYuri Pankov 			break;
695a40ea1a7SYuri Pankov 		if (child == NULL) {
696a40ea1a7SYuri Pankov 			child = exprterm(search, argc, argv, argi);
697a40ea1a7SYuri Pankov 			if (child != NULL)
698a40ea1a7SYuri Pankov 				needterm = 0;
699a40ea1a7SYuri Pankov 			continue;
700a40ea1a7SYuri Pankov 		}
701a40ea1a7SYuri Pankov 		needterm = 0;
702a40ea1a7SYuri Pankov 		if (parent == NULL) {
703a40ea1a7SYuri Pankov 			parent = mandoc_calloc(1, sizeof(*parent));
704a40ea1a7SYuri Pankov 			parent->type = EXPR_AND;
705a40ea1a7SYuri Pankov 			parent->next = NULL;
706a40ea1a7SYuri Pankov 			parent->child = child;
707a40ea1a7SYuri Pankov 		}
708a40ea1a7SYuri Pankov 		child->next = exprterm(search, argc, argv, argi);
709a40ea1a7SYuri Pankov 		if (child->next != NULL) {
710a40ea1a7SYuri Pankov 			child = child->next;
711a40ea1a7SYuri Pankov 			needterm = 0;
712a40ea1a7SYuri Pankov 		}
713a40ea1a7SYuri Pankov 	}
714a40ea1a7SYuri Pankov 	if (needterm && *argi)
715a40ea1a7SYuri Pankov 		warnx("ignoring trailing %s", argv[*argi - 1]);
716a40ea1a7SYuri Pankov 	return parent == NULL ? child : parent;
717a40ea1a7SYuri Pankov }
718a40ea1a7SYuri Pankov 
719a40ea1a7SYuri Pankov static struct expr *
720a40ea1a7SYuri Pankov exprterm(const struct mansearch *search, int argc, char *argv[], int *argi)
721a40ea1a7SYuri Pankov {
722a40ea1a7SYuri Pankov 	char		 errbuf[BUFSIZ];
723a40ea1a7SYuri Pankov 	struct expr	*e;
724a40ea1a7SYuri Pankov 	char		*key, *val;
725a40ea1a7SYuri Pankov 	uint64_t	 iterbit;
726a40ea1a7SYuri Pankov 	int		 cs, i, irc;
727a40ea1a7SYuri Pankov 
728a40ea1a7SYuri Pankov 	if (strcmp("(", argv[*argi]) == 0) {
729a40ea1a7SYuri Pankov 		++*argi;
730a40ea1a7SYuri Pankov 		e = exprcomp(search, argc, argv, argi);
731a40ea1a7SYuri Pankov 		if (*argi < argc) {
732a40ea1a7SYuri Pankov 			assert(strcmp(")", argv[*argi]) == 0);
733a40ea1a7SYuri Pankov 			++*argi;
734a40ea1a7SYuri Pankov 		} else
735a40ea1a7SYuri Pankov 			warnx("unclosed parenthesis");
736a40ea1a7SYuri Pankov 		return e;
737a40ea1a7SYuri Pankov 	}
738a40ea1a7SYuri Pankov 
739c66b8046SYuri Pankov 	if (strcmp("-i", argv[*argi]) == 0 && *argi + 1 < argc) {
740c66b8046SYuri Pankov 		cs = 0;
741c66b8046SYuri Pankov 		++*argi;
742c66b8046SYuri Pankov 	} else
743c66b8046SYuri Pankov 		cs = 1;
744c66b8046SYuri Pankov 
745a40ea1a7SYuri Pankov 	e = mandoc_calloc(1, sizeof(*e));
746a40ea1a7SYuri Pankov 	e->type = EXPR_TERM;
747a40ea1a7SYuri Pankov 	e->bits = 0;
748a40ea1a7SYuri Pankov 	e->next = NULL;
749a40ea1a7SYuri Pankov 	e->child = NULL;
750a40ea1a7SYuri Pankov 
751a40ea1a7SYuri Pankov 	if (search->argmode == ARG_NAME) {
752a40ea1a7SYuri Pankov 		e->bits = TYPE_Nm;
753a40ea1a7SYuri Pankov 		e->match.type = DBM_EXACT;
754a40ea1a7SYuri Pankov 		e->match.str = argv[(*argi)++];
755a40ea1a7SYuri Pankov 		return e;
756a40ea1a7SYuri Pankov 	}
757a40ea1a7SYuri Pankov 
758a40ea1a7SYuri Pankov 	/*
759a40ea1a7SYuri Pankov 	 * Separate macro keys from search string.
760a40ea1a7SYuri Pankov 	 * If needed, request regular expression handling.
761a40ea1a7SYuri Pankov 	 */
762a40ea1a7SYuri Pankov 
763a40ea1a7SYuri Pankov 	if (search->argmode == ARG_WORD) {
764a40ea1a7SYuri Pankov 		e->bits = TYPE_Nm;
765a40ea1a7SYuri Pankov 		e->match.type = DBM_REGEX;
766a40ea1a7SYuri Pankov #if HAVE_REWB_BSD
767a40ea1a7SYuri Pankov 		mandoc_asprintf(&val, "[[:<:]]%s[[:>:]]", argv[*argi]);
768a40ea1a7SYuri Pankov #elif HAVE_REWB_SYSV
769a40ea1a7SYuri Pankov 		mandoc_asprintf(&val, "\\<%s\\>", argv[*argi]);
770a40ea1a7SYuri Pankov #else
771a40ea1a7SYuri Pankov 		mandoc_asprintf(&val,
772a40ea1a7SYuri Pankov 		    "(^|[^a-zA-Z01-9_])%s([^a-zA-Z01-9_]|$)", argv[*argi]);
773a40ea1a7SYuri Pankov #endif
774a40ea1a7SYuri Pankov 		cs = 0;
775a40ea1a7SYuri Pankov 	} else if ((val = strpbrk(argv[*argi], "=~")) == NULL) {
776a40ea1a7SYuri Pankov 		e->bits = TYPE_Nm | TYPE_Nd;
777a40ea1a7SYuri Pankov 		e->match.type = DBM_SUB;
778a40ea1a7SYuri Pankov 		e->match.str = argv[*argi];
779a40ea1a7SYuri Pankov 	} else {
780a40ea1a7SYuri Pankov 		if (val == argv[*argi])
781a40ea1a7SYuri Pankov 			e->bits = TYPE_Nm | TYPE_Nd;
782a40ea1a7SYuri Pankov 		if (*val == '=') {
783a40ea1a7SYuri Pankov 			e->match.type = DBM_SUB;
784a40ea1a7SYuri Pankov 			e->match.str = val + 1;
785a40ea1a7SYuri Pankov 		} else
786a40ea1a7SYuri Pankov 			e->match.type = DBM_REGEX;
787a40ea1a7SYuri Pankov 		*val++ = '\0';
788a40ea1a7SYuri Pankov 		if (strstr(argv[*argi], "arch") != NULL)
789a40ea1a7SYuri Pankov 			cs = 0;
790a40ea1a7SYuri Pankov 	}
791a40ea1a7SYuri Pankov 
792a40ea1a7SYuri Pankov 	/* Compile regular expressions. */
793a40ea1a7SYuri Pankov 
794a40ea1a7SYuri Pankov 	if (e->match.type == DBM_REGEX) {
795a40ea1a7SYuri Pankov 		e->match.re = mandoc_malloc(sizeof(*e->match.re));
796a40ea1a7SYuri Pankov 		irc = regcomp(e->match.re, val,
797a40ea1a7SYuri Pankov 		    REG_EXTENDED | REG_NOSUB | (cs ? 0 : REG_ICASE));
798a40ea1a7SYuri Pankov 		if (irc) {
799a40ea1a7SYuri Pankov 			regerror(irc, e->match.re, errbuf, sizeof(errbuf));
800a40ea1a7SYuri Pankov 			warnx("regcomp /%s/: %s", val, errbuf);
801a40ea1a7SYuri Pankov 		}
802a40ea1a7SYuri Pankov 		if (search->argmode == ARG_WORD)
803a40ea1a7SYuri Pankov 			free(val);
804a40ea1a7SYuri Pankov 		if (irc) {
805a40ea1a7SYuri Pankov 			free(e->match.re);
806a40ea1a7SYuri Pankov 			free(e);
807a40ea1a7SYuri Pankov 			++*argi;
808a40ea1a7SYuri Pankov 			return NULL;
809a40ea1a7SYuri Pankov 		}
810a40ea1a7SYuri Pankov 	}
811a40ea1a7SYuri Pankov 
812a40ea1a7SYuri Pankov 	if (e->bits) {
813a40ea1a7SYuri Pankov 		++*argi;
814a40ea1a7SYuri Pankov 		return e;
815a40ea1a7SYuri Pankov 	}
816a40ea1a7SYuri Pankov 
817a40ea1a7SYuri Pankov 	/*
818a40ea1a7SYuri Pankov 	 * Parse out all possible fields.
819a40ea1a7SYuri Pankov 	 * If the field doesn't resolve, bail.
820a40ea1a7SYuri Pankov 	 */
821a40ea1a7SYuri Pankov 
822a40ea1a7SYuri Pankov 	while (NULL != (key = strsep(&argv[*argi], ","))) {
823a40ea1a7SYuri Pankov 		if ('\0' == *key)
824a40ea1a7SYuri Pankov 			continue;
825a40ea1a7SYuri Pankov 		for (i = 0, iterbit = 1; i < KEY_MAX; i++, iterbit <<= 1) {
826a40ea1a7SYuri Pankov 			if (0 == strcasecmp(key, mansearch_keynames[i])) {
827a40ea1a7SYuri Pankov 				e->bits |= iterbit;
828a40ea1a7SYuri Pankov 				break;
829a40ea1a7SYuri Pankov 			}
830a40ea1a7SYuri Pankov 		}
831a40ea1a7SYuri Pankov 		if (i == KEY_MAX) {
832a40ea1a7SYuri Pankov 			if (strcasecmp(key, "any"))
833a40ea1a7SYuri Pankov 				warnx("treating unknown key "
834a40ea1a7SYuri Pankov 				    "\"%s\" as \"any\"", key);
835a40ea1a7SYuri Pankov 			e->bits |= ~0ULL;
836a40ea1a7SYuri Pankov 		}
837a40ea1a7SYuri Pankov 	}
838a40ea1a7SYuri Pankov 
839a40ea1a7SYuri Pankov 	++*argi;
840a40ea1a7SYuri Pankov 	return e;
841a40ea1a7SYuri Pankov }
842a40ea1a7SYuri Pankov 
843a40ea1a7SYuri Pankov static void
844a40ea1a7SYuri Pankov exprfree(struct expr *e)
845a40ea1a7SYuri Pankov {
846a40ea1a7SYuri Pankov 	if (e->next != NULL)
847a40ea1a7SYuri Pankov 		exprfree(e->next);
848a40ea1a7SYuri Pankov 	if (e->child != NULL)
849a40ea1a7SYuri Pankov 		exprfree(e->child);
850a40ea1a7SYuri Pankov 	free(e);
851a40ea1a7SYuri Pankov }
852