#include #include #include #include #include #include #include <9p.h> #include #include "trie.h" #include "util.h" #include "query.h" typedef struct Query Query; struct Query{ char* text; Texpr* expr; // non-nil after query write completed }; Trie* trie; File* ctlf; char* tfname; char* ttfname; static void fscreate(Req* r) { File* file; Query* q; char* name; char* uid; int mode; File* f; file = r->fid->file; name = r->ifcall.name; uid = r->fid->uid; mode = r->fid->file->mode & 0x777 & r->ifcall.perm; mode |= (r->ifcall.perm & ~0x777); if(mode&DMDIR){ respond(r, "queries cannot be directories"); return; } if(f = createfile(file, name, uid, mode, nil)){ q = emalloc9p(sizeof *q); q->text = estrdup9p(""); q->expr = nil; f->aux = q; closefile(r->fid->file); r->fid->file = f; r->ofcall.qid = f->qid; respond(r, nil); } else responderror(r); } void tag(Trie* t, char* s, uvlong qid) { int l; char* q; Rune r; int nc; l = strlen(s); // don't use and and two char words as tags // don't use uttlerly long words, probably not tags. if(l < 3 || l > 40) return; // don't use as tags things other than alphanumeric. for(q = s; *q != 0; ){ nc = chartorune(&r, q); if(!isalpharune(r) && !isdigit(r)) return; q += nc; } trieput(t, s, qid); } static void ctlwrite(Req* r) { char buf[1024]; char* toks[512]; int fd; Biobuf bout; int ntoks; long count; uvlong qid; int i; count = r->ifcall.count; if(count > sizeof(buf)-1) count=sizeof(buf)-1; r->ofcall.count = count; memmove(buf, r->ifcall.data, count); buf[count] = 0; ntoks = tokenize(buf, toks, nelem(toks)); if(ntoks < 1){ respond(r, "null ctl"); return; } if(strcmp(toks[0], "tag") == 0){ if(ntoks < 3){ respond(r, "ctl usage: tag qid tag..."); return; } qid = strtoull(toks[1], nil, 16); for(i = 2; i < ntoks; i++) tag(trie, toks[i], qid); } else if(strcmp(toks[0], "sync") == 0){ fd = create(ttfname, OWRITE, 0664); if(fd < 0){ responderror(r); return; } Binit(&bout, fd, OWRITE); if(wrtrie(&bout, trie) < 0){ close(fd); remove(ttfname); responderror(r); return; } Bterm(&bout); close(fd); if(rename(tfname, ttfname) < 0){ responderror(r); remove(ttfname); return; } } else { respond(r, "bad ctl request"); return; } respond(r, nil); } static void fswrite(Req* r) { Query* q; File* f; long count; char* ntext; int l; if(r->fid->qid.type&QTDIR){ respond(r, "bug: write on dir"); return; } f = r->fid->file; if(f == ctlf){ ctlwrite(r); return; } count = r->ifcall.count; r->ofcall.count = count; q = f->aux; if(q->expr != nil){ // a previous query was made. start another. freeexpr(q->expr); free(q->text); q->expr = nil; q->text = nil; } /* append text to query text, ignore offset. */ l = strlen(q->text); ntext = emalloc9p(l + count + 1); strcpy(ntext, q->text); memmove(ntext+l, r->ifcall.data, count); ntext[l+count]=0; free(q->text); q->text = ntext; respond(r, nil); } static void ctlread(Req* r) { char buf[4096]; char* s; int i; s = seprint(buf, buf+sizeof(buf), "trie %s\n", tfname); s = seprint(s, buf+sizeof(buf), "prefixes %ld\n", ntries); s = seprint(s, buf+sizeof(buf), "tags %ld\n", nvaltries); s = seprint(s, buf+sizeof(buf), "max entry %ld\n", maxvals); if(trie->nents > 0){ s = seprint(s, buf+sizeof(buf), "%d root runes [", trie->nents); for(i = 0; i < trie->nents; i++) s = seprint(s, buf+sizeof(buf), "%C", trie->ents[i].r); s = seprint(s, buf+sizeof(buf), "]\n"); } if(roott.nents > 0){ s = seprint(s, buf+sizeof(buf), "%d used runes [", roott.nents); for(i = 0; i < roott.nents; i++) s = seprint(s, buf+sizeof(buf), "%C", roott.ents[i].r); seprint(s, buf+sizeof(buf), "]\n"); } readstr(r, buf); respond(r, nil); } static void fsread(Req* r) { Query* q; char** toks; int ntoks; int atoks; char* s; int pos; File* f; if(r->fid->qid.type&QTDIR){ respond(r, "bug: write on dir"); return; } f = r->fid->file; if(f == ctlf){ ctlread(r); return; } q = f->aux; /* The first read process the query. * Further reads just retrieve more data, * if any. */ if(q->expr == nil){ atoks = 512; toks = emalloc9p(atoks*sizeof(char*)); do { s = estrdup9p(q->text); ntoks = tokenize(s, toks, atoks); if(ntoks == atoks){ atoks += 512; toks = erealloc9p(toks, atoks*sizeof(char*)); free(s); } }while(ntoks == atoks); pos = 0; if(chatty9p) fprint(2, "compiling %s (%d toks)\n", q->text, ntoks); q->expr = parseexpr(ntoks, toks, &pos); if(q->expr == nil){ free(s); free(toks); respond(r, "syntax error"); return; } if(chatty9p) fprint(2, "evaluating %s (%d toks)\n", q->text, ntoks); evalexpr(trie, q->expr); free(s); free(toks); free(q->text); /* smprintexprval limits the total output to * at most 64K of text */ q->text = smprintexprval(q->expr); if(chatty9p) fprint(2, "result is [%s]\n", q->text); } /* After the query is process, * q->text holds the reply. */ readstr(r, q->text); respond(r, nil); } static void fsclunk(Fid* fid) { Query* q; File* f; f = fid->file; if(f == nil) return; q = f->aux; if(q == nil) return; if(q->expr != nil){ /* the query was already made, destroy the file. * * We must incref the file because * removefile assumes that we hold the * reference given to it, and we do not. * We just want the file removed from the tree. */ free(q->text); freeexpr(q->expr); free(q); f->aux = nil; incref(f); removefile(f); } } static Srv sfs= { .create = fscreate, .read = fsread, .write = fswrite, .destroyfid = fsclunk, }; char* srvname(char* triefname) { char* s; char* r; s = strrchr(triefname, '/'); if(s != nil) s++; else s = triefname; s = estrdup(s); r = strstr(s, ".trie.db"); if(r != nil) strcpy(r, ".tagfs"); else s = smprint("%s.tagfs", s); return s; } void usage(void) { fprint(2, "usage: %s [-abcD] [-s srv] [-m mnt] trie\n", argv0); exits("usage"); } void main(int argc, char* argv[]) { char* mnt; char* srv; int mflag; Biobuf* b; char* user; srv = nil; mnt = nil; mflag = MREPL|MCREATE; ARGBEGIN{ case 'a': mflag = MAFTER|MCREATE; break; case 'b': mflag = MBEFORE|MCREATE; break; case 'c': mflag = MREPL|MCREATE; break; case 's': srv = EARGF(usage()); break; case 'm': mnt = EARGF(usage()); break; case 'D': debug = 1; chatty9p++; break; default: usage(); }ARGEND; if(argc != 1) usage(); tfname = argv[0]; ttfname = smprint("%s.new", tfname); b = Bopen(tfname, OREAD); if(b == nil) sysfatal("%s: %r", tfname); trie = rdtrie(b); Bterm(b); if(trie == nil) sysfatal("%s: %r", tfname); if(srv == nil && mnt == nil){ mnt = "/mnt/tags"; srv = srvname(tfname); } if(!chatty9p) rfork(RFNOTEG); sfs.tree = alloctree(nil, nil, DMDIR|0777, nil); user = getuser(); ctlf = createfile(sfs.tree->root, "ctl", user, 0666, nil); postmountsrv(&sfs, srv, mnt, mflag); exits(nil); }