shithub: purgatorio

ref: 51bf59e016b43f16ec91943829af30d99a886cc1
dir: /appl/spree/spree.b/

View raw version
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;
}