ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/cmd/auxi/mangaload.b/
implement Mangaload;
# to do:
# - set arp entry based on /lib/ndb if necessary
include "sys.m";
sys: Sys;
include "draw.m";
include "ip.m";
ip: IP;
IPaddr: import ip;
include "timers.m";
timers: Timers;
Timer: import timers;
include "dial.m";
dial: Dial;
include "arg.m";
Mangaload: module
{
init: fn(nil: ref Draw->Context, nil: list of string);
};
# manga parameters
FlashBlocksize: con 16r10000;
FlashSize: con 16r400000; # 4meg for now
FlashUserArea: con 16r3C0000;
# magic values
FooterOffset: con 16rFFEC;
FooterSig: con 16rA0FFFF9F; # ARM flash library
FileInfosize: con 64;
FileNamesize: con FileInfosize - 3*4; # x, y, z
Packetdatasize: con 1500-28; # ether data less IP + ICMP header
RequestTimeout: con 500;
Probecount: con 10; # query unit every so many packets
# manga uses extended TFTP ops in ICMP InfoRequest packets
Tftp_Req: con 0;
Tftp_Read: con 1;
Tftp_Write: con 2;
Tftp_Data: con 3;
Tftp_Ack: con 4;
Tftp_Error: con 5;
Tftp_Last: con 6;
Icmp: adt
{
ttl: int; # time to live
src: IPaddr;
dst: IPaddr;
ptype: int;
code: int;
id: int;
seq: int;
data: array of byte;
munged: int; # packet received but corrupt
unpack: fn(b: array of byte): ref Icmp;
};
# ICMP packet types
EchoReply: con 0;
Unreachable: con 3;
SrcQuench: con 4;
EchoRequest: con 8;
TimeExceed: con 11;
Timestamp: con 13;
TimestampReply: con 14;
InfoRequest: con 15;
InfoReply: con 16;
Nmsg: con 32;
Interval: con 1000; # ms
debug := 0;
flashblock := 1; # never 0, that's the boot firmware
maxfilesize := 8*FlashBlocksize;
flashlim := FlashSize/FlashBlocksize;
loadinitrd := 0;
maxlen := 512*1024;
mypid := 0;
Datablocksize: con 4096;
init(nil: ref Draw->Context, args: list of string)
{
sys = load Sys Sys->PATH;
timers = load Timers Timers->PATH;
dial = load Dial Dial->PATH;
ip = load IP IP->PATH;
ip->init();
arg := load Arg Arg->PATH;
arg->init(args);
arg->setusage("mangaload [-48dr] destination file");
while((o := arg->opt()) != 0)
case o {
'4' =>
flashlim = 4*1024*1024/FlashBlocksize;
'8' =>
flashlim = 8*1024*1024/FlashBlocksize;
'r' =>
loadinitrd = 1;
flashblock = 9;
if(flashlim > 4*1024*1024/FlashBlocksize)
maxfilesize = 113*FlashBlocksize;
else
maxfilesize = 50*FlashBlocksize;
'd' =>
debug++;
}
args = arg->argv();
if(len args != 2)
arg->usage();
arg = nil;
sys->pctl(Sys->NEWPGRP|Sys->FORKFD, nil);
filename := hd tl args;
fd := sys->open(filename, Sys->OREAD);
if(fd == nil){
sys->fprint(sys->fildes(2), "mangaload: can't open %s: %r\n", filename);
raise "fail:open";
}
(ok, d) := sys->fstat(fd);
if(ok < 0){
sys->fprint(sys->fildes(2), "mangaload: can't stat %s: %r\n", filename);
raise "fail:stat";
}
if(d.length > big maxfilesize){
sys->fprint(sys->fildes(2), "mangaload: file %s too long (must not exceed %d bytes)\n",
filename, maxfilesize);
raise "fail:size";
}
filesize := int d.length;
port := sys->sprint("%d", 16r8695);
addr := dial->netmkaddr(hd args, "icmp", port);
c := dial->dial(addr, port);
if(c == nil){
sys->fprint(sys->fildes(2), "mangaload: can't dial %s: %r\n", addr);
raise "fail:dial";
}
tpid := timers->init(20);
pids := chan of int;
replies := chan [2] of ref Icmp;
spawn reader(c.dfd, replies, pids);
rpid := <-pids;
flashoffset := flashblock * FlashBlocksize;
# file name first
bname := array of byte filename;
l := len bname;
buf := array[Packetdatasize] of byte;
ip->put4(buf, 0, filesize);
ip->put4(buf, 4, l);
buf[8:] = bname;
l += 2*4;
buf[l++] = byte 0;
ip->put4(buf, l, flashoffset);
l += 4;
{
if(send(c.dfd, buf[0:l], Tftp_Write, 0) < 0)
senderr();
(op, iseq, data) := recv(replies, 400);
sys->print("initial reply: %d %d\n", op, iseq);
if(op != Tftp_Ack){
why := "no response";
if(op == Tftp_Error)
why = "manga cannot receive file";
sys->fprint(sys->fildes(2), "mangaload: %s\n", why);
raise "fail:error";
}
sys->print("sending %s size %d at address %d (0x%x)\n", filename, filesize, flashoffset, flashoffset);
seq := 1;
nsent := 0;
last := 0;
while((n := sys->read(fd, buf, len buf)) >= 0 && !last){
last = n != len buf;
Retry:
for(;;){
if(++nsent%10 == 0){ # probe
o = Tftp_Req;
send(c.dfd, array[0] of byte, Tftp_Req, seq);
(op, iseq, data) = recv(replies, 500);
if(debug || op != Tftp_Ack)
sys->print("ack reply: %d %d\n", op, iseq);
if(op == Tftp_Last || op == Tftp_Error){
if(op == Tftp_Last)
sys->print("timed out\n");
else
sys->print("error reply\n");
raise "disaster";
}
if(debug)
sys->print("ok\n");
continue Retry;
}
send(c.dfd, buf[0:n], Tftp_Data, seq);
(op, iseq, data) = recv(replies, 40);
case op {
Tftp_Error =>
sys->fprint(sys->fildes(2), "mangaload: manga refused data\n");
raise "disaster";
Tftp_Ack =>
if(seq == iseq){
seq++;
break Retry;
}
sys->print("sequence error: rcvd %d expected %d\n", iseq, seq);
if(iseq > seq){
sys->print("unrecoverable sequence error\n");
send(c.dfd, array[0] of byte, Tftp_Data, ++seq); # stop manga
raise "disaster";
}
# resend
sys->seek(fd, -big ((seq-iseq)*len buf), 1);
seq = iseq;
Tftp_Last =>
seq++;
break Retry; # timeout ok: manga doesn't usually reply unless packet lost
}
}
}
}exception{
* =>
;
}
kill(rpid);
kill(tpid);
sys->print("ok?\n");
}
kill(pid: int)
{
if(pid)
sys->fprint(sys->open("#p/"+string pid+"/ctl", Sys->OWRITE), "kill");
}
senderr()
{
sys->fprint(sys->fildes(2), "mangaload: icmp write failed: %r\n");
raise "disaster";
}
send(fd: ref Sys->FD, data: array of byte, op: int, seq: int): int
{
buf := array[64*1024+512] of {* => byte 0};
buf[Odata:] = data;
ip->put2(buf, Oseq, seq);
buf[Otype] = byte InfoRequest;
buf[Ocode] = byte op;
if(sys->write(fd, buf, Odata+len data) < Odata+len data)
return -1;
if(debug)
sys->print("sent op=%d seq=%d ld=%d\n", op, seq, len data);
return 0;
}
flush(input: chan of ref Icmp)
{
for(;;)alt{
<-input =>
;
* =>
return;
}
}
recv(input: chan of ref Icmp, msec: int): (int, int, array of byte)
{
t := Timer.start(msec);
alt{
<-t.timeout =>
return (Tftp_Last, 0, nil);
ic := <-input =>
t.stop();
if(ic.ptype == InfoReply)
return (ic.code, ic.seq, ic.data);
return (Tftp_Last, 0, nil);
}
}
reader(fd: ref Sys->FD, out: chan of ref Icmp, pid: chan of int)
{
pid <-= sys->pctl(0, nil);
for(;;){
buf := array[64*1024+512] of byte;
n := sys->read(fd, buf, len buf);
if(n <= 0){
if(n == 0)
sys->werrstr("unexpected eof");
break;
}
ic := Icmp.unpack(buf[0:n]);
if(ic != nil){
if(debug)
sys->print("recv type=%d op=%d seq=%d id=%d\n", ic.ptype, ic.code, ic.seq, ic.id);
out <-= ic;
}else
sys->fprint(sys->fildes(2), "mangaload: corrupt icmp packet rcvd\n");
}
sys->print("read: %r\n");
out <-= nil;
}
# IP and ICMP packet header
Ovihl: con 0;
Otos: con 1;
Olength: con 2;
Oid: con Olength+2;
Ofrag: con Oid+2;
Ottl: con Ofrag+2;
Oproto: con Ottl+1;
Oipcksum: con Oproto+1;
Osrc: con Oipcksum+2;
Odst: con Osrc+4;
Otype: con Odst+4;
Ocode: con Otype+1;
Ocksum: con Ocode+1;
Oicmpid: con Ocksum+2;
Oseq: con Oicmpid+2;
Odata: con Oseq+2;
Icmp.unpack(b: array of byte): ref Icmp
{
if(len b < Odata)
return nil;
ic := ref Icmp;
ic.ttl = int b[Ottl];
ic.src = IPaddr.newv4(b[Osrc:]);
ic.dst = IPaddr.newv4(b[Odst:]);
ic.ptype = int b[Otype];
ic.code = int b[Ocode];
ic.seq = ip->get2(b, Oseq);
ic.id = ip->get2(b, Oicmpid);
ic.munged = 0;
if(len b > Odata)
ic.data = b[Odata:];
return ic;
}