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