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