Warning: Attempt to read property "date" on null in /usr/local/www/websvn.planix.org/blame.php on line 247

Warning: Attempt to read property "msg" on null in /usr/local/www/websvn.planix.org/blame.php on line 247
WebSVN – planix.SVN – Blame – /os/branches/feature_unix/sys/src/cmd/wikifs/io.c – Rev 2

Subversion Repositories planix.SVN

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 1
/*
2
 * I/O for a Wiki document set.
3
 * 
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
8
 *		append-only
9
 *	L.nnn	- write lock file for the document
10
 * 
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
14
 * anyway.
15
 *
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.
19
 *
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
23
 *	nnn Title Here
24
 * The lock file L.map must be held to add to the end, to
25
 * make sure that the numbers are allocated sequentially.
26
 * 
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.
29
 */
30
 
31
#include <u.h>
32
#include <libc.h>
33
#include <bio.h>
34
#include <String.h>
35
#include <thread.h>
36
#include "wiki.h"
37
 
38
enum {
39
	Nhash = 64,
40
	Mcache = 128,
41
};
42
 
43
typedef struct Wcache Wcache;
44
struct Wcache {
45
	int n;
46
	ulong use;
47
	RWLock;
48
	ulong tcurrent;
49
	ulong thist;
50
	Whist *hist;
51
	Whist *current;
52
	Qid qid;
53
	Qid qidhist;
54
	Wcache *hash;
55
};
56
 
57
static RWLock cachelock;
58
static Wcache *tab[Nhash];
59
int ncache;
60
 
61
void
62
closewhist(Whist *wh)
63
{
64
	int i;
65
 
66
	if(wh && decref(wh) == 0){
67
		free(wh->title);
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);
72
		}
73
		free(wh->doc);
74
		free(wh);
75
	}
76
}
77
 
78
void
79
freepage(Wpage *p)
80
{
81
	Wpage *next;
82
 
83
	for(; p; p=next){
84
		next = p->next;
85
		free(p->text);
86
		free(p->url);
87
		free(p);
88
	}
89
}
90
 
91
static Wcache*
92
findcache(int n)
93
{
94
	Wcache *w;
95
 
96
	for(w=tab[n%Nhash]; w; w=w->hash)
97
		if(w->n == n){
98
			w->use = time(0);
99
			return w;
100
		}
101
	return nil;
102
}
103
 
104
static int
105
getlock(char *lock)
106
{
107
	char buf[ERRMAX];
108
	int i, fd;
109
	enum { SECS = 200 };
110
 
111
	for(i=0; i<SECS*10; i++){
112
		fd = wcreate(lock, ORDWR, DMEXCL|0666);
113
		if(fd >= 0)
114
			return fd;
115
		buf[0] = '\0';
116
		rerrstr(buf, sizeof buf);
117
		if(strstr(buf, "locked") == nil)
118
			break;
119
		sleep(1000/10);
120
	}
121
	werrstr("couldn't acquire lock %s: %r", lock);
122
	return -1;
123
}
124
 
125
static Whist*
126
readwhist(char *file, char *lock, Qid *qid)
127
{
128
	int lfd;
129
	Biobuf *b;
130
	Dir *d;
131
	Whist *wh;
132
 
133
	if((lfd=getlock(lock)) < 0)	// LOG?
134
		return nil;
135
 
136
	if(qid){
137
		if((d = wdirstat(file)) == nil){
138
			close(lfd);
139
			return nil;
140
		}
141
		*qid = d->qid;
142
		free(d);
143
	}
144
 
145
	if((b = wBopen(file, OREAD)) == nil){	//LOG?
146
		close(lfd);
147
		return nil;
148
	}
149
 
150
	wh = Brdwhist(b);
151
 
152
	Bterm(b);
153
	close(lfd);
154
	return wh;
155
}
156
 
157
static void
158
gencurrent(Wcache *w, Qid *q, char *file, char *lock, ulong *t, Whist **wp, int n)
159
{
160
	Dir *d;
161
	Whist *wh;
162
 
163
	if(*wp && *t+Tcache >= time(0))
164
		return;
165
 
166
	wlock(w);
167
	if(*wp && *t+Tcache >= time(0)){
168
		wunlock(w);
169
		return;
170
	}
171
 
172
	if(((d = wdirstat(file)) == nil) || (d->qid.path==q->path && d->qid.vers==q->vers)){
173
		*t = time(0);
174
		wunlock(w);
175
		free(d);
176
		return;
177
	}
178
 
179
	free(d);
180
	if(wh = readwhist(file, lock, q)){
181
		wh->n = n;
182
		*t = time(0);
183
		closewhist(*wp);
184
		*wp = wh;
185
	}
186
else fprint(2, "error file=%s lock=%s %r\n", file, lock);
187
	wunlock(w);
188
}
189
 
