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