shithub: gefs

Download patch

ref: add0bcdaa6fb15ba19507b530ef12babd4b64087
parent: 880f8939fdec5e63d0f284dccd7148bbfa4cafb6
author: Ori Bernstein <ori@eigenstate.org>
date: Sun Dec 12 00:04:13 EST 2021

fs: reclaim blocks when deleting files

--- a/blk.c
+++ b/blk.c
@@ -38,7 +38,7 @@
 		off += n;
 		rem -= n;
 	}
-	memset(&b->RWLock, 0, sizeof(RWLock));
+	memset(&b->Lock, 0, sizeof(Lock));
 	b->type = (flg&GBraw) ? Traw : GBIT16(b->buf+0);
 	b->bp.addr = bp;
 	b->bp.hash = -1;
@@ -589,9 +589,9 @@
 syncblk(Blk *b)
 {
 	assert(b->flag & Bfinal);
-	wlock(b);
+	lock(b);
 	b->flag &= ~(Bqueued|Bdirty);
-	wunlock(b);
+	unlock(b);
 	return pwrite(fs->fd, b->buf, Blksz, b->bp.addr);
 }
 
@@ -614,9 +614,9 @@
 
 	assert(b->type == Tsuper);
 	p = b->data;
-	wlock(b);
+	lock(b);
 	b->flag |= Bdirty;
-	wunlock(b);
+	unlock(b);
 	memcpy(p, "gefs0001", 8); p += 8;
 	PBIT32(p, 0); p += 4; /* dirty */
 	PBIT32(p, Blksz); p += 4;
@@ -638,7 +638,7 @@
 	vlong h;
 
 //	assert((b->flag & Bfinal) == 0);
-	wlock(b);
+	lock(b);
 	b->flag |= Bfinal;
 	if(b->type != Traw)
 		PBIT16(b->buf, b->type);
@@ -669,7 +669,7 @@
 	case Tarena:
 		break;
 	}
-	wunlock(b);
+	unlock(b);
 }
 
 Blk*
@@ -745,26 +745,36 @@
 void
 freeblk(Blk *b)
 {
+	lock(b);
+	assert((b->flag & Bqueued) == 0);
+	b->flag |= Bzombie;
+	b->freed = getcallerpc(&b);
+	unlock(b);
+	dprint("freeing block %B @ %ld, from 0x%p\n", b->bp, b->ref, getcallerpc(&b));
+	freebp(b->bp);
+}
+
+void
+freebp(Bptr bp)
+{
+	Bfree *f;
+
+	if((f = malloc(sizeof(Bfree))) == nil)
+		return;
+	f->bp = bp;
 	lock(&fs->freelk);
-	b->fnext = fs->freehd;
-	fs->freehd = b;
+	f->next = fs->freehd;
+	fs->freehd = f;
 	unlock(&fs->freelk);
 }
 
 void
-reclaimblk(Blk *b)
+reclaimblk(Bptr bp)
 {
 	Arena *a;
 
-	wlock(b);
-	b->flag |= Bzombie;
-	b->freed = getcallerpc(&b);
-	wunlock(b);
-	dprint("freeing block %B @ %ld, from 0x%p\n", b->bp, b->ref, getcallerpc(&b));
-
-	assert((b->flag & Bqueued) == 0);
-	a = getarena(b->bp.addr);
+	a = getarena(bp.addr);
 	lock(a);
-	blkdealloc_lk(b->bp.addr);
+	blkdealloc_lk(bp.addr);
 	unlock(a);
 }
--- a/cache.c
+++ b/cache.c
@@ -85,9 +85,9 @@
 	b->cprev = nil;
 	fs->chead = b;
 	if((b->flag&Bcached) == 0){
-		wlock(b);
+		lock(b);
 		b->flag |= Bcached;
-		wunlock(b);
+		unlock(b);
 		fs->ccount++;
 		refblk(b);
 	}
