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