shithub: riscv

Download patch

ref: 218f7a9ec7773484202d6fffb43b53f33524104c
parent: 75d6267a5f788162d92e7a5ae126cd8b0770aa8a
author: Jacob Moody <moody@posixcafe.org>
date: Sat Apr 1 14:05:27 EDT 2023

qcowfs(8)

--- a/sys/lib/dist/mkfile
+++ b/sys/lib/dist/mkfile
@@ -21,6 +21,17 @@
 	mv $target.$pid.pc.iso $target
 	}
 
+%.amd64.qcow2:
+	@{
+	objtype=amd64
+	kernel=/n/src9/$objtype/9pc64
+	echo 'bootfile='^`{basename $kernel} > /env/plan9.ini
+	fatfiles=(/386/9bootfat /env/plan9.ini $kernel)
+	mb=3770
+	mk $target.$pid.disk
+	mv $target.$pid.disk $target
+	}
+
 %.pi.img:
 	@{
 	objtype=arm
@@ -136,9 +147,15 @@
 	@{rfork n
 	mk binds
 	rm -f $target
-	dd -if /dev/zero -of $target -bs 1048576 -oseek $mb -count 1
 	s=`{basename $target}
-	disk/partfs -m /n/$s $target
+	if(~ $target *.amd64.qcow2.*){
+		disk/qcowfs -n `{echo $mb '*1048576' | pc} $target
+		disk/partfs -m /n/$s /mnt/qcow/data
+	}
+	if not {
+		dd -if /dev/zero -of $target -bs 1048576 -oseek $mb -count 1
+		disk/partfs -m /n/$s $target
+	}
 	d=/n/$s/sdXX
 	disk/mbr $d/data
 	if(~ $target *.pi.img.* *.pi3.img.*){
@@ -168,6 +185,12 @@
 		} | disk/fdisk -b $d/data
 		disk/prep -bw -a^(nvram fs) $d/plan9
 		disk/format -d $d/dos $fatfiles
+	}
+	if not if(~ $target *.amd64.qcow2.*){
+		disk/mbr -m /386/mbr $d/data
+		disk/fdisk -baw $d/data
+		disk/prep -bw -a^(9fat nvram fs) $d/plan9
+		disk/format -b /386/pbs -d -r 2 $d/9fat $fatfiles
 	}
 	if not {
 		disk/fdisk -baw $d/data
--- /dev/null
+++ b/sys/man/8/qcowfs
@@ -1,0 +1,63 @@
+.TH QCOWFS 8
+.SH NAME
+qcowfs \- QCOW2 file system
+.SH SYNOPSIS
+.B disk/qcowfs
+[
+.B -n
+.I size
+]
+[
+.B -m
+.I mtpt
+]
+[
+.B -s
+.I service
+]
+.I diskimage
+.SH DESCRIPTION
+.I Qcowfs
+exposes a
+.B data
+file using the provided
+.I diskimage
+as the backing store.
+.PP
+The
+.B -n
+flag truncates
+.I imagefile
+with the specified
+.I size
+(in bytes) before using it.
+The
+.B -m
+flag sets the
+.I mtpt
+(default /mnt/qcow).
+The
+.B -s
+flag causes
+.I qcowfs
+to post its 9P service as
+.BI /srv/ service \fR.
+.SH EXAMPLES
+Create a new QCOW2 diskimage and partition it
+.IP
+.EX
+disk/qcowfs -n $size image.qcow2
+disk/partfs /mnt/qcow/data
+disk/mbr -m /386/mbr /dev/sdXX/data
+disk/fdisk -baw /dev/sdXX/data
+disk/prep /dev/sdXX/plan9
+.EE
+.SH SOURCE
+.B /sys/src/cmd/disk/qcowfs.c
+.SH SEE ALSO
+.IR partfs (8),
+.IR disksim (8),
+.IR prep (8)
+.SH HISTORY
+.I qcowfs
+first appeared in 9front (April, 2023).
--- a/sys/src/cmd/disk/mkfile
+++ b/sys/src/cmd/disk/mkfile
@@ -7,6 +7,7 @@
 	mkfs\
 	partfs\
 	cryptsetup\
+	qcowfs\
 
 DIRS=\
 	9660\
--- /dev/null
+++ b/sys/src/cmd/disk/qcowfs.c
@@ -1,0 +1,603 @@
+/* Adapted from OpenBSD's src/usr.sbin/vmd/vioqcow2.c */
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+typedef struct Header Header;
+typedef struct Disk Disk;
+
+struct Header {
+	char magic[4];
+	u32int version;
+	u64int backingoff;
+	u32int backingsz;
+	u32int clustershift;
+	u64int disksz;
+	u32int cryptmethod;
+	u32int l1sz;
+	u64int l1off;
+	u64int refoff;
+	u32int refsz;
+	u32int snapcount;
+	u64int snapsz;
+	/* v3 additions */
+	u64int incompatfeatures;
+	u64int compatfeatures;
+	u64int autoclearfeatures;
+	u32int reforder;	/* Bits = 1 << reforder */
+	u32int headersz;
+};
+
+#define QCOW2_COMPRESSED	0x4000000000000000ull
+#define QCOW2_INPLACE		0x8000000000000000ull
+char *MAGIC_QCOW		= "QFI\xfb";
+enum{
+	QCOW2_DIRTY		= 1 << 0,
+	QCOW2_CORRUPT		= 1 << 1,
+
+	ICFEATURE_DIRTY		= 1 << 0,
+	ICFEATURE_CORRUPT	= 1 << 1,
+
+	ACFEATURE_BITEXT	= 1 << 0,
+
+	HDRSZ = 4 + 4 + 8 + 4 + 4 + 8 + 4 + 4 + 8 + 8 + 4 + 4 + 8 + 8 + 8 + 8 + 4 + 4,
+};
+
+struct Disk {
+	RWLock lock;
+	Disk *base;
+	Header h;
+
+	int       fd;
+	u64int *l1;
+	s64int     end;
+	s64int	  clustersz;
+	s64int	  disksz; /* In bytes */
+	u32int  cryptmethod;
+
+	u32int l1sz;
+	s64int	 l1off;
+
+	s64int	 refoff;
+	s64int	 refsz;
+
+	u32int nsnap;
+	s64int	 snapoff;
+
+	/* v3 features */
+	u64int incompatfeatures;
+	u64int autoclearfeatures;
+	u32int refssz;
+	u32int headersz;
+};
+
+#define PUT2(p, u) (p)[0] = (u)>>8, (p)[1] = (u)
+#define GET2(p) (u16int)(p)[2] | (u16int)(p)[1]<<8
+#define PUT4(p, u) (p)[0] = (u)>>24, (p)[1] = (u)>>16, (p)[2] = (u)>>8, (p)[3] = (u)
+#define GET4(p)	(u32int)(p)[3] | (u32int)(p)[2]<<8 | (u32int)(p)[1]<<16 | (u32int)(p)[0]<<24
+
+#define PUT8(p, u) (p)[0] = (u)>>56, (p)[1] = (u)>>48, (p)[2] = (u)>>40, (p)[3] = (u)>>32, \
+	(p)[4] = (u)>>24, (p)[5] = (u)>>16, (p)[6] = (u)>>8, (p)[7] = (u)
+
+#define GET8(p)	(u64int)(p)[7] | (u64int)(p)[6]<<8 | (u64int)(p)[5]<<16 | (u64int)(p)[4]<<24 | \
+	(u64int)(p)[3]<<32 | (u64int)(p)[2]<<40 | (u64int)(p)[1]<<48 | (u64int)(p)[0]<<56
+
+int
+ftruncate(int fd, s64int length)
+{
+	Dir d;
+
+	if(length < 0)
+		return -1;
+	nulldir(&d);
+	d.length = length;
+	if(dirfwstat(fd, &d) < 0)
+		return -1;
+	return 0;
+}
+
+static void
+writehdr(Header *src, int fd)
+{
+	uchar store[HDRSZ];
+	uchar *buf = store;
+
+	memcpy(buf, src->magic, strlen(src->magic)); buf += 4;
+	PUT4(buf, src->version); buf += 4;
+
+	PUT8(buf, src->backingoff); buf += 8;
+	PUT4(buf, src->backingsz); buf += 4;
+	PUT4(buf, src->clustershift); buf += 4;
+	PUT8(buf, src->disksz); buf += 8;
+	PUT4(buf, src->cryptmethod); buf += 4;
+	PUT4(buf, src->l1sz); buf += 4;
+	PUT8(buf, src->l1off); buf += 8;
+	PUT8(buf, src->refoff); buf += 8;
+	PUT4(buf, src->refsz); buf += 4;
+	PUT4(buf, src->snapcount); buf += 4;
+	PUT8(buf, src->snapsz); buf += 8;
+	PUT8(buf, src->incompatfeatures); buf += 8;
+	PUT8(buf, src->compatfeatures); buf += 8;
+	PUT8(buf, src->autoclearfeatures); buf += 8;
+	PUT4(buf, src->reforder); buf += 4;
+	PUT4(buf, src->headersz);
+
+	if(write(fd, store, sizeof store) != sizeof store)
+		sysfatal("writehdr: %r");
+}
+
+static void
+readhdr(Header *dst, int fd)
+{
+	uchar store[HDRSZ];
+	uchar *buf = store;
+
+	if(readn(fd, store, sizeof store) != sizeof store)
+		sysfatal("short read on header: %r");
+	if(memcmp(MAGIC_QCOW, buf, strlen(MAGIC_QCOW)) != 0)
+		sysfatal("invalid magic");
+	buf += 4;
+
+	dst->version = GET4(buf);
+	if(dst->version != 2 && dst->version != 3)
+		sysfatal("unsupported version: %d", dst->version);
+	buf += 4;
+
+	dst->backingoff = GET8(buf); buf += 8;
+	dst->backingsz = GET4(buf); buf += 4;
+	dst->clustershift = GET4(buf); buf += 4;
+	dst->disksz = GET8(buf); buf += 8;
+	dst->cryptmethod = GET4(buf); buf += 4;
+	dst->l1sz = GET4(buf); buf += 4;
+	dst->l1off = GET8(buf); buf += 8;
+	dst->refoff = GET8(buf); buf += 8;
+	dst->refsz = GET4(buf); buf += 4;
+	dst->snapcount = GET4(buf); buf += 4;
+	dst->snapsz = GET8(buf); buf += 8;
+	dst->incompatfeatures = GET8(buf); buf += 8;
+	dst->compatfeatures = GET8(buf); buf += 8;
+	dst->autoclearfeatures = GET8(buf); buf += 8;
+	dst->reforder = GET4(buf); buf += 4;
+	dst->headersz = GET4(buf);
+}
+
+#define ALIGNSZ(sz, align)	((sz + align - 1) & ~(align - 1))
+
+static void
+qc2create(int fd, u64int disksz)
+{
+	Header hdr;
+	s64int base_len;
+	u64int l1sz, refsz, initsz, clustersz;
+	u64int l1off, refoff, i, l1entrysz, refentrysz;
+	uchar v[8], v2[2];
+
+	clustersz = 1<<16;
+	l1off = ALIGNSZ(HDRSZ, clustersz);
+
+	l1entrysz = clustersz * clustersz / 8;
+	l1sz = (disksz + l1entrysz - 1) / l1entrysz;
+
+	refoff = ALIGNSZ(l1off + 8*l1sz, clustersz);
+	refentrysz = clustersz * clustersz * clustersz / 2;
+	refsz = (disksz + refentrysz - 1) / refentrysz;
+
+	initsz = ALIGNSZ(refoff + refsz*clustersz, clustersz);
+	base_len = 0;
+
+	memcpy(hdr.magic, MAGIC_QCOW, strlen(MAGIC_QCOW));
+	hdr.version		= 3;
+	hdr.backingoff		= 0;
+	hdr.backingsz		= base_len;
+	hdr.clustershift	= 16;
+	hdr.disksz		= disksz;
+	hdr.cryptmethod		= 0;
+	hdr.l1sz		= l1sz;
+	hdr.l1off		= l1off;
+	hdr.refoff		= refoff;
+	hdr.refsz		= refsz;
+	hdr.snapcount		= 0;
+	hdr.snapsz		= 0;
+	hdr.incompatfeatures	= 0;
+	hdr.compatfeatures	= 0;
+	hdr.autoclearfeatures	= 0;
+	hdr.reforder		= 4;
+	hdr.headersz		= HDRSZ;
+
+	writehdr(&hdr, fd);
+	if(ftruncate(fd, (s64int)initsz + clustersz) == -1)
+		sysfatal("ftruncate: %r");
+
+	assert(initsz/clustersz < clustersz/2);
+
+	PUT8(v, initsz);
+	if(pwrite(fd, v, sizeof v, refoff) != sizeof v)
+		sysfatal("q2create: pwrite: %r");
+
+	for(i=0; i < initsz/clustersz + 1; i++){
+		PUT2(v2, 1);
+		if(pwrite(fd, v2, sizeof v2, initsz + 2*i) != sizeof v2)
+			sysfatal("q2create: pwrite: %r");
+	}
+}
+
+static void
+qc2open(Disk *disk, int fd)
+{
+	int i;
+	Dir *d;
+	uchar buf[8];
+
+	disk->fd = fd;
+	disk->base = nil;
+	disk->l1 = nil;
+	readhdr(&disk->h, disk->fd);
+
+	disk->clustersz = 1ull << disk->h.clustershift;
+	disk->disksz = disk->h.disksz;
+	disk->cryptmethod = disk->h.cryptmethod;
+	disk->l1sz = disk->h.l1sz;
+	disk->l1off = disk->h.l1off;
+	disk->refsz = disk->h.refsz;
+	disk->refoff = disk->h.refoff;
+	disk->nsnap = disk->h.snapcount;
+	disk->snapoff = disk->h.snapsz;
+
+	disk->incompatfeatures = disk->h.incompatfeatures;
+	disk->autoclearfeatures = disk->h.autoclearfeatures;
+	disk->refssz = disk->h.refsz;
+	disk->headersz = disk->h.headersz;
+
+	if(disk->h.reforder != 4)
+		sysfatal("unsupoprted refcount size %d", disk->h.reforder);
+
+	disk->l1 = mallocz(disk->l1sz * 8, 1);
+	pread(disk->fd, disk->l1, disk->l1sz * 8, disk->l1off);
+	for(i = 0; i < disk->l1sz; i++){
+		memcpy(buf, disk->l1 + i, sizeof buf);
+		disk->l1[i] = GET8(buf);
+	}
+
+	d = dirfstat(fd);
+	if(d == nil)
+		sysfatal("dirfstat: %r");
+	disk->end = d->length;
+	free(d);
+}
+
+static u64int
+xlate(Disk *disk, s64int off, int *inplace)
+{
+	s64int l2sz, l1off, l2tab, l2off, cluster, clusteroff;
+	uchar buf[8];
+
+	/*
+	 * Clear out inplace flag -- xlate misses should not
+	 * be flagged as updatable in place. We will still
+	 * return 0 from them, but this leaves less surprises
+	 * in the API.
+	 */
+	if (inplace)
+		*inplace = 0;
+	rlock(&disk->lock);
+	if (off < 0)
+		goto err;
+
+	l2sz = disk->clustersz / 8;
+	l1off = (off / disk->clustersz) / l2sz;
+	if (l1off >= disk->l1sz)
+		goto err;
+
+	l2tab = disk->l1[l1off];
+	l2tab &= ~QCOW2_INPLACE;
+	if (l2tab == 0) {
+		runlock(&disk->lock);
+		return 0;
+	}
+	l2off = (off / disk->clustersz) % l2sz;
+	pread(disk->fd, buf, sizeof(buf), l2tab + l2off * 8);
+	cluster = GET8(buf);
+	/*
+	 * cluster may be 0, but all future operations don't affect
+	 * the return value.
+	 */
+	if (inplace)
+		*inplace = !!(cluster & QCOW2_INPLACE);
+	if (cluster & QCOW2_COMPRESSED)
+		sysfatal("xlate: compressed clusters unsupported");
+	runlock(&disk->lock);
+	clusteroff = 0;
+	cluster &= ~QCOW2_INPLACE;
+	if (cluster)
+		clusteroff = off % disk->clustersz;
+	return cluster + clusteroff;
+err:
+	runlock(&disk->lock);
+	return -1;
+}
+
+static void
+inc_refs(Disk *disk, s64int off, int newcluster)
+{
+	s64int l1off, l1idx, l2idx, l2cluster;
+	u64int nper;
+	u16int refs;
+	uchar buf[8], buf2[2];
+
+	off &= ~QCOW2_INPLACE;
+	nper = disk->clustersz / 2;
+	l1idx = (off / disk->clustersz) / nper;
+	l2idx = (off / disk->clustersz) % nper;
+	l1off = disk->refoff + 8 * l1idx;
+	if (pread(disk->fd, buf, sizeof(buf), l1off) != 8)
+		sysfatal("could not read refs");
+
+	l2cluster = GET8(buf);
+	if (l2cluster == 0) {
+		l2cluster = disk->end;
+		disk->end += disk->clustersz;
+		if (ftruncate(disk->fd, disk->end) < 0)
+			sysfatal("inc_refs: failed to allocate ref block");
+		PUT8(buf, l2cluster);
+		if (pwrite(disk->fd, buf, sizeof(buf), l1off) != 8)
+			sysfatal("inc_refs: failed to write ref block");
+	}
+
+	refs = 1;
+	if (!newcluster) {
+		if (pread(disk->fd, buf2, sizeof buf2,
+		    l2cluster + 2 * l2idx) != 2)
+			sysfatal("could not read ref cluster");
+		refs = GET2(buf2) + 1;
+	}
+	PUT2(buf2, refs);
+	if (pwrite(disk->fd, buf2, sizeof buf2, l2cluster + 2 * l2idx) != 2)
+		sysfatal("inc_refs: could not write ref block");
+}
+
+static void
+copy_cluster(Disk *disk, Disk *base, u64int dst, u64int src)
+{
+	char *scratch;
+
+	scratch = malloc(disk->clustersz);
+	if(!scratch)
+		sysfatal("out of memory");
+	src &= ~(disk->clustersz - 1);
+	dst &= ~(disk->clustersz - 1);
+	if(pread(base->fd, scratch, disk->clustersz, src) == -1)
+		sysfatal("copy_cluster: could not read cluster");
+	if(pwrite(disk->fd, scratch, disk->clustersz, dst) == -1)
+		sysfatal("copy_cluster: could not write cluster");
+	free(scratch);
+}
+
+/*
+ * Allocates a new cluster on disk, creating a new L2 table
+ * if needed. The cluster starts off with a refs of one,
+ * and the writable bit set.
+ *
+ * Returns -1 on error, and the physical address within the
+ * cluster of the write offset if it exists.
+ */
+static s64int
+mkcluster(Disk *disk, Disk *base, s64int off, s64int src_phys)
+{
+	s64int l2sz, l1off, l2tab, l2off, cluster, clusteroff, orig;
+	uchar buf[8];
+
+	wlock(&disk->lock);
+
+	/* L1 entries always exist */
+	l2sz = disk->clustersz / 8;
+	l1off = off / (disk->clustersz * l2sz);
+	if (l1off >= disk->l1sz)
+		sysfatal("l1 offset outside disk");
+
+	disk->end = (disk->end + disk->clustersz - 1) & ~(disk->clustersz - 1);
+
+	l2tab = disk->l1[l1off];
+	l2off = (off / disk->clustersz) % l2sz;
+	/* We may need to create or clone an L2 entry to map the block */
+	if (l2tab == 0 || (l2tab & QCOW2_INPLACE) == 0) {
+		orig = l2tab & ~QCOW2_INPLACE;
+		l2tab = disk->end;
+		disk->end += disk->clustersz;
+		if (ftruncate(disk->fd, disk->end) == -1)
+			sysfatal("mkcluster: ftruncate failed");
+
+		/*
+		 * If we translated, found a L2 entry, but it needed to
+		 * be copied, copy it.
+		 */
+		if (orig != 0)
+			copy_cluster(disk, disk, l2tab, orig);
+		/* Update l1 -- we flush it later */
+		disk->l1[l1off] = l2tab | QCOW2_INPLACE;
+		inc_refs(disk, l2tab, 1);
+	}
+	l2tab &= ~QCOW2_INPLACE;
+
+	/* Grow the disk */
+	if (ftruncate(disk->fd, disk->end + disk->clustersz) < 0)
+		sysfatal("mkcluster: could not grow disk");
+	if (src_phys > 0)
+		copy_cluster(disk, base, disk->end, src_phys);
+	cluster = disk->end;
+	disk->end += disk->clustersz;
+	PUT8(buf, cluster | QCOW2_INPLACE);
+	if (pwrite(disk->fd, buf, sizeof(buf), l2tab + l2off * 8) != 8)
+		sysfatal("mkcluster: could not write cluster");
+
+	PUT8(buf, disk->l1[l1off]);
+	if (pwrite(disk->fd, buf, sizeof(buf), disk->l1off + 8 * l1off) != 8)
+		sysfatal("mkcluster: could not write l1");
+	inc_refs(disk, cluster, 1);
+
+	wunlock(&disk->lock);
+	clusteroff = off % disk->clustersz;
+	if (cluster + clusteroff < disk->clustersz)
+		sysfatal("write would clobber header");
+	return cluster + clusteroff;
+}
+
+static void
+fsread(Req *r)
+{
+	char *buf;
+	Disk *disk, *d;
+	s64int off, phys_off, end, cluster_off;
+	u64int len, sz, rem;
+
+	off = r->ifcall.offset;
+	buf = r->ofcall.data;
+	len = r->ifcall.count;
+	disk = d = r->fid->file->aux;
+
+	end = off + len;
+	if(end > d->disksz)
+		len -= end - d->disksz;
+
+	rem = len;
+	while(rem != 0){
+		phys_off = xlate(d, off, nil);
+		if(phys_off <= 0)
+			d = nil;
+		cluster_off = off % disk->clustersz;
+		sz = disk->clustersz - cluster_off;
+		if(sz > rem)
+			sz = rem;
+
+		if(!d)
+			memset(buf, 0, sz);
+		else
+			sz = pread(d->fd, buf, sz, phys_off);
+		off += sz;
+		buf += sz;
+		rem -= sz;
+	}
+	r->ofcall.count = len;
+	respond(r, nil);
+}
+
+static void
+fswrite(Req *r)
+{
+	char *buf;
+	Disk *d;
+	s64int off, phys_off, end, cluster_off;
+	u64int len, sz, rem;
+	int inplace;
+
+	off = r->ifcall.offset;
+	buf = r->ifcall.data;
+	len = r->ifcall.count;
+	d = r->fid->file->aux;
+	inplace = 1;
+
+	end = off + len;
+	if(end > d->disksz){
+		respond(r, "end of device");
+		return;
+	}
+
+	rem = len;
+	while(off != end){
+		cluster_off = off % d->clustersz;
+		sz = d->clustersz - cluster_off;
+		if(sz > rem)
+			sz = rem;
+		phys_off = xlate(d, off, nil);
+		if(phys_off == -1){
+			respond(r, "xlate error");
+			return;
+		}
+
+		if(!inplace || phys_off == 0)
+			phys_off = mkcluster(d, d, off, phys_off);
+		if(phys_off == -1){
+			respond(r, "mkcluster error");
+			return;
+		}
+		if(phys_off < d->clustersz)
+			sysfatal("fswrite: writing reserved cluster");
+		if(pwrite(d->fd, buf, sz, phys_off) != sz){
+			respond(r, "phase error");
+			return;
+		}
+		off += sz;
+		buf += sz;
+		rem -= sz;
+	}
+
+	r->ofcall.count = len;
+	respond(r, nil);
+}
+
+Srv fs = {
+.read = fsread,
+.write = fswrite,
+};
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s [-s srv] [-m mntpt ] [-n size] file\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	int fd;
+	char *uid;
+	File *f;
+	Disk *d;
+	uvlong size;
+	int nflag;
+	char *mntpt = "/mnt/qcow";
+	char *srvname = nil;
+
+	size = 0;
+	nflag = 0;
+	ARGBEGIN{
+	case 'm':
+		mntpt = EARGF(usage());
+		break;
+	case 'n':
+		size = strtoull(EARGF(usage()), nil, 0);
+		nflag++;
+		break;
+	case 's':
+		srvname = EARGF(usage());
+		break;
+	default:
+		usage();
+		break;
+	}ARGEND
+	if(argc < 1)
+		usage();
+
+	if(nflag){
+		if((fd = create(argv[0], ORDWR, 0666)) < 0)
+			sysfatal("create: %r");
+		qc2create(fd, size);
+		seek(fd, 0, 0);
+	} else if((fd = open(argv[0], ORDWR)) < 0)
+			sysfatal("open: %r");
+
+	uid = getuser();
+	fs.tree = alloctree(uid, uid, 0755, nil);
+	if(fs.tree == nil)
+		sysfatal("alloctree: %r");
+
+	f = createfile(fs.tree->root, "data", uid, 0666, nil);
+	d = mallocz(sizeof(Disk), 1);
+	qc2open(d, fd);
+	f->aux = d;
+	f->length = d->disksz;
+	postmountsrv(&fs, srvname, mntpt, MREPL);
+	exits(nil);
+}