code: purgatorio

ref: 82a3f55c5fb7b9f7a82449e4eb943c535ec3e491
dir: /appl/cmd/memfs.b/

View raw version
implement MemFS;

include "sys.m";
	sys: Sys;
	OTRUNC, ORCLOSE, OREAD, OWRITE: import Sys;
include "styx.m";
	styx: Styx;
	Tmsg, Rmsg: import styx;
include "styxlib.m";
	styxlib: Styxlib;
	Styxserver: import styxlib;
include "draw.m";
include "arg.m";

MemFS: module {
	init: fn(ctxt: ref Draw->Context, args: list of string);
};


blksz : con 512;
Efull : con "filesystem full";

Memfile : adt {
	name : string;
	owner : string;
	qid : Sys->Qid;
	perm : int;
	atime : int;
	mtime : int;
	nopen : int;
	data : array of array of byte;			# allocated in blks, no holes
	length : int;
	parent : cyclic ref Memfile;	# Dir entry linkage
	kids : cyclic ref Memfile;
	prev : cyclic ref Memfile;
	next : cyclic ref Memfile;
	hashnext : cyclic ref Memfile;	# Qid hash linkage
};

Qidhash : adt {
	buckets : array of ref Memfile;
	nextqid : int;
	new : fn () : ref Qidhash;
	add : fn (h : self ref Qidhash, mf : ref Memfile);
	remove : fn (h : self ref Qidhash, mf : ref Memfile);
	lookup : fn (h : self ref Qidhash, qid : Sys->Qid) : ref Memfile;
};

timefd: ref Sys->FD;

init(nil: ref Draw->Context, argv: list of string)
{
	sys = load Sys Sys->PATH;
	styx = checkload(load Styx Styx->PATH, Styx->PATH);
	styxlib = checkload(load Styxlib Styxlib->PATH, Styxlib->PATH);
	arg := checkload(load Arg Arg->PATH, Arg->PATH);

	amode := Sys->MREPL;
	maxsz := 16r7fffffff;
	srv := 0;
	mntpt := "/tmp";

	arg->init(argv);
	arg->setusage("memfs [-s] [-rab] [-m size] [mountpoint]");
	while((opt := arg->opt()) != 0) {
		case opt{
		's' =>
			srv = 1;
		'r' =>
			amode = Sys->MREPL;
		'a' =>
			amode = Sys->MAFTER;
		'b' =>
			amode = Sys->MBEFORE;
		'm' =>
			maxsz = int arg->earg();
		* =>
			arg->usage();
		}
	}
	argv = arg->argv();
	arg = nil;
	if (argv != nil)
		mntpt = hd argv;

	srvfd: ref Sys->FD;
	mntfd: ref Sys->FD;
	if (srv)
		srvfd = sys->fildes(0);
	else {
		p := array [2] of ref Sys->FD;
		if (sys->pipe(p) == -1)
			error(sys->sprint("cannot create pipe: %r"));
		mntfd = p[0];
		srvfd = p[1];
	}
	styx->init();
	styxlib->init(styx);
	timefd = sys->open("/dev/time", sys->OREAD);

	(tc, styxsrv) := Styxserver.new(srvfd);
	if (srv)
		memfs(maxsz, tc, styxsrv, nil);
	else {
		sync := chan of int;
		spawn memfs(maxsz, tc, styxsrv, sync);
		<-sync;
		if (sys->mount(mntfd, nil, mntpt, amode | Sys->MCREATE, nil) == -1)
			error(sys->sprint("failed to mount onto %s: %r", mntpt));
	}
}

checkload[T](x: T, p: string): T
{
	if(x == nil)
		error(sys->sprint("cannot load %s: %r", p));
	return x;
}

stderr(): ref Sys->FD
{
	return sys->fildes(2);
}

error(e: string)
{
	sys->fprint(stderr(), "memfs: %s\n", e);
	raise "fail:error";
}

freeblks: int;

