wip - main, config, ctlfs
This commit is contained in:
commit
aee91c4ed0
|
@ -0,0 +1,2 @@
|
||||||
|
*.dis
|
||||||
|
*.sbl
|
|
@ -0,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.
|
|
@ -0,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
|
|
@ -0,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
|
||||||
|
}
|
|
@ -0,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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,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
|
|
@ -0,0 +1,11 @@
|
||||||
|
<../../mkconfig
|
||||||
|
|
||||||
|
TARG=\
|
||||||
|
|
||||||
|
MODULES=\
|
||||||
|
|
||||||
|
SYSMODULES=\
|
||||||
|
|
||||||
|
DISBIN=$ROOT/dis/lib
|
||||||
|
|
||||||
|
<$ROOT/mkfiles/mkdis
|
|
@ -0,0 +1,6 @@
|
||||||
|
<../mkconfig
|
||||||
|
|
||||||
|
DIRS=\
|
||||||
|
cmd\
|
||||||
|
|
||||||
|
<$ROOT/mkfiles/mksubdirs
|
|
@ -0,0 +1,6 @@
|
||||||
|
<$ROOT/mkconfig
|
||||||
|
|
||||||
|
# Comment the below lines for hosted builds
|
||||||
|
SYSHOST=Inferno
|
||||||
|
SYSTARG=Inferno
|
||||||
|
ROOT=/
|
Loading…
Reference in New Issue