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