code: 9ferno

ref: 6d69f6fba35087686f79adb2ea0d67944a62ca7b
dir: /appl/lib/styxlib.b/

View raw version
implement Styxlib;

#
# Copyright © 1999 Vita Nuova Limited.  All rights reserved.
# Revisions copyright © 2002 Vita Nuova Holdings Limited.  All rights reserved.
#

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

include "styxlib.m";

CHANHASHSIZE: con 32;
starttime: int;
timefd: ref Sys->FD;

DEBUG: con 0;

init(s: Styx): string
{
	sys = load Sys Sys->PATH;
	styx = s;	# our caller inits
	return nil;
}

Styxserver.new(fd: ref Sys->FD): (chan of ref Tmsg, ref Styxserver)
{
	starttime = now();
	srv := ref Styxserver(fd, array[CHANHASHSIZE] of list of ref Chan, getuname(), 0);
	if(fd == nil)
		return (nil, srv);
	tchan := chan of ref Tmsg;
	sync := chan of int;
	spawn tmsgreader(fd, srv, tchan, sync);
	<-sync;
	return (tchan, srv);
}

now(): int
{
	if(timefd == nil){
		timefd = sys->open("/dev/time", sys->OREAD);
		if(timefd == nil)
			return 0;
	}
	buf := array[64] 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;
}


getuname(): string
{
	if ((fd := sys->open("/dev/user", Sys->OREAD)) == nil)
		return "unknown";
	buf := array[Sys->NAMEMAX] of byte;
	n := sys->read(fd, buf, len buf);
	if (n <= 0)
		return "unknown";
	return string buf[0:n];
}

tmsgreader(fd: ref Sys->FD, srv: ref Styxserver, tchan: chan of ref Tmsg, sync: chan of int)
{
	sys->pctl(Sys->NEWFD|Sys->NEWNS, fd.fd :: nil);
	sync <-= 1;
	fd = sys->fildes(fd.fd);
	while((m := Tmsg.read(fd, srv.msize)) != nil && tagof m != tagof Tmsg.Readerror){
		tchan <-= m;
		m = nil;
	}
	tchan <-= m;
}

Styxserver.reply(srv: self ref Styxserver, m: ref Rmsg): int
{
	if (DEBUG) 
		sys->fprint(sys->fildes(2), "%s\n", m.text());
	a := m.pack();
	if(a == nil)
		return -1;
	return sys->write(srv.fd, a, len a);
}

Styxserver.devversion(srv: self ref Styxserver, m: ref Tmsg.Version): int
{
	if(srv.msize <= 0)
		srv.msize = Styx->MAXRPC;
	(msize, version) := styx->compatible(m, srv.msize, Styx->VERSION);
	if(msize < 128){
		srv.reply(ref Rmsg.Error(m.tag, "unusable message size"));
		return -1;
	}
	srv.msize = msize;
	srv.reply(ref Rmsg.Version(m.tag, msize, version));
	return 0;
}

Styxserver.devauth(srv: self ref Styxserver, m: ref Tmsg.Auth)
{
	srv.reply(ref Rmsg.Error(m.tag, "authentication not required"));
}

Styxserver.devattach(srv: self ref Styxserver, m: ref Tmsg.Attach): ref Chan
{
	c := srv.newchan(m.fid);
	if (c == nil) {
		srv.reply(ref Rmsg.Error(m.tag, Einuse));
		return nil;
	}
	c.uname = m.uname;
	c.qid.qtype = Sys->QTDIR;
	c.qid.path = big 0;
	c.path = "dev";
	srv.reply(ref Rmsg.Attach(m.tag, c.qid));
	return c;
}

Styxserver.clone(srv: self ref Styxserver, oc: ref Chan, newfid: int): ref Chan
{
	c := srv.newchan(newfid);
	if (c == nil) 
		return nil;
	c.qid = oc.qid;
	c.uname  = oc.uname;
	c.open = oc.open;
	c.mode = oc.mode;
	c.path = oc.path;
	c.data = oc.data;
	return c;
}

Styxserver.devflush(srv: self ref Styxserver, m: ref Tmsg.Flush)
{
	srv.reply(ref Rmsg.Flush(m.tag));
}

