xref: /dflybsd-src/contrib/mdocml/mansearch.c (revision 1e4d43f9c96723e4e55543d240f182e1aac9a4c2)
1*99db7d0eSSascha Wildner /*	$Id: mansearch.c,v 1.82 2019/07/01 22:56:24 schwarze Exp $ */
2070c62a6SFranco Fichtner /*
3070c62a6SFranco Fichtner  * Copyright (c) 2012 Kristaps Dzonsons <kristaps@bsd.lv>
454ba9607SSascha Wildner  * Copyright (c) 2013-2018 Ingo Schwarze <schwarze@openbsd.org>
5070c62a6SFranco Fichtner  *
6070c62a6SFranco Fichtner  * Permission to use, copy, modify, and distribute this software for any
7070c62a6SFranco Fichtner  * purpose with or without fee is hereby granted, provided that the above
8070c62a6SFranco Fichtner  * copyright notice and this permission notice appear in all copies.
9070c62a6SFranco Fichtner  *
1054ba9607SSascha Wildner  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11070c62a6SFranco Fichtner  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1254ba9607SSascha Wildner  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13070c62a6SFranco Fichtner  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14070c62a6SFranco Fichtner  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15070c62a6SFranco Fichtner  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16070c62a6SFranco Fichtner  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17070c62a6SFranco Fichtner  */
18070c62a6SFranco Fichtner #include "config.h"
19070c62a6SFranco Fichtner 
20070c62a6SFranco Fichtner #include <sys/mman.h>
2154ba9607SSascha Wildner #include <sys/types.h>
2254ba9607SSascha Wildner 
23070c62a6SFranco Fichtner #include <assert.h>
2454ba9607SSascha Wildner #if HAVE_ERR
2554ba9607SSascha Wildner #include <err.h>
2654ba9607SSascha Wildner #endif
2754ba9607SSascha Wildner #include <errno.h>
28070c62a6SFranco Fichtner #include <fcntl.h>
2954ba9607SSascha Wildner #include <glob.h>
30070c62a6SFranco Fichtner #include <limits.h>
31070c62a6SFranco Fichtner #include <regex.h>
32070c62a6SFranco Fichtner #include <stdio.h>
33070c62a6SFranco Fichtner #include <stdint.h>
34070c62a6SFranco Fichtner #include <stddef.h>
35070c62a6SFranco Fichtner #include <stdlib.h>
36070c62a6SFranco Fichtner #include <string.h>
37070c62a6SFranco Fichtner #include <unistd.h>
38070c62a6SFranco Fichtner 
39070c62a6SFranco Fichtner #include "mandoc_aux.h"
4054ba9607SSascha Wildner #include "mandoc_ohash.h"
4154ba9607SSascha Wildner #include "manconf.h"
42070c62a6SFranco Fichtner #include "mansearch.h"
4354ba9607SSascha Wildner #include "dbm.h"
44070c62a6SFranco Fichtner 
45070c62a6SFranco Fichtner struct	expr {
4654ba9607SSascha Wildner 	/* Used for terms: */
4754ba9607SSascha Wildner 	struct dbm_match match;   /* Match type and expression. */
4854ba9607SSascha Wildner 	uint64_t	 bits;    /* Type mask. */
4954ba9607SSascha Wildner 	/* Used for OR and AND groups: */
5054ba9607SSascha Wildner 	struct expr	*next;    /* Next child in the parent group. */
5154ba9607SSascha Wildner 	struct expr	*child;   /* First child in this group. */
5254ba9607SSascha Wildner 	enum { EXPR_TERM, EXPR_OR, EXPR_AND } type;
53070c62a6SFranco Fichtner };
54070c62a6SFranco Fichtner 
5554ba9607SSascha Wildner const char *const mansearch_keynames[KEY_MAX] = {
5654ba9607SSascha Wildner 	"arch",	"sec",	"Xr",	"Ar",	"Fa",	"Fl",	"Dv",	"Fn",
5754ba9607SSascha Wildner 	"Ic",	"Pa",	"Cm",	"Li",	"Em",	"Cd",	"Va",	"Ft",
5854ba9607SSascha Wildner 	"Tn",	"Er",	"Ev",	"Sy",	"Sh",	"In",	"Ss",	"Ox",
5954ba9607SSascha Wildner 	"An",	"Mt",	"St",	"Bx",	"At",	"Nx",	"Fx",	"Lk",
6054ba9607SSascha Wildner 	"Ms",	"Bsx",	"Dx",	"Rs",	"Vt",	"Lb",	"Nm",	"Nd"
61070c62a6SFranco Fichtner };
62070c62a6SFranco Fichtner 
6354ba9607SSascha Wildner 
6454ba9607SSascha Wildner static	struct ohash	*manmerge(struct expr *, struct ohash *);
6554ba9607SSascha Wildner static	struct ohash	*manmerge_term(struct expr *, struct ohash *);
6654ba9607SSascha Wildner static	struct ohash	*manmerge_or(struct expr *, struct ohash *);
6754ba9607SSascha Wildner static	struct ohash	*manmerge_and(struct expr *, struct ohash *);
6854ba9607SSascha Wildner static	char		*buildnames(const struct dbm_page *);
6954ba9607SSascha Wildner static	char		*buildoutput(size_t, struct dbm_page *);
7054ba9607SSascha Wildner static	size_t		 lstlen(const char *, size_t);
7154ba9607SSascha Wildner static	void		 lstcat(char *, size_t *, const char *, const char *);
7254ba9607SSascha Wildner static	int		 lstmatch(const char *, const char *);
73070c62a6SFranco Fichtner static	struct expr	*exprcomp(const struct mansearch *,
7454ba9607SSascha Wildner 				int, char *[], int *);
7554ba9607SSascha Wildner static	struct expr	*expr_and(const struct mansearch *,
7654ba9607SSascha Wildner 				int, char *[], int *);
7754ba9607SSascha Wildner static	struct expr	*exprterm(const struct mansearch *,
7854ba9607SSascha Wildner 				int, char *[], int *);
79070c62a6SFranco Fichtner static	void		 exprfree(struct expr *);
80070c62a6SFranco Fichtner static	int		 manpage_compare(const void *, const void *);
81070c62a6SFranco Fichtner 
82070c62a6SFranco Fichtner 
83070c62a6SFranco Fichtner int
mansearch(const struct mansearch * search,const struct manpaths * paths,int argc,char * argv[],struct manpage ** res,size_t * sz)84070c62a6SFranco Fichtner mansearch(const struct mansearch *search,
85070c62a6SFranco Fichtner 		const struct manpaths *paths,
86070c62a6SFranco Fichtner 		int argc, char *argv[],
87070c62a6SFranco Fichtner 		struct manpage **res, size_t *sz)
88070c62a6SFranco Fichtner {
89070c62a6SFranco Fichtner 	char		 buf[PATH_MAX];
9054ba9607SSascha Wildner 	struct dbm_res	*rp;
9154ba9607SSascha Wildner 	struct expr	*e;
9254ba9607SSascha Wildner 	struct dbm_page	*page;
93070c62a6SFranco Fichtner 	struct manpage	*mpage;
9454ba9607SSascha Wildner 	struct ohash	*htab;
9554ba9607SSascha Wildner 	size_t		 cur, i, maxres, outkey;
9654ba9607SSascha Wildner 	unsigned int	 slot;
9754ba9607SSascha Wildner 	int		 argi, chdir_status, getcwd_status, im;
98070c62a6SFranco Fichtner 
9954ba9607SSascha Wildner 	argi = 0;
10054ba9607SSascha Wildner 	if ((e = exprcomp(search, argc, argv, &argi)) == NULL) {
10154ba9607SSascha Wildner 		*sz = 0;
10254ba9607SSascha Wildner 		return 0;
10354ba9607SSascha Wildner 	}
104070c62a6SFranco Fichtner 
10554ba9607SSascha Wildner 	cur = maxres = 0;
10654ba9607SSascha Wildner 	if (res != NULL)
107070c62a6SFranco Fichtner 		*res = NULL;
108070c62a6SFranco Fichtner 
10954ba9607SSascha Wildner 	outkey = KEY_Nd;
11054ba9607SSascha Wildner 	if (search->outkey != NULL)
11154ba9607SSascha Wildner 		for (im = 0; im < KEY_MAX; im++)
11254ba9607SSascha Wildner 			if (0 == strcasecmp(search->outkey,
11354ba9607SSascha Wildner 			    mansearch_keynames[im])) {
11454ba9607SSascha Wildner 				outkey = im;
115070c62a6SFranco Fichtner 				break;
116070c62a6SFranco Fichtner 			}
117070c62a6SFranco Fichtner 
118070c62a6SFranco Fichtner 	/*
11954ba9607SSascha Wildner 	 * Remember the original working directory, if possible.
12054ba9607SSascha Wildner 	 * This will be needed if the second or a later directory
12154ba9607SSascha Wildner 	 * is given as a relative path.
12254ba9607SSascha Wildner 	 * Do not error out if the current directory is not
12354ba9607SSascha Wildner 	 * searchable: Maybe it won't be needed after all.
124070c62a6SFranco Fichtner 	 */
125070c62a6SFranco Fichtner 
12654ba9607SSascha Wildner 	if (getcwd(buf, PATH_MAX) == NULL) {
12754ba9607SSascha Wildner 		getcwd_status = 0;
12854ba9607SSascha Wildner 		(void)strlcpy(buf, strerror(errno), sizeof(buf));
12954ba9607SSascha Wildner 	} else
13054ba9607SSascha Wildner 		getcwd_status = 1;
131070c62a6SFranco Fichtner 
132070c62a6SFranco Fichtner 	/*
133070c62a6SFranco Fichtner 	 * Loop over the directories (containing databases) for us to
134070c62a6SFranco Fichtner 	 * search.
135070c62a6SFranco Fichtner 	 * Don't let missing/bad databases/directories phase us.
136070c62a6SFranco Fichtner 	 * In each, try to open the resident database and, if it opens,
137070c62a6SFranco Fichtner 	 * scan it for our match expression.
138070c62a6SFranco Fichtner 	 */
139070c62a6SFranco Fichtner 
14054ba9607SSascha Wildner 	chdir_status = 0;
141070c62a6SFranco Fichtner 	for (i = 0; i < paths->sz; i++) {
14254ba9607SSascha Wildner 		if (chdir_status && paths->paths[i][0] != '/') {
14354ba9607SSascha Wildner 			if ( ! getcwd_status) {
14454ba9607SSascha Wildner 				warnx("%s: getcwd: %s", paths->paths[i], buf);
14554ba9607SSascha Wildner 				continue;
14654ba9607SSascha Wildner 			} else if (chdir(buf) == -1) {
14754ba9607SSascha Wildner 				warn("%s", buf);
14854ba9607SSascha Wildner 				continue;
14954ba9607SSascha Wildner 			}
15054ba9607SSascha Wildner 		}
15154ba9607SSascha Wildner 		if (chdir(paths->paths[i]) == -1) {
15254ba9607SSascha Wildner 			warn("%s", paths->paths[i]);
15354ba9607SSascha Wildner 			continue;
15454ba9607SSascha Wildner 		}
15554ba9607SSascha Wildner 		chdir_status = 1;
15654ba9607SSascha Wildner 
15754ba9607SSascha Wildner 		if (dbm_open(MANDOC_DB) == -1) {
15854ba9607SSascha Wildner 			if (errno != ENOENT)
15954ba9607SSascha Wildner 				warn("%s/%s", paths->paths[i], MANDOC_DB);
16054ba9607SSascha Wildner 			continue;
16154ba9607SSascha Wildner 		}
16254ba9607SSascha Wildner 
16354ba9607SSascha Wildner 		if ((htab = manmerge(e, NULL)) == NULL) {
16454ba9607SSascha Wildner 			dbm_close();
16554ba9607SSascha Wildner 			continue;
16654ba9607SSascha Wildner 		}
16754ba9607SSascha Wildner 
16854ba9607SSascha Wildner 		for (rp = ohash_first(htab, &slot); rp != NULL;
16954ba9607SSascha Wildner 		    rp = ohash_next(htab, &slot)) {
17054ba9607SSascha Wildner 			page = dbm_page_get(rp->page);
17154ba9607SSascha Wildner 
17254ba9607SSascha Wildner 			if (lstmatch(search->sec, page->sect) == 0 ||
17354ba9607SSascha Wildner 			    lstmatch(search->arch, page->arch) == 0 ||
17454ba9607SSascha Wildner 			    (search->argmode == ARG_NAME &&
17554ba9607SSascha Wildner 			     rp->bits <= (int32_t)(NAME_SYN & NAME_MASK)))
17654ba9607SSascha Wildner 				continue;
17754ba9607SSascha Wildner 
17854ba9607SSascha Wildner 			if (res == NULL) {
17954ba9607SSascha Wildner 				cur = 1;
180070c62a6SFranco Fichtner 				break;
181070c62a6SFranco Fichtner 			}
182070c62a6SFranco Fichtner 			if (cur + 1 > maxres) {
183070c62a6SFranco Fichtner 				maxres += 1024;
184070c62a6SFranco Fichtner 				*res = mandoc_reallocarray(*res,
18554ba9607SSascha Wildner 				    maxres, sizeof(**res));
186070c62a6SFranco Fichtner 			}
187070c62a6SFranco Fichtner 			mpage = *res + cur;
18854ba9607SSascha Wildner 			mandoc_asprintf(&mpage->file, "%s/%s",
18954ba9607SSascha Wildner 			    paths->paths[i], page->file + 1);
19054ba9607SSascha Wildner 			if (access(chdir_status ? page->file + 1 :
19154ba9607SSascha Wildner 			    mpage->file, R_OK) == -1) {
19254ba9607SSascha Wildner 				warn("%s", mpage->file);
19354ba9607SSascha Wildner 				warnx("outdated mandoc.db contains "
19454ba9607SSascha Wildner 				    "bogus %s entry, run makewhatis %s",
19554ba9607SSascha Wildner 				    page->file + 1, paths->paths[i]);
19654ba9607SSascha Wildner 				free(mpage->file);
19754ba9607SSascha Wildner 				free(rp);
19854ba9607SSascha Wildner 				continue;
19954ba9607SSascha Wildner 			}
20054ba9607SSascha Wildner 			mpage->names = buildnames(page);
20154ba9607SSascha Wildner 			mpage->output = buildoutput(outkey, page);
202*99db7d0eSSascha Wildner 			mpage->bits = search->firstmatch ? rp->bits : 0;
20354ba9607SSascha Wildner 			mpage->ipath = i;
20454ba9607SSascha Wildner 			mpage->sec = *page->sect - '0';
20554ba9607SSascha Wildner 			if (mpage->sec < 0 || mpage->sec > 9)
206070c62a6SFranco Fichtner 				mpage->sec = 10;
20754ba9607SSascha Wildner 			mpage->form = *page->file;
20854ba9607SSascha Wildner 			free(rp);
209070c62a6SFranco Fichtner 			cur++;
210070c62a6SFranco Fichtner 		}
21154ba9607SSascha Wildner 		ohash_delete(htab);
21254ba9607SSascha Wildner 		free(htab);
21354ba9607SSascha Wildner 		dbm_close();
214070c62a6SFranco Fichtner 
21554ba9607SSascha Wildner 		/*
21654ba9607SSascha Wildner 		 * In man(1) mode, prefer matches in earlier trees
21754ba9607SSascha Wildner 		 * over matches in later trees.
21854ba9607SSascha Wildner 		 */
21954ba9607SSascha Wildner 
22054ba9607SSascha Wildner 		if (cur && search->firstmatch)
22154ba9607SSascha Wildner 			break;
222070c62a6SFranco Fichtner 	}
22354ba9607SSascha Wildner 	if (res != NULL)
224070c62a6SFranco Fichtner 		qsort(*res, cur, sizeof(struct manpage), manpage_compare);
22554ba9607SSascha Wildner 	if (chdir_status && getcwd_status && chdir(buf) == -1)
22654ba9607SSascha Wildner 		warn("%s", buf);
227070c62a6SFranco Fichtner 	exprfree(e);
228070c62a6SFranco Fichtner 	*sz = cur;
22954ba9607SSascha Wildner 	return res != NULL || cur;
23054ba9607SSascha Wildner }
23154ba9607SSascha Wildner 
23254ba9607SSascha Wildner /*
23354ba9607SSascha Wildner  * Merge the results for the expression tree rooted at e
23454ba9607SSascha Wildner  * into the the result list htab.
23554ba9607SSascha Wildner  */
23654ba9607SSascha Wildner static struct ohash *
manmerge(struct expr * e,struct ohash * htab)23754ba9607SSascha Wildner manmerge(struct expr *e, struct ohash *htab)
23854ba9607SSascha Wildner {
23954ba9607SSascha Wildner 	switch (e->type) {
24054ba9607SSascha Wildner 	case EXPR_TERM:
24154ba9607SSascha Wildner 		return manmerge_term(e, htab);
24254ba9607SSascha Wildner 	case EXPR_OR:
24354ba9607SSascha Wildner 		return manmerge_or(e->child, htab);
24454ba9607SSascha Wildner 	case EXPR_AND:
24554ba9607SSascha Wildner 		return manmerge_and(e->child, htab);
24654ba9607SSascha Wildner 	default:
24754ba9607SSascha Wildner 		abort();
24854ba9607SSascha Wildner 	}
24954ba9607SSascha Wildner }
25054ba9607SSascha Wildner 
25154ba9607SSascha Wildner static struct ohash *
manmerge_term(struct expr * e,struct ohash * htab)25254ba9607SSascha Wildner manmerge_term(struct expr *e, struct ohash *htab)
25354ba9607SSascha Wildner {
25454ba9607SSascha Wildner 	struct dbm_res	 res, *rp;
25554ba9607SSascha Wildner 	uint64_t	 ib;
25654ba9607SSascha Wildner 	unsigned int	 slot;
25754ba9607SSascha Wildner 	int		 im;
25854ba9607SSascha Wildner 
25954ba9607SSascha Wildner 	if (htab == NULL) {
26054ba9607SSascha Wildner 		htab = mandoc_malloc(sizeof(*htab));
26154ba9607SSascha Wildner 		mandoc_ohash_init(htab, 4, offsetof(struct dbm_res, page));
26254ba9607SSascha Wildner 	}
26354ba9607SSascha Wildner 
26454ba9607SSascha Wildner 	for (im = 0, ib = 1; im < KEY_MAX; im++, ib <<= 1) {
26554ba9607SSascha Wildner 		if ((e->bits & ib) == 0)
26654ba9607SSascha Wildner 			continue;
26754ba9607SSascha Wildner 
26854ba9607SSascha Wildner 		switch (ib) {
26954ba9607SSascha Wildner 		case TYPE_arch:
27054ba9607SSascha Wildner 			dbm_page_byarch(&e->match);
27154ba9607SSascha Wildner 			break;
27254ba9607SSascha Wildner 		case TYPE_sec:
27354ba9607SSascha Wildner 			dbm_page_bysect(&e->match);
27454ba9607SSascha Wildner 			break;
27554ba9607SSascha Wildner 		case TYPE_Nm:
27654ba9607SSascha Wildner 			dbm_page_byname(&e->match);
27754ba9607SSascha Wildner 			break;
27854ba9607SSascha Wildner 		case TYPE_Nd:
27954ba9607SSascha Wildner 			dbm_page_bydesc(&e->match);
28054ba9607SSascha Wildner 			break;
28154ba9607SSascha Wildner 		default:
28254ba9607SSascha Wildner 			dbm_page_bymacro(im - 2, &e->match);
28354ba9607SSascha Wildner 			break;
28454ba9607SSascha Wildner 		}
28554ba9607SSascha Wildner 
28654ba9607SSascha Wildner 		/*
28754ba9607SSascha Wildner 		 * When hashing for deduplication, use the unique
28854ba9607SSascha Wildner 		 * page ID itself instead of a hash function;
28954ba9607SSascha Wildner 		 * that is quite efficient.
29054ba9607SSascha Wildner 		 */
29154ba9607SSascha Wildner 
29254ba9607SSascha Wildner 		for (;;) {
29354ba9607SSascha Wildner 			res = dbm_page_next();
29454ba9607SSascha Wildner 			if (res.page == -1)
29554ba9607SSascha Wildner 				break;
29654ba9607SSascha Wildner 			slot = ohash_lookup_memory(htab,
29754ba9607SSascha Wildner 			    (char *)&res, sizeof(res.page), res.page);
298*99db7d0eSSascha Wildner 			if ((rp = ohash_find(htab, slot)) != NULL) {
299*99db7d0eSSascha Wildner 				rp->bits |= res.bits;
30054ba9607SSascha Wildner 				continue;
301*99db7d0eSSascha Wildner 			}
30254ba9607SSascha Wildner 			rp = mandoc_malloc(sizeof(*rp));
30354ba9607SSascha Wildner 			*rp = res;
30454ba9607SSascha Wildner 			ohash_insert(htab, slot, rp);
30554ba9607SSascha Wildner 		}
30654ba9607SSascha Wildner 	}
30754ba9607SSascha Wildner 	return htab;
30854ba9607SSascha Wildner }
30954ba9607SSascha Wildner 
31054ba9607SSascha Wildner static struct ohash *
manmerge_or(struct expr * e,struct ohash * htab)31154ba9607SSascha Wildner manmerge_or(struct expr *e, struct ohash *htab)
31254ba9607SSascha Wildner {
31354ba9607SSascha Wildner 	while (e != NULL) {
31454ba9607SSascha Wildner 		htab = manmerge(e, htab);
31554ba9607SSascha Wildner 		e = e->next;
31654ba9607SSascha Wildner 	}
31754ba9607SSascha Wildner 	return htab;
31854ba9607SSascha Wildner }
31954ba9607SSascha Wildner 
32054ba9607SSascha Wildner static struct ohash *
manmerge_and(struct expr * e,struct ohash * htab)32154ba9607SSascha Wildner manmerge_and(struct expr *e, struct ohash *htab)
32254ba9607SSascha Wildner {
32354ba9607SSascha Wildner 	struct ohash	*hand, *h1, *h2;
32454ba9607SSascha Wildner 	struct dbm_res	*res;
32554ba9607SSascha Wildner 	unsigned int	 slot1, slot2;
32654ba9607SSascha Wildner 
32754ba9607SSascha Wildner 	/* Evaluate the first term of the AND clause. */
32854ba9607SSascha Wildner 
32954ba9607SSascha Wildner 	hand = manmerge(e, NULL);
33054ba9607SSascha Wildner 
33154ba9607SSascha Wildner 	while ((e = e->next) != NULL) {
33254ba9607SSascha Wildner 
33354ba9607SSascha Wildner 		/* Evaluate the next term and prepare for ANDing. */
33454ba9607SSascha Wildner 
33554ba9607SSascha Wildner 		h2 = manmerge(e, NULL);
33654ba9607SSascha Wildner 		if (ohash_entries(h2) < ohash_entries(hand)) {
33754ba9607SSascha Wildner 			h1 = h2;
33854ba9607SSascha Wildner 			h2 = hand;
33954ba9607SSascha Wildner 		} else
34054ba9607SSascha Wildner 			h1 = hand;
34154ba9607SSascha Wildner 		hand = mandoc_malloc(sizeof(*hand));
34254ba9607SSascha Wildner 		mandoc_ohash_init(hand, 4, offsetof(struct dbm_res, page));
34354ba9607SSascha Wildner 
34454ba9607SSascha Wildner 		/* Keep all pages that are in both result sets. */
34554ba9607SSascha Wildner 
34654ba9607SSascha Wildner 		for (res = ohash_first(h1, &slot1); res != NULL;
34754ba9607SSascha Wildner 		    res = ohash_next(h1, &slot1)) {
34854ba9607SSascha Wildner 			if (ohash_find(h2, ohash_lookup_memory(h2,
34954ba9607SSascha Wildner 			    (char *)res, sizeof(res->page),
35054ba9607SSascha Wildner 			    res->page)) == NULL)
35154ba9607SSascha Wildner 				free(res);
35254ba9607SSascha Wildner 			else
35354ba9607SSascha Wildner 				ohash_insert(hand, ohash_lookup_memory(hand,
35454ba9607SSascha Wildner 				    (char *)res, sizeof(res->page),
35554ba9607SSascha Wildner 				    res->page), res);
35654ba9607SSascha Wildner 		}
35754ba9607SSascha Wildner 
35854ba9607SSascha Wildner 		/* Discard the merged results. */
35954ba9607SSascha Wildner 
36054ba9607SSascha Wildner 		for (res = ohash_first(h2, &slot2); res != NULL;
36154ba9607SSascha Wildner 		    res = ohash_next(h2, &slot2))
36254ba9607SSascha Wildner 			free(res);
36354ba9607SSascha Wildner 		ohash_delete(h2);
36454ba9607SSascha Wildner 		free(h2);
36554ba9607SSascha Wildner 		ohash_delete(h1);
36654ba9607SSascha Wildner 		free(h1);
36754ba9607SSascha Wildner 	}
36854ba9607SSascha Wildner 
36954ba9607SSascha Wildner 	/* Merge the result of the AND into htab. */
37054ba9607SSascha Wildner 
37154ba9607SSascha Wildner 	if (htab == NULL)
37254ba9607SSascha Wildner 		return hand;
37354ba9607SSascha Wildner 
37454ba9607SSascha Wildner 	for (res = ohash_first(hand, &slot1); res != NULL;
37554ba9607SSascha Wildner 	    res = ohash_next(hand, &slot1)) {
37654ba9607SSascha Wildner 		slot2 = ohash_lookup_memory(htab,
37754ba9607SSascha Wildner 		    (char *)res, sizeof(res->page), res->page);
37854ba9607SSascha Wildner 		if (ohash_find(htab, slot2) == NULL)
37954ba9607SSascha Wildner 			ohash_insert(htab, slot2, res);
38054ba9607SSascha Wildner 		else
38154ba9607SSascha Wildner 			free(res);
38254ba9607SSascha Wildner 	}
38354ba9607SSascha Wildner 
38454ba9607SSascha Wildner 	/* Discard the merged result. */
38554ba9607SSascha Wildner 
38654ba9607SSascha Wildner 	ohash_delete(hand);
38754ba9607SSascha Wildner 	free(hand);
38854ba9607SSascha Wildner 	return htab;
38954ba9607SSascha Wildner }
39054ba9607SSascha Wildner 
39154ba9607SSascha Wildner void
mansearch_free(struct manpage * res,size_t sz)39254ba9607SSascha Wildner mansearch_free(struct manpage *res, size_t sz)
39354ba9607SSascha Wildner {
39454ba9607SSascha Wildner 	size_t	 i;
39554ba9607SSascha Wildner 
39654ba9607SSascha Wildner 	for (i = 0; i < sz; i++) {
39754ba9607SSascha Wildner 		free(res[i].file);
39854ba9607SSascha Wildner 		free(res[i].names);
39954ba9607SSascha Wildner 		free(res[i].output);
40054ba9607SSascha Wildner 	}
40154ba9607SSascha Wildner 	free(res);
402070c62a6SFranco Fichtner }
403070c62a6SFranco Fichtner 
404070c62a6SFranco Fichtner static int
manpage_compare(const void * vp1,const void * vp2)405070c62a6SFranco Fichtner manpage_compare(const void *vp1, const void *vp2)
406070c62a6SFranco Fichtner {
407070c62a6SFranco Fichtner 	const struct manpage	*mp1, *mp2;
40854ba9607SSascha Wildner 	const char		*cp1, *cp2;
40954ba9607SSascha Wildner 	size_t			 sz1, sz2;
410070c62a6SFranco Fichtner 	int			 diff;
411070c62a6SFranco Fichtner 
412070c62a6SFranco Fichtner 	mp1 = vp1;
413070c62a6SFranco Fichtner 	mp2 = vp2;
414*99db7d0eSSascha Wildner 	if ((diff = mp2->bits - mp1->bits) ||
415*99db7d0eSSascha Wildner 	    (diff = mp1->sec - mp2->sec))
41654ba9607SSascha Wildner 		return diff;
417070c62a6SFranco Fichtner 
41854ba9607SSascha Wildner 	/* Fall back to alphabetic ordering of names. */
41954ba9607SSascha Wildner 	sz1 = strcspn(mp1->names, "(");
42054ba9607SSascha Wildner 	sz2 = strcspn(mp2->names, "(");
42154ba9607SSascha Wildner 	if (sz1 < sz2)
42254ba9607SSascha Wildner 		sz1 = sz2;
42354ba9607SSascha Wildner 	if ((diff = strncasecmp(mp1->names, mp2->names, sz1)))
42454ba9607SSascha Wildner 		return diff;
425070c62a6SFranco Fichtner 
42654ba9607SSascha Wildner 	/* For identical names and sections, prefer arch-dependent. */
42754ba9607SSascha Wildner 	cp1 = strchr(mp1->names + sz1, '/');
42854ba9607SSascha Wildner 	cp2 = strchr(mp2->names + sz2, '/');
42954ba9607SSascha Wildner 	return cp1 != NULL && cp2 != NULL ? strcasecmp(cp1, cp2) :
43054ba9607SSascha Wildner 	    cp1 != NULL ? -1 : cp2 != NULL ? 1 : 0;
431070c62a6SFranco Fichtner }
432070c62a6SFranco Fichtner 
433070c62a6SFranco Fichtner static char *
buildnames(const struct dbm_page * page)43454ba9607SSascha Wildner buildnames(const struct dbm_page *page)
435070c62a6SFranco Fichtner {
43654ba9607SSascha Wildner 	char	*buf;
43754ba9607SSascha Wildner 	size_t	 i, sz;
43854ba9607SSascha Wildner 
43954ba9607SSascha Wildner 	sz = lstlen(page->name, 2) + 1 + lstlen(page->sect, 2) +
44054ba9607SSascha Wildner 	    (page->arch == NULL ? 0 : 1 + lstlen(page->arch, 2)) + 2;
44154ba9607SSascha Wildner 	buf = mandoc_malloc(sz);
44254ba9607SSascha Wildner 	i = 0;
44354ba9607SSascha Wildner 	lstcat(buf, &i, page->name, ", ");
44454ba9607SSascha Wildner 	buf[i++] = '(';
44554ba9607SSascha Wildner 	lstcat(buf, &i, page->sect, ", ");
44654ba9607SSascha Wildner 	if (page->arch != NULL) {
44754ba9607SSascha Wildner 		buf[i++] = '/';
44854ba9607SSascha Wildner 		lstcat(buf, &i, page->arch, ", ");
44954ba9607SSascha Wildner 	}
45054ba9607SSascha Wildner 	buf[i++] = ')';
45154ba9607SSascha Wildner 	buf[i++] = '\0';
45254ba9607SSascha Wildner 	assert(i == sz);
45354ba9607SSascha Wildner 	return buf;
45454ba9607SSascha Wildner }
45554ba9607SSascha Wildner 
45654ba9607SSascha Wildner /*
45754ba9607SSascha Wildner  * Count the buffer space needed to print the NUL-terminated
45854ba9607SSascha Wildner  * list of NUL-terminated strings, when printing sep separator
45954ba9607SSascha Wildner  * characters between strings.
46054ba9607SSascha Wildner  */
46154ba9607SSascha Wildner static size_t
lstlen(const char * cp,size_t sep)46254ba9607SSascha Wildner lstlen(const char *cp, size_t sep)
46354ba9607SSascha Wildner {
46454ba9607SSascha Wildner 	size_t	 sz;
46554ba9607SSascha Wildner 
46654ba9607SSascha Wildner 	for (sz = 0; *cp != '\0'; cp++) {
46754ba9607SSascha Wildner 
46854ba9607SSascha Wildner 		/* Skip names appearing only in the SYNOPSIS. */
46954ba9607SSascha Wildner 		if (*cp <= (char)(NAME_SYN & NAME_MASK)) {
47054ba9607SSascha Wildner 			while (*cp != '\0')
47154ba9607SSascha Wildner 				cp++;
47254ba9607SSascha Wildner 			continue;
47354ba9607SSascha Wildner 		}
47454ba9607SSascha Wildner 
47554ba9607SSascha Wildner 		/* Skip name class markers. */
47654ba9607SSascha Wildner 		if (*cp < ' ')
47754ba9607SSascha Wildner 			cp++;
47854ba9607SSascha Wildner 
47954ba9607SSascha Wildner 		/* Print a separator before each but the first string. */
48054ba9607SSascha Wildner 		if (sz)
48154ba9607SSascha Wildner 			sz += sep;
48254ba9607SSascha Wildner 
48354ba9607SSascha Wildner 		/* Copy one string. */
48454ba9607SSascha Wildner 		while (*cp != '\0') {
48554ba9607SSascha Wildner 			sz++;
48654ba9607SSascha Wildner 			cp++;
48754ba9607SSascha Wildner 		}
48854ba9607SSascha Wildner 	}
48954ba9607SSascha Wildner 	return sz;
49054ba9607SSascha Wildner }
49154ba9607SSascha Wildner 
49254ba9607SSascha Wildner /*
49354ba9607SSascha Wildner  * Print the NUL-terminated list of NUL-terminated strings
49454ba9607SSascha Wildner  * into the buffer, seperating strings with sep.
49554ba9607SSascha Wildner  */
49654ba9607SSascha Wildner static void
lstcat(char * buf,size_t * i,const char * cp,const char * sep)49754ba9607SSascha Wildner lstcat(char *buf, size_t *i, const char *cp, const char *sep)
49854ba9607SSascha Wildner {
49954ba9607SSascha Wildner 	const char	*s;
50054ba9607SSascha Wildner 	size_t		 i_start;
50154ba9607SSascha Wildner 
50254ba9607SSascha Wildner 	for (i_start = *i; *cp != '\0'; cp++) {
50354ba9607SSascha Wildner 
50454ba9607SSascha Wildner 		/* Skip names appearing only in the SYNOPSIS. */
50554ba9607SSascha Wildner 		if (*cp <= (char)(NAME_SYN & NAME_MASK)) {
50654ba9607SSascha Wildner 			while (*cp != '\0')
50754ba9607SSascha Wildner 				cp++;
50854ba9607SSascha Wildner 			continue;
50954ba9607SSascha Wildner 		}
51054ba9607SSascha Wildner 
51154ba9607SSascha Wildner 		/* Skip name class markers. */
51254ba9607SSascha Wildner 		if (*cp < ' ')
51354ba9607SSascha Wildner 			cp++;
51454ba9607SSascha Wildner 
51554ba9607SSascha Wildner 		/* Print a separator before each but the first string. */
51654ba9607SSascha Wildner 		if (*i > i_start) {
51754ba9607SSascha Wildner 			s = sep;
51854ba9607SSascha Wildner 			while (*s != '\0')
51954ba9607SSascha Wildner 				buf[(*i)++] = *s++;
52054ba9607SSascha Wildner 		}
52154ba9607SSascha Wildner 
52254ba9607SSascha Wildner 		/* Copy one string. */
52354ba9607SSascha Wildner 		while (*cp != '\0')
52454ba9607SSascha Wildner 			buf[(*i)++] = *cp++;
52554ba9607SSascha Wildner 	}
52654ba9607SSascha Wildner 
52754ba9607SSascha Wildner }
52854ba9607SSascha Wildner 
52954ba9607SSascha Wildner /*
53054ba9607SSascha Wildner  * Return 1 if the string *want occurs in any of the strings
53154ba9607SSascha Wildner  * in the NUL-terminated string list *have, or 0 otherwise.
53254ba9607SSascha Wildner  * If either argument is NULL or empty, assume no filtering
53354ba9607SSascha Wildner  * is desired and return 1.
53454ba9607SSascha Wildner  */
53554ba9607SSascha Wildner static int
lstmatch(const char * want,const char * have)53654ba9607SSascha Wildner lstmatch(const char *want, const char *have)
53754ba9607SSascha Wildner {
53854ba9607SSascha Wildner         if (want == NULL || have == NULL || *have == '\0')
53954ba9607SSascha Wildner                 return 1;
54054ba9607SSascha Wildner         while (*have != '\0') {
54154ba9607SSascha Wildner                 if (strcasestr(have, want) != NULL)
54254ba9607SSascha Wildner                         return 1;
54354ba9607SSascha Wildner                 have = strchr(have, '\0') + 1;
54454ba9607SSascha Wildner         }
54554ba9607SSascha Wildner         return 0;
54654ba9607SSascha Wildner }
54754ba9607SSascha Wildner 
54854ba9607SSascha Wildner /*
54954ba9607SSascha Wildner  * Build a list of values taken by the macro im in the manual page.
55054ba9607SSascha Wildner  */
55154ba9607SSascha Wildner static char *
buildoutput(size_t im,struct dbm_page * page)55254ba9607SSascha Wildner buildoutput(size_t im, struct dbm_page *page)
55354ba9607SSascha Wildner {
55454ba9607SSascha Wildner 	const char	*oldoutput, *sep, *input;
55554ba9607SSascha Wildner 	char		*output, *newoutput, *value;
55654ba9607SSascha Wildner 	size_t		 sz, i;
55754ba9607SSascha Wildner 
55854ba9607SSascha Wildner 	switch (im) {
55954ba9607SSascha Wildner 	case KEY_Nd:
56054ba9607SSascha Wildner 		return mandoc_strdup(page->desc);
56154ba9607SSascha Wildner 	case KEY_Nm:
56254ba9607SSascha Wildner 		input = page->name;
56354ba9607SSascha Wildner 		break;
56454ba9607SSascha Wildner 	case KEY_sec:
56554ba9607SSascha Wildner 		input = page->sect;
56654ba9607SSascha Wildner 		break;
56754ba9607SSascha Wildner 	case KEY_arch:
56854ba9607SSascha Wildner 		input = page->arch;
56954ba9607SSascha Wildner 		if (input == NULL)
57054ba9607SSascha Wildner 			input = "all\0";
57154ba9607SSascha Wildner 		break;
57254ba9607SSascha Wildner 	default:
57354ba9607SSascha Wildner 		input = NULL;
57454ba9607SSascha Wildner 		break;
57554ba9607SSascha Wildner 	}
57654ba9607SSascha Wildner 
57754ba9607SSascha Wildner 	if (input != NULL) {
57854ba9607SSascha Wildner 		sz = lstlen(input, 3) + 1;
57954ba9607SSascha Wildner 		output = mandoc_malloc(sz);
58054ba9607SSascha Wildner 		i = 0;
58154ba9607SSascha Wildner 		lstcat(output, &i, input, " # ");
58254ba9607SSascha Wildner 		output[i++] = '\0';
58354ba9607SSascha Wildner 		assert(i == sz);
58454ba9607SSascha Wildner 		return output;
58554ba9607SSascha Wildner 	}
586070c62a6SFranco Fichtner 
587070c62a6SFranco Fichtner 	output = NULL;
58854ba9607SSascha Wildner 	dbm_macro_bypage(im - 2, page->addr);
58954ba9607SSascha Wildner 	while ((value = dbm_macro_next()) != NULL) {
59054ba9607SSascha Wildner 		if (output == NULL) {
591070c62a6SFranco Fichtner 			oldoutput = "";
59254ba9607SSascha Wildner 			sep = "";
593070c62a6SFranco Fichtner 		} else {
594070c62a6SFranco Fichtner 			oldoutput = output;
59554ba9607SSascha Wildner 			sep = " # ";
596070c62a6SFranco Fichtner 		}
59754ba9607SSascha Wildner 		mandoc_asprintf(&newoutput, "%s%s%s", oldoutput, sep, value);
598070c62a6SFranco Fichtner 		free(output);
599070c62a6SFranco Fichtner 		output = newoutput;
600070c62a6SFranco Fichtner 	}
60154ba9607SSascha Wildner 	return output;
602070c62a6SFranco Fichtner }
603070c62a6SFranco Fichtner 
604070c62a6SFranco Fichtner /*
605070c62a6SFranco Fichtner  * Compile a set of string tokens into an expression.
606070c62a6SFranco Fichtner  * Tokens in "argv" are assumed to be individual expression atoms (e.g.,
607070c62a6SFranco Fichtner  * "(", "foo=bar", etc.).
608070c62a6SFranco Fichtner  */
609070c62a6SFranco Fichtner static struct expr *
exprcomp(const struct mansearch * search,int argc,char * argv[],int * argi)61054ba9607SSascha Wildner exprcomp(const struct mansearch *search, int argc, char *argv[], int *argi)
611070c62a6SFranco Fichtner {
61254ba9607SSascha Wildner 	struct expr	*parent, *child;
61354ba9607SSascha Wildner 	int		 needterm, nested;
614070c62a6SFranco Fichtner 
61554ba9607SSascha Wildner 	if ((nested = *argi) == argc)
61654ba9607SSascha Wildner 		return NULL;
61754ba9607SSascha Wildner 	needterm = 1;
61854ba9607SSascha Wildner 	parent = child = NULL;
61954ba9607SSascha Wildner 	while (*argi < argc) {
62054ba9607SSascha Wildner 		if (strcmp(")", argv[*argi]) == 0) {
62154ba9607SSascha Wildner 			if (needterm)
62254ba9607SSascha Wildner 				warnx("missing term "
62354ba9607SSascha Wildner 				    "before closing parenthesis");
62454ba9607SSascha Wildner 			needterm = 0;
62554ba9607SSascha Wildner 			if (nested)
62654ba9607SSascha Wildner 				break;
62754ba9607SSascha Wildner 			warnx("ignoring unmatched right parenthesis");
62854ba9607SSascha Wildner 			++*argi;
629070c62a6SFranco Fichtner 			continue;
630070c62a6SFranco Fichtner 		}
63154ba9607SSascha Wildner 		if (strcmp("-o", argv[*argi]) == 0) {
63254ba9607SSascha Wildner 			if (needterm) {
63354ba9607SSascha Wildner 				if (*argi > 0)
63454ba9607SSascha Wildner 					warnx("ignoring -o after %s",
63554ba9607SSascha Wildner 					    argv[*argi - 1]);
636070c62a6SFranco Fichtner 				else
63754ba9607SSascha Wildner 					warnx("ignoring initial -o");
638070c62a6SFranco Fichtner 			}
63954ba9607SSascha Wildner 			needterm = 1;
64054ba9607SSascha Wildner 			++*argi;
64154ba9607SSascha Wildner 			continue;
642070c62a6SFranco Fichtner 		}
64354ba9607SSascha Wildner 		needterm = 0;
64454ba9607SSascha Wildner 		if (child == NULL) {
64554ba9607SSascha Wildner 			child = expr_and(search, argc, argv, argi);
64654ba9607SSascha Wildner 			continue;
647070c62a6SFranco Fichtner 		}
64854ba9607SSascha Wildner 		if (parent == NULL) {
64954ba9607SSascha Wildner 			parent = mandoc_calloc(1, sizeof(*parent));
65054ba9607SSascha Wildner 			parent->type = EXPR_OR;
65154ba9607SSascha Wildner 			parent->next = NULL;
65254ba9607SSascha Wildner 			parent->child = child;
65354ba9607SSascha Wildner 		}
65454ba9607SSascha Wildner 		child->next = expr_and(search, argc, argv, argi);
65554ba9607SSascha Wildner 		child = child->next;
65654ba9607SSascha Wildner 	}
65754ba9607SSascha Wildner 	if (needterm && *argi)
65854ba9607SSascha Wildner 		warnx("ignoring trailing %s", argv[*argi - 1]);
65954ba9607SSascha Wildner 	return parent == NULL ? child : parent;
660070c62a6SFranco Fichtner }
661070c62a6SFranco Fichtner 
662070c62a6SFranco Fichtner static struct expr *
expr_and(const struct mansearch * search,int argc,char * argv[],int * argi)66354ba9607SSascha Wildner expr_and(const struct mansearch *search, int argc, char *argv[], int *argi)
664070c62a6SFranco Fichtner {
66554ba9607SSascha Wildner 	struct expr	*parent, *child;
66654ba9607SSascha Wildner 	int		 needterm;
667070c62a6SFranco Fichtner 
66854ba9607SSascha Wildner 	needterm = 1;
66954ba9607SSascha Wildner 	parent = child = NULL;
67054ba9607SSascha Wildner 	while (*argi < argc) {
67154ba9607SSascha Wildner 		if (strcmp(")", argv[*argi]) == 0) {
67254ba9607SSascha Wildner 			if (needterm)
67354ba9607SSascha Wildner 				warnx("missing term "
67454ba9607SSascha Wildner 				    "before closing parenthesis");
67554ba9607SSascha Wildner 			needterm = 0;
67654ba9607SSascha Wildner 			break;
677070c62a6SFranco Fichtner 		}
67854ba9607SSascha Wildner 		if (strcmp("-o", argv[*argi]) == 0)
67954ba9607SSascha Wildner 			break;
68054ba9607SSascha Wildner 		if (strcmp("-a", argv[*argi]) == 0) {
68154ba9607SSascha Wildner 			if (needterm) {
68254ba9607SSascha Wildner 				if (*argi > 0)
68354ba9607SSascha Wildner 					warnx("ignoring -a after %s",
68454ba9607SSascha Wildner 					    argv[*argi - 1]);
68554ba9607SSascha Wildner 				else
68654ba9607SSascha Wildner 					warnx("ignoring initial -a");
68754ba9607SSascha Wildner 			}
68854ba9607SSascha Wildner 			needterm = 1;
68954ba9607SSascha Wildner 			++*argi;
69054ba9607SSascha Wildner 			continue;
69154ba9607SSascha Wildner 		}
69254ba9607SSascha Wildner 		if (needterm == 0)
69354ba9607SSascha Wildner 			break;
69454ba9607SSascha Wildner 		if (child == NULL) {
69554ba9607SSascha Wildner 			child = exprterm(search, argc, argv, argi);
69654ba9607SSascha Wildner 			if (child != NULL)
69754ba9607SSascha Wildner 				needterm = 0;
69854ba9607SSascha Wildner 			continue;
69954ba9607SSascha Wildner 		}
70054ba9607SSascha Wildner 		needterm = 0;
70154ba9607SSascha Wildner 		if (parent == NULL) {
70254ba9607SSascha Wildner 			parent = mandoc_calloc(1, sizeof(*parent));
70354ba9607SSascha Wildner 			parent->type = EXPR_AND;
70454ba9607SSascha Wildner 			parent->next = NULL;
70554ba9607SSascha Wildner 			parent->child = child;
70654ba9607SSascha Wildner 		}
70754ba9607SSascha Wildner 		child->next = exprterm(search, argc, argv, argi);
70854ba9607SSascha Wildner 		if (child->next != NULL) {
70954ba9607SSascha Wildner 			child = child->next;
71054ba9607SSascha Wildner 			needterm = 0;
71154ba9607SSascha Wildner 		}
71254ba9607SSascha Wildner 	}
71354ba9607SSascha Wildner 	if (needterm && *argi)
71454ba9607SSascha Wildner 		warnx("ignoring trailing %s", argv[*argi - 1]);
71554ba9607SSascha Wildner 	return parent == NULL ? child : parent;
716070c62a6SFranco Fichtner }
717070c62a6SFranco Fichtner 
718070c62a6SFranco Fichtner static struct expr *
exprterm(const struct mansearch * search,int argc,char * argv[],int * argi)71954ba9607SSascha Wildner exprterm(const struct mansearch *search, int argc, char *argv[], int *argi)
720070c62a6SFranco Fichtner {
721070c62a6SFranco Fichtner 	char		 errbuf[BUFSIZ];
722070c62a6SFranco Fichtner 	struct expr	*e;
723070c62a6SFranco Fichtner 	char		*key, *val;
724070c62a6SFranco Fichtner 	uint64_t	 iterbit;
72554ba9607SSascha Wildner 	int		 cs, i, irc;
726070c62a6SFranco Fichtner 
72754ba9607SSascha Wildner 	if (strcmp("(", argv[*argi]) == 0) {
72854ba9607SSascha Wildner 		++*argi;
72954ba9607SSascha Wildner 		e = exprcomp(search, argc, argv, argi);
73054ba9607SSascha Wildner 		if (*argi < argc) {
73154ba9607SSascha Wildner 			assert(strcmp(")", argv[*argi]) == 0);
73254ba9607SSascha Wildner 			++*argi;
73354ba9607SSascha Wildner 		} else
73454ba9607SSascha Wildner 			warnx("unclosed parenthesis");
73554ba9607SSascha Wildner 		return e;
73654ba9607SSascha Wildner 	}
737070c62a6SFranco Fichtner 
73854ba9607SSascha Wildner 	if (strcmp("-i", argv[*argi]) == 0 && *argi + 1 < argc) {
73954ba9607SSascha Wildner 		cs = 0;
74054ba9607SSascha Wildner 		++*argi;
74154ba9607SSascha Wildner 	} else
74254ba9607SSascha Wildner 		cs = 1;
743070c62a6SFranco Fichtner 
74454ba9607SSascha Wildner 	e = mandoc_calloc(1, sizeof(*e));
74554ba9607SSascha Wildner 	e->type = EXPR_TERM;
74654ba9607SSascha Wildner 	e->bits = 0;
74754ba9607SSascha Wildner 	e->next = NULL;
74854ba9607SSascha Wildner 	e->child = NULL;
74954ba9607SSascha Wildner 
75054ba9607SSascha Wildner 	if (search->argmode == ARG_NAME) {
75154ba9607SSascha Wildner 		e->bits = TYPE_Nm;
75254ba9607SSascha Wildner 		e->match.type = DBM_EXACT;
75354ba9607SSascha Wildner 		e->match.str = argv[(*argi)++];
75454ba9607SSascha Wildner 		return e;
755070c62a6SFranco Fichtner 	}
756070c62a6SFranco Fichtner 
757070c62a6SFranco Fichtner 	/*
75854ba9607SSascha Wildner 	 * Separate macro keys from search string.
75954ba9607SSascha Wildner 	 * If needed, request regular expression handling.
760070c62a6SFranco Fichtner 	 */
761070c62a6SFranco Fichtner 
76254ba9607SSascha Wildner 	if (search->argmode == ARG_WORD) {
76354ba9607SSascha Wildner 		e->bits = TYPE_Nm;
76454ba9607SSascha Wildner 		e->match.type = DBM_REGEX;
76554ba9607SSascha Wildner #if HAVE_REWB_BSD
76654ba9607SSascha Wildner 		mandoc_asprintf(&val, "[[:<:]]%s[[:>:]]", argv[*argi]);
76754ba9607SSascha Wildner #elif HAVE_REWB_SYSV
76854ba9607SSascha Wildner 		mandoc_asprintf(&val, "\\<%s\\>", argv[*argi]);
76954ba9607SSascha Wildner #else
77054ba9607SSascha Wildner 		mandoc_asprintf(&val,
77154ba9607SSascha Wildner 		    "(^|[^a-zA-Z01-9_])%s([^a-zA-Z01-9_]|$)", argv[*argi]);
77254ba9607SSascha Wildner #endif
77354ba9607SSascha Wildner 		cs = 0;
77454ba9607SSascha Wildner 	} else if ((val = strpbrk(argv[*argi], "=~")) == NULL) {
77554ba9607SSascha Wildner 		e->bits = TYPE_Nm | TYPE_Nd;
77654ba9607SSascha Wildner 		e->match.type = DBM_REGEX;
77754ba9607SSascha Wildner 		val = argv[*argi];
77854ba9607SSascha Wildner 		cs = 0;
779070c62a6SFranco Fichtner 	} else {
78054ba9607SSascha Wildner 		if (val == argv[*argi])
78154ba9607SSascha Wildner 			e->bits = TYPE_Nm | TYPE_Nd;
78254ba9607SSascha Wildner 		if (*val == '=') {
78354ba9607SSascha Wildner 			e->match.type = DBM_SUB;
78454ba9607SSascha Wildner 			e->match.str = val + 1;
78554ba9607SSascha Wildner 		} else
78654ba9607SSascha Wildner 			e->match.type = DBM_REGEX;
787070c62a6SFranco Fichtner 		*val++ = '\0';
78854ba9607SSascha Wildner 		if (strstr(argv[*argi], "arch") != NULL)
789070c62a6SFranco Fichtner 			cs = 0;
790070c62a6SFranco Fichtner 	}
791070c62a6SFranco Fichtner 
792070c62a6SFranco Fichtner 	/* Compile regular expressions. */
793070c62a6SFranco Fichtner 
79454ba9607SSascha Wildner 	if (e->match.type == DBM_REGEX) {
79554ba9607SSascha Wildner 		e->match.re = mandoc_malloc(sizeof(*e->match.re));
79654ba9607SSascha Wildner 		irc = regcomp(e->match.re, val,
797070c62a6SFranco Fichtner 		    REG_EXTENDED | REG_NOSUB | (cs ? 0 : REG_ICASE));
79854ba9607SSascha Wildner 		if (irc) {
79954ba9607SSascha Wildner 			regerror(irc, e->match.re, errbuf, sizeof(errbuf));
80054ba9607SSascha Wildner 			warnx("regcomp /%s/: %s", val, errbuf);
80154ba9607SSascha Wildner 		}
80254ba9607SSascha Wildner 		if (search->argmode == ARG_WORD)
803070c62a6SFranco Fichtner 			free(val);
804070c62a6SFranco Fichtner 		if (irc) {
80554ba9607SSascha Wildner 			free(e->match.re);
806070c62a6SFranco Fichtner 			free(e);
80754ba9607SSascha Wildner 			++*argi;
80854ba9607SSascha Wildner 			return NULL;
809070c62a6SFranco Fichtner 		}
810070c62a6SFranco Fichtner 	}
811070c62a6SFranco Fichtner 
81254ba9607SSascha Wildner 	if (e->bits) {
81354ba9607SSascha Wildner 		++*argi;
81454ba9607SSascha Wildner 		return e;
81554ba9607SSascha Wildner 	}
816070c62a6SFranco Fichtner 
817070c62a6SFranco Fichtner 	/*
818070c62a6SFranco Fichtner 	 * Parse out all possible fields.
819070c62a6SFranco Fichtner 	 * If the field doesn't resolve, bail.
820070c62a6SFranco Fichtner 	 */
821070c62a6SFranco Fichtner 
82254ba9607SSascha Wildner 	while (NULL != (key = strsep(&argv[*argi], ","))) {
823070c62a6SFranco Fichtner 		if ('\0' == *key)
824070c62a6SFranco Fichtner 			continue;
82554ba9607SSascha Wildner 		for (i = 0, iterbit = 1; i < KEY_MAX; i++, iterbit <<= 1) {
82654ba9607SSascha Wildner 			if (0 == strcasecmp(key, mansearch_keynames[i])) {
827070c62a6SFranco Fichtner 				e->bits |= iterbit;
828070c62a6SFranco Fichtner 				break;
829070c62a6SFranco Fichtner 			}
830070c62a6SFranco Fichtner 		}
83154ba9607SSascha Wildner 		if (i == KEY_MAX) {
83254ba9607SSascha Wildner 			if (strcasecmp(key, "any"))
83354ba9607SSascha Wildner 				warnx("treating unknown key "
83454ba9607SSascha Wildner 				    "\"%s\" as \"any\"", key);
835070c62a6SFranco Fichtner 			e->bits |= ~0ULL;
836070c62a6SFranco Fichtner 		}
837070c62a6SFranco Fichtner 	}
838070c62a6SFranco Fichtner 
83954ba9607SSascha Wildner 	++*argi;
84054ba9607SSascha Wildner 	return e;
841070c62a6SFranco Fichtner }
842070c62a6SFranco Fichtner 
843070c62a6SFranco Fichtner static void
exprfree(struct expr * e)84454ba9607SSascha Wildner exprfree(struct expr *e)
845070c62a6SFranco Fichtner {
84654ba9607SSascha Wildner 	if (e->next != NULL)
84754ba9607SSascha Wildner 		exprfree(e->next);
84854ba9607SSascha Wildner 	if (e->child != NULL)
84954ba9607SSascha Wildner 		exprfree(e->child);
85054ba9607SSascha Wildner 	free(e);
851070c62a6SFranco Fichtner }
852