ref: 82b046f36f8084a22bbb5d71edd0edd9179561eb
dir: /appl/spree/spree.b/
implement Spree;
include "sys.m";
sys: Sys;
include "readdir.m";
readdir: Readdir;
include "styx.m";
Rmsg, Tmsg: import Styx;
include "styxservers.m";
styxservers: Styxservers;
Styxserver, Fid, Eperm, Navigator: import styxservers;
nametree: Nametree;
include "draw.m";
include "arg.m";
include "sets.m";
sets: Sets;
Set, set, A, B, All, None: import sets;
include "spree.m";
archives: Archives;
Archive: import archives;
stderr: ref Sys->FD;
myself: Spree;
Debug: con 0;
Update: adt {
pick {
Set =>
o: ref Object;
objid: int; # member-specific id
attr: ref Attribute;
Transfer =>
srcid: int; # parent object
from: Range; # range within src to transfer
dstid: int; # destination object
index: int; # insertion point
Create =>
objid: int;
parentid: int;
visibility: Sets->Set;
objtype: string;
Delete =>
parentid: int;
r: Range;
objs: array of int;
Setvisibility =>
objid: int;
visibility: Sets->Set; # set of members that can see it
Action =>
s: string;
objs: list of int;
rest: string;
Break =>
# break in transmission
}
};
T: type ref Update;
Queue: adt {
h, t: list of T;
put: fn(q: self ref Queue, s: T);
get: fn(q: self ref Queue): T;
isempty: fn(q: self ref Queue): int;
peek: fn(q: self ref Queue): T;
};
Openfid: adt {
fid: int;
uname: string;
fileid: int;
member: ref Member; # nil for non-clique files.
updateq: ref Queue;
readreq: ref Tmsg.Read;
hungup: int;
# alias: string; # could use this to allow a member to play themselves
new: fn(fid: ref Fid, file: ref Qfile): ref Openfid;
find: fn(fid: int): ref Openfid;
close: fn(fid: self ref Openfid);
# cmd: fn(fid: self ref Openfid, cmd: string): string;
};
Qfile: adt {
id: int; # index into files array
owner: string;
qid: Sys->Qid;
ofids: list of ref Openfid; # list of all fids that are holding this open
needsupdate: int; # updates have been added since last updateall
create: fn(parent: big, d: Sys->Dir): ref Qfile;
delete: fn(f: self ref Qfile);
};
# which updates do we send even though the clique isn't yet started?
alwayssend := array[] of {
tagof(Update.Set) => 0,
tagof(Update.Transfer) => 0,
tagof(Update.Create) => 0,
tagof(Update.Delete) => 0,
tagof(Update.Setvisibility) => 0,
tagof(Update.Action) => 1,
tagof(Update.Break) => 1,
};
srv: ref Styxserver;
tree: ref Nametree->Tree;
cliques: array of ref Clique;
qfiles: array of ref Qfile;
fids := array[47] of list of ref Openfid; # hash table
lobby: ref Clique;
Qroot: big;
sequence := 0;
fROOT,
fGAME,
fNAME,
fGAMEDIR,
fGAMEDATA: con iota;
GAMEDIR: con "/n/remote";
ENGINES: con "/dis/spree/engines";
ARCHIVEDIR: con "/lib/spreearchive";
badmod(p: string)
{
sys->fprint(stderr, "spree: cannot load %s: %r\n", p);
raise "fail:bad module";
}
init(nil: ref Draw->Context, nil: list of string)
{
sys = load Sys Sys->PATH;
stderr = sys->fildes(2);
myself = load Spree "$self";
styx := load Styx Styx->PATH;
if (styx == nil)
badmod(Styx->PATH);
styx->init();
styxservers = load Styxservers Styxservers->PATH;
if (styxservers == nil)
badmod(Styxservers->PATH);
styxservers->init(styx);
nametree = load Nametree Nametree->PATH;
if (nametree == nil)
badmod(Nametree->PATH);
nametree->init();
sets = load Sets Sets->PATH;
if (sets == nil)
badmod(Sets->PATH);
sets->init();
readdir = load Readdir Readdir->PATH;
if (readdir == nil)
badmod(Readdir->PATH);
archives = load Archives Archives->PATH;
if (archives == nil)
badmod(Archives->PATH);
archives->init(myself);
initrand();
navop: chan of ref Styxservers->Navop;
(tree, navop) = nametree->start();
tchan: chan of ref Tmsg;
Qroot = mkqid(fROOT, 0);
(tchan, srv) = Styxserver.new(sys->fildes(0), Navigator.new(navop), Qroot);
nametree->tree.create(Qroot, dir(Qroot, ".", 8r555|Sys->DMDIR, "spree"));
nametree->tree.create(Qroot, dir(mkqid(fNAME, 0), "name", 8r444, "spree"));
(lobbyid, nil, err) := lobby.new(ref Archive("lobby" :: nil, nil, nil, nil), "spree");
if (lobbyid == -1) {
sys->fprint(stderr, "spree: couldn't start lobby: %s\n", err);
raise "fail:no lobby";
}
sys->pctl(Sys->FORKNS, nil);
for (;;) {
gm := <-tchan;
if (gm == nil || tagof(gm) == tagof(Tmsg.Readerror)) {
if (gm != nil) {
pick m := gm {
Readerror =>
sys->print("spree: read error: %s\n", m.error);
}
}
sys->print("spree: exiting\n");
exit;
} else {
e := handletmsg(gm);
if (e != nil)
srv.reply(ref Rmsg.Error(gm.tag, e));
}
}
}
dir(qidpath: big, name: string, perm: int, owner: string): Sys->Dir
{
DM2QT: con 24;
d := Sys->zerodir;
d.name = name;
d.uid = owner;
d.gid = owner;
d.qid.path = qidpath;
d.qid.qtype = (perm >> DM2QT) & 16rff;
d.mode = perm;
# d.atime = now;
# d.mtime = now;
return d;
}
handletmsg(tmsg: ref Tmsg): string
{
pick m := tmsg {
Open =>
(fid, omode, d, err) := srv.canopen(m);
if (fid == nil)
return err;
if (d.qid.qtype & Sys->QTDIR) {
srv.default(m);
return nil;
}
case qidkind(d.qid.path) {
fGAMEDATA =>
fid.open(m.mode, Sys->Qid(fid.path, fid.qtype, 0));
srv.reply(ref Rmsg.Open(m.tag, Sys->Qid(fid.path, fid.qtype, 0), 0));
fGAME =>
f := qid2file(d.qid.path);
if (f == nil)
return "cannot find qid";
ofid := Openfid.new(fid, f);
err = openfile(ofid);
if (err != nil) {
ofid.close();
return err;
}
fid.open(m.mode, f.qid);
srv.reply(ref Rmsg.Open(m.tag, Sys->Qid(fid.path, fid.qtype, 0), 0));
* =>
srv.default(m);
}
updateall();
Read =>
(fid, err) := srv.canread(m);
if (fid == nil)
return err;
if (fid.qtype & Sys->QTDIR) {
srv.default(m);
return nil;
}
case qidkind(fid.path) {
fGAMEDATA =>
f := qidindex(fid.path);
id := f & 16rffff;
f = (f >> 16) & 16rffff;
data := cliques[id].mod->readfile(f, m.offset, m.count);
srv.reply(ref Rmsg.Read(m.tag, data));
fGAME =>
ff := Openfid.find(m.fid);
if (ff.readreq != nil)
return "duplicate read";
ff.readreq = m;
sendupdate(ff);
fNAME =>
srv.reply(styxservers->readstr(m, fid.uname));
* =>
return "darn rats!";
}
Write =>
(fid, err) := srv.canwrite(m);
if (fid == nil)
return err;
ff := Openfid.find(m.fid);
err = command(ff, string m.data);
if (err != nil) {
updateall();
return err;
}
srv.reply(ref Rmsg.Write(m.tag, len m.data));
updateall(); # XXX might we need to do this on error too?
Clunk =>
fid := srv.clunk(m);
if (fid != nil) {
clunked(fid);
updateall();
}
Flush =>
for (i := 0; i < len qfiles; i++) {
if (qfiles[i] == nil)
continue;
for (ol := qfiles[i].ofids; ol != nil; ol = tl ol) {
ofid := hd ol;
if (ofid.readreq != nil && ofid.readreq.tag == m.oldtag)
ofid.readreq = nil;
}
}
srv.reply(ref Rmsg.Flush(m.tag));
# Removed => clunked too.
* =>
srv.default(tmsg);
}
return nil;
}
clunked(fid: ref Fid)
{
if (!fid.isopen || (fid.qtype & Sys->QTDIR))
return;
ofid := Openfid.find(fid.fid);
if (ofid == nil)
return;
if (ofid.member != nil)
memberleaves(ofid.member);
ofid.close();
f := qfiles[ofid.fileid];
# if it's the last close, and clique is hung up, then remove clique from
# directory hierarchy.
if (f.ofids == nil && qidkind(f.qid.path) == fGAME) {
g := cliques[qidindex(f.qid.path)];
if (g.hungup) {
stopclique(g);
nametree->tree.remove(mkqid(fGAMEDIR, g.id));
f.delete();
cliques[g.id] = nil;
}
}
}
mkqid(kind, i: int): big
{
return big kind | (big i << 4);
}
qidkind(qid: big): int
{
return int (qid & big 16rf);
}
qidindex(qid: big): int
{
return int (qid >> 4);
}
qid2file(qid: big): ref Qfile
{
for (i := 0; i < len qfiles; i++) {
f := qfiles[i];
if (f != nil && f.qid.path == qid)
return f;
}
return nil;
}
Qfile.create(parent: big, d: Sys->Dir): ref Qfile
{
nametree->tree.create(parent, d);
for (i := 0; i < len qfiles; i++)
if (qfiles[i] == nil)
break;
if (i == len qfiles)
qfiles = (array[len qfiles + 1] of ref Qfile)[0:] = qfiles;
f := qfiles[i] = ref Qfile(i, d.uid, d.qid, nil, 0);
return f;
}
Qfile.delete(f: self ref Qfile)
{
nametree->tree.remove(f.qid.path);
qfiles[f.id] = nil;
}
Openfid.new(fid: ref Fid, file: ref Qfile): ref Openfid
{
i := fid.fid % len fids;
ofid := ref Openfid(fid.fid, fid.uname, file.id, nil, ref Queue, nil, 0);
fids[i] = ofid :: fids[i];
file.ofids = ofid :: file.ofids;
return ofid;
}
Openfid.find(fid: int): ref Openfid
{
for (ol := fids[fid % len fids]; ol != nil; ol = tl ol)
if ((hd ol).fid == fid)
return hd ol;
return nil;
}
Openfid.close(ofid: self ref Openfid)
{
i := ofid.fid % len fids;
newol: list of ref Openfid;
for (ol := fids[i]; ol != nil; ol = tl ol)
if (hd ol != ofid)
newol = hd ol :: newol;
fids[i] = newol;
newol = nil;
for (ol = qfiles[ofid.fileid].ofids; ol != nil; ol = tl ol)
if (hd ol != ofid)
newol = hd ol :: newol;
qfiles[ofid.fileid].ofids = newol;
}
openfile(ofid: ref Openfid): string
{
name := ofid.uname;
f := qfiles[ofid.fileid];
if (qidkind(f.qid.path) == fGAME) {
if (cliques[qidindex(f.qid.path)].hungup)
return "hungup";
i := 0;
for (o := f.ofids; o != nil; o = tl o) {
if ((hd o) != ofid && (hd o).uname == name)
return "you cannot join a clique twice";
i++;
}
if (i > MAXPLAYERS)
return "too many members";
}
return nil;
}
# process a client's command; return a non-nil string on error.
command(ofid: ref Openfid, cmd: string): string
{
err: string;
f := qfiles[ofid.fileid];
qid := f.qid.path;
if (ofid.hungup)
return "hung up";
if (cmd == nil) {
ofid.hungup = 1;
sys->print("hanging up file %s for user %s, fid %d\n", nametree->tree.getpath(f.qid.path), ofid.uname, ofid.fid);
return nil;
}
case qidkind(qid) {
fGAME =>
clique := cliques[qidindex(qid)];
if (ofid.member == nil)
err = newmember(clique, ofid, cmd);
else
err = cliquerequest(clique, ref Rq.Command(ofid.member, cmd));
* =>
err = "invalid command " + string qid; # XXX dud error message
}
return err;
}
Clique.notify(src: self ref Clique, dstid: int, cmd: string)
{
if (cmd == nil)
return; # don't allow faking of clique exit.
if (dstid < 0 || dstid >= len cliques) {
if (dstid != -1)
sys->fprint(stderr, "%d cannot notify invalid %d: '%s'\n", src.id, dstid, cmd);
return;
}
dst := cliques[dstid];
if (dst.parentid != src.id && dstid != src.parentid) {
sys->fprint(stderr, "%d cannot notify %d: '%s'\n", src.id, dstid, cmd);
return;
}
src.notes = (src.id, dstid, cmd) :: src.notes;
}
# add a new member to a clique.
# it should already have been checked that the member's name
# isn't a duplicate of another in the same clique.
newmember(clique: ref Clique, ofid: ref Openfid, cmd: string): string
{
name := ofid.uname;
# check if member was suspended, and give them their old id back
# if so, otherwise find first free id.
for (s := clique.suspended; s != nil; s = tl s)
if ((hd s).name == name)
break;
id: int;
suspended := 0;
member: ref Member;
if (s != nil) {
member = hd s;
# remove from suspended list
q := tl s;
for (t := clique.suspended; t != s; t = tl t)
q = hd t :: q;
clique.suspended = q;
suspended = 1;
member.suspended = 0;
} else {
for (id = 0; clique.memberids.holds(id); id++)
;
member = ref Member(id, clique.id, nil, nil, nil, name, 0, 0);
clique.memberids = clique.memberids.add(member.id);
}
q := ofid.updateq;
ofid.member = member;
started := clique.started;
err := cliquerequest(clique, ref Rq.Join(member, cmd, suspended));
if (err != nil) {
member.del(0);
if (suspended) {
member.suspended = 1;
clique.suspended = member :: clique.suspended;
}
return err;
}
if (started) {
qrecreateobject(q, member, clique.objects[0], nil);
qfiles[ofid.fileid].needsupdate = 1;
}
member.updating = 1;
return nil;
}
Clique.start(clique: self ref Clique)
{
if (clique.started)
return;
for (ol := qfiles[clique.fileid].ofids; ol != nil; ol = tl ol)
if ((hd ol).member != nil)
qrecreateobject((hd ol).updateq, (hd ol).member, clique.objects[0], nil);
clique.started = 1;
}
Blankclique: Clique;
maxcliqueid := 0;
Clique.new(parent: self ref Clique, archive: ref Archive, owner: string): (int, string, string)
{
for (id := 0; id < len cliques; id++)
if (cliques[id] == nil)
break;
if (id == len cliques)
cliques = (array[len cliques + 1] of ref Clique)[0:] = cliques;
mod := load Engine ENGINES +"/" + hd archive.argv + ".dis";
if (mod == nil)
return (-1, nil, sys->sprint("cannot load engine: %r"));
dirq := mkqid(fGAMEDIR, id);
fname := string maxcliqueid++;
e := nametree->tree.create(Qroot, dir(dirq, fname, 8r555|Sys->DMDIR, owner));
if (e != nil)
return (-1, nil, e);
f := Qfile.create(dirq, dir(mkqid(fGAME, id), "ctl", 8r666, owner));
objs: array of ref Object;
if (archive.objects != nil) {
objs = archive.objects;
for (i := 0; i < len objs; i++)
objs[i].cliqueid = id;
} else
objs = array[] of {ref Object(0, Attributes.new(), All, -1, nil, id, nil)};
memberids := None;
suspended: list of ref Member;
for (i := 0; i < len archive.members; i++) {
suspended = ref Member(i, id, nil, nil, nil, archive.members[i], 0, 1) :: suspended;
memberids = memberids.add(i);
}
archive = ref *archive;
archive.objects = nil;
g := cliques[id] = ref Clique(
id, # id
f.id, # fileid
fname, # fname
objs, # objects
archive, # archive
nil, # freelist
mod, # mod
memberids, # memberids
suspended,
chan of ref Rq, # request
chan of string, # reply
0, # hungup
0, # started
-1, # parentid
nil # notes
);
if (parent != nil) {
g.parentid = parent.id;
g.notes = parent.notes;
}
spawn cliqueproc(g);
e = cliquerequest1(g, ref Rq.Init);
if (e != nil) {
stopclique(g);
nametree->tree.remove(dirq);
f.delete();
cliques[id] = nil;
return (-1, nil, e);
}
# only send notifications if the clique was successfully created, otherwise
# pretend it never existed.
if (parent != nil) {
parent.notes = g.notes;
g.notes = nil;
}
return (g.id, fname, nil);
}
# as a special case, if parent is nil, we use the root object.
Clique.newobject(clique: self ref Clique, parent: ref Object, visibility: Set, objtype: string): ref Object
{
if (clique.freelist == nil)
(clique.objects, clique.freelist) =
makespace(clique.objects, clique.freelist);
id := hd clique.freelist;
clique.freelist = tl clique.freelist;
if (parent == nil)
parent = clique.objects[0];
obj := ref Object(id, Attributes.new(), visibility, parent.id, nil, clique.id, objtype);
n := len parent.children;
newchildren := array[n + 1] of ref Object;
newchildren[0:] = parent.children;
newchildren[n] = obj;
parent.children = newchildren;
clique.objects[id] = obj;
applycliqueupdate(clique, ref Update.Create(id, parent.id, visibility, objtype), All);
if (Debug)
sys->print("new %d, parent %d, visibility %s\n", obj.id, parent.id, visibility.str());
return obj;
}
Clique.hangup(clique: self ref Clique)
{
if (clique.hungup)
return;
sys->print("clique.hangup(%s)\n", clique.fname);
f := qfiles[clique.fileid];
for (ofids := f.ofids; ofids != nil; ofids = tl ofids)
(hd ofids).hungup = 1;
f.needsupdate = 1;
clique.hungup = 1;
if (clique.parentid != -1) {
clique.notes = (clique.id, clique.parentid, nil) :: clique.notes;
clique.parentid = -1;
}
# orphan children
# XXX could be more efficient for childless cliques by keeping child count
for(i := 0; i < len cliques; i++)
if (cliques[i] != nil && cliques[i].parentid == clique.id)
cliques[i].parentid = -1;
}
stopclique(clique: ref Clique)
{
clique.hangup();
if (clique.request != nil)
clique.request <-= nil;
}
Clique.breakmsg(clique: self ref Clique, whoto: Set)
{
applycliqueupdate(clique, ref Update.Break, whoto);
}
Clique.action(clique: self ref Clique, cmd: string,
objs: list of int, rest: string, whoto: Set)
{
applycliqueupdate(clique, ref Update.Action(cmd, objs, rest), whoto);
}
Clique.member(clique: self ref Clique, id: int): ref Member
{
for (ol := qfiles[clique.fileid].ofids; ol != nil; ol = tl ol)
if ((hd ol).member != nil && (hd ol).member.id == id)
return (hd ol).member;
for (s := clique.suspended; s != nil; s = tl s)
if ((hd s).id == id)
return hd s;
return nil;
}
Clique.membernamed(clique: self ref Clique, name: string): ref Member
{
for (ol := qfiles[clique.fileid].ofids; ol != nil; ol = tl ol)
if ((hd ol).uname == name)
return (hd ol).member;
for (s := clique.suspended; s != nil; s = tl s)
if ((hd s).name == name)
return hd s;
return nil;
}
Clique.owner(clique: self ref Clique): string
{
return qfiles[clique.fileid].owner;
}
Clique.fcreate(clique: self ref Clique, f: int, parent: int, d: Sys->Dir): string
{
pq: big;
if (parent == -1)
pq = mkqid(fGAMEDIR, clique.id);
else
pq = mkqid(fGAMEDATA, clique.id | (parent<<16));
d.qid.path = mkqid(fGAMEDATA, clique.id | (f<<16));
d.mode &= ~8r222;
return nametree->tree.create(pq, d);
}
Clique.fremove(clique: self ref Clique, f: int): string
{
return nametree->tree.remove(mkqid(fGAMEDATA, clique.id | (f<<16)));
}
# debugging...
Clique.show(nil: self ref Clique, nil: ref Member)
{
# sys->print("**************** all objects:\n");
# showobject(clique, clique.objects[0], p, 0, ~0);
# if (p == nil) {
# f := qfiles[clique.fileid];
# for (ol := f.ofids; ol != nil; ol = tl ol) {
# p = (hd ol).member;
# if (p == nil) {
# sys->print("lurker (name '%s')\n",
# (hd ol).uname);
# continue;
# }
# sys->print("member %d, '%s': ext->obj ", p.id, p.name);
# for (j := 0; j < len p.ext2obj; j++)
# if (p.ext2obj[j] != nil)
# sys->print("%d->%d[%d] ", j, p.ext2obj[j].id, p.ext(p.ext2obj[j].id));
# sys->print("\n");
# }
# }
}
cliquerequest(clique: ref Clique, rq: ref Rq): string
{
e := cliquerequest1(clique, rq);
sendnotifications(clique);
return e;
}
cliquerequest1(clique: ref Clique, rq: ref Rq): string
{
if (clique.request == nil)
return "clique has exited";
clique.request <-= rq;
err := <-clique.reply;
if (clique.hungup && clique.request != nil) {
clique.request <-= nil;
clique.request = nil;
}
return err;
}
sendnotifications(clique: ref Clique)
{
notes, pending: list of (int, int, string);
(pending, clique.notes) = (clique.notes, nil);
n := 0;
while (pending != nil) {
for (notes = nil; pending != nil; pending = tl pending)
notes = hd pending :: notes;
for (; notes != nil; notes = tl notes) {
(srcid, dstid, cmd) := hd notes;
dst := cliques[dstid];
if (!dst.hungup) {
dst.notes = pending;
cliquerequest1(dst, ref Rq.Notify(srcid, cmd));
(pending, dst.notes) = (dst.notes, nil);
}
}
if (n++ > 50)
panic("probable loop in clique notification"); # XXX probably shouldn't panic, but useful for debugging
}
}
cliqueproc(clique: ref Clique)
{
wfd := sys->open("/prog/" + string sys->pctl(0, nil) + "/wait", Sys->OREAD);
spawn cliqueproc1(clique);
buf := array[Sys->ATOMICIO] of byte;
n := sys->read(wfd, buf, len buf);
sys->print("spree: clique '%s' exited: %s\n", clique.fname, string buf[0:n]);
clique.hangup();
clique.request = nil;
clique.reply <-= "clique exited";
}
cliqueproc1(clique: ref Clique)
{
for (;;) {
rq := <-clique.request;
if (rq == nil)
break;
reply := "";
pick r := rq {
Init =>
reply = clique.mod->init(myself, clique, clique.archive.argv);
Join =>
reply = clique.mod->join(r.member, r.cmd, r.suspended);
Command =>
reply = clique.mod->command(r.member, r.cmd);
Leave =>
if (clique.mod->leave(r.member) == 0)
reply = "suspended";
Notify =>
clique.mod->notify(r.srcid, r.cmd);
* =>
panic("unknown engine request, tag " + string tagof(rq));
}
clique.reply <-= reply;
}
sys->print("spree: clique '%s' exiting\n", clique.fname);
}
Member.ext(member: self ref Member, id: int): int
{
obj2ext := member.obj2ext;
if (id >= len obj2ext || id < 0)
return -1;
return obj2ext[id];
}
Member.obj(member: self ref Member, ext: int): ref Object
{
if (ext < 0 || ext >= len member.ext2obj)
return nil;
return member.ext2obj[ext];
}
# allocate an object in a member's map.
memberaddobject(p: ref Member, o: ref Object)
{
if (p.freelist == nil)
(p.ext2obj, p.freelist) = makespace(p.ext2obj, p.freelist);
ext := hd p.freelist;
p.freelist = tl p.freelist;
if (o.id >= len p.obj2ext) {
oldmap := p.obj2ext;
newmap := array[o.id + 10] of int;
newmap[0:] = oldmap;
for (i := len oldmap; i < len newmap; i++)
newmap[i] = -1;
p.obj2ext = newmap;
}
p.obj2ext[o.id] = ext;
p.ext2obj[ext] = o;
if (Debug)
sys->print("addobject member %d, internal %d, external %d\n", p.id, o.id, ext);
}
# delete an object from a member's map.
memberdelobject(member: ref Member, id: int)
{
if (id >= len member.obj2ext) {
sys->fprint(stderr, "spree: bad delobject (member %d, id %d, len obj2ext %d)\n",
member.id, id, len member.obj2ext);
return;
}
ext := member.obj2ext[id];
member.ext2obj[ext] = nil;
member.obj2ext[id] = -1;
member.freelist = ext :: member.freelist;
if (Debug)
sys->print("delobject member %d, internal %d, external %d\n", member.id, id, ext);
}
memberleaves(member: ref Member)
{
clique := cliques[member.cliqueid];
sys->print("member %d leaving clique %d\n", member.id, member.cliqueid);
suspend := 0;
if (!clique.hungup)
suspend = cliquerequest(clique, ref Rq.Leave(member)) != nil;
member.del(suspend);
}
resetvisibilities(o: ref Object, id: int)
{
o.visibility = setreset(o.visibility, id);
a := o.attrs.a;
for (i := 0; i < len a; i++) {
for (al := a[i]; al != nil; al = tl al) {
(hd al).visibility = setreset((hd al).visibility, id);
(hd al).needupdate = setreset((hd al).needupdate, id);
}
}
for (i = 0; i < len o.children; i++)
resetvisibilities(o.children[i], id);
}
# remove a member from their clique.
# the client is still there, but won't get any clique updates.
Member.del(member: self ref Member, suspend: int)
{
clique := cliques[member.cliqueid];
if (!member.suspended) {
for (ofids := qfiles[clique.fileid].ofids; ofids != nil; ofids = tl ofids)
if ((hd ofids).member == member) {
(hd ofids).member = nil;
(hd ofids).hungup = 1;
# XXX purge update queue?
}
# go through all clique objects and attributes, resetting
# permissions for member id to their default values.
if (suspend) {
member.obj2ext = nil;
member.ext2obj = nil;
member.freelist = nil;
member.updating = 0;
member.suspended = 1;
clique.suspended = member :: clique.suspended;
}
} else if (!suspend) {
ns: list of ref Member;
for (s := clique.suspended; s != nil; s = tl s)
if (hd s != member)
ns = hd s :: ns;
clique.suspended = ns;
}
if (!suspend) {
resetvisibilities(clique.objects[0], member.id);
clique.memberids = clique.memberids.del(member.id);
}
}
Clique.members(clique: self ref Clique): list of ref Member
{
pl := clique.suspended;
for (ofids := qfiles[clique.fileid].ofids; ofids != nil; ofids = tl ofids)
if ((hd ofids).member != nil)
pl = (hd ofids).member :: pl;
return pl;
}
Object.delete(o: self ref Object)
{
clique := cliques[o.cliqueid];
if (o.parentid != -1) {
parent := clique.objects[o.parentid];
siblings := parent.children;
for (i := 0; i < len siblings; i++)
if (siblings[i] == o)
break;
if (i == len siblings)
panic("object " + string o.id + " not found in parent");
parent.deletechildren((i, i+1));
} else
sys->fprint(stderr, "spree: cannot delete root object\n");
}
Object.deletechildren(parent: self ref Object, r: Range)
{
if (len parent.children == 0)
return;
clique := cliques[parent.cliqueid];
n := r.end - r.start;
objs := array[r.end - r.start] of int;
children := parent.children;
for (i := r.start; i < r.end; i++) {
o := children[i];
objs[i - r.start] = o.id;
o.deletechildren((0, len o.children));
clique.objects[o.id] = nil;
clique.freelist = o.id :: clique.freelist;
o.id = -1;
o.parentid = -1;
}
children[r.start:] = children[r.end:];
for (i = len children - n; i < len children; i++)
children[i] = nil;
if (n < len children)
parent.children = children[0:len children - n];
else
parent.children = nil;
if (Debug) {
sys->print("+del from %d, range [%d %d], objs: ", parent.id, r.start, r.end);
for (i = 0; i < len objs; i++)
sys->print("%d ", objs[i]);
sys->print("\n");
}
applycliqueupdate(clique, ref Update.Delete(parent.id, r, objs), All);
}
# move a range of objects from src and insert them at index in dst.
Object.transfer(src: self ref Object, r: Range, dst: ref Object, index: int)
{
if (index == -1)
index = len dst.children;
if (src == dst && index >= r.start && index <= r.end)
return;
n := r.end - r.start;
objs := src.children[r.start:r.end];
newchildren := array[len src.children - n] of ref Object;
newchildren[0:] = src.children[0:r.start];
newchildren[r.start:] = src.children[r.end:];
src.children = newchildren;
if (Debug) {
sys->print("+transfer from %d[%d,%d] to %d[%d], objs: ",
src.id, r.start, r.end, dst.id, index);
for (x := 0; x < len objs; x++)
sys->print("%d ", objs[x].id);
sys->print("\n");
}
nindex := index;
# if we've just removed some cards from the destination,
# then adjust the destination index accordingly.
if (src == dst && nindex > r.start) {
if (nindex < r.end)
nindex = r.start;
else
nindex -= n;
}
newchildren = array[len dst.children + n] of ref Object;
newchildren[0:] = dst.children[0:index];
newchildren[nindex + n:] = dst.children[nindex:];
newchildren[nindex:] = objs;
dst.children = newchildren;
for (i := 0; i < len objs; i++)
objs[i].parentid = dst.id;
clique := cliques[src.cliqueid];
applycliqueupdate(clique,
ref Update.Transfer(src.id, r, dst.id, index),
All);
}
# visibility is only set when the attribute is newly created.
Object.setattr(o: self ref Object, name, val: string, visibility: Set)
{
(changed, attr) := o.attrs.set(name, val, visibility);
if (changed) {
attr.needupdate = All;
applycliqueupdate(cliques[o.cliqueid], ref Update.Set(o, o.id, attr), objvisibility(o));
}
}
Object.getattr(o: self ref Object, name: string): string
{
attr := o.attrs.get(name);
if (attr == nil)
return nil;
return attr.val;
}
# set visibility of an object - reveal any uncovered descendents
# if necessary.
Object.setvisibility(o: self ref Object, visibility: Set)
{
if (o.visibility.eq(visibility))
return;
o.visibility = visibility;
applycliqueupdate(cliques[o.cliqueid], ref Update.Setvisibility(o.id, visibility), objvisibility(o));
}
Object.setattrvisibility(o: self ref Object, name: string, visibility: Set)
{
attr := o.attrs.get(name);
if (attr == nil) {
sys->fprint(stderr, "spree: setattrvisibility, no attribute '%s', id %d\n", name, o.id);
return;
}
if (attr.visibility.eq(visibility))
return;
# send updates to anyone that has needs updating,
# is in the new visibility list, but not in the old one.
ovisibility := objvisibility(o);
before := ovisibility.X(A&B, attr.visibility);
after := ovisibility.X(A&B, visibility);
attr.visibility = visibility;
applycliqueupdate(cliques[o.cliqueid], ref Update.Set(o, o.id, attr), before.X(~A&B, after));
}
# an object's visibility is the intersection
# of the visibility of all its parents.
objvisibility(o: ref Object): Set
{
clique := cliques[o.cliqueid];
visibility := All;
for (id := o.parentid; id != -1; id = o.parentid) {
o = clique.objects[id];
visibility = visibility.X(A&B, o.visibility);
}
return visibility;
}
makespace(objects: array of ref Object,
freelist: list of int): (array of ref Object, list of int)
{
if (freelist == nil) {
na := array[len objects + 10] of ref Object;
na[0:] = objects;
for (j := len na - 1; j >= len objects; j--)
freelist = j :: freelist;
objects = na;
}
return (objects, freelist);
}
updateall()
{
for (i := 0; i < len qfiles; i++) {
f := qfiles[i];
if (f != nil && f.needsupdate) {
for (ol := f.ofids; ol != nil; ol = tl ol)
sendupdate(hd ol);
f.needsupdate = 0;
}
}
}
applyupdate(f: ref Qfile, upd: ref Update)
{
for (ol := f.ofids; ol != nil; ol = tl ol)
(hd ol).updateq.put(upd);
f.needsupdate = 1;
}
# send update to members in the clique in the needupdate set.
applycliqueupdate(clique: ref Clique, upd: ref Update, needupdate: Set)
{
always := alwayssend[tagof(upd)];
if (needupdate.isempty() || (!clique.started && !always))
return;
f := qfiles[clique.fileid];
for (ol := f.ofids; ol != nil; ol = tl ol) {
ofid := hd ol;
member := ofid.member;
if (member != nil && needupdate.holds(member.id) && (member.updating || always))
queueupdate(ofid.updateq, member, upd);
}
f.needsupdate = 1;
}
# transform an outgoing update according to the visibility
# of the object(s) concerned.
# the update concerned has already occurred.
queueupdate(q: ref Queue, p: ref Member, upd: ref Update)
{
clique := cliques[p.cliqueid];
pick u := upd {
Set =>
if (p.ext(u.o.id) != -1 && u.attr.needupdate.holds(p.id)) {
q.put(ref Update.Set(u.o, p.ext(u.o.id), u.attr));
u.attr.needupdate = u.attr.needupdate.del(p.id);
} else
u.attr.needupdate = u.attr.needupdate.add(p.id);
Transfer =>
# if moving from an invisible object, create the objects
# temporarily in the source object, and then transfer from that.
# if moving to an invisible object, delete the objects.
# if moving from invisible to invisible, do nothing.
src := clique.objects[u.srcid];
dst := clique.objects[u.dstid];
fromvisible := objvisibility(src).X(A&B, src.visibility).holds(p.id);
tovisible := objvisibility(dst).X(A&B, dst.visibility).holds(p.id);
if (fromvisible || tovisible) {
# N.B. objects are already in destination object at this point.
(r, index, srcid) := (u.from, u.index, u.srcid);
# XXX this scheme is all very well when the parent of src
# or dst is visible, but not when it's not... in that case
# we should revert to the old scheme of deleting objects in src
# or recreating them in dst as appropriate.
if (!tovisible) {
# transfer objects to destination, then delete them,
# so client knows where they've gone.
q.put(ref Update.Transfer(p.ext(srcid), r, p.ext(u.dstid), 0));
qdelobjects(q, p, dst, (u.index, u.index + r.end - r.start), 0);
break;
}
if (!fromvisible) {
# create at the end of source object,
# then transfer into correct place in destination.
n := r.end - r.start;
for (i := 0; i < n; i++) {
o := dst.children[index + i];
qrecreateobject(q, p, o, src);
}
r = (0, n);
}
if (p.ext(srcid) == -1 || p.ext(u.dstid) == -1)
panic("external objects do not exist");
q.put(ref Update.Transfer(p.ext(srcid), r, p.ext(u.dstid), index));
}
Create =>
dst := clique.objects[u.parentid];
if (objvisibility(dst).X(A&B, dst.visibility).holds(p.id)) {
memberaddobject(p, clique.objects[u.objid]);
q.put(ref Update.Create(p.ext(u.objid), p.ext(u.parentid), u.visibility, u.objtype));
}
Delete =>
# we can only get this update when all the children are
# leaf nodes.
o := clique.objects[u.parentid];
if (objvisibility(o).X(A&B, o.visibility).holds(p.id)) {
r := u.r;
extobjs := array[len u.objs] of int;
for (i := 0; i < len u.objs; i++) {
extobjs[i] = p.ext(u.objs[i]);
memberdelobject(p, u.objs[i]);
}
q.put(ref Update.Delete(p.ext(o.id), u.r, extobjs));
}
Setvisibility =>
# if the object doesn't exist for this member, don't do anything.
# else if there are children, check whether they exist, and
# create or delete them as necessary.
if (p.ext(u.objid) != -1) {
o := clique.objects[u.objid];
if (len o.children > 0) {
visible := u.visibility.holds(p.id);
made := p.ext(o.children[0].id) != -1;
if (!visible && made)
qdelobjects(q, p, o, (0, len o.children), 0);
else if (visible && !made)
for (i := 0; i < len o.children; i++)
qrecreateobject(q, p, o.children[i], nil);
}
q.put(ref Update.Setvisibility(p.ext(u.objid), u.visibility));
}
Action =>
s := u.s;
for (ol := u.objs; ol != nil; ol = tl ol)
s += " " + string p.ext(hd ol);
s += " " + u.rest;
q.put(ref Update.Action(s, nil, nil));
* =>
q.put(upd);
}
}
# queue deletions for o; we pretend to the client that
# the deletions are at index.
qdelobjects(q: ref Queue, p: ref Member, o: ref Object, r: Range, index: int)
{
if (r.start >= r.end)
return;
children := o.children;
extobjs := array[r.end - r.start] of int;
for (i := r.start; i < r.end; i++) {
c := children[i];
qdelobjects(q, p, c, (0, len c.children), 0);
extobjs[i - r.start] = p.ext(c.id);
memberdelobject(p, c.id);
}
q.put(ref Update.Delete(p.ext(o.id), (index, index + (r.end - r.start)), extobjs));
}
# parent visibility now allows o to be seen, so recreate
# it for the member. (if parent is non-nil, pretend we're creating it there)
qrecreateobject(q: ref Queue, p: ref Member, o: ref Object, parent: ref Object)
{
memberaddobject(p, o);
parentid := o.parentid;
if (parent != nil)
parentid = parent.id;
q.put(ref Update.Create(p.ext(o.id), p.ext(parentid), o.visibility, o.objtype));
recreateattrs(q, p, o);
if (o.visibility.holds(p.id)) {
a := o.children;
for (i := 0; i < len a; i++)
qrecreateobject(q, p, a[i], nil);
}
}
recreateattrs(q: ref Queue, p: ref Member, o: ref Object)
{
a := o.attrs.a;
for (i := 0; i < len a; i++) {
for (al := a[i]; al != nil; al = tl al) {
attr := hd al;
q.put(ref Update.Set(o, p.ext(o.id), attr));
}
}
}
CONTINUATION := array[] of {byte '\n', byte '*'};
# send the client as many updates as we can fit in their read request
# (if there are some updates to send and there's an outstanding read request)
sendupdate(ofid: ref Openfid)
{
clique: ref Clique;
if (ofid.readreq == nil || (ofid.updateq.isempty() && !ofid.hungup))
return;
m := ofid.readreq;
q := ofid.updateq;
if (ofid.hungup) {
srv.reply(ref Rmsg.Read(m.tag, nil));
q.h = q.t = nil;
return;
}
data := array[m.count] of byte;
nb := 0;
plid := -1;
if (ofid.member != nil) {
plid = ofid.member.id;
clique = cliques[ofid.member.cliqueid];
}
avail := len data - len CONTINUATION;
Putdata:
for (; !q.isempty(); q.get()) {
upd := q.peek();
pick u := upd {
Set =>
if (plid != -1 && !objvisibility(u.o).X(A&B, u.attr.visibility).holds(plid)) {
u.attr.needupdate = u.attr.needupdate.add(plid);
continue Putdata;
}
Break =>
if (nb > 0) {
q.get();
break Putdata;
}
continue Putdata;
}
d := array of byte update2s(upd, plid);
if (len d + nb > avail)
break;
data[nb:] = d;
nb += len d;
}
err := "";
if (nb == 0) {
if (q.isempty())
return;
err = "short read";
} else if (!q.isempty()) {
data[nb:] = CONTINUATION;
nb += len CONTINUATION;
}
data = data[0:nb];
if (err != nil)
srv.reply(ref Rmsg.Error(m.tag, err));
else
srv.reply(ref Rmsg.Read(m.tag, data));
ofid.readreq = nil;
}
# convert an Update adt to a string.
update2s(upd: ref Update, plid: int): string
{
s: string;
pick u := upd {
Create =>
objtype := u.objtype;
if (objtype == nil)
objtype = "nil";
s = sys->sprint("create %d %d %d %s\n", u.objid, u.parentid, u.visibility.holds(plid) != 0, objtype);
Transfer =>
# tx src dst dstindex start end
if (u.srcid == -1 || u.dstid == -1)
panic("src or dst object is -1");
s = sys->sprint("tx %d %d %d %d %d\n",
u.srcid, u.dstid, u.from.start, u.from.end, u.index);
Delete =>
s = sys->sprint("del %d %d %d", u.parentid, u.r.start, u.r.end);
for (i := 0; i < len u.objs; i++)
s += " " + string u.objs[i];
s[len s] = '\n';
Set =>
s = sys->sprint("set %d %s %s\n", u.objid, u.attr.name, u.attr.val);
Setvisibility =>
s = sys->sprint("vis %d %d\n", u.objid, u.visibility.holds(plid) != 0);
Action =>
s = u.s + "\n";
* =>
sys->fprint(stderr, "unknown update tag %d\n", tagof(upd));
}
return s;
}
Queue.put(q: self ref Queue, s: T)
{
q.t = s :: q.t;
}
Queue.get(q: self ref Queue): T
{
s: T;
if(q.h == nil){
q.h = revlist(q.t);
q.t = nil;
}
if(q.h != nil){
s = hd q.h;
q.h = tl q.h;
}
return s;
}
Queue.peek(q: self ref Queue): T
{
s: T;
if (q.isempty())
return s;
s = q.get();
q.h = s :: q.h;
return s;
}
Queue.isempty(q: self ref Queue): int
{
return q.h == nil && q.t == nil;
}
revlist(ls: list of T) : list of T
{
rs: list of T;
for (; ls != nil; ls = tl ls)
rs = hd ls :: rs;
return rs;
}
Attributes.new(): ref Attributes
{
return ref Attributes(array[7] of list of ref Attribute);
}
Attributes.get(attrs: self ref Attributes, name: string): ref Attribute
{
for (al := attrs.a[strhash(name, len attrs.a)]; al != nil; al = tl al)
if ((hd al).name == name)
return hd al;
return nil;
}
# return (haschanged, attr)
Attributes.set(attrs: self ref Attributes, name, val: string, visibility: Set): (int, ref Attribute)
{
h := strhash(name, len attrs.a);
for (al := attrs.a[h]; al != nil; al = tl al) {
attr := hd al;
if (attr.name == name) {
if (attr.val == val)
return (0, attr);
attr.val = val;
return (1, attr);
}
}
attr := ref Attribute(name, val, visibility, All);
attrs.a[h] = attr :: attrs.a[h];
return (1, attr);
}
setreset(set: Set, i: int): Set
{
if (set.msb())
return set.add(i);
return set.del(i);
}
# from Aho Hopcroft Ullman
strhash(s: string, n: int): int
{
h := 0;
m := len s;
for(i := 0; i<m; i++){
h = 65599 * h + s[i];
}
return (h & 16r7fffffff) % n;
}
panic(s: string)
{
cliques[0].show(nil);
sys->fprint(stderr, "panic: %s\n", s);
raise "panic";
}
randbits: chan of int;
initrand()
{
randbits = chan of int;
spawn randproc();
}
randproc()
{
fd := sys->open("/dev/notquiterandom", Sys->OREAD);
if (fd == nil) {
sys->print("cannot open /dev/random: %r\n");
exit;
}
randbits <-= sys->pctl(0, nil);
buf := array[1] of byte;
while ((n := sys->read(fd, buf, len buf)) > 0) {
b := buf[0];
for (i := byte 1; i != byte 0; i <<= 1)
randbits <-= (b & i) != byte 0;
}
}
rand(n: int): int
{
x: int;
for (nbits := 0; (1 << nbits) < n; nbits++)
x ^= <-randbits << nbits;
x ^= <-randbits << nbits;
x &= (1 << nbits) - 1;
i := 0;
while (x >= n) {
x ^= <-randbits << i;
i = (i + 1) % nbits;
}
return x;
}
archivenum := -1;
newarchivename(): string
{
if (archivenum == -1) {
(d, nil) := readdir->init(ARCHIVEDIR, Readdir->MTIME|Readdir->COMPACT);
for (i := 0; i < len d; i++) {
name := d[i].name;
if (name != nil && name[0] == 'a') {
for (j := 1; j < len name; j++)
if (name[j] < '0' || name[j] > '9')
break;
if (j == len name && int name[1:] > archivenum)
archivenum = int name[1:];
}
}
archivenum++;
}
return ARCHIVEDIR + "/a" + string archivenum++;
}
archivenames(): list of string
{
names: list of string;
(d, nil) := readdir->init(ARCHIVEDIR, Readdir->MTIME|Readdir->COMPACT);
for (i := 0; i < len d; i++)
if (len d[i].name < 4 || d[i].name[len d[i].name - 4:] != ".old")
names = ARCHIVEDIR + "/" + d[i].name :: names;
return names;
}