--- a/check.c
+++ b/check.c
@@ -120,11 +120,11 @@
 			switch(my.op){
 			case Oinsert:	/* new kvp */
 			case Odelete:	/* delete kvp */
-			case Oqdelete:	/* delete kvp if exists */
+			case Oclearb:	/* delete kvp if exists */
 				break;
 			case Owstat:		/* kvp dirent */
-				if((my.statop & ~(Owsize|Owname|Owmode|Owmtime)) != 0){
-					fprint(fd, "invalid stat op %d\n", my.statop);
+				if((my.statop & ~(Owsize|Owmode|Owmtime)) != 0){
+					fprint(2, "invalid stat op %d\n", my.statop);
 					fail++;
 				}
 				break;
--- a/dat.h
+++ b/dat.h
@@ -7,6 +7,7 @@
 typedef struct Val	Val;
 typedef struct Kvp	Kvp;
 typedef struct Bptr	Bptr;
+typedef struct Bfree	Bfree;
 typedef struct Path	Path;
 typedef struct Scan	Scan;
 typedef struct Dent	Dent;
@@ -69,8 +70,8 @@
 	 */
 	Kdat,	/* qid[8] off[8] => ptr[16]:	pointer to data page */
 	Kent,	/* pqid[8] name[n] => dir[n]:	serialized Dir */
-	Ksnap,	/* id[8] => tree[]:		snapshot */
-	Ksnapid,	/* qid[8] => tree[]:		snapshot for exec, transient */
+	Ksnap,	/* sid[8] => ref[8], tree[24]:	snapshot root */
+	Kdset,	/* name[] => snapid[]:		dataset (snapshot ref) */
 	Ksuper,	/* qid[8] => pqid[8]:		parent dir */
 };
 
@@ -83,7 +84,7 @@
 };
 
 //#define Efs	"i will not buy this fs, it is scratched"
-#define Efs	(abort(), "broken")
+#define Efs (abort(), "nope")
 #define Eio	"i/o error"
 #define Efid	"bad fid"
 #define Edscan	"invalid dir scan offset"
@@ -197,13 +198,12 @@
 	Onop,
 	Oinsert,	/* new kvp */
 	Odelete,	/* delete kvp */
-	Oqdelete,	/* delete kvp if exists */
+	Oclearb,	/* free block ptr if exists */
 	Owstat,		/* kvp dirent */
 	/* wstat flags */
 	Owsize	= 1<<4,
-	Owname	= 1<<5,
-	Owmode	= 1<<6,
-	Owmtime	= 1<<7,
+	Owmode	= 1<<5,
+	Owmtime	= 1<<6,
 };
 
 /*
@@ -255,6 +255,11 @@
 	int	ht;
 };
 
+struct Bfree {
+	Bptr	bp;
+	Bfree	*next;
+};
+
 /*
  * Overall state of the file sytem.
  * Shadows the superblock contents.
@@ -276,8 +281,8 @@
 	int	active[Maxproc];
 	int	lastactive[Maxproc];
 	Lock	freelk;
-	Blk	*freep;
-	Blk	*freehd;
+	Bfree	*freep;
+	Bfree	*freehd;
 
 	int	fd;
 	long	broken;
@@ -286,8 +291,7 @@
 
 	Lock	qidlk;
 	vlong	nextqid;
-	Lock	genlk;
-	vlong	nextgen;
+	vlong	nextgen; /* unlocked: only touched by mutator thread */
 
 	Arena	*arenas;
 	int	narena;
@@ -362,7 +366,6 @@
 
 	Qid	qid;
 	vlong	length;
-	vlong	rootb;
 	char	buf[Maxent];
 };
 
