xref: /inferno-os/emu/port/devtinyfs.c (revision 6cde411a8ffd477459336cedf48034e46f56f913)
1 #include	"dat.h"
2 #include	"fns.h"
3 #include	"error.h"
4 
5 
6 enum{
7 	Qdir,
8 	Qmedium,
9 
10 	Maxfs=		10,	/* max file systems */
11 
12 	Blen=		48,	/* block length */
13 	Nlen=		28,	/* name length */
14 	Dlen=		Blen - 4,
15 
16 	Tagdir=		'd',
17 	Tagdata=	'D',
18 	Tagend=		'e',
19 	Tagfree=	'f',
20 
21 	Notapin=		0xffff,
22 	Notabno=		0xffff,
23 
24 	Fcreating=	1,
25 	Frmonclose=	2
26 };
27 
28 /* representation of a Tdir on medium */
29 typedef struct Mdir Mdir;
30 struct Mdir {
31 	uchar	type;
32 	uchar	bno[2];
33 	uchar	pin[2];
34 	char	name[Nlen];
35 	char	pad[Blen - Nlen - 6];
36 	uchar	sum;
37 };
38 
39 /* representation of a Tdata/Tend on medium */
40 typedef struct Mdata Mdata;
41 struct Mdata {
42 	uchar	type;
43 	uchar	bno[2];
44 	uchar	data[Dlen];
45 	uchar	sum;
46 };
47 
48 typedef struct Tfile Tfile;
49 struct Tfile {
50 	int	r;
51 	char	name[Nlen];
52 	ushort	bno;
53 	ushort	dbno;
54 	ushort	pin;
55 	uchar	flag;
56 	ulong	length;
57 
58 	/* hint to avoid egregious reading */
59 	ushort	fbno;
60 	ulong	finger;
61 };
62 
63 typedef struct Tfs Tfs;
64 struct Tfs {
65 	QLock	ql;
66 	int	r;
67 	Chan	*c;
68 	uchar	*map;
69 	int	nblocks;
70 	Tfile	*f;
71 	int	nf;
72 	int	fsize;
73 };
74 
75 static struct {
76 	Tfs	fs[Maxfs];
77 } tinyfs;
78 
79 #define GETS(x) ((x)[0]|((x)[1]<<8))
80 #define PUTS(x, v) {(x)[0] = (v);(x)[1] = ((v)>>8);}
81 
82 #define GETL(x) (GETS(x)|(GETS(x+2)<<16))
83 #define PUTL(x, v) {PUTS(x, v);PUTS(x+2, (v)>>16)};
84 
85 static uchar
86 checksum(uchar *p)
87 {
88 	uchar *e;
89 	uchar s;
90 
91 	s = 0;
92 	for(e = p + Blen; p < e; p++)
93 		s += *p;
94 	return s;
95 }
96 
97 static void
98 mapclr(Tfs *fs, ulong bno)
99 {
100 	fs->map[bno>>3] &= ~(1<<(bno&7));
101 }
102 
103 static void
104 mapset(Tfs *fs, ulong bno)
105 {
106 	fs->map[bno>>3] |= 1<<(bno&7);
107 }
108 
109 static int
110 isalloced(Tfs *fs, ulong bno)
111 {
112 	return fs->map[bno>>3] & (1<<(bno&7));
113 }
114 
115 static int
116 mapalloc(Tfs *fs)
117 {
118 	int i, j, lim;
119 	uchar x;
120 
121 	lim = (fs->nblocks + 8 - 1)/8;
122 	for(i = 0; i < lim; i++){
123 		x = fs->map[i];
124 		if(x == 0xff)
125 			continue;
126 		for(j = 0; j < 8; j++)
127 			if((x & (1<<j)) == 0){
128 				fs->map[i] = x|(1<<j);
129 				return i*8 + j;
130 			}
131 	}
132 
133 	return Notabno;
134 }
135 
136 static Mdir*
137 validdir(Tfs *fs, uchar *p)
138 {
139 	Mdir *md;
140 	ulong x;
141 
142 	if(checksum(p) != 0)
143 		return 0;
144 	if(p[0] != Tagdir)
145 		return 0;
146 	md = (Mdir*)p;
147 	x = GETS(md->bno);
148 	if(x >= fs->nblocks)
149 		return 0;
150 	return md;
151 }
152 
153 static Mdata*
154 validdata(Tfs *fs, uchar *p, int *lenp)
155 {
156 	Mdata *md;
157 	ulong x;
158 
159 	if(checksum(p) != 0)
160 		return 0;
161 	md = (Mdata*)p;
162 	switch(md->type){
163 	case Tagdata:
164 		x = GETS(md->bno);
165 		if(x >= fs->nblocks)
166 			return 0;
167 		if(lenp)
168 			*lenp = Dlen;
169 		break;
170 	case Tagend:
171 		x = GETS(md->bno);
172 		if(x > Dlen)
173 			return 0;
174 		if(lenp)
175 			*lenp = x;
176 		break;
177 	default:
178 		return 0;
179 	}
180 	return md;
181 }
182 
183 static Mdata*
184 readdata(Tfs *fs, ulong bno, uchar *buf, int *lenp)
185 {
186 	if(bno >= fs->nblocks)
187 		return 0;
188 	if(fs->c->dev->read(fs->c, buf, Blen, Blen*bno) != Blen)
189 		error(Eio);
190 	return validdata(fs, buf, lenp);
191 }
192 
193 static void
194 writedata(Tfs *fs, ulong bno, ulong next, uchar *buf, int len, int last)
195 {
196 	Mdata md;
197 
198 	if(bno >= fs->nblocks)
199 		error(Eio);
200 	if(len > Dlen)
201 		len = Dlen;
202 	if(len < 0)
203 		error(Eio);
204 	memset(&md, 0, sizeof(md));
205 	if(last){
206 		md.type = Tagend;
207 		PUTS(md.bno, len);
208 	} else {
209 		md.type = Tagdata;
210 		PUTS(md.bno, next);
211 	}
212 	memmove(md.data, buf, len);
213 	md.sum = 0 - checksum((uchar*)&md);
214 
215 	if(fs->c->dev->write(fs->c, &md, Blen, Blen*bno) != Blen)
216 		error(Eio);
217 }
218 
219 static void
220 writedir(Tfs *fs, Tfile *f)
221 {
222 	Mdir *md;
223 	uchar buf[Blen];
224 
225 	if(f->bno == Notabno)
226 		return;
227 
228 	md = (Mdir*)buf;
229 	memset(buf, 0, Blen);
230 	md->type = Tagdir;
231 	strncpy(md->name, f->name, sizeof(md->name)-1);
232 	PUTS(md->bno, f->dbno);
233 	PUTS(md->pin, f->pin);
234 	md->sum = 0 - checksum(buf);
235 
236 	if(fs->c->dev->write(fs->c, buf, Blen, Blen*f->bno) != Blen)
237 		error(Eio);
238 }
239 
240 static void
241 freeblocks(Tfs *fs, ulong bno, ulong bend)
242 {
243 	uchar buf[Blen];
244 	Mdata *md;
245 
246 	if(waserror())
247 		return;
248 
249 	while(bno != bend && bno != Notabno){
250 		mapclr(fs, bno);
251 		if(fs->c->dev->read(fs->c, buf, Blen, Blen*bno) != Blen)
252 			break;
253 		md = validdata(fs, buf, 0);
254 		if(md == 0)
255 			break;
256 		if(md->type == Tagend)
257 			break;
258 		bno = GETS(md->bno);
259 	}
260 
261 	poperror();
262 }
263 
264 static void
265 freefile(Tfs *fs, Tfile *f, ulong bend)
266 {
267 	uchar buf[Blen];
268 
269 	/* remove blocks from map */
270 	freeblocks(fs, f->dbno, bend);
271 
272 	/* change file type to free on medium */
273 	if(f->bno != Notabno){
274 		memset(buf, 0x55, Blen);
275 		fs->c->dev->write(fs->c, buf, Blen, Blen*f->bno);
276 		mapclr(fs, f->bno);
277 	}
278 
279 	/* forget we ever knew about it */
280 	memset(f, 0, sizeof(*f));
281 }
282 
283 static void
284 expand(Tfs *fs)
285 {
286 	Tfile *f;
287 
288 	fs->fsize += 8;
289 	f = malloc(fs->fsize*sizeof(*f));
290 	if(f == nil)
291 		error(Enomem);
292 
293 	if(fs->f){
294 		memmove(f, fs->f, fs->nf*sizeof(*f));
295 		free(fs->f);
296 	}
297 	fs->f = f;
298 }
299 
300 static Tfile*
301 newfile(Tfs *fs, char *name)
302 {
303 	int i;
304 	volatile struct {
305 		Tfile *f;
306 		Tfs *fs;
307 	} rock;
308 
309 	/* find free entry in file table */
310 	rock.f = 0;
311 	rock.fs = fs;
312 	for(;;) {
313 		for(i = 0; i < rock.fs->fsize; i++){
314 			rock.f = &rock.fs->f[i];
315 			if(rock.f->name[0] == 0){
316 				strncpy(rock.f->name, name, sizeof(rock.f->name)-1);
317 				break;
318 			}
319 		}
320 
321 		if(i < rock.fs->fsize){
322 			if(i >= rock.fs->nf)
323 				rock.fs->nf = i+1;
324 			break;
325 		}
326 
327 		expand(rock.fs);
328 	}
329 
330 	rock.f->flag = Fcreating;
331 	rock.f->dbno = Notabno;
332 	rock.f->bno = mapalloc(rock.fs);
333 	rock.f->fbno = Notabno;
334 	rock.f->r = 1;
335 	rock.f->pin = Notapin;
336 
337 	/* write directory block */
338 	if(waserror()){
339 		freefile(rock.fs, rock.f, Notabno);
340 		nexterror();
341 	}
342 	if(rock.f->bno == Notabno)
343 		error("out of space");
344 	writedir(rock.fs, rock.f);
345 	poperror();
346 
347 	return rock.f;
348 }
349 
350 /*
351  *  Read the whole medium and build a file table and used
352  *  block bitmap.  Inconsistent files are purged.  The medium
353  *  had better be small or this could take a while.
354  */
355 static void
356 tfsinit(Tfs *fs)
357 {
358 	uchar dbuf[STATFIXLEN+32*4];
359 	Dir d;
360 	uchar buf[Blen];
361 	ulong x, bno;
362 	int n, done;
363 	Tfile *f;
364 	Mdir *mdir;
365 	Mdata *mdata;
366 
367 	n = fs->c->dev->stat(fs->c, dbuf, sizeof(dbuf));
368 	n = convM2D(dbuf, n, &d, nil);
369 	if(n <= 0)
370 		error("cannot stat tinyfs medium");
371 	fs->nblocks = d.length/Blen;
372 	if(fs->nblocks < 3)
373 		error("tinyfs medium too small");
374 
375 	/* bitmap for block usage */
376 	x = (fs->nblocks + 8 - 1)/8;
377 	fs->map = malloc(x);
378 	if(fs->map == nil)
379 		error(Enomem);
380 	for(bno = fs->nblocks; bno < x*8; bno++)
381 		mapset(fs, bno);
382 
383 	/* find files */
384 	for(bno = 0; bno < fs->nblocks; bno++){
385 		n = fs->c->dev->read(fs->c, buf, Blen, Blen*bno);
386 		if(n != Blen)
387 			break;
388 
389 		mdir = validdir(fs, buf);
390 		if(mdir == 0)
391 			continue;
392 
393 		if(fs->nf >= fs->fsize)
394 			expand(fs);
395 
396 		f = &fs->f[fs->nf++];
397 
398 		x = GETS(mdir->bno);
399 		mapset(fs, bno);
400 		strncpy(f->name, mdir->name, sizeof(f->name));
401 		f->pin = GETS(mdir->pin);
402 		f->bno = bno;
403 		f->dbno = x;
404 		f->fbno = Notabno;
405 	}
406 
407 	/* follow files */
408 	for(f = fs->f; f < &(fs->f[fs->nf]); f++){
409 		bno = f->dbno;
410 		for(done = 0; !done;) {
411 			if(isalloced(fs, bno)){
412 				freefile(fs, f, bno);
413 				break;
414 			}
415 			n = fs->c->dev->read(fs->c, buf, Blen, Blen*bno);
416 			if(n != Blen){
417 				freefile(fs, f, bno);
418 				break;
419 			}
420 			mdata = validdata(fs, buf, 0);
421 			if(mdata == 0){
422 				freefile(fs, f, bno);
423 				break;
424 			}
425 			mapset(fs, bno);
426 			switch(mdata->type){
427 			case Tagdata:
428 				bno = GETS(mdata->bno);
429 				f->length += Dlen;
430 				break;
431 			case Tagend:
432 				f->length += GETS(mdata->bno);
433 				done = 1;
434 				break;
435 			}
436 			if(done)
437 				f->flag &= ~Fcreating;
438 		}
439 	}
440 }
441 
442 /*
443  *  single directory
444  */
445 static int
446 tinyfsgen(Chan *c, char *name, Dirtab *tab, int ntab, int i, Dir *dp)
447 {
448 	Tfs *fs;
449 	Tfile *f;
450 	Qid qid;
451 
452 	USED(name);
453 	USED(ntab);
454 	USED(tab);
455 
456 	fs = &tinyfs.fs[c->devno];
457 	if(i >= fs->nf)
458 		return -1;
459 	qid.vers = 0;
460 	if(i == DEVDOTDOT){
461 		qid.type = QTDIR;
462 		devdir(c, qid, ".", 0, eve, DMDIR|0555, dp);
463 		return 1;
464 	}
465 	f = &fs->f[i];
466 	if(f->name[0] == 0)
467 		return 0;
468 	qid.path = i+1;
469 	qid.type = QTFILE;
470 	devdir(c, qid, f->name, f->length, eve, 0664, dp);
471 	return 1;
472 }
473 
474 /*
475  *  specifier is the name of a device in /dev
476  */
477 static Chan *
478 tinyfsattach(char *spec)
479 {
480 	Tfs *fs;
481 	Chan *c;
482 	volatile struct { Chan *cc; } rock;
483 	int i;
484 	char buf[KNAMELEN*3];
485 
486 	if(*spec == 0 || strchr(spec, '/') != nil)
487 		error("bad specifier");
488 
489 	snprint(buf, sizeof(buf), "/dev/%s", spec);
490 	rock.cc = namec(buf, Aopen, ORDWR, 0);
491 	if(waserror()){
492 		cclose(rock.cc);
493 		nexterror();
494 	}
495 
496 	fs = 0;
497 	for(i = 0; i < Maxfs; i++){
498 		fs = &tinyfs.fs[i];
499 		qlock(&fs->ql);
500 		if(fs->r && eqchan(rock.cc, fs->c, 1))
501 			break;
502 		qunlock(&fs->ql);
503 	}
504 	if(i < Maxfs){
505 		fs->r++;
506 		qunlock(&fs->ql);
507 		cclose(rock.cc);
508 	} else {
509 		for(fs = tinyfs.fs; fs < &tinyfs.fs[Maxfs]; fs++){
510 			qlock(&fs->ql);
511 			if(fs->r == 0)
512 				break;
513 			qunlock(&fs->ql);
514 		}
515 		if(fs == &tinyfs.fs[Maxfs])
516 			error("too many tinyfs's");
517 		fs->c = rock.cc;
518 		fs->r = 1;
519 		fs->f = 0;
520 		fs->nf = 0;
521 		fs->fsize = 0;
522 		tfsinit(fs);
523 		qunlock(&fs->ql);
524 	}
525 	poperror();
526 
527 	c = devattach('F', spec);
528 	c->devno = fs - tinyfs.fs;
529 	c->qid.type = QTDIR;
530 	c->qid.vers = 0;
531 
532 	return c;
533 }
534 
535 static Walkqid*
536 tinyfswalk(Chan *c, Chan *nc, char **name, int nname)
537 {
538 	Tfs *fs;
539 	Walkqid *wq;
540 
541 	fs = &tinyfs.fs[c->devno];
542 
543 	qlock(&fs->ql);
544 	wq = devwalk(c, nc, name, nname, 0, 0, tinyfsgen);
545 	if(wq != nil && (nc = wq->clone) != nil && nc->qid.path != Qdir){
546 		fs = &tinyfs.fs[nc->devno];
547 		fs->f[nc->qid.path-1].r++;
548 	}
549 	qunlock(&fs->ql);
550 	return wq;
551 }
552 
553 static int
554 tinyfsstat(Chan *c, uchar *db, int n)
555 {
556 	return devstat(c, db, n, 0, 0, tinyfsgen);
557 }
558 
559 static Chan *
560 tinyfsopen(Chan *c, int omode)
561 {
562 	Tfile *f;
563 	volatile struct { Tfs *fs; } rock;
564 
565 	rock.fs = &tinyfs.fs[c->devno];
566 
567 	if(c->qid.type & QTDIR){
568 		if(omode != OREAD)
569 			error(Eperm);
570 	} else {
571 		qlock(&rock.fs->ql);
572 		if(waserror()){
573 			qunlock(&rock.fs->ql);
574 			nexterror();
575 		}
576 		switch(omode){
577 		case OTRUNC|ORDWR:
578 		case OTRUNC|OWRITE:
579 			f = newfile(rock.fs, rock.fs->f[c->qid.path-1].name);
580 			rock.fs->f[c->qid.path-1].r--;
581 			c->qid.path = f - rock.fs->f;
582 			break;
583 		case OREAD:
584 			break;
585 		default:
586 			error(Eperm);
587 		}
588 		qunlock(&rock.fs->ql);
589 		poperror();
590 	}
591 
592 	return devopen(c, omode, 0, 0, tinyfsgen);
593 }
594 
595 static void
596 tinyfscreate(Chan *c, char *name, int omode, ulong perm)
597 {
598 	volatile struct { Tfs *fs; } rock;
599 	Tfile *f;
600 
601 	USED(perm);
602 
603 	rock.fs = &tinyfs.fs[c->devno];
604 
605 	qlock(&rock.fs->ql);
606 	if(waserror()){
607 		qunlock(&rock.fs->ql);
608 		nexterror();
609 	}
610 	f = newfile(rock.fs, name);
611 	qunlock(&rock.fs->ql);
612 	poperror();
613 
614 	c->qid.path = (f - rock.fs->f)+1;
615 	c->qid.vers = 0;
616 	c->qid.type = QTFILE;
617 	c->mode = openmode(omode);
618 }
619 
620 static void
621 tinyfsremove(Chan *c)
622 {
623 	Tfs *fs;
624 	Tfile *f;
625 
626 	if(c->qid.path == Qdir)
627 		error(Eperm);
628 	fs = &tinyfs.fs[c->devno];
629 	f = &fs->f[c->qid.path-1];
630 	qlock(&fs->ql);
631 	freefile(fs, f, Notabno);
632 	qunlock(&fs->ql);
633 }
634 
635 static void
636 tinyfsclose(Chan *c)
637 {
638 	volatile struct { Tfs *fs; } rock;
639 	Tfile *f, *nf;
640 	int i;
641 
642 	rock.fs = &tinyfs.fs[c->devno];
643 
644 	qlock(&rock.fs->ql);
645 
646 	/* dereference file and remove old versions */
647 	if(!waserror()){
648 		if(c->qid.path != Qdir){
649 			f = &rock.fs->f[c->qid.path-1];
650 			f->r--;
651 			if(f->r == 0){
652 				if(f->flag & Frmonclose)
653 					freefile(rock.fs, f, Notabno);
654 				else if(f->flag & Fcreating){
655 					/* remove all other files with this name */
656 					for(i = 0; i < rock.fs->fsize; i++){
657 						nf = &rock.fs->f[i];
658 						if(f == nf)
659 							continue;
660 						if(strcmp(nf->name, f->name) == 0){
661 							if(nf->r)
662 								nf->flag |= Frmonclose;
663 							else
664 								freefile(rock.fs, nf, Notabno);
665 						}
666 					}
667 					f->flag &= ~Fcreating;
668 				}
669 			}
670 		}
671 		poperror();
672 	}
673 
674 	/* dereference rock.fs and remove on zero refs */
675 	rock.fs->r--;
676 	if(rock.fs->r == 0){
677 		if(rock.fs->f)
678 			free(rock.fs->f);
679 		rock.fs->f = 0;
680 		rock.fs->nf = 0;
681 		rock.fs->fsize = 0;
682 		if(rock.fs->map)
683 			free(rock.fs->map);
684 		rock.fs->map = 0;
685 		cclose(rock.fs->c);
686 		rock.fs->c = 0;
687 	}
688 	qunlock(&rock.fs->ql);
689 }
690 
691 static long
692 tinyfsread(Chan *c, void *a, long n, vlong offset)
693 {
694 	volatile struct { Tfs *fs; } rock;
695 	Tfile *f;
696 	int sofar, i, off;
697 	ulong bno;
698 	Mdata *md;
699 	uchar buf[Blen];
700 	uchar *p;
701 
702 	if(c->qid.type & QTDIR)
703 		return devdirread(c, a, n, 0, 0, tinyfsgen);
704 
705 	p = a;
706 	rock.fs = &tinyfs.fs[c->devno];
707 	f = &rock.fs->f[c->qid.path-1];
708 	if(offset >= f->length)
709 		return 0;
710 
711 	qlock(&rock.fs->ql);
712 	if(waserror()){
713 		qunlock(&rock.fs->ql);
714 		nexterror();
715 	}
716 	if(n + offset >= f->length)
717 		n = f->length - offset;
718 
719 	/* walk to starting data block */
720 	if(0 && f->finger <= offset && f->fbno != Notabno){
721 		sofar = f->finger;
722 		bno = f->fbno;
723 	} else {
724 		sofar = 0;
725 		bno = f->dbno;
726 	}
727 	for(; sofar + Dlen <= offset; sofar += Dlen){
728 		md = readdata(rock.fs, bno, buf, 0);
729 		if(md == 0)
730 			error(Eio);
731 		bno = GETS(md->bno);
732 	}
733 
734 	/* read data */
735 	off = offset%Dlen;
736 	offset -= off;
737 	for(sofar = 0; sofar < n; sofar += i){
738 		md = readdata(rock.fs, bno, buf, &i);
739 		if(md == 0)
740 			error(Eio);
741 
742 		/* update finger for successful read */
743 		f->finger = offset;
744 		f->fbno = bno;
745 		offset += Dlen;
746 
747 		i -= off;
748 		if(i > n - sofar)
749 			i = n - sofar;
750 		memmove(p, md->data+off, i);
751 		p += i;
752 		bno = GETS(md->bno);
753 		off = 0;
754 	}
755 	qunlock(&rock.fs->ql);
756 	poperror();
757 
758 	return sofar;
759 }
760 
761 /*
762  *  if we get a write error in this routine, blocks will
763  *  be lost.  They should be recovered next fsinit.
764  */
765 static long
766 tinyfswrite(Chan *c, void *a, long n, vlong offset)
767 {
768 	Tfile *f;
769 	int last, next, i, finger, off, used;
770 	ulong bno, fbno;
771 	Mdata *md;
772 	uchar buf[Blen];
773 	uchar *p;
774 	volatile struct {
775 		Tfs *fs;
776 		ulong dbno;
777 	} rock;
778 
779 	if(c->qid.type & QTDIR)
780 		error(Eperm);
781 
782 	if(n == 0)
783 		return 0;
784 
785 	p = a;
786 	rock.fs = &tinyfs.fs[c->devno];
787 	f = &rock.fs->f[c->qid.path-1];
788 
789 	qlock(&rock.fs->ql);
790 	rock.dbno = Notabno;
791 	if(waserror()){
792 		freeblocks(rock.fs, rock.dbno, Notabno);
793 		qunlock(&rock.fs->ql);
794 		nexterror();
795 	}
796 
797 	/* files are append only, anything else is illegal */
798 	if(offset != f->length)
799 		error("append only");
800 
801 	/* write blocks backwards */
802 	p += n;
803 	last = offset + n;
804 	fbno = Notabno;
805 	finger = 0;
806 	off = offset; /* so we have something signed to compare against */
807 	for(next = ((last-1)/Dlen)*Dlen; next >= off; next -= Dlen){
808 		bno = mapalloc(rock.fs);
809 		if(bno == Notabno)
810 			error("out of space");
811 		i = last - next;
812 		p -= i;
813 		if(last == n+offset){
814 			writedata(rock.fs, bno, rock.dbno, p, i, 1);
815 			finger = next;	/* remember for later */
816 			fbno = bno;
817 		} else {
818 			writedata(rock.fs, bno, rock.dbno, p, i, 0);
819 		}
820 		rock.dbno = bno;
821 		last = next;
822 	}
823 
824 	/* walk to last data block */
825 	md = (Mdata*)buf;
826 	if(0 && f->finger < offset && f->fbno != Notabno){
827 		next = f->finger;
828 		bno = f->fbno;
829 	} else {
830 		next = 0;
831 		bno = f->dbno;
832 	}
833 
834 	used = 0;
835 	while(bno != Notabno){
836 		md = readdata(rock.fs, bno, buf, &used);
837 		if(md == 0)
838 			error(Eio);
839 		if(md->type == Tagend){
840 			if(next + Dlen < offset)
841 				panic("devtinyfs1");
842 			break;
843 		}
844 		next += Dlen;
845 		if(next > offset)
846 			panic("devtinyfs1");
847 		bno = GETS(md->bno);
848 	}
849 
850 	/* point to new blocks */
851 	if(offset == 0){
852 		/* first block in a file */
853 		f->dbno = rock.dbno;
854 		writedir(rock.fs, f);
855 	} else {
856 		/* updating a current block */
857 		i = last - offset;
858 		if(i > 0){
859 			p -= i;
860 			memmove(md->data + used, p, i);
861 			used += i;
862 		}
863 		writedata(rock.fs, bno, rock.dbno, md->data, used, last == n+offset);
864 	}
865 	f->length += n;
866 
867 	/* update finger */
868 	if(fbno != Notabno){
869 		f->finger = finger;
870 		f->fbno =  fbno;
871 	}
872 	poperror();
873 	qunlock(&rock.fs->ql);
874 
875 	return n;
876 }
877 
878 Dev tinyfsdevtab = {
879 	'F',
880 	"tinyfs",
881 
882 	devreset,
883 	devinit,
884 	devshutdown,
885 	tinyfsattach,
886 	tinyfswalk,
887 	tinyfsstat,
888 	tinyfsopen,
889 	tinyfscreate,
890 	tinyfsclose,
891 	tinyfsread,
892 	devbread,
893 	tinyfswrite,
894 	devbwrite,
895 	tinyfsremove,
896 	devwstat
897 };
898