190
static void
191
current(Wcache *w)
192
{
193
	char tmp[40];
194
	char tmplock[40];
195
 
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);
199
}
200
 
201
static void
202
currenthist(Wcache *w)
203
{
204
	char hist[40], lock[40];
205
 
206
	sprint(hist, "d/%d.hist", w->n);
207
	sprint(lock, "d/L.%d", w->n);
208
 
209
	gencurrent(w, &w->qidhist, hist, lock, &w->thist, &w->hist, w->n);
210
}
211
 
212
void
213
voidcache(int n)
214
{
215
	Wcache *c;
216
 
217
	rlock(&cachelock);
218
	if(c = findcache(n)){
219
		wlock(c);
220
		c->tcurrent = 0;
221
		c->thist = 0;
222
		/* aggressively free memory */
223
		closewhist(c->hist);
224
		c->hist = nil;
225
		closewhist(c->current);
226
		c->current = nil;
227
		wunlock(c);
228
	}
229
	runlock(&cachelock);
230
}
231
 
232
static Whist*
233
getcache(int n, int hist)
234
{
235
	int i, isw;
236
	ulong t;
237
	Wcache *c, **cp, **evict;
238
	Whist *wh;
239
 
240
	isw = 0;
241
	rlock(&cachelock);
242
	if(c = findcache(n)){
243
	Found:
244
		current(c);
245
		if(hist)
246
			currenthist(c);
247
		rlock(c);
248
		if(hist)
249
			wh = c->hist;
250
		else
251
			wh = c->current;
252
		if(wh)
253
			incref(wh);
254
		runlock(c);
255
		if(isw)
256
			wunlock(&cachelock);
257
		else
258
			runlock(&cachelock);
259
		return wh;
260
	}
261
	runlock(&cachelock);
262
 
263
	wlock(&cachelock);
264
	if(c = findcache(n)){
265
		isw = 1;	/* better to downgrade lock but can't */
266
		goto Found;
267
	}
268
 
269
	if(ncache < Mcache){
270
	Alloc:
271
		c = emalloc(sizeof *c);
272
		ncache++;
273
	}else{
274
		/* find something to evict. */
275
		t = ~0;
276
		evict = nil;
277
		for(i=0; i<Nhash; i++){
278
			for(cp=&tab[i], c=*cp; c; cp=&c->hash, c=*cp){
279
				if(c->use < t
280
				&& (!c->hist || c->hist->ref==1)
281
				&& (!c->current || c->current->ref==1)){
282
					evict = cp;
283
					t = c->use;
284
				}
285
			}
286
		}
287
 
288
		if(evict == nil){
289
			fprint(2, "wikifs: nothing to evict\n");
290
			goto Alloc;
291
		}
292
 
293
		c = *evict;
294
		*evict = c->hash;
295
 
296
		closewhist(c->current);
297
		closewhist(c->hist);
298
		memset(c, 0, sizeof *c);
299
	}
300
 
301
	c->n = n;
302
	c->hash = tab[n%Nhash];
303
	tab[n%Nhash] = c;
304
	isw = 1;
305
	goto Found;
306
}
307
 
308
Whist*
309
getcurrent(int n)
310
{
311
	return getcache(n, 0);
312
}
313
 
314
Whist*
315
gethistory(int n)
316
{
317
	return getcache(n, 1);
318
}
319
 
320
RWLock maplock;
321
Map *map;
322
 
323
static int
324
mapcmp(const void *va, const void *vb)
325
{
326
	Mapel *a, *b;
327
 
328
	a = (Mapel*)va;
329
	b = (Mapel*)vb;
330
 
331
	return strcmp(a->s, b->s);
332
}
333
 
334
void
335
closemap(Map *m)
336
{
337
	if(decref(m)==0){
338
		free(m->buf);
339
		free(m->el);
340
		free(m);
341
	}
342
}
343
 
