]>
git.cameronkatri.com Git - mandoc.git/blob - mansearch.c
1 /* $Id: mansearch.c,v 1.6 2013/06/05 02:00:26 schwarze Exp $ */
3 * Copyright (c) 2012 Kristaps Dzonsons <kristaps@bsd.lv>
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
35 #include "compat_ohash.h"
41 #include "mansearch.h"
43 #define SQL_BIND_TEXT(_db, _s, _i, _v) \
44 if (SQLITE_OK != sqlite3_bind_text \
45 ((_s), (_i)++, (_v), -1, SQLITE_STATIC)) \
46 fprintf(stderr, "%s\n", sqlite3_errmsg((_db)))
47 #define SQL_BIND_INT64(_db, _s, _i, _v) \
48 if (SQLITE_OK != sqlite3_bind_int64 \
49 ((_s), (_i)++, (_v))) \
50 fprintf(stderr, "%s\n", sqlite3_errmsg((_db)))
53 int glob
; /* is glob? */
54 uint64_t bits
; /* type-mask */
55 const char *v
; /* search value */
56 struct expr
*next
; /* next in sequence */
60 uint64_t id
; /* identifier in database */
61 char *file
; /* relative filepath of manpage */
62 char *desc
; /* description of manpage */
63 int form
; /* 0 == catpage */
71 static const struct type types
[] = {
115 static void *hash_alloc(size_t, void *);
116 static void hash_free(void *, size_t, void *);
117 static void *hash_halloc(size_t, void *);
118 static struct expr
*exprcomp(const struct mansearch
*,
120 static void exprfree(struct expr
*);
121 static struct expr
*exprterm(const struct mansearch
*, char *);
122 static char *sql_statement(const struct expr
*,
123 const char *, const char *);
126 mansearch(const struct mansearch
*search
,
127 const struct manpaths
*paths
,
128 int argc
, char *argv
[],
129 struct manpage
**res
, size_t *sz
)
139 struct ohash_info info
;
142 size_t i
, j
, cur
, maxres
;
144 memset(&info
, 0, sizeof(struct ohash_info
));
146 info
.halloc
= hash_halloc
;
147 info
.alloc
= hash_alloc
;
148 info
.hfree
= hash_free
;
149 info
.key_offset
= offsetof(struct match
, id
);
151 *sz
= cur
= maxres
= 0;
160 if (NULL
== (e
= exprcomp(search
, argc
, argv
)))
164 * Save a descriptor to the current working directory.
165 * Since pathnames in the "paths" variable might be relative,
166 * and we'll be chdir()ing into them, we need to keep a handle
167 * on our current directory from which to start the chdir().
170 if (NULL
== getcwd(buf
, PATH_MAX
)) {
173 } else if (-1 == (fd
= open(buf
, O_RDONLY
, 0))) {
178 sql
= sql_statement(e
, search
->arch
, search
->sec
);
181 * Loop over the directories (containing databases) for us to
183 * Don't let missing/bad databases/directories phase us.
184 * In each, try to open the resident database and, if it opens,
185 * scan it for our match expression.
188 for (i
= 0; i
< paths
->sz
; i
++) {
189 if (-1 == fchdir(fd
)) {
193 } else if (-1 == chdir(paths
->paths
[i
])) {
194 perror(paths
->paths
[i
]);
200 SQLITE_OPEN_READONLY
, NULL
);
202 if (SQLITE_OK
!= c
) {
209 c
= sqlite3_prepare_v2(db
, sql
, -1, &s
, NULL
);
211 fprintf(stderr
, "%s\n", sqlite3_errmsg(db
));
213 if (NULL
!= search
->arch
)
214 SQL_BIND_TEXT(db
, s
, j
, search
->arch
);
215 if (NULL
!= search
->sec
)
216 SQL_BIND_TEXT(db
, s
, j
, search
->sec
);
218 for (ep
= e
; NULL
!= ep
; ep
= ep
->next
) {
219 SQL_BIND_TEXT(db
, s
, j
, ep
->v
);
220 SQL_BIND_INT64(db
, s
, j
, ep
->bits
);
223 memset(&htab
, 0, sizeof(struct ohash
));
224 ohash_init(&htab
, 4, &info
);
227 * Hash each entry on its [unique] document identifier.
228 * This is a uint64_t.
229 * Instead of using a hash function, simply convert the
230 * uint64_t to a uint32_t, the hash value's type.
231 * This gives good performance and preserves the
232 * distribution of buckets in the table.
234 while (SQLITE_ROW
== (c
= sqlite3_step(s
))) {
235 id
= sqlite3_column_int64(s
, 0);
236 idx
= ohash_lookup_memory
238 sizeof(uint64_t), (uint32_t)id
);
240 if (NULL
!= ohash_find(&htab
, idx
))
243 mp
= mandoc_calloc(1, sizeof(struct match
));
245 mp
->file
= mandoc_strdup
246 ((char *)sqlite3_column_text(s
, 3));
247 mp
->desc
= mandoc_strdup
248 ((char *)sqlite3_column_text(s
, 4));
249 mp
->form
= sqlite3_column_int(s
, 5);
250 ohash_insert(&htab
, idx
, mp
);
253 if (SQLITE_DONE
!= c
)
254 fprintf(stderr
, "%s\n", sqlite3_errmsg(db
));
259 for (mp
= ohash_first(&htab
, &idx
);
261 mp
= ohash_next(&htab
, &idx
)) {
262 if (cur
+ 1 > maxres
) {
264 *res
= mandoc_realloc
265 (*res
, maxres
* sizeof(struct manpage
));
267 strlcpy((*res
)[cur
].file
,
268 paths
->paths
[i
], PATH_MAX
);
269 strlcat((*res
)[cur
].file
, "/", PATH_MAX
);
270 strlcat((*res
)[cur
].file
, mp
->file
, PATH_MAX
);
271 (*res
)[cur
].desc
= mp
->desc
;
272 (*res
)[cur
].form
= mp
->form
;
290 * Prepare the search SQL statement.
291 * We search for any of the words specified in our match expression.
292 * We filter the per-doc AND expressions when collecting results.
295 sql_statement(const struct expr
*e
, const char *arch
, const char *sec
)
298 const char *glob
= "(key GLOB ? AND bits & ?)";
299 const char *eq
= "(key = ? AND bits & ?)";
300 const char *andarch
= "arch = ? AND ";
301 const char *andsec
= "sec = ? AND ";
307 ("SELECT docid,bits,key,file,desc,form,sec,arch "
309 "INNER JOIN docs ON docs.id=keys.docid "
312 globsz
= strlen(glob
);
316 sz
+= strlen(andarch
) + 1;
317 sql
= mandoc_realloc(sql
, sz
);
318 strlcat(sql
, andarch
, sz
);
322 sz
+= strlen(andsec
) + 1;
323 sql
= mandoc_realloc(sql
, sz
);
324 strlcat(sql
, andsec
, sz
);
328 sql
= mandoc_realloc(sql
, sz
);
329 strlcat(sql
, "(", sz
);
331 for ( ; NULL
!= e
; e
= e
->next
) {
332 sz
+= (e
->glob
? globsz
: eqsz
) +
333 (NULL
== e
->next
? 3 : 5);
334 sql
= mandoc_realloc(sql
, sz
);
335 strlcat(sql
, e
->glob
? glob
: eq
, sz
);
336 strlcat(sql
, NULL
== e
->next
? ");" : " OR ", sz
);
343 * Compile a set of string tokens into an expression.
344 * Tokens in "argv" are assumed to be individual expression atoms (e.g.,
345 * "(", "foo=bar", etc.).
348 exprcomp(const struct mansearch
*search
, int argc
, char *argv
[])
351 struct expr
*first
, *next
, *cur
;
355 for (i
= 0; i
< argc
; i
++) {
356 next
= exprterm(search
, argv
[i
]);
372 exprterm(const struct mansearch
*search
, char *buf
)
381 e
= mandoc_calloc(1, sizeof(struct expr
));
383 /*"whatis" mode uses an opaque string and default fields. */
385 if (MANSEARCH_WHATIS
& search
->flags
) {
387 e
->bits
= search
->deftype
;
392 * If no =~ is specified, search with equality over names and
394 * If =~ begins the phrase, use name and description fields.
397 if (NULL
== (v
= strpbrk(buf
, "=~"))) {
399 e
->bits
= search
->deftype
;
402 e
->bits
= search
->deftype
;
409 * Parse out all possible fields.
410 * If the field doesn't resolve, bail.
413 while (NULL
!= (key
= strsep(&buf
, ","))) {
417 while (types
[i
].bits
&&
418 strcasecmp(types
[i
].name
, key
))
420 if (0 == types
[i
].bits
) {
424 e
->bits
|= types
[i
].bits
;
431 exprfree(struct expr
*p
)
443 hash_halloc(size_t sz
, void *arg
)
446 return(mandoc_calloc(sz
, 1));
450 hash_alloc(size_t sz
, void *arg
)
453 return(mandoc_malloc(sz
));
457 hash_free(void *p
, size_t sz
, void *arg
)