xref: /inferno-os/appl/cmd/auxi/mangaload.b (revision cc8204a326b4d4f4213a28c97130677a829e4091)
1implement Mangaload;
2
3# to do:
4#	- set arp entry based on /lib/ndb if necessary
5
6include "sys.m";
7	sys: Sys;
8
9include "draw.m";
10
11include "ip.m";
12	ip: IP;
13	IPaddr: import ip;
14
15include "timers.m";
16	timers: Timers;
17	Timer: import timers;
18
19include "dial.m";
20	dial: Dial;
21
22include "arg.m";
23
24Mangaload: module
25{
26	init:	fn(nil: ref Draw->Context, nil: list of string);
27};
28
29# manga parameters
30FlashBlocksize: con 16r10000;
31FlashSize: con 16r400000;	# 4meg for now
32FlashUserArea: con 16r3C0000;
33
34# magic values
35FooterOffset: con 16rFFEC;
36FooterSig: con 16rA0FFFF9F;	# ARM flash library
37FileInfosize: con 64;
38FileNamesize: con FileInfosize - 3*4;	# x, y, z
39Packetdatasize: con 1500-28;	# ether data less IP + ICMP header
40RequestTimeout: con 500;
41Probecount: con 10;	# query unit every so many packets
42
43# manga uses extended TFTP ops in ICMP InfoRequest packets
44Tftp_Req: con 0;
45Tftp_Read: con 1;
46Tftp_Write: con 2;
47Tftp_Data: con 3;
48Tftp_Ack: con 4;
49Tftp_Error: con 5;
50Tftp_Last: con 6;
51
52Icmp: adt
53{
54	ttl:	int;	# time to live
55	src:	IPaddr;
56	dst:	IPaddr;
57	ptype:	int;
58	code:	int;
59	id:	int;
60	seq:	int;
61	data:	array of byte;
62	munged:	int;	# packet received but corrupt
63
64	unpack:	fn(b: array of byte): ref Icmp;
65};
66
67# ICMP packet types
68EchoReply: con 0;
69Unreachable: con 3;
70SrcQuench: con 4;
71EchoRequest: con 8;
72TimeExceed: con 11;
73Timestamp: con 13;
74TimestampReply: con 14;
75InfoRequest: con 15;
76InfoReply: con 16;
77
78Nmsg: con 32;
79Interval: con 1000;	# ms
80
81debug := 0;
82flashblock := 1;	# never 0, that's the boot firmware
83maxfilesize := 8*FlashBlocksize;
84flashlim := FlashSize/FlashBlocksize;
85loadinitrd := 0;
86maxlen := 512*1024;
87mypid := 0;
88Datablocksize: con 4096;
89
90init(nil: ref Draw->Context, args: list of string)
91{
92	sys = load Sys Sys->PATH;
93	timers = load Timers Timers->PATH;
94	dial = load Dial Dial->PATH;
95	ip = load IP IP->PATH;
96	ip->init();
97
98
99	arg := load Arg Arg->PATH;
100	arg->init(args);
101	arg->setusage("mangaload [-48dr] destination file");
102	while((o := arg->opt()) != 0)
103		case o {
104		'4' =>
105			flashlim = 4*1024*1024/FlashBlocksize;
106		'8' =>
107			flashlim = 8*1024*1024/FlashBlocksize;
108		'r' =>
109			loadinitrd = 1;
110			flashblock = 9;
111			if(flashlim > 4*1024*1024/FlashBlocksize)
112				maxfilesize = 113*FlashBlocksize;
113			else
114				maxfilesize = 50*FlashBlocksize;
115		'd' =>
116			debug++;
117		}
118	args = arg->argv();
119	if(len args != 2)
120		arg->usage();
121	arg = nil;
122
123	sys->pctl(Sys->NEWPGRP|Sys->FORKFD, nil);
124
125	filename := hd tl args;
126	fd := sys->open(filename, Sys->OREAD);
127	if(fd == nil){
128		sys->fprint(sys->fildes(2), "mangaload: can't open %s: %r\n", filename);
129		raise "fail:open";
130	}
131	(ok, d) := sys->fstat(fd);
132	if(ok < 0){
133		sys->fprint(sys->fildes(2), "mangaload: can't stat %s: %r\n", filename);
134		raise "fail:stat";
135	}
136	if(d.length > big maxfilesize){
137		sys->fprint(sys->fildes(2), "mangaload: file %s too long (must not exceed %d bytes)\n",
138			filename, maxfilesize);
139		raise "fail:size";
140	}
141	filesize := int d.length;
142
143	port := sys->sprint("%d", 16r8695);
144	addr := dial->netmkaddr(hd args, "icmp", port);
145	c := dial->dial(addr, port);
146	if(c == nil){
147		sys->fprint(sys->fildes(2), "mangaload: can't dial %s: %r\n", addr);
148		raise "fail:dial";
149	}
150
151	tpid := timers->init(20);
152
153	pids := chan of int;
154	replies := chan [2] of ref Icmp;
155	spawn reader(c.dfd, replies, pids);
156	rpid := <-pids;
157
158	flashoffset := flashblock * FlashBlocksize;
159
160	# file name first
161	bname := array of byte filename;
162	l := len bname;
163	buf := array[Packetdatasize] of byte;
164	ip->put4(buf, 0, filesize);
165	ip->put4(buf, 4, l);
166	buf[8:] = bname;
167	l += 2*4;
168	buf[l++] = byte 0;
169	ip->put4(buf, l, flashoffset);
170	l += 4;
171	{
172		if(send(c.dfd, buf[0:l], Tftp_Write, 0) < 0)
173			senderr();
174		(op, iseq, data) := recv(replies, 400);
175		sys->print("initial reply: %d %d\n", op, iseq);
176		if(op != Tftp_Ack){
177			why := "no response";
178			if(op == Tftp_Error)
179				why = "manga cannot receive file";
180			sys->fprint(sys->fildes(2), "mangaload: %s\n", why);
181			raise "fail:error";
182		}
183		sys->print("sending %s size %d at address %d (0x%x)\n", filename, filesize, flashoffset, flashoffset);
184		seq := 1;
185		nsent := 0;
186		last := 0;
187		while((n := sys->read(fd, buf, len buf)) >= 0 && !last){
188			last = n != len buf;
189		  Retry:
190			for(;;){
191				if(++nsent%10 == 0){	# probe
192					o = Tftp_Req;
193					send(c.dfd, array[0] of byte, Tftp_Req, seq);
194					(op, iseq, data) = recv(replies, 500);
195					if(debug || op != Tftp_Ack)
196						sys->print("ack reply: %d %d\n", op, iseq);
197					if(op == Tftp_Last || op == Tftp_Error){
198						if(op == Tftp_Last)
199							sys->print("timed out\n");
200						else
201							sys->print("error reply\n");
202						raise "disaster";
203					}
204					if(debug)
205						sys->print("ok\n");
206					continue Retry;
207				}
208				send(c.dfd, buf[0:n], Tftp_Data, seq);
209				(op, iseq, data) = recv(replies, 40);
210				case op {
211				Tftp_Error =>
212					sys->fprint(sys->fildes(2), "mangaload: manga refused data\n");
213					raise "disaster";
214				Tftp_Ack =>
215					if(seq == iseq){
216						seq++;
217						break Retry;
218					}
219					sys->print("sequence error: rcvd %d expected %d\n", iseq, seq);
220					if(iseq > seq){
221						sys->print("unrecoverable sequence error\n");
222						send(c.dfd, array[0] of byte, Tftp_Data, ++seq);	# stop manga
223						raise "disaster";
224					}
225					# resend
226					sys->seek(fd, -big ((seq-iseq)*len buf), 1);
227					seq = iseq;
228				Tftp_Last =>
229					seq++;
230					break Retry;	# timeout ok: manga doesn't usually reply unless packet lost
231				}
232			}
233		}
234	}exception{
235	* =>
236		;
237	}
238	kill(rpid);
239	kill(tpid);
240	sys->print("ok?\n");
241}
242
243kill(pid: int)
244{
245	if(pid)
246		sys->fprint(sys->open("#p/"+string pid+"/ctl", Sys->OWRITE), "kill");
247}
248
249senderr()
250{
251	sys->fprint(sys->fildes(2), "mangaload: icmp write failed: %r\n");
252	raise "disaster";
253}
254
255send(fd: ref Sys->FD, data: array of byte, op: int, seq: int): int
256{
257	buf := array[64*1024+512] of {* => byte 0};
258	buf[Odata:] = data;
259	ip->put2(buf, Oseq, seq);
260	buf[Otype] = byte InfoRequest;
261	buf[Ocode] = byte op;
262	if(sys->write(fd, buf, Odata+len data) < Odata+len data)
263		return -1;
264	if(debug)
265		sys->print("sent op=%d seq=%d ld=%d\n", op, seq, len data);
266	return 0;
267}
268
269flush(input: chan of ref Icmp)
270{
271	for(;;)alt{
272	<-input =>
273		;
274	* =>
275		return;
276	}
277}
278
279recv(input: chan of ref Icmp, msec: int): (int, int, array of byte)
280{
281	t := Timer.start(msec);
282	alt{
283	<-t.timeout =>
284		return (Tftp_Last, 0, nil);
285	ic := <-input =>
286		t.stop();
287		if(ic.ptype == InfoReply)
288			return (ic.code, ic.seq, ic.data);
289		return (Tftp_Last, 0, nil);
290	}
291}
292
293reader(fd: ref Sys->FD, out: chan of ref Icmp, pid: chan of int)
294{
295	pid <-= sys->pctl(0, nil);
296	for(;;){
297		buf := array[64*1024+512] of byte;
298		n := sys->read(fd, buf, len buf);
299		if(n <= 0){
300			if(n == 0)
301				sys->werrstr("unexpected eof");
302			break;
303		}
304		ic := Icmp.unpack(buf[0:n]);
305		if(ic != nil){
306			if(debug)
307				sys->print("recv type=%d op=%d seq=%d id=%d\n", ic.ptype, ic.code, ic.seq, ic.id);
308			out <-= ic;
309		}else
310			sys->fprint(sys->fildes(2), "mangaload: corrupt icmp packet rcvd\n");
311	}
312	sys->print("read: %r\n");
313	out <-= nil;
314}
315
316# IP and ICMP packet header
317Ovihl: con 0;
318Otos: con 1;
319Olength: con 2;
320Oid: con Olength+2;
321Ofrag: con Oid+2;
322Ottl: con Ofrag+2;
323Oproto: con Ottl+1;
324Oipcksum: con Oproto+1;
325Osrc: con Oipcksum+2;
326Odst: con Osrc+4;
327Otype: con Odst+4;
328Ocode: con Otype+1;
329Ocksum: con Ocode+1;
330Oicmpid: con Ocksum+2;
331Oseq: con Oicmpid+2;
332Odata: con Oseq+2;
333
334Icmp.unpack(b: array of byte): ref Icmp
335{
336	if(len b < Odata)
337		return nil;
338	ic := ref Icmp;
339	ic.ttl = int b[Ottl];
340	ic.src = IPaddr.newv4(b[Osrc:]);
341	ic.dst = IPaddr.newv4(b[Odst:]);
342	ic.ptype = int b[Otype];
343	ic.code = int b[Ocode];
344	ic.seq = ip->get2(b, Oseq);
345	ic.id = ip->get2(b, Oicmpid);
346	ic.munged = 0;
347	if(len b > Odata)
348		ic.data = b[Odata:];
349	return ic;
350}
351