344
void
345
currentmap(int force)
346
{
347
	char *p, *q, *r;
348
	int lfd, fd, m, n;
349
	Dir *d;
350
	Map *nmap;
351
	char *err = nil;
352
 
353
	lfd = -1;
354
	fd = -1;
355
	d = nil;
356
	nmap = nil;
357
	if(!force && map && map->t+Tcache >= time(0))
358
		return;
359
 
360
	wlock(&maplock);
361
	if(!force && map && map->t+Tcache >= time(0))
362
		goto Return;
363
 
364
	if((lfd = getlock("d/L.map")) < 0){
365
		err = "can't lock";
366
		goto Return;
367
	}
368
 
369
	if((d = wdirstat("d/map")) == nil)
370
		goto Return;
371
 
372
	if(map && d->qid.path == map->qid.path && d->qid.vers == map->qid.vers){
373
		map->t = time(0);
374
		goto Return;
375
	}
376
 
377
	if(d->length > Maxmap){
378
		//LOG
379
		err = "too long";
380
		goto Return;
381
	}
382
 
383
	if((fd = wopen("d/map", OREAD)) < 0)
384
		goto Return;
385
 
386
	nmap = emalloc(sizeof *nmap);
387
	nmap->buf = emalloc(d->length+1);
388
	n = readn(fd, nmap->buf, d->length);
389
	if(n != d->length){
390
		err = "bad length";
391
		goto Return;
392
	}
393
	nmap->buf[n] = '\0';
394
 
395
	n = 0;
396
	for(p=nmap->buf; p; p=strchr(p+1, '\n'))
397
		n++;
398
	nmap->el = emalloc(n*sizeof(nmap->el[0]));
399
 
400
	m = 0;
401
	for(p=nmap->buf; p && *p && m < n; p=q){
402
		if(q = strchr(p+1, '\n'))
403
			*q++ = '\0';
404
		nmap->el[m].n = strtol(p, &r, 10);
405
		if(*r == ' ')
406
			r++;
407
		else
408
			{}//LOG?
409
		nmap->el[m].s = strcondense(r, 1);
410
		m++;
411
	}
412
	//LOG if m != n
413
 
414
	nmap->qid = d->qid;
415
	nmap->t = time(0);
416
	nmap->nel = m;
417
	qsort(nmap->el, nmap->nel, sizeof(nmap->el[0]), mapcmp);
418
	if(map)
419
		closemap(map);
420
	map = nmap;
421
	incref(map);
422
	nmap = nil;
423
 
424
Return:
425
	free(d);
426
	if(nmap){
427
		free(nmap->el);
428
		free(nmap->buf);
429
		free(nmap);
430
	}
431
	if(map == nil)
432
		sysfatal("cannot get map: %s: %r", err);
433
 
434
	if(fd >= 0)
435
		close(fd);
436
	if(lfd >= 0)
437
		close(lfd);
438
	wunlock(&maplock);
439
}
440
 
441
int
442
allocnum(char *title, int mustbenew)
443
{
444
	char *p, *q;
445
	int lfd, fd, n;
446
	Biobuf b;
447
 
448
	if(strcmp(title, "map")==0 || strcmp(title, "new")==0){
449
		werrstr("reserved title name");
450
		return -1;
451
	}
452
 
453
	if(title[0]=='\0' || strpbrk(title, "/<>:?")){
454
		werrstr("invalid character in name");
455
		return -1;
456
	}
457
	if((n = nametonum(title)) >= 0){
458
		if(mustbenew){
459
			werrstr("duplicate title");
460
			return -1;
461
		}
462
		return n;
463
	}
464
 
465
	title = estrdup(title);
466
	strcondense(title, 1);
467
	strlower(title);
468
	if(strchr(title, '\n') || strlen(title) > 200){
469
		werrstr("bad title");
470
		free(title);
471
		return -1;
472
	}
473
 
474
	if((lfd = getlock("d/L.map")) < 0){
475
		free(title);
476
		return -1;
477
	}
478
 
479
	if((fd = wopen("d/map", ORDWR)) < 0){	// LOG?
480
		close(lfd);
481
		free(title);
482
		return -1;
483
	}
484
 
485
	/*
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.
489
	 *
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.
494
	 */
495
	Binit(&b, fd, OREAD);
496
	n = 0;
497
	while(p = Brdline(&b, '\n')){
498
		p[Blinelen(&b)-1] = '\0';
499
		n = atoi(p)+1;
500
		q = strchr(p, ' ');
501
		if(q == nil)
502
			continue;
503
		if(strcmp(q+1, title) == 0){
504
			free(title);
505
			close(fd);
506
			close(lfd);
507
			if(mustbenew){
508
				werrstr("duplicate title");
509
				return -1;
510
			}else
511
				return n;
512
		}
513
	}
514
 
515
	seek(fd, 0, 2);	/* just in case it's not append only */
516
	fprint(fd, "%d %s\n", n, title);
517
	close(fd);
518
	close(lfd);
519
	free(title);
520
	/* kick the map */
521
	currentmap(1);
522
	return n;
523
}
524
 
