xref: /openbsd-src/usr.bin/mandoc/mansearch.c (revision 2777ee89d0e541ec819d05abee114837837abbec)
1 /*	$OpenBSD: mansearch.c,v 1.49 2016/01/08 15:01:58 schwarze Exp $ */
2 /*
3  * Copyright (c) 2012 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2013, 2014, 2015 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 <getopt.h>
27 #include <glob.h>
28 #include <limits.h>
29 #include <regex.h>
30 #include <stdio.h>
31 #include <stdint.h>
32 #include <stddef.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <unistd.h>
36 
37 #include <sqlite3.h>
38 
39 #include "mandoc.h"
40 #include "mandoc_aux.h"
41 #include "mandoc_ohash.h"
42 #include "manconf.h"
43 #include "mansearch.h"
44 
45 extern int mansearch_keymax;
46 extern const char *const mansearch_keynames[];
47 
48 #define	SQL_BIND_TEXT(_db, _s, _i, _v) \
49 	do { if (SQLITE_OK != sqlite3_bind_text \
50 		((_s), (_i)++, (_v), -1, SQLITE_STATIC)) \
51 		errx((int)MANDOCLEVEL_SYSERR, "%s", sqlite3_errmsg((_db))); \
52 	} while (0)
53 #define	SQL_BIND_INT64(_db, _s, _i, _v) \
54 	do { if (SQLITE_OK != sqlite3_bind_int64 \
55 		((_s), (_i)++, (_v))) \
56 		errx((int)MANDOCLEVEL_SYSERR, "%s", sqlite3_errmsg((_db))); \
57 	} while (0)
58 #define	SQL_BIND_BLOB(_db, _s, _i, _v) \
59 	do { if (SQLITE_OK != sqlite3_bind_blob \
60 		((_s), (_i)++, (&_v), sizeof(_v), SQLITE_STATIC)) \
61 		errx((int)MANDOCLEVEL_SYSERR, "%s", sqlite3_errmsg((_db))); \
62 	} while (0)
63 
64 struct	expr {
65 	regex_t		 regexp;  /* compiled regexp, if applicable */
66 	const char	*substr;  /* to search for, if applicable */
67 	struct expr	*next;    /* next in sequence */
68 	uint64_t	 bits;    /* type-mask */
69 	int		 equal;   /* equality, not subsring match */
70 	int		 open;    /* opening parentheses before */
71 	int		 and;	  /* logical AND before */
72 	int		 close;   /* closing parentheses after */
73 };
74 
75 struct	match {
76 	uint64_t	 pageid; /* identifier in database */
77 	uint64_t	 bits; /* name type mask */
78 	char		*desc; /* manual page description */
79 	int		 form; /* bit field: formatted, zipped? */
80 };
81 
82 static	void		 buildnames(const struct mansearch *,
83 				struct manpage *, sqlite3 *,
84 				sqlite3_stmt *, uint64_t,
85 				const char *, int form);
86 static	char		*buildoutput(sqlite3 *, sqlite3_stmt *,
87 				 uint64_t, uint64_t);
88 static	struct expr	*exprcomp(const struct mansearch *,
89 				int, char *[]);
90 static	void		 exprfree(struct expr *);
91 static	struct expr	*exprterm(const struct mansearch *, char *, int);
92 static	int		 manpage_compare(const void *, const void *);
93 static	void		 sql_append(char **sql, size_t *sz,
94 				const char *newstr, int count);
95 static	void		 sql_match(sqlite3_context *context,
96 				int argc, sqlite3_value **argv);
97 static	void		 sql_regexp(sqlite3_context *context,
98 				int argc, sqlite3_value **argv);
99 static	char		*sql_statement(const struct expr *);
100 
101 
102 int
103 mansearch_setup(int start)
104 {
105 	static void	*pagecache;
106 	int		 c;
107 
108 #define	PC_PAGESIZE	1280
109 #define	PC_NUMPAGES	256
110 
111 	if (start) {
112 		if (NULL != pagecache) {
113 			warnx("pagecache already enabled");
114 			return (int)MANDOCLEVEL_BADARG;
115 		}
116 
117 		pagecache = mmap(NULL, PC_PAGESIZE * PC_NUMPAGES,
118 		    PROT_READ | PROT_WRITE,
119 		    MAP_SHARED | MAP_ANON, -1, 0);
120 
121 		if (MAP_FAILED == pagecache) {
122 			warn("mmap");
123 			pagecache = NULL;
124 			return (int)MANDOCLEVEL_SYSERR;
125 		}
126 
127 		c = sqlite3_config(SQLITE_CONFIG_PAGECACHE,
128 		    pagecache, PC_PAGESIZE, PC_NUMPAGES);
129 
130 		if (SQLITE_OK == c)
131 			return (int)MANDOCLEVEL_OK;
132 
133 		warnx("pagecache: %s", sqlite3_errstr(c));
134 
135 	} else if (NULL == pagecache) {
136 		warnx("pagecache missing");
137 		return (int)MANDOCLEVEL_BADARG;
138 	}
139 
140 	if (-1 == munmap(pagecache, PC_PAGESIZE * PC_NUMPAGES)) {
141 		warn("munmap");
142 		pagecache = NULL;
143 		return (int)MANDOCLEVEL_SYSERR;
144 	}
145 
146 	pagecache = NULL;
147 	return (int)MANDOCLEVEL_OK;
148 }
149 
150 int
151 mansearch(const struct mansearch *search,
152 		const struct manpaths *paths,
153 		int argc, char *argv[],
154 		struct manpage **res, size_t *sz)
155 {
156 	int64_t		 pageid;
157 	uint64_t	 outbit, iterbit;
158 	char		 buf[PATH_MAX];
159 	char		*sql;
160 	struct manpage	*mpage;
161 	struct expr	*e, *ep;
162 	sqlite3		*db;
163 	sqlite3_stmt	*s, *s2;
164 	struct match	*mp;
165 	struct ohash	 htab;
166 	unsigned int	 idx;
167 	size_t		 i, j, cur, maxres;
168 	int		 c, chdir_status, getcwd_status, indexbit;
169 
170 	if (argc == 0 || (e = exprcomp(search, argc, argv)) == NULL) {
171 		*sz = 0;
172 		return 0;
173 	}
174 
175 	cur = maxres = 0;
176 	*res = NULL;
177 
178 	if (NULL != search->outkey) {
179 		outbit = TYPE_Nd;
180 		for (indexbit = 0, iterbit = 1;
181 		     indexbit < mansearch_keymax;
182 		     indexbit++, iterbit <<= 1) {
183 			if (0 == strcasecmp(search->outkey,
184 			    mansearch_keynames[indexbit])) {
185 				outbit = iterbit;
186 				break;
187 			}
188 		}
189 	} else
190 		outbit = 0;
191 
192 	/*
193 	 * Remember the original working directory, if possible.
194 	 * This will be needed if the second or a later directory
195 	 * is given as a relative path.
196 	 * Do not error out if the current directory is not
197 	 * searchable: Maybe it won't be needed after all.
198 	 */
199 
200 	if (getcwd(buf, PATH_MAX) == NULL) {
201 		getcwd_status = 0;
202 		(void)strlcpy(buf, strerror(errno), sizeof(buf));
203 	} else
204 		getcwd_status = 1;
205 
206 	sql = sql_statement(e);
207 
208 	/*
209 	 * Loop over the directories (containing databases) for us to
210 	 * search.
211 	 * Don't let missing/bad databases/directories phase us.
212 	 * In each, try to open the resident database and, if it opens,
213 	 * scan it for our match expression.
214 	 */
215 
216 	chdir_status = 0;
217 	for (i = 0; i < paths->sz; i++) {
218 		if (chdir_status && paths->paths[i][0] != '/') {
219 			if ( ! getcwd_status) {
220 				warnx("%s: getcwd: %s", paths->paths[i], buf);
221 				continue;
222 			} else if (chdir(buf) == -1) {
223 				warn("%s", buf);
224 				continue;
225 			}
226 		}
227 		if (chdir(paths->paths[i]) == -1) {
228 			warn("%s", paths->paths[i]);
229 			continue;
230 		}
231 		chdir_status = 1;
232 
233 		c = sqlite3_open_v2(MANDOC_DB, &db,
234 		    SQLITE_OPEN_READONLY, NULL);
235 
236 		if (SQLITE_OK != c) {
237 			warn("%s/%s", paths->paths[i], MANDOC_DB);
238 			sqlite3_close(db);
239 			continue;
240 		}
241 
242 		/*
243 		 * Define the SQL functions for substring
244 		 * and regular expression matching.
245 		 */
246 
247 		c = sqlite3_create_function(db, "match", 2,
248 		    SQLITE_UTF8 | SQLITE_DETERMINISTIC,
249 		    NULL, sql_match, NULL, NULL);
250 		assert(SQLITE_OK == c);
251 		c = sqlite3_create_function(db, "regexp", 2,
252 		    SQLITE_UTF8 | SQLITE_DETERMINISTIC,
253 		    NULL, sql_regexp, NULL, NULL);
254 		assert(SQLITE_OK == c);
255 
256 		j = 1;
257 		c = sqlite3_prepare_v2(db, sql, -1, &s, NULL);
258 		if (SQLITE_OK != c)
259 			errx((int)MANDOCLEVEL_SYSERR,
260 			    "%s", sqlite3_errmsg(db));
261 
262 		for (ep = e; NULL != ep; ep = ep->next) {
263 			if (NULL == ep->substr) {
264 				SQL_BIND_BLOB(db, s, j, ep->regexp);
265 			} else
266 				SQL_BIND_TEXT(db, s, j, ep->substr);
267 			if (0 == ((TYPE_Nd | TYPE_Nm) & ep->bits))
268 				SQL_BIND_INT64(db, s, j, ep->bits);
269 		}
270 
271 		mandoc_ohash_init(&htab, 4, offsetof(struct match, pageid));
272 
273 		/*
274 		 * Hash each entry on its [unique] document identifier.
275 		 * This is a uint64_t.
276 		 * Instead of using a hash function, simply convert the
277 		 * uint64_t to a uint32_t, the hash value's type.
278 		 * This gives good performance and preserves the
279 		 * distribution of buckets in the table.
280 		 */
281 		while (SQLITE_ROW == (c = sqlite3_step(s))) {
282 			pageid = sqlite3_column_int64(s, 2);
283 			idx = ohash_lookup_memory(&htab,
284 			    (char *)&pageid, sizeof(uint64_t),
285 			    (uint32_t)pageid);
286 
287 			if (NULL != ohash_find(&htab, idx))
288 				continue;
289 
290 			mp = mandoc_calloc(1, sizeof(struct match));
291 			mp->pageid = pageid;
292 			mp->form = sqlite3_column_int(s, 1);
293 			mp->bits = sqlite3_column_int64(s, 3);
294 			if (TYPE_Nd == outbit)
295 				mp->desc = mandoc_strdup((const char *)
296 				    sqlite3_column_text(s, 0));
297 			ohash_insert(&htab, idx, mp);
298 		}
299 
300 		if (SQLITE_DONE != c)
301 			warnx("%s", sqlite3_errmsg(db));
302 
303 		sqlite3_finalize(s);
304 
305 		c = sqlite3_prepare_v2(db,
306 		    "SELECT sec, arch, name, pageid FROM mlinks "
307 		    "WHERE pageid=? ORDER BY sec, arch, name",
308 		    -1, &s, NULL);
309 		if (SQLITE_OK != c)
310 			errx((int)MANDOCLEVEL_SYSERR,
311 			    "%s", sqlite3_errmsg(db));
312 
313 		c = sqlite3_prepare_v2(db,
314 		    "SELECT bits, key, pageid FROM keys "
315 		    "WHERE pageid=? AND bits & ?",
316 		    -1, &s2, NULL);
317 		if (SQLITE_OK != c)
318 			errx((int)MANDOCLEVEL_SYSERR,
319 			    "%s", sqlite3_errmsg(db));
320 
321 		for (mp = ohash_first(&htab, &idx);
322 				NULL != mp;
323 				mp = ohash_next(&htab, &idx)) {
324 			if (cur + 1 > maxres) {
325 				maxres += 1024;
326 				*res = mandoc_reallocarray(*res,
327 				    maxres, sizeof(struct manpage));
328 			}
329 			mpage = *res + cur;
330 			mpage->ipath = i;
331 			mpage->bits = mp->bits;
332 			mpage->sec = 10;
333 			mpage->form = mp->form;
334 			buildnames(search, mpage, db, s, mp->pageid,
335 			    paths->paths[i], mp->form);
336 			if (mpage->names != NULL) {
337 				mpage->output = TYPE_Nd & outbit ?
338 				    mp->desc : outbit ?
339 				    buildoutput(db, s2, mp->pageid, outbit) :
340 				    NULL;
341 				cur++;
342 			}
343 			free(mp);
344 		}
345 
346 		sqlite3_finalize(s);
347 		sqlite3_finalize(s2);
348 		sqlite3_close(db);
349 		ohash_delete(&htab);
350 
351 		/*
352 		 * In man(1) mode, prefer matches in earlier trees
353 		 * over matches in later trees.
354 		 */
355 
356 		if (cur && search->firstmatch)
357 			break;
358 	}
359 	qsort(*res, cur, sizeof(struct manpage), manpage_compare);
360 	if (chdir_status && getcwd_status && chdir(buf) == -1)
361 		warn("%s", buf);
362 	exprfree(e);
363 	free(sql);
364 	*sz = cur;
365 	return 1;
366 }
367 
368 void
369 mansearch_free(struct manpage *res, size_t sz)
370 {
371 	size_t	 i;
372 
373 	for (i = 0; i < sz; i++) {
374 		free(res[i].file);
375 		free(res[i].names);
376 		free(res[i].output);
377 	}
378 	free(res);
379 }
380 
381 static int
382 manpage_compare(const void *vp1, const void *vp2)
383 {
384 	const struct manpage	*mp1, *mp2;
385 	int			 diff;
386 
387 	mp1 = vp1;
388 	mp2 = vp2;
389 	return (diff = mp2->bits - mp1->bits) ? diff :
390 	    (diff = mp1->sec - mp2->sec) ? diff :
391 	    strcasecmp(mp1->names, mp2->names);
392 }
393 
394 static void
395 buildnames(const struct mansearch *search, struct manpage *mpage,
396 		sqlite3 *db, sqlite3_stmt *s,
397 		uint64_t pageid, const char *path, int form)
398 {
399 	glob_t		 globinfo;
400 	char		*firstname, *newnames, *prevsec, *prevarch;
401 	const char	*oldnames, *sep1, *name, *sec, *sep2, *arch, *fsec;
402 	size_t		 i;
403 	int		 c, globres;
404 
405 	mpage->file = NULL;
406 	mpage->names = NULL;
407 	firstname = prevsec = prevarch = NULL;
408 	i = 1;
409 	SQL_BIND_INT64(db, s, i, pageid);
410 	while (SQLITE_ROW == (c = sqlite3_step(s))) {
411 
412 		/* Decide whether we already have some names. */
413 
414 		if (NULL == mpage->names) {
415 			oldnames = "";
416 			sep1 = "";
417 		} else {
418 			oldnames = mpage->names;
419 			sep1 = ", ";
420 		}
421 
422 		/* Fetch the next name, rejecting sec/arch mismatches. */
423 
424 		sec = (const char *)sqlite3_column_text(s, 0);
425 		if (search->sec != NULL && strcasecmp(sec, search->sec))
426 			continue;
427 		arch = (const char *)sqlite3_column_text(s, 1);
428 		if (search->arch != NULL && *arch != '\0' &&
429 		    strcasecmp(arch, search->arch))
430 			continue;
431 		name = (const char *)sqlite3_column_text(s, 2);
432 
433 		/* Remember the first section found. */
434 
435 		if (9 < mpage->sec && '1' <= *sec && '9' >= *sec)
436 			mpage->sec = (*sec - '1') + 1;
437 
438 		/* If the section changed, append the old one. */
439 
440 		if (NULL != prevsec &&
441 		    (strcmp(sec, prevsec) ||
442 		     strcmp(arch, prevarch))) {
443 			sep2 = '\0' == *prevarch ? "" : "/";
444 			mandoc_asprintf(&newnames, "%s(%s%s%s)",
445 			    oldnames, prevsec, sep2, prevarch);
446 			free(mpage->names);
447 			oldnames = mpage->names = newnames;
448 			free(prevsec);
449 			free(prevarch);
450 			prevsec = prevarch = NULL;
451 		}
452 
453 		/* Save the new section, to append it later. */
454 
455 		if (NULL == prevsec) {
456 			prevsec = mandoc_strdup(sec);
457 			prevarch = mandoc_strdup(arch);
458 		}
459 
460 		/* Append the new name. */
461 
462 		mandoc_asprintf(&newnames, "%s%s%s",
463 		    oldnames, sep1, name);
464 		free(mpage->names);
465 		mpage->names = newnames;
466 
467 		/* Also save the first file name encountered. */
468 
469 		if (mpage->file != NULL)
470 			continue;
471 
472 		if (form & FORM_SRC) {
473 			sep1 = "man";
474 			fsec = sec;
475 		} else {
476 			sep1 = "cat";
477 			fsec = "0";
478 		}
479 		sep2 = *arch == '\0' ? "" : "/";
480 		mandoc_asprintf(&mpage->file, "%s/%s%s%s%s/%s.%s",
481 		    path, sep1, sec, sep2, arch, name, fsec);
482 		if (access(mpage->file, R_OK) != -1)
483 			continue;
484 
485 		/* Handle unusual file name extensions. */
486 
487 		if (firstname == NULL)
488 			firstname = mpage->file;
489 		else
490 			free(mpage->file);
491 		mandoc_asprintf(&mpage->file, "%s/%s%s%s%s/%s.*",
492 		    path, sep1, sec, sep2, arch, name);
493 		globres = glob(mpage->file, 0, NULL, &globinfo);
494 		free(mpage->file);
495 		mpage->file = globres ? NULL :
496 		    mandoc_strdup(*globinfo.gl_pathv);
497 		globfree(&globinfo);
498 	}
499 	if (c != SQLITE_DONE)
500 		warnx("%s", sqlite3_errmsg(db));
501 	sqlite3_reset(s);
502 
503 	/* If none of the files is usable, use the first name. */
504 
505 	if (mpage->file == NULL)
506 		mpage->file = firstname;
507 	else if (mpage->file != firstname)
508 		free(firstname);
509 
510 	/* Append one final section to the names. */
511 
512 	if (prevsec != NULL) {
513 		sep2 = *prevarch == '\0' ? "" : "/";
514 		mandoc_asprintf(&newnames, "%s(%s%s%s)",
515 		    mpage->names, prevsec, sep2, prevarch);
516 		free(mpage->names);
517 		mpage->names = newnames;
518 		free(prevsec);
519 		free(prevarch);
520 	}
521 }
522 
523 static char *
524 buildoutput(sqlite3 *db, sqlite3_stmt *s, uint64_t pageid, uint64_t outbit)
525 {
526 	char		*output, *newoutput;
527 	const char	*oldoutput, *sep1, *data;
528 	size_t		 i;
529 	int		 c;
530 
531 	output = NULL;
532 	i = 1;
533 	SQL_BIND_INT64(db, s, i, pageid);
534 	SQL_BIND_INT64(db, s, i, outbit);
535 	while (SQLITE_ROW == (c = sqlite3_step(s))) {
536 		if (NULL == output) {
537 			oldoutput = "";
538 			sep1 = "";
539 		} else {
540 			oldoutput = output;
541 			sep1 = " # ";
542 		}
543 		data = (const char *)sqlite3_column_text(s, 1);
544 		mandoc_asprintf(&newoutput, "%s%s%s",
545 		    oldoutput, sep1, data);
546 		free(output);
547 		output = newoutput;
548 	}
549 	if (SQLITE_DONE != c)
550 		warnx("%s", sqlite3_errmsg(db));
551 	sqlite3_reset(s);
552 	return output;
553 }
554 
555 /*
556  * Implement substring match as an application-defined SQL function.
557  * Using the SQL LIKE or GLOB operators instead would be a bad idea
558  * because that would require escaping metacharacters in the string
559  * being searched for.
560  */
561 static void
562 sql_match(sqlite3_context *context, int argc, sqlite3_value **argv)
563 {
564 
565 	assert(2 == argc);
566 	sqlite3_result_int(context, NULL != strcasestr(
567 	    (const char *)sqlite3_value_text(argv[1]),
568 	    (const char *)sqlite3_value_text(argv[0])));
569 }
570 
571 /*
572  * Implement regular expression match
573  * as an application-defined SQL function.
574  */
575 static void
576 sql_regexp(sqlite3_context *context, int argc, sqlite3_value **argv)
577 {
578 
579 	assert(2 == argc);
580 	sqlite3_result_int(context, !regexec(
581 	    (regex_t *)sqlite3_value_blob(argv[0]),
582 	    (const char *)sqlite3_value_text(argv[1]),
583 	    0, NULL, 0));
584 }
585 
586 static void
587 sql_append(char **sql, size_t *sz, const char *newstr, int count)
588 {
589 	size_t		 newsz;
590 
591 	newsz = 1 < count ? (size_t)count : strlen(newstr);
592 	*sql = mandoc_realloc(*sql, *sz + newsz + 1);
593 	if (1 < count)
594 		memset(*sql + *sz, *newstr, (size_t)count);
595 	else
596 		memcpy(*sql + *sz, newstr, newsz);
597 	*sz += newsz;
598 	(*sql)[*sz] = '\0';
599 }
600 
601 /*
602  * Prepare the search SQL statement.
603  */
604 static char *
605 sql_statement(const struct expr *e)
606 {
607 	char		*sql;
608 	size_t		 sz;
609 	int		 needop;
610 
611 	sql = mandoc_strdup(e->equal ?
612 	    "SELECT desc, form, pageid, bits "
613 		"FROM mpages NATURAL JOIN names WHERE " :
614 	    "SELECT desc, form, pageid, 0 FROM mpages WHERE ");
615 	sz = strlen(sql);
616 
617 	for (needop = 0; NULL != e; e = e->next) {
618 		if (e->and)
619 			sql_append(&sql, &sz, " AND ", 1);
620 		else if (needop)
621 			sql_append(&sql, &sz, " OR ", 1);
622 		if (e->open)
623 			sql_append(&sql, &sz, "(", e->open);
624 		sql_append(&sql, &sz,
625 		    TYPE_Nd & e->bits
626 		    ? (NULL == e->substr
627 			? "desc REGEXP ?"
628 			: "desc MATCH ?")
629 		    : TYPE_Nm == e->bits
630 		    ? (NULL == e->substr
631 			? "pageid IN (SELECT pageid FROM names "
632 			  "WHERE name REGEXP ?)"
633 			: e->equal
634 			? "name = ? "
635 			: "pageid IN (SELECT pageid FROM names "
636 			  "WHERE name MATCH ?)")
637 		    : (NULL == e->substr
638 			? "pageid IN (SELECT pageid FROM keys "
639 			  "WHERE key REGEXP ? AND bits & ?)"
640 			: "pageid IN (SELECT pageid FROM keys "
641 			  "WHERE key MATCH ? AND bits & ?)"), 1);
642 		if (e->close)
643 			sql_append(&sql, &sz, ")", e->close);
644 		needop = 1;
645 	}
646 
647 	return sql;
648 }
649 
650 /*
651  * Compile a set of string tokens into an expression.
652  * Tokens in "argv" are assumed to be individual expression atoms (e.g.,
653  * "(", "foo=bar", etc.).
654  */
655 static struct expr *
656 exprcomp(const struct mansearch *search, int argc, char *argv[])
657 {
658 	uint64_t	 mask;
659 	int		 i, toopen, logic, igncase, toclose;
660 	struct expr	*first, *prev, *cur, *next;
661 
662 	first = cur = NULL;
663 	logic = igncase = toopen = toclose = 0;
664 
665 	for (i = 0; i < argc; i++) {
666 		if (0 == strcmp("(", argv[i])) {
667 			if (igncase)
668 				goto fail;
669 			toopen++;
670 			toclose++;
671 			continue;
672 		} else if (0 == strcmp(")", argv[i])) {
673 			if (toopen || logic || igncase || NULL == cur)
674 				goto fail;
675 			cur->close++;
676 			if (0 > --toclose)
677 				goto fail;
678 			continue;
679 		} else if (0 == strcmp("-a", argv[i])) {
680 			if (toopen || logic || igncase || NULL == cur)
681 				goto fail;
682 			logic = 1;
683 			continue;
684 		} else if (0 == strcmp("-o", argv[i])) {
685 			if (toopen || logic || igncase || NULL == cur)
686 				goto fail;
687 			logic = 2;
688 			continue;
689 		} else if (0 == strcmp("-i", argv[i])) {
690 			if (igncase)
691 				goto fail;
692 			igncase = 1;
693 			continue;
694 		}
695 		next = exprterm(search, argv[i], !igncase);
696 		if (NULL == next)
697 			goto fail;
698 		if (NULL == first)
699 			first = next;
700 		else
701 			cur->next = next;
702 		prev = cur = next;
703 
704 		/*
705 		 * Searching for descriptions must be split out
706 		 * because they are stored in the mpages table,
707 		 * not in the keys table.
708 		 */
709 
710 		for (mask = TYPE_Nm; mask <= TYPE_Nd; mask <<= 1) {
711 			if (mask & cur->bits && ~mask & cur->bits) {
712 				next = mandoc_calloc(1,
713 				    sizeof(struct expr));
714 				memcpy(next, cur, sizeof(struct expr));
715 				prev->open = 1;
716 				cur->bits = mask;
717 				cur->next = next;
718 				cur = next;
719 				cur->bits &= ~mask;
720 			}
721 		}
722 		prev->and = (1 == logic);
723 		prev->open += toopen;
724 		if (cur != prev)
725 			cur->close = 1;
726 
727 		toopen = logic = igncase = 0;
728 	}
729 	if ( ! (toopen || logic || igncase || toclose))
730 		return first;
731 
732 fail:
733 	if (NULL != first)
734 		exprfree(first);
735 	return NULL;
736 }
737 
738 static struct expr *
739 exprterm(const struct mansearch *search, char *buf, int cs)
740 {
741 	char		 errbuf[BUFSIZ];
742 	struct expr	*e;
743 	char		*key, *val;
744 	uint64_t	 iterbit;
745 	int		 i, irc;
746 
747 	if ('\0' == *buf)
748 		return NULL;
749 
750 	e = mandoc_calloc(1, sizeof(struct expr));
751 
752 	if (search->argmode == ARG_NAME) {
753 		e->bits = TYPE_Nm;
754 		e->substr = buf;
755 		e->equal = 1;
756 		return e;
757 	}
758 
759 	/*
760 	 * Separate macro keys from search string.
761 	 * If needed, request regular expression handling
762 	 * by setting e->substr to NULL.
763 	 */
764 
765 	if (search->argmode == ARG_WORD) {
766 		e->bits = TYPE_Nm;
767 		e->substr = NULL;
768 		mandoc_asprintf(&val, "[[:<:]]%s[[:>:]]", buf);
769 		cs = 0;
770 	} else if ((val = strpbrk(buf, "=~")) == NULL) {
771 		e->bits = TYPE_Nm | TYPE_Nd;
772 		e->substr = buf;
773 	} else {
774 		if (val == buf)
775 			e->bits = TYPE_Nm | TYPE_Nd;
776 		if ('=' == *val)
777 			e->substr = val + 1;
778 		*val++ = '\0';
779 		if (NULL != strstr(buf, "arch"))
780 			cs = 0;
781 	}
782 
783 	/* Compile regular expressions. */
784 
785 	if (NULL == e->substr) {
786 		irc = regcomp(&e->regexp, val,
787 		    REG_EXTENDED | REG_NOSUB | (cs ? 0 : REG_ICASE));
788 		if (search->argmode == ARG_WORD)
789 			free(val);
790 		if (irc) {
791 			regerror(irc, &e->regexp, errbuf, sizeof(errbuf));
792 			warnx("regcomp: %s", errbuf);
793 			free(e);
794 			return NULL;
795 		}
796 	}
797 
798 	if (e->bits)
799 		return e;
800 
801 	/*
802 	 * Parse out all possible fields.
803 	 * If the field doesn't resolve, bail.
804 	 */
805 
806 	while (NULL != (key = strsep(&buf, ","))) {
807 		if ('\0' == *key)
808 			continue;
809 		for (i = 0, iterbit = 1;
810 		     i < mansearch_keymax;
811 		     i++, iterbit <<= 1) {
812 			if (0 == strcasecmp(key,
813 			    mansearch_keynames[i])) {
814 				e->bits |= iterbit;
815 				break;
816 			}
817 		}
818 		if (i == mansearch_keymax) {
819 			if (strcasecmp(key, "any")) {
820 				free(e);
821 				return NULL;
822 			}
823 			e->bits |= ~0ULL;
824 		}
825 	}
826 
827 	return e;
828 }
829 
830 static void
831 exprfree(struct expr *p)
832 {
833 	struct expr	*pp;
834 
835 	while (NULL != p) {
836 		pp = p->next;
837 		free(p);
838 		p = pp;
839 	}
840 }
841