ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/cmd/ndb/registry.b/
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);
};
Event: adt {
id: int; # fid reading from Qevents
vers: int; # last change seen
m: ref Tmsg.Read; # outstanding read request
new: fn(id: int): ref Event;
find: fn(id: int): ref Event;
remove: fn(e: self ref Event);
queue: fn(e: self ref Event, m: ref Tmsg.Read): string;
post: fn(vers: int);
flush: fn(tag: int);
};
filters: list of ref Filter;
events: list of ref Event;
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
attrdb = nil;
}
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 =>
sys->fprint(sys->fildes(2), "registry: styx read error: %s\n", m.error);
break Serve;
Open =>
(fid, nil, nil, 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));
Flush =>
Event.flush(m.oldtag);
srv.default(gm);
* =>
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));
Qevent =>
Event.new(fid.fid);
fid.open(m.mode, (fid.path, 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 =>
e := Event.find(fid.fid);
if(e.vers == rootvers)
return e.queue(m);
else{
s := sys->sprint("%8.8d\n", rootvers);
e.vers = rootvers;
m.offset = big 0;
srv.reply(styxservers->readstr(m, s));
return nil;
}
* =>
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){
err := svcnameok(hd toks);
if(err != nil)
return err;
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);
rootvers++;
Event.post(rootvers);
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 && (fid.mode & Sys->ORCLOSE || int svc.get("persist") == 0)){
svc.remove();
if(svc.name != nil){ # otherwise there's no visible change
rootvers++;
Event.post(rootvers);
}
}
Qevent =>
if((e := Event.find(fid.fid)) != nil)
e.remove();
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();
rootvers++;
Event.post(rootvers);
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" or
"" =>
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 4;
d = array[Nstatic + nservices] of int;
d[0] = Qnew;
d[1] = Qindex;
d[2] = Qfind;
d[3] = Qevent;
nd := 0;
for(i := 0; i < nservices; i++)
if(services[i].name != nil){
d[nd + Nstatic] = (services[i].id<<Shift) | Qsvc;
nd++;
}
d = d[0:Nstatic + nd];
}
if(d == nil){
n.reply <-= (nil, Enotdir);
break;
}
for(i := n.offset; i < len d; i++){
(dir, err) := dirgen(d[i]);
if(dir == nil)
sys->fprint(sys->fildes(2), "registry: bad qid %#ux: %s\n", d[i], err);
else
n.reply <-= (dir, err);
}
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++;
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--;
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;
}
Event.new(id: int): ref Event
{
e := ref Event(id, rootvers, nil);
events = e::events;
return e;
}
Event.find(id: int): ref Event
{
for(l := events; l != nil; l = tl l)
if((hd l).id == id)
return hd l;
return nil;
}
Event.remove(e: self ref Event)
{
rl: list of ref Event;
for(l := events; l != nil; l = tl l)
if((hd l).id != e.id)
rl = hd l :: rl;
events = rl;
}
Event.queue(e: self ref Event, m: ref Tmsg.Read): string
{
if(e.m != nil)
return "concurrent read for event fid";
m.offset = big 0;
e.m = m;
return nil;
}
Event.post(vers: int)
{
s := sys->sprint("%8.8d\n", vers);
for(l := events; l != nil; l = tl l){
e := hd l;
if(e.vers < vers && e.m != nil){
srv.reply(styxservers->readstr(e.m, s));
e.vers = vers;
e.m = nil;
}
}
}
Event.flush(tag: int)
{
for(l := events; l != nil; l = tl l){
e := hd l;
if(e.m != nil && e.m.tag == tag){
e.m = nil;
break;
}
}
}
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;
}