Styxserver.devwalk(srv: self ref Styxserver, m: ref Tmsg.Walk,
							gen: Dirgenmod, tab: array of Dirtab): ref Chan
{
	c := srv.fidtochan(m.fid);
	if (c == nil) {
		srv.reply(ref Rmsg.Error(m.tag, Ebadfid));
		return nil;
	}
	if (c.open) {
		srv.reply(ref Rmsg.Error(m.tag, Eopen));
		return nil;
	}
	if (!c.isdir()) {
		srv.reply(ref Rmsg.Error(m.tag, Enotdir));
		return nil;
	}
	# should check permissions here?
	qids: array of Sys->Qid;
	cc := ref *c;	# walk a temporary copy
	if(len m.names > 0){
		qids = array[len m.names] of Sys->Qid;
		for(i := 0; i < len m.names; i++){
			for(k := 0;; k++){
				(ok, d) := gen->dirgen(srv, cc, tab, k);
				if(ok < 0){
					if(i == 0)
						srv.reply(ref Rmsg.Error(m.tag, Enotfound));
					else
						srv.reply(ref Rmsg.Walk(m.tag, qids[0:i]));
					return nil;
				}
				if (d.name == m.names[i]) {
					cc.qid = d.qid;
					cc.path = d.name;
					qids[i] = cc.qid;
					break;
				}
			}
		}
	}
	# successful walk
	if(m.newfid != m.fid){
		# clone/walk
		nc := srv.clone(cc, m.newfid);
		if(nc == nil){
			srv.reply(ref Rmsg.Error(m.tag, Einuse));
			return nil;
		}
		c = nc;
	}else{
		# walk c itself
		c.qid = cc.qid;
		c.path = cc.path;
	}
	srv.reply(ref Rmsg.Walk(m.tag, qids));
	return c;
}

Styxserver.devclunk(srv: self ref Styxserver, m: ref Tmsg.Clunk): ref Chan
{
	c := srv.fidtochan(m.fid);
	if (c == nil) {
		srv.reply(ref Rmsg.Error(m.tag, Ebadfid));
		return nil;
	}
	srv.chanfree(c);
	srv.reply(ref Rmsg.Clunk(m.tag));
	return c;
}

Styxserver.devstat(srv: self ref Styxserver, m: ref Tmsg.Stat,
							gen: Dirgenmod, tab: array of Dirtab)
{
	c := srv.fidtochan(m.fid);
	if (c == nil) {
		srv.reply(ref Rmsg.Error(m.tag, Ebadfid));
		return;
	}
	i := 0;
	(ok, d) := gen->dirgen(srv, c, tab, i++);
	while (ok >= 0) {
		if (ok > 0 && c.qid.path == d.qid.path) {
			srv.reply(ref Rmsg.Stat(m.tag, d));
			return;
		}
		(ok, d) = gen->dirgen(srv, c, tab, i++);
	}
	# auto-generate entry for directory if not found.
	# XXX this is asking for trouble, as the permissions given
	# on stat() of a directory can be different from those given
	# when reading the directory's entry in its parent dir.
	if (c.qid.qtype & Sys->QTDIR)
		srv.reply(ref Rmsg.Stat(m.tag, devdir(c, c.qid, c.path, big 0, srv.uname, Sys->DMDIR|8r555)));
	else
		srv.reply(ref Rmsg.Error(m.tag, Enotfound));
}

Styxserver.devdirread(srv: self ref Styxserver, m: ref Tmsg.Read,
							gen: Dirgenmod, tab: array of Dirtab)
{
	c := srv.fidtochan(m.fid);
	if (c == nil) {
		srv.reply(ref Rmsg.Error(m.tag, Ebadfid));
		return;
	}
	offset := int m.offset;
	data := array[m.count] of byte;
	start := 0;
	n := 0;
	for (k := 0;; k++) {
		(ok, d) := gen->dirgen(srv, c, tab, k);
		if(ok < 0){
			srv.reply(ref Rmsg.Read(m.tag, data[0:n]));
			return;
		}
		size := styx->packdirsize(d);
		if(start < offset){
			start += size;
			continue;
		}
		if(n+size > m.count)
			break;
		data[n:] = styx->packdir(d);
		n += size;
	}
	srv.reply(ref Rmsg.Read(m.tag, data[0:n]));
}

