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