123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700 |
- /*
- * 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");
- 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;
- }
|