ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/lib/styxlib.b/
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;
}