memfs(maxsz : int, tc : chan of ref Tmsg, srv : ref Styxserver, sync: chan of int)
{
	sys->pctl(Sys->NEWNS, nil);
	if (sync != nil)
		sync <-= 1;
	freeblks = (maxsz / blksz);
	qhash := Qidhash.new();

	# init root
	root := newmf(qhash, nil, "memfs", srv.uname, 8r755 | Sys->DMDIR);
	root.parent = root;

	while((tmsg := <-tc) != nil) {
#		sys->print("%s\n", tmsg.text());
	Msg:
		pick tm := tmsg {
		Readerror =>
			break;
		Version =>
			srv.devversion(tm);
		Auth =>
			srv.devauth(tm);
		Flush =>
			srv.reply(ref Rmsg.Flush(tm.tag));
		Walk =>
			(err, c, mf) := fidtomf(srv, qhash, tm.fid);
			if (err != "") {
				srv.reply(ref Rmsg.Error(tm.tag, err));
				continue;
			}
			nc: ref styxlib->Chan;
			if (tm.newfid != tm.fid) {
				nc = srv.clone(c, tm.newfid);
				if (nc == nil) {
					srv.reply(ref Rmsg.Error(tm.tag, "fid in use"));
					continue;
				}
				c = nc;
			}
			qids: array of Sys->Qid;
			if (len tm.names > 0) {
				oqid := c.qid;
				opath := c.path;
				qids = array[len tm.names] of Sys->Qid;
				wmf := mf;
				for (i := 0; i < len tm.names; i++) {
					wmf = dirlookup(wmf, tm.names[i]);
					if (wmf == nil) {
						if (nc == nil) {
							c.qid = oqid;
							c.path = opath;
						} else
							srv.chanfree(nc);
						if (i == 0)
							srv.reply(ref Rmsg.Error(tm.tag, Styxlib->Enotfound));
						else
							srv.reply(ref Rmsg.Walk(tm.tag, qids[0:i]));
						break Msg;
					}
					c.qid = wmf.qid;
					qids[i] = wmf.qid;
				}
			}
			srv.reply(ref Rmsg.Walk(tm.tag, qids));
		Open =>
			(err, c, mf) := fidtomf(srv, qhash, tm.fid);
			if (err == "" && c.open)
				err = Styxlib->Eopen;
			if (err == "" && !modeok(tm.mode, mf.perm, c.uname, mf.owner))
				err = Styxlib->Eperm;
			if (err == "" && (mf.perm & Sys->DMDIR) && (tm.mode & (OTRUNC|OWRITE|ORCLOSE)))
				err = Styxlib->Eperm;
			if (err == "" && (tm.mode & ORCLOSE)) {
				p := mf.parent;
				if (p == nil || !modeok(OWRITE, p.perm, c.uname, p.owner))
					err = Styxlib->Eperm;
			}

			if (err != "") {
				srv.reply(ref Rmsg.Error(tm.tag, err));
				continue;
			}

			c.open = 1;
			c.mode = tm.mode;
			c.qid.vers = mf.qid.vers;
			mf.nopen++;
			if ((tm.mode & OTRUNC) && !(mf.perm & Sys->DMAPPEND)) {
				# OTRUNC cannot be set for a directory
				# always at least one blk so don't need to check fs limit
				freeblks += (len mf.data);
				mf.data = nil;
				freeblks--;
				mf.data = array[1] of {* => array [blksz] of byte};
				mf.length = 0;
				mf.mtime = now();
			}
			srv.reply(ref Rmsg.Open(tm.tag, mf.qid, Styx->MAXFDATA));
		Create =>
			(err, c, parent) := fidtomf(srv, qhash, tm.fid);
			if (err == "" && c.open)
				err = Styxlib->Eopen;
			if (err == "" && !(parent.qid.qtype & Sys->QTDIR))
				err = Styxlib->Enotdir;
			if (err == "" && !modeok(OWRITE, parent.perm, c.uname, parent.owner))
				err = Styxlib->Eperm;
			if (err == "" && (tm.perm & Sys->DMDIR) && (tm.mode & (OTRUNC|OWRITE|ORCLOSE)))
				err = Styxlib->Eperm;
			if (err == "" && dirlookup(parent, tm.name) != nil)
				err = Styxlib->Eexists;

			if (err != "") {
				srv.reply(ref Rmsg.Error(tm.tag, err));
				continue;
			}

			isdir := tm.perm & Sys->DMDIR;
			if (!isdir && freeblks <= 0) {
				srv.reply(ref Rmsg.Error(tm.tag, Efull));
				continue;
			}

			# modify perms as per Styx specification...
			perm : int;
			if (isdir)
				perm = (tm.perm&~8r777) | (parent.perm&tm.perm&8r777);
			else
				perm = (tm.perm&(~8r777|8r111)) | (parent.perm&tm.perm& 8r666);

			nmf := newmf(qhash, parent, tm.name, c.uname, perm);
			if (!isdir) {
				freeblks--;
				nmf.data = array[1] of {* => array [blksz] of byte};
			}

			# link in the new MemFile
			nmf.next = parent.kids;
			if (parent.kids != nil)
				parent.kids.prev = nmf;
			parent.kids = nmf;

			c.open = 1;
			c.mode = tm.mode;
			c.qid = nmf.qid;
			nmf.nopen = 1;
			srv.reply(ref Rmsg.Create(tm.tag, nmf.qid, Styx->MAXFDATA));
		Read =>
			(err, c, mf) := fidtomf(srv, qhash, tm.fid);
			if (err == "" && !c.open)
				err = Styxlib->Ebadfid;

			if (err != "") {
				srv.reply(ref Rmsg.Error(tm.tag, err));
				continue;
			}
			data: array of byte = nil;
			if (mf.perm & Sys->DMDIR)
				data = dirdata(mf, int tm.offset, tm.count);
			else
				data = filedata(mf, int tm.offset, tm.count);
			mf.atime = now();
			srv.reply(ref Rmsg.Read(tm.tag, data));
		Write =>
			(err, c, mf) := fidtomf(srv, qhash, tm.fid);
			if (c != nil && !c.open)
				err = Styxlib->Ebadfid;
			if (err == nil && (mf.perm & Sys->DMDIR))
				err = Styxlib->Eperm;
			if (err == nil)
				err = writefile(mf, int tm.offset, tm.data);
			if (err != nil) {
				srv.reply(ref Rmsg.Error(tm.tag, err));
				continue;
			}
			srv.reply(ref Rmsg.Write(tm.tag, len tm.data));
		Clunk =>
			(err, c, mf) := fidtomf(srv, qhash, tm.fid);
			if (c != nil)
				srv.chanfree(c);
			if (err != nil) {
				srv.reply(ref Rmsg.Error(tm.tag, err));
				continue;
			}
			if (c.open) {
				if (c.mode & ORCLOSE)
					unlink(mf);
				mf.nopen--;
				freeblks += delfile(qhash, mf);
			}
			srv.reply(ref Rmsg.Clunk(tm.tag));
		Stat =>
			(err, nil, mf) := fidtomf(srv, qhash, tm.fid);
			if (err != nil) {
				srv.reply(ref Rmsg.Error(tm.tag, err));
				continue;
			}
			srv.reply(ref Rmsg.Stat(tm.tag, fileinfo(mf)));
		Remove =>
			(err, c, mf) := fidtomf(srv, qhash, tm.fid);
			if (err != nil) {
				srv.reply(ref Rmsg.Error(tm.tag, err));
				continue;
			}
			srv.chanfree(c);
			parent := mf.parent;
			if (!modeok(OWRITE, parent.perm, c.uname, parent.owner))
				err = Styxlib->Eperm;
			if (err == "" && (mf.perm & Sys->DMDIR) && mf.kids != nil)
				err = "directory not empty";
			if (err == "" && mf == root)
				err = "root directory";
			if (err != nil) {
				srv.reply(ref Rmsg.Error(tm.tag, err));
				continue;
			}

			unlink(mf);
			if (c.open)
				mf.nopen--;
			freeblks += delfile(qhash, mf);
			srv.reply(ref Rmsg.Remove(tm.tag));
		Wstat =>
			(err, c, mf) := fidtomf(srv, qhash, tm.fid);
			stat := tm.stat;

			if (err == nil && stat.name != mf.name) {
				parent := mf.parent;
				if (!modeok(OWRITE, parent.perm, c.uname, parent.owner))
					err = Styxlib->Eperm;
				else if (dirlookup(parent, stat.name) != nil)
					err = Styxlib->Eexists;
			}
			if (err == nil && (stat.mode != mf.perm || stat.mtime != mf.mtime)) {
				if (c.uname != mf.owner)
					err = Styxlib->Eperm;
			}
			if (err != nil) {
				srv.reply(ref Rmsg.Error(tm.tag, err));
				continue;
			}
			isdir := mf.perm & Sys->DMDIR;
			if(stat.name != nil)
				mf.name = stat.name;
			if(stat.mode != ~0)
				mf.perm = stat.mode | isdir;
			if(stat.mtime != ~0)
				mf.mtime = stat.mtime;
 			if(stat.uid != nil)
 				mf.owner = stat.uid;
			t := now();
			mf.atime = t;
			mf.parent.mtime = t;
			# not supporting group id at the moment
			srv.reply(ref Rmsg.Wstat(tm.tag));
		Attach =>
			c := srv.newchan(tm.fid);
			if (c == nil) {
				srv.reply(ref Rmsg.Error(tm.tag, Styxlib->Einuse));
				continue;
			}
			c.uname = tm.uname;
			c.qid = root.qid;
			srv.reply(ref Rmsg.Attach(tm.tag, c.qid));
		}
	}
}

