implement Dbfs; # # Copyright © 1999 Vita Nuova Limited. All rights reserved. # include "sys.m"; sys: Sys; include "draw.m"; include "styxlib.m"; styx: Styxlib; Dirtab, Styxserver, Tmsg, Rmsg, Chan: import styx; Eperm, Ebadfid: import styx; devgen: Dirgenmod; 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 data: array of byte; }; Database: adt { name: string; file: ref Iobuf; records: array of ref Record; dirty: int; }; Dbfs: module { init: fn(nil: ref Draw->Context, nil: list of string); dirgen: fn(srv: ref Styxlib->Styxserver, c: ref Styxlib->Chan, tab: array of Styxlib->Dirtab, i: int): (int, Sys->Dir); }; Qdir, Qnew, Qdata: con iota; clockfd: ref Sys->FD; stderr: ref Sys->FD; database: ref Database; usage() { sys->fprint(stderr, "Usage: dbfs [-abcr] file mountpoint\n"); exit; } init(nil: ref Draw->Context, args: list of string) { sys = load Sys Sys->PATH; sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil); styx = load Styxlib Styxlib->PATH; if (styx == nil) sys->raise(sys->sprint("can't load %s: %r", Styxlib->PATH)); devgen = load Dirgenmod "$self"; if (devgen == nil) sys->raise(sys->sprint("can't load Dirgenmod: %r")); bufio = load Bufio Bufio->PATH; if(bufio == nil) sys->raise(sys->sprint("can't load Bufio: %r")); stderr = sys->fildes(2); flags := Sys->MREPL; copt := 0; empty := 0; if(args != nil) args = tl args; for(; args != nil; args = tl args){ s := hd args; if(s[0] != '-') break; for(i := 1; i < len s; i++) case s[i] { 'a' => flags = Sys->MAFTER; 'b' => flags = Sys->MBEFORE; 'r' => flags = Sys->MREPL; 'c' => copt = 1; 'e' => empty = 1; * => usage(); } } 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); exit; } (db, err) := dbread(ref Database(file, df, nil, 0)); if(db == nil){ sys->fprint(stderr, "dbfs: can't read %s: %s\n", file, err); exit; } db.file = nil; # dbprint(db); database = db; sys->pctl(Sys->FORKFD, nil); fds := array[2] of ref Sys->FD; sys->pipe(fds); (tchan, srv) := Styxserver.new(fds[0]); fds[0] = nil; pidc := chan of int; spawn serveloop(tchan, srv, pidc); <-pidc; if(sys->mount(fds[1], mountpt, flags, nil) == -1) { sys->print("mount failed: %r\n"); return; } } dbread(db: ref Database): (ref Database, string) { db.file.seek(0, Sys->SEEKSTART); rl: list of ref Record; 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 := len rl; 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, 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++){ printrec(stdout, db.records[i]); sys->print("\n"); } } newrecord(fields: array of byte): ref Record { n := len database.records; r := ref Record(n, n, 0, fields); a := array[n+1] of ref Record; if(n) a[0:] = database.records[0:]; a[n] = r; database.records = a; return r; } printrec(fd: ref Sys->FD, r: ref Record) { if(r.data != nil) sys->write(fd, r.data, len r.data); } serveloop(tchan: chan of ref Tmsg, srv: ref Styxserver, pidc: chan of int) { pidc <-= sys->pctl(Sys->FORKNS|Sys->NEWFD, 1::srv.fd.fd::nil); dirtab := array[1] of Dirtab; for (;;) { gm := <-tchan; if (gm == nil) { #sys->print("server got EOF: exiting\n"); exit; } pick m := gm { Readerror => sys->fprint(stderr, "dbfs: fatal read error: %s\n", m.error); exit; Nop => srv.reply(ref Rmsg.Nop(m.tag)); Flush => srv.devflush(m); Clone => srv.devclone(m); Walk => srv.devwalk(m, devgen, dirtab); Open => devopen(srv, m); Create => srv.reply(ref Rmsg.Error(m.tag, Eperm)); Read => c := srv.fidtochan(m.fid); if (c == nil) { srv.reply(ref Rmsg.Error(m.tag, Ebadfid)); break; } if (c.isdir()){ srv.devdirread(m, devgen, dirtab); break; } r := database.records[FILENO(c.qid)]; if(r == nil) srv.reply(ref Rmsg.Error(m.tag, "phase error")); else srv.reply(styx->readbytes(m, r.data)); Write => c := srv.fidtochan(m.fid); if(c == nil || !c.open){ srv.reply(ref Rmsg.Error(m.tag, Ebadfid)); break; } if(TYPE(c.qid) != Qdata){ srv.reply(ref Rmsg.Error(m.tag, Eperm)); break; } (r, err) := data2rec(m.data); if(err != nil){ srv.reply(ref Rmsg.Error(m.tag, err)); break; } database.records[FILENO(c.qid)] = r; # TO DO: r.vers++ database.dirty++; if(dbsync(database) == 0) srv.reply(ref Rmsg.Write(m.tag, m.fid, len m.data)); else srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r"))); Clunk => srv.devclunk(m); Stat => srv.devstat(m, devgen, dirtab); Remove => c := srv.fidtochan(m.fid); if(c == nil || c.isdir() || TYPE(c.qid) != Qdata){ srv.devremove(m); break; } r := database.records[FILENO(c.qid)]; if(r != nil) r.data = nil; database.dirty++; srv.chanfree(c); if(dbsync(database) == 0) srv.reply(ref Rmsg.Remove(m.tag, m.fid)); else srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r"))); Wstat => srv.reply(ref Rmsg.Error(m.tag, Eperm)); Attach => srv.devattach(m); } } } dirslot(n: int): int { for(i := 0; i < len database.records; i++){ # n² but the file will be small r := database.records[i]; if(r != nil && r.data != nil){ if(n == 0) return i; n--; } } return -1; } dirgen(srv: ref Styxserver, c: ref Styxlib->Chan, nil: array of Dirtab, i: int): (int, Sys->Dir) { d: Sys->Dir; if(i == 0) return (1, styx->devdir(c, QID(0, Qnew), "new", big 0, srv.uname, 8r600)); i--; j := dirslot(i); if(j < 0 || j >= len database.records) return (-1, d); return (1, styx->devdir(c, QID(j,Qdata), sys->sprint("%d", j), big 0, srv.uname, 8r600)); } devopen(srv: ref Styxserver, m: ref Tmsg.Open): ref Chan { c := srv.fidtochan(m.fid); if (c == nil) { srv.reply(ref Rmsg.Error(m.tag, Ebadfid)); return nil; } if(c.qid.path & Sys->CHDIR){ if(m.mode != Sys->OREAD) { srv.reply(ref Rmsg.Error(m.tag, Eperm)); return nil; } } else { if(c.uname != srv.uname) { srv.reply(ref Rmsg.Error(m.tag, Eperm)); return nil; } if(TYPE(c.qid) == Qnew){ # generate new file r := newrecord(array[0] of byte); c.qid = QID(r.x, Qdata); } c.qid.vers = 0; # TO DO: r.vers } if ((c.mode = styx->openmode(m.mode)) == -1) { srv.reply(ref Rmsg.Error(m.tag, styx->Ebadarg)); return nil; } c.open = 1; srv.reply(ref Rmsg.Open(m.tag, m.fid, c.qid)); return c; } QID(w, q: int): Sys->Qid { return Sys->Qid((w<<8)|q, 0); } TYPE(q: Sys->Qid): int { return q.path & 16rFF; } FILENO(q : Sys->Qid) : int { return ((q.path&~Sys->CHDIR)>>8) & 16rFFFFFF; } # # a record is (.+\n)*, without final empty line # data2rec(data: array of byte): (ref Record, string) { r := ref Record(-1, -1, 0, nil); 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 } r.data = data; return (r, 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]); }