]>
git.cameronkatri.com Git - mandoc.git/blob - mansearch.c
ad18f94285da9281984e890560f4d8300e612e2c
1 /* $Id: mansearch.c,v 1.2 2012/06/08 14:14:30 kristaps 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.
21 #include <sys/param.h>
39 #include "mansearch.h"
41 #define BIND_TEXT(_db, _s, _i, _v) \
42 if (SQLITE_OK != sqlite3_bind_text \
43 ((_s), (_i)++, (_v), -1, SQLITE_STATIC)) \
44 fprintf(stderr, "%s\n", sqlite3_errmsg((_db)))
45 #define BIND_INT64(_db, _s, _i, _v) \
46 if (SQLITE_OK != sqlite3_bind_int64 \
47 ((_s), (_i)++, (_v))) \
48 fprintf(stderr, "%s\n", sqlite3_errmsg((_db)))
51 int glob
; /* is glob? */
52 uint64_t bits
; /* type-mask */
53 const char *v
; /* search value */
54 struct expr
*next
; /* next in sequence */
58 uint64_t id
; /* identifier in database */
59 char *file
; /* relative filepath of manpage */
60 char *desc
; /* description of manpage */
61 int form
; /* 0 == catpage */
69 static const struct type types
[] = {
113 static void *hash_alloc(size_t, void *);
114 static void hash_free(void *, size_t, void *);
115 static void *hash_halloc(size_t, void *);
116 static struct expr
*exprcomp(int, char *[]);
117 static void exprfree(struct expr
*);
118 static struct expr
*exprterm(char *);
119 static char *sql_statement(const struct expr
*,
120 const char *, const char *);
123 mansearch(const struct manpaths
*paths
,
124 const char *arch
, const char *sec
,
125 int argc
, char *argv
[],
126 struct manpage
**res
, size_t *sz
)
130 char buf
[MAXPATHLEN
];
136 struct ohash_info info
;
139 size_t i
, j
, cur
, maxres
;
141 memset(&info
, 0, sizeof(struct ohash_info
));
143 info
.halloc
= hash_halloc
;
144 info
.alloc
= hash_alloc
;
145 info
.hfree
= hash_free
;
146 info
.key_offset
= offsetof(struct match
, id
);
148 *sz
= cur
= maxres
= 0;
157 if (NULL
== (e
= exprcomp(argc
, argv
)))
161 * Save a descriptor to the current working directory.
162 * Since pathnames in the "paths" variable might be relative,
163 * and we'll be chdir()ing into them, we need to keep a handle
164 * on our current directory from which to start the chdir().
167 if (NULL
== getcwd(buf
, MAXPATHLEN
)) {
170 } else if (-1 == (fd
= open(buf
, O_RDONLY
, 0))) {
175 sql
= sql_statement(e
, arch
, sec
);
178 * Loop over the directories (containing databases) for us to
180 * Don't let missing/bad databases/directories phase us.
181 * In each, try to open the resident database and, if it opens,
182 * scan it for our match expression.
185 for (i
= 0; i
< paths
->sz
; i
++) {
186 if (-1 == fchdir(fd
)) {
190 } else if (-1 == chdir(paths
->paths
[i
])) {
191 perror(paths
->paths
[i
]);
197 SQLITE_OPEN_READONLY
, NULL
);
199 if (SQLITE_OK
!= c
) {
206 c
= sqlite3_prepare_v2(db
, sql
, -1, &s
, NULL
);
208 fprintf(stderr
, "%s\n", sqlite3_errmsg(db
));
211 BIND_TEXT(db
, s
, j
, arch
);
213 BIND_TEXT(db
, s
, j
, arch
);
215 for (ep
= e
; NULL
!= ep
; ep
= ep
->next
) {
216 BIND_TEXT(db
, s
, j
, ep
->v
);
217 BIND_INT64(db
, s
, j
, ep
->bits
);
220 memset(&htab
, 0, sizeof(struct ohash
));
221 ohash_init(&htab
, 4, &info
);
224 * Hash each entry on its [unique] document identifier.
225 * This is a uint64_t.
226 * Instead of using a hash function, simply convert the
227 * uint64_t to a uint32_t, the hash value's type.
228 * This gives good performance and preserves the
229 * distribution of buckets in the table.
231 while (SQLITE_ROW
== (c
= sqlite3_step(s
))) {
232 id
= sqlite3_column_int64(s
, 0);
233 idx
= ohash_lookup_memory
235 sizeof(uint64_t), (uint32_t)id
);
237 if (NULL
!= ohash_find(&htab
, idx
))
240 mp
= mandoc_calloc(1, sizeof(struct match
));
242 mp
->file
= mandoc_strdup
243 ((char *)sqlite3_column_text(s
, 3));
244 mp
->desc
= mandoc_strdup
245 ((char *)sqlite3_column_text(s
, 4));
246 mp
->form
= sqlite3_column_int(s
, 5);
247 ohash_insert(&htab
, idx
, mp
);
250 if (SQLITE_DONE
!= c
)
251 fprintf(stderr
, "%s\n", sqlite3_errmsg(db
));
256 for (mp
= ohash_first(&htab
, &idx
);
258 mp
= ohash_next(&htab
, &idx
)) {
259 if (cur
+ 1 > maxres
) {
261 *res
= mandoc_realloc
262 (*res
, maxres
* sizeof(struct manpage
));
264 strlcpy((*res
)[cur
].file
,
265 paths
->paths
[i
], MAXPATHLEN
);
266 strlcat((*res
)[cur
].file
, "/", MAXPATHLEN
);
267 strlcat((*res
)[cur
].file
, mp
->file
, MAXPATHLEN
);
268 (*res
)[cur
].desc
= mp
->desc
;
269 (*res
)[cur
].form
= mp
->form
;
287 * Prepare the search SQL statement.
288 * We search for any of the words specified in our match expression.
289 * We filter the per-doc AND expressions when collecting results.
292 sql_statement(const struct expr
*e
, const char *arch
, const char *sec
)
295 const char *glob
= "(key GLOB ? AND bits & ?)";
296 const char *eq
= "(key = ? AND bits & ?)";
297 const char *andarch
= "arch = ? AND ";
298 const char *andsec
= "sec = ? AND ";
304 ("SELECT docid,bits,key,file,desc,form,sec,arch "
306 "INNER JOIN docs ON docs.id=keys.docid "
309 globsz
= strlen(glob
);
313 sz
+= strlen(andarch
) + 1;
314 sql
= mandoc_realloc(sql
, sz
);
315 strlcat(sql
, andarch
, sz
);
319 sz
+= strlen(andsec
) + 1;
320 sql
= mandoc_realloc(sql
, sz
);
321 strlcat(sql
, andsec
, sz
);
325 sql
= mandoc_realloc(sql
, sz
);
326 strlcat(sql
, "(", sz
);
328 for ( ; NULL
!= e
; e
= e
->next
) {
329 sz
+= (e
->glob
? globsz
: eqsz
) +
330 (NULL
== e
->next
? 3 : 5);
331 sql
= mandoc_realloc(sql
, sz
);
332 strlcat(sql
, e
->glob
? glob
: eq
, sz
);
333 strlcat(sql
, NULL
== e
->next
? ");" : " OR ", sz
);
340 * Compile a set of string tokens into an expression.
341 * Tokens in "argv" are assumed to be individual expression atoms (e.g.,
342 * "(", "foo=bar", etc.).
345 exprcomp(int argc
, char *argv
[])
348 struct expr
*first
, *next
, *cur
;
352 for (i
= 0; i
< argc
; i
++) {
353 next
= exprterm(argv
[i
]);
378 e
= mandoc_calloc(1, sizeof(struct expr
));
381 * If no =~ is specified, search with equality over names and
383 * If =~ begins the phrase, use name and description fields.
386 if (NULL
== (v
= strpbrk(buf
, "=~"))) {
388 e
->bits
= TYPE_Nm
| TYPE_Nd
;
391 e
->bits
= TYPE_Nm
| TYPE_Nd
;
398 * Parse out all possible fields.
399 * If the field doesn't resolve, bail.
402 while (NULL
!= (key
= strsep(&buf
, ","))) {
406 while (types
[i
].bits
&&
407 strcasecmp(types
[i
].name
, key
))
409 if (0 == types
[i
].bits
) {
413 e
->bits
|= types
[i
].bits
;
420 exprfree(struct expr
*p
)
432 hash_halloc(size_t sz
, void *arg
)
435 return(mandoc_calloc(sz
, 1));
439 hash_alloc(size_t sz
, void *arg
)
442 return(mandoc_malloc(sz
));
446 hash_free(void *p
, size_t sz
, void *arg
)