ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /os/init/bootinit.b/
#
# Generalized boot Inferno
#
implement Init;
include "sys.m";
sys: Sys;
include "draw.m";
include "keyring.m";
kr: Keyring;
include "security.m";
auth: Auth;
random: Random;
include "tftp.m";
Bootpreadlen: con 128;
Init: module
{
init: fn();
};
ip: string;
mask: string;
fsip: string;
bootprotocol: string;
bootserver: string;
bootfile: string;
debug: con 0;
init()
{
ipavailable: int;
sys = load Sys Sys->PATH;
kexecfd := sys->open("#B/kexec", Sys->OWRITE);
if (kexecfd == nil)
fatal(sys->sprint("opening #B/kexec: %r"));
ipavailable = 0;
if (dobind("#l", "/net", sys->MREPL) && dobind("#I", "/net", sys->MAFTER))
ipavailable = 1;
dobind("#c", "/dev", sys->MAFTER); # console device
if (!ipavailable)
fatal("no IP stack available");
cfd := sys->open("/net/ipifc/clone", sys->ORDWR);
if(cfd == nil)
fatal(sys->sprint("open /net/ipifc/clone: %r"));
if (sys->fprint(cfd, "bind ether ether0") < 0)
fatal(sys->sprint("binding ether0: %r"));
fsready := 0;
fsip = ipconfig(cfd);
bootstring := getenvdefault("bootpath", "tftp");
(bootprotocol, bootserver, bootfile) = parsebootstring(bootstring);
if (bootprotocol == nil)
fatal(bootstring + ": unrecognised syntax");
# Run dhcp if necessary
if (bootprotocol == "tftp" && (bootserver == nil || bootfile == nil))
dhcp();
# determine server
if (bootprotocol == "net" && bootserver == nil)
bootserver = fsip;
if (bootserver == nil)
fatal("couldn't determine boot server");
if (bootfile == nil)
fatal("couldn't determine boot file");
if (bootprotocol == nil)
fatal("couldn't determine boot protocol");
sys->print("loading %s!%s!%s\n", bootprotocol, bootserver, bootfile);
if (bootprotocol == "net") {
sys->print("Attempting remote mount\n");
if (netfs(bootserver) == 0)
sys->print("Remote mount successful\n");
else
fatal(sys->sprint("Remote mount failed: %r"));
fd := sys->open("/n/remote" + bootfile, Sys->OREAD);
if (fd == nil)
fatal(sys->sprint("%s:/n/remote%s: %r", bootserver, bootfile));
if (sys->stream(fd, kexecfd, 4096) < 0)
fatal(sys->sprint("copying %s: %r", bootfile));
}
else if (bootprotocol == "tftp") {
tftp := load Tftp Tftp->PATH;
if (tftp == nil)
fatal("can't load tftp module");
tftp->init(1);
errstr := tftp->receive(bootserver, bootfile, kexecfd);
if (errstr != nil)
fatal("tftp: " + errstr);
}
else
fatal("protocol " + bootprotocol + " not supported");
sys->print("Launching new kernel\n");
kexecfd = nil;
}
parsebootstring(s: string): (string, string, string)
{
proto, server, file: string;
(n, l) := sys->tokenize(s, "!");
if (n > 3)
return (nil, nil, nil);
proto = hd l;
l = tl l;
if (l != nil) {
server = hd l;
l = tl l;
}
if (l != nil)
file = hd l;
case proto {
"tftp" =>
;
"net" =>
# can't have a default file, so n must be 3
if (n != 3)
return (nil, nil, nil);
* =>
return (nil, nil, nil);
}
return (proto, server, file);
}
dobind(f, t: string, flags: int): int
{
if(sys->bind(f, t, flags) < 0) {
err(sys->sprint("can't bind %s on %s: %r", f, t));
return 0;
}
return 1;
}
err(s: string)
{
sys->fprint(sys->fildes(2), "bootinit: %s\n", s);
}
hang()
{
<-(chan of int);
}
fatal(s: string)
{
err(s);
hang();
}
envlist: list of string;
getenv(name: string): string
{
if (envlist == nil) {
fd := sys->open("/dev/sysenv", Sys->OREAD);
if (fd != nil) {
ntok: int;
buf := array[1024] of byte;
nr := sys->read(fd, buf, len buf);
if(nr > 0)
(ntok, envlist) = sys->tokenize(string buf, "\n");
}
}
ls := envlist;
while(ls != nil) {
(ntok2, ls2) := sys->tokenize(hd ls, "=");
if(hd ls2 == name)
return hd tl ls2;
ls = tl ls;
}
return nil;
}
getenvdefault(name: string, default: string): string
{
rv := getenv(name);
if (rv == nil)
return default;
return rv;
}
ipconfig(cfd: ref sys->FD): string
{
ip = getenv("wireip");
if (ip == nil)
ip = getenv("ip");
mask = getenv("ipmask");
fsip = getenv("fsip");
if (ip != nil && mask != nil) {
sys->print("ip %s %s\n", ip, mask);
sys->fprint(cfd, "add %s %s", ip, mask);
gwip := getenv("gwip");
if (gwip != nil) {
sys->print("gwip %s\n", gwip);
rfd := sys->open("/net/iproute", Sys->ORDWR);
if (rfd == nil || sys->fprint(rfd, "add 0.0.0.0 0.0.0.0 %s", gwip) < 0)
err(sys->sprint("failed to add default route: %r"));
}
}
if (ip == nil || mask == nil)
return bootp(cfd);
return fsip;
}
bootpdone: int;
bootp(cfd: ref sys->FD): string
{
if (bootpdone == 1)
return fsip;
bootpdone = 1;
sys->print("bootp ...");
if (sys->fprint(cfd, "bootp") < 0) {
sys->print("init: bootp: %r");
return nil;
}
fd := sys->open("/net/bootp", sys->OREAD);
if(fd == nil) {
err(sys->sprint("open /net/bootp: %r"));
return nil;
}
buf := array[Bootpreadlen] of byte;
nr := sys->read(fd, buf, len buf);
fd = nil;
if(nr <= 0) {
err(sys->sprint("read /net/bootp: %r"));
return nil;
}
(ntok, ls) := sys->tokenize(string buf, " \t\n");
while(ls != nil) {
name := hd ls;
ls = tl ls;
if (ls == nil)
break;
value := hd ls;
ls = tl ls;
if (name == "fsip")
fsip = value;
else if (name == "ipaddr")
ip = value;
else if (name == "ipmask")
mask = value;
}
return fsip;
}
netfs(server: string): int
{
auth = load Auth Auth->PATH;
if (auth != nil)
auth->init();
kr = load Keyring Keyring->PATH;
sys->print("dial...");
(ok, c) := sys->dial("tcp!" + server + "!6666", nil);
if(ok < 0)
return -1;
if(kr != nil && auth != nil){
err: string;
sys->print("Authenticate ...");
ai := kr->readauthinfo("/nvfs/default");
if(ai == nil){
sys->print("readauthinfo /nvfs/default failed: %r\n");
sys->print("trying mount as `nobody'\n");
}
(c.dfd, err) = auth->client("none", ai, c.dfd);
if(c.dfd == nil){
sys->print("authentication failed: %s\n", err);
return -1;
}
}
sys->print("mount ...");
c.cfd = nil;
n := sys->mount(c.dfd, nil, "/n/remote", sys->MREPL, "");
if(n > 0)
return 0;
return -1;
}
#
#
# DHCP
#
#
Dhcp: adt {
op: int;
htype: int;
hops: int;
xid: int;
secs: int;
flags: int;
ciaddr: int;
yiaddr: int;
siaddr: int;
giaddr: int;
chaddr: array of byte;
sname: string;
file: string;
};
nboputl(buf: array of byte, val: int)
{
buf[0] = byte (val >> 24);
buf[1] = byte (val >> 16);
buf[2] = byte (val >> 8);
buf[3] = byte val;
}
nboputs(buf: array of byte, val: int)
{
buf[0] = byte (val >> 8);
buf[1] = byte val;
}
nbogets(buf: array of byte): int
{
return (int buf[0] << 8) | int buf[1];
}
nbogetl(buf: array of byte): int
{
return (int buf[0] << 24) | (int buf[1] << 16) | (int buf[2] << 8) | int buf[3];
}
stringget(buf: array of byte): string
{
for (x := 0; x < len buf; x++)
if (buf[x] == byte 0)
break;
if (x == 0)
return nil;
return string buf[0 : x];
}
memcmp(b1: array of byte, b2: array of byte): int
{
l := len b1;
if (l < len b2)
return int -b2[l];
if (l > len b2)
return int b1[l];
for (i := 0; i < l; i++) {
d := int b1[i] - int b2[i];
if (d != 0)
return d;
}
return 0;
}
memncpy(out: array of byte, in: array of byte)
{
if (in == nil)
return;
l := len in;
if (l > len out)
l = len out;
out[0 :] = in[0 : l];
}
memset(out: array of byte, val: byte)
{
for (l := 0; l < len out; l++)
out[l] = val;
}
dhcpsend(dfd: ref Sys->FD, dhcp: ref Dhcp)
{
buf := array[576] of byte;
buf[0] = byte dhcp.op;
buf[1] = byte dhcp.htype;
buf[2] = byte len dhcp.chaddr;
buf[3] = byte dhcp.hops;
nboputl(buf[4 : 8], dhcp.xid);
nboputs(buf[8 : 10], dhcp.secs);
nboputs(buf[10 : 12], dhcp.flags);
nboputl(buf[12 : 16], dhcp.ciaddr);
nboputl(buf[16 : 20], dhcp.yiaddr);
nboputl(buf[20 : 24], dhcp.siaddr);
nboputl(buf[24 : 28], dhcp.giaddr);
memset(buf[28 :], byte 0);
memncpy(buf[28 : 44], dhcp.chaddr);
memncpy(buf[44 : 108], array of byte dhcp.sname);
memncpy(buf[108 : 236], array of byte dhcp.file);
sys->write(dfd, buf, len buf);
}
kill(pid: int)
{
fd := sys->open("#p/" + string pid + "/ctl", sys->OWRITE);
if (fd == nil)
return;
msg := array of byte "kill";
sys->write(fd, msg, len msg);
}
ipfmt(ipaddr: int): string
{
return sys->sprint("%ud.%ud.%ud.%ud",
(ipaddr >> 24) & 16rff,
(ipaddr >> 16) & 16rff,
(ipaddr >> 8) & 16rff,
ipaddr & 16rff);
}
dumpdhcp(dhcp: ref Dhcp)
{
sys->print("op %d htype %d hops %d xid %ud\n", dhcp.op, dhcp.htype, dhcp.hops, dhcp.xid);
sys->print("secs %d flags 0x%.4ux\n", dhcp.secs, dhcp.flags);
sys->print("ciaddr %s\n", ipfmt(dhcp.ciaddr));
sys->print("yiaddr %s\n", ipfmt(dhcp.yiaddr));
sys->print("siaddr %s\n", ipfmt(dhcp.siaddr));
sys->print("giaddr %s\n", ipfmt(dhcp.giaddr));
sys->print("chaddr ");
for (x := 0; x < len dhcp.chaddr; x++)
sys->print("%.2ux", int dhcp.chaddr[x]);
sys->print("\n");
if (dhcp.sname != nil)
sys->print("sname %s\n", dhcp.sname);
if (dhcp.file != nil)
sys->print("file %s\n", dhcp.file);
}
dhcplisten(pidc: chan of int, fd: ref Sys->FD, dc: chan of ref Dhcp)
{
pid := sys->pctl(0, nil);
pidc <-= pid;
buf := array [576] of byte;
while (1) {
n := sys->read(fd, buf, len buf);
dhcp := ref Dhcp;
dhcp.op = int buf[0];
dhcp.htype = int buf[1];
hlen := int buf[2];
dhcp.hops = int buf[3];
dhcp.xid = nbogetl(buf[4 : 8]);
dhcp.secs = nbogets(buf[8 : 10]);
dhcp.flags = nbogets(buf[10 : 12]);
dhcp.ciaddr = nbogetl(buf[12 : 16]);
dhcp.yiaddr = nbogetl(buf[16 : 20]);
dhcp.siaddr = nbogetl(buf[20 : 24]);
dhcp.giaddr = nbogetl(buf[24: 28]);
dhcp.chaddr = buf[28 : 28 + hlen];
dhcp.sname = stringget(buf[44 : 108]);
dhcp.file = stringget(buf[108 : 236]);
dc <-= dhcp;
}
}
timeoutproc(pid: chan of int, howlong: int, c: chan of string)
{
pid <-= sys->pctl(0, nil);
sys->sleep(howlong);
# send timeout
c <-= "timed out";
}
tpid := -1;
tc: chan of string;
timeoutcancel()
{
if (tpid >= 0) {
kill(tpid);
tpid = -1;
}
}
timeoutstart(howlong: int): (chan of string)
{
timeoutcancel();
pidc := chan of int;
tc = chan of string;
spawn timeoutproc(pidc, howlong, tc);
tpid = <- pidc;
return tc;
}
atohn(b: byte): int
{
if (b >= byte '0' && b <= byte '9')
return int (b - byte '0');
if (b >= byte 'A' && b <= byte 'F')
return int b - 'A' + 10;
if (b >= byte 'a' && b <= byte 'f')
return int b - 'a' + 10;
return -1;
}
atohb(buf: array of byte): int
{
tn := atohn(buf[0]);
bn := atohn(buf[1]);
if (tn < 0 || bn < 0)
return -1;
return tn * 16 + bn;
}
gethaddr(dhcp: ref Dhcp): int
{
fd := sys->open("#l/ether0/addr", Sys->OREAD);
if (fd == nil)
return 0;
buf := array [100] of byte;
n := sys->read(fd, buf, len buf);
if (n < 0)
return 0;
dhcp.htype = 1;
hlen := n / 2;
dhcp.chaddr = array [hlen] of byte;
for (i := 0; i < hlen; i++)
dhcp.chaddr[i] = byte atohb(buf[i * 2 : i * 2 + 2]);
return 1;
}
parsedq(dq: string): (int, int)
{
(c, l) := sys->tokenize(dq, ".");
if (c != 4)
return (0, 0);
a := hd l;
l = tl l;
b := hd l;
l = tl l;
d := hd l;
l = tl l;
addr := (int a << 24) | (int b << 16) | (int d << 8) | int hd l;
return (1, addr);
}
dhcp()
{
ok: int;
conn: Sys->Connection;
rdhcp: ref Dhcp;
if (random == nil)
random = load Random Random->PATH;
(ok, conn) = sys->dial("udp!255.255.255.255!67", "68");
if (!ok)
fatal(sys->sprint("failed to dial udp broadcast: %r"));
pidc := chan of int;
dc := chan of ref Dhcp;
spawn dhcplisten(pidc, conn.dfd, dc);
dhcppid := <- pidc;
dhcp := ref Dhcp;
dhcp.op = 1;
dhcp.htype = 1;
gethaddr(dhcp);
dhcp.hops = 0;
dhcp.xid = random->randomint(Random->NotQuiteRandom);
dhcp.secs = 0;
dhcp.flags = 0;
(ok, dhcp.ciaddr) = parsedq(ip);
dhcp.yiaddr = 0;
dhcp.siaddr = 0;
dhcp.giaddr = 0;
if (bootfile != "bootp")
dhcp.file = bootfile;
else
dhcp.file = nil;
ok = 0;
for (count := 0; !ok && count < 5; count++) {
mtc := timeoutstart(3000);
dhcpsend(conn.dfd, dhcp);
timedout := 0;
do {
alt {
<- mtc =>
timedout = 1;
rdhcp = <- dc =>
if (debug)
dumpdhcp(rdhcp);
if (rdhcp.ciaddr != dhcp.ciaddr || rdhcp.xid != dhcp.xid
|| memcmp(rdhcp.chaddr, dhcp.chaddr) != 0) {
break;
}
if (rdhcp.file != nil) {
ok = 1;
timeoutcancel();
}
}
} while (!timedout && !ok);
dhcp.xid++;
}
if (ok) {
if (bootfile == nil)
bootfile = rdhcp.file;
if (bootserver == nil)
bootserver = ipfmt(rdhcp.siaddr);
}
else
err("bootp timed out");
kill(dhcppid);
}