summaryrefslogtreecommitdiff
path: root/sys/src/cmd/wikifs/io.c
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/wikifs/io.c
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/wikifs/io.c')
-rwxr-xr-xsys/src/cmd/wikifs/io.c700
1 files changed, 700 insertions, 0 deletions
diff --git a/sys/src/cmd/wikifs/io.c b/sys/src/cmd/wikifs/io.c
new file mode 100755
index 000000000..43743744e
--- /dev/null
+++ b/sys/src/cmd/wikifs/io.c
@@ -0,0 +1,700 @@
+/*
+ * I/O for a Wiki document set.
+ *
+ * The files are kept in one flat directory.
+ * There are three files for each document:
+ * nnn - current version of the document
+ * nnn.hist - history (all old versions) of the document
+ * append-only
+ * L.nnn - write lock file for the document
+ *
+ * At the moment, since we don't have read/write locks
+ * in the file system, we use the L.nnn file as a read lock too.
+ * It's a hack but there aren't supposed to be many readers
+ * anyway.
+ *
+ * The nnn.hist file is in the format read by Brdwhist.
+ * The nnn file is in that format too, but only contains the
+ * last entry of the nnn.hist file.
+ *
+ * In addition to this set of files, there is an append-only
+ * map file that provides a mapping between numbers and titles.
+ * The map file is a sequence of lines of the form
+ * nnn Title Here
+ * The lock file L.map must be held to add to the end, to
+ * make sure that the numbers are allocated sequentially.
+ *
+ * We assume that writes to the map file will fit in one message,
+ * so that we don't have to read-lock the file.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <String.h>
+#include <thread.h>
+#include "wiki.h"
+
+enum {
+ Nhash = 64,
+ Mcache = 128,
+};
+
+typedef struct Wcache Wcache;
+struct Wcache {
+ int n;
+ ulong use;
+ RWLock;
+ ulong tcurrent;
+ ulong thist;
+ Whist *hist;
+ Whist *current;
+ Qid qid;
+ Qid qidhist;
+ Wcache *hash;
+};
+
+static RWLock cachelock;
+static Wcache *tab[Nhash];
+int ncache;
+
+void
+closewhist(Whist *wh)
+{
+ int i;
+
+ if(wh && decref(wh) == 0){
+ free(wh->title);
+ for(i=0; i<wh->ndoc; i++){
+ free(wh->doc[i].author);
+ free(wh->doc[i].comment);
+ freepage(wh->doc[i].wtxt);
+ }
+ free(wh->doc);
+ free(wh);
+ }
+}
+
+void
+freepage(Wpage *p)
+{
+ Wpage *next;
+
+ for(; p; p=next){
+ next = p->next;
+ free(p->text);
+ free(p->url);
+ free(p);
+ }
+}
+
+static Wcache*
+findcache(int n)
+{
+ Wcache *w;
+
+ for(w=tab[n%Nhash]; w; w=w->hash)
+ if(w->n == n){
+ w->use = time(0);
+ return w;
+ }
+ return nil;
+}
+
+static int
+getlock(char *lock)
+{
+ char buf[ERRMAX];
+ int i, fd;
+ enum { SECS = 200 };
+
+ for(i=0; i<SECS*10; i++){
+ fd = wcreate(lock, ORDWR, DMEXCL|0666);
+ if(fd >= 0)
+ return fd;
+ buf[0] = '\0';
+ rerrstr(buf, sizeof buf);
+ if(strstr(buf, "locked") == nil)
+ break;
+ sleep(1000/10);
+ }
+ werrstr("couldn't acquire lock %s: %r", lock);
+ return -1;
+}
+
+static Whist*
+readwhist(char *file, char *lock, Qid *qid)
+{
+ int lfd;
+ Biobuf *b;
+ Dir *d;
+ Whist *wh;
+
+ if((lfd=getlock(lock)) < 0) // LOG?
+ return nil;
+
+ if(qid){
+ if((d = wdirstat(file)) == nil){
+ close(lfd);
+ return nil;
+ }
+ *qid = d->qid;
+ free(d);
+ }
+
+ if((b = wBopen(file, OREAD)) == nil){ //LOG?
+ close(lfd);
+ return nil;
+ }
+
+ wh = Brdwhist(b);
+
+ Bterm(b);
+ close(lfd);
+ return wh;
+}
+
+static void
+gencurrent(Wcache *w, Qid *q, char *file, char *lock, ulong *t, Whist **wp, int n)
+{
+ Dir *d;
+ Whist *wh;
+
+ if(*wp && *t+Tcache >= time(0))
+ return;
+
+ wlock(w);
+ if(*wp && *t+Tcache >= time(0)){
+ wunlock(w);
+ return;
+ }
+
+ if(((d = wdirstat(file)) == nil) || (d->qid.path==q->path && d->qid.vers==q->vers)){
+ *t = time(0);
+ wunlock(w);
+ free(d);
+ return;
+ }
+
+ free(d);
+ if(wh = readwhist(file, lock, q)){
+ wh->n = n;
+ *t = time(0);
+ closewhist(*wp);
+ *wp = wh;
+ }
+else fprint(2, "error file=%s lock=%s %r\n", file, lock);
+ wunlock(w);
+}
+
+static void
+current(Wcache *w)
+{
+ char tmp[40];
+ char tmplock[40];
+
+ sprint(tmplock, "d/L.%d", w->n);
+ sprint(tmp, "d/%d", w->n);
+ gencurrent(w, &w->qid, tmp, tmplock, &w->tcurrent, &w->current, w->n);
+}
+
+static void
+currenthist(Wcache *w)
+{
+ char hist[40], lock[40];
+
+ sprint(hist, "d/%d.hist", w->n);
+ sprint(lock, "d/L.%d", w->n);
+
+ gencurrent(w, &w->qidhist, hist, lock, &w->thist, &w->hist, w->n);
+}
+
+void
+voidcache(int n)
+{
+ Wcache *c;
+
+ rlock(&cachelock);
+ if(c = findcache(n)){
+ wlock(c);
+ c->tcurrent = 0;
+ c->thist = 0;
+ /* aggressively free memory */
+ closewhist(c->hist);
+ c->hist = nil;
+ closewhist(c->current);
+ c->current = nil;
+ wunlock(c);
+ }
+ runlock(&cachelock);
+}
+
+static Whist*
+getcache(int n, int hist)
+{
+ int i, isw;
+ ulong t;
+ Wcache *c, **cp, **evict;
+ Whist *wh;
+
+ isw = 0;
+ rlock(&cachelock);
+ if(c = findcache(n)){
+ Found:
+ current(c);
+ if(hist)
+ currenthist(c);
+ rlock(c);
+ if(hist)
+ wh = c->hist;
+ else
+ wh = c->current;
+ if(wh)
+ incref(wh);
+ runlock(c);
+ if(isw)
+ wunlock(&cachelock);
+ else
+ runlock(&cachelock);
+ return wh;
+ }
+ runlock(&cachelock);
+
+ wlock(&cachelock);
+ if(c = findcache(n)){
+ isw = 1; /* better to downgrade lock but can't */
+ goto Found;
+ }
+
+ if(ncache < Mcache){
+ Alloc:
+ c = emalloc(sizeof *c);
+ ncache++;
+ }else{
+ /* find something to evict. */
+ t = ~0;
+ evict = nil;
+ for(i=0; i<Nhash; i++){
+ for(cp=&tab[i], c=*cp; c; cp=&c->hash, c=*cp){
+ if(c->use < t
+ && (!c->hist || c->hist->ref==1)
+ && (!c->current || c->current->ref==1)){
+ evict = cp;
+ t = c->use;
+ }
+ }
+ }
+
+ if(evict == nil){
+ fprint(2, "wikifs: nothing to evict\n");
+ goto Alloc;
+ }
+
+ c = *evict;
+ *evict = c->hash;
+
+ closewhist(c->current);
+ closewhist(c->hist);
+ memset(c, 0, sizeof *c);
+ }
+
+ c->n = n;
+ c->hash = tab[n%Nhash];
+ tab[n%Nhash] = c;
+ isw = 1;
+ goto Found;
+}
+
+Whist*
+getcurrent(int n)
+{
+ return getcache(n, 0);
+}
+
+Whist*
+gethistory(int n)
+{
+ return getcache(n, 1);
+}
+
+RWLock maplock;
+Map *map;
+
+static int
+mapcmp(const void *va, const void *vb)
+{
+ Mapel *a, *b;
+
+ a = (Mapel*)va;
+ b = (Mapel*)vb;
+
+ return strcmp(a->s, b->s);
+}
+
+void
+closemap(Map *m)
+{
+ if(decref(m)==0){
+ free(m->buf);
+ free(m->el);
+ free(m);
+ }
+}
+
+void
+currentmap(int force)
+{
+ char *p, *q, *r;
+ int lfd, fd, m, n;
+ Dir *d;
+ Map *nmap;
+ char *err = nil;
+
+ lfd = -1;
+ fd = -1;
+ d = nil;
+ nmap = nil;
+ if(!force && map && map->t+Tcache >= time(0))
+ return;
+
+ wlock(&maplock);
+ if(!force && map && map->t+Tcache >= time(0))
+ goto Return;
+
+ if((lfd = getlock("d/L.map")) < 0){
+ err = "can't lock";
+ goto Return;
+ }
+
+ if((d = wdirstat("d/map")) == nil)
+ goto Return;
+
+ if(map && d->qid.path == map->qid.path && d->qid.vers == map->qid.vers){
+ map->t = time(0);
+ goto Return;
+ }
+
+ if(d->length > Maxmap){
+ //LOG
+ err = "too long";
+ goto Return;
+ }
+
+ if((fd = wopen("d/map", OREAD)) < 0)
+ goto Return;
+
+ nmap = emalloc(sizeof *nmap);
+ nmap->buf = emalloc(d->length+1);
+ n = readn(fd, nmap->buf, d->length);
+ if(n != d->length){
+ err = "bad length";
+ goto Return;
+ }
+ nmap->buf[n] = '\0';
+
+ n = 0;
+ for(p=nmap->buf; p; p=strchr(p+1, '\n'))
+ n++;
+ nmap->el = emalloc(n*sizeof(nmap->el[0]));
+
+ m = 0;
+ for(p=nmap->buf; p && *p && m < n; p=q){
+ if(q = strchr(p+1, '\n'))
+ *q++ = '\0';
+ nmap->el[m].n = strtol(p, &r, 10);
+ if(*r == ' ')
+ r++;
+ else
+ {}//LOG?
+ nmap->el[m].s = strcondense(r, 1);
+ m++;
+ }
+ //LOG if m != n
+
+ nmap->qid = d->qid;
+ nmap->t = time(0);
+ nmap->nel = m;
+ qsort(nmap->el, nmap->nel, sizeof(nmap->el[0]), mapcmp);
+ if(map)
+ closemap(map);
+ map = nmap;
+ incref(map);
+ nmap = nil;
+
+Return:
+ free(d);
+ if(nmap){
+ free(nmap->el);
+ free(nmap->buf);
+ free(nmap);
+ }
+ if(map == nil)
+ sysfatal("cannot get map: %s: %r", err);
+
+ if(fd >= 0)
+ close(fd);
+ if(lfd >= 0)
+ close(lfd);
+ wunlock(&maplock);
+}
+
+int
+allocnum(char *title, int mustbenew)
+{
+ char *p, *q;
+ int lfd, fd, n;
+ Biobuf b;
+
+ if(strcmp(title, "map")==0 || strcmp(title, "new")==0){
+ werrstr("reserved title name");
+ return -1;
+ }
+
+ if(title[0]=='\0' || strpbrk(title, "/<>:?")){
+ werrstr("invalid character in name");
+ return -1;
+ }
+ if((n = nametonum(title)) >= 0){
+ if(mustbenew){
+ werrstr("duplicate title");
+ return -1;
+ }
+ return n;
+ }
+
+ title = estrdup(title);
+ strcondense(title, 1);
+ strlower(title);
+ if(strchr(title, '\n') || strlen(title) > 200){
+ werrstr("bad title");
+ free(title);
+ return -1;
+ }
+
+ if((lfd = getlock("d/L.map")) < 0){
+ free(title);
+ return -1;
+ }
+
+ if((fd = wopen("d/map", ORDWR)) < 0){ // LOG?
+ close(lfd);
+ free(title);
+ return -1;
+ }
+
+ /*
+ * What we really need to do here is make sure the
+ * map is up-to-date, then make sure the title isn't
+ * taken, and then add it, all without dropping the locks.
+ *
+ * This turns out to be a mess when you start adding
+ * all the necessary dolock flags, so instead we just
+ * read through the file ourselves, and let our
+ * map catch up on its own.
+ */
+ Binit(&b, fd, OREAD);
+ n = 0;
+ while(p = Brdline(&b, '\n')){
+ p[Blinelen(&b)-1] = '\0';
+ n = atoi(p)+1;
+ q = strchr(p, ' ');
+ if(q == nil)
+ continue;
+ if(strcmp(q+1, title) == 0){
+ free(title);
+ close(fd);
+ close(lfd);
+ if(mustbenew){
+ werrstr("duplicate title");
+ return -1;
+ }else
+ return n;
+ }
+ }
+
+ seek(fd, 0, 2); /* just in case it's not append only */
+ fprint(fd, "%d %s\n", n, title);
+ close(fd);
+ close(lfd);
+ free(title);
+ /* kick the map */
+ currentmap(1);
+ return n;
+}
+
+int
+nametonum(char *s)
+{
+ char *p;
+ int i, lo, hi, m, rv;
+
+ s = estrdup(s);
+ strlower(s);
+ for(p=s; *p; p++)
+ if(*p=='_')
+ *p = ' ';
+
+ currentmap(0);
+ rlock(&maplock);
+ lo = 0;
+ hi = map->nel;
+ while(hi-lo > 1){
+ m = (lo+hi)/2;
+ i = strcmp(s, map->el[m].s);
+ if(i < 0)
+ hi = m;
+ else
+ lo = m;
+ }
+ if(hi-lo == 1 && strcmp(s, map->el[lo].s)==0)
+ rv = map->el[lo].n;
+ else
+ rv = -1;
+ runlock(&maplock);
+ free(s);
+ return rv;
+}
+
+char*
+numtoname(int n)
+{
+ int i;
+ char *s;
+
+ currentmap(0);
+ rlock(&maplock);
+ for(i=0; i<map->nel; i++){
+ if(map->el[i].n==n)
+ break;
+ }
+ if(i==map->nel){
+ runlock(&maplock);
+ return nil;
+ }
+ s = estrdup(map->el[i].s);
+ runlock(&maplock);
+ return s;
+}
+
+Whist*
+getcurrentbyname(char *s)
+{
+ int n;
+
+ if((n = nametonum(s)) < 0)
+ return nil;
+ return getcache(n, 0);
+}
+
+static String*
+Brdstring(Biobuf *b)
+{
+ long len;
+ String *s;
+ Dir *d;
+
+ d = dirfstat(Bfildes(b));
+ if (d == nil) /* shouldn't happen, we just opened it */
+ len = 0;
+ else
+ len = d->length;
+ free(d);
+ s = s_newalloc(len);
+ s_read(b, s, len);
+ return s;
+}
+
+/*
+ * Attempt to install a new page. If t==0 we are creating.
+ * Otherwise, we are editing and t must be set to the current
+ * version (t is the version we started with) to avoid conflicting
+ * writes.
+ *
+ * If there is a conflicting write, we still write the page to
+ * the history file, but mark it as a failed write.
+ */
+int
+writepage(int num, ulong t, String *s, char *title)
+{
+ char tmp[40], tmplock[40], err[ERRMAX], hist[40], *p;
+ int conflict, lfd, fd;
+ Biobuf *b;
+ String *os;
+
+ sprint(tmp, "d/%d", num);
+ sprint(tmplock, "d/L.%d", num);
+ sprint(hist, "d/%d.hist", num);
+ if((lfd = getlock(tmplock)) < 0)
+ return -1;
+
+ conflict = 0;
+ if(b = wBopen(tmp, OREAD)){
+ Brdline(b, '\n'); /* title */
+ if(p = Brdline(b, '\n')) /* version */
+ p[Blinelen(b)-1] = '\0';
+ if(p==nil || p[0] != 'D'){
+ snprint(err, sizeof err, "bad format in extant file");
+ conflict = 1;
+ }else if(strtoul(p+1, 0, 0) != t){
+ os = Brdstring(b); /* why read the whole file? */
+ p = strchr(s_to_c(s), '\n');
+ if(p!=nil && strcmp(p+1, s_to_c(os))==0){ /* ignore dup write */
+ close(lfd);
+ s_free(os);
+ Bterm(b);
+ return 0;
+ }
+ s_free(os);
+ snprint(err, sizeof err, "update conflict %lud != %s", t, p+1);
+ conflict = 1;
+ }
+ Bterm(b);
+ }else{
+ if(t != 0){
+ close(lfd);
+ werrstr("did not expect to create");
+ return -1;
+ }
+ }
+
+ if((fd = wopen(hist, OWRITE)) < 0){
+ if((fd = wcreate(hist, OWRITE, 0666)) < 0){
+ close(lfd);
+ return -1;
+ }else
+ fprint(fd, "%s\n", title);
+ }
+ if(seek(fd, 0, 2) < 0
+ || (conflict && write(fd, "X\n", 2) != 2)
+ || write(fd, s_to_c(s), s_len(s)) != s_len(s)){
+ close(fd);
+ close(lfd);
+ return -1;
+ }
+ close(fd);
+
+ if(conflict){
+ close(lfd);
+ voidcache(num);
+ werrstr(err);
+ return -1;
+ }
+
+ if((fd = wcreate(tmp, OWRITE, 0666)) < 0){
+ close(lfd);
+ voidcache(num);
+ return -1;
+ }
+ if(write(fd, title, strlen(title)) != strlen(title)
+ || write(fd, "\n", 1) != 1
+ || write(fd, s_to_c(s), s_len(s)) != s_len(s)){
+ close(fd);
+ close(lfd);
+ voidcache(num);
+ return -1;
+ }
+ close(fd);
+ close(lfd);
+ voidcache(num);
+ return 0;
+}