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