#include <u.h>
#include <libc.h>
#include <auth.h>
#include <fcall.h>
/*
* caveat:
* this stuff is only meant to work for ascii databases
*/
typedef struct Fid Fid;
typedef struct Fs Fs;
typedef struct Quick Quick;
typedef struct Match Match;
typedef struct Search Search;
enum
{
OPERM = 0x3, /* mask of all permission types in open mode */
Nfidhash = 32,
/*
* qids
*/
Qroot = 1,
Qsearch = 2,
Qstats = 3,
};
/*
* boyer-moore quick string matching
*/
struct Quick
{
char *pat;
char *up; /* match string for upper case of pat */
int len; /* of pat (and up) -1; used for fast search */
uchar jump[256]; /* jump index table */
int miss; /* amount to jump if we falsely match the last char */
};
extern void quickmk(Quick*, char*, int);
extern void quickfree(Quick*);
extern char* quicksearch(Quick*, char*, char*);
/*
* exact matching of a search string
*/
struct Match
{
Match *next;
char *pat; /* null-terminated search string */
char *up; /* upper case of pat */
int len; /* length of both pat and up */
int (*op)(Match*, char*, char*); /* method for this partiticular search */
};
struct Search
{
Quick quick; /* quick match */
Match *match; /* exact matches */
int skip; /* number of matches to skip */
};
extern char* searchsearch(Search*, char*, char*, int*);
extern Search* searchparse(char*, char*);
extern void searchfree(Search*);
struct Fid
{
Lock;
Fid *next;
Fid **last;
uint fid;
int ref; /* number of fcalls using the fid */
int attached; /* fid has beed attached or cloned and not clunked */
int open;
Qid qid;
Search *search; /* search patterns */
char *where; /* current location in the database */
int n; /* number of bytes left in found item */
};
int dostat(int, uchar*, int);
void* emalloc(uint);
void fatal(char*, ...);
Match* mkmatch(Match*, int(*)(Match*, char*, char*), char*);
Match* mkstrmatch(Match*, char*);
char* nextsearch(char*, char*, char**, char**);
int strlook(Match*, char*, char*);
char* strndup(char*, int);
int tolower(int);
int toupper(int);
char* urlunesc(char*, char*);
void usage(void);
struct Fs
{
Lock; /* for fids */
Fid *hash[Nfidhash];
uchar statbuf[1024]; /* plenty big enough */
};
extern void fsrun(Fs*, int);
extern Fid* getfid(Fs*, uint);
extern Fid* mkfid(Fs*, uint);
extern void putfid(Fs*, Fid*);
extern char* fsversion(Fs*, Fcall*);
extern char* fsauth(Fs*, Fcall*);
extern char* fsattach(Fs*, Fcall*);
extern char* fswalk(Fs*, Fcall*);
extern char* fsopen(Fs*, Fcall*);
extern char* fscreate(Fs*, Fcall*);
extern char* fsread(Fs*, Fcall*);
extern char* fswrite(Fs*, Fcall*);
extern char* fsclunk(Fs*, Fcall*);
extern char* fsremove(Fs*, Fcall*);
extern char* fsstat(Fs*, Fcall*);
extern char* fswstat(Fs*, Fcall*);
char *(*fcalls[])(Fs*, Fcall*) =
{
[Tversion] fsversion,
[Tattach] fsattach,
[Tauth] fsauth,
[Twalk] fswalk,
[Topen] fsopen,
[Tcreate] fscreate,
[Tread] fsread,
[Twrite] fswrite,
[Tclunk] fsclunk,
[Tremove] fsremove,
[Tstat] fsstat,
[Twstat] fswstat
};
char Eperm[] = "permission denied";
char Enotdir[] = "not a directory";
char Enotexist[] = "file does not exist";
char Eisopen[] = "file already open for I/O";
char Einuse[] = "fid is already in use";
char Enofid[] = "no such fid";
char Enotopen[] = "file is not open";
char Ebadsearch[] = "bad search string";
Fs fs;
char *database;
char *edatabase;
int messagesize = 8192+IOHDRSZ;
void
main(int argc, char **argv)
{
Dir *d;
char buf[12], *mnt, *srv;
int fd, p[2], n;
mnt = "/tmp";
srv = nil;
ARGBEGIN{
case 's':
srv = ARGF();
mnt = nil;
break;
case 'm':
mnt = ARGF();
break;
}ARGEND
fmtinstall('F', fcallfmt);
if(argc != 1)
usage();
d = nil;
fd = open(argv[0], OREAD);
if(fd < 0 || (d=dirfstat(fd)) == nil)
fatal("can't open %s: %r", argv[0]);
n = d->length;
free(d);
if(n == 0)
fatal("zero length database %s", argv[0]);
database = emalloc(n);
if(read(fd, database, n) != n)
fatal("can't read %s: %r", argv[0]);
close(fd);
edatabase = database + n;
if(pipe(p) < 0)
fatal("pipe failed");
switch(rfork(RFPROC|RFMEM|RFNOTEG|RFNAMEG)){
case 0:
fsrun(&fs, p[0]);
exits(nil);
case -1:
fatal("fork failed");
}
if(mnt == nil){
if(srv == nil)
usage();
fd = create(srv, OWRITE, 0666);
if(fd < 0){
remove(srv);
fd = create(srv, OWRITE, 0666);
if(fd < 0){
close(p[1]);
fatal("create of %s failed", srv);
}
}
sprint(buf, "%d", p[1]);
if(write(fd, buf, strlen(buf)) < 0){
close(p[1]);
fatal("writing %s", srv);
}
close(p[1]);
exits(nil);
}
if(mount(p[1], -1, mnt, MREPL, "") < 0){
close(p[1]);
fatal("mount failed");
}
close(p[1]);
exits(nil);
}
/*
* execute the search
* do a quick match,
* isolate the line in which the occured,
* and try all of the exact matches
*/
char*
searchsearch(Search *search, char *where, char *end, int *np)
{
Match *m;
char *s, *e;
*np = 0;
if(search == nil || where == nil)
return nil;
for(;;){
s = quicksearch(&search->quick, where, end);
if(s == nil)
return nil;
e = memchr(s, '\n', end - s);
if(e == nil)
e = end;
else
e++;
while(s > where && s[-1] != '\n')
s--;
for(m = search->match; m != nil; m = m->next){
if((*m->op)(m, s, e) == 0)
break;
}
if(m == nil){
if(search->skip > 0)
search->skip--;
else{
*np = e - s;
return s;
}
}
where = e;
}
}
/*
* parse a search string of the form
* tag=val&tag1=val1...
*/
Search*
searchparse(char *search, char *esearch)
{
Search *s;
Match *m, *next, **last;
char *tag, *val, *p;
int ok;
s = emalloc(sizeof *s);
s->match = nil;
/*
* acording to the http spec,
* repeated search queries are ingored.
* the last search given is performed on the original object
*/
while((p = memchr(s, '?', esearch - search)) != nil){
search = p + 1;
}
while(search < esearch){
search = nextsearch(search, esearch, &tag, &val);
if(tag == nil)
continue;
ok = 0;
if(strcmp(tag, "skip") == 0){
s->skip = strtoul(val, &p, 10);
if(*p == 0)
ok = 1;
}else if(strcmp(tag, "search") == 0){
s->match = mkstrmatch(s->match, val);
ok = 1;
}
free(tag);
free(val);
if(!ok){
searchfree(s);
return nil;
}
}
if(s->match == nil){
free(s);
return nil;
}
/*
* order the matches by probability of occurance
* first cut is just by length
*/
for(ok = 0; !ok; ){
ok = 1;
last = &s->match;
for(m = *last; m && m->next; m = *last){
if(m->next->len > m->len){
next = m->next;
m->next = next->next;
next->next = m;
*last = next;
ok = 0;
}
last = &m->next;
}
}
/*
* convert the best search into a fast lookup
*/
m = s->match;
s->match = m->next;
quickmk(&s->quick, m->pat, 1);
free(m->pat);
free(m->up);
free(m);
return s;
}
void
searchfree(Search *s)
{
Match *m, *next;
if(s == nil)
return;
for(m = s->match; m; m = next){
next = m->next;
free(m->pat);
free(m->up);
free(m);
}
quickfree(&s->quick);
free(s);
}
char*
nextsearch(char *search, char *esearch, char **tagp, char **valp)
{
char *tag, *val;
*tagp = nil;
*valp = nil;
for(tag = search; search < esearch && *search != '='; search++)
;
if(search == esearch)
return search;
tag = urlunesc(tag, search);
search++;
for(val = search; search < esearch && *search != '&'; search++)
;
val = urlunesc(val, search);
if(search != esearch)
search++;
*tagp = tag;
*valp = val;
return search;
}
Match*
mkstrmatch(Match *m, char *pat)
{
char *s;
for(s = pat; *s; s++){
if(*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r'){
*s = 0;
m = mkmatch(m, strlook, pat);
pat = s + 1;
}else
*s = tolower(*s);
}
return mkmatch(m, strlook, pat);
}
Match*
mkmatch(Match *next, int (*op)(Match*, char*, char*), char *pat)
{
Match *m;
char *p;
int n;
n = strlen(pat);
if(n == 0)
return next;
m = emalloc(sizeof *m);
m->op = op;
m->len = n;
m->pat = strdup(pat);
m->up = strdup(pat);
for(p = m->up; *p; p++)
*p = toupper(*p);
for(p = m->pat; *p; p++)
*p = tolower(*p);
m->next = next;
return m;
}
int
strlook(Match *m, char *str, char *e)
{
char *pat, *up, *s;
int c, pc, fc, fuc, n;
n = m->len;
fc = m->pat[0];
fuc = m->up[0];
for(; str + n <= e; str++){
c = *str;
if(c != fc && c != fuc)
continue;
s = str + 1;
up = m->up + 1;
for(pat = m->pat + 1; pc = *pat; pat++){
c = *s;
if(c != pc && c != *up)
break;
up++;
s++;
}
if(pc == 0)
return 1;
}
return 0;
}
/*
* boyer-moore style pattern matching
* implements an exact match for ascii
* however, if mulitbyte upper-case and lower-case
* characters differ in length or in more than one byte,
* it only implements an approximate match
*/
void
quickmk(Quick *q, char *spat, int ignorecase)
{
char *pat, *up;
uchar *j;
int ep, ea, cp, ca, i, c, n;
/*
* allocate the machine
*/
n = strlen(spat);
if(n == 0){
q->pat = nil;
q->up = nil;
q->len = -1;
return;
}
pat = emalloc(2* n + 2);
q->pat = pat;
up = pat;
if(ignorecase)
up = pat + n + 1;
q->up = up;
while(c = *spat++){
if(ignorecase){
*up++ = toupper(c);
c = tolower(c);
}
*pat++ = c;
}
pat = q->pat;
up = q->up;
pat[n] = up[n] = '\0';
/*
* make the skipping table
*/
if(n > 255)
n = 255;
j = q->jump;
memset(j, n, 256);
n--;
q->len = n;
for(i = 0; i <= n; i++){
j[(uchar)pat[i]] = n - i;
j[(uchar)up[i]] = n - i;
}
/*
* find the minimum safe amount to skip
* if we match the last char but not the whole pat
*/
ep = pat[n];
ea = up[n];
for(i = n - 1; i >= 0; i--){
cp = pat[i];
ca = up[i];
if(cp == ep || cp == ea || ca == ep || ca == ea)
break;
}
q->miss = n - i;
}
void
quickfree(Quick *q)
{
if(q->pat != nil)
free(q->pat);
q->pat = nil;
}
char *
quicksearch(Quick *q, char *s, char *e)
{
char *pat, *up, *m, *ee;
uchar *j;
int len, n, c, mc;
len = q->len;
if(len < 0)
return s;
j = q->jump;
pat = q->pat;
up = q->up;
s += len;
ee = e - (len * 4 + 4);
while(s < e){
/*
* look for a match on the last char
*/
while(s < ee && (n = j[(uchar)*s])){
s += n;
s += j[(uchar)*s];
s += j[(uchar)*s];
s += j[(uchar)*s];
}
if(s >= e)
return nil;
while(n = j[(uchar)*s]){
s += n;
if(s >= e)
return nil;
}
/*
* does the string match?
*/
m = s - len;
for(n = 0; c = pat[n]; n++){
mc = *m++;
if(c != mc && mc != up[n])
break;
}
if(!c)
return s - len;
s += q->miss;
}
return nil;
}
void
fsrun(Fs *fs, int fd)
{
Fcall rpc;
char *err;
uchar *buf;
int n;
buf = emalloc(messagesize);
for(;;){
/*
* reading from a pipe or a network device
* will give an error after a few eof reads
* however, we cannot tell the difference
* between a zero-length read and an interrupt
* on the processes writing to us,
* so we wait for the error
*/
n = read9pmsg(fd, buf, messagesize);
if(n == 0)
continue;
if(n < 0)
fatal("mount read");
rpc.data = (char*)buf + IOHDRSZ;
if(convM2S(buf, n, &rpc) == 0)
continue;
// fprint(2, "recv: %F\n", &rpc);
/*
* flushes are way too hard.
* a reply to the original message seems to work
*/
if(rpc.type == Tflush)
continue;
else if(rpc.type >= Tmax || !fcalls[rpc.type])
err = "bad fcall type";
else
err = (*fcalls[rpc.type])(fs, &rpc);
if(err){
rpc.type = Rerror;
rpc.ename = err;
}else
rpc.type++;
n = convS2M(&rpc, buf, messagesize);
// fprint(2, "send: %F\n", &rpc);
if(write(fd, buf, n) != n)
fatal("mount write");
}
}
Fid*
mkfid(Fs *fs, uint fid)
{
Fid *f;
int h;
h = fid % Nfidhash;
for(f = fs->hash[h]; f; f = f->next){
if(f->fid == fid)
return nil;
}
f = emalloc(sizeof *f);
f->next = fs->hash[h];
if(f->next != nil)
f->next->last = &f->next;
f->last = &fs->hash[h];
fs->hash[h] = f;
f->fid = fid;
f->ref = 1;
f->attached = 1;
f->open = 0;
return f;
}
Fid*
getfid(Fs *fs, uint fid)
{
Fid *f;
int h;
h = fid % Nfidhash;
for(f = fs->hash[h]; f; f = f->next){
if(f->fid == fid){
if(f->attached == 0)
break;
f->ref++;
return f;
}
}
return nil;
}
void
putfid(Fs *, Fid *f)
{
f->ref--;
if(f->ref == 0 && f->attached == 0){
*f->last = f->next;
if(f->next != nil)
f->next->last = f->last;
if(f->search != nil)
searchfree(f->search);
free(f);
}
}
char*
fsversion(Fs *, Fcall *rpc)
{
if(rpc->msize < 256)
return "version: message size too small";
if(rpc->msize > messagesize)
rpc->msize = messagesize;
messagesize = rpc->msize;
if(strncmp(rpc->version, "9P2000", 6) != 0)
return "unrecognized 9P version";
rpc->version = "9P2000";
return nil;
}
char*
fsauth(Fs *, Fcall *)
{
return "searchfs: authentication not required";
}
char*
fsattach(Fs *fs, Fcall *rpc)
{
Fid *f;
f = mkfid(fs, rpc->fid);
if(f == nil)
return Einuse;
f->open = 0;
f->qid.type = QTDIR;
f->qid.path = Qroot;
f->qid.vers = 0;
rpc->qid = f->qid;
putfid(fs, f);
return nil;
}
char*
fswalk(Fs *fs, Fcall *rpc)
{
Fid *f, *nf;
int nqid, nwname, type;
char *err, *name;
ulong path;
f = getfid(fs, rpc->fid);
if(f == nil)
return Enofid;
nf = nil;
if(rpc->fid != rpc->newfid){
nf = mkfid(fs, rpc->newfid);
if(nf == nil){
putfid(fs, f);
return Einuse;
}
nf->qid = f->qid;
putfid(fs, f);
f = nf; /* walk f */
}
err = nil;
path = f->qid.path;
nwname = rpc->nwname;
for(nqid=0; nqid<nwname; nqid++){
if(path != Qroot){
err = Enotdir;
break;
}
name = rpc->wname[nqid];
if(strcmp(name, "search") == 0){
type = QTFILE;
path = Qsearch;
}else if(strcmp(name, "stats") == 0){
type = QTFILE;
path = Qstats;
}else if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0){
type = QTDIR;
path = path;
}else{
err = Enotexist;
break;
}
rpc->wqid[nqid] = (Qid){path, 0, type};
}
if(nwname > 0){
if(nf != nil && nqid < nwname)
nf->attached = 0;
if(nqid == nwname)
f->qid = rpc->wqid[nqid-1];
}
putfid(fs, f);
rpc->nwqid = nqid;
f->open = 0;
return err;
}
char *
fsopen(Fs *fs, Fcall *rpc)
{
Fid *f;
int mode;
f = getfid(fs, rpc->fid);
if(f == nil)
return Enofid;
if(f->open){
putfid(fs, f);
return Eisopen;
}
mode = rpc->mode & OPERM;
if(mode == OEXEC
|| f->qid.path == Qroot && (mode == OWRITE || mode == ORDWR)){
putfid(fs, f);
return Eperm;
}
f->open = 1;
f->where = nil;
f->n = 0;
f->search = nil;
rpc->qid = f->qid;
rpc->iounit = messagesize-IOHDRSZ;
putfid(fs, f);
return nil;
}
char *
fscreate(Fs *, Fcall *)
{
return Eperm;
}
char*
fsread(Fs *fs, Fcall *rpc)
{
Fid *f;
int n, off, count, len;
f = getfid(fs, rpc->fid);
if(f == nil)
return Enofid;
if(!f->open){
putfid(fs, f);
return Enotopen;
}
count = rpc->count;
off = rpc->offset;
rpc->count = 0;
if(f->qid.path == Qroot){
if(off > 0)
rpc->count = 0;
else
rpc->count = dostat(Qsearch, (uchar*)rpc->data, count);
putfid(fs, f);
if(off == 0 && rpc->count <= BIT16SZ)
return "directory read count too small";
return nil;
}
if(f->qid.path == Qstats){
len = 0;
}else{
for(len = 0; len < count; len += n){
if(f->where == nil || f->search == nil)
break;
if(f->n == 0)
f->where = searchsearch(f->search, f->where, edatabase, &f->n);
n = f->n;
if(n != 0){
if(n > count-len)
n = count-len;
memmove(rpc->data+len, f->where, n);
f->where += n;
f->n -= n;
}
}
}
putfid(fs, f);
rpc->count = len;
return nil;
}
char*
fswrite(Fs *fs, Fcall *rpc)
{
Fid *f;
f = getfid(fs, rpc->fid);
if(f == nil)
return Enofid;
if(!f->open || f->qid.path != Qsearch){
putfid(fs, f);
return Enotopen;
}
if(f->search != nil)
searchfree(f->search);
f->search = searchparse(rpc->data, rpc->data + rpc->count);
if(f->search == nil){
putfid(fs, f);
return Ebadsearch;
}
f->where = database;
f->n = 0;
putfid(fs, f);
return nil;
}
char *
fsclunk(Fs *fs, Fcall *rpc)
{
Fid *f;
f = getfid(fs, rpc->fid);
if(f != nil){
f->attached = 0;
putfid(fs, f);
}
return nil;
}
char *
fsremove(Fs *, Fcall *)
{
return Eperm;
}
char *
fsstat(Fs *fs, Fcall *rpc)
{
Fid *f;
f = getfid(fs, rpc->fid);
if(f == nil)
return Enofid;
rpc->stat = fs->statbuf;
rpc->nstat = dostat(f->qid.path, rpc->stat, sizeof fs->statbuf);
putfid(fs, f);
if(rpc->nstat <= BIT16SZ)
return "stat count too small";
return nil;
}
char *
fswstat(Fs *, Fcall *)
{
return Eperm;
}
int
dostat(int path, uchar *buf, int nbuf)
{
Dir d;
switch(path){
case Qroot:
d.name = ".";
d.mode = DMDIR|0555;
d.qid.type = QTDIR;
break;
case Qsearch:
d.name = "search";
d.mode = 0666;
d.qid.type = QTFILE;
break;
case Qstats:
d.name = "stats";
d.mode = 0666;
d.qid.type = QTFILE;
break;
}
d.qid.path = path;
d.qid.vers = 0;
d.length = 0;
d.uid = d.gid = d.muid = "none";
d.atime = d.mtime = time(nil);
return convD2M(&d, buf, nbuf);
}
char *
urlunesc(char *s, char *e)
{
char *t, *v;
int c, n;
v = emalloc((e - s) + 1);
for(t = v; s < e; s++){
c = *s;
if(c == '%'){
if(s + 2 >= e)
break;
n = s[1];
if(n >= '0' && n <= '9')
n = n - '0';
else if(n >= 'A' && n <= 'F')
n = n - 'A' + 10;
else if(n >= 'a' && n <= 'f')
n = n - 'a' + 10;
else
break;
c = n;
n = s[2];
if(n >= '0' && n <= '9')
n = n - '0';
else if(n >= 'A' && n <= 'F')
n = n - 'A' + 10;
else if(n >= 'a' && n <= 'f')
n = n - 'a' + 10;
else
break;
s += 2;
c = c * 16 + n;
}
*t++ = c;
}
*t = 0;
return v;
}
int
toupper(int c)
{
if(c >= 'a' && c <= 'z')
c += 'A' - 'a';
return c;
}
int
tolower(int c)
{
if(c >= 'A' && c <= 'Z')
c += 'a' - 'A';
return c;
}
void
fatal(char *fmt, ...)
{
va_list arg;
char buf[1024];
write(2, "searchfs: ", 8);
va_start(arg, fmt);
vseprint(buf, buf+1024, fmt, arg);
va_end(arg);
write(2, buf, strlen(buf));
write(2, "\n", 1);
exits(fmt);
}
void *
emalloc(uint n)
{
void *p;
p = malloc(n);
if(p == nil)
fatal("out of memory");
memset(p, 0, n);
return p;
}
void
usage(void)
{
fprint(2, "usage: searchfs [-m mountpoint] [-s srvfile] database\n");
exits("usage");
}
|