implement Styxservers;
#
# This is /appl/lib/styxservers.b with two important changes for ofs:
# 1. the implementation of walk lets our navigator walk the entire path
# with a single message (before std. navigator walks).
# This is needed because when walking a/b/c, we what to check that the
# entry for a/b/c is up to date, but we do not care if a or a/b are up to date
# or not. This is the difference between three gets and one get (for this path).
# 2. To admit concurrent navigator requests, we cannot share a single reply channel.
# The navigator implemented here does NOT include a replyc. It allocates channels as needed.
# This was /appl/styxservers.b,
# 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 "ofsstyx.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.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, m.tag);
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, tag: int): (ref Sys->Dir, string)
{
(d, err) := n.stat(c.path, tag);
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, tag);
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;
# send /a/b/c/ walk to the nav. The first /
# is a request to check that c is ok in the cache (and the server)
# Later, we ask a, b, and c to the nav, which would now work
# on cached entries. Kludge, for sure.
err := ofswalkall(srv.t, c.path, m.names, m.tag);
if (err != nil){
c.path = oldpath; # restore c
c.qtype = oldqtype;
if(m.newfid != m.fid)
srv.delfid(c);
srv.reply(ref Rmsg.Error(m.tag, err));
return nil;
}
d : ref Sys->Dir;
for(i := 0; i < len m.names; i++){
if (err == nil)
(d, err) = walk1(srv.t, c, m.names[i], m.tag);
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;
}
ofswalkall(n: ref Navigator, q: big, names: array of string, tag: int): string
{
s := "";
for (i := 0; i < len names; i++)
s = s + "/" + names[i];
(nil, err) := n.walk(q, s, tag);
return err;
}
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, m.tag);
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, "..", m.tag);
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, m.tag);
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, m.tag).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, m.tag)) != 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, m.tag);
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, "..", m.tag); # 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); # t.reply is NOT used.
}
wchans: list of chan of (ref Sys->Dir, string);
getwchan(): chan of (ref Sys->Dir, string)
{
if (wchans == nil)
return chan of (ref Sys->Dir, string);
else {
wc := hd wchans;
wchans = tl wchans;
return wc;
}
}
putwchan(wc: chan of (ref Sys->Dir, string))
{
wchans = wc::wchans;
}
Navigator.stat(t: self ref Navigator, q: big, tag: int): (ref Sys->Dir, string)
{
rc := getwchan();
t.c <-= ref Navop.Stat(tag, rc, q);
x := <-rc;
putwchan(rc);
return x;
}
Navigator.walk(t: self ref Navigator, q: big, name: string, tag: int): (ref Sys->Dir, string)
{
rc := getwchan();
t.c <-= ref Navop.Walk(tag, rc, q, name);
x := <-rc;
putwchan(rc);
return x;
}
Navigator.readdir(t: self ref Navigator, q: big, offset, count: int, tag: int): array of ref Sys->Dir
{
rc := getwchan();
a := array[count] of ref Sys->Dir;
t.c <-= ref Navop.Readdir(tag, rc, q, offset, count);
i := 0;
while((d := (<-rc).t0) != nil)
if(i < count)
a[i++] = d;
putwchan(rc);
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;
}
|