code: purgatorio

ref: 6b84c3a6548f1c577948763b0a09a7b6e2460e04
dir: /appl/lib/styxservers.b/

View raw version
implement Styxservers;

#
# Copyright © 1999 Vita Nuova Limited.  All rights reserved.
# Revisions copyright © 2000-2003 Vita Nuova Holdings Limited.  All rights reserved.
#
#	Derived from Roger Peppe's Styxlib by Martin C. Atkins, 2001/2002 by
#	adding new helper functions, and then removing Dirgenmod and its helpers
#
#	Further modified by Roger Peppe to simplify the interface by
#	adding the Navigator/Navop channel interface and making other changes,
#	including using the Styx module
#
# converted to revised Styx at Vita Nuova
# further revised August/September 2002
#
# TO DO:
#	- directory reading interface revision?
#

include "sys.m";
	sys: Sys;

include "styx.m";
	styx: Styx;
	Tmsg, Rmsg: import styx;

include "styxservers.m";

CHANHASHSIZE: con 32;
DIRREADSIZE: con Styx->STATFIXLEN+4*20;	# ``reasonable'' chunk for reading directories

debug := 0;

init(styxmod: Styx)
{
	sys = load Sys Sys->PATH;
	styx = styxmod;
}

traceset(d: int)
{
	debug = d;
}

Styxserver.new(fd: ref Sys->FD, t: ref Navigator, rootpath: big): (chan of ref Tmsg, ref Styxserver)
{
	tchan := chan of ref Tmsg;
	srv := ref Styxserver(fd, array[CHANHASHSIZE] of list of ref Fid, chan[1] of int, t, rootpath, 0, nil);

	sync := chan of int;
	spawn tmsgreader(fd, srv, tchan, sync);
	<-sync;
	return (tchan, srv);
}

tmsgreader(fd: ref Sys->FD, srv: ref Styxserver, tchan: chan of ref Tmsg, sync: chan of int)
{
	if(debug)
		sys->pctl(Sys->NEWFD|Sys->NEWNS, fd.fd :: 2 :: nil);
	else
		sys->pctl(Sys->NEWFD|Sys->NEWNS, fd.fd :: nil);
	sync <-= 1;
	fd = sys->fildes(fd.fd);
	m: ref Tmsg;
	do {
		m = Tmsg.read(fd, srv.msize);
		if(debug && m != nil)
			sys->fprint(sys->fildes(2), "<- %s\n", m.text());
		tchan <-= m;
	} while(m != nil && tagof(m) != tagof(Tmsg.Readerror));
}

Fid.clone(oc: self ref Fid, c: ref Fid): ref Fid
{
	# c.fid not touched, other values copied from c
	c.path = oc.path;
	c.qtype = oc.qtype;
	c.isopen = oc.isopen;
	c.mode = oc.mode;
	c.doffset = oc.doffset;
	c.uname  = oc.uname;
	c.param = oc.param;
	c.data = oc.data;
	return c;
}

Fid.walk(c: self ref Fid, qid: Sys->Qid)
{
	c.path = qid.path;
	c.qtype = qid.qtype;
}

Fid.open(c: self ref Fid, mode: int, qid: Sys->Qid)
{
	c.isopen = 1;
	c.mode = mode;
	c.doffset = (0, 0);
	c.path = qid.path;
	c.qtype = qid.qtype;
}

Styxserver.error(srv: self ref Styxserver, m: ref Tmsg, msg: string)
{
	srv.reply(ref Rmsg.Error(m.tag, msg));
}

Styxserver.reply(srv: self ref Styxserver, m: ref Rmsg): int
{
	if(debug)
		sys->fprint(sys->fildes(2), "-> %s\n", m.text());
	if(srv.replychan != nil){
		srv.replychan <-= m;
		return 0;
	}
	return srv.replydirect(m);
}

Styxserver.replydirect(srv: self ref Styxserver, m: ref Rmsg): int
{
	if(srv.msize == 0)
		m = ref Rmsg.Error(m.tag, "Tversion not seen");
	d := m.pack();
	if(srv.msize != 0 && len d > srv.msize){
		m = ref Rmsg.Error(m.tag, "Styx reply didn't fit");
		d = m.pack();
	}
	return sys->write(srv.fd, d, len d);
}

