summaryrefslogtreecommitdiff
path: root/appl/cmd/dbfs.b
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
commit37da2899f40661e3e9631e497da8dc59b971cbd0 (patch)
treecbc6d4680e347d906f5fa7fca73214418741df72 /appl/cmd/dbfs.b
parent54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff)
20060303a
Diffstat (limited to 'appl/cmd/dbfs.b')
-rw-r--r--appl/cmd/dbfs.b518
1 files changed, 518 insertions, 0 deletions
diff --git a/appl/cmd/dbfs.b b/appl/cmd/dbfs.b
new file mode 100644
index 00000000..9482e8df
--- /dev/null
+++ b/appl/cmd/dbfs.b
@@ -0,0 +1,518 @@
+implement Dbfs;
+
+#
+# Copyright © 1999 Vita Nuova Limited. All rights reserved.
+# Revisions copyright © 2002 Vita Nuova Holdings Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+ Qid: import Sys;
+
+include "draw.m";
+
+include "arg.m";
+
+include "styx.m";
+ styx: Styx;
+ Tmsg, Rmsg: import styx;
+
+include "styxservers.m";
+ styxservers: Styxservers;
+ Fid, Styxserver, Navigator, Navop: import styxservers;
+ Enotfound, Eperm, Ebadarg: import styxservers;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+Record: adt {
+ id: int; # file number in directory
+ x: int; # index in file
+ dirty: int; # modified but not written
+ vers: int; # version
+ data: array of byte;
+
+ new: fn(x: array of byte): ref Record;
+ print: fn(r: self ref Record, fd: ref Sys->FD);
+ qid: fn(r: self ref Record): Sys->Qid;
+};
+
+Database: adt {
+ name: string;
+ file: ref Iobuf;
+ records: array of ref Record;
+ dirty: int;
+ vers: int;
+ nextid: int;
+
+ findrec: fn(db: self ref Database, id: int): ref Record;
+};
+
+Dbfs: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+Qdir, Qnew, Qdata: con iota;
+
+clockfd: ref Sys->FD;
+stderr: ref Sys->FD;
+database: ref Database;
+user: string;
+Eremoved: con "file removed";
+
+usage()
+{
+ sys->fprint(stderr, "Usage: dbfs [-a|-b|-ac|-bc] [-D] file mountpoint\n");
+ raise "fail:usage";
+}
+
+nomod(s: string)
+{
+ sys->fprint(stderr, "dbfs: can't load %s: %r\n", s);
+ raise "fail:load";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil);
+ stderr = sys->fildes(2);
+ styx = load Styx Styx->PATH;
+ if(styx == nil)
+ nomod(Styx->PATH);
+ styx->init();
+ styxservers = load Styxservers Styxservers->PATH;
+ if(styxservers == nil)
+ nomod(Styxservers->PATH);
+ styxservers->init(styx);
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ nomod(Bufio->PATH);
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ nomod(Arg->PATH);
+ arg->init(args);
+ flags := Sys->MREPL;
+ copt := 0;
+ empty := 0;
+ while((o := arg->opt()) != 0)
+ case o {
+ 'a' => flags = Sys->MAFTER;
+ 'b' => flags = Sys->MBEFORE;
+ 'c' => copt = 1;
+ 'e' => empty = 1;
+ 'D' => styxservers->traceset(1);
+ * => usage();
+ }
+ args = arg->argv();
+ arg = nil;
+
+ if(len args != 2)
+ usage();
+ if(copt)
+ flags |= Sys->MCREATE;
+ file := hd args;
+ args = tl args;
+ mountpt := hd args;
+
+ df := bufio->open(file, Sys->OREAD);
+ if(df == nil && empty){
+ (rc, d) := sys->stat(file);
+ if(rc < 0)
+ df = bufio->create(file, Sys->OREAD, 8r600);
+ }
+ if(df == nil){
+ sys->fprint(stderr, "dbfs: can't open %s: %r\n", file);
+ raise "fail:open";
+ }
+ (db, err) := dbread(ref Database(file, df, nil, 0, 0, 0));
+ if(db == nil){
+ sys->fprint(stderr, "dbfs: can't read %s: %s\n", file, err);
+ raise "fail:dbread";
+ }
+ db.file = nil;
+# dbprint(db);
+ database = db;
+
+ sys->pctl(Sys->FORKFD, nil);
+
+ user = rf("/dev/user");
+ if(user == nil)
+ user = "inferno";
+
+ fds := array[2] of ref Sys->FD;
+ if(sys->pipe(fds) < 0){
+ sys->fprint(stderr, "dbfs: can't create pipe: %r\n");
+ raise "fail:pipe";
+ }
+
+ navops := chan of ref Navop;
+ spawn navigator(navops);
+
+ (tchan, srv) := Styxserver.new(fds[0], Navigator.new(navops), big Qdir);
+ fds[0] = nil;
+
+ pidc := chan of int;
+ spawn serveloop(tchan, srv, pidc, navops);
+ <-pidc;
+
+ if(sys->mount(fds[1], nil, mountpt, flags, nil) < 0) {
+ sys->fprint(stderr, "dbfs: mount failed: %r\n");
+ raise "fail:mount";
+ }
+}
+
+rf(f: string): string
+{
+ fd := sys->open(f, Sys->OREAD);
+ if(fd == nil)
+ return nil;
+ b := array[Sys->NAMEMAX] of byte;
+ n := sys->read(fd, b, len b);
+ if(n < 0)
+ return nil;
+ return string b[0:n];
+}
+
+dbread(db: ref Database): (ref Database, string)
+{
+ db.file.seek(big 0, Sys->SEEKSTART);
+ rl: list of ref Record;
+ n := 0;
+ for(;;){
+ (r, err) := getrec(db);
+ if(err != nil)
+ return (nil, err); # could press on without it, or make it the `file' contents
+ if(r == nil)
+ break;
+ rl = r :: rl;
+ n++;
+ }
+ db.nextid = n;
+ db.records = array[n] of ref Record;
+ for(; rl != nil; rl = tl rl){
+ r := hd rl;
+ n--;
+ r.id = n;
+ r.x = n;
+ db.records[n] = r;
+ }
+ return (db, nil);
+}
+
+#
+# a record is (.+\n)*\n
+#
+getrec(db: ref Database): (ref Record, string)
+{
+ r := ref Record(-1, -1, 0, 0, nil);
+ data := "";
+ for(;;){
+ s := db.file.gets('\n');
+ if(s == nil){
+ if(data == nil)
+ return (nil, nil); # BUG: distinguish i/o error from EOF?
+ break;
+ }
+ if(s[len s - 1] != '\n')
+# return (nil, "file missing newline"); # possibly truncated
+ s += "\n";
+ if(s == "\n")
+ break;
+ data += s;
+ }
+ r.data = array of byte data;
+ return (r, nil);
+}
+
+dbsync(db: ref Database): int
+{
+ if(db.dirty){
+ db.file = bufio->create(db.name, Sys->OWRITE, 8r666);
+ if(db.file == nil)
+ return -1;
+ for(i := 0; i < len db.records; i++){
+ r := db.records[i];
+ if(r != nil && r.data != nil){
+ if(db.file.write(r.data, len r.data) != len r.data)
+ return -1;
+ db.file.putc('\n');
+ }
+ }
+ if(db.file.flush())
+ return -1;
+ db.file = nil;
+ db.dirty = 0;
+ }
+ return 0;
+}
+
+dbprint(db: ref Database)
+{
+ stdout := sys->fildes(1);
+ for(i := 0; i < len db.records; i++){
+ db.records[i].print(stdout);
+ sys->print("\n");
+ }
+}
+
+Database.findrec(db: self ref Database, id: int): ref Record
+{
+ for(i:=0; i<len db.records; i++)
+ if((r := db.records[i]) != nil && r.id == id)
+ return r;
+ return nil;
+}
+
+Record.new(fields: array of byte): ref Record
+{
+ n := len database.records;
+ r := ref Record(n, n, 0, 0, fields);
+ a := array[n+1] of ref Record;
+ if(n)
+ a[0:] = database.records[0:];
+ a[n] = r;
+ database.records = a;
+ database.vers++;
+ return r;
+}
+
+Record.print(r: self ref Record, fd: ref Sys->FD)
+{
+ if(r.data != nil)
+ sys->write(fd, r.data, len r.data);
+}
+
+Record.qid(r: self ref Record): Sys->Qid
+{
+ return Sys->Qid(QPATH(r.x, Qdata), r.vers, Sys->QTFILE);
+}
+
+serveloop(tchan: chan of ref Tmsg, srv: ref Styxserver, pidc: chan of int, navops: chan of ref Navop)
+{
+ pidc <-= sys->pctl(Sys->FORKNS|Sys->NEWFD, 1::2::srv.fd.fd::nil);
+Serve:
+ while((gm := <-tchan) != nil){
+ pick m := gm {
+ Readerror =>
+ sys->fprint(stderr, "dbfs: fatal read error: %s\n", m.error);
+ break Serve;
+ Open =>
+ c := srv.getfid(m.fid);
+ if(c == nil || TYPE(c.path) != Qnew){
+ srv.open(m); # default action
+ break;
+ }
+ if(c.uname != user) {
+ srv.reply(ref Rmsg.Error(m.tag, Eperm));
+ break;
+ }
+ mode := styxservers->openmode(m.mode);
+ if(mode < 0) {
+ srv.reply(ref Rmsg.Error(m.tag, Ebadarg));
+ break;
+ }
+ # generate new file, change Fid's qid to match
+ r := Record.new(array[0] of byte);
+ qid := r.qid();
+ c.open(mode, qid);
+ srv.reply(ref Rmsg.Open(m.tag, qid, srv.iounit()));
+ Read =>
+ (c, err) := srv.canread(m);
+ if(c == nil){
+ srv.reply(ref Rmsg.Error(m.tag, err));
+ break;
+ }
+ if(c.qtype & Sys->QTDIR){
+ srv.read(m); # does readdir
+ break;
+ }
+ r := database.records[FILENO(c.path)];
+ if(r == nil)
+ srv.reply(ref Rmsg.Error(m.tag, Eremoved));
+ else
+ srv.reply(styxservers->readbytes(m, r.data));
+ Write =>
+ (c, merr) := srv.canwrite(m);
+ if(c == nil){
+ srv.reply(ref Rmsg.Error(m.tag, merr));
+ break;
+ }
+ (value, err) := data2rec(m.data);
+ if(err != nil){
+ srv.reply(ref Rmsg.Error(m.tag, err));
+ break;
+ }
+ fno := FILENO(c.path);
+ r := database.records[fno];
+ if(r == nil){
+ srv.reply(ref Rmsg.Error(m.tag, Eremoved));
+ break;
+ }
+ r.data = value;
+ r.vers++;
+ database.dirty++;
+ if(dbsync(database) == 0)
+ srv.reply(ref Rmsg.Write(m.tag, len m.data));
+ else
+ srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r")));
+ Clunk =>
+ # a transaction-oriented dbfs could delay updating the record until clunk
+ srv.clunk(m);
+ Remove =>
+ c := srv.getfid(m.fid);
+ if(c == nil || c.qtype & Sys->QTDIR || TYPE(c.path) != Qdata){
+ # let it diagnose all the errors
+ srv.remove(m);
+ break;
+ }
+ r := database.records[FILENO(c.path)];
+ if(r != nil)
+ r.data = nil;
+ database.dirty++;
+ srv.delfid(c);
+ if(dbsync(database) == 0)
+ srv.reply(ref Rmsg.Remove(m.tag));
+ else
+ srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r")));
+ Wstat =>
+ srv.default(gm); # TO DO?
+ * =>
+ srv.default(gm);
+ }
+ }
+ navops <-= nil; # shut down navigator
+}
+
+dirslot(n: int): int
+{
+ for(i := 0; i < len database.records; i++){
+ r := database.records[i];
+ if(r != nil && r.data != nil){
+ if(n == 0)
+ return i;
+ n--;
+ }
+ }
+ return -1;
+}
+
+dir(qid: Sys->Qid, name: string, length: big, uid: string, perm: int): ref Sys->Dir
+{
+ d := ref sys->zerodir;
+ d.qid = qid;
+ if(qid.qtype & Sys->QTDIR)
+ perm |= Sys->DMDIR;
+ d.mode = perm;
+ d.name = name;
+ d.uid = uid;
+ d.gid = uid;
+ d.length = length;
+ return d;
+}
+
+dirgen(p: big): (ref Sys->Dir, string)
+{
+ case TYPE(p) {
+ Qdir =>
+ return (dir(Qid(QPATH(0, Qdir),database.vers,Sys->QTDIR), "/", big 0, user, 8r700), nil);
+ Qnew =>
+ return (dir(Qid(QPATH(0, Qnew),0,Sys->QTFILE), "new", big 0, user, 8r600), nil);
+ * =>
+ n := FILENO(p);
+ if(n < 0 || n >= len database.records)
+ return (nil, nil);
+ r := database.records[n];
+ if(r == nil || r.data == nil)
+ return (nil, Enotfound);
+ return (dir(r.qid(), sys->sprint("%d", r.id), big len r.data, user, 8r600), nil);
+ }
+}
+
+navigator(navops: chan of ref Navop)
+{
+ while((m := <-navops) != nil){
+ pick n := m {
+ Stat =>
+ n.reply <-= dirgen(n.path);
+ Walk =>
+ if(int n.path != Qdir){
+ n.reply <-= (nil, "not a directory");
+ break;
+ }
+ case n.name {
+ ".." =>
+ ; # nop
+ "new" =>
+ n.path = QPATH(0, Qnew);
+ * =>
+ if(len n.name < 1 || !(n.name[0]>='0' && n.name[0]<='9')){ # weak test for now
+ n.reply <-= (nil, Enotfound);
+ continue;
+ }
+ r := database.findrec(int n.name);
+ if(r == nil){
+ n.reply <-= (nil, Enotfound);
+ continue;
+ }
+ n.path = QPATH(r.x, Qdata);
+ }
+ n.reply <-= dirgen(n.path);
+ Readdir =>
+ if(int m.path != Qdir){
+ n.reply <-= (nil, "not a directory");
+ break;
+ }
+ i := n.offset;
+ if(i == 0)
+ n.reply <-= dirgen(QPATH(0,Qnew));
+ for(; --n.count >= 0 && (j := dirslot(i)) >= 0; i++)
+ n.reply <-= dirgen(QPATH(j,Qdata)); # n² but the file will be small
+ n.reply <-= (nil, nil);
+ }
+ }
+}
+
+QPATH(w, q: int): big
+{
+ return big ((w<<8)|q);
+}
+
+TYPE(path: big): int
+{
+ return int path & 16rFF;
+}
+
+FILENO(path: big) : int
+{
+ return (int path >> 8) & 16rFFFFFF;
+}
+
+#
+# a record is (.+\n)*, without final empty line
+#
+data2rec(data: array of byte): (array of byte, string)
+{
+ s: string;
+ for(b := data; len b > 0;){
+ (b, s) = getline(b);
+ if(s == nil || s[len s - 1] != '\n' || s == "\n")
+ return (nil, "partial or malformed record"); # possibly truncated
+ }
+ return (data, nil);
+}
+
+getline(b: array of byte): (array of byte, string)
+{
+ n := len b;
+ for(i := 0; i < n; i++){
+ (ch, l, nil) := sys->byte2char(b, i);
+ i += l;
+ if(l == 0 || ch == '\n')
+ break;
+ }
+ return (b[i:], string b[0:i]);
+}