xref: /freebsd/contrib/mandoc/mansearch.c (revision a63915c2d7ff177ce364488f86eff99949402051)
1*45a5aec3SBaptiste Daroussin /*	$Id: mansearch.c,v 1.82 2019/07/01 22:56:24 schwarze Exp $ */
261d06d6bSBaptiste Daroussin /*
361d06d6bSBaptiste Daroussin  * Copyright (c) 2012 Kristaps Dzonsons <kristaps@bsd.lv>
47295610fSBaptiste Daroussin  * Copyright (c) 2013-2018 Ingo Schwarze <schwarze@openbsd.org>
561d06d6bSBaptiste Daroussin  *
661d06d6bSBaptiste Daroussin  * Permission to use, copy, modify, and distribute this software for any
761d06d6bSBaptiste Daroussin  * purpose with or without fee is hereby granted, provided that the above
861d06d6bSBaptiste Daroussin  * copyright notice and this permission notice appear in all copies.
961d06d6bSBaptiste Daroussin  *
1061d06d6bSBaptiste Daroussin  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
1161d06d6bSBaptiste Daroussin  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1261d06d6bSBaptiste Daroussin  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1361d06d6bSBaptiste Daroussin  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1461d06d6bSBaptiste Daroussin  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1561d06d6bSBaptiste Daroussin  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1661d06d6bSBaptiste Daroussin  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1761d06d6bSBaptiste Daroussin  */
1861d06d6bSBaptiste Daroussin #include "config.h"
1961d06d6bSBaptiste Daroussin 
2061d06d6bSBaptiste Daroussin #include <sys/mman.h>
2161d06d6bSBaptiste Daroussin #include <sys/types.h>
2261d06d6bSBaptiste Daroussin 
2361d06d6bSBaptiste Daroussin #include <assert.h>
2461d06d6bSBaptiste Daroussin #if HAVE_ERR
2561d06d6bSBaptiste Daroussin #include <err.h>
2661d06d6bSBaptiste Daroussin #endif
2761d06d6bSBaptiste Daroussin #include <errno.h>
2861d06d6bSBaptiste Daroussin #include <fcntl.h>
2961d06d6bSBaptiste Daroussin #include <glob.h>
3061d06d6bSBaptiste Daroussin #include <limits.h>
3161d06d6bSBaptiste Daroussin #include <regex.h>
3261d06d6bSBaptiste Daroussin #include <stdio.h>
3361d06d6bSBaptiste Daroussin #include <stdint.h>
3461d06d6bSBaptiste Daroussin #include <stddef.h>
3561d06d6bSBaptiste Daroussin #include <stdlib.h>
3661d06d6bSBaptiste Daroussin #include <string.h>
3761d06d6bSBaptiste Daroussin #include <unistd.h>
3861d06d6bSBaptiste Daroussin 
3961d06d6bSBaptiste Daroussin #include "mandoc_aux.h"
4061d06d6bSBaptiste Daroussin #include "mandoc_ohash.h"
4161d06d6bSBaptiste Daroussin #include "manconf.h"
4261d06d6bSBaptiste Daroussin #include "mansearch.h"
4361d06d6bSBaptiste Daroussin #include "dbm.h"
4461d06d6bSBaptiste Daroussin 
4561d06d6bSBaptiste Daroussin struct	expr {
4661d06d6bSBaptiste Daroussin 	/* Used for terms: */
4761d06d6bSBaptiste Daroussin 	struct dbm_match match;   /* Match type and expression. */
4861d06d6bSBaptiste Daroussin 	uint64_t	 bits;    /* Type mask. */
4961d06d6bSBaptiste Daroussin 	/* Used for OR and AND groups: */
5061d06d6bSBaptiste Daroussin 	struct expr	*next;    /* Next child in the parent group. */
5161d06d6bSBaptiste Daroussin 	struct expr	*child;   /* First child in this group. */
5261d06d6bSBaptiste Daroussin 	enum { EXPR_TERM, EXPR_OR, EXPR_AND } type;
5361d06d6bSBaptiste Daroussin };
5461d06d6bSBaptiste Daroussin 
5561d06d6bSBaptiste Daroussin const char *const mansearch_keynames[KEY_MAX] = {
5661d06d6bSBaptiste Daroussin 	"arch",	"sec",	"Xr",	"Ar",	"Fa",	"Fl",	"Dv",	"Fn",
5761d06d6bSBaptiste Daroussin 	"Ic",	"Pa",	"Cm",	"Li",	"Em",	"Cd",	"Va",	"Ft",
5861d06d6bSBaptiste Daroussin 	"Tn",	"Er",	"Ev",	"Sy",	"Sh",	"In",	"Ss",	"Ox",
5961d06d6bSBaptiste Daroussin 	"An",	"Mt",	"St",	"Bx",	"At",	"Nx",	"Fx",	"Lk",
6061d06d6bSBaptiste Daroussin 	"Ms",	"Bsx",	"Dx",	"Rs",	"Vt",	"Lb",	"Nm",	"Nd"
6161d06d6bSBaptiste Daroussin };
6261d06d6bSBaptiste Daroussin 
6361d06d6bSBaptiste Daroussin 
6461d06d6bSBaptiste Daroussin static	struct ohash	*manmerge(struct expr *, struct ohash *);
6561d06d6bSBaptiste Daroussin static	struct ohash	*manmerge_term(struct expr *, struct ohash *);
6661d06d6bSBaptiste Daroussin static	struct ohash	*manmerge_or(struct expr *, struct ohash *);
6761d06d6bSBaptiste Daroussin static	struct ohash	*manmerge_and(struct expr *, struct ohash *);
6861d06d6bSBaptiste Daroussin static	char		*buildnames(const struct dbm_page *);
6961d06d6bSBaptiste Daroussin static	char		*buildoutput(size_t, struct dbm_page *);
7061d06d6bSBaptiste Daroussin static	size_t		 lstlen(const char *, size_t);
7161d06d6bSBaptiste Daroussin static	void		 lstcat(char *, size_t *, const char *, const char *);
7261d06d6bSBaptiste Daroussin static	int		 lstmatch(const char *, const char *);
7361d06d6bSBaptiste Daroussin static	struct expr	*exprcomp(const struct mansearch *,
7461d06d6bSBaptiste Daroussin 				int, char *[], int *);
7561d06d6bSBaptiste Daroussin static	struct expr	*expr_and(const struct mansearch *,
7661d06d6bSBaptiste Daroussin 				int, char *[], int *);
7761d06d6bSBaptiste Daroussin static	struct expr	*exprterm(const struct mansearch *,
7861d06d6bSBaptiste Daroussin 				int, char *[], int *);
7961d06d6bSBaptiste Daroussin static	void		 exprfree(struct expr *);
8061d06d6bSBaptiste Daroussin static	int		 manpage_compare(const void *, const void *);
8161d06d6bSBaptiste Daroussin 
8261d06d6bSBaptiste Daroussin 
8361d06d6bSBaptiste Daroussin int
mansearch(const struct mansearch * search,const struct manpaths * paths,int argc,char * argv[],struct manpage ** res,size_t * sz)8461d06d6bSBaptiste Daroussin mansearch(const struct mansearch *search,
8561d06d6bSBaptiste Daroussin 		const struct manpaths *paths,
8661d06d6bSBaptiste Daroussin 		int argc, char *argv[],
8761d06d6bSBaptiste Daroussin 		struct manpage **res, size_t *sz)
8861d06d6bSBaptiste Daroussin {
8961d06d6bSBaptiste Daroussin 	char		 buf[PATH_MAX];
9061d06d6bSBaptiste Daroussin 	struct dbm_res	*rp;
9161d06d6bSBaptiste Daroussin 	struct expr	*e;
9261d06d6bSBaptiste Daroussin 	struct dbm_page	*page;
9361d06d6bSBaptiste Daroussin 	struct manpage	*mpage;
9461d06d6bSBaptiste Daroussin 	struct ohash	*htab;
9561d06d6bSBaptiste Daroussin 	size_t		 cur, i, maxres, outkey;
9661d06d6bSBaptiste Daroussin 	unsigned int	 slot;
9761d06d6bSBaptiste Daroussin 	int		 argi, chdir_status, getcwd_status, im;
9861d06d6bSBaptiste Daroussin 
9961d06d6bSBaptiste Daroussin 	argi = 0;
10061d06d6bSBaptiste Daroussin 	if ((e = exprcomp(search, argc, argv, &argi)) == NULL) {
10161d06d6bSBaptiste Daroussin 		*sz = 0;
10261d06d6bSBaptiste Daroussin 		return 0;
10361d06d6bSBaptiste Daroussin 	}
10461d06d6bSBaptiste Daroussin 
10561d06d6bSBaptiste Daroussin 	cur = maxres = 0;
10661d06d6bSBaptiste Daroussin 	if (res != NULL)
10761d06d6bSBaptiste Daroussin 		*res = NULL;
10861d06d6bSBaptiste Daroussin 
10961d06d6bSBaptiste Daroussin 	outkey = KEY_Nd;
11061d06d6bSBaptiste Daroussin 	if (search->outkey != NULL)
11161d06d6bSBaptiste Daroussin 		for (im = 0; im < KEY_MAX; im++)
11261d06d6bSBaptiste Daroussin 			if (0 == strcasecmp(search->outkey,
11361d06d6bSBaptiste Daroussin 			    mansearch_keynames[im])) {
11461d06d6bSBaptiste Daroussin 				outkey = im;
11561d06d6bSBaptiste Daroussin 				break;
11661d06d6bSBaptiste Daroussin 			}
11761d06d6bSBaptiste Daroussin 
11861d06d6bSBaptiste Daroussin 	/*
11961d06d6bSBaptiste Daroussin 	 * Remember the original working directory, if possible.
12061d06d6bSBaptiste Daroussin 	 * This will be needed if the second or a later directory
12161d06d6bSBaptiste Daroussin 	 * is given as a relative path.
12261d06d6bSBaptiste Daroussin 	 * Do not error out if the current directory is not
12361d06d6bSBaptiste Daroussin 	 * searchable: Maybe it won't be needed after all.
12461d06d6bSBaptiste Daroussin 	 */
12561d06d6bSBaptiste Daroussin 
12661d06d6bSBaptiste Daroussin 	if (getcwd(buf, PATH_MAX) == NULL) {
12761d06d6bSBaptiste Daroussin 		getcwd_status = 0;
12861d06d6bSBaptiste Daroussin 		(void)strlcpy(buf, strerror(errno), sizeof(buf));
12961d06d6bSBaptiste Daroussin 	} else
13061d06d6bSBaptiste Daroussin 		getcwd_status = 1;
13161d06d6bSBaptiste Daroussin 
13261d06d6bSBaptiste Daroussin 	/*
13361d06d6bSBaptiste Daroussin 	 * Loop over the directories (containing databases) for us to
13461d06d6bSBaptiste Daroussin 	 * search.
13561d06d6bSBaptiste Daroussin 	 * Don't let missing/bad databases/directories phase us.
13661d06d6bSBaptiste Daroussin 	 * In each, try to open the resident database and, if it opens,
13761d06d6bSBaptiste Daroussin 	 * scan it for our match expression.
13861d06d6bSBaptiste Daroussin 	 */
13961d06d6bSBaptiste Daroussin 
14061d06d6bSBaptiste Daroussin 	chdir_status = 0;
14161d06d6bSBaptiste Daroussin 	for (i = 0; i < paths->sz; i++) {
14261d06d6bSBaptiste Daroussin 		if (chdir_status && paths->paths[i][0] != '/') {
14361d06d6bSBaptiste Daroussin 			if ( ! getcwd_status) {
14461d06d6bSBaptiste Daroussin 				warnx("%s: getcwd: %s", paths->paths[i], buf);
14561d06d6bSBaptiste Daroussin 				continue;
14661d06d6bSBaptiste Daroussin 			} else if (chdir(buf) == -1) {
14761d06d6bSBaptiste Daroussin 				warn("%s", buf);
14861d06d6bSBaptiste Daroussin 				continue;
14961d06d6bSBaptiste Daroussin 			}
15061d06d6bSBaptiste Daroussin 		}
15161d06d6bSBaptiste Daroussin 		if (chdir(paths->paths[i]) == -1) {
15261d06d6bSBaptiste Daroussin 			warn("%s", paths->paths[i]);
15361d06d6bSBaptiste Daroussin 			continue;
15461d06d6bSBaptiste Daroussin 		}
15561d06d6bSBaptiste Daroussin 		chdir_status = 1;
15661d06d6bSBaptiste Daroussin 
15761d06d6bSBaptiste Daroussin 		if (dbm_open(MANDOC_DB) == -1) {
15861d06d6bSBaptiste Daroussin 			if (errno != ENOENT)
15961d06d6bSBaptiste Daroussin 				warn("%s/%s", paths->paths[i], MANDOC_DB);
16061d06d6bSBaptiste Daroussin 			continue;
16161d06d6bSBaptiste Daroussin 		}
16261d06d6bSBaptiste Daroussin 
16361d06d6bSBaptiste Daroussin 		if ((htab = manmerge(e, NULL)) == NULL) {
16461d06d6bSBaptiste Daroussin 			dbm_close();
16561d06d6bSBaptiste Daroussin 			continue;
16661d06d6bSBaptiste Daroussin 		}
16761d06d6bSBaptiste Daroussin 
16861d06d6bSBaptiste Daroussin 		for (rp = ohash_first(htab, &slot); rp != NULL;
16961d06d6bSBaptiste Daroussin 		    rp = ohash_next(htab, &slot)) {
17061d06d6bSBaptiste Daroussin 			page = dbm_page_get(rp->page);
17161d06d6bSBaptiste Daroussin 
17261d06d6bSBaptiste Daroussin 			if (lstmatch(search->sec, page->sect) == 0 ||
17361d06d6bSBaptiste Daroussin 			    lstmatch(search->arch, page->arch) == 0 ||
17461d06d6bSBaptiste Daroussin 			    (search->argmode == ARG_NAME &&
17561d06d6bSBaptiste Daroussin 			     rp->bits <= (int32_t)(NAME_SYN & NAME_MASK)))
17661d06d6bSBaptiste Daroussin 				continue;
17761d06d6bSBaptiste Daroussin 
17861d06d6bSBaptiste Daroussin 			if (res == NULL) {
17961d06d6bSBaptiste Daroussin 				cur = 1;
18061d06d6bSBaptiste Daroussin 				break;
18161d06d6bSBaptiste Daroussin 			}
18261d06d6bSBaptiste Daroussin 			if (cur + 1 > maxres) {
18361d06d6bSBaptiste Daroussin 				maxres += 1024;
18461d06d6bSBaptiste Daroussin 				*res = mandoc_reallocarray(*res,
18561d06d6bSBaptiste Daroussin 				    maxres, sizeof(**res));
18661d06d6bSBaptiste Daroussin 			}
18761d06d6bSBaptiste Daroussin 			mpage = *res + cur;
18861d06d6bSBaptiste Daroussin 			mandoc_asprintf(&mpage->file, "%s/%s",
18961d06d6bSBaptiste Daroussin 			    paths->paths[i], page->file + 1);
19061d06d6bSBaptiste Daroussin 			if (access(chdir_status ? page->file + 1 :
19161d06d6bSBaptiste Daroussin 			    mpage->file, R_OK) == -1) {
19261d06d6bSBaptiste Daroussin 				warn("%s", mpage->file);
19361d06d6bSBaptiste Daroussin 				warnx("outdated mandoc.db contains "
19461d06d6bSBaptiste Daroussin 				    "bogus %s entry, run makewhatis %s",
19561d06d6bSBaptiste Daroussin 				    page->file + 1, paths->paths[i]);
19661d06d6bSBaptiste Daroussin 				free(mpage->file);
19761d06d6bSBaptiste Daroussin 				free(rp);
19861d06d6bSBaptiste Daroussin 				continue;
19961d06d6bSBaptiste Daroussin 			}
20061d06d6bSBaptiste Daroussin 			mpage->names = buildnames(page);
20161d06d6bSBaptiste Daroussin 			mpage->output = buildoutput(outkey, page);
202*45a5aec3SBaptiste Daroussin 			mpage->bits = search->firstmatch ? rp->bits : 0;
20361d06d6bSBaptiste Daroussin 			mpage->ipath = i;
20461d06d6bSBaptiste Daroussin 			mpage->sec = *page->sect - '0';
20561d06d6bSBaptiste Daroussin 			if (mpage->sec < 0 || mpage->sec > 9)
20661d06d6bSBaptiste Daroussin 				mpage->sec = 10;
20761d06d6bSBaptiste Daroussin 			mpage->form = *page->file;
20861d06d6bSBaptiste Daroussin 			free(rp);
20961d06d6bSBaptiste Daroussin 			cur++;
21061d06d6bSBaptiste Daroussin 		}
21161d06d6bSBaptiste Daroussin 		ohash_delete(htab);
21261d06d6bSBaptiste Daroussin 		free(htab);
21361d06d6bSBaptiste Daroussin 		dbm_close();
21461d06d6bSBaptiste Daroussin 
21561d06d6bSBaptiste Daroussin 		/*
21661d06d6bSBaptiste Daroussin 		 * In man(1) mode, prefer matches in earlier trees
21761d06d6bSBaptiste Daroussin 		 * over matches in later trees.
21861d06d6bSBaptiste Daroussin 		 */
21961d06d6bSBaptiste Daroussin 
22061d06d6bSBaptiste Daroussin 		if (cur && search->firstmatch)
22161d06d6bSBaptiste Daroussin 			break;
22261d06d6bSBaptiste Daroussin 	}
22361d06d6bSBaptiste Daroussin 	if (res != NULL)
22461d06d6bSBaptiste Daroussin 		qsort(*res, cur, sizeof(struct manpage), manpage_compare);
22561d06d6bSBaptiste Daroussin 	if (chdir_status && getcwd_status && chdir(buf) == -1)
22661d06d6bSBaptiste Daroussin 		warn("%s", buf);
22761d06d6bSBaptiste Daroussin 	exprfree(e);
22861d06d6bSBaptiste Daroussin 	*sz = cur;
22961d06d6bSBaptiste Daroussin 	return res != NULL || cur;
23061d06d6bSBaptiste Daroussin }
23161d06d6bSBaptiste Daroussin 
23261d06d6bSBaptiste Daroussin /*
23361d06d6bSBaptiste Daroussin  * Merge the results for the expression tree rooted at e
23461d06d6bSBaptiste Daroussin  * into the the result list htab.
23561d06d6bSBaptiste Daroussin  */
23661d06d6bSBaptiste Daroussin static struct ohash *
manmerge(struct expr * e,struct ohash * htab)23761d06d6bSBaptiste Daroussin manmerge(struct expr *e, struct ohash *htab)
23861d06d6bSBaptiste Daroussin {
23961d06d6bSBaptiste Daroussin 	switch (e->type) {
24061d06d6bSBaptiste Daroussin 	case EXPR_TERM:
24161d06d6bSBaptiste Daroussin 		return manmerge_term(e, htab);
24261d06d6bSBaptiste Daroussin 	case EXPR_OR:
24361d06d6bSBaptiste Daroussin 		return manmerge_or(e->child, htab);
24461d06d6bSBaptiste Daroussin 	case EXPR_AND:
24561d06d6bSBaptiste Daroussin 		return manmerge_and(e->child, htab);
24661d06d6bSBaptiste Daroussin 	default:
24761d06d6bSBaptiste Daroussin 		abort();
24861d06d6bSBaptiste Daroussin 	}
24961d06d6bSBaptiste Daroussin }
25061d06d6bSBaptiste Daroussin 
25161d06d6bSBaptiste Daroussin static struct ohash *
manmerge_term(struct expr * e,struct ohash * htab)25261d06d6bSBaptiste Daroussin manmerge_term(struct expr *e, struct ohash *htab)
25361d06d6bSBaptiste Daroussin {
25461d06d6bSBaptiste Daroussin 	struct dbm_res	 res, *rp;
25561d06d6bSBaptiste Daroussin 	uint64_t	 ib;
25661d06d6bSBaptiste Daroussin 	unsigned int	 slot;
25761d06d6bSBaptiste Daroussin 	int		 im;
25861d06d6bSBaptiste Daroussin 
25961d06d6bSBaptiste Daroussin 	if (htab == NULL) {
26061d06d6bSBaptiste Daroussin 		htab = mandoc_malloc(sizeof(*htab));
26161d06d6bSBaptiste Daroussin 		mandoc_ohash_init(htab, 4, offsetof(struct dbm_res, page));
26261d06d6bSBaptiste Daroussin 	}
26361d06d6bSBaptiste Daroussin 
26461d06d6bSBaptiste Daroussin 	for (im = 0, ib = 1; im < KEY_MAX; im++, ib <<= 1) {
26561d06d6bSBaptiste Daroussin 		if ((e->bits & ib) == 0)
26661d06d6bSBaptiste Daroussin 			continue;
26761d06d6bSBaptiste Daroussin 
26861d06d6bSBaptiste Daroussin 		switch (ib) {
26961d06d6bSBaptiste Daroussin 		case TYPE_arch:
27061d06d6bSBaptiste Daroussin 			dbm_page_byarch(&e->match);
27161d06d6bSBaptiste Daroussin 			break;
27261d06d6bSBaptiste Daroussin 		case TYPE_sec:
27361d06d6bSBaptiste Daroussin 			dbm_page_bysect(&e->match);
27461d06d6bSBaptiste Daroussin 			break;
27561d06d6bSBaptiste Daroussin 		case TYPE_Nm:
27661d06d6bSBaptiste Daroussin 			dbm_page_byname(&e->match);
27761d06d6bSBaptiste Daroussin 			break;
27861d06d6bSBaptiste Daroussin 		case TYPE_Nd:
27961d06d6bSBaptiste Daroussin 			dbm_page_bydesc(&e->match);
28061d06d6bSBaptiste Daroussin 			break;
28161d06d6bSBaptiste Daroussin 		default:
28261d06d6bSBaptiste Daroussin 			dbm_page_bymacro(im - 2, &e->match);
28361d06d6bSBaptiste Daroussin 			break;
28461d06d6bSBaptiste Daroussin 		}
28561d06d6bSBaptiste Daroussin 
28661d06d6bSBaptiste Daroussin 		/*
28761d06d6bSBaptiste Daroussin 		 * When hashing for deduplication, use the unique
28861d06d6bSBaptiste Daroussin 		 * page ID itself instead of a hash function;
28961d06d6bSBaptiste Daroussin 		 * that is quite efficient.
29061d06d6bSBaptiste Daroussin 		 */
29161d06d6bSBaptiste Daroussin 
29261d06d6bSBaptiste Daroussin 		for (;;) {
29361d06d6bSBaptiste Daroussin 			res = dbm_page_next();
29461d06d6bSBaptiste Daroussin 			if (res.page == -1)
29561d06d6bSBaptiste Daroussin 				break;
29661d06d6bSBaptiste Daroussin 			slot = ohash_lookup_memory(htab,
29761d06d6bSBaptiste Daroussin 			    (char *)&res, sizeof(res.page), res.page);
298*45a5aec3SBaptiste Daroussin 			if ((rp = ohash_find(htab, slot)) != NULL) {
299*45a5aec3SBaptiste Daroussin 				rp->bits |= res.bits;
30061d06d6bSBaptiste Daroussin 				continue;
301*45a5aec3SBaptiste Daroussin 			}
30261d06d6bSBaptiste Daroussin 			rp = mandoc_malloc(sizeof(*rp));
30361d06d6bSBaptiste Daroussin 			*rp = res;
30461d06d6bSBaptiste Daroussin 			ohash_insert(htab, slot, rp);
30561d06d6bSBaptiste Daroussin 		}
30661d06d6bSBaptiste Daroussin 	}
30761d06d6bSBaptiste Daroussin 	return htab;
30861d06d6bSBaptiste Daroussin }
30961d06d6bSBaptiste Daroussin 
31061d06d6bSBaptiste Daroussin static struct ohash *
manmerge_or(struct expr * e,struct ohash * htab)31161d06d6bSBaptiste Daroussin manmerge_or(struct expr *e, struct ohash *htab)
31261d06d6bSBaptiste Daroussin {
31361d06d6bSBaptiste Daroussin 	while (e != NULL) {
31461d06d6bSBaptiste Daroussin 		htab = manmerge(e, htab);
31561d06d6bSBaptiste Daroussin 		e = e->next;
31661d06d6bSBaptiste Daroussin 	}
31761d06d6bSBaptiste Daroussin 	return htab;
31861d06d6bSBaptiste Daroussin }
31961d06d6bSBaptiste Daroussin 
32061d06d6bSBaptiste Daroussin static struct ohash *
manmerge_and(struct expr * e,struct ohash * htab)32161d06d6bSBaptiste Daroussin manmerge_and(struct expr *e, struct ohash *htab)
32261d06d6bSBaptiste Daroussin {
32361d06d6bSBaptiste Daroussin 	struct ohash	*hand, *h1, *h2;
32461d06d6bSBaptiste Daroussin 	struct dbm_res	*res;
32561d06d6bSBaptiste Daroussin 	unsigned int	 slot1, slot2;
32661d06d6bSBaptiste Daroussin 
32761d06d6bSBaptiste Daroussin 	/* Evaluate the first term of the AND clause. */
32861d06d6bSBaptiste Daroussin 
32961d06d6bSBaptiste Daroussin 	hand = manmerge(e, NULL);
33061d06d6bSBaptiste Daroussin 
33161d06d6bSBaptiste Daroussin 	while ((e = e->next) != NULL) {
33261d06d6bSBaptiste Daroussin 
33361d06d6bSBaptiste Daroussin 		/* Evaluate the next term and prepare for ANDing. */
33461d06d6bSBaptiste Daroussin 
33561d06d6bSBaptiste Daroussin 		h2 = manmerge(e, NULL);
33661d06d6bSBaptiste Daroussin 		if (ohash_entries(h2) < ohash_entries(hand)) {
33761d06d6bSBaptiste Daroussin 			h1 = h2;
33861d06d6bSBaptiste Daroussin 			h2 = hand;
33961d06d6bSBaptiste Daroussin 		} else
34061d06d6bSBaptiste Daroussin 			h1 = hand;
34161d06d6bSBaptiste Daroussin 		hand = mandoc_malloc(sizeof(*hand));
34261d06d6bSBaptiste Daroussin 		mandoc_ohash_init(hand, 4, offsetof(struct dbm_res, page));
34361d06d6bSBaptiste Daroussin 
34461d06d6bSBaptiste Daroussin 		/* Keep all pages that are in both result sets. */
34561d06d6bSBaptiste Daroussin 
34661d06d6bSBaptiste Daroussin 		for (res = ohash_first(h1, &slot1); res != NULL;
34761d06d6bSBaptiste Daroussin 		    res = ohash_next(h1, &slot1)) {
34861d06d6bSBaptiste Daroussin 			if (ohash_find(h2, ohash_lookup_memory(h2,
34961d06d6bSBaptiste Daroussin 			    (char *)res, sizeof(res->page),
35061d06d6bSBaptiste Daroussin 			    res->page)) == NULL)
35161d06d6bSBaptiste Daroussin 				free(res);
35261d06d6bSBaptiste Daroussin 			else
35361d06d6bSBaptiste Daroussin 				ohash_insert(hand, ohash_lookup_memory(hand,
35461d06d6bSBaptiste Daroussin 				    (char *)res, sizeof(res->page),
35561d06d6bSBaptiste Daroussin 				    res->page), res);
35661d06d6bSBaptiste Daroussin 		}
35761d06d6bSBaptiste Daroussin 
35861d06d6bSBaptiste Daroussin 		/* Discard the merged results. */
35961d06d6bSBaptiste Daroussin 
36061d06d6bSBaptiste Daroussin 		for (res = ohash_first(h2, &slot2); res != NULL;
36161d06d6bSBaptiste Daroussin 		    res = ohash_next(h2, &slot2))
36261d06d6bSBaptiste Daroussin 			free(res);
36361d06d6bSBaptiste Daroussin 		ohash_delete(h2);
36461d06d6bSBaptiste Daroussin 		free(h2);
36561d06d6bSBaptiste Daroussin 		ohash_delete(h1);
36661d06d6bSBaptiste Daroussin 		free(h1);
36761d06d6bSBaptiste Daroussin 	}
36861d06d6bSBaptiste Daroussin 
36961d06d6bSBaptiste Daroussin 	/* Merge the result of the AND into htab. */
37061d06d6bSBaptiste Daroussin 
37161d06d6bSBaptiste Daroussin 	if (htab == NULL)
37261d06d6bSBaptiste Daroussin 		return hand;
37361d06d6bSBaptiste Daroussin 
37461d06d6bSBaptiste Daroussin 	for (res = ohash_first(hand, &slot1); res != NULL;
37561d06d6bSBaptiste Daroussin 	    res = ohash_next(hand, &slot1)) {
37661d06d6bSBaptiste Daroussin 		slot2 = ohash_lookup_memory(htab,
37761d06d6bSBaptiste Daroussin 		    (char *)res, sizeof(res->page), res->page);
37861d06d6bSBaptiste Daroussin 		if (ohash_find(htab, slot2) == NULL)
37961d06d6bSBaptiste Daroussin 			ohash_insert(htab, slot2, res);
38061d06d6bSBaptiste Daroussin 		else
38161d06d6bSBaptiste Daroussin 			free(res);
38261d06d6bSBaptiste Daroussin 	}
38361d06d6bSBaptiste Daroussin 
38461d06d6bSBaptiste Daroussin 	/* Discard the merged result. */
38561d06d6bSBaptiste Daroussin 
38661d06d6bSBaptiste Daroussin 	ohash_delete(hand);
38761d06d6bSBaptiste Daroussin 	free(hand);
38861d06d6bSBaptiste Daroussin 	return htab;
38961d06d6bSBaptiste Daroussin }
39061d06d6bSBaptiste Daroussin 
39161d06d6bSBaptiste Daroussin void
mansearch_free(struct manpage * res,size_t sz)39261d06d6bSBaptiste Daroussin mansearch_free(struct manpage *res, size_t sz)
39361d06d6bSBaptiste Daroussin {
39461d06d6bSBaptiste Daroussin 	size_t	 i;
39561d06d6bSBaptiste Daroussin 
39661d06d6bSBaptiste Daroussin 	for (i = 0; i < sz; i++) {
39761d06d6bSBaptiste Daroussin 		free(res[i].file);
39861d06d6bSBaptiste Daroussin 		free(res[i].names);
39961d06d6bSBaptiste Daroussin 		free(res[i].output);
40061d06d6bSBaptiste Daroussin 	}
40161d06d6bSBaptiste Daroussin 	free(res);
40261d06d6bSBaptiste Daroussin }
40361d06d6bSBaptiste Daroussin 
40461d06d6bSBaptiste Daroussin static int
manpage_compare(const void * vp1,const void * vp2)40561d06d6bSBaptiste Daroussin manpage_compare(const void *vp1, const void *vp2)
40661d06d6bSBaptiste Daroussin {
40761d06d6bSBaptiste Daroussin 	const struct manpage	*mp1, *mp2;
40861d06d6bSBaptiste Daroussin 	const char		*cp1, *cp2;
40961d06d6bSBaptiste Daroussin 	size_t			 sz1, sz2;
41061d06d6bSBaptiste Daroussin 	int			 diff;
41161d06d6bSBaptiste Daroussin 
41261d06d6bSBaptiste Daroussin 	mp1 = vp1;
41361d06d6bSBaptiste Daroussin 	mp2 = vp2;
414*45a5aec3SBaptiste Daroussin 	if ((diff = mp2->bits - mp1->bits) ||
415*45a5aec3SBaptiste Daroussin 	    (diff = mp1->sec - mp2->sec))
41661d06d6bSBaptiste Daroussin 		return diff;
41761d06d6bSBaptiste Daroussin 
41861d06d6bSBaptiste Daroussin 	/* Fall back to alphabetic ordering of names. */
41961d06d6bSBaptiste Daroussin 	sz1 = strcspn(mp1->names, "(");
42061d06d6bSBaptiste Daroussin 	sz2 = strcspn(mp2->names, "(");
42161d06d6bSBaptiste Daroussin 	if (sz1 < sz2)
42261d06d6bSBaptiste Daroussin 		sz1 = sz2;
42361d06d6bSBaptiste Daroussin 	if ((diff = strncasecmp(mp1->names, mp2->names, sz1)))
42461d06d6bSBaptiste Daroussin 		return diff;
42561d06d6bSBaptiste Daroussin 
42661d06d6bSBaptiste Daroussin 	/* For identical names and sections, prefer arch-dependent. */
42761d06d6bSBaptiste Daroussin 	cp1 = strchr(mp1->names + sz1, '/');
42861d06d6bSBaptiste Daroussin 	cp2 = strchr(mp2->names + sz2, '/');
42961d06d6bSBaptiste Daroussin 	return cp1 != NULL && cp2 != NULL ? strcasecmp(cp1, cp2) :
43061d06d6bSBaptiste Daroussin 	    cp1 != NULL ? -1 : cp2 != NULL ? 1 : 0;
43161d06d6bSBaptiste Daroussin }
43261d06d6bSBaptiste Daroussin 
43361d06d6bSBaptiste Daroussin static char *
buildnames(const struct dbm_page * page)43461d06d6bSBaptiste Daroussin buildnames(const struct dbm_page *page)
43561d06d6bSBaptiste Daroussin {
43661d06d6bSBaptiste Daroussin 	char	*buf;
43761d06d6bSBaptiste Daroussin 	size_t	 i, sz;
43861d06d6bSBaptiste Daroussin 
43961d06d6bSBaptiste Daroussin 	sz = lstlen(page->name, 2) + 1 + lstlen(page->sect, 2) +
44061d06d6bSBaptiste Daroussin 	    (page->arch == NULL ? 0 : 1 + lstlen(page->arch, 2)) + 2;
44161d06d6bSBaptiste Daroussin 	buf = mandoc_malloc(sz);
44261d06d6bSBaptiste Daroussin 	i = 0;
44361d06d6bSBaptiste Daroussin 	lstcat(buf, &i, page->name, ", ");
44461d06d6bSBaptiste Daroussin 	buf[i++] = '(';
44561d06d6bSBaptiste Daroussin 	lstcat(buf, &i, page->sect, ", ");
44661d06d6bSBaptiste Daroussin 	if (page->arch != NULL) {
44761d06d6bSBaptiste Daroussin 		buf[i++] = '/';
44861d06d6bSBaptiste Daroussin 		lstcat(buf, &i, page->arch, ", ");
44961d06d6bSBaptiste Daroussin 	}
45061d06d6bSBaptiste Daroussin 	buf[i++] = ')';
45161d06d6bSBaptiste Daroussin 	buf[i++] = '\0';
45261d06d6bSBaptiste Daroussin 	assert(i == sz);
45361d06d6bSBaptiste Daroussin 	return buf;
45461d06d6bSBaptiste Daroussin }
45561d06d6bSBaptiste Daroussin 
45661d06d6bSBaptiste Daroussin /*
45761d06d6bSBaptiste Daroussin  * Count the buffer space needed to print the NUL-terminated
45861d06d6bSBaptiste Daroussin  * list of NUL-terminated strings, when printing sep separator
45961d06d6bSBaptiste Daroussin  * characters between strings.
46061d06d6bSBaptiste Daroussin  */
46161d06d6bSBaptiste Daroussin static size_t
lstlen(const char * cp,size_t sep)46261d06d6bSBaptiste Daroussin lstlen(const char *cp, size_t sep)
46361d06d6bSBaptiste Daroussin {
46461d06d6bSBaptiste Daroussin 	size_t	 sz;
46561d06d6bSBaptiste Daroussin 
46661d06d6bSBaptiste Daroussin 	for (sz = 0; *cp != '\0'; cp++) {
46761d06d6bSBaptiste Daroussin 
46861d06d6bSBaptiste Daroussin 		/* Skip names appearing only in the SYNOPSIS. */
46961d06d6bSBaptiste Daroussin 		if (*cp <= (char)(NAME_SYN & NAME_MASK)) {
47061d06d6bSBaptiste Daroussin 			while (*cp != '\0')
47161d06d6bSBaptiste Daroussin 				cp++;
47261d06d6bSBaptiste Daroussin 			continue;
47361d06d6bSBaptiste Daroussin 		}
47461d06d6bSBaptiste Daroussin 
47561d06d6bSBaptiste Daroussin 		/* Skip name class markers. */
47661d06d6bSBaptiste Daroussin 		if (*cp < ' ')
47761d06d6bSBaptiste Daroussin 			cp++;
47861d06d6bSBaptiste Daroussin 
47961d06d6bSBaptiste Daroussin 		/* Print a separator before each but the first string. */
48061d06d6bSBaptiste Daroussin 		if (sz)
48161d06d6bSBaptiste Daroussin 			sz += sep;
48261d06d6bSBaptiste Daroussin 
48361d06d6bSBaptiste Daroussin 		/* Copy one string. */
48461d06d6bSBaptiste Daroussin 		while (*cp != '\0') {
48561d06d6bSBaptiste Daroussin 			sz++;
48661d06d6bSBaptiste Daroussin 			cp++;
48761d06d6bSBaptiste Daroussin 		}
48861d06d6bSBaptiste Daroussin 	}
48961d06d6bSBaptiste Daroussin 	return sz;
49061d06d6bSBaptiste Daroussin }
49161d06d6bSBaptiste Daroussin 
49261d06d6bSBaptiste Daroussin /*
49361d06d6bSBaptiste Daroussin  * Print the NUL-terminated list of NUL-terminated strings
49461d06d6bSBaptiste Daroussin  * into the buffer, seperating strings with sep.
49561d06d6bSBaptiste Daroussin  */
49661d06d6bSBaptiste Daroussin static void
lstcat(char * buf,size_t * i,const char * cp,const char * sep)49761d06d6bSBaptiste Daroussin lstcat(char *buf, size_t *i, const char *cp, const char *sep)
49861d06d6bSBaptiste Daroussin {
49961d06d6bSBaptiste Daroussin 	const char	*s;
50061d06d6bSBaptiste Daroussin 	size_t		 i_start;
50161d06d6bSBaptiste Daroussin 
50261d06d6bSBaptiste Daroussin 	for (i_start = *i; *cp != '\0'; cp++) {
50361d06d6bSBaptiste Daroussin 
50461d06d6bSBaptiste Daroussin 		/* Skip names appearing only in the SYNOPSIS. */
50561d06d6bSBaptiste Daroussin 		if (*cp <= (char)(NAME_SYN & NAME_MASK)) {
50661d06d6bSBaptiste Daroussin 			while (*cp != '\0')
50761d06d6bSBaptiste Daroussin 				cp++;
50861d06d6bSBaptiste Daroussin 			continue;
50961d06d6bSBaptiste Daroussin 		}
51061d06d6bSBaptiste Daroussin 
51161d06d6bSBaptiste Daroussin 		/* Skip name class markers. */
51261d06d6bSBaptiste Daroussin 		if (*cp < ' ')
51361d06d6bSBaptiste Daroussin 			cp++;
51461d06d6bSBaptiste Daroussin 
51561d06d6bSBaptiste Daroussin 		/* Print a separator before each but the first string. */
51661d06d6bSBaptiste Daroussin 		if (*i > i_start) {
51761d06d6bSBaptiste Daroussin 			s = sep;
51861d06d6bSBaptiste Daroussin 			while (*s != '\0')
51961d06d6bSBaptiste Daroussin 				buf[(*i)++] = *s++;
52061d06d6bSBaptiste Daroussin 		}
52161d06d6bSBaptiste Daroussin 
52261d06d6bSBaptiste Daroussin 		/* Copy one string. */
52361d06d6bSBaptiste Daroussin 		while (*cp != '\0')
52461d06d6bSBaptiste Daroussin 			buf[(*i)++] = *cp++;
52561d06d6bSBaptiste Daroussin 	}
52661d06d6bSBaptiste Daroussin 
52761d06d6bSBaptiste Daroussin }
52861d06d6bSBaptiste Daroussin 
52961d06d6bSBaptiste Daroussin /*
53061d06d6bSBaptiste Daroussin  * Return 1 if the string *want occurs in any of the strings
53161d06d6bSBaptiste Daroussin  * in the NUL-terminated string list *have, or 0 otherwise.
53261d06d6bSBaptiste Daroussin  * If either argument is NULL or empty, assume no filtering
53361d06d6bSBaptiste Daroussin  * is desired and return 1.
53461d06d6bSBaptiste Daroussin  */
53561d06d6bSBaptiste Daroussin static int
lstmatch(const char * want,const char * have)53661d06d6bSBaptiste Daroussin lstmatch(const char *want, const char *have)
53761d06d6bSBaptiste Daroussin {
53861d06d6bSBaptiste Daroussin         if (want == NULL || have == NULL || *have == '\0')
53961d06d6bSBaptiste Daroussin                 return 1;
54061d06d6bSBaptiste Daroussin         while (*have != '\0') {
54161d06d6bSBaptiste Daroussin                 if (strcasestr(have, want) != NULL)
54261d06d6bSBaptiste Daroussin                         return 1;
54361d06d6bSBaptiste Daroussin                 have = strchr(have, '\0') + 1;
54461d06d6bSBaptiste Daroussin         }
54561d06d6bSBaptiste Daroussin         return 0;
54661d06d6bSBaptiste Daroussin }
54761d06d6bSBaptiste Daroussin 
54861d06d6bSBaptiste Daroussin /*
54961d06d6bSBaptiste Daroussin  * Build a list of values taken by the macro im in the manual page.
55061d06d6bSBaptiste Daroussin  */
55161d06d6bSBaptiste Daroussin static char *
buildoutput(size_t im,struct dbm_page * page)55261d06d6bSBaptiste Daroussin buildoutput(size_t im, struct dbm_page *page)
55361d06d6bSBaptiste Daroussin {
55461d06d6bSBaptiste Daroussin 	const char	*oldoutput, *sep, *input;
55561d06d6bSBaptiste Daroussin 	char		*output, *newoutput, *value;
55661d06d6bSBaptiste Daroussin 	size_t		 sz, i;
55761d06d6bSBaptiste Daroussin 
55861d06d6bSBaptiste Daroussin 	switch (im) {
55961d06d6bSBaptiste Daroussin 	case KEY_Nd:
56061d06d6bSBaptiste Daroussin 		return mandoc_strdup(page->desc);
56161d06d6bSBaptiste Daroussin 	case KEY_Nm:
56261d06d6bSBaptiste Daroussin 		input = page->name;
56361d06d6bSBaptiste Daroussin 		break;
56461d06d6bSBaptiste Daroussin 	case KEY_sec:
56561d06d6bSBaptiste Daroussin 		input = page->sect;
56661d06d6bSBaptiste Daroussin 		break;
56761d06d6bSBaptiste Daroussin 	case KEY_arch:
56861d06d6bSBaptiste Daroussin 		input = page->arch;
56961d06d6bSBaptiste Daroussin 		if (input == NULL)
57061d06d6bSBaptiste Daroussin 			input = "all\0";
57161d06d6bSBaptiste Daroussin 		break;
57261d06d6bSBaptiste Daroussin 	default:
57361d06d6bSBaptiste Daroussin 		input = NULL;
57461d06d6bSBaptiste Daroussin 		break;
57561d06d6bSBaptiste Daroussin 	}
57661d06d6bSBaptiste Daroussin 
57761d06d6bSBaptiste Daroussin 	if (input != NULL) {
57861d06d6bSBaptiste Daroussin 		sz = lstlen(input, 3) + 1;
57961d06d6bSBaptiste Daroussin 		output = mandoc_malloc(sz);
58061d06d6bSBaptiste Daroussin 		i = 0;
58161d06d6bSBaptiste Daroussin 		lstcat(output, &i, input, " # ");
58261d06d6bSBaptiste Daroussin 		output[i++] = '\0';
58361d06d6bSBaptiste Daroussin 		assert(i == sz);
58461d06d6bSBaptiste Daroussin 		return output;
58561d06d6bSBaptiste Daroussin 	}
58661d06d6bSBaptiste Daroussin 
58761d06d6bSBaptiste Daroussin 	output = NULL;
58861d06d6bSBaptiste Daroussin 	dbm_macro_bypage(im - 2, page->addr);
58961d06d6bSBaptiste Daroussin 	while ((value = dbm_macro_next()) != NULL) {
59061d06d6bSBaptiste Daroussin 		if (output == NULL) {
59161d06d6bSBaptiste Daroussin 			oldoutput = "";
59261d06d6bSBaptiste Daroussin 			sep = "";
59361d06d6bSBaptiste Daroussin 		} else {
59461d06d6bSBaptiste Daroussin 			oldoutput = output;
59561d06d6bSBaptiste Daroussin 			sep = " # ";
59661d06d6bSBaptiste Daroussin 		}
59761d06d6bSBaptiste Daroussin 		mandoc_asprintf(&newoutput, "%s%s%s", oldoutput, sep, value);
59861d06d6bSBaptiste Daroussin 		free(output);
59961d06d6bSBaptiste Daroussin 		output = newoutput;
60061d06d6bSBaptiste Daroussin 	}
60161d06d6bSBaptiste Daroussin 	return output;
60261d06d6bSBaptiste Daroussin }
60361d06d6bSBaptiste Daroussin 
60461d06d6bSBaptiste Daroussin /*
60561d06d6bSBaptiste Daroussin  * Compile a set of string tokens into an expression.
60661d06d6bSBaptiste Daroussin  * Tokens in "argv" are assumed to be individual expression atoms (e.g.,
60761d06d6bSBaptiste Daroussin  * "(", "foo=bar", etc.).
60861d06d6bSBaptiste Daroussin  */
60961d06d6bSBaptiste Daroussin static struct expr *
exprcomp(const struct mansearch * search,int argc,char * argv[],int * argi)61061d06d6bSBaptiste Daroussin exprcomp(const struct mansearch *search, int argc, char *argv[], int *argi)
61161d06d6bSBaptiste Daroussin {
61261d06d6bSBaptiste Daroussin 	struct expr	*parent, *child;
61361d06d6bSBaptiste Daroussin 	int		 needterm, nested;
61461d06d6bSBaptiste Daroussin 
61561d06d6bSBaptiste Daroussin 	if ((nested = *argi) == argc)
61661d06d6bSBaptiste Daroussin 		return NULL;
61761d06d6bSBaptiste Daroussin 	needterm = 1;
61861d06d6bSBaptiste Daroussin 	parent = child = NULL;
61961d06d6bSBaptiste Daroussin 	while (*argi < argc) {
62061d06d6bSBaptiste Daroussin 		if (strcmp(")", argv[*argi]) == 0) {
62161d06d6bSBaptiste Daroussin 			if (needterm)
62261d06d6bSBaptiste Daroussin 				warnx("missing term "
62361d06d6bSBaptiste Daroussin 				    "before closing parenthesis");
62461d06d6bSBaptiste Daroussin 			needterm = 0;
62561d06d6bSBaptiste Daroussin 			if (nested)
62661d06d6bSBaptiste Daroussin 				break;
62761d06d6bSBaptiste Daroussin 			warnx("ignoring unmatched right parenthesis");
62861d06d6bSBaptiste Daroussin 			++*argi;
62961d06d6bSBaptiste Daroussin 			continue;
63061d06d6bSBaptiste Daroussin 		}
63161d06d6bSBaptiste Daroussin 		if (strcmp("-o", argv[*argi]) == 0) {
63261d06d6bSBaptiste Daroussin 			if (needterm) {
63361d06d6bSBaptiste Daroussin 				if (*argi > 0)
63461d06d6bSBaptiste Daroussin 					warnx("ignoring -o after %s",
63561d06d6bSBaptiste Daroussin 					    argv[*argi - 1]);
63661d06d6bSBaptiste Daroussin 				else
63761d06d6bSBaptiste Daroussin 					warnx("ignoring initial -o");
63861d06d6bSBaptiste Daroussin 			}
63961d06d6bSBaptiste Daroussin 			needterm = 1;
64061d06d6bSBaptiste Daroussin 			++*argi;
64161d06d6bSBaptiste Daroussin 			continue;
64261d06d6bSBaptiste Daroussin 		}
64361d06d6bSBaptiste Daroussin 		needterm = 0;
64461d06d6bSBaptiste Daroussin 		if (child == NULL) {
64561d06d6bSBaptiste Daroussin 			child = expr_and(search, argc, argv, argi);
64661d06d6bSBaptiste Daroussin 			continue;
64761d06d6bSBaptiste Daroussin 		}
64861d06d6bSBaptiste Daroussin 		if (parent == NULL) {
64961d06d6bSBaptiste Daroussin 			parent = mandoc_calloc(1, sizeof(*parent));
65061d06d6bSBaptiste Daroussin 			parent->type = EXPR_OR;
65161d06d6bSBaptiste Daroussin 			parent->next = NULL;
65261d06d6bSBaptiste Daroussin 			parent->child = child;
65361d06d6bSBaptiste Daroussin 		}
65461d06d6bSBaptiste Daroussin 		child->next = expr_and(search, argc, argv, argi);
65561d06d6bSBaptiste Daroussin 		child = child->next;
65661d06d6bSBaptiste Daroussin 	}
65761d06d6bSBaptiste Daroussin 	if (needterm && *argi)
65861d06d6bSBaptiste Daroussin 		warnx("ignoring trailing %s", argv[*argi - 1]);
65961d06d6bSBaptiste Daroussin 	return parent == NULL ? child : parent;
66061d06d6bSBaptiste Daroussin }
66161d06d6bSBaptiste Daroussin 
66261d06d6bSBaptiste Daroussin static struct expr *
expr_and(const struct mansearch * search,int argc,char * argv[],int * argi)66361d06d6bSBaptiste Daroussin expr_and(const struct mansearch *search, int argc, char *argv[], int *argi)
66461d06d6bSBaptiste Daroussin {
66561d06d6bSBaptiste Daroussin 	struct expr	*parent, *child;
66661d06d6bSBaptiste Daroussin 	int		 needterm;
66761d06d6bSBaptiste Daroussin 
66861d06d6bSBaptiste Daroussin 	needterm = 1;
66961d06d6bSBaptiste Daroussin 	parent = child = NULL;
67061d06d6bSBaptiste Daroussin 	while (*argi < argc) {
67161d06d6bSBaptiste Daroussin 		if (strcmp(")", argv[*argi]) == 0) {
67261d06d6bSBaptiste Daroussin 			if (needterm)
67361d06d6bSBaptiste Daroussin 				warnx("missing term "
67461d06d6bSBaptiste Daroussin 				    "before closing parenthesis");
67561d06d6bSBaptiste Daroussin 			needterm = 0;
67661d06d6bSBaptiste Daroussin 			break;
67761d06d6bSBaptiste Daroussin 		}
67861d06d6bSBaptiste Daroussin 		if (strcmp("-o", argv[*argi]) == 0)
67961d06d6bSBaptiste Daroussin 			break;
68061d06d6bSBaptiste Daroussin 		if (strcmp("-a", argv[*argi]) == 0) {
68161d06d6bSBaptiste Daroussin 			if (needterm) {
68261d06d6bSBaptiste Daroussin 				if (*argi > 0)
68361d06d6bSBaptiste Daroussin 					warnx("ignoring -a after %s",
68461d06d6bSBaptiste Daroussin 					    argv[*argi - 1]);
68561d06d6bSBaptiste Daroussin 				else
68661d06d6bSBaptiste Daroussin 					warnx("ignoring initial -a");
68761d06d6bSBaptiste Daroussin 			}
68861d06d6bSBaptiste Daroussin 			needterm = 1;
68961d06d6bSBaptiste Daroussin 			++*argi;
69061d06d6bSBaptiste Daroussin 			continue;
69161d06d6bSBaptiste Daroussin 		}
69261d06d6bSBaptiste Daroussin 		if (needterm == 0)
69361d06d6bSBaptiste Daroussin 			break;
69461d06d6bSBaptiste Daroussin 		if (child == NULL) {
69561d06d6bSBaptiste Daroussin 			child = exprterm(search, argc, argv, argi);
69661d06d6bSBaptiste Daroussin 			if (child != NULL)
69761d06d6bSBaptiste Daroussin 				needterm = 0;
69861d06d6bSBaptiste Daroussin 			continue;
69961d06d6bSBaptiste Daroussin 		}
70061d06d6bSBaptiste Daroussin 		needterm = 0;
70161d06d6bSBaptiste Daroussin 		if (parent == NULL) {
70261d06d6bSBaptiste Daroussin 			parent = mandoc_calloc(1, sizeof(*parent));
70361d06d6bSBaptiste Daroussin 			parent->type = EXPR_AND;
70461d06d6bSBaptiste Daroussin 			parent->next = NULL;
70561d06d6bSBaptiste Daroussin 			parent->child = child;
70661d06d6bSBaptiste Daroussin 		}
70761d06d6bSBaptiste Daroussin 		child->next = exprterm(search, argc, argv, argi);
70861d06d6bSBaptiste Daroussin 		if (child->next != NULL) {
70961d06d6bSBaptiste Daroussin 			child = child->next;
71061d06d6bSBaptiste Daroussin 			needterm = 0;
71161d06d6bSBaptiste Daroussin 		}
71261d06d6bSBaptiste Daroussin 	}
71361d06d6bSBaptiste Daroussin 	if (needterm && *argi)
71461d06d6bSBaptiste Daroussin 		warnx("ignoring trailing %s", argv[*argi - 1]);
71561d06d6bSBaptiste Daroussin 	return parent == NULL ? child : parent;
71661d06d6bSBaptiste Daroussin }
71761d06d6bSBaptiste Daroussin 
71861d06d6bSBaptiste Daroussin static struct expr *
exprterm(const struct mansearch * search,int argc,char * argv[],int * argi)71961d06d6bSBaptiste Daroussin exprterm(const struct mansearch *search, int argc, char *argv[], int *argi)
72061d06d6bSBaptiste Daroussin {
72161d06d6bSBaptiste Daroussin 	char		 errbuf[BUFSIZ];
72261d06d6bSBaptiste Daroussin 	struct expr	*e;
72361d06d6bSBaptiste Daroussin 	char		*key, *val;
72461d06d6bSBaptiste Daroussin 	uint64_t	 iterbit;
72561d06d6bSBaptiste Daroussin 	int		 cs, i, irc;
72661d06d6bSBaptiste Daroussin 
72761d06d6bSBaptiste Daroussin 	if (strcmp("(", argv[*argi]) == 0) {
72861d06d6bSBaptiste Daroussin 		++*argi;
72961d06d6bSBaptiste Daroussin 		e = exprcomp(search, argc, argv, argi);
73061d06d6bSBaptiste Daroussin 		if (*argi < argc) {
73161d06d6bSBaptiste Daroussin 			assert(strcmp(")", argv[*argi]) == 0);
73261d06d6bSBaptiste Daroussin 			++*argi;
73361d06d6bSBaptiste Daroussin 		} else
73461d06d6bSBaptiste Daroussin 			warnx("unclosed parenthesis");
73561d06d6bSBaptiste Daroussin 		return e;
73661d06d6bSBaptiste Daroussin 	}
73761d06d6bSBaptiste Daroussin 
73861d06d6bSBaptiste Daroussin 	if (strcmp("-i", argv[*argi]) == 0 && *argi + 1 < argc) {
73961d06d6bSBaptiste Daroussin 		cs = 0;
74061d06d6bSBaptiste Daroussin 		++*argi;
74161d06d6bSBaptiste Daroussin 	} else
74261d06d6bSBaptiste Daroussin 		cs = 1;
74361d06d6bSBaptiste Daroussin 
74461d06d6bSBaptiste Daroussin 	e = mandoc_calloc(1, sizeof(*e));
74561d06d6bSBaptiste Daroussin 	e->type = EXPR_TERM;
74661d06d6bSBaptiste Daroussin 	e->bits = 0;
74761d06d6bSBaptiste Daroussin 	e->next = NULL;
74861d06d6bSBaptiste Daroussin 	e->child = NULL;
74961d06d6bSBaptiste Daroussin 
75061d06d6bSBaptiste Daroussin 	if (search->argmode == ARG_NAME) {
75161d06d6bSBaptiste Daroussin 		e->bits = TYPE_Nm;
75261d06d6bSBaptiste Daroussin 		e->match.type = DBM_EXACT;
75361d06d6bSBaptiste Daroussin 		e->match.str = argv[(*argi)++];
75461d06d6bSBaptiste Daroussin 		return e;
75561d06d6bSBaptiste Daroussin 	}
75661d06d6bSBaptiste Daroussin 
75761d06d6bSBaptiste Daroussin 	/*
75861d06d6bSBaptiste Daroussin 	 * Separate macro keys from search string.
75961d06d6bSBaptiste Daroussin 	 * If needed, request regular expression handling.
76061d06d6bSBaptiste Daroussin 	 */
76161d06d6bSBaptiste Daroussin 
76261d06d6bSBaptiste Daroussin 	if (search->argmode == ARG_WORD) {
76361d06d6bSBaptiste Daroussin 		e->bits = TYPE_Nm;
76461d06d6bSBaptiste Daroussin 		e->match.type = DBM_REGEX;
76561d06d6bSBaptiste Daroussin #if HAVE_REWB_BSD
76661d06d6bSBaptiste Daroussin 		mandoc_asprintf(&val, "[[:<:]]%s[[:>:]]", argv[*argi]);
76761d06d6bSBaptiste Daroussin #elif HAVE_REWB_SYSV
76861d06d6bSBaptiste Daroussin 		mandoc_asprintf(&val, "\\<%s\\>", argv[*argi]);
76961d06d6bSBaptiste Daroussin #else
77061d06d6bSBaptiste Daroussin 		mandoc_asprintf(&val,
77161d06d6bSBaptiste Daroussin 		    "(^|[^a-zA-Z01-9_])%s([^a-zA-Z01-9_]|$)", argv[*argi]);
77261d06d6bSBaptiste Daroussin #endif
77361d06d6bSBaptiste Daroussin 		cs = 0;
77461d06d6bSBaptiste Daroussin 	} else if ((val = strpbrk(argv[*argi], "=~")) == NULL) {
77561d06d6bSBaptiste Daroussin 		e->bits = TYPE_Nm | TYPE_Nd;
7767295610fSBaptiste Daroussin 		e->match.type = DBM_REGEX;
7777295610fSBaptiste Daroussin 		val = argv[*argi];
7787295610fSBaptiste Daroussin 		cs = 0;
77961d06d6bSBaptiste Daroussin 	} else {
78061d06d6bSBaptiste Daroussin 		if (val == argv[*argi])
78161d06d6bSBaptiste Daroussin 			e->bits = TYPE_Nm | TYPE_Nd;
78261d06d6bSBaptiste Daroussin 		if (*val == '=') {
78361d06d6bSBaptiste Daroussin 			e->match.type = DBM_SUB;
78461d06d6bSBaptiste Daroussin 			e->match.str = val + 1;
78561d06d6bSBaptiste Daroussin 		} else
78661d06d6bSBaptiste Daroussin 			e->match.type = DBM_REGEX;
78761d06d6bSBaptiste Daroussin 		*val++ = '\0';
78861d06d6bSBaptiste Daroussin 		if (strstr(argv[*argi], "arch") != NULL)
78961d06d6bSBaptiste Daroussin 			cs = 0;
79061d06d6bSBaptiste Daroussin 	}
79161d06d6bSBaptiste Daroussin 
79261d06d6bSBaptiste Daroussin 	/* Compile regular expressions. */
79361d06d6bSBaptiste Daroussin 
79461d06d6bSBaptiste Daroussin 	if (e->match.type == DBM_REGEX) {
79561d06d6bSBaptiste Daroussin 		e->match.re = mandoc_malloc(sizeof(*e->match.re));
79661d06d6bSBaptiste Daroussin 		irc = regcomp(e->match.re, val,
79761d06d6bSBaptiste Daroussin 		    REG_EXTENDED | REG_NOSUB | (cs ? 0 : REG_ICASE));
79861d06d6bSBaptiste Daroussin 		if (irc) {
79961d06d6bSBaptiste Daroussin 			regerror(irc, e->match.re, errbuf, sizeof(errbuf));
80061d06d6bSBaptiste Daroussin 			warnx("regcomp /%s/: %s", val, errbuf);
80161d06d6bSBaptiste Daroussin 		}
80261d06d6bSBaptiste Daroussin 		if (search->argmode == ARG_WORD)
80361d06d6bSBaptiste Daroussin 			free(val);
80461d06d6bSBaptiste Daroussin 		if (irc) {
80561d06d6bSBaptiste Daroussin 			free(e->match.re);
80661d06d6bSBaptiste Daroussin 			free(e);
80761d06d6bSBaptiste Daroussin 			++*argi;
80861d06d6bSBaptiste Daroussin 			return NULL;
80961d06d6bSBaptiste Daroussin 		}
81061d06d6bSBaptiste Daroussin 	}
81161d06d6bSBaptiste Daroussin 
81261d06d6bSBaptiste Daroussin 	if (e->bits) {
81361d06d6bSBaptiste Daroussin 		++*argi;
81461d06d6bSBaptiste Daroussin 		return e;
81561d06d6bSBaptiste Daroussin 	}
81661d06d6bSBaptiste Daroussin 
81761d06d6bSBaptiste Daroussin 	/*
81861d06d6bSBaptiste Daroussin 	 * Parse out all possible fields.
81961d06d6bSBaptiste Daroussin 	 * If the field doesn't resolve, bail.
82061d06d6bSBaptiste Daroussin 	 */
82161d06d6bSBaptiste Daroussin 
82261d06d6bSBaptiste Daroussin 	while (NULL != (key = strsep(&argv[*argi], ","))) {
82361d06d6bSBaptiste Daroussin 		if ('\0' == *key)
82461d06d6bSBaptiste Daroussin 			continue;
82561d06d6bSBaptiste Daroussin 		for (i = 0, iterbit = 1; i < KEY_MAX; i++, iterbit <<= 1) {
82661d06d6bSBaptiste Daroussin 			if (0 == strcasecmp(key, mansearch_keynames[i])) {
82761d06d6bSBaptiste Daroussin 				e->bits |= iterbit;
82861d06d6bSBaptiste Daroussin 				break;
82961d06d6bSBaptiste Daroussin 			}
83061d06d6bSBaptiste Daroussin 		}
83161d06d6bSBaptiste Daroussin 		if (i == KEY_MAX) {
83261d06d6bSBaptiste Daroussin 			if (strcasecmp(key, "any"))
83361d06d6bSBaptiste Daroussin 				warnx("treating unknown key "
83461d06d6bSBaptiste Daroussin 				    "\"%s\" as \"any\"", key);
83561d06d6bSBaptiste Daroussin 			e->bits |= ~0ULL;
83661d06d6bSBaptiste Daroussin 		}
83761d06d6bSBaptiste Daroussin 	}
83861d06d6bSBaptiste Daroussin 
83961d06d6bSBaptiste Daroussin 	++*argi;
84061d06d6bSBaptiste Daroussin 	return e;
84161d06d6bSBaptiste Daroussin }
84261d06d6bSBaptiste Daroussin 
84361d06d6bSBaptiste Daroussin static void
exprfree(struct expr * e)84461d06d6bSBaptiste Daroussin exprfree(struct expr *e)
84561d06d6bSBaptiste Daroussin {
84661d06d6bSBaptiste Daroussin 	if (e->next != NULL)
84761d06d6bSBaptiste Daroussin 		exprfree(e->next);
84861d06d6bSBaptiste Daroussin 	if (e->child != NULL)
84961d06d6bSBaptiste Daroussin 		exprfree(e->child);
85061d06d6bSBaptiste Daroussin 	free(e);
85161d06d6bSBaptiste Daroussin }
852