Styxserver.attach(srv: self ref Styxserver, m: ref Tmsg.Attach): ref Fid
{
	(d, err) := srv.t.stat(srv.rootpath);
	if(d == nil) {
		srv.reply(ref Rmsg.Error(m.tag, err));
		return nil;
	}
	if((d.qid.qtype & Sys->QTDIR) == 0) {
		srv.reply(ref Rmsg.Error(m.tag, Enotdir));
		return nil;
	}
	c := srv.newfid(m.fid);
	if(c == nil) {
		srv.reply(ref Rmsg.Error(m.tag, Einuse));
		return nil;
	}
	c.uname = m.uname;
	c.param = m.aname;
	c.path = d.qid.path;
	c.qtype = d.qid.qtype;
	srv.reply(ref Rmsg.Attach(m.tag, d.qid));
	return c;
}

walk1(n: ref Navigator, c: ref Fid, name: string): (ref Sys->Dir, string)
{
	(d, err) := n.stat(c.path);
	if(d == nil)
		return (nil, err);
	if((d.qid.qtype & Sys->QTDIR) == 0)
		return (nil, Enotdir);
	if(!openok(c.uname, Styx->OEXEC, d.mode, d.uid, d.gid))
		return (nil, Eperm);
	(d, err) = n.walk(d.qid.path, name);
	if(d == nil)
		return (nil, err);
	return (d, nil);
}

Styxserver.walk(srv: self ref Styxserver, m: ref Tmsg.Walk): ref Fid
{
	c := srv.getfid(m.fid);
	if(c == nil) {
		srv.reply(ref Rmsg.Error(m.tag, Ebadfid));
		return nil;
	}
	if(c.isopen) {
		srv.reply(ref Rmsg.Error(m.tag, Eopen));
		return nil;
	}
	if(m.newfid != m.fid){
		nc := srv.newfid(m.newfid);
		if(nc == nil){
			srv.reply(ref Rmsg.Error(m.tag, Einuse));
			return nil;
		}
		c = c.clone(nc);
	}
	qids := array[len m.names] of Sys->Qid;
	oldpath := c.path;
	oldqtype := c.qtype;
	for(i := 0; i < len m.names; i++){
		(d, err) := walk1(srv.t, c, m.names[i]);
		if(d == nil){
			c.path = oldpath;	# restore c
			c.qtype = oldqtype;
			if(m.newfid != m.fid)
				srv.delfid(c);
			if(i == 0)
				srv.reply(ref Rmsg.Error(m.tag, err));
			else
				srv.reply(ref Rmsg.Walk(m.tag, qids[0:i]));
			return nil;
		}
		c.walk(d.qid);
		qids[i] = d.qid;
	}
	srv.reply(ref Rmsg.Walk(m.tag, qids));
	return c;
}

Styxserver.canopen(srv: self ref Styxserver, m: ref Tmsg.Open): (ref Fid, int, ref Sys->Dir, string)
{
	c := srv.getfid(m.fid);
	if(c == nil)
		return (nil, 0, nil, Ebadfid);
	if(c.isopen)
		return (nil, 0, nil, Eopen);
	(f, err) := srv.t.stat(c.path);
	if(f == nil)
		return (nil, 0, nil, err);
	mode := openmode(m.mode);
	if(mode == -1)
		return (nil, 0, nil, Ebadarg);
	if(mode != Sys->OREAD && f.qid.qtype & Sys->QTDIR)
		return (nil, 0, nil, Eperm);
	if(!openok(c.uname, m.mode, f.mode, f.uid, f.gid))
		return (nil, 0, nil, Eperm);
	if(m.mode & Sys->ORCLOSE) {
		(dir, nil) := srv.t.walk(c.path, "..");
		if(dir == nil || dir.qid.path == f.qid.path && dir.qid.qtype == f.qid.qtype ||	# can't remove root directory
		   !openok(c.uname, Sys->OWRITE, dir.mode, dir.uid, dir.gid))
			return (nil, 0, nil, Eperm);
		mode |= Sys->ORCLOSE;
	}
	return (c, mode, f, err);
}

