xref: /inferno-os/appl/lib/tftp.b (revision e45fa0eb0763b57d6fb0649c064bc3b95ccdea6c)
1implement Tftp;
2
3include "sys.m";
4	sys: Sys;
5
6include "dial.m";
7	dial: Dial;
8
9include "tftp.m";
10
11Maxretry: con 5;	# retries per block
12Maxblock: con 512;	# protocol's usual maximum data block size
13Tftphdrlen: con 4;
14Read, Write, Data, Ack, Error: con 1+iota;	# tftp opcode
15
16progress: int;
17
18put2(buf: array of byte, o: int, val: int)
19{
20	buf[o] = byte (val >> 8);
21	buf[o+1] = byte val;
22}
23
24get2(buf: array of byte, o: int): int
25{
26	return (int buf[o] << 8) | int buf[o+1];
27}
28
29kill(pid: int)
30{
31	fd := sys->open("#p/" + string pid + "/ctl", sys->OWRITE);
32	if(fd == nil)
33		return;
34
35	msg := array of byte "kill";
36	sys->write(fd, msg, len msg);
37}
38
39timeoutproc(c: chan of int, howlong: int)
40{
41	c <-= sys->pctl(0, nil);
42	sys->sleep(howlong);
43	c <-= 1;
44}
45
46tpid := -1;
47
48timeoutcancel()
49{
50	if(tpid >= 0) {
51		kill(tpid);
52		tpid = -1;
53	}
54}
55
56timeoutstart(howlong: int): chan of int
57{
58	timeoutcancel();
59	tc := chan of int;
60	spawn timeoutproc(tc, howlong);
61	tpid = <-tc;
62	return tc;
63}
64
65init(p: int)
66{
67	sys = load Sys Sys->PATH;
68	dial = load Dial Dial->PATH;
69	progress = p;
70}
71
72reader(pidc: chan of int, fd: ref Sys->FD, bc: chan of array of byte)
73{
74	pid := sys->pctl(0, nil);
75	pidc <-= pid;
76	buf := array[Tftphdrlen + Maxblock] of byte;
77	for(;;){
78		n := sys->read(fd, buf, len buf);
79		bc <-= buf[0 : n];
80	}
81}
82
83receive(host: string, filename: string, fd: ref Sys->FD): string
84{
85	rbuf: array of byte;
86
87	conn := dial->dial(dial->netmkaddr(host, "udp", "69"), nil);
88	if(conn == nil)
89		return sys->sprint("can't dial %s: %r", host);
90	buf := array[Tftphdrlen + Maxblock] of byte;
91	i := 0;
92	put2(buf, i, Read);
93	i += 2;
94	a := array of byte filename;
95	buf[i:] = a;
96	i += len a;
97	buf[i++] = byte 0;
98	mode := array of byte "binary";
99	buf[i:] = mode;
100	i += len mode;
101	buf[i++] = byte 0;
102	pidc := chan of int;
103	bc := chan of array of byte;
104	spawn reader(pidc, conn.dfd, bc);
105	tftppid := <-pidc;
106	lastblock := 0;
107	for(;;) {
108	  Retry:
109		for(count := 0;; count++) {
110			if(count >= Maxretry){
111				kill(tftppid);
112				return sys->sprint("tftp timeout");
113			}
114
115			# (re)send request/ack
116			if(sys->write(conn.dfd, buf, i) < 0) {
117				kill(tftppid);
118				return sys->sprint( "error writing %s/data: %r", conn.dir);
119			}
120
121			# wait for next block
122			mtc := timeoutstart(3000);
123			for(;;){
124				alt {
125				<-mtc =>
126					if(progress)
127						sys->print("T");
128					continue Retry;
129				rbuf = <-bc =>
130					if(len rbuf < Tftphdrlen)
131						break;
132					op := get2(rbuf, 0);
133					case op {
134					Data =>
135						block := get2(rbuf, 2);
136						if(block == lastblock + 1) {
137							timeoutcancel();
138							break Retry;
139						}else if(progress)
140							sys->print("S");
141					Error =>
142						timeoutcancel();
143						kill(tftppid);
144						return sys->sprint("server error %d: %s", get2(rbuf, 2), string rbuf[4:]);
145					* =>
146						timeoutcancel();
147						kill(tftppid);
148						return sys->sprint("phase error op=%d", op);
149					}
150				}
151			}
152		}
153		n := len rbuf;
154		# copy the data somewhere
155		if(sys->write(fd, rbuf[Tftphdrlen:], n - Tftphdrlen) < 0) {
156			kill(tftppid);
157			return sys->sprint("writing destination: %r");
158		}
159		lastblock++;
160		if(progress && lastblock % 25 == 0)
161			sys->print(".");
162		if(n < Maxblock + Tftphdrlen) {
163			if(progress)
164				sys->print("\n");
165			break;
166		}
167
168		# send an ack
169		put2(buf, 0, Ack);
170		put2(buf, 2, lastblock);
171	}
172	kill(tftppid);
173	return nil;
174}
175