@@ -443,7 +446,7 @@
 };
 
 struct Blk {
-	RWLock;
+	Lock;
 
 	/* cache entry */
 	Blk	*cnext;
--- a/dump.c
+++ b/dump.c
@@ -49,11 +49,19 @@
 	n = 0;
 	switch(v->k[0]){
 	case Kdat:	/* qid[8] off[8] => ptr[16]:	pointer to data page */
-		bp.addr = GBIT64(v->v+0);
-		bp.hash = GBIT64(v->v+8);
-		bp.gen = GBIT64(v->v+16);
-		n = fmtprint(fmt, "ptr:%B", bp);
-		break;
+		switch(op){
+		case Odelete:
+		case Oclearb:
+			n = 0;
+			break;
+		case Onop:
+		case Oinsert:
+			bp.addr = GBIT64(v->v+0);
+			bp.hash = GBIT64(v->v+8);
+			bp.gen = GBIT64(v->v+16);
+			n = fmtprint(fmt, "ptr:%B", bp);
+			break;
+		}
 	case Kent:	/* pqid[8] name[n] => dir[n]:	serialized Dir */
 		switch(op){
 		case Onop:
@@ -120,7 +128,7 @@
 	char *opname[] = {
 	[Oinsert]	"Oinsert",
 	[Odelete]	"Odelete",
-	[Oqdelete]	"Oqdelete",
+	[Oclearb]	"Oclearb",
 	[Owstat]	"Owstat",
 	};
 	Msg *m;
--- a/fns.h
+++ b/fns.h
@@ -23,7 +23,8 @@
 void	enqueue(Blk*);
 void	quiesce(int);
 void	freeblk(Blk*);
-void	reclaimblk(Blk*);
+void	freebp(Bptr);
+void	reclaimblk(Bptr);
 ushort	blkfill(Blk*);
 uvlong	blkhash(Blk*);
 u32int	ihash(vlong);
--- a/fs.c
+++ b/fs.c
@@ -7,7 +7,9 @@
 #include "dat.h"
 #include "fns.h"
 
-int
+static char*	clearb(Fid*, vlong, vlong);
+
+static int
 okname(char *name)
 {
 	int i;
@@ -36,8 +38,105 @@
 	return q;
 }
 
+Chan*
+mkchan(int size)
+{
+	Chan *c;
+
+	if((c = mallocz(sizeof(Chan) + size*sizeof(void*), 1)) == nil)
+		sysfatal("create channel");
+	c->size = size;
+	c->avail = size;
+	c->count = 0;
+	c->rp = c->args;
+	c->wp = c->args;
+	return c;
+
+}
+
+Fmsg*
+chrecv(Chan *c)
+{
+	void *a;
+	long v;
+
+	v = c->count;
+	if(v == 0 || cas(&c->count, v, v-1) == 0)
+		semacquire(&c->count, 1);
+	lock(&c->rl);
+	a = *c->rp;
+	if(++c->rp >= &c->args[c->size])
+		c->rp = c->args;
+	unlock(&c->rl);
+	semrelease(&c->avail, 1);
+	return a;
+}
+
+void
+chsend(Chan *c, Fmsg *m)
+{
+	long v;
+
+	v = c->avail;
+	if(v == 0 || cas(&c->avail, v, v-1) == 0)
+		semacquire(&c->avail, 1);
+	lock(&c->wl);
+	*c->wp = m;
+	if(++c->wp >= &c->args[c->size])
+		c->wp = c->args;
+	unlock(&c->wl);
+	semrelease(&c->count, 1);
+
+}
+
+void
+fshangup(int fd, char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	vfprint(2, fmt, ap);
+	va_end(ap);
+	close(fd);
+	abort();
+}
+
+static void
+respond(Fmsg *m, Fcall *r)
+{
+	uchar buf[Max9p];
+	int w, n;
+
+	r->tag = m->tag;
+	dprint("→ %F\n", r);
+	if((n = convS2M(r, buf, sizeof(buf))) == 0)
+		abort();
+	qlock(m->wrlk);
+	w = write(m->fd, buf, n);
+	qunlock(m->wrlk);
+	if(w != n)
+		fshangup(m->fd, "failed write");
+	free(m);
+}
+
+static void
+rerror(Fmsg *m, char *fmt, ...)
+{
+	char buf[128];
+	va_list ap;
+	Fcall r;
+
+	va_start(ap, fmt);
+	vsnprint(buf, sizeof(buf), fmt, ap);
+	va_end(ap);
+	r.type = Rerror;
+	r.ename = buf;
+	respond(m, &r);
+}
+
+
 static char*
-fslookup(Fid *f, Key *k, Kvp *kv, char *buf, int nbuf, int lk)
+lookup(Fid *f, Key *k, Kvp *kv, char *buf, int nbuf, int lk)
 {
 	char *e;
 
@@ -51,6 +150,111 @@
 	return e;
 }
 
+static char*
+clearb(Fid *f, vlong o, vlong sz)
+{
+	char *e, buf[Offksz];
+	Msg m;
+
+	for(; o < sz; o += Blksz){
+		m.k = buf;
+		m.nk = sizeof(buf);
+		m.op = Oclearb;
+		m.k[0] = Kdat;
+		PBIT64(m.k+1, f->qpath);
+		PBIT64(m.k+9, o);
+		m.v = nil;
+		m.nv = 0;
+		if((e = btupsert(&f->mnt->root, &m, 1)) != nil)
+			return e;
+	}
+	return nil;
+}
+
+static int
+readb(Fid *f, char *d, vlong o, vlong n, int sz)
+{
+	char *e, buf[17], kvbuf[17+32];
+	vlong fb, fo;
+	Bptr bp;
+	Blk *b;
+	Key k;
+	Kvp kv;
+
+	if(o >= sz)
+		return 0;
+
+	fb = o & ~(Blksz-1);
+	fo = o & (Blksz-1);
+
+	k.k = buf;
+	k.nk = sizeof(buf);
+	k.k[0] = Kdat;
+	PBIT64(k.k+1, f->qpath);
+	PBIT64(k.k+9, fb);
+
+	e = lookup(f, &k, &kv, kvbuf, sizeof(kvbuf), 0);
+	if(e != nil && e != Eexist){
+		werrstr(e);
+		return -1;
+	}
+
+	bp = unpackbp(kv.v);
+	if((b = getblk(bp, GBraw)) == nil)
+		return -1;
+	if(fo+n > Blksz)
+		n = Blksz-fo;
+	if(b != nil){
+		memcpy(d, b->buf+fo, n);
+		putblk(b);
+	}else
+		memset(d, 0, n);
+	return n;
+}
+
+static int
+writeb(Fid *f, Msg *m, char *s, vlong o, vlong n, vlong sz)
+{
+	char buf[Kvmax];
+	vlong fb, fo;
+	Bptr bp;
+	Blk *b, *t;
+	Kvp kv;
+
+	fb = o & ~(Blksz-1);
+	fo = o & (Blksz-1);
+
+	m->k[0] = Kdat;
+	PBIT64(m->k+1, f->qpath);
+	PBIT64(m->k+9, fb);
+
+
+	b = newblk(Traw);
+	if(b == nil)
+		return -1;
+	if(fb < sz && (fo != 0 || n != Blksz)){
+		dprint("\tappending to block %B\n", b->bp);
+		if(lookup(f, m, &kv, buf, sizeof(buf), 0) != nil)
+			return -1;
+		bp = unpackbp(kv.v);
+		if((t = getblk(bp, GBraw)) == nil)
+			return -1;
+		memcpy(b->buf, t->buf, Blksz);
+		freeblk(t);
+		putblk(t);
+	}
+	if(fo+n > Blksz)
+		n = Blksz-fo;
+	memcpy(b->buf+fo, s, n);
+	enqueue(b);
+
+	bp.gen = fs->nextgen;
+	assert(b->flag & Bfinal);
+	packbp(m->v, &b->bp);
+	putblk(b);
+	return n;
+}
+
 static Dent*
 getdent(vlong pqid, Dir *d)
 {
@@ -74,6 +278,7 @@
 		return nil;
 	e->ref = 1;
 	e->qid = d->qid;
+	e->length = d->length;
 	e->k = e->buf;
 	e->nk = 9 + strlen(d->name) + 1;
 
@@ -104,7 +309,7 @@
 	u32int h;
 
 	if(adec(&de->ref) == 0){
-		h = (ihash(de->qid.path) ^ ihash(de->rootb)) % Ndtab;
+		h = ihash(de->qid.path) % Ndtab;
 		lock(&fs->dtablk);
 		pe = &fs->dtab[h];
 		for(e = fs->dtab[h]; e != nil; e = e->next){
@@ -138,7 +343,7 @@
 	unlock(&fs->fidtablk);
 }
 
-Fid*
+static Fid*
 getfid(u32int fid)
 {
 	u32int h;
@@ -154,7 +359,7 @@
 	return f;
 }
 
-void
+static void
 putfid(Fid *f)
 {
 	if(adec(&f->ref) != 0)
@@ -164,7 +369,7 @@
 	free(f);
 }
 
-Fid*
+static Fid*
 dupfid(int new, Fid *f)
 {
 	Fid *n, *o;
@@ -202,7 +407,7 @@
 	return n;
 }
 
-void
+static void
 clunkfid(Fid *fid)
 {
 	Fid *f, **pf;
@@ -222,19 +427,7 @@
 	unlock(&fs->fidtablk);
 }
 
-void
-fshangup(int fd, char *fmt, ...)
-{
-	va_list ap;
-
-	va_start(ap, fmt);
-	vfprint(2, fmt, ap);
-	va_end(ap);
-	close(fd);
-	abort();
-}
-
-Fmsg*
+static Fmsg*
 readmsg(int fd, int max)
 {
 	char szbuf[4];
@@ -259,91 +452,7 @@
 	return m;
 }
 
-void
-respond(Fmsg *m, Fcall *r)
-{
-	uchar buf[Max9p];
-	int w, n;
-
-	r->tag = m->tag;
-	dprint("→ %F\n", r);
-	if((n = convS2M(r, buf, sizeof(buf))) == 0)
-		abort();
-	qlock(m->wrlk);
-	w = write(m->fd, buf, n);
-	qunlock(m->wrlk);
-	if(w != n)
-		fshangup(m->fd, "failed write");
-	free(m);
-}
-
-void
-rerror(Fmsg *m, char *fmt, ...)
-{
-	char buf[128];
-	va_list ap;
-	Fcall r;
-
-	va_start(ap, fmt);
-	vsnprint(buf, sizeof(buf), fmt, ap);
-	va_end(ap);
-	r.type = Rerror;
-	r.ename = buf;
-	respond(m, &r);
-}
-
-Chan*
-mkchan(int size)
-{
-	Chan *c;
-
-	if((c = mallocz(sizeof(Chan) + size*sizeof(void*), 1)) == nil)
-		sysfatal("create channel");
-	c->size = size;
-	c->avail = size;
-	c->count = 0;
-	c->rp = c->args;
-	c->wp = c->args;
-	return c;
-
-}
-
-Fmsg*
-chrecv(Chan *c)
-{
-	void *a;
-	long v;
-
-	v = c->count;
-	if(v == 0 || cas(&c->count, v, v-1) == 0)
-		semacquire(&c->count, 1);
-	lock(&c->rl);
-	a = *c->rp;
-	if(++c->rp >= &c->args[c->size])
-		c->rp = c->args;
-	unlock(&c->rl);
-	semrelease(&c->avail, 1);
-	return a;
-}
-
-void
-chsend(Chan *c, Fmsg *m)
-{
-	long v;
-
-	v = c->avail;
-	if(v == 0 || cas(&c->avail, v, v-1) == 0)
-		semacquire(&c->avail, 1);
-	lock(&c->wl);
-	*c->wp = m;
-	if(++c->wp >= &c->args[c->size])
-		c->wp = c->args;
-	unlock(&c->wl);
-	semrelease(&c->count, 1);
-
-}
-
-void
+static void
 fsversion(Fmsg *m, int *msz)
 {
 	Fcall r;
@@ -361,7 +470,7 @@
 	respond(m, &r);
 }
 
-void
+static void
 fsauth(Fmsg *m)
 {
 	Fcall r;
@@ -371,7 +480,7 @@
 	respond(m, &r);
 }
 
-void
+static void
 fsattach(Fmsg *m, int iounit)
 {
 	char *p, *ep, dbuf[Kvmax], kvbuf[Kvmax];
@@ -506,7 +615,7 @@
 		}
 		k.k = kbuf;
 		k.nk = p - kbuf;
-		if((estr = fslookup(o, &k, &kv, kvbuf, sizeof(kvbuf), 0)) != nil){
+		if((estr = lookup(o, &k, &kv, kvbuf, sizeof(kvbuf), 0)) != nil){
 			break;
 		}
 		if(kv2dir(&kv, &d) == -1){
@@ -685,7 +794,13 @@
 	f->qpath = d.qid.path;
 	f->dent = dent;
 	wlock(f->dent);
-//	freeb(dent, 0, dent->length);
+	if((e = clearb(f, 0, dent->length)) != nil){
+		unlock(f);
+		clunkdent(dent);
+		rerror(m, e);
+		putfid(f);
+		return;
+	}
 	dent->length = 0;
 	wunlock(f->dent);
 	unlock(f);
@@ -720,7 +835,6 @@
 	mb.k = f->dent->k;
 	mb.nk = f->dent->nk;
 	mb.nv = 0;
-//showfs("preremove");
 	if((e = btupsert(&f->mnt->root, &mb, 1)) != nil){
 		runlock(f->dent);
 		rerror(m, e);
@@ -727,6 +841,12 @@
 		putfid(f);
 		return;
 	}
+	if((e = clearb(f, 0, f->dent->length)) != nil){
+		runlock(f->dent);
+		rerror(m, e);
+		putfid(f);
+		return;
+	}
 	runlock(f->dent);
 	clunkfid(f);
 
@@ -760,7 +880,7 @@
 		rerror(m, Efid);
 		return;
 	}
-	if((e = fslookup(f, f->dent, &kv, buf, sizeof(buf), 0)) != nil){
+	if((e = lookup(f, f->dent, &kv, buf, sizeof(buf), 0)) != nil){
 		rerror(m, e);
 		putfid(f);
 		return;
@@ -868,47 +988,6 @@
 	return nil;
 }
 
-int
-readb(Fid *f, char *d, vlong o, vlong n, int sz)
-{
-	char *e, buf[17], kvbuf[17+32];
-	vlong fb, fo;
-	Bptr bp;
-	Blk *b;
-	Key k;
-	Kvp kv;
-
-	if(o >= sz)
-		return 0;
-
-	fb = o & ~(Blksz-1);
-	fo = o & (Blksz-1);
-
-	k.k = buf;
-	k.nk = sizeof(buf);
-	k.k[0] = Kdat;
-	PBIT64(k.k+1, f->qpath);
-	PBIT64(k.k+9, fb);
-
-	e = fslookup(f, &k, &kv, kvbuf, sizeof(kvbuf), 0);
-	if(e != nil && e != Eexist){
-		werrstr(e);
-		return -1;
-	}
-
-	bp = unpackbp(kv.v);
-	if((b = getblk(bp, GBraw)) == nil)
-		return -1;
-	if(fo+n > Blksz)
-		n = Blksz-fo;
-	if(b != nil){
-		memcpy(d, b->buf+fo, n);
-		putblk(b);
-	}else
-		memset(d, 0, n);
-	return n;
-}
-
 char*
 fsreadfile(Fmsg *m, Fid *f, Fcall *r)
 {
@@ -983,49 +1062,7 @@
 	putfid(f);
 }
 
-int
-writeb(Fid *f, Msg *m, char *s, vlong o, vlong n, vlong sz)
-{
-	char buf[Kvmax];
-	vlong fb, fo;
-	Bptr bp;
-	Blk *b, *t;
-	Kvp kv;
 
-	fb = o & ~(Blksz-1);
-	fo = o & (Blksz-1);
-
-	m->k[0] = Kdat;
-	PBIT64(m->k+1, f->qpath);
-	PBIT64(m->k+9, fb);
-
-
-	b = newblk(Traw);
-	if(b == nil)
-		return -1;
-	if(fb < sz && (fo != 0 || n != Blksz)){
-		dprint("\tappending to block %B\n", b->bp);
-		if(fslookup(f, m, &kv, buf, sizeof(buf), 0) != nil)
-			return -1;
-		bp = unpackbp(kv.v);
-		if((t = getblk(bp, GBraw)) == nil)
-			return -1;
-		memcpy(b->buf, t->buf, Blksz);
-		freeblk(t);
-		putblk(t);
-	}
-	if(fo+n > Blksz)
-		n = Blksz-fo;
-	memcpy(b->buf+fo, s, n);
-	enqueue(b);
-
-	bp.gen = fs->nextgen;
-	assert(b->flag & Bfinal);
-	packbp(m->v, &b->bp);
-	putblk(b);
-	return n;
-}
-
 void
 fswrite(Fmsg *m)
 {
@@ -1207,7 +1244,7 @@
 quiesce(int tid)
 {
 	int i, allquiesced;
-	Blk *p, *n;
+	Bfree *p, *n;
 
 	lock(&fs->activelk);
 	allquiesced = 1;
@@ -1235,14 +1272,14 @@
 	lock(&fs->freelk);
 	p = nil;
 	if(fs->freep != nil){
-		p = fs->freep->fnext;
-		fs->freep->fnext = nil;
+		p = fs->freep->next;
+		fs->freep->next = nil;
 	}
 	unlock(&fs->freelk);
 
 	while(p != nil){
-		n = p->fnext;
-		reclaimblk(p);
+		n = p->next;
+		reclaimblk(p->bp);
 		p = n;
 	}
 	fs->freep = fs->freehd;
--- a/tree.c
+++ b/tree.c
@@ -349,10 +349,6 @@
 		p += 4;
 		PBIT32(kv->v+33, v);
 	}
-	if(m->statop & Owname){
-		fprint(2, "renames not yet supported\n");
-		abort();
-	}
 	if(p != m->v + m->nv)
 		fprint(2, "malformed wstat message");
 }
@@ -361,6 +357,7 @@
 apply(Kvp *r, Msg *m, char *buf, int nbuf)
 {
 	switch(m->op){
+	case Oclearb:
 	case Odelete:
 		assert(keycmp(r, m) == 0);
 		return 0;
@@ -406,6 +403,7 @@
 	char buf[Msgmax];
 	int i, j, o, ok, full, spc;
 	Blk *b, *n;
+	Bptr bp;
 	Msg m;
 	Kvp v;
 
@@ -448,6 +446,10 @@
 		case 0:
 			i++;
 			while(j < up->hi){
+				if(m.op == Oclearb){
+					bp = unpackbp(v.v);
+					freebp(bp);
+				}
 				ok = apply(&v, &m, buf, sizeof(buf));
 		Copy:
 				j++;