525
int
526
nametonum(char *s)
527
{
528
	char *p;
529
	int i, lo, hi, m, rv;
530
 
531
	s = estrdup(s);
532
	strlower(s);
533
	for(p=s; *p; p++)
534
		if(*p=='_')
535
			*p = ' ';
536
 
537
	currentmap(0);
538
	rlock(&maplock);
539
	lo = 0;
540
	hi = map->nel;
541
	while(hi-lo > 1){
542
		m = (lo+hi)/2;
543
		i = strcmp(s, map->el[m].s);
544
		if(i < 0)
545
			hi = m;
546
		else
547
			lo = m;
548
	}
549
	if(hi-lo == 1 && strcmp(s, map->el[lo].s)==0)
550
		rv = map->el[lo].n;
551
	else
552
		rv = -1;
553
	runlock(&maplock);
554
	free(s);
555
	return rv;
556
}
557
 
558
char*
559
numtoname(int n)
560
{
561
	int i;
562
	char *s;
563
 
564
	currentmap(0);
565
	rlock(&maplock);
566
	for(i=0; i<map->nel; i++){
567
		if(map->el[i].n==n)
568
			break;
569
	}
570
	if(i==map->nel){
571
		runlock(&maplock);
572
		return nil;
573
	}
574
	s = estrdup(map->el[i].s);
575
	runlock(&maplock);
576
	return s;
577
}
578
 
579
Whist*
580
getcurrentbyname(char *s)
581
{
582
	int n;
583
 
584
	if((n = nametonum(s)) < 0)
585
		return nil;
586
	return getcache(n, 0);
587
}
588
 
589
static String*
590
Brdstring(Biobuf *b)
591
{
592
	long len;
593
	String *s;
594
	Dir *d;
595
 
596
	d = dirfstat(Bfildes(b));
597
	if (d == nil)	/* shouldn't happen, we just opened it */
598
		len = 0;
599
	else
600
		len = d->length;
601
	free(d);
602
	s = s_newalloc(len);
603
	s_read(b, s, len);
604
	return s;
605
}
606
 
607
/*
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
611
 * writes.
612
 *
613
 * If there is a conflicting write, we still write the page to 
614
 * the history file, but mark it as a failed write.
615
 */
616
int
617
writepage(int num, ulong t, String *s, char *title)
618
{
619
	char tmp[40], tmplock[40], err[ERRMAX], hist[40], *p;
620
	int conflict, lfd, fd;
621
	Biobuf *b;
622
	String *os;
623
 
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)
628
		return -1;
629
 
630
	conflict = 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");
637
			conflict = 1;
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 */
642
				close(lfd);
643
				s_free(os);
644
				Bterm(b);
645
				return 0;
646
			}
647
			s_free(os);
648
			snprint(err, sizeof err, "update conflict %lud != %s", t, p+1);
649
			conflict = 1;
650
		}
651
		Bterm(b);
652
	}else{
653
		if(t != 0){
654
			close(lfd);
655
			werrstr("did not expect to create");
656
			return -1;
657
		}
658
	}
659
 
660
	if((fd = wopen(hist, OWRITE)) < 0){
661
		if((fd = wcreate(hist, OWRITE, 0666)) < 0){
662
			close(lfd);
663
			return -1;
664
		}else
665
			fprint(fd, "%s\n", title);
666
	}
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)){
670
		close(fd);
671
		close(lfd);
672
		return -1;
673
	}
674
	close(fd);
675
 
676
	if(conflict){
677
		close(lfd);
678
		voidcache(num);
679
		werrstr(err);
680
		return -1;
681
	}
682
 
683
	if((fd = wcreate(tmp, OWRITE, 0666)) < 0){
684
		close(lfd);
685
		voidcache(num);
686
		return -1;
687
	}
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)){
691
		close(fd);
692
		close(lfd);
693
		voidcache(num);
694
		return -1;
695
	}
696
	close(fd);
697
	close(lfd);
698
	voidcache(num);
699
	return 0;
700
}