pxe, tftp: remove global buffers, double buffering
[syslinux/sherbszt.git] / core / fs / pxe / tftp.c
blobf1e22435ee65f406c48e97cc05edbcfaaf810802
1 #include <minmax.h>
2 #include <lwip/api.h>
3 #include "pxe.h"
4 #include "url.h"
5 #include "tftp.h"
7 const uint8_t TimeoutTable[] = {
8 2, 2, 3, 3, 4, 5, 6, 7, 9, 10, 12, 15, 18, 21, 26, 31, 37, 44,
9 53, 64, 77, 92, 110, 132, 159, 191, 229, 255, 255, 255, 255, 0
11 struct tftp_options {
12 const char *str_ptr; /* string pointer */
13 size_t offset; /* offset into socket structre */
15 struct tftp_packet {
16 uint16_t opcode;
17 uint16_t serial;
18 char data[];
21 #define IFIELD(x) offsetof(struct inode, x)
22 #define PFIELD(x) (offsetof(struct inode, pvt) + \
23 offsetof(struct pxe_pvt_inode, x))
25 static const struct tftp_options tftp_options[] =
27 { "tsize", IFIELD(size) },
28 { "blksize", PFIELD(tftp_blksize) },
30 static const int tftp_nopts = sizeof tftp_options / sizeof tftp_options[0];
32 static void tftp_error(struct inode *file, uint16_t errnum,
33 const char *errstr);
35 static void tftp_close_file(struct inode *inode)
37 struct pxe_pvt_inode *socket = PVT(inode);
38 if (!socket->tftp_goteof) {
39 tftp_error(inode, 0, "No error, file close");
41 if (socket->conn) {
42 netconn_delete(socket->conn);
43 socket->conn = NULL;
47 /**
48 * Send an ERROR packet. This is used to terminate a connection.
50 * @inode: Inode structure
51 * @errnum: Error number (network byte order)
52 * @errstr: Error string (included in packet)
54 static void tftp_error(struct inode *inode, uint16_t errnum,
55 const char *errstr)
57 static struct {
58 uint16_t err_op;
59 uint16_t err_num;
60 char err_msg[64];
61 } __packed err_buf;
62 struct netbuf *nbuf;
63 int len = min(strlen(errstr), sizeof(err_buf.err_msg)-1);
64 struct pxe_pvt_inode *socket = PVT(inode);
66 err_buf.err_op = TFTP_ERROR;
67 err_buf.err_num = errnum;
68 memcpy(err_buf.err_msg, errstr, len);
69 err_buf.err_msg[len] = '\0';
71 nbuf = netbuf_new();
72 netbuf_ref(nbuf, &err_buf, 4 + len + 1);
73 netconn_send(socket->conn, nbuf);
74 /* If something goes wrong, there is nothing we can do, anyway... */
75 netbuf_delete(nbuf);
78 /**
79 * Send ACK packet. This is a common operation and so is worth canning.
81 * @param: inode, Inode pointer
82 * @param: ack_num, Packet # to ack (host byte order)
85 static void ack_packet(struct inode *inode, uint16_t ack_num)
87 err_t err;
88 static uint16_t ack_packet_buf[2];
89 struct netbuf *nbuf;
90 struct pxe_pvt_inode *socket = PVT(inode);
92 /* Packet number to ack */
93 ack_packet_buf[0] = TFTP_ACK;
94 ack_packet_buf[1] = htons(ack_num);
96 nbuf = netbuf_new();
97 netbuf_ref(nbuf, ack_packet_buf, 4);
98 err = netconn_send(socket->conn, nbuf);
99 netbuf_delete(nbuf);
100 (void)err;
101 #if 0
102 printf("sent %s\n", err ? "FAILED" : "OK");
103 #endif
107 * Get a fresh packet if the buffer is drained, and we haven't hit
108 * EOF yet. The buffer should be filled immediately after draining!
110 static void tftp_get_packet(struct inode *inode)
112 int last_pkt;
113 const uint8_t *timeout_ptr;
114 uint8_t timeout;
115 uint16_t buffersize;
116 uint16_t serial;
117 jiffies_t oldtime;
118 struct tftp_packet *pkt = NULL;
119 struct netbuf *nbuf;
120 u16_t nbuf_len;
121 struct pxe_pvt_inode *socket = PVT(inode);
124 * Start by ACKing the previous packet; this should cause
125 * the next packet to be sent.
127 timeout_ptr = TimeoutTable;
128 timeout = *timeout_ptr++;
129 oldtime = jiffies();
131 ack_again:
132 ack_packet(inode, socket->tftp_lastpkt);
134 while (timeout) {
135 nbuf = netconn_recv(socket->conn);
136 if (!nbuf) {
137 jiffies_t now = jiffies();
139 if (now-oldtime >= timeout) {
140 oldtime = now;
141 timeout = *timeout_ptr++;
142 if (!timeout)
143 break;
144 goto ack_again;
146 continue;
149 netbuf_first(nbuf);
150 nbuf_len = 0;
151 nbuf_len = netbuf_len(nbuf);
152 if (nbuf_len <= socket->tftp_blksize + 4)
153 netbuf_copy(nbuf, socket->tftp_pktbuf, nbuf_len);
154 else
155 nbuf_len = 0; /* invalid packet */
156 netbuf_delete(nbuf);
157 if (nbuf_len < 4) /* Bad size for a DATA packet */
158 continue;
160 pkt = (struct tftp_packet *)(socket->tftp_pktbuf);
161 if (pkt->opcode != TFTP_DATA) /* Not a data packet */
162 continue;
164 /* If goes here, recevie OK, break */
165 break;
168 /* time runs out */
169 if (timeout == 0)
170 kaboom();
172 last_pkt = socket->tftp_lastpkt;
173 last_pkt++;
174 serial = ntohs(pkt->serial);
175 if (serial != last_pkt) {
177 * Wrong packet, ACK the packet and try again.
178 * This is presumably because the ACK got lost,
179 * so the server just resent the previous packet.
181 #if 0
182 printf("Wrong packet, wanted %04x, got %04x\n", \
183 htons(last_pkt), htons(*(uint16_t *)(data+2)));
184 #endif
185 goto ack_again;
188 /* It's the packet we want. We're also EOF if the size < blocksize */
189 socket->tftp_lastpkt = last_pkt; /* Update last packet number */
190 buffersize = nbuf_len - 4; /* Skip TFTP header */
191 socket->tftp_dataptr = socket->tftp_pktbuf + 4;
192 socket->tftp_filepos += buffersize;
193 socket->tftp_bytesleft = buffersize;
194 if (buffersize < socket->tftp_blksize) {
195 /* it's the last block, ACK packet immediately */
196 ack_packet(inode, serial);
198 /* Make sure we know we are at end of file */
199 inode->size = socket->tftp_filepos;
200 socket->tftp_goteof = 1;
201 tftp_close_file(inode);
205 const struct pxe_conn_ops tftp_conn_ops = {
206 .fill_buffer = tftp_get_packet,
207 .close = tftp_close_file,
211 * Open a TFTP connection to the server
213 * @param:inode, the inode to store our state in
214 * @param:ip, the ip to contact to get the file
215 * @param:filename, the file we wanna open
217 * @out: open_file_t structure, stores in file->open_file
218 * @out: the lenght of this file, stores in file->file_len
221 void tftp_open(struct url_info *url, int flags, struct inode *inode,
222 const char **redir)
224 struct pxe_pvt_inode *socket = PVT(inode);
225 char *buf;
226 struct netbuf *nbuf;
227 u16_t nbuf_len;
228 char *p;
229 char *options;
230 char *data;
231 static const char rrq_tail[] = "octet\0""tsize\0""0\0""blksize\0""1408";
232 char rrq_packet_buf[2+2*FILENAME_MAX+sizeof rrq_tail];
233 char reply_packet_buf[PKTBUF_SIZE];
234 const struct tftp_options *tftp_opt;
235 int i = 0;
236 int err;
237 int buffersize;
238 int rrq_len;
239 const uint8_t *timeout_ptr;
240 jiffies_t timeout;
241 jiffies_t oldtime;
242 uint16_t opcode;
243 uint16_t blk_num;
244 uint32_t opdata, *opdata_ptr;
245 struct ip_addr addr;
247 (void)redir; /* TFTP does not redirect */
248 (void)flags;
250 if (url->type != URL_OLD_TFTP) {
252 * The TFTP URL specification allows the TFTP to end with a
253 * ;mode= which we just ignore.
255 url_unescape(url->path, ';');
258 if (!url->port)
259 url->port = TFTP_PORT;
261 socket->ops = &tftp_conn_ops;
262 socket->conn = netconn_new(NETCONN_UDP);
263 if (!socket->conn)
264 return;
266 socket->conn->recv_timeout = 15; /* A 15 ms recv timeout... */
267 err = netconn_bind(socket->conn, NULL, 0);
268 if (err) {
269 printf("netconn_bind error %d\n", err);
270 return;
273 buf = rrq_packet_buf;
274 *(uint16_t *)buf = TFTP_RRQ; /* TFTP opcode */
275 buf += 2;
277 buf = stpcpy(buf, url->path);
279 buf++; /* Point *past* the final NULL */
280 memcpy(buf, rrq_tail, sizeof rrq_tail);
281 buf += sizeof rrq_tail;
283 rrq_len = buf - rrq_packet_buf;
285 timeout_ptr = TimeoutTable; /* Reset timeout */
287 sendreq:
288 netconn_disconnect(socket->conn);
289 timeout = *timeout_ptr++;
290 if (!timeout)
291 return; /* No file available... */
292 oldtime = jiffies();
294 nbuf = netbuf_new();
295 netbuf_ref(nbuf, rrq_packet_buf, rrq_len);
296 addr.addr = url->ip;
297 netconn_sendto(socket->conn, nbuf, &addr, url->port);
298 netbuf_delete(nbuf);
300 /* If the WRITE call fails, we let the timeout take care of it... */
301 wait_pkt:
302 netconn_disconnect(socket->conn);
303 for (;;) {
304 nbuf = netconn_recv(socket->conn);
305 if (!nbuf) {
306 jiffies_t now = jiffies();
307 if (now - oldtime >= timeout)
308 goto sendreq;
309 } else {
310 /* Make sure the packet actually came from the server */
311 bool ok_source;
312 ok_source = netbuf_fromaddr(nbuf)->addr == url->ip;
313 nbuf_len = netbuf_len(nbuf);
314 if (nbuf_len <= PKTBUF_SIZE)
315 netbuf_copy(nbuf, reply_packet_buf, nbuf_len);
316 else
317 nbuf_len = 0; /* impossible mtu < PKTBUF_SIZE */
318 netbuf_delete(nbuf);
319 if (ok_source)
320 break;
324 netconn_connect(socket->conn, netbuf_fromaddr(nbuf), netbuf_fromport(nbuf));
326 /* filesize <- -1 == unknown */
327 inode->size = -1;
328 socket->tftp_blksize = TFTP_BLOCKSIZE;
329 buffersize = nbuf_len - 2; /* bytes after opcode */
330 if (buffersize < 0)
331 goto wait_pkt; /* Garbled reply */
334 * Get the opcode type, and parse it
336 opcode = *(uint16_t *)reply_packet_buf;
337 switch (opcode) {
338 case TFTP_ERROR:
339 inode->size = 0;
340 goto done; /* ERROR reply; don't try again */
342 case TFTP_DATA:
344 * If the server doesn't support any options, we'll get a
345 * DATA reply instead of OACK. Stash the data in the file
346 * buffer and go with the default value for all options...
348 * We got a DATA packet, meaning no options are
349 * suported. Save the data away and consider the
350 * length undefined, *unless* this is the only
351 * data packet...
353 buffersize -= 2;
354 if (buffersize < 0)
355 goto wait_pkt;
356 data = reply_packet_buf + 2;
357 blk_num = ntohs(*(uint16_t *)data);
358 data += 2;
359 if (blk_num != 1)
360 goto wait_pkt;
361 socket->tftp_lastpkt = blk_num;
362 if (buffersize > TFTP_BLOCKSIZE)
363 goto err_reply; /* Corrupt */
365 socket->tftp_pktbuf = malloc(TFTP_BLOCKSIZE + 4);
366 if (!socket->tftp_pktbuf)
367 goto err_reply; /* Internal error */
369 if (buffersize < TFTP_BLOCKSIZE) {
371 * This is the final EOF packet, already...
372 * We know the filesize, but we also want to
373 * ack the packet and set the EOF flag.
375 inode->size = buffersize;
376 socket->tftp_goteof = 1;
377 ack_packet(inode, blk_num);
380 socket->tftp_bytesleft = buffersize;
381 socket->tftp_dataptr = socket->tftp_pktbuf;
382 memcpy(socket->tftp_pktbuf, data, buffersize);
383 goto done;
385 case TFTP_OACK:
387 * Now we need to parse the OACK packet to get the transfer
388 * and packet sizes.
391 options = reply_packet_buf + 2;
392 p = options;
394 while (buffersize) {
395 const char *opt = p;
398 * If we find an option which starts with a NUL byte,
399 * (a null option), we're either seeing garbage that some
400 * TFTP servers add to the end of the packet, or we have
401 * no clue how to parse the rest of the packet (what is
402 * an option name and what is a value?) In either case,
403 * discard the rest.
405 if (!*opt)
406 goto done;
408 while (buffersize) {
409 if (!*p)
410 break; /* Found a final null */
411 *p++ |= 0x20;
412 buffersize--;
414 if (!buffersize)
415 break; /* Unterminated option */
417 /* Consume the terminal null */
418 p++;
419 buffersize--;
421 if (!buffersize)
422 break; /* No option data */
425 * Parse option pointed to by options; guaranteed to be
426 * null-terminated
428 tftp_opt = tftp_options;
429 for (i = 0; i < tftp_nopts; i++) {
430 if (!strcmp(opt, tftp_opt->str_ptr))
431 break;
432 tftp_opt++;
434 if (i == tftp_nopts)
435 goto err_reply; /* Non-negotitated option returned,
436 no idea what it means ...*/
438 /* get the address of the filed that we want to write on */
439 opdata_ptr = (uint32_t *)((char *)inode + tftp_opt->offset);
440 opdata = 0;
442 /* do convert a number-string to decimal number, just like atoi */
443 while (buffersize--) {
444 uint8_t d = *p++;
445 if (d == '\0')
446 break; /* found a final null */
447 d -= '0';
448 if (d > 9)
449 goto err_reply; /* Not a decimal digit */
450 opdata = opdata*10 + d;
452 *opdata_ptr = opdata;
455 if (socket->tftp_blksize < 64 || socket->tftp_blksize > PKTBUF_SIZE)
456 goto err_reply;
458 /* Parsing successful, allocate buffer */
459 socket->tftp_pktbuf = malloc(socket->tftp_blksize + 4);
460 if (!socket->tftp_pktbuf)
461 goto err_reply;
462 else
463 goto done;
465 default:
466 printf("TFTP unknown opcode %d\n", ntohs(opcode));
467 goto err_reply;
470 err_reply:
471 /* Build the TFTP error packet */
472 tftp_error(inode, TFTP_EOPTNEG, "TFTP protocol error");
473 inode->size = 0;
475 done:
476 if (!inode->size) {
477 netconn_delete(socket->conn);
478 socket->conn = NULL;
480 return;