2 * I/O for a Wiki document set.
4 * The files are kept in one flat directory.
5 * There are three files for each document:
6 * nnn - current version of the document
7 * nnn.hist - history (all old versions) of the document
9 * L.nnn - write lock file for the document
11 * At the moment, since we don't have read/write locks
12 * in the file system, we use the L.nnn file as a read lock too.
13 * It's a hack but there aren't supposed to be many readers
16 * The nnn.hist file is in the format read by Brdwhist.
17 * The nnn file is in that format too, but only contains the
18 * last entry of the nnn.hist file.
20 * In addition to this set of files, there is an append-only
21 * map file that provides a mapping between numbers and titles.
22 * The map file is a sequence of lines of the form
24 * The lock file L.map must be held to add to the end, to
25 * make sure that the numbers are allocated sequentially.
27 * We assume that writes to the map file will fit in one message,
28 * so that we don't have to read-lock the file.
43 typedef struct Wcache Wcache;
57 static RWLock cachelock;
58 static Wcache *tab[Nhash];
66 if(wh && decref(wh) == 0){
68 for(i=0; i<wh->ndoc; i++){
69 free(wh->doc[i].author);
70 free(wh->doc[i].comment);
71 freepage(wh->doc[i].wtxt);
96 for(w=tab[n%Nhash]; w; w=w->hash)
111 for(i=0; i<SECS*10; i++){
112 fd = wcreate(lock, ORDWR, DMEXCL|0666);
116 rerrstr(buf, sizeof buf);
117 if(strstr(buf, "locked") == nil)
121 werrstr("couldn't acquire lock %s: %r", lock);
126 readwhist(char *file, char *lock, Qid *qid)
133 if((lfd=getlock(lock)) < 0) // LOG?
137 if((d = wdirstat(file)) == nil){
145 if((b = wBopen(file, OREAD)) == nil){ //LOG?
158 gencurrent(Wcache *w, Qid *q, char *file, char *lock, ulong *t, Whist **wp, int n)
163 if(*wp && *t+Tcache >= time(0))
167 if(*wp && *t+Tcache >= time(0)){
172 if(((d = wdirstat(file)) == nil) || (d->qid.path==q->path && d->qid.vers==q->vers)){
180 if(wh = readwhist(file, lock, q)){
186 else fprint(2, "error file=%s lock=%s %r\n", file, lock);
196 sprint(tmplock, "d/L.%d", w->n);
197 sprint(tmp, "d/%d", w->n);
198 gencurrent(w, &w->qid, tmp, tmplock, &w->tcurrent, &w->current, w->n);
202 currenthist(Wcache *w)
204 char hist[40], lock[40];
206 sprint(hist, "d/%d.hist", w->n);
207 sprint(lock, "d/L.%d", w->n);
209 gencurrent(w, &w->qidhist, hist, lock, &w->thist, &w->hist, w->n);
218 if(c = findcache(n)){
222 /* aggressively free memory */
225 closewhist(c->current);
233 getcache(int n, int hist)
237 Wcache *c, **cp, **evict;
242 if(c = findcache(n)){
264 if(c = findcache(n)){
265 isw = 1; /* better to downgrade lock but can't */
271 c = emalloc(sizeof *c);
274 /* find something to evict. */
277 for(i=0; i<Nhash; i++){
278 for(cp=&tab[i], c=*cp; c; cp=&c->hash, c=*cp){
280 && (!c->hist || c->hist->ref==1)
281 && (!c->current || c->current->ref==1)){
289 fprint(2, "wikifs: nothing to evict\n");
296 closewhist(c->current);
298 memset(c, 0, sizeof *c);
302 c->hash = tab[n%Nhash];
311 return getcache(n, 0);
317 return getcache(n, 1);
324 mapcmp(const void *va, const void *vb)
331 return strcmp(a->s, b->s);
345 currentmap(int force)
357 if(!force && map && map->t+Tcache >= time(0))
361 if(!force && map && map->t+Tcache >= time(0))
364 if((lfd = getlock("d/L.map")) < 0){
369 if((d = wdirstat("d/map")) == nil)
372 if(map && d->qid.path == map->qid.path && d->qid.vers == map->qid.vers){
377 if(d->length > Maxmap){
383 if((fd = wopen("d/map", OREAD)) < 0)
386 nmap = emalloc(sizeof *nmap);
387 nmap->buf = emalloc(d->length+1);
388 n = readn(fd, nmap->buf, d->length);
396 for(p=nmap->buf; p; p=strchr(p+1, '\n'))
398 nmap->el = emalloc(n*sizeof(nmap->el[0]));
401 for(p=nmap->buf; p && *p && m < n; p=q){
402 if(q = strchr(p+1, '\n'))
404 nmap->el[m].n = strtol(p, &r, 10);
409 nmap->el[m].s = strcondense(r, 1);
417 qsort(nmap->el, nmap->nel, sizeof(nmap->el[0]), mapcmp);
432 sysfatal("cannot get map: %s: %r", err);
442 allocnum(char *title, int mustbenew)
448 if(strcmp(title, "map")==0 || strcmp(title, "new")==0){
449 werrstr("reserved title name");
453 if(title[0]=='\0' || strpbrk(title, "/<>:?")){
454 werrstr("invalid character in name");
457 if((n = nametonum(title)) >= 0){
459 werrstr("duplicate title");
465 title = estrdup(title);
466 strcondense(title, 1);
468 if(strchr(title, '\n') || strlen(title) > 200){
469 werrstr("bad title");
474 if((lfd = getlock("d/L.map")) < 0){
479 if((fd = wopen("d/map", ORDWR)) < 0){ // LOG?
486 * What we really need to do here is make sure the
487 * map is up-to-date, then make sure the title isn't
488 * taken, and then add it, all without dropping the locks.
490 * This turns out to be a mess when you start adding
491 * all the necessary dolock flags, so instead we just
492 * read through the file ourselves, and let our
493 * map catch up on its own.
495 Binit(&b, fd, OREAD);
497 while(p = Brdline(&b, '\n')){
498 p[Blinelen(&b)-1] = '\0';
503 if(strcmp(q+1, title) == 0){
508 werrstr("duplicate title");
515 seek(fd, 0, 2); /* just in case it's not append only */
516 fprint(fd, "%d %s\n", n, title);
529 int i, lo, hi, m, rv;
543 i = strcmp(s, map->el[m].s);
549 if(hi-lo == 1 && strcmp(s, map->el[lo].s)==0)
566 for(i=0; i<map->nel; i++){
574 s = estrdup(map->el[i].s);
580 getcurrentbyname(char *s)
584 if((n = nametonum(s)) < 0)
586 return getcache(n, 0);
596 d = dirfstat(Bfildes(b));
597 if (d == nil) /* shouldn't happen, we just opened it */
608 * Attempt to install a new page. If t==0 we are creating.
609 * Otherwise, we are editing and t must be set to the current
610 * version (t is the version we started with) to avoid conflicting
613 * If there is a conflicting write, we still write the page to
614 * the history file, but mark it as a failed write.
617 writepage(int num, ulong t, String *s, char *title)
619 char tmp[40], tmplock[40], err[ERRMAX], hist[40], *p;
620 int conflict, lfd, fd;
624 sprint(tmp, "d/%d", num);
625 sprint(tmplock, "d/L.%d", num);
626 sprint(hist, "d/%d.hist", num);
627 if((lfd = getlock(tmplock)) < 0)
631 if(b = wBopen(tmp, OREAD)){
632 Brdline(b, '\n'); /* title */
633 if(p = Brdline(b, '\n')) /* version */
634 p[Blinelen(b)-1] = '\0';
635 if(p==nil || p[0] != 'D'){
636 snprint(err, sizeof err, "bad format in extant file");
638 }else if(strtoul(p+1, 0, 0) != t){
639 os = Brdstring(b); /* why read the whole file? */
640 p = strchr(s_to_c(s), '\n');
641 if(p!=nil && strcmp(p+1, s_to_c(os))==0){ /* ignore dup write */
648 snprint(err, sizeof err, "update conflict %lud != %s", t, p+1);
655 werrstr("did not expect to create");
660 if((fd = wopen(hist, OWRITE)) < 0){
661 if((fd = wcreate(hist, OWRITE, 0666)) < 0){
665 fprint(fd, "%s\n", title);
667 if(seek(fd, 0, 2) < 0
668 || (conflict && write(fd, "X\n", 2) != 2)
669 || write(fd, s_to_c(s), s_len(s)) != s_len(s)){
679 errstr(err, sizeof err);
683 if((fd = wcreate(tmp, OWRITE, 0666)) < 0){
688 if(write(fd, title, strlen(title)) != strlen(title)
689 || write(fd, "\n", 1) != 1
690 || write(fd, s_to_c(s), s_len(s)) != s_len(s)){