version 1.3, 2011/10/07 13:29:03 |
version 1.30, 2012/03/24 02:18:51 |
|
|
/* $Id$ */ |
/* $Id$ */ |
/* |
/* |
* Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv> |
* Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv> |
|
* Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org> |
* |
* |
* Permission to use, copy, modify, and distribute this software for any |
* Permission to use, copy, modify, and distribute this software for any |
* purpose with or without fee is hereby granted, provided that the above |
* purpose with or without fee is hereby granted, provided that the above |
|
|
#ifdef HAVE_CONFIG_H |
#ifdef HAVE_CONFIG_H |
#include "config.h" |
#include "config.h" |
#endif |
#endif |
|
#include <sys/param.h> |
|
|
#include <sys/types.h> |
|
|
|
#include <assert.h> |
#include <assert.h> |
#include <errno.h> |
|
#include <fcntl.h> |
|
#include <getopt.h> |
#include <getopt.h> |
#include <limits.h> |
|
#include <regex.h> |
|
#include <stdarg.h> |
|
#include <stdint.h> |
|
#include <stdio.h> |
#include <stdio.h> |
#include <stdlib.h> |
#include <stdlib.h> |
#include <string.h> |
#include <string.h> |
#include <unistd.h> |
#include <unistd.h> |
|
|
#ifdef __linux__ |
#include "apropos_db.h" |
# include <db_185.h> |
|
#else |
|
# include <db.h> |
|
#endif |
|
|
|
#include "mandoc.h" |
#include "mandoc.h" |
|
#include "manpath.h" |
|
|
#define MAXRESULTS 256 |
#define SINGLETON(_res, _sz) \ |
|
((_sz) && (_res)[0].matched && \ |
|
(1 == (_sz) || 0 == (_res)[1].matched)) |
|
#define EMPTYSET(_res, _sz) \ |
|
((0 == (_sz)) || 0 == (_res)[0].matched) |
|
|
/* Bit-fields. See mandocdb.8. */ |
static int cmp(const void *, const void *); |
|
static void list(struct res *, size_t, void *); |
#define TYPE_NAME 0x01 |
|
#define TYPE_FUNCTION 0x02 |
|
#define TYPE_UTILITY 0x04 |
|
#define TYPE_INCLUDES 0x08 |
|
#define TYPE_VARIABLE 0x10 |
|
#define TYPE_STANDARD 0x20 |
|
#define TYPE_AUTHOR 0x40 |
|
#define TYPE_CONFIG 0x80 |
|
#define TYPE_DESC 0x100 |
|
#define TYPE_XREF 0x200 |
|
#define TYPE_PATH 0x400 |
|
#define TYPE_ENV 0x800 |
|
#define TYPE_ERR 0x1000 |
|
|
|
enum match { |
|
MATCH_SUBSTR = 0, |
|
MATCH_REGEX, |
|
MATCH_EXACT |
|
}; |
|
|
|
enum sort { |
|
SORT_TITLE = 0, |
|
SORT_CAT, |
|
SORT__MAX |
|
}; |
|
|
|
struct opts { |
|
enum sort sort; /* output sorting */ |
|
const char *arch; /* restrict to architecture */ |
|
const char *cat; /* restrict to category */ |
|
int types; /* only types in bitmask */ |
|
int insens; /* case-insensitive match */ |
|
enum match match; /* match type */ |
|
}; |
|
|
|
struct type { |
|
int mask; |
|
const char *name; /* command-line type name */ |
|
}; |
|
|
|
struct rec { |
|
char *file; /* file in file-system */ |
|
char *cat; /* category (3p, 3, etc.) */ |
|
char *title; /* title (FOO, etc.) */ |
|
char *arch; /* arch (or empty string) */ |
|
char *desc; /* description (from Nd) */ |
|
recno_t rec; /* record in index */ |
|
}; |
|
|
|
struct res { |
|
char *arch; /* architecture */ |
|
char *desc; /* free-form description */ |
|
char *keyword; /* matched keyword */ |
|
int types; /* bitmask of field selectors */ |
|
char *cat; /* manual section */ |
|
char *title; /* manual section */ |
|
char *uri; /* formatted uri of file */ |
|
recno_t rec; /* unique id of underlying manual */ |
|
}; |
|
|
|
struct state { |
|
DB *db; /* database */ |
|
DB *idx; /* index */ |
|
const char *dbf; /* database name */ |
|
const char *idxf; /* index name */ |
|
}; |
|
|
|
static const char * const sorts[SORT__MAX] = { |
|
"cat", /* SORT_CAT */ |
|
"title", /* SORT_TITLE */ |
|
}; |
|
|
|
static const struct type types[] = { |
|
{ TYPE_NAME, "name" }, |
|
{ TYPE_FUNCTION, "func" }, |
|
{ TYPE_UTILITY, "utility" }, |
|
{ TYPE_INCLUDES, "incl" }, |
|
{ TYPE_VARIABLE, "var" }, |
|
{ TYPE_STANDARD, "stand" }, |
|
{ TYPE_AUTHOR, "auth" }, |
|
{ TYPE_CONFIG, "conf" }, |
|
{ TYPE_DESC, "desc" }, |
|
{ TYPE_XREF, "xref" }, |
|
{ TYPE_PATH, "path" }, |
|
{ TYPE_ENV, "env" }, |
|
{ TYPE_ERR, "err" }, |
|
{ INT_MAX, "all" }, |
|
{ 0, NULL } |
|
}; |
|
|
|
static void buf_alloc(char **, size_t *, size_t); |
|
static void buf_dup(struct mchars *, char **, const char *); |
|
static void buf_redup(struct mchars *, char **, |
|
size_t *, const char *); |
|
static int sort_cat(const void *, const void *); |
|
static int sort_title(const void *, const void *); |
|
static int state_getrecord(struct state *, |
|
recno_t, struct rec *); |
|
static void state_output(const struct res *, int); |
|
static void state_search(struct state *, |
|
const struct opts *, char *); |
|
static void usage(void); |
static void usage(void); |
|
|
static char *progname; |
static char *progname; |
Line 153 static char *progname; |
|
Line 46 static char *progname; |
|
int |
int |
main(int argc, char *argv[]) |
main(int argc, char *argv[]) |
{ |
{ |
BTREEINFO info; |
int ch, rc, whatis, usecat; |
int ch, i, rc; |
struct res *res; |
const char *dbf, *idxf; |
struct manpaths paths; |
struct state state; |
const char *prog; |
char *q, *v; |
pid_t pid; |
|
char path[PATH_MAX]; |
|
int fds[2]; |
|
size_t terms, ressz, sz; |
struct opts opts; |
struct opts opts; |
|
struct expr *e; |
|
char *defpaths, *auxpaths, *conf_file, *cp; |
extern int optind; |
extern int optind; |
extern char *optarg; |
extern char *optarg; |
|
|
memset(&opts, 0, sizeof(struct opts)); |
|
memset(&state, 0, sizeof(struct state)); |
|
|
|
dbf = "mandoc.db"; |
|
idxf = "mandoc.index"; |
|
q = NULL; |
|
rc = EXIT_FAILURE; |
|
|
|
progname = strrchr(argv[0], '/'); |
progname = strrchr(argv[0], '/'); |
if (progname == NULL) |
if (progname == NULL) |
progname = argv[0]; |
progname = argv[0]; |
else |
else |
++progname; |
++progname; |
|
|
opts.match = MATCH_SUBSTR; |
whatis = 0 == strncmp(progname, "whatis", 6); |
|
|
while (-1 != (ch = getopt(argc, argv, "a:c:eIrs:t:"))) |
memset(&paths, 0, sizeof(struct manpaths)); |
|
memset(&opts, 0, sizeof(struct opts)); |
|
|
|
usecat = 0; |
|
ressz = 0; |
|
res = NULL; |
|
auxpaths = defpaths = NULL; |
|
conf_file = NULL; |
|
e = NULL; |
|
path[0] = '\0'; |
|
|
|
while (-1 != (ch = getopt(argc, argv, "C:M:m:S:s:"))) |
switch (ch) { |
switch (ch) { |
case ('a'): |
case ('C'): |
opts.arch = optarg; |
conf_file = optarg; |
break; |
break; |
case ('c'): |
case ('M'): |
opts.cat = optarg; |
defpaths = optarg; |
break; |
break; |
case ('e'): |
case ('m'): |
opts.match = MATCH_EXACT; |
auxpaths = optarg; |
break; |
break; |
case ('I'): |
case ('S'): |
opts.insens = 1; |
opts.arch = optarg; |
break; |
break; |
case ('r'): |
|
opts.match = MATCH_REGEX; |
|
break; |
|
case ('s'): |
case ('s'): |
for (i = 0; i < SORT__MAX; i++) { |
opts.cat = optarg; |
if (strcmp(optarg, sorts[i])) |
break; |
continue; |
|
opts.sort = (enum sort)i; |
|
break; |
|
} |
|
|
|
if (i < SORT__MAX) |
|
break; |
|
|
|
fprintf(stderr, "%s: Bad sort\n", optarg); |
|
return(EXIT_FAILURE); |
|
case ('t'): |
|
while (NULL != (v = strsep(&optarg, ","))) { |
|
if ('\0' == *v) |
|
continue; |
|
for (i = 0; types[i].mask; i++) { |
|
if (strcmp(types[i].name, v)) |
|
continue; |
|
break; |
|
} |
|
if (0 == types[i].mask) |
|
break; |
|
opts.types |= types[i].mask; |
|
} |
|
if (NULL == v) |
|
break; |
|
|
|
fprintf(stderr, "%s: Bad type\n", v); |
|
return(EXIT_FAILURE); |
|
default: |
default: |
usage(); |
usage(); |
return(EXIT_FAILURE); |
return(EXIT_FAILURE); |
Line 234 main(int argc, char *argv[]) |
|
Line 104 main(int argc, char *argv[]) |
|
argc -= optind; |
argc -= optind; |
argv += optind; |
argv += optind; |
|
|
if (0 == argc || '\0' == **argv) { |
if (0 == argc) |
usage(); |
return(EXIT_SUCCESS); |
goto out; |
|
} else |
|
q = *argv; |
|
|
|
if (0 == opts.types) |
rc = 0; |
opts.types = TYPE_NAME | TYPE_DESC; |
|
|
|
/* |
manpath_parse(&paths, conf_file, defpaths, auxpaths); |
* Configure databases. |
|
* The keyword database is a btree that allows for duplicate |
|
* entries. |
|
* The index database is a recno. |
|
*/ |
|
|
|
memset(&info, 0, sizeof(BTREEINFO)); |
e = whatis ? termcomp(argc, argv, &terms) : |
info.flags = R_DUP; |
exprcomp(argc, argv, &terms); |
|
|
state.db = dbopen(dbf, O_RDONLY, 0, DB_BTREE, &info); |
if (NULL == e) { |
if (NULL == state.db) { |
fprintf(stderr, "%s: Bad expression\n", progname); |
perror(dbf); |
|
goto out; |
goto out; |
} |
} |
|
|
state.idx = dbopen(idxf, O_RDONLY, 0, DB_RECNO, NULL); |
rc = apropos_search |
if (NULL == state.idx) { |
(paths.sz, paths.paths, &opts, |
perror(idxf); |
e, terms, NULL, &ressz, &res, list); |
goto out; |
|
} |
|
|
|
/* Main search function. */ |
terms = 1; |
|
|
state_search(&state, &opts, q); |
if (0 == rc) { |
|
fprintf(stderr, "%s: Bad database\n", progname); |
|
goto out; |
|
} else if ( ! isatty(STDOUT_FILENO) || EMPTYSET(res, ressz)) |
|
goto out; |
|
|
rc = EXIT_SUCCESS; |
if ( ! SINGLETON(res, ressz)) { |
out: |
printf("Which manpage would you like [1]? "); |
if (state.db) |
fflush(stdout); |
(*state.db->close)(state.db); |
if (NULL != (cp = fgetln(stdin, &sz)) && |
if (state.idx) |
sz > 1 && '\n' == cp[--sz]) { |
(*state.idx->close)(state.idx); |
if ((ch = atoi(cp)) <= 0) |
|
goto out; |
return(rc); |
terms = (size_t)ch; |
} |
|
|
|
static void |
|
state_search(struct state *p, const struct opts *opts, char *q) |
|
{ |
|
int i, len, ch, rflags, dflag; |
|
struct mchars *mc; |
|
char *buf; |
|
size_t bufsz; |
|
recno_t rec; |
|
uint32_t fl; |
|
DBT key, val; |
|
struct res *res; |
|
regex_t reg; |
|
regex_t *regp; |
|
char filebuf[10]; |
|
struct rec record; |
|
|
|
res = NULL; |
|
len = 0; |
|
buf = NULL; |
|
bufsz = 0; |
|
ch = 0; |
|
regp = NULL; |
|
|
|
/* |
|
* Configure how we scan through results to see if we match: |
|
* whether by regexp or exact matches. |
|
*/ |
|
|
|
switch (opts->match) { |
|
case (MATCH_REGEX): |
|
rflags = REG_EXTENDED | REG_NOSUB | |
|
(opts->insens ? REG_ICASE : 0); |
|
|
|
if (0 != regcomp(®, q, rflags)) { |
|
fprintf(stderr, "%s: Bad pattern\n", q); |
|
return; |
|
} |
} |
|
|
regp = ® |
|
dflag = R_FIRST; |
|
break; |
|
case (MATCH_EXACT): |
|
key.data = q; |
|
key.size = strlen(q) + 1; |
|
dflag = R_CURSOR; |
|
break; |
|
default: |
|
dflag = R_FIRST; |
|
break; |
|
} |
} |
|
|
if (NULL == (mc = mchars_alloc())) { |
if (--terms < ressz && res[terms].matched) { |
perror(NULL); |
chdir(paths.paths[res[terms].volume]); |
exit(EXIT_FAILURE); |
strlcpy(path, res[terms].file, PATH_MAX); |
|
usecat = RESTYPE_CAT == res[terms].type; |
} |
} |
|
out: |
|
manpath_free(&paths); |
|
resfree(res, ressz); |
|
exprfree(e); |
|
|
/* |
if ('\0' == path[0]) |
* Iterate over the entire keyword database. |
return(rc ? EXIT_SUCCESS : EXIT_FAILURE); |
* For each record, we must first translate the key into UTF-8. |
|
* Following that, make sure it's acceptable. |
|
* Lastly, add it to the available records. |
|
*/ |
|
|
|
while (0 == (ch = (*p->db->seq)(p->db, &key, &val, dflag))) { |
if (-1 == pipe(fds)) { |
dflag = R_NEXT; |
perror(NULL); |
|
|
/* |
|
* Keys must be sized as such: the keyword must be |
|
* non-empty (nil terminator plus one character) and the |
|
* value must be 8 (recno_t---uint32_t---index reference |
|
* and a uint32_t flag field). |
|
*/ |
|
|
|
if (key.size < 2 || 8 != val.size) { |
|
fprintf(stderr, "%s: Corrupt database\n", p->dbf); |
|
exit(EXIT_FAILURE); |
|
} |
|
|
|
buf_redup(mc, &buf, &bufsz, (char *)key.data); |
|
|
|
fl = *(uint32_t *)val.data; |
|
|
|
if ( ! (fl & opts->types)) |
|
continue; |
|
|
|
switch (opts->match) { |
|
case (MATCH_REGEX): |
|
if (regexec(regp, buf, 0, NULL, 0)) |
|
continue; |
|
break; |
|
case (MATCH_EXACT): |
|
if (opts->insens && strcasecmp(buf, q)) |
|
goto send; |
|
if ( ! opts->insens && strcmp(buf, q)) |
|
goto send; |
|
break; |
|
default: |
|
if (opts->insens && NULL == strcasestr(buf, q)) |
|
continue; |
|
if ( ! opts->insens && NULL == strstr(buf, q)) |
|
continue; |
|
break; |
|
} |
|
|
|
/* |
|
* Now look up the file itself in our index. The file's |
|
* indexed by its recno for fast lookups. |
|
*/ |
|
|
|
memcpy(&rec, val.data + 4, sizeof(recno_t)); |
|
|
|
if ( ! state_getrecord(p, rec, &record)) |
|
exit(EXIT_FAILURE); |
|
|
|
/* If we're in a different section, skip... */ |
|
|
|
if (opts->cat && strcasecmp(opts->cat, record.cat)) |
|
continue; |
|
if (opts->arch && strcasecmp(opts->arch, record.arch)) |
|
continue; |
|
|
|
/* FIXME: this needs to be changed. Ugh. Linear. */ |
|
|
|
for (i = 0; i < len; i++) |
|
if (res[i].rec == record.rec) |
|
break; |
|
|
|
if (i < len) |
|
continue; |
|
|
|
res = mandoc_realloc |
|
(res, (len + 1) * sizeof(struct res)); |
|
|
|
/* |
|
* Now we have our filename, keywords, types, and all |
|
* other necessary information. |
|
* Process it and add it to our list of results. |
|
*/ |
|
|
|
filebuf[9] = '\0'; |
|
snprintf(filebuf, 10, "%u", record.rec); |
|
assert('\0' == filebuf[9]); |
|
|
|
res[len].rec = record.rec; |
|
res[len].types = fl; |
|
|
|
buf_dup(mc, &res[len].keyword, buf); |
|
buf_dup(mc, &res[len].uri, filebuf); |
|
buf_dup(mc, &res[len].cat, record.cat); |
|
buf_dup(mc, &res[len].arch, record.arch); |
|
buf_dup(mc, &res[len].title, record.title); |
|
buf_dup(mc, &res[len].desc, record.desc); |
|
len++; |
|
} |
|
|
|
send: |
|
if (ch < 0) { |
|
perror(p->dbf); |
|
exit(EXIT_FAILURE); |
exit(EXIT_FAILURE); |
} |
|
|
|
/* |
|
* Sort our results. |
|
* We do this post-scan (instead of an in-line sort) because |
|
* it's more or less the same in terms of run-time. Assuming we |
|
* sort in-line with a tree versus post: |
|
* |
|
* In-place: n * O(lg n) |
|
* After: n + O(n lg n) |
|
* |
|
* Whatever. This also buys us simplicity. |
|
*/ |
|
|
|
switch (opts->sort) { |
|
case (SORT_CAT): |
|
qsort(res, len, sizeof(struct res), sort_cat); |
|
break; |
|
default: |
|
qsort(res, len, sizeof(struct res), sort_title); |
|
break; |
|
} |
} |
|
|
state_output(res, len); |
if (-1 == (pid = fork())) { |
|
|
for (len-- ; len >= 0; len--) { |
|
free(res[len].keyword); |
|
free(res[len].title); |
|
free(res[len].cat); |
|
free(res[len].arch); |
|
free(res[len].desc); |
|
free(res[len].uri); |
|
} |
|
|
|
free(res); |
|
free(buf); |
|
mchars_free(mc); |
|
|
|
if (regp) |
|
regfree(regp); |
|
} |
|
|
|
/* |
|
* Track allocated buffer size for buf_redup(). |
|
*/ |
|
static inline void |
|
buf_alloc(char **buf, size_t *bufsz, size_t sz) |
|
{ |
|
|
|
if (sz < *bufsz) |
|
return; |
|
|
|
*bufsz = sz + 1024; |
|
if (NULL == (*buf = realloc(*buf, *bufsz))) { |
|
perror(NULL); |
perror(NULL); |
exit(EXIT_FAILURE); |
exit(EXIT_FAILURE); |
|
} else if (pid > 0) { |
|
dup2(fds[0], STDIN_FILENO); |
|
close(fds[1]); |
|
prog = NULL != getenv("MANPAGER") ? |
|
getenv("MANPAGER") : |
|
(NULL != getenv("PAGER") ? |
|
getenv("PAGER") : "more"); |
|
execlp(prog, prog, (char *)NULL); |
|
perror(prog); |
|
return(EXIT_FAILURE); |
} |
} |
} |
|
|
|
/* |
dup2(fds[1], STDOUT_FILENO); |
* Like buf_redup() but throwing away the buffer size. |
close(fds[0]); |
*/ |
prog = usecat ? "cat" : "mandoc"; |
static void |
execlp(prog, prog, path, (char *)NULL); |
buf_dup(struct mchars *mc, char **buf, const char *val) |
perror(prog); |
{ |
return(EXIT_FAILURE); |
size_t bufsz; |
|
|
|
bufsz = 0; |
|
*buf = NULL; |
|
buf_redup(mc, buf, &bufsz, val); |
|
} |
} |
|
|
/* |
/* ARGSUSED */ |
* Normalise strings from the index and database. |
|
* These strings are escaped as defined by mandoc_char(7) along with |
|
* other goop in mandoc.h (e.g., soft hyphens). |
|
*/ |
|
static void |
static void |
buf_redup(struct mchars *mc, char **buf, |
list(struct res *res, size_t sz, void *arg) |
size_t *bufsz, const char *val) |
|
{ |
{ |
size_t sz; |
size_t i; |
const char *seq, *cpp; |
|
int len, pos; |
|
enum mandoc_esc esc; |
|
const char rsv[] = { '\\', ASCII_NBRSP, ASCII_HYPH, '\0' }; |
|
|
|
/* Pre-allocate by the length of the input */ |
qsort(res, sz, sizeof(struct res), cmp); |
|
|
buf_alloc(buf, bufsz, strlen(val) + 1); |
if (EMPTYSET(res, sz) || SINGLETON(res, sz)) |
|
return; |
|
|
pos = 0; |
if ( ! isatty(STDOUT_FILENO)) |
|
for (i = 0; i < sz && res[i].matched; i++) |
while ('\0' != *val) { |
printf("%s(%s%s%s) - %.70s\n", |
/* |
res[i].title, res[i].cat, |
* Halt on the first escape sequence. |
*res[i].arch ? "/" : "", |
* This also halts on the end of string, in which case |
*res[i].arch ? res[i].arch : "", |
* we just copy, fallthrough, and exit the loop. |
res[i].desc); |
*/ |
else |
if ((sz = strcspn(val, rsv)) > 0) { |
for (i = 0; i < sz && res[i].matched; i++) |
memcpy(&(*buf)[pos], val, sz); |
printf("[%zu] %s(%s%s%s) - %.70s\n", i + 1, |
pos += (int)sz; |
res[i].title, res[i].cat, |
val += (int)sz; |
*res[i].arch ? "/" : "", |
} |
*res[i].arch ? res[i].arch : "", |
|
res[i].desc); |
if (ASCII_HYPH == *val) { |
|
(*buf)[pos++] = '-'; |
|
val++; |
|
continue; |
|
} else if (ASCII_NBRSP == *val) { |
|
(*buf)[pos++] = ' '; |
|
val++; |
|
continue; |
|
} else if ('\\' != *val) |
|
break; |
|
|
|
/* Read past the slash. */ |
|
|
|
val++; |
|
|
|
/* |
|
* Parse the escape sequence and see if it's a |
|
* predefined character or special character. |
|
*/ |
|
|
|
esc = mandoc_escape(&val, &seq, &len); |
|
if (ESCAPE_ERROR == esc) |
|
break; |
|
|
|
cpp = ESCAPE_SPECIAL == esc ? |
|
mchars_spec2str(mc, seq, len, &sz) : NULL; |
|
|
|
if (NULL == cpp) |
|
continue; |
|
|
|
/* Copy the rendered glyph into the stream. */ |
|
|
|
buf_alloc(buf, bufsz, sz); |
|
|
|
memcpy(&(*buf)[pos], cpp, sz); |
|
pos += (int)sz; |
|
} |
|
|
|
(*buf)[pos] = '\0'; |
|
} |
} |
|
|
static void |
static int |
state_output(const struct res *res, int sz) |
cmp(const void *p1, const void *p2) |
{ |
{ |
int i; |
const struct res *r1 = p1; |
|
const struct res *r2 = p2; |
|
|
for (i = 0; i < sz; i++) |
if (0 == r1->matched) |
printf("%s(%s%s%s) - %s\n", res[i].title, |
return(1); |
res[i].cat, |
else if (0 == r2->matched) |
*res[i].arch ? "/" : "", |
return(1); |
*res[i].arch ? res[i].arch : "", |
|
res[i].desc); |
return(strcasecmp(r1->title, r2->title)); |
} |
} |
|
|
static void |
static void |
|
|
{ |
{ |
|
|
fprintf(stderr, "usage: %s " |
fprintf(stderr, "usage: %s " |
"[-eIr] " |
"[-C file] " |
"[-a arch] " |
"[-M manpath] " |
"[-c cat] " |
"[-m manpath] " |
"[-s sort] " |
"[-S arch] " |
"[-t type[,...]] " |
"[-s section] " |
"key\n", progname); |
"expression ...\n", |
} |
progname); |
|
|
static int |
|
state_getrecord(struct state *p, recno_t rec, struct rec *rp) |
|
{ |
|
DBT key, val; |
|
size_t sz; |
|
int rc; |
|
|
|
key.data = &rec; |
|
key.size = sizeof(recno_t); |
|
|
|
rc = (*p->idx->get)(p->idx, &key, &val, 0); |
|
if (rc < 0) { |
|
perror(p->idxf); |
|
return(0); |
|
} else if (rc > 0) |
|
goto err; |
|
|
|
rp->file = (char *)val.data; |
|
if ((sz = strlen(rp->file) + 1) >= val.size) |
|
goto err; |
|
|
|
rp->cat = (char *)val.data + (int)sz; |
|
if ((sz += strlen(rp->cat) + 1) >= val.size) |
|
goto err; |
|
|
|
rp->title = (char *)val.data + (int)sz; |
|
if ((sz += strlen(rp->title) + 1) >= val.size) |
|
goto err; |
|
|
|
rp->arch = (char *)val.data + (int)sz; |
|
if ((sz += strlen(rp->arch) + 1) >= val.size) |
|
goto err; |
|
|
|
rp->desc = (char *)val.data + (int)sz; |
|
rp->rec = rec; |
|
return(1); |
|
err: |
|
fprintf(stderr, "%s: Corrupt index\n", p->idxf); |
|
return(0); |
|
} |
|
|
|
static int |
|
sort_title(const void *p1, const void *p2) |
|
{ |
|
|
|
return(strcmp(((const struct res *)p1)->title, |
|
((const struct res *)p2)->title)); |
|
} |
|
|
|
static int |
|
sort_cat(const void *p1, const void *p2) |
|
{ |
|
int rc; |
|
|
|
rc = strcmp(((const struct res *)p1)->cat, |
|
((const struct res *)p2)->cat); |
|
|
|
return(0 == rc ? sort_title(p1, p2) : rc); |
|
} |
} |