=================================================================== RCS file: /cvs/mandoc/Attic/apropos_db.c,v retrieving revision 1.3 retrieving revision 1.28 diff -u -p -r1.3 -r1.28 --- mandoc/Attic/apropos_db.c 2011/11/13 11:10:27 1.3 +++ mandoc/Attic/apropos_db.c 2011/12/25 14:58:39 1.28 @@ -1,4 +1,4 @@ -/* $Id: apropos_db.c,v 1.3 2011/11/13 11:10:27 schwarze Exp $ */ +/* $Id: apropos_db.c,v 1.28 2011/12/25 14:58:39 schwarze Exp $ */ /* * Copyright (c) 2011 Kristaps Dzonsons * Copyright (c) 2011 Ingo Schwarze @@ -15,15 +15,25 @@ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + #include #include #include #include +#include #include #include +#include -#ifdef __linux__ +#if defined(__linux__) +# include # include +#elif defined(__APPLE__) +# include +# include #else # include #endif @@ -32,46 +42,106 @@ #include "apropos_db.h" #include "mandoc.h" +struct rec { + struct res res; /* resulting record info */ + /* + * Maintain a binary tree for checking the uniqueness of `rec' + * when adding elements to the results array. + * Since the results array is dynamic, use offset in the array + * instead of a pointer to the structure. + */ + int lhs; + int rhs; + int matched; /* expression is true */ + int *matches; /* partial truth evaluations */ +}; + struct expr { - int regex; - int mask; - char *v; - regex_t re; + int regex; /* is regex? */ + int index; /* index in match array */ + uint64_t mask; /* type-mask */ + int and; /* is rhs of logical AND? */ + char *v; /* search value */ + regex_t re; /* compiled re, if regex */ + struct expr *next; /* next in sequence */ + struct expr *subexpr; }; struct type { - int mask; + uint64_t mask; const char *name; }; +struct rectree { + struct rec *node; /* record array for dir tree */ + int len; /* length of record array */ +}; + static const struct type types[] = { { TYPE_An, "An" }, + { TYPE_Ar, "Ar" }, + { TYPE_At, "At" }, + { TYPE_Bsx, "Bsx" }, + { TYPE_Bx, "Bx" }, { TYPE_Cd, "Cd" }, + { TYPE_Cm, "Cm" }, + { TYPE_Dv, "Dv" }, + { TYPE_Dx, "Dx" }, + { TYPE_Em, "Em" }, { TYPE_Er, "Er" }, { TYPE_Ev, "Ev" }, + { TYPE_Fa, "Fa" }, + { TYPE_Fl, "Fl" }, { TYPE_Fn, "Fn" }, { TYPE_Fn, "Fo" }, + { TYPE_Ft, "Ft" }, + { TYPE_Fx, "Fx" }, + { TYPE_Ic, "Ic" }, { TYPE_In, "In" }, + { TYPE_Lb, "Lb" }, + { TYPE_Li, "Li" }, + { TYPE_Lk, "Lk" }, + { TYPE_Ms, "Ms" }, + { TYPE_Mt, "Mt" }, { TYPE_Nd, "Nd" }, { TYPE_Nm, "Nm" }, + { TYPE_Nx, "Nx" }, + { TYPE_Ox, "Ox" }, { TYPE_Pa, "Pa" }, + { TYPE_Rs, "Rs" }, + { TYPE_Sh, "Sh" }, + { TYPE_Ss, "Ss" }, { TYPE_St, "St" }, + { TYPE_Sy, "Sy" }, + { TYPE_Tn, "Tn" }, { TYPE_Va, "Va" }, { TYPE_Va, "Vt" }, { TYPE_Xr, "Xr" }, - { INT_MAX, "any" }, + { UINT64_MAX, "any" }, { 0, NULL } }; static DB *btree_open(void); -static int btree_read(const DBT *, const struct mchars *, char **); -static int exprexec(const struct expr *, char *, int); +static int btree_read(const DBT *, const DBT *, + const struct mchars *, + uint64_t *, recno_t *, char **); +static int expreval(const struct expr *, int *); +static void exprexec(const struct expr *, + const char *, uint64_t, struct rec *); +static int exprmark(const struct expr *, + const char *, uint64_t, int *); +static struct expr *exprexpr(int, char *[], int *, int *, size_t *); +static struct expr *exprterm(char *, int); static DB *index_open(void); -static int index_read(const DBT *, const DBT *, +static int index_read(const DBT *, const DBT *, int, const struct mchars *, struct rec *); static void norm_string(const char *, const struct mchars *, char **); static size_t norm_utf8(unsigned int, char[7]); +static void recfree(struct rec *); +static int single_search(struct rectree *, const struct opts *, + const struct expr *, size_t terms, + struct mchars *, int); /* * Open the keyword mandoc-db database. @@ -86,7 +156,7 @@ btree_open(void) info.flags = R_DUP; db = dbopen(MANDOC_DB, O_RDONLY, 0, DB_BTREE, &info); - if (NULL != db) + if (NULL != db) return(db); return(NULL); @@ -97,29 +167,36 @@ btree_open(void) * Return 0 if the database is insane, else 1. */ static int -btree_read(const DBT *v, const struct mchars *mc, char **buf) +btree_read(const DBT *k, const DBT *v, const struct mchars *mc, + uint64_t *mask, recno_t *rec, char **buf) { + uint64_t vbuf[2]; - /* Sanity: are we nil-terminated? */ + /* Are our sizes sane? */ + if (k->size < 2 || sizeof(vbuf) != v->size) + return(0); - assert(v->size > 0); - if ('\0' != ((char *)v->data)[(int)v->size - 1]) + /* Is our string nil-terminated? */ + if ('\0' != ((const char *)k->data)[(int)k->size - 1]) return(0); - norm_string((char *)v->data, mc, buf); + norm_string((const char *)k->data, mc, buf); + memcpy(vbuf, v->data, v->size); + *mask = betoh64(vbuf[0]); + *rec = betoh64(vbuf[1]); return(1); } /* * Take a Unicode codepoint and produce its UTF-8 encoding. * This isn't the best way to do this, but it works. - * The magic numbers are from the UTF-8 packaging. + * The magic numbers are from the UTF-8 packaging. * They're not as scary as they seem: read the UTF-8 spec for details. */ static size_t norm_utf8(unsigned int cp, char out[7]) { - size_t rc; + int rc; rc = 0; @@ -160,7 +237,7 @@ norm_utf8(unsigned int cp, char out[7]) return(0); out[rc] = '\0'; - return(rc); + return((size_t)rc); } /* @@ -178,7 +255,7 @@ norm_string(const char *val, const struct mchars *mc, const char *seq, *cpp; int len, u, pos; enum mandoc_esc esc; - static const char res[] = { '\\', '\t', + static const char res[] = { '\\', '\t', ASCII_NBRSP, ASCII_HYPH, '\0' }; /* Pre-allocate by the length of the input */ @@ -224,7 +301,7 @@ norm_string(const char *val, const struct mchars *mc, if (ESCAPE_ERROR == esc) break; - /* + /* * XXX - this just does UTF-8, but we need to know * beforehand whether we should do text substitution. */ @@ -282,11 +359,12 @@ index_open(void) * Returns 1 if an entry was unpacked, 0 if the database is insane. */ static int -index_read(const DBT *key, const DBT *val, +index_read(const DBT *key, const DBT *val, int index, const struct mchars *mc, struct rec *rec) { size_t left; char *np, *cp; + char type; #define INDEX_BREAD(_dst) \ do { \ @@ -297,76 +375,136 @@ index_read(const DBT *key, const DBT *val, cp = np + 1; \ } while (/* CONSTCOND */ 0) - left = val->size; - cp = (char *)val->data; + if (0 == (left = val->size)) + return(0); - rec->rec = *(recno_t *)key->data; + cp = val->data; + assert(sizeof(recno_t) == key->size); + memcpy(&rec->res.rec, key->data, key->size); + rec->res.volume = index; - INDEX_BREAD(rec->file); - INDEX_BREAD(rec->cat); - INDEX_BREAD(rec->title); - INDEX_BREAD(rec->arch); - INDEX_BREAD(rec->desc); + if ('d' == (type = *cp++)) + rec->res.type = RESTYPE_MDOC; + else if ('a' == type) + rec->res.type = RESTYPE_MAN; + else if ('c' == type) + rec->res.type = RESTYPE_CAT; + else + return(0); + + left--; + INDEX_BREAD(rec->res.file); + INDEX_BREAD(rec->res.cat); + INDEX_BREAD(rec->res.title); + INDEX_BREAD(rec->res.arch); + INDEX_BREAD(rec->res.desc); return(1); } /* - * Search the mandocdb database for the expression "expr". + * Search mandocdb databases in paths for expression "expr". * Filter out by "opts". * Call "res" with the results, which may be zero. + * Return 0 if there was a database error, else return 1. */ -void -apropos_search(const struct opts *opts, const struct expr *expr, - void *arg, void (*res)(struct rec *, size_t, void *)) +int +apropos_search(int pathsz, char **paths, const struct opts *opts, + const struct expr *expr, size_t terms, void *arg, + void (*res)(struct res *, size_t, void *)) { - int i, len, root, leaf; + struct rectree tree; + struct mchars *mc; + struct res *ress; + int i, mlen, rc; + + memset(&tree, 0, sizeof(struct rectree)); + + rc = 0; + mc = mchars_alloc(); + + /* + * Main loop. Change into the directory containing manpage + * databases. Run our expession over each database in the set. + */ + + for (i = 0; i < pathsz; i++) { + if (chdir(paths[i])) + continue; + if ( ! single_search(&tree, opts, expr, terms, mc, i)) + goto out; + } + + /* + * Count matching files, transfer to a "clean" array, then feed + * them to the output handler. + */ + + for (mlen = i = 0; i < tree.len; i++) + if (tree.node[i].matched) + mlen++; + + ress = mandoc_malloc(mlen * sizeof(struct res)); + + for (mlen = i = 0; i < tree.len; i++) + if (tree.node[i].matched) + memcpy(&ress[mlen++], &tree.node[i].res, + sizeof(struct res)); + + (*res)(ress, mlen, arg); + free(ress); + + rc = 1; +out: + for (i = 0; i < tree.len; i++) + recfree(&tree.node[i]); + + free(tree.node); + mchars_free(mc); + return(rc); +} + +static int +single_search(struct rectree *tree, const struct opts *opts, + const struct expr *expr, size_t terms, + struct mchars *mc, int vol) +{ + int root, leaf, ch; DBT key, val; DB *btree, *idx; - struct mchars *mc; - int ch; char *buf; + struct rec *rs; + struct rec r; + uint64_t mask; recno_t rec; - struct rec *recs; - struct rec srec; root = -1; leaf = -1; btree = NULL; idx = NULL; - mc = NULL; buf = NULL; - recs = NULL; - len = 0; + rs = tree->node; - memset(&srec, 0, sizeof(struct rec)); + memset(&r, 0, sizeof(struct rec)); - /* XXX: error out with bad regexp? */ + if (NULL == (btree = btree_open())) + return(1); - mc = mchars_alloc(); + if (NULL == (idx = index_open())) { + (*btree->close)(btree); + return(1); + } - /* XXX: return fact that we've errored? */ - - if (NULL == (btree = btree_open())) - goto out; - if (NULL == (idx = index_open())) - goto out; - while (0 == (ch = (*btree->seq)(btree, &key, &val, R_NEXT))) { - /* - * Low-water mark for key and value. - * The key must have something in it, and the value must - * have the correct tags/recno mix. - */ - if (key.size < 2 || 8 != val.size) + if ( ! btree_read(&key, &val, mc, &mask, &rec, &buf)) break; - if ( ! btree_read(&key, mc, &buf)) - break; - if ( ! exprexec(expr, buf, *(int *)val.data)) + /* + * See if this keyword record matches any of the + * expressions we have stored. + */ + if ( ! exprmark(expr, buf, mask, NULL)) continue; - memcpy(&rec, val.data + 4, sizeof(recno_t)); - /* * O(log n) scan for prior records. Since a record * number is unbounded, this has decent performance over @@ -374,19 +512,31 @@ apropos_search(const struct opts *opts, const struct e */ for (leaf = root; leaf >= 0; ) - if (rec > recs[leaf].rec && recs[leaf].rhs >= 0) - leaf = recs[leaf].rhs; - else if (rec < recs[leaf].rec && recs[leaf].lhs >= 0) - leaf = recs[leaf].lhs; - else + if (rec > rs[leaf].res.rec && + rs[leaf].rhs >= 0) + leaf = rs[leaf].rhs; + else if (rec < rs[leaf].res.rec && + rs[leaf].lhs >= 0) + leaf = rs[leaf].lhs; + else break; - if (leaf >= 0 && recs[leaf].rec == rec) + /* + * If we find a record, see if it has already evaluated + * to true. If it has, great, just keep going. If not, + * try to evaluate it now and continue anyway. + */ + + if (leaf >= 0 && rs[leaf].res.rec == rec) { + if (0 == rs[leaf].matched) + exprexec(expr, buf, mask, &rs[leaf]); continue; + } /* - * Now we actually extract the manpage's metadata from - * the index database. + * We have a new file to examine. + * Extract the manpage's metadata from the index + * database, then begin partial evaluation. */ key.data = &rec; @@ -395,102 +545,245 @@ apropos_search(const struct opts *opts, const struct e if (0 != (*idx->get)(idx, &key, &val, 0)) break; - srec.lhs = srec.rhs = -1; - if ( ! index_read(&key, &val, mc, &srec)) + r.lhs = r.rhs = -1; + if ( ! index_read(&key, &val, vol, mc, &r)) break; - if (opts->cat && strcasecmp(opts->cat, srec.cat)) + /* XXX: this should be elsewhere, I guess? */ + + if (opts->cat && strcasecmp(opts->cat, r.res.cat)) continue; - if (opts->arch && strcasecmp(opts->arch, srec.arch)) - continue; - recs = mandoc_realloc - (recs, (len + 1) * sizeof(struct rec)); + if (opts->arch && *r.res.arch) + if (strcasecmp(opts->arch, r.res.arch)) + continue; - memcpy(&recs[len], &srec, sizeof(struct rec)); + tree->node = rs = mandoc_realloc + (rs, (tree->len + 1) * sizeof(struct rec)); + memcpy(&rs[tree->len], &r, sizeof(struct rec)); + memset(&r, 0, sizeof(struct rec)); + rs[tree->len].matches = + mandoc_calloc(terms, sizeof(int)); + + exprexec(expr, buf, mask, &rs[tree->len]); + /* Append to our tree. */ if (leaf >= 0) { - if (rec > recs[leaf].rec) - recs[leaf].rhs = len; + if (rec > rs[leaf].res.rec) + rs[leaf].rhs = tree->len; else - recs[leaf].lhs = len; + rs[leaf].lhs = tree->len; } else - root = len; - - memset(&srec, 0, sizeof(struct rec)); - len++; + root = tree->len; + + tree->len++; } - if (1 == ch) - (*res)(recs, len, arg); + (*btree->close)(btree); + (*idx->close)(idx); - /* XXX: else? corrupt database error? */ -out: - for (i = 0; i < len; i++) { - free(recs[i].file); - free(recs[i].cat); - free(recs[i].title); - free(recs[i].arch); - free(recs[i].desc); - } + free(buf); + recfree(&r); + return(1 == ch); +} - free(srec.file); - free(srec.cat); - free(srec.title); - free(srec.arch); - free(srec.desc); +static void +recfree(struct rec *rec) +{ - if (mc) - mchars_free(mc); - if (btree) - (*btree->close)(btree); - if (idx) - (*idx->close)(idx); + free(rec->res.file); + free(rec->res.cat); + free(rec->res.title); + free(rec->res.arch); + free(rec->res.desc); + free(rec->matches); +} + +/* + * Compile a list of straight-up terms. + * The arguments are re-written into ~[[:<:]]term[[:>:]], or "term" + * surrounded by word boundaries, then pumped through exprterm(). + * Terms are case-insensitive. + * This emulates whatis(1) behaviour. + */ +struct expr * +termcomp(int argc, char *argv[], size_t *tt) +{ + char *buf; + int pos; + struct expr *e, *next; + size_t sz; + + buf = NULL; + e = NULL; + *tt = 0; + + for (pos = argc - 1; pos >= 0; pos--) { + sz = strlen(argv[pos]) + 18; + buf = mandoc_realloc(buf, sz); + strlcpy(buf, "Nm~[[:<:]]", sz); + strlcat(buf, argv[pos], sz); + strlcat(buf, "[[:>:]]", sz); + if (NULL == (next = exprterm(buf, 0))) { + free(buf); + exprfree(e); + return(NULL); + } + next->next = e; + e = next; + (*tt)++; + } + free(buf); - free(recs); + return(e); } +/* + * Compile a sequence of logical expressions. + * See apropos.1 for a grammar of this sequence. + */ struct expr * -exprcomp(int argc, char *argv[]) +exprcomp(int argc, char *argv[], size_t *tt) { - struct expr *p; + int pos, lvl; + struct expr *e; + + pos = lvl = 0; + *tt = 0; + + e = exprexpr(argc, argv, &pos, &lvl, tt); + + if (0 == lvl && pos >= argc) + return(e); + + exprfree(e); + return(NULL); +} + +/* + * Compile an array of tokens into an expression. + * An informal expression grammar is defined in apropos(1). + * Return NULL if we fail doing so. All memory will be cleaned up. + * Return the root of the expression sequence if alright. + */ +static struct expr * +exprexpr(int argc, char *argv[], int *pos, int *lvl, size_t *tt) +{ + struct expr *e, *first, *next; + int log; + + first = next = NULL; + + for ( ; *pos < argc; (*pos)++) { + e = next; + + /* + * Close out a subexpression. + */ + + if (NULL != e && 0 == strcmp(")", argv[*pos])) { + if (--(*lvl) < 0) + goto err; + break; + } + + /* + * Small note: if we're just starting, don't let "-a" + * and "-o" be considered logical operators: they're + * just tokens unless pairwise joining, in which case we + * record their existence (or assume "OR"). + */ + log = 0; + + if (NULL != e && 0 == strcmp("-a", argv[*pos])) + log = 1; + else if (NULL != e && 0 == strcmp("-o", argv[*pos])) + log = 2; + + if (log > 0 && ++(*pos) >= argc) + goto err; + + /* + * Now we parse the term part. This can begin with + * "-i", in which case the expression is case + * insensitive. + */ + + if (0 == strcmp("(", argv[*pos])) { + ++(*pos); + ++(*lvl); + next = mandoc_calloc(1, sizeof(struct expr)); + next->subexpr = exprexpr(argc, argv, pos, lvl, tt); + if (NULL == next->subexpr) { + free(next); + next = NULL; + } + } else if (0 == strcmp("-i", argv[*pos])) { + if (++(*pos) >= argc) + goto err; + next = exprterm(argv[*pos], 0); + } else + next = exprterm(argv[*pos], 1); + + if (NULL == next) + goto err; + + next->and = log == 1; + next->index = (int)(*tt)++; + + /* Append to our chain of expressions. */ + + if (NULL == first) { + assert(NULL == e); + first = next; + } else { + assert(NULL != e); + e->next = next; + } + } + + return(first); +err: + exprfree(first); + return(NULL); +} + +/* + * Parse a terminal expression with the grammar as defined in + * apropos(1). + * Return NULL if we fail the parse. + */ +static struct expr * +exprterm(char *buf, int cs) +{ struct expr e; + struct expr *p; char *key; - int i, icase; + int i; - if (0 >= argc) - return(NULL); + memset(&e, 0, sizeof(struct expr)); - /* - * Choose regex or substring match. - */ + /* Choose regex or substring match. */ - if (NULL == (e.v = strpbrk(*argv, "=~"))) { + if (NULL == (e.v = strpbrk(buf, "=~"))) { e.regex = 0; - e.v = *argv; + e.v = buf; } else { e.regex = '~' == *e.v; *e.v++ = '\0'; } - /* - * Determine the record types to search for. - */ + /* Determine the record types to search for. */ - icase = 0; e.mask = 0; - if (*argv < e.v) { - while (NULL != (key = strsep(argv, ","))) { - if ('i' == key[0] && '\0' == key[1]) { - icase = REG_ICASE; - continue; - } + if (buf < e.v) { + while (NULL != (key = strsep(&buf, ","))) { i = 0; while (types[i].mask && - strcmp(types[i].name, key)) + strcmp(types[i].name, key)) i++; e.mask |= types[i].mask; } @@ -498,9 +791,11 @@ exprcomp(int argc, char *argv[]) if (0 == e.mask) e.mask = TYPE_Nm | TYPE_Nd; - if (e.regex && - regcomp(&e.re, e.v, REG_EXTENDED | REG_NOSUB | icase)) - return(NULL); + if (e.regex) { + i = REG_EXTENDED | REG_NOSUB | (cs ? 0 : REG_ICASE); + if (regcomp(&e.re, e.v, i)) + return(NULL); + } e.v = mandoc_strdup(e.v); @@ -512,26 +807,93 @@ exprcomp(int argc, char *argv[]) void exprfree(struct expr *p) { + struct expr *pp; - if (NULL == p) - return; + while (NULL != p) { + if (p->subexpr) + exprfree(p->subexpr); + if (p->regex) + regfree(&p->re); + free(p->v); + pp = p->next; + free(p); + p = pp; + } +} - if (p->regex) - regfree(&p->re); +static int +exprmark(const struct expr *p, const char *cp, + uint64_t mask, int *ms) +{ - free(p->v); - free(p); + for ( ; p; p = p->next) { + if (p->subexpr) { + if (exprmark(p->subexpr, cp, mask, ms)) + return(1); + continue; + } else if ( ! (mask & p->mask)) + continue; + + if (p->regex) { + if (regexec(&p->re, cp, 0, NULL, 0)) + continue; + } else if (NULL == strcasestr(cp, p->v)) + continue; + + if (NULL == ms) + return(1); + else + ms[p->index] = 1; + } + + return(0); } static int -exprexec(const struct expr *p, char *cp, int mask) +expreval(const struct expr *p, int *ms) { + int match; - if ( ! (mask & p->mask)) - return(0); + /* + * AND has precedence over OR. Analysis is left-right, though + * it doesn't matter because there are no side-effects. + * Thus, step through pairwise ANDs and accumulate their Boolean + * evaluation. If we encounter a single true AND collection or + * standalone term, the whole expression is true (by definition + * of OR). + */ - if (p->regex) - return(0 == regexec(&p->re, cp, 0, NULL, 0)); - else - return(NULL != strcasestr(cp, p->v)); + for (match = 0; p && ! match; p = p->next) { + /* Evaluate a subexpression, if applicable. */ + if (p->subexpr && ! ms[p->index]) + ms[p->index] = expreval(p->subexpr, ms); + + match = ms[p->index]; + for ( ; p->next && p->next->and; p = p->next) { + /* Evaluate a subexpression, if applicable. */ + if (p->next->subexpr && ! ms[p->next->index]) + ms[p->next->index] = + expreval(p->next->subexpr, ms); + match = match && ms[p->next->index]; + } + } + + return(match); +} + +/* + * First, update the array of terms for which this expression evaluates + * to true. + * Second, logically evaluate all terms over the updated array of truth + * values. + * If this evaluates to true, mark the expression as satisfied. + */ +static void +exprexec(const struct expr *e, const char *cp, + uint64_t mask, struct rec *r) +{ + + assert(0 == r->matched); + exprmark(e, cp, mask, r->matches); + r->matched = expreval(e, r->matches); }