writefile(mf: ref Memfile, offset: int, data: array of byte): string
{
	if(mf.perm & Sys->DMAPPEND)
		offset = mf.length;
	startblk := offset/blksz;
	nblks := ((len data + offset) - (startblk * blksz))/blksz;
	lastblk := startblk + nblks;
	need := lastblk + 1 - len mf.data;
	if (need > 0) {
		if (need > freeblks)
			return Efull;
		mf.data = (array [lastblk+1] of array of byte)[:] = mf.data;
		freeblks -= need;
	}
	mf.length = max(mf.length, offset + len data);

	# handle (possibly incomplete first block) separately
	offset %= blksz;
	end := min(blksz-offset, len data);
	if (mf.data[startblk] == nil)
		mf.data[startblk] = array [blksz] of byte;
	mf.data[startblk++][offset:] = data[:end];

	ix := blksz - offset;
	while (ix < len data) {
		if (mf.data[startblk] == nil)
			mf.data[startblk] = array [blksz] of byte;
		end = min(ix+blksz,len data);
		mf.data[startblk++][:] = data[ix:end];
		ix += blksz;
	}
	mf.mtime = now();
	return nil;
}

filedata(mf: ref Memfile, offset, n: int): array of byte
{
	if (offset +n > mf.length)
		n = mf.length - offset;
	if (n == 0)
		return nil;

	data := array [n] of byte;
	startblk := offset/blksz;
	offset %= blksz;
	rn := min(blksz - offset, n);
	data[:] = mf.data[startblk++][offset:offset+rn];
	ix := blksz - offset;
	while (ix < n) {
		rn = blksz;
		if (ix+rn > n)
			rn = n - ix;
		data[ix:] = mf.data[startblk++][:rn];
		ix += blksz;
	}
	return data;
}

