/* * This file is part of the UCB release of Plan 9. It is subject to the license * terms in the LICENSE file found in the top-level directory of this * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No * part of the UCB release of Plan 9, including this file, may be copied, * modified, propagated, or distributed except according to the terms contained * in the LICENSE file. */ /* * 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; voidclosewhist(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); }} voidfreepage(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 intgetlock(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 voidgencurrent(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 voidcurrent(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 voidcurrenthist(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);} voidvoidcache(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 intmapcmp(const void *va, const void *vb){ Mapel *a, *b; a = (Mapel*)va; b = (Mapel*)vb; return strcmp(a->s, b->s);} voidclosemap(Map *m){ if(decref(m)==0){ free(m->buf); free(m->el); free(m); }} voidcurrentmap(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);} intallocnum(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 justLines 1–500 of 709. Load more
/* * This file is part of the UCB release of Plan 9. It is subject to the license * terms in the LICENSE file found in the top-level directory of this * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No * part of the UCB release of Plan 9, including this file, may be copied, * modified, propagated, or distributed except according to the terms contained * in the LICENSE file. */ /* * 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