Styxserver.open(srv: self ref Styxserver, m: ref Tmsg.Open): ref Fid
{
	(c, mode, f, err) := srv.canopen(m);
	if(c == nil){
		srv.reply(ref Rmsg.Error(m.tag, err));
		return nil;
	}
	c.open(mode, f.qid);
	srv.reply(ref Rmsg.Open(m.tag, f.qid, srv.iounit()));
	return c;
}

Styxserver.cancreate(srv: self ref Styxserver, m: ref Tmsg.Create): (ref Fid, int, ref Sys->Dir, string)
{
	c := srv.getfid(m.fid);
	if(c == nil)
		return (nil, 0, nil, Ebadfid);
	if(c.isopen)
		return (nil, 0, nil, Eopen);
	(d, err) := srv.t.stat(c.path);
	if(d == nil)
		return (nil, 0, nil, err);
	if((d.mode & Sys->DMDIR) == 0)
		return (nil, 0, nil, Enotdir);
	if(m.name == "")
		return (nil, 0, nil, Ename);
	if(m.name == "." || m.name == "..")
		return (nil, 0, nil, Edot);
	if(!openok(c.uname, Sys->OWRITE, d.mode, d.uid, d.gid))
		return (nil, 0, nil, Eperm);
	if(srv.t.walk(d.qid.path, m.name).t0 != nil)
		return (nil, 0, nil, Eexists);
	if((mode := openmode(m.mode)) == -1)
		return (nil, 0, nil, Ebadarg);
	mode |= m.mode & Sys->ORCLOSE;		# can create, so directory known to be writable
	f := ref Sys->zerodir;
	if(m.perm & Sys->DMDIR){
		f.mode = m.perm & (~8r777 | (d.mode & 8r777));
		f.qid.qtype = Sys->QTDIR;
	}else{
		f.mode = m.perm & (~8r666 | (d.mode & 8r666));
		f.qid.qtype = Sys->QTFILE;
	}
	f.name = m.name;
	f.uid = c.uname;
	f.muid = c.uname;
	f.gid = d.gid;
	f.dtype = d.dtype;
	f.dev = d.dev;
	# caller must supply atime, mtime, qid.path
	return (c, mode, f, nil);
}

Styxserver.canread(srv: self ref Styxserver, m: ref Tmsg.Read): (ref Fid, string)
{
	c := srv.getfid(m.fid);
	if(c == nil)
		return (nil, Ebadfid);
	if(!c.isopen)
		return (nil, Enotopen);
	mode := c.mode & 3;
	if(mode != Sys->OREAD && mode != Sys->ORDWR)	# readable modes
		return (nil, Eaccess);
	if(m.count < 0 || m.count > srv.msize-Styx->IOHDRSZ)
		return (nil, Ecount);
	if(m.offset < big 0)
		return (nil, Eoffset);
	return (c, nil);
}

Styxserver.read(srv: self ref Styxserver, m: ref Tmsg.Read): ref Fid
{
	(c, err) := srv.canread(m);
	if(c == nil){
		srv.reply(ref Rmsg.Error(m.tag, err));
		return nil;
	}
	if((c.qtype & Sys->QTDIR) == 0) {
		srv.reply(ref Rmsg.Error(m.tag, Eperm));
		return nil;
	}
	if(m.count <= 0){
		srv.reply(ref Rmsg.Read(m.tag, nil));
		return c;
	}
	a := array[m.count] of byte;
	(offset, index) := c.doffset;
	if(int m.offset != offset){	# rescan from the beginning
		offset = 0;
		index = 0;
	}
	p := 0;
Dread:
	while((d := srv.t.readdir(c.path, index, (m.count+DIRREADSIZE-1)/DIRREADSIZE)) != nil && (nd := len d) > 0){
		for(i := 0; i < nd; i++) {
			size := styx->packdirsize(*d[i]);
			offset += size;
			index++;
			if(offset < int m.offset)
				continue;
			if((m.count -= size) < 0){	# won't fit, save state for next time
				offset -= size;
				index--;
				break Dread;
			}
			de := styx->packdir(*d[i]);
			a[p:] = de;
			p += size;
		}
	}
	c.doffset = (offset, index);
	srv.reply(ref Rmsg.Read(m.tag, a[0:p]));
	return c;
}