QHSIZE: con 256;
QHMASK: con QHSIZE-1;

Qidhash.new() : ref Qidhash
{
	qh := ref Qidhash;
	qh.buckets = array [QHSIZE] of ref Memfile;
	qh.nextqid = 0;
	return qh;
}

Qidhash.add(h : self ref Qidhash, mf : ref Memfile)
{
	path := h.nextqid++;
	mf.qid = Sys->Qid(big path, 0, Sys->QTFILE);
	bix := path & QHMASK;
	mf.hashnext = h.buckets[bix];
	h.buckets[bix] = mf;
}

Qidhash.remove(h : self ref Qidhash, mf : ref Memfile)
{

	bix := int mf.qid.path & QHMASK;
	prev : ref Memfile;
	for (cur := h.buckets[bix]; cur != nil; cur = cur.hashnext) {
		if (cur == mf)
			break;
		prev = cur;
	}
	if (cur != nil) {
		if (prev != nil)
			prev.hashnext = cur.hashnext;
		else
			h.buckets[bix] = cur.hashnext;
		cur.hashnext = nil;
	}
}

Qidhash.lookup(h : self ref Qidhash, qid : Sys->Qid) : ref Memfile
{
	bix := int qid.path & QHMASK;
	for (mf := h.buckets[bix]; mf != nil; mf = mf.hashnext)
		if (mf.qid.path == qid.path)
			break;
	return mf;
}

