# Mdbfs was written my Matt Heath matt@proweb.co.uk in July 2003 # It was heavily adapted from # /appl/cmd/dbfs.b # which contains this : # Copyright © 1999 Vita Nuova Limited. All rights reserved. # Revisions copyright © 2002 Vita Nuova Holdings Limited. All rights reserved. # I'm not sure what I'm supposed to put here. The Licence says that if I supply a binary I agree to supply the source # and any derivatative works (i.e. this one) carry the same burden # /me shrugs and carries on # on with the show : # Mdbfs is an extension to dbfs that takes it into the second dimension with both rows & columns # it mounts the data read only whereas dbfs was read/write. # Based on my tabfs the idea was that it would consume less memory but with the primary key hashtable # it actually uses more ! # mind you the trade off is time, it took six hours to run out of 256Mb of memory with the last one # at least this one has the grace to do it in 30 seconds # Usage: mdbfs [-a|-b] [-D] [-1] [-p n] [-P] file mountpoint # -a|-b - after / before # -D - print each Tmsg/Rmsg # -1 - use the first line of the file as the column names (otherwise they are numbered) # -p n - use column number n as the primary key and therefore the name of the directory for each row (otherwise numbered) # if this column contains duplicates they will still be created but with distinct Qids but the Hashtable will get screwed # so it's best not to do that, I've not played with that so be it on your own head # -P - print every cell of the database in qid order, one per line, data is bracketed thus : >%s< I was using it for debugging but left it in # file - The file is split into records on each non-empty line using string->unquoted. The number of columns is calculated # from the first line. If a row contains less they are still included, any extra are discarded implement Mdbfs; include "sys.m"; sys: Sys; Qid: import Sys; include "draw.m"; include "arg.m"; include "styx.m"; styx: Styx; Tmsg, Rmsg: import styx; include "string.m"; str: String; include "styxservers.m"; styxservers: Styxservers; Fid, Styxserver, Navigator, Navop: import styxservers; Enotfound, Eperm, Ebadarg: import styxservers; include "bufio.m"; bufio: Bufio; Iobuf: import bufio; include "hash.m"; hash : Hash; HashTable, HashVal : import hash; Record: adt { id: int; # file number in directory dirty: int; # modified but not written vers: int; # version data: string; print: fn(r: self ref Record); qid: fn (r: self ref Record): Sys->Qid; }; Database: adt { name: string; file: ref Iobuf; records: array of ref Record; dirty: int; vers: int; row_count : int; first_row_contains_column_names : int; primary_key_column_index : int; column_names_string : string; column_names_hash : ref HashTable; column_names_array : array of string; column_count : int; pk_hash : ref HashTable; getrec: fn(db: self ref Database): (string, array of string, string); findrec: fn(db: self ref Database, row, column: int): ref Record; set_column_names: fn(db: self ref Database, names : string); set_column_names_to_numbers: fn(db: self ref Database, num : int); print: fn(db : self ref Database); }; Mdbfs: module { init: fn(nil: ref Draw->Context, nil: list of string); }; Qroot, Qrow, Qcolumn: con iota; stderr: ref Sys->FD; database: ref Database; user: string; Eremoved: con "file removed"; progname : con "2dbfs"; rootfiles := array[] of {"/", "data", "src_filename", "column_names"}; usage() { sys->fprint(stderr, "Usage: %s [-a|-b] [-D] [-1] [-p n] [-P] file mountpoint\n", progname); raise "fail:usage"; } nomod(s: string) { sys->fprint(stderr, "%s: can't load %s: %r\n", progname, s); raise "fail:load"; } nomem(s : string) { sys->fprint(stderr, "%s : can't allocate memory - %s", progname, s); raise "fail:memory"; } 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); str = load String String->PATH; if(str == nil) nomod(String->PATH); hash = load Hash Hash->PATH; if(hash == nil) nomod(Hash->PATH); arg := load Arg Arg->PATH; if(arg == nil) nomod(Arg->PATH); arg->init(args); flags := Sys->MREPL; copt := 0; first_row_contains_column_names := 0; primary_key_column_index := -1; print_db := 0; while((o := arg->opt()) != 0) case o { 'a' => flags = Sys->MAFTER; 'b' => flags = Sys->MBEFORE; 'D' => styxservers->traceset(1); 'P' => print_db = 1; '1' => first_row_contains_column_names = 1; 'p' => primary_key_column_index = int arg->arg(); * => usage(); } args = arg->argv(); arg = nil; if(len args != 2) usage(); file := hd args; args = tl args; mountpt := hd args; df := bufio->open(file, Sys->OREAD); if(df == nil){ sys->fprint(stderr, "%s: can't open %s: %r\n", progname, file); raise "fail:open"; } (db, err) := dbread(ref Database(file, df, nil, 0, 0, 0, first_row_contains_column_names, primary_key_column_index, nil, nil, nil, 0, nil)); if(db == nil){ sys->fprint(stderr, "%s: can't read %s: %s\n", progname, file, err); raise "fail:dbread"; } db.file = nil; 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, "%s: can't create pipe: %r\n", progname); raise "fail:pipe"; } navops := chan of ref Navop; spawn navigator(navops); (tchan, srv) := Styxserver.new(fds[0], Navigator.new(navops), big Qroot); fds[0] = nil; pidc := chan of int; spawn serveloop(tchan, srv, pidc, navops); <-pidc; if (print_db) db.print(); if(sys->mount(fds[1], nil, mountpt, flags, nil) < 0) { sys->fprint(stderr, "%s: mount failed: %r\n", progname); 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 array of string; (data, r, err) := db.getrec(); if(err != nil) return (nil, err); if(db.first_row_contains_column_names == 1) { db.set_column_names(data); (nil, r, err) = db.getrec(); if(err != nil) return (nil, err); } else db.set_column_names_to_numbers(len r); rl = r :: rl; db.row_count = 1; if(db.column_count < 1) return (nil, "need at least one column name"); for(;;){ (nil, r, err) = db.getrec(); if(err != nil) return (nil, err); if(r == nil) break; rl = r :: rl; db.row_count++; } record_count := db.row_count * db.column_count; db.records = array[record_count] of ref Record; db.pk_hash = hash->new(record_count); row_num := db.row_count -1; pk_value : string; for(; rl != nil; rl = tl rl) { r = hd rl; if(db.primary_key_column_index < 0) pk_value = sys->sprint("%d", row_num); else if (len r < db.column_count) pk_value = sys->sprint("!%d!", row_num); else pk_value = r[db.primary_key_column_index]; db.pk_hash.insert(pk_value, HashVal(row_num--, 0.0, nil)); for (column_count := db.column_count - 1; column_count >= 0; column_count--) { rec := ref Record(--record_count, 0, 0, r[column_count]); db.records[record_count] = rec; } } return (db, nil); } Database.getrec(db: self ref Database): (string, array of string, string) { data := db.file.gets('\n'); if(data == nil) return (nil, nil, nil); # BUG: distinguish i/o error from EOF? - nah (data, nil) = str->splitl(data, "\n"); if(data == "") return db.getrec(); # skip blank lines return (data, list_to_array(str->unquoted(data)), nil); } Database.findrec(db: self ref Database, row, column: int): ref Record { a := db.records[row].data[column]; return nil; } Database.set_column_names(db: self ref Database, names : string) { uq := str->unquoted(names); db.column_names_hash = list_to_hash(uq); db.column_names_array = list_to_array(uq); db.column_names_string = names; db.column_count = len db.column_names_array; } Database.set_column_names_to_numbers(db: self ref Database, num : int) { namestring := sys->sprint("%d", 0); for(i:=1; i < num; i++) namestring += sys->sprint(" %d", i); db.set_column_names(namestring); } Database.print(db: self ref Database) { record_count := len db.records; for( i:=0; i < record_count; i++) db.records[i].print(); } Record.qid(r: self ref Record): Sys->Qid { return Sys->Qid(QPATH(r.id, Qcolumn), r.vers, Sys->QTFILE); } Record.print(r: self ref Record) { sys->fprint(stderr, "id: %6d qid: %16.16bx data: >%s<\n", r.id, r.qid().path, r.data); } 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, "%s: fatal read error: %s\n", progname, m.error); break Serve; 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; } case TYPE(c.path) { Qroot => case FILENO(c.path) { 2 => srv.reply(styxservers->readstr(m, database.name)); 3 => srv.reply(styxservers->readstr(m, database.column_names_string)); * => srv.default(gm); } Qcolumn => rec_index := FILENO(c.path); if (rec_index < len database.records) srv.reply(styxservers->readstr(m, database.records[rec_index].data)); else srv.default(gm); * => srv.default(gm); } Write => # nothing writable atm. (c, merr) := srv.canwrite(m); if(c == nil){ srv.reply(ref Rmsg.Error(m.tag, merr)); break; } case TYPE(c.path) { Qroot => case FILENO(c.path) { # 2 => srv.reply(styxservers->readstr(m, database.name)); # 3 => srv.reply(styxservers->readstr(m, database.column_names_string)); * => srv.default(gm); } } Clunk => srv.clunk(m); * => srv.default(gm); } } navops <-= nil; # shut down navigator } 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) { file_index := FILENO(p) ; case TYPE(p) { Qroot => case file_index { 0 => qid := Qid(QPATH(file_index, Qroot), database.vers, Sys->QTDIR); return (dir(qid, "/", big 0, user, 8r700), nil); 1 => qid := Qid(QPATH(file_index, Qroot), database.vers, Sys->QTDIR); return (dir(qid, "data", big 0, user, 8r555), nil); * => if (file_index < len rootfiles) { qid := Qid(QPATH(file_index, Qroot), database.vers, Sys->QTFILE); return (dir(qid, rootfiles[file_index], big 0, user, 8r666), nil); } } Qrow => pk_value : string; if (database.primary_key_column_index < 0) { pk_value = sys->sprint("%d", file_index); } else { cell := file_index * database.column_count + database.primary_key_column_index; if (cell < len database.records) pk_value = database.records[cell].data; } if (pk_value != nil) { qid := Qid(QPATH(file_index, Qrow), database.vers, Sys->QTDIR); return (dir(qid, pk_value, big 0, user, 8r555), nil); } Qcolumn => column_number := file_index % database.column_count; if (file_index < len database.records) return (dir(database.records[file_index].qid(), database.column_names_array[column_number], big 0, user, 8r444), nil); } return (nil, Enotfound); } navigator(navops: chan of ref Navop) { while((m := <-navops) != nil){ pick n := m { Stat => n.reply <-= dirgen(n.path); Walk => case (TYPE(n.path)) { Qroot => case FILENO(n.path) { 0 => case n.name { "data" => n.reply <-= dirgen(QPATH(1, Qroot)); "src_filename" => n.reply <-= dirgen(QPATH(2, Qroot)); "column_names" => n.reply <-= dirgen(QPATH(3, Qroot)); * => n.reply <-= (nil, Enotfound); } 1 => pk := database.pk_hash.find(n.name); if(pk == nil) { n.reply <-= (nil, Enotfound); continue; } n.path = QPATH(pk.i, Qrow); n.reply <-= dirgen(n.path); * => n.reply <-= (nil, "not a directory"); } Qrow => row_index := FILENO(n.path); column_hashval := database.column_names_hash.find(n.name); if(column_hashval == nil) { n.reply <-= (nil, Enotfound); continue; } n.path = QPATH(column_hashval.i + database.column_count * row_index, Qcolumn); n.reply <-= dirgen(n.path); * => n.reply <-= (nil, "not a directory"); } Readdir => case (TYPE(n.path)) { Qroot => case FILENO(n.path) { 0 => for(i := n.offset; --n.count >= 0 && i < len rootfiles; i++) n.reply <-= dirgen(QPATH(i,Qroot)); 1 => for(i := n.offset; --n.count >= 0 && i < database.row_count ; i++) n.reply <-= dirgen(QPATH(i,Qrow)); } n.reply <-= (nil, nil); Qrow => i_start := FILENO(n.path) * database.column_count; for(i := n.offset; --n.count >= 0 && i < database.column_count ; i++) n.reply <-= dirgen(QPATH(i_start++, Qcolumn)); n.reply <-= (nil, nil); * => n.reply <-= (nil, "not a directory"); } } } } QPATH(index, qtype: int): big { bigi := big index; bigi = bigi << 32; return bigi | big qtype; } TYPE(path: big): int { return int path; } FILENO(path: big) : int { return int (path >> 32) ; } list_to_array(lizt : list of string) : array of string { list_length := len lizt; hooray := array[list_length] of string; for(i := 0; i < list_length ; i++) { hooray[i] = hd lizt; lizt = tl lizt; } return hooray; } list_to_hash(lizt : list of string) : ref HashTable { list_length := len lizt; hsh := hash->new(list_length); j := 0; for(i := list_length - 1; i > -1 ; i--) { v := HashVal(j++, 0.0, nil); hsh.insert(hd lizt, v); lizt = tl lizt; } return hsh; }