version 1.1, 2011/10/06 23:00:54 |
version 1.38, 2014/04/11 15:46:52 |
|
|
/* $Id$ */ |
/* $Id$ */ |
/* |
/* |
* Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv> |
* 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 |
* 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 <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 "manpath.h" |
# include <db_185.h> |
#include "mansearch.h" |
#else |
|
# include <db.h> |
|
#endif |
|
|
|
#include "mandoc.h" |
|
|
|
#define MAXRESULTS 100 |
|
|
|
#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; |
|
}; |
|
|
|
struct rec { |
|
char *file; |
|
char *cat; |
|
char *title; |
|
char *arch; |
|
char *desc; |
|
recno_t rec; |
|
}; |
|
|
|
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 */ |
|
void (*err)(const char *); |
|
void (*errx)(const char *, ...); |
|
}; |
|
|
|
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 void error(const char *, ...); |
|
static int sort_cat(const void *, const void *); |
|
static int sort_title(const void *, const void *); |
|
static void state_destroy(struct state *); |
|
static int state_getrecord(struct state *, recno_t, struct rec *); |
|
static int state_init(struct state *, |
|
const char *, const char *, |
|
void (*err)(const char *), |
|
void (*errx)(const char *, ...)); |
|
static void state_output(const struct res *, int); |
|
static void state_search(struct state *, |
|
const struct opts *, char *); |
|
|
|
static void usage(void); |
|
|
|
static const char *progname; |
|
|
|
int |
int |
main(int argc, char *argv[]) |
main(int argc, char *argv[]) |
{ |
{ |
int ch, i; |
int ch, whatis; |
const char *dbf, *idxf; |
struct mansearch search; |
struct state state; |
size_t i, sz; |
char *q, *v; |
struct manpage *res; |
struct opts opts; |
struct manpaths paths; |
extern int optind; |
char *defpaths, *auxpaths; |
|
char *conf_file; |
|
char *progname; |
|
const char *outkey; |
extern char *optarg; |
extern char *optarg; |
|
extern int optind; |
|
|
memset(&opts, 0, sizeof(struct opts)); |
|
|
|
dbf = "mandoc.db"; |
|
idxf = "mandoc.index"; |
|
q = NULL; |
|
|
|
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(&search, 0, sizeof(struct mansearch)); |
|
|
|
auxpaths = defpaths = NULL; |
|
conf_file = NULL; |
|
outkey = "Nd"; |
|
|
|
while (-1 != (ch = getopt(argc, argv, "C:M:m:O: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 ('O'): |
opts.insens = 1; |
outkey = optarg; |
break; |
break; |
case ('r'): |
case ('S'): |
opts.match = MATCH_REGEX; |
search.arch = optarg; |
break; |
break; |
case ('s'): |
case ('s'): |
for (i = 0; i < SORT__MAX; i++) { |
search.sec = optarg; |
if (strcmp(optarg, sorts[i])) |
break; |
continue; |
|
opts.sort = (enum sort)i; |
|
break; |
|
} |
|
|
|
if (i < SORT__MAX) |
|
break; |
|
|
|
error("%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; |
|
|
|
error("%s: Bad type\n", v); |
|
return(EXIT_FAILURE); |
|
default: |
default: |
usage(); |
goto usage; |
return(EXIT_FAILURE); |
|
} |
} |
|
|
argc -= optind; |
argc -= optind; |
argv += optind; |
argv += optind; |
|
|
if (0 == argc || '\0' == **argv) { |
if (0 == argc) |
usage(); |
goto usage; |
return(EXIT_FAILURE); |
|
} else |
|
q = *argv; |
|
|
|
if (0 == opts.types) |
search.deftype = whatis ? TYPE_Nm : TYPE_Nm | TYPE_Nd; |
opts.types = TYPE_NAME | TYPE_DESC; |
search.flags = whatis ? MANSEARCH_WHATIS : 0; |
|
|
if ( ! state_init(&state, dbf, idxf, perror, error)) { |
manpath_parse(&paths, conf_file, defpaths, auxpaths); |
state_destroy(&state); |
mansearch_setup(1); |
return(EXIT_FAILURE); |
ch = mansearch(&search, &paths, argc, argv, outkey, &res, &sz); |
} |
manpath_free(&paths); |
|
|
state_search(&state, &opts, q); |
if (0 == ch) |
state_destroy(&state); |
goto usage; |
|
|
return(EXIT_SUCCESS); |
for (i = 0; i < sz; i++) { |
} |
printf("%s - %s\n", res[i].names, |
|
NULL == res[i].output ? "" : res[i].output); |
static void |
free(res[i].file); |
state_search(struct state *p, const struct opts *opts, char *q) |
free(res[i].names); |
{ |
free(res[i].output); |
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[MAXRESULTS]; |
|
regex_t reg; |
|
regex_t *regp; |
|
char filebuf[10]; |
|
struct rec record; |
|
|
|
len = 0; |
|
buf = NULL; |
|
bufsz = 0; |
|
ch = 0; |
|
regp = NULL; |
|
|
|
switch (opts->match) { |
|
case (MATCH_REGEX): |
|
rflags = REG_EXTENDED | REG_NOSUB | |
|
(opts->insens ? REG_ICASE : 0); |
|
|
|
if (0 != regcomp(®, q, rflags)) { |
|
error("%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())) { |
free(res); |
perror(NULL); |
mansearch_setup(0); |
exit(EXIT_FAILURE); |
return(sz ? EXIT_SUCCESS : EXIT_FAILURE); |
} |
usage: |
|
fprintf(stderr, "usage: %s [-C file] [-M path] [-m path] " |
/* |
"[-O outkey] " |
* Iterate over the entire keyword database. |
"[-S arch] [-s section]%s ...\n", progname, |
* For each record, we must first translate the key into UTF-8. |
whatis ? " name" : "\n expression"); |
* Following that, make sure it's acceptable. |
return(EXIT_FAILURE); |
* Lastly, add it to the available records. |
|
*/ |
|
|
|
while (len < MAXRESULTS) { |
|
if ((ch = (*p->db->seq)(p->db, &key, &val, dflag))) |
|
break; |
|
|
|
dflag = R_NEXT; |
|
|
|
/* |
|
* 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) { |
|
error("%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; |
|
|
|
/* |
|
* 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); |
|
} |
|
|
|
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); |
|
|
|
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(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); |
|
exit(EXIT_FAILURE); |
|
} |
|
} |
|
|
|
/* |
|
* Like buf_redup() but throwing away the buffer size. |
|
*/ |
|
static void |
|
buf_dup(struct mchars *mc, char **buf, const char *val) |
|
{ |
|
size_t bufsz; |
|
|
|
bufsz = 0; |
|
*buf = NULL; |
|
buf_redup(mc, buf, &bufsz, val); |
|
} |
|
|
|
/* |
|
* 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 |
|
buf_redup(struct mchars *mc, char **buf, |
|
size_t *bufsz, const char *val) |
|
{ |
|
size_t sz; |
|
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 */ |
|
|
|
buf_alloc(buf, bufsz, strlen(val) + 1); |
|
|
|
pos = 0; |
|
|
|
while ('\0' != *val) { |
|
/* |
|
* Halt on the first escape sequence. |
|
* This also halts on the end of string, in which case |
|
* we just copy, fallthrough, and exit the loop. |
|
*/ |
|
if ((sz = strcspn(val, rsv)) > 0) { |
|
memcpy(&(*buf)[pos], val, sz); |
|
pos += (int)sz; |
|
val += (int)sz; |
|
} |
|
|
|
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 |
|
error(const char *fmt, ...) |
|
{ |
|
va_list ap; |
|
|
|
va_start(ap, fmt); |
|
vfprintf(stderr, fmt, ap); |
|
va_end(ap); |
|
} |
|
|
|
static void |
|
state_output(const struct res *res, int sz) |
|
{ |
|
int i; |
|
|
|
for (i = 0; i < sz; i++) |
|
printf("%s(%s%s%s) - %s\n", res[i].title, |
|
res[i].cat, |
|
*res[i].arch ? "/" : "", |
|
*res[i].arch ? res[i].arch : "", |
|
res[i].desc); |
|
} |
|
|
|
static void |
|
usage(void) |
|
{ |
|
|
|
fprintf(stderr, "usage: %s " |
|
"[-eIr] " |
|
"[-a arch] " |
|
"[-c cat] " |
|
"[-s sort] " |
|
"[-t type[,...]] " |
|
"key\n", progname); |
|
} |
|
|
|
static int |
|
state_init(struct state *p, |
|
const char *dbf, const char *idxf, |
|
void (*err)(const char *), |
|
void (*errx)(const char *, ...)) |
|
{ |
|
BTREEINFO info; |
|
|
|
memset(p, 0, sizeof(struct state)); |
|
memset(&info, 0, sizeof(BTREEINFO)); |
|
|
|
info.flags = R_DUP; |
|
|
|
p->dbf = dbf; |
|
p->idxf = idxf; |
|
p->err = err; |
|
|
|
p->db = dbopen(p->dbf, O_RDONLY, 0, DB_BTREE, &info); |
|
if (NULL == p->db) { |
|
(*err)(p->dbf); |
|
return(0); |
|
} |
|
|
|
p->idx = dbopen(p->idxf, O_RDONLY, 0, DB_RECNO, NULL); |
|
if (NULL == p->idx) { |
|
(*err)(p->idxf); |
|
return(0); |
|
} |
|
|
|
return(1); |
|
} |
|
|
|
static void |
|
state_destroy(struct state *p) |
|
{ |
|
|
|
if (p->db) |
|
(*p->db->close)(p->db); |
|
if (p->idx) |
|
(*p->idx->close)(p->idx); |
|
} |
|
|
|
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) { |
|
(*p->err)(p->idxf); |
|
return(0); |
|
} else if (rc > 0) { |
|
(*p->errx)("%s: Corrupt index\n", p->idxf); |
|
return(0); |
|
} |
|
|
|
rp->file = (char *)val.data; |
|
if ((sz = strlen(rp->file) + 1) >= val.size) { |
|
(*p->errx)("%s: Corrupt index\n", p->idxf); |
|
return(0); |
|
} |
|
|
|
rp->cat = (char *)val.data + (int)sz; |
|
if ((sz += strlen(rp->cat) + 1) >= val.size) { |
|
(*p->errx)("%s: Corrupt index\n", p->idxf); |
|
return(0); |
|
} |
|
|
|
rp->title = (char *)val.data + (int)sz; |
|
if ((sz += strlen(rp->title) + 1) >= val.size) { |
|
(*p->errx)("%s: Corrupt index\n", p->idxf); |
|
return(0); |
|
} |
|
|
|
rp->arch = (char *)val.data + (int)sz; |
|
if ((sz += strlen(rp->arch) + 1) >= val.size) { |
|
(*p->errx)("%s: Corrupt index\n", p->idxf); |
|
return(0); |
|
} |
|
|
|
rp->desc = (char *)val.data + (int)sz; |
|
rp->rec = rec; |
|
return(1); |
|
} |
|
|
|
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); |
|
} |
} |