xref: /openbsd-src/usr.bin/mandoc/mansearch.c (revision d9a51c353c88dac7b4a389c112b4cfe97b8e3a46)
1*d9a51c35Sjmc /* $OpenBSD: mansearch.c,v 1.67 2022/12/26 19:16:02 jmc Exp $ */
2eea1c63dSschwarze /*
3eea1c63dSschwarze  * Copyright (c) 2012 Kristaps Dzonsons <kristaps@bsd.lv>
41ab4f06fSschwarze  * Copyright (c) 2013-2018 Ingo Schwarze <schwarze@openbsd.org>
5eea1c63dSschwarze  *
6eea1c63dSschwarze  * Permission to use, copy, modify, and distribute this software for any
7eea1c63dSschwarze  * purpose with or without fee is hereby granted, provided that the above
8eea1c63dSschwarze  * copyright notice and this permission notice appear in all copies.
9eea1c63dSschwarze  *
104de77decSschwarze  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11eea1c63dSschwarze  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
124de77decSschwarze  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13eea1c63dSschwarze  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14eea1c63dSschwarze  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15eea1c63dSschwarze  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16eea1c63dSschwarze  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17eea1c63dSschwarze  */
180f10154cSschwarze 
191d77badbSschwarze #include <sys/mman.h>
200f10154cSschwarze #include <sys/types.h>
210f10154cSschwarze 
22eea1c63dSschwarze #include <assert.h>
23eba1598bSschwarze #include <err.h>
24cd380c92Sschwarze #include <errno.h>
25eea1c63dSschwarze #include <fcntl.h>
26e8e3ce36Sschwarze #include <glob.h>
27eea1c63dSschwarze #include <limits.h>
28eea1c63dSschwarze #include <regex.h>
29eea1c63dSschwarze #include <stdio.h>
30eea1c63dSschwarze #include <stdint.h>
31eea1c63dSschwarze #include <stddef.h>
32eea1c63dSschwarze #include <stdlib.h>
33eea1c63dSschwarze #include <string.h>
34eea1c63dSschwarze #include <unistd.h>
35eea1c63dSschwarze 
364f4f7972Sschwarze #include "mandoc_aux.h"
37c4b66caeSschwarze #include "mandoc_ohash.h"
384de77decSschwarze #include "manconf.h"
39eea1c63dSschwarze #include "mansearch.h"
40ff2dbb0fSschwarze #include "dbm.h"
41eea1c63dSschwarze 
42eea1c63dSschwarze struct	expr {
43ff2dbb0fSschwarze 	/* Used for terms: */
44ff2dbb0fSschwarze 	struct dbm_match match;   /* Match type and expression. */
45ff2dbb0fSschwarze 	uint64_t	 bits;    /* Type mask. */
46ff2dbb0fSschwarze 	/* Used for OR and AND groups: */
47ff2dbb0fSschwarze 	struct expr	*next;    /* Next child in the parent group. */
48ff2dbb0fSschwarze 	struct expr	*child;   /* First child in this group. */
49ff2dbb0fSschwarze 	enum { EXPR_TERM, EXPR_OR, EXPR_AND } type;
50eea1c63dSschwarze };
51eea1c63dSschwarze 
52ff2dbb0fSschwarze const char *const mansearch_keynames[KEY_MAX] = {
53ff2dbb0fSschwarze 	"arch",	"sec",	"Xr",	"Ar",	"Fa",	"Fl",	"Dv",	"Fn",
54ff2dbb0fSschwarze 	"Ic",	"Pa",	"Cm",	"Li",	"Em",	"Cd",	"Va",	"Ft",
55ff2dbb0fSschwarze 	"Tn",	"Er",	"Ev",	"Sy",	"Sh",	"In",	"Ss",	"Ox",
56ff2dbb0fSschwarze 	"An",	"Mt",	"St",	"Bx",	"At",	"Nx",	"Fx",	"Lk",
57ff2dbb0fSschwarze 	"Ms",	"Bsx",	"Dx",	"Rs",	"Vt",	"Lb",	"Nm",	"Nd"
58eea1c63dSschwarze };
59eea1c63dSschwarze 
60ff2dbb0fSschwarze 
61ff2dbb0fSschwarze static	struct ohash	*manmerge(struct expr *, struct ohash *);
62ff2dbb0fSschwarze static	struct ohash	*manmerge_term(struct expr *, struct ohash *);
63ff2dbb0fSschwarze static	struct ohash	*manmerge_or(struct expr *, struct ohash *);
64ff2dbb0fSschwarze static	struct ohash	*manmerge_and(struct expr *, struct ohash *);
65ff2dbb0fSschwarze static	char		*buildnames(const struct dbm_page *);
66b0dedf92Sschwarze static	char		*buildoutput(size_t, struct dbm_page *);
67b0dedf92Sschwarze static	size_t		 lstlen(const char *, size_t);
68b0dedf92Sschwarze static	void		 lstcat(char *, size_t *, const char *, const char *);
69ff2dbb0fSschwarze static	int		 lstmatch(const char *, const char *);
70eea1c63dSschwarze static	struct expr	*exprcomp(const struct mansearch *,
71ff2dbb0fSschwarze 				int, char *[], int *);
72ff2dbb0fSschwarze static	struct expr	*expr_and(const struct mansearch *,
73ff2dbb0fSschwarze 				int, char *[], int *);
74ff2dbb0fSschwarze static	struct expr	*exprterm(const struct mansearch *,
75ff2dbb0fSschwarze 				int, char *[], int *);
76eea1c63dSschwarze static	void		 exprfree(struct expr *);
77923fed8aSschwarze static	int		 manpage_compare(const void *, const void *);
78eea1c63dSschwarze 
7949aff9f8Sschwarze 
80eea1c63dSschwarze int
mansearch(const struct mansearch * search,const struct manpaths * paths,int argc,char * argv[],struct manpage ** res,size_t * sz)81eea1c63dSschwarze mansearch(const struct mansearch *search,
82eea1c63dSschwarze 		const struct manpaths *paths,
83eea1c63dSschwarze 		int argc, char *argv[],
84eea1c63dSschwarze 		struct manpage **res, size_t *sz)
85eea1c63dSschwarze {
86eea1c63dSschwarze 	char		 buf[PATH_MAX];
87ff2dbb0fSschwarze 	struct dbm_res	*rp;
88ff2dbb0fSschwarze 	struct expr	*e;
89ff2dbb0fSschwarze 	struct dbm_page	*page;
90eea1c63dSschwarze 	struct manpage	*mpage;
91ff2dbb0fSschwarze 	struct ohash	*htab;
92ff2dbb0fSschwarze 	size_t		 cur, i, maxres, outkey;
93ff2dbb0fSschwarze 	unsigned int	 slot;
94ff2dbb0fSschwarze 	int		 argi, chdir_status, getcwd_status, im;
95f2b25a73Sschwarze 
96ff2dbb0fSschwarze 	argi = 0;
97ff2dbb0fSschwarze 	if ((e = exprcomp(search, argc, argv, &argi)) == NULL) {
98f2b25a73Sschwarze 		*sz = 0;
99526e306bSschwarze 		return 0;
100f2b25a73Sschwarze 	}
101eea1c63dSschwarze 
102f2b25a73Sschwarze 	cur = maxres = 0;
10319b6bef7Sschwarze 	if (res != NULL)
104eea1c63dSschwarze 		*res = NULL;
105eea1c63dSschwarze 
106ff2dbb0fSschwarze 	outkey = KEY_Nd;
107ff2dbb0fSschwarze 	if (search->outkey != NULL)
108ff2dbb0fSschwarze 		for (im = 0; im < KEY_MAX; im++)
1090f10154cSschwarze 			if (0 == strcasecmp(search->outkey,
110ff2dbb0fSschwarze 			    mansearch_keynames[im])) {
111ff2dbb0fSschwarze 				outkey = im;
11247a70b7eSschwarze 				break;
11347a70b7eSschwarze 			}
11447a70b7eSschwarze 
115eea1c63dSschwarze 	/*
116f2b25a73Sschwarze 	 * Remember the original working directory, if possible.
117f2b25a73Sschwarze 	 * This will be needed if the second or a later directory
118f2b25a73Sschwarze 	 * is given as a relative path.
119f2b25a73Sschwarze 	 * Do not error out if the current directory is not
120f2b25a73Sschwarze 	 * searchable: Maybe it won't be needed after all.
121eea1c63dSschwarze 	 */
122eea1c63dSschwarze 
123f2b25a73Sschwarze 	if (getcwd(buf, PATH_MAX) == NULL) {
124f2b25a73Sschwarze 		getcwd_status = 0;
125f2b25a73Sschwarze 		(void)strlcpy(buf, strerror(errno), sizeof(buf));
126f2b25a73Sschwarze 	} else
127f2b25a73Sschwarze 		getcwd_status = 1;
128eea1c63dSschwarze 
129eea1c63dSschwarze 	/*
130eea1c63dSschwarze 	 * Loop over the directories (containing databases) for us to
131eea1c63dSschwarze 	 * search.
132eea1c63dSschwarze 	 * Don't let missing/bad databases/directories phase us.
133eea1c63dSschwarze 	 * In each, try to open the resident database and, if it opens,
134eea1c63dSschwarze 	 * scan it for our match expression.
135eea1c63dSschwarze 	 */
136eea1c63dSschwarze 
137f2b25a73Sschwarze 	chdir_status = 0;
138eea1c63dSschwarze 	for (i = 0; i < paths->sz; i++) {
139f2b25a73Sschwarze 		if (chdir_status && paths->paths[i][0] != '/') {
140f2b25a73Sschwarze 			if ( ! getcwd_status) {
141eba1598bSschwarze 				warnx("%s: getcwd: %s", paths->paths[i], buf);
142f2b25a73Sschwarze 				continue;
143f2b25a73Sschwarze 			} else if (chdir(buf) == -1) {
144dd978576Sschwarze 				warn("%s", buf);
145f2b25a73Sschwarze 				continue;
146f2b25a73Sschwarze 			}
147f2b25a73Sschwarze 		}
148f2b25a73Sschwarze 		if (chdir(paths->paths[i]) == -1) {
149dd978576Sschwarze 			warn("%s", paths->paths[i]);
150eea1c63dSschwarze 			continue;
151eea1c63dSschwarze 		}
152f2b25a73Sschwarze 		chdir_status = 1;
153eea1c63dSschwarze 
154ff2dbb0fSschwarze 		if (dbm_open(MANDOC_DB) == -1) {
155c9651edbSschwarze 			if (errno != ENOENT)
156eba1598bSschwarze 				warn("%s/%s", paths->paths[i], MANDOC_DB);
157eea1c63dSschwarze 			continue;
158eea1c63dSschwarze 		}
159eea1c63dSschwarze 
160ff2dbb0fSschwarze 		if ((htab = manmerge(e, NULL)) == NULL) {
161ff2dbb0fSschwarze 			dbm_close();
162ff2dbb0fSschwarze 			continue;
163eea1c63dSschwarze 		}
164eea1c63dSschwarze 
165ff2dbb0fSschwarze 		for (rp = ohash_first(htab, &slot); rp != NULL;
166ff2dbb0fSschwarze 		    rp = ohash_next(htab, &slot)) {
167ff2dbb0fSschwarze 			page = dbm_page_get(rp->page);
168eea1c63dSschwarze 
169ff2dbb0fSschwarze 			if (lstmatch(search->sec, page->sect) == 0 ||
1702ab19127Sschwarze 			    lstmatch(search->arch, page->arch) == 0 ||
1712ab19127Sschwarze 			    (search->argmode == ARG_NAME &&
1722ab19127Sschwarze 			     rp->bits <= (int32_t)(NAME_SYN & NAME_MASK)))
173eea1c63dSschwarze 				continue;
174eea1c63dSschwarze 
17519b6bef7Sschwarze 			if (res == NULL) {
17619b6bef7Sschwarze 				cur = 1;
17719b6bef7Sschwarze 				break;
17819b6bef7Sschwarze 			}
179eea1c63dSschwarze 			if (cur + 1 > maxres) {
180eea1c63dSschwarze 				maxres += 1024;
1818286bf36Sschwarze 				*res = mandoc_reallocarray(*res,
182ff2dbb0fSschwarze 				    maxres, sizeof(**res));
183eea1c63dSschwarze 			}
184eea1c63dSschwarze 			mpage = *res + cur;
185ff2dbb0fSschwarze 			mandoc_asprintf(&mpage->file, "%s/%s",
186ff2dbb0fSschwarze 			    paths->paths[i], page->file + 1);
187205c63d5Sschwarze 			if (access(chdir_status ? page->file + 1 :
188205c63d5Sschwarze 			    mpage->file, R_OK) == -1) {
189205c63d5Sschwarze 				warn("%s", mpage->file);
190205c63d5Sschwarze 				warnx("outdated mandoc.db contains "
191205c63d5Sschwarze 				    "bogus %s entry, run makewhatis %s",
192205c63d5Sschwarze 				    page->file + 1, paths->paths[i]);
193205c63d5Sschwarze 				free(mpage->file);
194205c63d5Sschwarze 				free(rp);
195205c63d5Sschwarze 				continue;
196205c63d5Sschwarze 			}
197ff2dbb0fSschwarze 			mpage->names = buildnames(page);
198b0dedf92Sschwarze 			mpage->output = buildoutput(outkey, page);
199eac741fcSschwarze 			mpage->bits = search->firstmatch ? rp->bits : 0;
2006f9f8da1Sschwarze 			mpage->ipath = i;
201ff2dbb0fSschwarze 			mpage->sec = *page->sect - '0';
202ff2dbb0fSschwarze 			if (mpage->sec < 0 || mpage->sec > 9)
203923fed8aSschwarze 				mpage->sec = 10;
204ff2dbb0fSschwarze 			mpage->form = *page->file;
205ff2dbb0fSschwarze 			free(rp);
206eea1c63dSschwarze 			cur++;
207eea1c63dSschwarze 		}
208ff2dbb0fSschwarze 		ohash_delete(htab);
209ff2dbb0fSschwarze 		free(htab);
210ff2dbb0fSschwarze 		dbm_close();
211fea71919Sschwarze 
212fea71919Sschwarze 		/*
213fea71919Sschwarze 		 * In man(1) mode, prefer matches in earlier trees
214fea71919Sschwarze 		 * over matches in later trees.
215fea71919Sschwarze 		 */
216fea71919Sschwarze 
217fea71919Sschwarze 		if (cur && search->firstmatch)
218fea71919Sschwarze 			break;
219eea1c63dSschwarze 	}
22014baa4a2Sschwarze 	if (res != NULL && cur > 1)
221923fed8aSschwarze 		qsort(*res, cur, sizeof(struct manpage), manpage_compare);
222f2b25a73Sschwarze 	if (chdir_status && getcwd_status && chdir(buf) == -1)
223dd978576Sschwarze 		warn("%s", buf);
2248a326ddaSschwarze 	exprfree(e);
225eea1c63dSschwarze 	*sz = cur;
22619b6bef7Sschwarze 	return res != NULL || cur;
227eea1c63dSschwarze }
228eea1c63dSschwarze 
229ff2dbb0fSschwarze /*
230ff2dbb0fSschwarze  * Merge the results for the expression tree rooted at e
231ff2dbb0fSschwarze  * into the the result list htab.
232ff2dbb0fSschwarze  */
233ff2dbb0fSschwarze static struct ohash *
manmerge(struct expr * e,struct ohash * htab)234ff2dbb0fSschwarze manmerge(struct expr *e, struct ohash *htab)
235ff2dbb0fSschwarze {
236ff2dbb0fSschwarze 	switch (e->type) {
237ff2dbb0fSschwarze 	case EXPR_TERM:
238ff2dbb0fSschwarze 		return manmerge_term(e, htab);
239ff2dbb0fSschwarze 	case EXPR_OR:
240ff2dbb0fSschwarze 		return manmerge_or(e->child, htab);
241ff2dbb0fSschwarze 	case EXPR_AND:
242ff2dbb0fSschwarze 		return manmerge_and(e->child, htab);
243ff2dbb0fSschwarze 	default:
244ff2dbb0fSschwarze 		abort();
245ff2dbb0fSschwarze 	}
246ff2dbb0fSschwarze }
247ff2dbb0fSschwarze 
248ff2dbb0fSschwarze static struct ohash *
manmerge_term(struct expr * e,struct ohash * htab)249ff2dbb0fSschwarze manmerge_term(struct expr *e, struct ohash *htab)
250ff2dbb0fSschwarze {
251ff2dbb0fSschwarze 	struct dbm_res	 res, *rp;
252ff2dbb0fSschwarze 	uint64_t	 ib;
253ff2dbb0fSschwarze 	unsigned int	 slot;
254ff2dbb0fSschwarze 	int		 im;
255ff2dbb0fSschwarze 
256ff2dbb0fSschwarze 	if (htab == NULL) {
257ff2dbb0fSschwarze 		htab = mandoc_malloc(sizeof(*htab));
258ff2dbb0fSschwarze 		mandoc_ohash_init(htab, 4, offsetof(struct dbm_res, page));
259ff2dbb0fSschwarze 	}
260ff2dbb0fSschwarze 
261ff2dbb0fSschwarze 	for (im = 0, ib = 1; im < KEY_MAX; im++, ib <<= 1) {
262ff2dbb0fSschwarze 		if ((e->bits & ib) == 0)
263ff2dbb0fSschwarze 			continue;
264ff2dbb0fSschwarze 
265ff2dbb0fSschwarze 		switch (ib) {
266ff2dbb0fSschwarze 		case TYPE_arch:
267ff2dbb0fSschwarze 			dbm_page_byarch(&e->match);
268ff2dbb0fSschwarze 			break;
269ff2dbb0fSschwarze 		case TYPE_sec:
270ff2dbb0fSschwarze 			dbm_page_bysect(&e->match);
271ff2dbb0fSschwarze 			break;
272ff2dbb0fSschwarze 		case TYPE_Nm:
273ff2dbb0fSschwarze 			dbm_page_byname(&e->match);
274ff2dbb0fSschwarze 			break;
275ff2dbb0fSschwarze 		case TYPE_Nd:
276ff2dbb0fSschwarze 			dbm_page_bydesc(&e->match);
277ff2dbb0fSschwarze 			break;
278ff2dbb0fSschwarze 		default:
279ff2dbb0fSschwarze 			dbm_page_bymacro(im - 2, &e->match);
280ff2dbb0fSschwarze 			break;
281ff2dbb0fSschwarze 		}
282ff2dbb0fSschwarze 
283ff2dbb0fSschwarze 		/*
284ff2dbb0fSschwarze 		 * When hashing for deduplication, use the unique
285ff2dbb0fSschwarze 		 * page ID itself instead of a hash function;
286ff2dbb0fSschwarze 		 * that is quite efficient.
287ff2dbb0fSschwarze 		 */
288ff2dbb0fSschwarze 
289ff2dbb0fSschwarze 		for (;;) {
290ff2dbb0fSschwarze 			res = dbm_page_next();
291ff2dbb0fSschwarze 			if (res.page == -1)
292ff2dbb0fSschwarze 				break;
293ff2dbb0fSschwarze 			slot = ohash_lookup_memory(htab,
294ff2dbb0fSschwarze 			    (char *)&res, sizeof(res.page), res.page);
295eac741fcSschwarze 			if ((rp = ohash_find(htab, slot)) != NULL) {
296eac741fcSschwarze 				rp->bits |= res.bits;
297ff2dbb0fSschwarze 				continue;
298eac741fcSschwarze 			}
299ff2dbb0fSschwarze 			rp = mandoc_malloc(sizeof(*rp));
300ff2dbb0fSschwarze 			*rp = res;
301ff2dbb0fSschwarze 			ohash_insert(htab, slot, rp);
302ff2dbb0fSschwarze 		}
303ff2dbb0fSschwarze 	}
304ff2dbb0fSschwarze 	return htab;
305ff2dbb0fSschwarze }
306ff2dbb0fSschwarze 
307ff2dbb0fSschwarze static struct ohash *
manmerge_or(struct expr * e,struct ohash * htab)308ff2dbb0fSschwarze manmerge_or(struct expr *e, struct ohash *htab)
309ff2dbb0fSschwarze {
310ff2dbb0fSschwarze 	while (e != NULL) {
311ff2dbb0fSschwarze 		htab = manmerge(e, htab);
312ff2dbb0fSschwarze 		e = e->next;
313ff2dbb0fSschwarze 	}
314ff2dbb0fSschwarze 	return htab;
315ff2dbb0fSschwarze }
316ff2dbb0fSschwarze 
317ff2dbb0fSschwarze static struct ohash *
manmerge_and(struct expr * e,struct ohash * htab)318ff2dbb0fSschwarze manmerge_and(struct expr *e, struct ohash *htab)
319ff2dbb0fSschwarze {
320ff2dbb0fSschwarze 	struct ohash	*hand, *h1, *h2;
321ff2dbb0fSschwarze 	struct dbm_res	*res;
322ff2dbb0fSschwarze 	unsigned int	 slot1, slot2;
323ff2dbb0fSschwarze 
324ff2dbb0fSschwarze 	/* Evaluate the first term of the AND clause. */
325ff2dbb0fSschwarze 
326ff2dbb0fSschwarze 	hand = manmerge(e, NULL);
327ff2dbb0fSschwarze 
328ff2dbb0fSschwarze 	while ((e = e->next) != NULL) {
329ff2dbb0fSschwarze 
330ff2dbb0fSschwarze 		/* Evaluate the next term and prepare for ANDing. */
331ff2dbb0fSschwarze 
332ff2dbb0fSschwarze 		h2 = manmerge(e, NULL);
333ff2dbb0fSschwarze 		if (ohash_entries(h2) < ohash_entries(hand)) {
334ff2dbb0fSschwarze 			h1 = h2;
335ff2dbb0fSschwarze 			h2 = hand;
336ff2dbb0fSschwarze 		} else
337ff2dbb0fSschwarze 			h1 = hand;
338ff2dbb0fSschwarze 		hand = mandoc_malloc(sizeof(*hand));
339ff2dbb0fSschwarze 		mandoc_ohash_init(hand, 4, offsetof(struct dbm_res, page));
340ff2dbb0fSschwarze 
341ff2dbb0fSschwarze 		/* Keep all pages that are in both result sets. */
342ff2dbb0fSschwarze 
343ff2dbb0fSschwarze 		for (res = ohash_first(h1, &slot1); res != NULL;
344ff2dbb0fSschwarze 		    res = ohash_next(h1, &slot1)) {
345ff2dbb0fSschwarze 			if (ohash_find(h2, ohash_lookup_memory(h2,
346ff2dbb0fSschwarze 			    (char *)res, sizeof(res->page),
347ff2dbb0fSschwarze 			    res->page)) == NULL)
348ff2dbb0fSschwarze 				free(res);
349ff2dbb0fSschwarze 			else
350ff2dbb0fSschwarze 				ohash_insert(hand, ohash_lookup_memory(hand,
351ff2dbb0fSschwarze 				    (char *)res, sizeof(res->page),
352ff2dbb0fSschwarze 				    res->page), res);
353ff2dbb0fSschwarze 		}
354ff2dbb0fSschwarze 
355ff2dbb0fSschwarze 		/* Discard the merged results. */
356ff2dbb0fSschwarze 
357ff2dbb0fSschwarze 		for (res = ohash_first(h2, &slot2); res != NULL;
358ff2dbb0fSschwarze 		    res = ohash_next(h2, &slot2))
359ff2dbb0fSschwarze 			free(res);
360ff2dbb0fSschwarze 		ohash_delete(h2);
361ff2dbb0fSschwarze 		free(h2);
362ff2dbb0fSschwarze 		ohash_delete(h1);
363ff2dbb0fSschwarze 		free(h1);
364ff2dbb0fSschwarze 	}
365ff2dbb0fSschwarze 
366ff2dbb0fSschwarze 	/* Merge the result of the AND into htab. */
367ff2dbb0fSschwarze 
368ff2dbb0fSschwarze 	if (htab == NULL)
369ff2dbb0fSschwarze 		return hand;
370ff2dbb0fSschwarze 
371ff2dbb0fSschwarze 	for (res = ohash_first(hand, &slot1); res != NULL;
372ff2dbb0fSschwarze 	    res = ohash_next(hand, &slot1)) {
373ff2dbb0fSschwarze 		slot2 = ohash_lookup_memory(htab,
374ff2dbb0fSschwarze 		    (char *)res, sizeof(res->page), res->page);
375ff2dbb0fSschwarze 		if (ohash_find(htab, slot2) == NULL)
376ff2dbb0fSschwarze 			ohash_insert(htab, slot2, res);
377ff2dbb0fSschwarze 		else
378ff2dbb0fSschwarze 			free(res);
379ff2dbb0fSschwarze 	}
380ff2dbb0fSschwarze 
381ff2dbb0fSschwarze 	/* Discard the merged result. */
382ff2dbb0fSschwarze 
383ff2dbb0fSschwarze 	ohash_delete(hand);
384ff2dbb0fSschwarze 	free(hand);
385ff2dbb0fSschwarze 	return htab;
386ff2dbb0fSschwarze }
387ff2dbb0fSschwarze 
3880f10154cSschwarze void
mansearch_free(struct manpage * res,size_t sz)3890f10154cSschwarze mansearch_free(struct manpage *res, size_t sz)
3900f10154cSschwarze {
3910f10154cSschwarze 	size_t	 i;
3920f10154cSschwarze 
3930f10154cSschwarze 	for (i = 0; i < sz; i++) {
3940f10154cSschwarze 		free(res[i].file);
3950f10154cSschwarze 		free(res[i].names);
3960f10154cSschwarze 		free(res[i].output);
3970f10154cSschwarze 	}
3980f10154cSschwarze 	free(res);
3990f10154cSschwarze }
4000f10154cSschwarze 
401923fed8aSschwarze static int
manpage_compare(const void * vp1,const void * vp2)402923fed8aSschwarze manpage_compare(const void *vp1, const void *vp2)
403923fed8aSschwarze {
404923fed8aSschwarze 	const struct manpage	*mp1, *mp2;
4052270ef14Sschwarze 	const char		*cp1, *cp2;
4062270ef14Sschwarze 	size_t			 sz1, sz2;
407923fed8aSschwarze 	int			 diff;
408923fed8aSschwarze 
409923fed8aSschwarze 	mp1 = vp1;
410923fed8aSschwarze 	mp2 = vp2;
411eac741fcSschwarze 	if ((diff = mp2->bits - mp1->bits) ||
412eac741fcSschwarze 	    (diff = mp1->sec - mp2->sec))
4132270ef14Sschwarze 		return diff;
4142270ef14Sschwarze 
4152270ef14Sschwarze 	/* Fall back to alphabetic ordering of names. */
4162270ef14Sschwarze 	sz1 = strcspn(mp1->names, "(");
4172270ef14Sschwarze 	sz2 = strcspn(mp2->names, "(");
4182270ef14Sschwarze 	if (sz1 < sz2)
4192270ef14Sschwarze 		sz1 = sz2;
4202270ef14Sschwarze 	if ((diff = strncasecmp(mp1->names, mp2->names, sz1)))
4212270ef14Sschwarze 		return diff;
4222270ef14Sschwarze 
4232270ef14Sschwarze 	/* For identical names and sections, prefer arch-dependent. */
4242270ef14Sschwarze 	cp1 = strchr(mp1->names + sz1, '/');
4252270ef14Sschwarze 	cp2 = strchr(mp2->names + sz2, '/');
4262270ef14Sschwarze 	return cp1 != NULL && cp2 != NULL ? strcasecmp(cp1, cp2) :
4272270ef14Sschwarze 	    cp1 != NULL ? -1 : cp2 != NULL ? 1 : 0;
428923fed8aSschwarze }
429923fed8aSschwarze 
43047a70b7eSschwarze static char *
buildnames(const struct dbm_page * page)431ff2dbb0fSschwarze buildnames(const struct dbm_page *page)
43247a70b7eSschwarze {
433ff2dbb0fSschwarze 	char	*buf;
434ff2dbb0fSschwarze 	size_t	 i, sz;
435ff2dbb0fSschwarze 
436b0dedf92Sschwarze 	sz = lstlen(page->name, 2) + 1 + lstlen(page->sect, 2) +
437b0dedf92Sschwarze 	    (page->arch == NULL ? 0 : 1 + lstlen(page->arch, 2)) + 2;
438ff2dbb0fSschwarze 	buf = mandoc_malloc(sz);
439ff2dbb0fSschwarze 	i = 0;
440b0dedf92Sschwarze 	lstcat(buf, &i, page->name, ", ");
441ff2dbb0fSschwarze 	buf[i++] = '(';
442b0dedf92Sschwarze 	lstcat(buf, &i, page->sect, ", ");
443ff2dbb0fSschwarze 	if (page->arch != NULL) {
444ff2dbb0fSschwarze 		buf[i++] = '/';
445b0dedf92Sschwarze 		lstcat(buf, &i, page->arch, ", ");
446ff2dbb0fSschwarze 	}
447ff2dbb0fSschwarze 	buf[i++] = ')';
448ff2dbb0fSschwarze 	buf[i++] = '\0';
449ff2dbb0fSschwarze 	assert(i == sz);
450ff2dbb0fSschwarze 	return buf;
451ff2dbb0fSschwarze }
452ff2dbb0fSschwarze 
453ff2dbb0fSschwarze /*
454ff2dbb0fSschwarze  * Count the buffer space needed to print the NUL-terminated
455b0dedf92Sschwarze  * list of NUL-terminated strings, when printing sep separator
456ff2dbb0fSschwarze  * characters between strings.
457ff2dbb0fSschwarze  */
458ff2dbb0fSschwarze static size_t
lstlen(const char * cp,size_t sep)459b0dedf92Sschwarze lstlen(const char *cp, size_t sep)
460ff2dbb0fSschwarze {
461ff2dbb0fSschwarze 	size_t	 sz;
462ff2dbb0fSschwarze 
4632ab19127Sschwarze 	for (sz = 0; *cp != '\0'; cp++) {
4642ab19127Sschwarze 
4652ab19127Sschwarze 		/* Skip names appearing only in the SYNOPSIS. */
4662ab19127Sschwarze 		if (*cp <= (char)(NAME_SYN & NAME_MASK)) {
4672ab19127Sschwarze 			while (*cp != '\0')
468ff2dbb0fSschwarze 				cp++;
4692ab19127Sschwarze 			continue;
4702ab19127Sschwarze 		}
4712ab19127Sschwarze 
4722ab19127Sschwarze 		/* Skip name class markers. */
4732ab19127Sschwarze 		if (*cp < ' ')
4742ab19127Sschwarze 			cp++;
4752ab19127Sschwarze 
4762ab19127Sschwarze 		/* Print a separator before each but the first string. */
4772ab19127Sschwarze 		if (sz)
4782ab19127Sschwarze 			sz += sep;
4792ab19127Sschwarze 
4802ab19127Sschwarze 		/* Copy one string. */
4812ab19127Sschwarze 		while (*cp != '\0') {
4822ab19127Sschwarze 			sz++;
4832ab19127Sschwarze 			cp++;
4842ab19127Sschwarze 		}
485ff2dbb0fSschwarze 	}
486ff2dbb0fSschwarze 	return sz;
487ff2dbb0fSschwarze }
488ff2dbb0fSschwarze 
489ff2dbb0fSschwarze /*
490ff2dbb0fSschwarze  * Print the NUL-terminated list of NUL-terminated strings
491*d9a51c35Sjmc  * into the buffer, separating strings with sep.
492ff2dbb0fSschwarze  */
493ff2dbb0fSschwarze static void
lstcat(char * buf,size_t * i,const char * cp,const char * sep)494b0dedf92Sschwarze lstcat(char *buf, size_t *i, const char *cp, const char *sep)
495ff2dbb0fSschwarze {
496b0dedf92Sschwarze 	const char	*s;
4972ab19127Sschwarze 	size_t		 i_start;
498b0dedf92Sschwarze 
4992ab19127Sschwarze 	for (i_start = *i; *cp != '\0'; cp++) {
5002ab19127Sschwarze 
5012ab19127Sschwarze 		/* Skip names appearing only in the SYNOPSIS. */
5022ab19127Sschwarze 		if (*cp <= (char)(NAME_SYN & NAME_MASK)) {
5032ab19127Sschwarze 			while (*cp != '\0')
5042ab19127Sschwarze 				cp++;
5052ab19127Sschwarze 			continue;
5062ab19127Sschwarze 		}
5072ab19127Sschwarze 
5082ab19127Sschwarze 		/* Skip name class markers. */
5092ab19127Sschwarze 		if (*cp < ' ')
5102ab19127Sschwarze 			cp++;
5112ab19127Sschwarze 
5122ab19127Sschwarze 		/* Print a separator before each but the first string. */
5132ab19127Sschwarze 		if (*i > i_start) {
514b0dedf92Sschwarze 			s = sep;
515b0dedf92Sschwarze 			while (*s != '\0')
516b0dedf92Sschwarze 				buf[(*i)++] = *s++;
517ff2dbb0fSschwarze 		}
5182ab19127Sschwarze 
5192ab19127Sschwarze 		/* Copy one string. */
5202ab19127Sschwarze 		while (*cp != '\0')
5212ab19127Sschwarze 			buf[(*i)++] = *cp++;
5222ab19127Sschwarze 	}
5232ab19127Sschwarze 
524ff2dbb0fSschwarze }
525ff2dbb0fSschwarze 
526ff2dbb0fSschwarze /*
527ff2dbb0fSschwarze  * Return 1 if the string *want occurs in any of the strings
528ff2dbb0fSschwarze  * in the NUL-terminated string list *have, or 0 otherwise.
529ff2dbb0fSschwarze  * If either argument is NULL or empty, assume no filtering
530ff2dbb0fSschwarze  * is desired and return 1.
531ff2dbb0fSschwarze  */
532ff2dbb0fSschwarze static int
lstmatch(const char * want,const char * have)533ff2dbb0fSschwarze lstmatch(const char *want, const char *have)
534ff2dbb0fSschwarze {
535ff2dbb0fSschwarze         if (want == NULL || have == NULL || *have == '\0')
536ff2dbb0fSschwarze                 return 1;
537ff2dbb0fSschwarze         while (*have != '\0') {
538ff2dbb0fSschwarze                 if (strcasestr(have, want) != NULL)
539ff2dbb0fSschwarze                         return 1;
540ff2dbb0fSschwarze                 have = strchr(have, '\0') + 1;
541ff2dbb0fSschwarze         }
542ff2dbb0fSschwarze         return 0;
543ff2dbb0fSschwarze }
544ff2dbb0fSschwarze 
545ff2dbb0fSschwarze /*
546b0dedf92Sschwarze  * Build a list of values taken by the macro im in the manual page.
547ff2dbb0fSschwarze  */
548ff2dbb0fSschwarze static char *
buildoutput(size_t im,struct dbm_page * page)549b0dedf92Sschwarze buildoutput(size_t im, struct dbm_page *page)
550ff2dbb0fSschwarze {
551b0dedf92Sschwarze 	const char	*oldoutput, *sep, *input;
552ff2dbb0fSschwarze 	char		*output, *newoutput, *value;
553b0dedf92Sschwarze 	size_t		 sz, i;
554b0dedf92Sschwarze 
555b0dedf92Sschwarze 	switch (im) {
556b0dedf92Sschwarze 	case KEY_Nd:
557b0dedf92Sschwarze 		return mandoc_strdup(page->desc);
558b0dedf92Sschwarze 	case KEY_Nm:
559b0dedf92Sschwarze 		input = page->name;
560b0dedf92Sschwarze 		break;
561b0dedf92Sschwarze 	case KEY_sec:
562b0dedf92Sschwarze 		input = page->sect;
563b0dedf92Sschwarze 		break;
564b0dedf92Sschwarze 	case KEY_arch:
565b0dedf92Sschwarze 		input = page->arch;
566b0dedf92Sschwarze 		if (input == NULL)
567b0dedf92Sschwarze 			input = "all\0";
568b0dedf92Sschwarze 		break;
569b0dedf92Sschwarze 	default:
570b0dedf92Sschwarze 		input = NULL;
571b0dedf92Sschwarze 		break;
572b0dedf92Sschwarze 	}
573b0dedf92Sschwarze 
574b0dedf92Sschwarze 	if (input != NULL) {
575b0dedf92Sschwarze 		sz = lstlen(input, 3) + 1;
576b0dedf92Sschwarze 		output = mandoc_malloc(sz);
577b0dedf92Sschwarze 		i = 0;
578b0dedf92Sschwarze 		lstcat(output, &i, input, " # ");
57965807c88Sschwarze 		output[i++] = '\0';
58065807c88Sschwarze 		assert(i == sz);
581b0dedf92Sschwarze 		return output;
582b0dedf92Sschwarze 	}
58347a70b7eSschwarze 
58447a70b7eSschwarze 	output = NULL;
585b0dedf92Sschwarze 	dbm_macro_bypage(im - 2, page->addr);
586ff2dbb0fSschwarze 	while ((value = dbm_macro_next()) != NULL) {
587ff2dbb0fSschwarze 		if (output == NULL) {
58847a70b7eSschwarze 			oldoutput = "";
589ff2dbb0fSschwarze 			sep = "";
59047a70b7eSschwarze 		} else {
59147a70b7eSschwarze 			oldoutput = output;
592ff2dbb0fSschwarze 			sep = " # ";
59347a70b7eSschwarze 		}
594ff2dbb0fSschwarze 		mandoc_asprintf(&newoutput, "%s%s%s", oldoutput, sep, value);
59547a70b7eSschwarze 		free(output);
59647a70b7eSschwarze 		output = newoutput;
59747a70b7eSschwarze 	}
598526e306bSschwarze 	return output;
59947a70b7eSschwarze }
60047a70b7eSschwarze 
601eea1c63dSschwarze /*
602eea1c63dSschwarze  * Compile a set of string tokens into an expression.
603eea1c63dSschwarze  * Tokens in "argv" are assumed to be individual expression atoms (e.g.,
604eea1c63dSschwarze  * "(", "foo=bar", etc.).
605eea1c63dSschwarze  */
606eea1c63dSschwarze static struct expr *
exprcomp(const struct mansearch * search,int argc,char * argv[],int * argi)607ff2dbb0fSschwarze exprcomp(const struct mansearch *search, int argc, char *argv[], int *argi)
608eea1c63dSschwarze {
609ff2dbb0fSschwarze 	struct expr	*parent, *child;
610ff2dbb0fSschwarze 	int		 needterm, nested;
611eea1c63dSschwarze 
612ff2dbb0fSschwarze 	if ((nested = *argi) == argc)
613526e306bSschwarze 		return NULL;
614ff2dbb0fSschwarze 	needterm = 1;
615ff2dbb0fSschwarze 	parent = child = NULL;
616ff2dbb0fSschwarze 	while (*argi < argc) {
617ff2dbb0fSschwarze 		if (strcmp(")", argv[*argi]) == 0) {
618ff2dbb0fSschwarze 			if (needterm)
619ff2dbb0fSschwarze 				warnx("missing term "
620ff2dbb0fSschwarze 				    "before closing parenthesis");
621ff2dbb0fSschwarze 			needterm = 0;
622ff2dbb0fSschwarze 			if (nested)
623ff2dbb0fSschwarze 				break;
624ff2dbb0fSschwarze 			warnx("ignoring unmatched right parenthesis");
625ff2dbb0fSschwarze 			++*argi;
626ff2dbb0fSschwarze 			continue;
627ff2dbb0fSschwarze 		}
628ff2dbb0fSschwarze 		if (strcmp("-o", argv[*argi]) == 0) {
629ff2dbb0fSschwarze 			if (needterm) {
630ff2dbb0fSschwarze 				if (*argi > 0)
631ff2dbb0fSschwarze 					warnx("ignoring -o after %s",
632ff2dbb0fSschwarze 					    argv[*argi - 1]);
633ff2dbb0fSschwarze 				else
634ff2dbb0fSschwarze 					warnx("ignoring initial -o");
635ff2dbb0fSschwarze 			}
636ff2dbb0fSschwarze 			needterm = 1;
637ff2dbb0fSschwarze 			++*argi;
638ff2dbb0fSschwarze 			continue;
639ff2dbb0fSschwarze 		}
640ff2dbb0fSschwarze 		needterm = 0;
641ff2dbb0fSschwarze 		if (child == NULL) {
642ff2dbb0fSschwarze 			child = expr_and(search, argc, argv, argi);
643ff2dbb0fSschwarze 			continue;
644ff2dbb0fSschwarze 		}
645ff2dbb0fSschwarze 		if (parent == NULL) {
646ff2dbb0fSschwarze 			parent = mandoc_calloc(1, sizeof(*parent));
647ff2dbb0fSschwarze 			parent->type = EXPR_OR;
648ff2dbb0fSschwarze 			parent->next = NULL;
649ff2dbb0fSschwarze 			parent->child = child;
650ff2dbb0fSschwarze 		}
651ff2dbb0fSschwarze 		child->next = expr_and(search, argc, argv, argi);
652ff2dbb0fSschwarze 		child = child->next;
653ff2dbb0fSschwarze 	}
654ff2dbb0fSschwarze 	if (needterm && *argi)
655ff2dbb0fSschwarze 		warnx("ignoring trailing %s", argv[*argi - 1]);
656ff2dbb0fSschwarze 	return parent == NULL ? child : parent;
657eea1c63dSschwarze }
658eea1c63dSschwarze 
659eea1c63dSschwarze static struct expr *
expr_and(const struct mansearch * search,int argc,char * argv[],int * argi)660ff2dbb0fSschwarze expr_and(const struct mansearch *search, int argc, char *argv[], int *argi)
661ff2dbb0fSschwarze {
662ff2dbb0fSschwarze 	struct expr	*parent, *child;
663ff2dbb0fSschwarze 	int		 needterm;
664ff2dbb0fSschwarze 
665ff2dbb0fSschwarze 	needterm = 1;
666ff2dbb0fSschwarze 	parent = child = NULL;
667ff2dbb0fSschwarze 	while (*argi < argc) {
668ff2dbb0fSschwarze 		if (strcmp(")", argv[*argi]) == 0) {
669ff2dbb0fSschwarze 			if (needterm)
670ff2dbb0fSschwarze 				warnx("missing term "
671ff2dbb0fSschwarze 				    "before closing parenthesis");
672ff2dbb0fSschwarze 			needterm = 0;
673ff2dbb0fSschwarze 			break;
674ff2dbb0fSschwarze 		}
675ff2dbb0fSschwarze 		if (strcmp("-o", argv[*argi]) == 0)
676ff2dbb0fSschwarze 			break;
677ff2dbb0fSschwarze 		if (strcmp("-a", argv[*argi]) == 0) {
678ff2dbb0fSschwarze 			if (needterm) {
679ff2dbb0fSschwarze 				if (*argi > 0)
680ff2dbb0fSschwarze 					warnx("ignoring -a after %s",
681ff2dbb0fSschwarze 					    argv[*argi - 1]);
682ff2dbb0fSschwarze 				else
683ff2dbb0fSschwarze 					warnx("ignoring initial -a");
684ff2dbb0fSschwarze 			}
685ff2dbb0fSschwarze 			needterm = 1;
686ff2dbb0fSschwarze 			++*argi;
687ff2dbb0fSschwarze 			continue;
688ff2dbb0fSschwarze 		}
689ff2dbb0fSschwarze 		if (needterm == 0)
690ff2dbb0fSschwarze 			break;
691ff2dbb0fSschwarze 		if (child == NULL) {
692ff2dbb0fSschwarze 			child = exprterm(search, argc, argv, argi);
693ff2dbb0fSschwarze 			if (child != NULL)
694ff2dbb0fSschwarze 				needterm = 0;
695ff2dbb0fSschwarze 			continue;
696ff2dbb0fSschwarze 		}
697ff2dbb0fSschwarze 		needterm = 0;
698ff2dbb0fSschwarze 		if (parent == NULL) {
699ff2dbb0fSschwarze 			parent = mandoc_calloc(1, sizeof(*parent));
700ff2dbb0fSschwarze 			parent->type = EXPR_AND;
701ff2dbb0fSschwarze 			parent->next = NULL;
702ff2dbb0fSschwarze 			parent->child = child;
703ff2dbb0fSschwarze 		}
704ff2dbb0fSschwarze 		child->next = exprterm(search, argc, argv, argi);
705ff2dbb0fSschwarze 		if (child->next != NULL) {
706ff2dbb0fSschwarze 			child = child->next;
707ff2dbb0fSschwarze 			needterm = 0;
708ff2dbb0fSschwarze 		}
709ff2dbb0fSschwarze 	}
710ff2dbb0fSschwarze 	if (needterm && *argi)
711ff2dbb0fSschwarze 		warnx("ignoring trailing %s", argv[*argi - 1]);
712ff2dbb0fSschwarze 	return parent == NULL ? child : parent;
713ff2dbb0fSschwarze }
714ff2dbb0fSschwarze 
715ff2dbb0fSschwarze static struct expr *
exprterm(const struct mansearch * search,int argc,char * argv[],int * argi)716ff2dbb0fSschwarze exprterm(const struct mansearch *search, int argc, char *argv[], int *argi)
717eea1c63dSschwarze {
718f81d09daSschwarze 	char		 errbuf[BUFSIZ];
719eea1c63dSschwarze 	struct expr	*e;
720eb258672Sschwarze 	char		*key, *val;
72162a7ee29Sschwarze 	uint64_t	 iterbit;
722ff2dbb0fSschwarze 	int		 cs, i, irc;
723eea1c63dSschwarze 
724ff2dbb0fSschwarze 	if (strcmp("(", argv[*argi]) == 0) {
725ff2dbb0fSschwarze 		++*argi;
726ff2dbb0fSschwarze 		e = exprcomp(search, argc, argv, argi);
727ff2dbb0fSschwarze 		if (*argi < argc) {
728ff2dbb0fSschwarze 			assert(strcmp(")", argv[*argi]) == 0);
729ff2dbb0fSschwarze 			++*argi;
730ff2dbb0fSschwarze 		} else
731ff2dbb0fSschwarze 			warnx("unclosed parenthesis");
732ff2dbb0fSschwarze 		return e;
733ff2dbb0fSschwarze 	}
734eea1c63dSschwarze 
73576cc2bb0Sschwarze 	if (strcmp("-i", argv[*argi]) == 0 && *argi + 1 < argc) {
73676cc2bb0Sschwarze 		cs = 0;
73776cc2bb0Sschwarze 		++*argi;
73876cc2bb0Sschwarze 	} else
73976cc2bb0Sschwarze 		cs = 1;
74076cc2bb0Sschwarze 
741ff2dbb0fSschwarze 	e = mandoc_calloc(1, sizeof(*e));
742ff2dbb0fSschwarze 	e->type = EXPR_TERM;
743ff2dbb0fSschwarze 	e->bits = 0;
744ff2dbb0fSschwarze 	e->next = NULL;
745ff2dbb0fSschwarze 	e->child = NULL;
746eea1c63dSschwarze 
7470f10154cSschwarze 	if (search->argmode == ARG_NAME) {
7480f10154cSschwarze 		e->bits = TYPE_Nm;
749ff2dbb0fSschwarze 		e->match.type = DBM_EXACT;
750ff2dbb0fSschwarze 		e->match.str = argv[(*argi)++];
751526e306bSschwarze 		return e;
752eea1c63dSschwarze 	}
753eea1c63dSschwarze 
754eea1c63dSschwarze 	/*
7550f10154cSschwarze 	 * Separate macro keys from search string.
756ff2dbb0fSschwarze 	 * If needed, request regular expression handling.
757eea1c63dSschwarze 	 */
758eea1c63dSschwarze 
7590f10154cSschwarze 	if (search->argmode == ARG_WORD) {
7600f10154cSschwarze 		e->bits = TYPE_Nm;
761ff2dbb0fSschwarze 		e->match.type = DBM_REGEX;
762ff2dbb0fSschwarze 		mandoc_asprintf(&val, "[[:<:]]%s[[:>:]]", argv[*argi]);
7630f10154cSschwarze 		cs = 0;
764ff2dbb0fSschwarze 	} else if ((val = strpbrk(argv[*argi], "=~")) == NULL) {
7650f10154cSschwarze 		e->bits = TYPE_Nm | TYPE_Nd;
7661ab4f06fSschwarze 		e->match.type = DBM_REGEX;
7671ab4f06fSschwarze 		val = argv[*argi];
7681ab4f06fSschwarze 		cs = 0;
769eb258672Sschwarze 	} else {
770ff2dbb0fSschwarze 		if (val == argv[*argi])
7710f10154cSschwarze 			e->bits = TYPE_Nm | TYPE_Nd;
772ff2dbb0fSschwarze 		if (*val == '=') {
773ff2dbb0fSschwarze 			e->match.type = DBM_SUB;
774ff2dbb0fSschwarze 			e->match.str = val + 1;
775ff2dbb0fSschwarze 		} else
776ff2dbb0fSschwarze 			e->match.type = DBM_REGEX;
777eb258672Sschwarze 		*val++ = '\0';
778ff2dbb0fSschwarze 		if (strstr(argv[*argi], "arch") != NULL)
779b530bd48Sschwarze 			cs = 0;
780eb258672Sschwarze 	}
781eb258672Sschwarze 
782eb258672Sschwarze 	/* Compile regular expressions. */
783eb258672Sschwarze 
784ff2dbb0fSschwarze 	if (e->match.type == DBM_REGEX) {
785ff2dbb0fSschwarze 		e->match.re = mandoc_malloc(sizeof(*e->match.re));
786ff2dbb0fSschwarze 		irc = regcomp(e->match.re, val,
787eb258672Sschwarze 		    REG_EXTENDED | REG_NOSUB | (cs ? 0 : REG_ICASE));
788ff2dbb0fSschwarze 		if (irc) {
789ff2dbb0fSschwarze 			regerror(irc, e->match.re, errbuf, sizeof(errbuf));
790ff2dbb0fSschwarze 			warnx("regcomp /%s/: %s", val, errbuf);
791ff2dbb0fSschwarze 		}
7920f10154cSschwarze 		if (search->argmode == ARG_WORD)
793eb258672Sschwarze 			free(val);
794eb258672Sschwarze 		if (irc) {
795ff2dbb0fSschwarze 			free(e->match.re);
796eea1c63dSschwarze 			free(e);
797ff2dbb0fSschwarze 			++*argi;
798526e306bSschwarze 			return NULL;
799eea1c63dSschwarze 		}
800eb258672Sschwarze 	}
801eb258672Sschwarze 
802ff2dbb0fSschwarze 	if (e->bits) {
803ff2dbb0fSschwarze 		++*argi;
804526e306bSschwarze 		return e;
805ff2dbb0fSschwarze 	}
806eea1c63dSschwarze 
807eea1c63dSschwarze 	/*
808eea1c63dSschwarze 	 * Parse out all possible fields.
809eea1c63dSschwarze 	 * If the field doesn't resolve, bail.
810eea1c63dSschwarze 	 */
811eea1c63dSschwarze 
812ff2dbb0fSschwarze 	while (NULL != (key = strsep(&argv[*argi], ","))) {
813eea1c63dSschwarze 		if ('\0' == *key)
814eea1c63dSschwarze 			continue;
815ff2dbb0fSschwarze 		for (i = 0, iterbit = 1; i < KEY_MAX; i++, iterbit <<= 1) {
816ff2dbb0fSschwarze 			if (0 == strcasecmp(key, mansearch_keynames[i])) {
81762a7ee29Sschwarze 				e->bits |= iterbit;
81862a7ee29Sschwarze 				break;
81962a7ee29Sschwarze 			}
82062a7ee29Sschwarze 		}
821ff2dbb0fSschwarze 		if (i == KEY_MAX) {
822ff2dbb0fSschwarze 			if (strcasecmp(key, "any"))
823ff2dbb0fSschwarze 				warnx("treating unknown key "
824ff2dbb0fSschwarze 				    "\"%s\" as \"any\"", key);
82562a7ee29Sschwarze 			e->bits |= ~0ULL;
82662a7ee29Sschwarze 		}
827eea1c63dSschwarze 	}
828eea1c63dSschwarze 
829ff2dbb0fSschwarze 	++*argi;
830526e306bSschwarze 	return e;
831eea1c63dSschwarze }
832eea1c63dSschwarze 
833eea1c63dSschwarze static void
exprfree(struct expr * e)834ff2dbb0fSschwarze exprfree(struct expr *e)
835eea1c63dSschwarze {
836ff2dbb0fSschwarze 	if (e->next != NULL)
837ff2dbb0fSschwarze 		exprfree(e->next);
838ff2dbb0fSschwarze 	if (e->child != NULL)
839ff2dbb0fSschwarze 		exprfree(e->child);
840ff2dbb0fSschwarze 	free(e);
841eea1c63dSschwarze }
842