Styxserver.devopen(srv: self ref Styxserver, m: ref Tmsg.Open,
							gen: Dirgenmod, tab: array of Dirtab): ref Chan
{
	c := srv.fidtochan(m.fid);
	if (c == nil) {
		srv.reply(ref Rmsg.Error(m.tag, Ebadfid));
		return nil;
	}
	omode := m.mode;
	i := 0;
	(ok, d) := gen->dirgen(srv, c, tab, i++);
	while (ok >= 0) {
		# XXX dev.c checks vers as well... is that desirable?
		if (ok > 0 && c.qid.path == d.qid.path) {
			if (openok(omode, d.mode, c.uname, d.uid, d.gid)) {
				c.qid.vers = d.qid.vers;
				break;
			}
			srv.reply(ref Rmsg.Error(m.tag, Eperm));
			return nil;
		}
		(ok, d) = gen->dirgen(srv, c, tab, i++);
	}
	if ((c.qid.qtype & Sys->QTDIR) && omode != Sys->OREAD) {
		srv.reply(ref Rmsg.Error(m.tag, Eperm));
		return nil;
	}
	if ((c.mode = openmode(omode)) == -1) {
		srv.reply(ref Rmsg.Error(m.tag, Ebadarg));
		return nil;
	}
	c.open = 1;
	c.mode = omode;
	srv.reply(ref Rmsg.Open(m.tag, c.qid, Styx->MAXFDATA));
	return c;
}

Styxserver.devremove(srv: self ref Styxserver, m: ref Tmsg.Remove): ref Chan
{
	c := srv.fidtochan(m.fid);
	if (c == nil) {
		srv.reply(ref Rmsg.Error(m.tag, Ebadfid));
		return nil;
	}
	srv.chanfree(c);
	srv.reply(ref Rmsg.Error(m.tag, Eperm));
	return c;
}

Styxserver.fidtochan(srv: self ref Styxserver, fid: int): ref Chan
{
	for (l := srv.chans[fid & (CHANHASHSIZE-1)]; l != nil; l = tl l)
		if ((hd l).fid == fid)
			return hd l;
	return nil;
}

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

Styxserver.chanlist(srv: self ref Styxserver): list of ref Chan
{
	cl: list of ref Chan;
	for (i := 0; i < len srv.chans; i++)
		for (l := srv.chans[i]; l != nil; l = tl l)
			cl = hd l :: cl;
	return cl;
}

Styxserver.newchan(srv: self ref Styxserver, fid: int): ref Chan
{
	# fid already in use
	if ((c := srv.fidtochan(fid)) != nil)
		return nil;
	c = ref Chan;
	c.qid = Sys->Qid(big 0, 0, Sys->QTFILE);
	c.open = 0;
	c.mode = 0;
	c.fid = fid;
	slot := fid & (CHANHASHSIZE-1);
	srv.chans[slot] = c :: srv.chans[slot];
	return c;
}

devdir(nil: ref Chan, qid: Sys->Qid, name: string, length: big,
				user: string, perm: int): Sys->Dir
{
	d: Sys->Dir;
	d.name = name;
	d.qid = qid;
	d.dtype = 'X';
	d.dev = 0;		# XXX what should this be?
	d.mode = perm;
	if (qid.qtype & Sys->QTDIR)
		d.mode |= Sys->DMDIR;
	d.atime = starttime;	# XXX should be better than this.
	d.mtime = starttime;
	d.length = length;
	d.uid = user;
	d.gid = user;
	return d;
}

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

readnum(m: ref Tmsg.Read, val, size: int): ref Rmsg.Read
{
	return readbytes(m, sys->aprint("%-*d", size, val));
}

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

dirgenmodule(): Dirgenmod
{
	return load Dirgenmod "$self";
}

dirgen(srv: ref Styxserver, c: ref Styxlib->Chan,
				tab: array of Dirtab, i: int): (int, Sys->Dir)
{
	d: Sys->Dir;
	if (tab == nil || i >= len tab)
		return (-1, d);
	return (1, devdir(c, tab[i].qid, tab[i].name, tab[i].length, srv.uname, tab[i].perm));
}

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

access := array[] of {8r400, 8r200, 8r600, 8r100};
openok(omode, perm: int, uname, funame, nil: string): int
{
	# XXX what should we do about groups?
	# this is inadequate anyway:
	# OTRUNC
	# user should be allowed to open it if permission
	# is allowed to others.
	mode: int;
	if (uname == funame)
		mode = perm;
	else
		mode = perm << 6;

	t := access[omode & 3];
	return ((t & mode) == t);
}	

Chan.isdir(c: self ref Chan): int
{
	return (c.qid.qtype & Sys->QTDIR) != 0;
}