-/* $Id: mansearch.c,v 1.2 2012/06/08 14:14:30 kristaps Exp $ */
+/* $Id: mansearch.c,v 1.8 2013/10/20 00:03:05 schwarze Exp $ */
/*
* Copyright (c) 2012 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2013 Ingo Schwarze <schwarze@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
#include "config.h"
#endif
-#include <sys/param.h>
-
#include <assert.h>
#include <fcntl.h>
#include <getopt.h>
+#include <limits.h>
+#include <regex.h>
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
+#ifdef HAVE_OHASH
#include <ohash.h>
+#else
+#include "compat_ohash.h"
+#endif
#include <sqlite3.h>
#include "mandoc.h"
#include "manpath.h"
-#include "mandocdb.h"
#include "mansearch.h"
-#define BIND_TEXT(_db, _s, _i, _v) \
- if (SQLITE_OK != sqlite3_bind_text \
+#define SQL_BIND_TEXT(_db, _s, _i, _v) \
+ do { if (SQLITE_OK != sqlite3_bind_text \
((_s), (_i)++, (_v), -1, SQLITE_STATIC)) \
- fprintf(stderr, "%s\n", sqlite3_errmsg((_db)))
-#define BIND_INT64(_db, _s, _i, _v) \
- if (SQLITE_OK != sqlite3_bind_int64 \
+ fprintf(stderr, "%s\n", sqlite3_errmsg((_db))); \
+ } while (0)
+#define SQL_BIND_INT64(_db, _s, _i, _v) \
+ do { if (SQLITE_OK != sqlite3_bind_int64 \
((_s), (_i)++, (_v))) \
- fprintf(stderr, "%s\n", sqlite3_errmsg((_db)))
+ fprintf(stderr, "%s\n", sqlite3_errmsg((_db))); \
+ } while (0)
+#define SQL_BIND_BLOB(_db, _s, _i, _v) \
+ do { if (SQLITE_OK != sqlite3_bind_blob \
+ ((_s), (_i)++, (&_v), sizeof(_v), SQLITE_STATIC)) \
+ fprintf(stderr, "%s\n", sqlite3_errmsg((_db))); \
+ } while (0)
struct expr {
- int glob; /* is glob? */
- uint64_t bits; /* type-mask */
- const char *v; /* search value */
- struct expr *next; /* next in sequence */
+ uint64_t bits; /* type-mask */
+ const char *substr; /* to search for, if applicable */
+ regex_t regexp; /* compiled regexp, if applicable */
+ struct expr *next; /* next in sequence */
};
struct match {
static void *hash_alloc(size_t, void *);
static void hash_free(void *, size_t, void *);
static void *hash_halloc(size_t, void *);
-static struct expr *exprcomp(int, char *[]);
+static struct expr *exprcomp(const struct mansearch *,
+ int, char *[]);
static void exprfree(struct expr *);
-static struct expr *exprterm(char *);
+static struct expr *exprterm(const struct mansearch *, char *, int);
+static void sql_match(sqlite3_context *context,
+ int argc, sqlite3_value **argv);
+static void sql_regexp(sqlite3_context *context,
+ int argc, sqlite3_value **argv);
static char *sql_statement(const struct expr *,
const char *, const char *);
int
-mansearch(const struct manpaths *paths,
- const char *arch, const char *sec,
+mansearch(const struct mansearch *search,
+ const struct manpaths *paths,
int argc, char *argv[],
struct manpage **res, size_t *sz)
{
int fd, rc, c;
int64_t id;
- char buf[MAXPATHLEN];
+ char buf[PATH_MAX];
char *sql;
struct expr *e, *ep;
sqlite3 *db;
if (0 == argc)
goto out;
- if (NULL == (e = exprcomp(argc, argv)))
+ if (NULL == (e = exprcomp(search, argc, argv)))
goto out;
/*
* on our current directory from which to start the chdir().
*/
- if (NULL == getcwd(buf, MAXPATHLEN)) {
+ if (NULL == getcwd(buf, PATH_MAX)) {
perror(NULL);
goto out;
} else if (-1 == (fd = open(buf, O_RDONLY, 0))) {
goto out;
}
- sql = sql_statement(e, arch, sec);
+ sql = sql_statement(e, search->arch, search->sec);
/*
* Loop over the directories (containing databases) for us to
continue;
}
+ /*
+ * Define the SQL functions for substring
+ * and regular expression matching.
+ */
+
+ c = sqlite3_create_function(db, "match", 2,
+ SQLITE_ANY, NULL, sql_match, NULL, NULL);
+ assert(SQLITE_OK == c);
+ c = sqlite3_create_function(db, "regexp", 2,
+ SQLITE_ANY, NULL, sql_regexp, NULL, NULL);
+ assert(SQLITE_OK == c);
+
j = 1;
c = sqlite3_prepare_v2(db, sql, -1, &s, NULL);
if (SQLITE_OK != c)
fprintf(stderr, "%s\n", sqlite3_errmsg(db));
- if (NULL != arch)
- BIND_TEXT(db, s, j, arch);
- if (NULL != sec)
- BIND_TEXT(db, s, j, arch);
+ if (NULL != search->arch)
+ SQL_BIND_TEXT(db, s, j, search->arch);
+ if (NULL != search->sec)
+ SQL_BIND_TEXT(db, s, j, search->sec);
for (ep = e; NULL != ep; ep = ep->next) {
- BIND_TEXT(db, s, j, ep->v);
- BIND_INT64(db, s, j, ep->bits);
+ if (NULL == ep->substr) {
+ SQL_BIND_BLOB(db, s, j, ep->regexp);
+ } else
+ SQL_BIND_TEXT(db, s, j, ep->substr);
+ SQL_BIND_INT64(db, s, j, ep->bits);
}
memset(&htab, 0, sizeof(struct ohash));
(*res, maxres * sizeof(struct manpage));
}
strlcpy((*res)[cur].file,
- paths->paths[i], MAXPATHLEN);
- strlcat((*res)[cur].file, "/", MAXPATHLEN);
- strlcat((*res)[cur].file, mp->file, MAXPATHLEN);
+ paths->paths[i], PATH_MAX);
+ strlcat((*res)[cur].file, "/", PATH_MAX);
+ strlcat((*res)[cur].file, mp->file, PATH_MAX);
(*res)[cur].desc = mp->desc;
(*res)[cur].form = mp->form;
free(mp->file);
return(rc);
}
+/*
+ * Implement substring match as an application-defined SQL function.
+ * Using the SQL LIKE or GLOB operators instead would be a bad idea
+ * because that would require escaping metacharacters in the string
+ * being searched for.
+ */
+static void
+sql_match(sqlite3_context *context, int argc, sqlite3_value **argv)
+{
+
+ assert(2 == argc);
+ sqlite3_result_int(context, NULL != strcasestr(
+ (const char *)sqlite3_value_text(argv[1]),
+ (const char *)sqlite3_value_text(argv[0])));
+}
+
+/*
+ * Implement regular expression match
+ * as an application-defined SQL function.
+ */
+static void
+sql_regexp(sqlite3_context *context, int argc, sqlite3_value **argv)
+{
+
+ assert(2 == argc);
+ sqlite3_result_int(context, !regexec(
+ (regex_t *)sqlite3_value_blob(argv[0]),
+ (const char *)sqlite3_value_text(argv[1]),
+ 0, NULL, 0));
+}
+
/*
* Prepare the search SQL statement.
* We search for any of the words specified in our match expression.
sql_statement(const struct expr *e, const char *arch, const char *sec)
{
char *sql;
- const char *glob = "(key GLOB ? AND bits & ?)";
- const char *eq = "(key = ? AND bits & ?)";
+ const char *substr = "(key MATCH ? AND bits & ?)";
+ const char *regexp = "(key REGEXP ? AND bits & ?)";
const char *andarch = "arch = ? AND ";
const char *andsec = "sec = ? AND ";
- size_t globsz;
- size_t eqsz;
+ size_t substrsz;
+ size_t regexpsz;
size_t sz;
sql = mandoc_strdup
"INNER JOIN docs ON docs.id=keys.docid "
"WHERE ");
sz = strlen(sql);
- globsz = strlen(glob);
- eqsz = strlen(eq);
+ substrsz = strlen(substr);
+ regexpsz = strlen(regexp);
if (NULL != arch) {
sz += strlen(andarch) + 1;
strlcat(sql, "(", sz);
for ( ; NULL != e; e = e->next) {
- sz += (e->glob ? globsz : eqsz) +
+ sz += (NULL == e->substr ? regexpsz : substrsz) +
(NULL == e->next ? 3 : 5);
sql = mandoc_realloc(sql, sz);
- strlcat(sql, e->glob ? glob : eq, sz);
+ strlcat(sql, NULL == e->substr ? regexp : substr, sz);
strlcat(sql, NULL == e->next ? ");" : " OR ", sz);
}
* "(", "foo=bar", etc.).
*/
static struct expr *
-exprcomp(int argc, char *argv[])
+exprcomp(const struct mansearch *search, int argc, char *argv[])
{
- int i;
+ int i, cs;
struct expr *first, *next, *cur;
first = cur = NULL;
for (i = 0; i < argc; i++) {
- next = exprterm(argv[i]);
+ if (0 == strcmp("-i", argv[i])) {
+ if (++i >= argc)
+ return(NULL);
+ cs = 0;
+ } else
+ cs = 1;
+ next = exprterm(search, argv[i], cs);
if (NULL == next) {
exprfree(first);
return(NULL);
}
static struct expr *
-exprterm(char *buf)
+exprterm(const struct mansearch *search, char *buf, int cs)
{
struct expr *e;
char *key, *v;
e = mandoc_calloc(1, sizeof(struct expr));
+ /*"whatis" mode uses an opaque string and default fields. */
+
+ if (MANSEARCH_WHATIS & search->flags) {
+ e->substr = buf;
+ e->bits = search->deftype;
+ return(e);
+ }
+
/*
* If no =~ is specified, search with equality over names and
* descriptions.
*/
if (NULL == (v = strpbrk(buf, "=~"))) {
- e->v = buf;
- e->bits = TYPE_Nm | TYPE_Nd;
+ e->substr = buf;
+ e->bits = search->deftype;
return(e);
} else if (v == buf)
- e->bits = TYPE_Nm | TYPE_Nd;
+ e->bits = search->deftype;
- e->glob = '~' == *v;
- *v++ = '\0';
- e->v = v;
+ if ('~' == *v++) {
+ if (regcomp(&e->regexp, v,
+ REG_EXTENDED | REG_NOSUB | (cs ? 0 : REG_ICASE))) {
+ free(e);
+ return(NULL);
+ }
+ } else
+ e->substr = v;
+ v[-1] = '\0';
/*
* Parse out all possible fields.