Styxserver.canwrite(srv: self ref Styxserver, m: ref Tmsg.Write): (ref Fid, string)
{
	c := srv.getfid(m.fid);
	if(c == nil)
		return (nil, Ebadfid);
	if(!c.isopen)
		return (nil, Enotopen);
	if(c.qtype & Sys->QTDIR)
		return (nil, Eperm);
	mode := c.mode & 3;
	if(mode != Sys->OWRITE && mode != Sys->ORDWR)	# writable modes
		return (nil, Eaccess);
	if(m.offset < big 0)
		return (nil, Eoffset);
	# could check len m.data > iounit, but since we've got it now, it doesn't matter
	return (c, nil);
}

Styxserver.stat(srv: self ref Styxserver, m: ref Tmsg.Stat)
{
	c := srv.getfid(m.fid);
	if(c == nil) {
		srv.reply(ref Rmsg.Error(m.tag, Ebadfid));
		return;
	}
	(d, err) := srv.t.stat(c.path);
	if(d == nil) {
		srv.reply(ref Rmsg.Error(m.tag, err));
		return;
	}
	srv.reply(ref Rmsg.Stat(m.tag, *d));
}

Styxserver.canremove(srv: self ref Styxserver, m: ref Tmsg.Remove): (ref Fid, big, string)
{
	c := srv.getfid(m.fid);
	if(c == nil)
		return (nil, big 0, Ebadfid);
	(dir, nil) := srv.t.walk(c.path, "..");	# this relies on .. working for non-directories
	if(dir == nil)
		return (nil, big 0, "can't find parent directory");
	if(dir.qid.path == c.path && dir.qid.qtype == c.qtype ||	# can't remove root directory
	   !openok(c.uname, Sys->OWRITE, dir.mode, dir.uid, dir.gid))
		return (nil, big 0, Eperm);
	return (c, dir.qid.path, nil);
}

Styxserver.remove(srv: self ref Styxserver, m: ref Tmsg.Remove): ref Fid
{
	c := srv.getfid(m.fid);
	if(c == nil) {
		srv.reply(ref Rmsg.Error(m.tag, Ebadfid));
		return nil;
	}
	srv.delfid(c);			# Remove always clunks the fid
	srv.reply(ref Rmsg.Error(m.tag, Eperm));
	return c;	
}

Styxserver.clunk(srv: self ref Styxserver, m: ref Tmsg.Clunk): ref Fid
{
	c := srv.getfid(m.fid);
	if(c == nil) {
		srv.reply(ref Rmsg.Error(m.tag, Ebadfid));
		return nil;
	}
	srv.delfid(c);
	srv.reply(ref Rmsg.Clunk(m.tag));
	return c;
}

Styxserver.default(srv: self ref Styxserver, gm: ref Tmsg)
{
	if(gm == nil) {
		srv.t.c <-= nil;
		exit;
	}
	pick m := gm {
	Readerror =>
		srv.t.c <-= nil;
		exit;
	Version =>
		if(srv.msize <= 0)
			srv.msize = Styx->MAXRPC;
		(msize, version) := styx->compatible(m, srv.msize, Styx->VERSION);
		if(msize < 256){
			srv.reply(ref Rmsg.Error(m.tag, "message size too small"));
			break;
		}
		srv.msize = msize;
		srv.reply(ref Rmsg.Version(m.tag, msize, version));
	Auth =>
		srv.reply(ref Rmsg.Error(m.tag, "authentication not required"));
	Flush =>
		srv.reply(ref Rmsg.Flush(m.tag));
	Walk =>
		srv.walk(m);
	Open =>
		srv.open(m);
	Create =>
		srv.reply(ref Rmsg.Error(m.tag, Eperm));
	Read =>
		srv.read(m);
	Write =>
		srv.reply(ref Rmsg.Error(m.tag, Eperm));
	Clunk =>
		srv.clunk(m);
		# to delete on ORCLOSE:
		# c := srv.clunk(m);
		# if(c != nil && c.mode & Sys->ORCLOSE)
		# 	srv.doremove(c);
	Stat =>
		srv.stat(m);
	Remove =>
		srv.remove(m);
	Wstat =>
		srv.reply(ref Rmsg.Error(m.tag, Eperm));
	Attach =>
		srv.attach(m);
	* =>
		sys->fprint(sys->fildes(2), "styxservers: unhandled Tmsg tag %d - should not happen\n", tagof gm);
		raise "fail: unhandled case";
	}
}