newmf(qh : ref Qidhash, parent : ref Memfile, name, owner : string, perm : int) : ref Memfile
{
	# qid gets set by Qidhash.add()
	t := now();
	mf := ref Memfile (name, owner, Sys->Qid(big 0,0,Sys->QTFILE), perm, t, t, 0, nil, 0, parent, nil, nil, nil, nil);
	qh.add(mf);
	if(perm & Sys->DMDIR)
		mf.qid.qtype = Sys->QTDIR;
	return mf;
}

fidtomf(srv : ref Styxserver, qh : ref Qidhash, fid : int) : (string, ref Styxlib->Chan, ref Memfile)
{
	c := srv.fidtochan(fid);
	if (c == nil)
		return (Styxlib->Ebadfid, nil, nil);
	mf := qh.lookup(c.qid);
	if (mf == nil)
		return (Styxlib->Enotfound, c, nil);
	return (nil, c, mf);
}

unlink(mf : ref Memfile)
{
	parent := mf.parent;
	if (parent == nil)
		return;
	if (mf.next != nil)
		mf.next.prev = mf.prev;
	if (mf.prev != nil)
		mf.prev.next = mf.next;
	else
		mf.parent.kids = mf.next;
	mf.parent = nil;
	mf.prev = nil;
	mf.next = nil;
}

delfile(qh : ref Qidhash, mf : ref Memfile) : int
{
	if (mf.nopen <= 0 && mf.parent == nil && mf.kids == nil
	&& mf.prev == nil && mf.next == nil) {
		qh.remove(mf);
		nblks := len mf.data;
		mf.data = nil;
		return nblks;
	}
	return 0;
}

dirlookup(dir : ref Memfile, name : string) : ref Memfile
{
	if (name == ".")
		return dir;
	if (name == "..")
		return dir.parent;
	for (mf := dir.kids; mf != nil; mf = mf.next) {
		if (mf.name == name)
			break;
	}
	return mf;
}

access := array[] of {8r400, 8r200, 8r600, 8r100};
modeok(mode, perm : int, user, owner : string) : int
{
	if(mode >= (OTRUNC|ORCLOSE|OREAD|OWRITE))
		return 0;

	# not handling groups!
	if (user != owner)
		perm <<= 6;
	
	if ((mode & OTRUNC) && !(perm & 8r200))
		return 0;

	a := access[mode &3];
	if ((a & perm) != a)
		return 0;
	return 1;
}

dirdata(dir : ref Memfile, start, n : int) : array of byte
{
	data := array[Styx->MAXFDATA] of byte;
	for (k := dir.kids; start > 0 && k != nil; k = k.next) {
		a := styx->packdir(fileinfo(k));
		start -= len a;
	}
	r := 0;
	for (; r < n && k != nil; k = k.next) {
		a := styx->packdir(fileinfo(k));
		if(r+len a > n)
			break;
		data[r:] = a;
		r += len a;
	}
	return data[0:r];
}

fileinfo(f : ref Memfile) : Sys->Dir
{
	dir := sys->zerodir;
	dir.name = f.name;
	dir.uid = f.owner;
	dir.gid = "memfs";
	dir.qid = f.qid;
	dir.mode = f.perm;
	dir.atime = f.atime;
	dir.mtime = f.mtime;
	dir.length = big f.length;
	dir.dtype = 0;
	dir.dev = 0;
	return dir;
}

min(a, b : int) : int
{
	if (a < b)
		return a;
	return b;
}

max(a, b : int) : int
{
	if (a > b)
		return a;
	return b;
}

now(): int
{
	if (timefd == nil)
		return 0;
	buf := array[128] of byte;
	sys->seek(timefd, big 0, 0);
	n := sys->read(timefd, buf, len buf);
	if(n < 0)
		return 0;

	t := (big string buf[0:n]) / big 1000000;
	return int t;
}