ref: aee91c4ed0277e343a62cd124ee7175f54ffefb3
author: Linux User <l@malkuth.lan>
date: Sat Jul 22 12:17:55 EDT 2023
wip - main, config, ctlfs
--- /dev/null
+++ b/.gitignore
@@ -1,0 +1,2 @@
+*.dis
+*.sbl
--- /dev/null
+++ b/LICENSE
@@ -1,0 +1,27 @@
+Copyright © 2023, kitzman @ disroot.org
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of {{ project }} nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
--- /dev/null
+++ b/README
@@ -1,0 +1,32 @@
+dddb (Distributed DisVM Database) is a WIP RDBMS, aimed
+to run on DisVM, hosted or native.
+
+TODO Components and functionalities
+
+* dddbctl fs - authenticated 9p fs to control, request, and query
+ operations related to the database instance; it
+ is the main driver for grid database
+* dddbclient fs - the tcp port 'dddb' is used such that clients
+ can have a fs in which queries can be opened;
+ it will support ANSI SQL
+* db registry - each instance will, on startup, connect to
+ other instances defined in an ndb configuration
+ file, register itself in that instance, and
+ will be able to use it for: duplicating data,
+ sharding data, and mount further instances;
+ (i.e if a cluster is behind a firewall, and
+ only one instance exposed, the hidden ones
+ can be mounted outside the fw)
+* data structures (1) - primitives, pages, sections, indices,
+ tables, rows
+* data structures (2) - B-trees, Bloom filters
+* storage operations - a mounted directory tree will be used
+ for RDRW operations; memfs will be used
+ for caching the data in RAM (performance?)
+* venti/vac support - venti + vac + memfs should suffice for
+ storing and persisting data (ramfossil-like);
+ this means scripts will clean and archive it
+* SQL parser - obviously
+* statement optimizer - obviously
+* wm/dddbmon - a nice admin monitor would be nice
+* dddbjdbc/dddbodbc - JDBC and ODBC drivers are mandatory
--- /dev/null
+++ b/appl/cmd/config.b
@@ -1,0 +1,118 @@
+include "bufio.m";
+include "attrdb.m";
+ attrdb: Attrdb;
+ Attr, Db, Dbptr, Dbentry: import attrdb;
+
+# Default configuration values
+DCFGPATH: con "/lib/ndb/dddbcfg";
+
+DADDR: con "tcp!*!dddbctl";
+DFSWRKS: con 10;
+
+# Attrdb constants
+KNAME: con "nodename";
+KSYSNAME: con "nodesysn";
+KADDR: con "addr";
+KSTORAGE: con "storage";
+KFSWRKS: con "readworkers";
+
+Config.open(nodename: string, path: string): Config
+{
+ sys = load Sys Sys->PATH;
+
+ n: int;
+ sysname: string;
+ buf := array[Sys->NAMEMAX] of byte;
+
+ attrdb = load Attrdb Attrdb->PATH;
+ if(attrdb == nil)
+ error("Attrb not found");
+ attrdb->init();
+
+ if(len path == 0)
+ path = DCFGPATH;
+
+ if(debug)
+ sys->fprint(stderr, "config: opening %s\n", path);
+ db := Db.open(path);
+
+ if(db == nil)
+ error(sys->sprint("ndb file %s could not be opened", path));
+
+ sysname_fd := sys->open("#c/sysname", Sys->OREAD);
+ if(sysname_fd == nil)
+ error(sys->sprint("#c/sysname could not be read"));
+ n = sys->read(sysname_fd, buf, len buf);
+ if(n <= 0)
+ error(sys->sprint("could not read sysname"));
+ sysname = string buf[0:n];
+
+ if(len nodename == 0) {
+ nodename = sysname;
+ }
+
+ thiscfg: ref NodeConfig;
+ nodecfgs: list of NodeConfig;
+ entry: ref Dbentry;
+ dbptr: ref Dbptr;
+
+ (entry, dbptr) = db.find(dbptr, KNAME);
+ while(entry != nil) {
+ nodecfg := NodeConfig.new(entry);
+
+ if(debug)
+ sys->fprint(stderr, "config: found node %s\n", nodecfg.name);
+ if(nodecfg.name == nodename) {
+ thiscfg = ref nodecfg;
+ }
+ else
+ nodecfgs = nodecfg :: nodecfgs;
+
+ (entry, dbptr) = db.find(dbptr, KNAME);
+ }
+
+ if(thiscfg == nil)
+ error("could not find config for nodename");
+
+ if(thiscfg.addr == "")
+ error("node lacks an address");
+
+ if(thiscfg.storage == "")
+ error("node lacks a storage path");
+
+ if(thiscfg.sysn != sysname)
+ error("node and system sysname do not match");
+
+ return Config(
+ thiscfg.name, thiscfg.sysn, thiscfg.addr, # own configuration
+ thiscfg.storage, thiscfg.fswrks,
+ nodecfgs); # configured nodes
+}
+
+NodeConfig.new(entry: ref Dbentry): NodeConfig
+{
+ name := entry.findfirst(KNAME);
+ sysname := entry.findfirst(KSYSNAME);
+ addr := entry.findfirst(KADDR);
+ storage := entry.findfirst(KSTORAGE);
+ fswrks_s := entry.findfirst(KFSWRKS);
+
+ fswrks := DFSWRKS;
+
+ if(len name == 0)
+ error("entry has no name");
+ if(len sysname == 0)
+ error("entry has no sysname");
+ if(len addr == 0)
+ addr = DADDR;
+ if(len fswrks_s != 0) {
+ (fswrks_i, rm) := strm->toint(fswrks_s, 10);
+ if(rm != "")
+ error(sys->sprint("malformed fs workers count: %s", fswrks_s));
+ fswrks = fswrks_i;
+ }
+
+ return NodeConfig(
+ name, sysname, addr, storage, # basic information
+ fswrks); # tunable options
+}
--- /dev/null
+++ b/appl/cmd/ctlfs.b
@@ -1,0 +1,222 @@
+include "dial.m";
+ dial: Dial;
+
+include "security.m";
+ auth: Auth;
+
+include "styx.m";
+ styx: Styx;
+ Tmsg, Rmsg: import Styx;
+
+include "styxservers.m";
+ styxservers: Styxservers;
+ Styxserver, Fid, Navigator,
+ Navop, Enotfound, Enotdir: import styxservers;
+
+# FS file index
+Qroot, Qctl, Qstats, Qmax: con iota;
+tab := array[] of {
+ (Qroot, ".", Sys->DMDIR|8r555),
+ (Qctl, "ctl", 8r222),
+ (Qstats, "stats", 8r111),
+};
+
+# create ctlfs and the appropriate listeners
+init_ctlfs(cfg: Config, keyfile: string, algs: list of string)
+{
+ dial = load Dial Dial->PATH;
+ auth = load Auth Auth->PATH;
+ styx = load Styx Styx->PATH;
+
+ if(dial == nil)
+ error("ctlfs: dial module not found");
+ if(auth == nil)
+ error("ctlfs: auth module not found");
+ if(styx == nil)
+ error("ctlfs: styx module not found");
+
+ auth->init();
+
+ styx->init();
+ styxservers->init(styx);
+ styxservers->traceset(chatty);
+
+ # authinfo init
+ if(debug)
+ sys->fprint(stderr, "ctlfs: reading authinfo");
+ authinfo: ref Keyring->Authinfo;
+ if (doauth) {
+ if (keyfile == nil)
+ keyfile = "/usr/" + user() + "/keyring/default";
+ authinfo = keyring->readauthinfo(keyfile);
+ if (authinfo == nil)
+ error(sys->sprint("ctlfs: cannot read %s: %r", keyfile));
+ }
+
+ # announcing
+ if(debug)
+ sys->fprint(stderr, "ctlfs: announcing dddbctl");
+ addr := dial->netmkaddr(cfg.addr, "tcp", "dddbctl");
+ c := dial->announce(addr);
+ if(c == nil)
+ error(sys->sprint("ctlfs: cannot listen on %s\n", addr));
+
+ # bootstrapping
+ if(debug)
+ sys->fprint
+ sys->unmount(nil, "/mnt/keys");
+
+ navch := chan of ref Navop;
+ spawn ctlfs_navigator(navch);
+
+ nav := Navigator.new(navch);
+ (tc, srv) := Styxserver.new(fildes(0), nav, big Qroot);
+
+ # listener entrypoint
+ listener(c, authinfo, algs);
+}
+
+# dddbctl listener loop
+ctlfs_listener(c: ref Dial->Connection, authinfo: ref Keyring->Authinfo, algs: list of string)
+{
+ for (;;) {
+ nc := dial->listen(c);
+ if (nc == nil)
+ error(sys->sprint("listen failed: %r"));
+ if (debug)
+ sys->fprint(stderr, "ctlfs: got connection from %s\n",
+ readfile(nc.dir + "/remote"));
+ dfd := dial->accept(nc);
+ if (dfd != nil) {
+ if(nc.cfd != nil)
+ sys->fprint(nc.cfd, "keepalive");
+ hostname: string;
+ if(passhostnames){
+ hostname = readfile(nc.dir + "/remote");
+ if(hostname != nil)
+ hostname = hostname[0:len hostname - 1];
+ }
+
+ spawn ctlfs_authenticator(dfd, authinfo, algs, hostname);
+ }
+ }
+}
+
+# authenticate a connection and set the user id.
+ctlfs_authenticator(dfd: ref Sys->FD, authinfo: ref Keyring->Authinfo,
+ algs: list of string, hostname: string)
+{
+ # authenticate and change user id appropriately
+ (fd, err) := auth->server(algs, authinfo, dfd, 1);
+ if (fd == nil) {
+ if (debug)
+ sys->fprint(stderr(), "ctlfs: authentication failed: %s\n", err);
+ return;
+ }
+ if (debug)
+ sys->fprint(stderr(), "ctlfs: client authenticated as %s\n", err);
+
+ spawn exportproc(sync, mfd, err, hostname, fd);
+}
+
+ctlfs_loop()
+{
+ # Primary server loop
+ loop:
+ while((tmsg := <-tc) != nil) {
+ # Switch on operations being performed on a given Fid
+ pick msg := tmsg {
+ Open =>
+ srv.default(msg);
+ Read =>
+ fid := srv.getfid(msg.fid);
+
+ if(fid.qtype & Sys->QTDIR) {
+ # This is a directory read
+ srv.default(msg);
+ continue loop;
+ }
+
+ case int fid.path {
+ Qlog =>
+ # A read on our log file, tell them what they've already said ?
+ s := "";
+
+ for(l := log; l != nil; l = tl l)
+ s = hd l + s;
+
+ srv.reply(styxservers->readstr(msg, s));
+
+ * =>
+ srv.default(msg);
+ }
+
+ Write =>
+ fid := srv.getfid(msg.fid);
+
+ case int fid.path {
+ Qctl =>
+ # Don't care about offset
+ cmd := string msg.data;
+
+ reply: ref Rmsg = ref Rmsg.Write(msg.tag, len msg.data);
+
+ case cmd {
+ * =>
+ # Ignore empty writes
+ if(cmd != nil)
+ log = cmd :: log;
+ else
+ reply = ref Rmsg.Error(msg.tag, "empty write!");
+ }
+ srv.reply(reply);
+
+ * =>
+ srv.default(msg);
+ }
+
+ * =>
+ srv.default(msg);
+ }
+ }
+
+ exit;
+}
+
+# Navigator function for moving around under /
+ctlfs_navigator(c: chan of ref Navop) {
+ loop:
+ for(;;) {
+ navop := <-c;
+ pick op := navop {
+ Stat =>
+ op.reply <-= (dir(int op.path), nil);
+
+ Walk =>
+ if(op.name == "..") {
+ op.reply <-= (dir(Qroot), nil);
+ continue loop;
+ }
+
+ case int op.path&16rff {
+
+ Qroot =>
+ for(i := 1; i < Qmax; i++)
+ if(tab[i].t1 == op.name) {
+ op.reply <-= (dir(i), nil);
+ continue loop;
+ }
+
+ op.reply <-= (nil, Enotfound);
+ * =>
+ op.reply <-= (nil, Enotdir);
+ }
+
+ Readdir =>
+ for(i := 0; i < op.count && i + op.offset < (len tab) - 1; i++)
+ op.reply <-= (dir(Qroot+1+i+op.offset), nil);
+
+ op.reply <-= (nil, nil);
+ }
+ }
+}
--- /dev/null
+++ b/appl/cmd/dddb.b
@@ -1,0 +1,94 @@
+implement Dddb;
+
+include "sys.m";
+ sys: Sys;
+include "arg.m";
+include "draw.m";
+include "string.m";
+ strm: String;
+
+include "config.b";
+include "ctlfs.b";
+
+stderr: ref Sys->FD;
+debug: int;
+
+error(s: string)
+{
+ sys->fprint(stderr, "dddb: %s\n", s);
+ raise "dddb:error";
+}
+
+Dddb: module {
+ init: fn(nil: ref Draw->Context, args: list of string);
+ run_fs: fn(cfg: Config);
+
+ Config: adt {
+ name: string;
+ sysn: string;
+ addr: string;
+ storage: string;
+ fswrks: int;
+ nodes: list of NodeConfig;
+
+ open: fn(nodename: string, path: string): Config;
+ };
+
+ NodeConfig: adt {
+ name: string;
+ sysn: string;
+ addr: string;
+ storage: string;
+ fswrks: int;
+
+ new: fn(entry: ref Dbentry): NodeConfig;
+ };
+
+};
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ arg := load Arg Arg->PATH;
+ strm = load String String->PATH;
+
+ stderr = sys->fildes(2);
+ cfgpath: string = "";
+
+ arg->init(args);
+ arg->setusage(arg->progname()+ " [-d] [-c config] nodename");
+ while((c := arg->opt()) != 0)
+ case c {
+ 'd' => debug++;
+ 'c' =>
+ cfgpath = arg->earg();
+ * =>
+ sys->fprint(sys->fildes(2), "bad option: -%c\n", c);
+ arg->usage();
+ }
+
+ args = arg->argv();
+
+ nodename := hd args;
+
+ if(nodename == nil) {
+ sys->fprint(stderr, "dddb: no nodename supplied\n");
+ arg->usage();
+ }
+
+ if(debug)
+ sys->fprint(stderr, "dddb: opening config file\n");
+ cfg := Config.open(nodename, cfgpath);
+
+ if(debug) {
+ sys->fprint(stderr, "dddb: database parms:\n");
+ sys->fprint(stderr, "cfg.name: %s\n", cfg.name);
+ sys->fprint(stderr, "cfg.sysn: %s\n", cfg.sysn);
+ sys->fprint(stderr, "cfg.storage: %s\n", cfg.storage);
+ sys->fprint(stderr, "cfg.fswrks: %d\n", cfg.fswrks);
+ }
+
+ run_fs(cfg);
+}
+
+
--- /dev/null
+++ b/appl/cmd/mkfile
@@ -1,0 +1,18 @@
+<../../mkconfig
+
+TARG=\
+ dddb.dis\
+
+MODULES=\
+
+SYSMODULES=\
+ arg.m\
+ dial.m\
+ draw.m\
+ sys.m\
+ styx.m\
+ styxservers.m\
+
+DISBIN=$home/dis
+
+<$ROOT/mkfiles/mkdis
--- /dev/null
+++ b/appl/lib/mkfile
@@ -1,0 +1,11 @@
+<../../mkconfig
+
+TARG=\
+
+MODULES=\
+
+SYSMODULES=\
+
+DISBIN=$ROOT/dis/lib
+
+<$ROOT/mkfiles/mkdis
--- /dev/null
+++ b/appl/mkfile
@@ -1,0 +1,6 @@
+<../mkconfig
+
+DIRS=\
+ cmd\
+
+<$ROOT/mkfiles/mksubdirs
--- /dev/null
+++ b/mkconfig
@@ -1,0 +1,6 @@
+<$ROOT/mkconfig
+
+# Comment the below lines for hosted builds
+SYSHOST=Inferno
+SYSTARG=Inferno
+ROOT=/
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,6 @@
+<mkconfig
+
+DIRS=\
+ appl\
+
+<$ROOT/mkfiles/mksubdirs