Styxserver.iounit(srv: self ref Styxserver): int
{
	n := srv.msize - Styx->IOHDRSZ;
	if(n <= 0)
		return 0;	# unknown
	return n;
}

Styxserver.getfid(srv: self ref Styxserver, fid: int): ref Fid
{
	# the list is safe to use without locking
	for(l := srv.fids[fid & (CHANHASHSIZE-1)]; l != nil; l = tl l)
		if((hd l).fid == fid)
			return hd l;
	return nil;
}

Styxserver.delfid(srv: self ref Styxserver, c: ref Fid)
{
	slot := c.fid & (CHANHASHSIZE-1);
	nl: list of ref Fid;
	srv.fidlock <-= 1;
	for(l := srv.fids[slot]; l != nil; l = tl l)
		if((hd l).fid != c.fid)
			nl = (hd l) :: nl;
	srv.fids[slot] = nl;
	<-srv.fidlock;
}

Styxserver.allfids(srv: self ref Styxserver): list of ref Fid
{
	cl: list of ref Fid;
	srv.fidlock <-= 1;
	for(i := 0; i < len srv.fids; i++)
		for(l := srv.fids[i]; l != nil; l = tl l)
			cl = hd l :: cl;
	<-srv.fidlock;
	return cl;
}

Styxserver.newfid(srv: self ref Styxserver, fid: int): ref Fid
{
	srv.fidlock <-= 1;
	if((c := srv.getfid(fid)) != nil){
		<-srv.fidlock;
		return nil;		# illegal: fid in use
	}
	c = ref Fid;
	c.path = big -1;
	c.qtype = 0;
	c.isopen = 0;
	c.mode = 0;
	c.fid = fid;
	c.doffset = (0, 0);
	slot := fid & (CHANHASHSIZE-1);
	srv.fids[slot] = c :: srv.fids[slot];
	<-srv.fidlock;
	return c;
}

readstr(m: ref Tmsg.Read, d: string): ref Rmsg.Read
{
	return readbytes(m, array of byte d);
}

readbytes(m: ref Tmsg.Read, d: array of byte): ref Rmsg.Read
{
	r := ref Rmsg.Read(m.tag, nil);
	if(m.offset >= big len d || m.offset < big 0)
		return r;
	offset := int m.offset;
	e := offset + m.count;
	if(e > len d)
		e = len d;
	r.data = d[offset:e];
	return r;
}

Navigator.new(c: chan of ref Navop): ref Navigator
{
	return ref Navigator(c, chan of (ref Sys->Dir, string));
}

Navigator.stat(t: self ref Navigator, q: big): (ref Sys->Dir, string)
{
	t.c <-= ref Navop.Stat(t.reply, q);
	return <-t.reply;
}

Navigator.walk(t: self ref Navigator, q: big, name: string): (ref Sys->Dir, string)
{
	t.c <-= ref Navop.Walk(t.reply, q, name);
	return <-t.reply;
}

Navigator.readdir(t: self ref Navigator, q: big, offset, count: int): array of ref Sys->Dir
{
	a := array[count] of ref Sys->Dir;
	t.c <-= ref Navop.Readdir(t.reply, q, offset, count);
	i := 0;
	while((d := (<-t.reply).t0) != nil)
		if(i < count)
			a[i++] = d;
	if(i == 0)
		return nil;
	return a[0:i];
}

openmode(o: int): int
{
	OTRUNC, ORCLOSE, OREAD, ORDWR: import Sys;
	o &= ~(OTRUNC|ORCLOSE);
	if(o > ORDWR)
		return -1;
	return o;
}

access := array[] of {8r400, 8r200, 8r600, 8r100};
openok(uname: string, omode: int, perm: int, fuid: string, fgid: string): int
{
	t := access[omode & 3];
	if(omode & Sys->OTRUNC){
		if(perm & Sys->DMDIR)
			return 0;
		t |= 8r200;
	}
	if(uname == fuid && (t&perm) == t)
		return 1;
	if(uname == fgid && (t&(perm<<3)) == t)
		return 1;
	return (t&(perm<<6)) == t;
}