summaryrefslogtreecommitdiff
path: root/appl/cmd/ndb/registry.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/cmd/ndb/registry.b')
-rw-r--r--appl/cmd/ndb/registry.b671
1 files changed, 671 insertions, 0 deletions
diff --git a/appl/cmd/ndb/registry.b b/appl/cmd/ndb/registry.b
new file mode 100644
index 00000000..f720781f
--- /dev/null
+++ b/appl/cmd/ndb/registry.b
@@ -0,0 +1,671 @@
+implement Registry;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "string.m";
+ str: String;
+include "daytime.m";
+ daytime: Daytime;
+include "bufio.m";
+include "attrdb.m";
+ attrdb: Attrdb;
+ Db, Dbf, Dbentry: import attrdb;
+include "styx.m";
+ styx: Styx;
+ Rmsg, Tmsg: import styx;
+include "styxservers.m";
+ styxservers: Styxservers;
+ Styxserver, Fid, Navigator, Navop: import styxservers;
+ Enotdir, Enotfound: import Styxservers;
+include "arg.m";
+
+# files:
+# 'new'
+# write name of new service; (and possibly attribute column names)
+# entry appears in directory of that name
+# can then write attributes/values
+# 'index'
+# read to get info on all services and their attributes.
+# 'find'
+# write to set filter.
+# read to get info on all services with matching attributes
+# 'event' (not needed initially)
+# read to block until changes happen.
+# servicename
+# write to change attributes (only by owner)
+# remove to unregister service.
+
+Registry: module {
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+Qroot,
+Qnew,
+Qindex,
+Qevent,
+Qfind,
+Qsvc: con iota;
+
+
+Shift: con 4;
+Mask: con 2r1111;
+
+Egreg: con "buggy program!";
+Maxreplyidle: con 3;
+
+Service: adt {
+ id: int;
+ slot: int;
+ owner: string;
+ name: string;
+ atime: int;
+ mtime: int;
+ vers: int;
+ fid: int; # fid that created it (NOFID if static)
+ attrs: list of (string, string);
+
+ new: fn(owner: string): ref Service;
+ find: fn(id: int): ref Service;
+ remove: fn(svc: self ref Service);
+ set: fn(svc: self ref Service, attr, val: string);
+ get: fn(svc: self ref Service, attr: string): string;
+};
+
+Filter: adt {
+ id: int; # filter ID (it's a fid)
+ attrs: array of (string, string);
+
+ new: fn(id: int): ref Filter;
+ find: fn(id: int): ref Filter;
+ set: fn(f: self ref Filter, a: array of (string, string));
+ match: fn(f: self ref Filter, attrs: list of (string, string)): int;
+ remove: fn(f: self ref Filter);
+};
+
+filters: list of ref Filter;
+
+
+services := array[9] of ref Service;
+nservices := 0;
+idseq := 0;
+rootvers := 0;
+now: int;
+startdate: int;
+dbfile: string;
+
+srv: ref Styxserver;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ str = load String String->PATH;
+ if(str == nil)
+ loaderr(String->PATH);
+ daytime = load Daytime Daytime->PATH;
+ if(daytime == nil)
+ loaderr(Daytime->PATH);
+ styx = load Styx Styx->PATH;
+ if (styx == nil)
+ loaderr(Styx->PATH);
+ styx->init();
+ styxservers = load Styxservers Styxservers->PATH;
+ if (styxservers == nil)
+ loaderr(Styxservers->PATH);
+ styxservers->init(styx);
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ loaderr(Arg->PATH);
+ arg->init(args);
+ arg->setusage("ndb/registry [-f initdb]");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'f' => dbfile = arg->earg();
+ * => arg->usage();
+ }
+ args = arg->argv();
+ if(args != nil)
+ arg->usage();
+ arg = nil;
+
+ sys->pctl(Sys->FORKNS|Sys->NEWFD, 0::1::2::nil);
+ startdate = now = daytime->now();
+ if(dbfile != nil){
+ attrdb = load Attrdb Attrdb->PATH;
+ if(attrdb == nil)
+ loaderr(Attrdb->PATH);
+ attrdb->init();
+ db := Db.open(dbfile);
+ if(db == nil)
+ error(sys->sprint("can't open %s: %r", dbfile));
+ dbload(db);
+ db = nil; # for now assume it's static
+ }
+ navops := chan of ref Navop;
+ spawn navigator(navops);
+ tchan: chan of ref Tmsg;
+ (tchan, srv) = Styxserver.new(sys->fildes(0), Navigator.new(navops), big Qroot);
+ spawn serve(tchan, navops);
+}
+
+loaderr(p: string)
+{
+ error(sys->sprint("can't load %s: %r", p));
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "registry: %s\n", s);
+ raise "fail:error";
+}
+
+serve(tchan: chan of ref Tmsg, navops: chan of ref Navop)
+{
+Serve:
+ while((gm := <-tchan) != nil){
+ now = daytime->now();
+ err := "";
+ pick m := gm {
+ Readerror =>
+ error(sys->sprint("fatal read error: %s\n", m.error));
+ break Serve;
+ Open =>
+ (fid, mode, d, e) := srv.canopen(m);
+ if((err = e) != nil)
+ break;
+ if(fid.qtype & Sys->QTDIR)
+ srv.default(m);
+ else
+ open(m, fid);
+ Read =>
+ (fid, e) := srv.canread(m);
+ if((err = e) != nil)
+ break;
+ if(fid.qtype & Sys->QTDIR)
+ srv.read(m);
+ else
+ err = read(m, fid);
+ Write =>
+ (fid, e) := srv.canwrite(m);
+ if((err = e) != nil)
+ break;
+ err = write(m, fid);
+ if(err == nil)
+ srv.reply(ref Rmsg.Write(m.tag, len m.data));
+ Clunk =>
+ clunk(srv.clunk(m));
+ Remove =>
+ (fid, nil, e) := srv.canremove(m);
+ srv.delfid(fid); # always clunked even on error
+ if((err = e) != nil)
+ break;
+ err = remove(fid);
+ if(err == nil)
+ srv.reply(ref Rmsg.Remove(m.tag));
+ * =>
+ srv.default(gm);
+ }
+ if(err != "")
+ srv.reply(ref Rmsg.Error(gm.tag, err));
+ }
+ navops <-= nil;
+}
+
+open(m: ref Tmsg.Open, fid: ref Fid)
+{
+ path := int fid.path;
+ case path & Mask {
+ Qnew =>
+ svc := Service.new(fid.uname);
+ svc.fid = fid.fid;
+ fid.open(m.mode, (big ((svc.id << Shift)|Qsvc), 0, Sys->QTFILE));
+ * =>
+ fid.open(m.mode, (fid.path, 0, fid.qtype));
+ }
+ srv.reply(ref Rmsg.Open(m.tag, (fid.path, 0, fid.qtype), 0));
+}
+
+read(m: ref Tmsg.Read, fid: ref Fid): string
+{
+ path := int fid.path;
+ case path & Mask {
+ Qindex =>
+ if(fid.data == nil || m.offset == big 0)
+ fid.data = getindexdata(-1, Styx->NOFID);
+ srv.reply(styxservers->readbytes(m, fid.data));
+ Qfind =>
+ if(fid.data == nil || m.offset == big 0)
+ fid.data = getindexdata(-1, fid.fid);
+ srv.reply(styxservers->readbytes(m, fid.data));
+ Qsvc =>
+ if(fid.data == nil || m.offset == big 0){
+ svc := Service.find(path >> Shift);
+ if(svc != nil)
+ svc.atime = now;
+ fid.data = getindexdata(path >> Shift, Styx->NOFID);
+ }
+ srv.reply(styxservers->readbytes(m, fid.data));
+ Qevent =>
+ return "not implemented yet";
+ * =>
+ return Egreg;
+ }
+ return nil;
+}
+
+write(m: ref Tmsg.Write, fid: ref Fid): string
+{
+ path := int fid.path;
+ case path & Mask {
+ Qsvc =>
+ svc := Service.find(path >> Shift);
+ if(svc == nil)
+ return Egreg;
+ s := string m.data;
+ toks := str->unquoted(s);
+ if(toks == nil)
+ return "bad syntax";
+ # first write names the service (possibly with attributes)
+ if(svc.name == nil){
+ if((e := svcnameok(hd toks)) != nil)
+ return "bad service name";
+ svc.name = hd toks;
+ toks = tl toks;
+ }
+ if(len toks % 2 != 0)
+ return "odd attribute/value pairs";
+ svc.mtime = now;
+ svc.vers++;
+ for(; toks != nil; toks = tl tl toks)
+ svc.set(hd toks, hd tl toks);
+ Qfind =>
+ s := string m.data;
+ toks := str->unquoted(s);
+ n := len toks;
+ if(n % 2 != 0)
+ return "odd attribute/value pairs";
+ f := Filter.find(fid.fid);
+ if(n != 0){
+ a := array[n/2] of (string, string);
+ for(n=0; toks != nil; n++){
+ a[n] = (hd toks, hd tl toks);
+ toks = tl tl toks;
+ }
+ if(f == nil)
+ f = Filter.new(fid.fid);
+ f.set(a);
+ }else{
+ if(f != nil)
+ f.remove();
+ }
+ * =>
+ return Egreg;
+ }
+ return nil;
+}
+
+clunk(fid: ref Fid)
+{
+ path := int fid.path;
+ case path & Mask {
+ Qsvc =>
+ svc := Service.find(path >> Shift);
+ if(svc != nil && svc.fid == fid.fid && int svc.get("persist") == 0)
+ svc.remove();
+ Qevent =>
+ ; # remove queued events?
+ Qfind =>
+ if((f := Filter.find(fid.fid)) != nil)
+ f.remove();
+ }
+}
+
+remove(fid: ref Fid): string
+{
+ path := int fid.path;
+ if((path & Mask) == Qsvc){
+ svc := Service.find(path >> Shift);
+ if(fid.uname == svc.owner){
+ svc.remove();
+ return nil;
+ }
+ }
+ return "permission denied";
+}
+
+svcnameok(s: string): string
+{
+ # could require that a service name contains at least one (or two) '!' characters.
+ for(i := 0; i < len s; i++){
+ c := s[i];
+ if(c <= 32 || c == '/' || c == 16r7f)
+ return "bad character in service name";
+ }
+ case s {
+ "new" or
+ "event" or
+ "find" or
+ "index" =>
+ return "bad service name";
+ }
+ for(i = 0; i < nservices; i++)
+ if(services[i].name == s)
+ return "duplicate service name";
+ return nil;
+}
+
+getindexdata(id: int, filterid: int): array of byte
+{
+ f: ref Filter;
+ if(filterid != Styx->NOFID)
+ f = Filter.find(filterid);
+ s := "";
+ for(i := 0; i < nservices; i++){
+ svc := services[i];
+ if(svc == nil || svc.name == nil)
+ continue;
+ if(id == -1){
+ if(f != nil && !f.match(svc.attrs))
+ continue;
+ }else if(svc.id != id)
+ continue;
+ s += sys->sprint("%q", services[i].name);
+ for(a := svc.attrs; a != nil; a = tl a){
+ (attr, val) := hd a;
+ s += sys->sprint(" %q %q", attr, val);
+ }
+ s[len s] = '\n';
+ }
+ return array of byte s;
+}
+
+navigator(navops: chan of ref Navop)
+{
+ while((m := <-navops) != nil){
+ path := int m.path;
+ pick n := m {
+ Stat =>
+ n.reply <-= dirgen(int n.path);
+ Walk =>
+ name := n.name;
+ case path & Mask {
+ Qroot =>
+ case name{
+ ".." =>
+ ; # nop
+ "new" =>
+ path = Qnew;
+ "index" =>
+ path = Qindex;
+ "event" =>
+ path = Qevent;
+ "find" =>
+ path = Qfind;
+ * =>
+ for(i := 0; i < nservices; i++)
+ if(services[i].name == name){
+ path = (services[i].id << Shift) | Qsvc;
+ break;
+ }
+ if(i == nservices){
+ n.reply <-= (nil, Enotfound);
+ continue;
+ }
+ }
+ * =>
+ if(name == ".."){
+ path = Qroot;
+ break;
+ }
+ n.reply <-= (nil, Enotdir);
+ continue;
+ }
+ n.reply <-= dirgen(path);
+ Readdir =>
+ d: array of int;
+ case path & Mask {
+ Qroot =>
+ Nstatic: con 3;
+ d = array[Nstatic + nservices] of int;
+ d[0] = Qnew;
+ d[1] = Qindex;
+ d[2] = Qfind;
+# d[3] = Qevent;
+ for(i := 0; i < nservices; i++)
+ if(services[i].name != nil)
+ d[i + Nstatic] = (services[i].id<<Shift) | Qsvc;
+ }
+ if(d == nil){
+ n.reply <-= (nil, Enotdir);
+ break;
+ }
+ for (i := n.offset; i < len d; i++)
+ n.reply <-= dirgen(d[i]);
+ n.reply <-= (nil, nil);
+ }
+ }
+}
+
+dirgen(path: int): (ref Sys->Dir, string)
+{
+ name: string;
+ perm: int;
+ svc: ref Service;
+ case path & Mask {
+ Qroot =>
+ name = ".";
+ perm = 8r777|Sys->DMDIR;
+ Qnew =>
+ name = "new";
+ perm = 8r666;
+ Qindex =>
+ name = "index";
+ perm = 8r444;
+ Qevent =>
+ name = "event";
+ perm = 8r444;
+ Qfind =>
+ name = "find";
+ perm = 8r666;
+ Qsvc =>
+ id := path >> Shift;
+ for(i := 0; i < nservices; i++)
+ if(services[i].id == id)
+ break;
+ if(i >= nservices)
+ return (nil, Enotfound);
+ svc = services[i];
+ name = svc.name;
+ perm = 8r644;
+ * =>
+ return (nil, Enotfound);
+ }
+ return (dir(path, name, perm, svc), nil);
+}
+
+dir(path: int, name: string, perm: int, svc: ref Service): ref Sys->Dir
+{
+ d := ref sys->zerodir;
+ d.qid.path = big path;
+ if(perm & Sys->DMDIR)
+ d.qid.qtype = Sys->QTDIR;
+ d.mode = perm;
+ d.name = name;
+ if(svc != nil){
+ d.uid = svc.owner;
+ d.gid = svc.owner;
+ d.atime = svc.atime;
+ d.mtime = svc.mtime;
+ d.qid.vers = svc.vers;
+ }else{
+ d.uid = "registry";
+ d.gid = "registry";
+ d.atime = startdate;
+ d.mtime = startdate;
+ if(path == Qroot)
+ d.qid.vers = rootvers;
+ }
+ return d;
+}
+
+blanksvc: Service;
+Service.new(owner: string): ref Service
+{
+ if(nservices == len services){
+ s := array[nservices * 3 / 2] of ref Service;
+ s[0:] = services;
+ services = s;
+ }
+ svc := ref blanksvc;
+ svc.id = idseq++;
+ svc.owner = owner;
+ svc.atime = now;
+ svc.mtime = now;
+
+ services[nservices] = svc;
+ svc.slot = nservices;
+ nservices++;
+ rootvers++;
+ return svc;
+}
+
+Service.find(id: int): ref Service
+{
+ for(i := 0; i < nservices; i++)
+ if(services[i].id == id)
+ return services[i];
+ return nil;
+}
+
+Service.remove(svc: self ref Service)
+{
+ slot := svc.slot;
+ services[slot] = nil;
+ nservices--;
+ rootvers++;
+ if(slot != nservices){
+ services[slot] = services[nservices];
+ services[slot].slot = slot;
+ services[nservices] = nil;
+ }
+}
+
+Service.get(svc: self ref Service, attr: string): string
+{
+ for(a := svc.attrs; a != nil; a = tl a)
+ if((hd a).t0 == attr)
+ return (hd a).t1;
+ return nil;
+}
+
+Service.set(svc: self ref Service, attr, val: string)
+{
+ for(a := svc.attrs; a != nil; a = tl a)
+ if((hd a).t0 == attr)
+ break;
+ if(a == nil){
+ svc.attrs = (attr, val) :: svc.attrs;
+ return;
+ }
+ attrs := (attr, val) :: tl a;
+ for(a = svc.attrs; a != nil; a = tl a){
+ if((hd a).t0 == attr)
+ break;
+ attrs = hd a :: attrs;
+ }
+ svc.attrs = attrs;
+}
+
+Filter.new(id: int): ref Filter
+{
+ f := ref Filter(id, nil);
+ filters = f :: filters;
+ return f;
+}
+
+Filter.find(id: int): ref Filter
+{
+ if(id != Styx->NOFID)
+ for(fl := filters; fl != nil; fl = tl fl)
+ if((hd fl).id == id)
+ return hd fl;
+ return nil;
+}
+
+Filter.set(f: self ref Filter, a: array of (string, string))
+{
+ f.attrs = a;
+}
+
+Filter.remove(f: self ref Filter)
+{
+ rl: list of ref Filter;
+ for(l := filters; l != nil; l = tl l)
+ if((hd l).id != f.id)
+ rl = hd l :: rl;
+ filters = rl;
+}
+
+Filter.match(f: self ref Filter, attrs: list of (string, string)): int
+{
+ for(i := 0; i < len f.attrs; i++){
+ (qn, qv) := f.attrs[i];
+ for(al := attrs; al != nil; al = tl al){
+ (n, v) := hd al;
+ if(n == qn && (qv == "*" || v == qv))
+ break;
+ }
+ if(al == nil)
+ break;
+ }
+ return i == len f.attrs;
+}
+
+dbload(db: ref Db)
+{
+ ptr: ref Attrdb->Dbptr;
+ for(;;){
+ e: ref Dbentry;
+ (e, ptr) = db.find(ptr, "service");
+ if(e == nil)
+ break;
+ svcname := e.findfirst("service");
+ if(svcname == nil || svcnameok(svcname) != nil)
+ continue;
+ svc := Service.new("registry"); # TO DO: read user's name
+ svc.name = svcname;
+ svc.fid = Styx->NOFID;
+ for(l := e.lines; l != nil; l = tl l){
+ for(al := (hd l).pairs; al != nil; al = tl al){
+ a := hd al;
+ if(a.attr != "service")
+ svc.set(a.attr, a.val);
+ }
+ }
+ }
+}
+
+# return index i >= start such that
+# s[i-1] == eoc, or len s if no such index exists.
+# eoc shouldn't be '
+qsplit(s: string, start: int, eoc: int): int
+{
+ inq := 0;
+ for(i := start; i < len s;){
+ c := s[i++];
+ if(inq){
+ if(c == '\'' && i < len s){
+ if(s[i] == '\'')
+ i++;
+ else
+ inq = 0;
+ }
+ }else{
+ if(c == eoc)
+ return i;
+ if(c == '\'')
+ inq = 1;
+ }
+ }
+ return i;
+}