| /* |
| * GRUB -- GRand Unified Bootloader |
| * Copyright (C) 2000 Free Software Foundation, Inc. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
| */ |
| |
| /* Based on "src/main.c" in etherboot-4.5.8. */ |
| /************************************************************************** |
| ETHERBOOT - BOOTP/TFTP Bootstrap Program |
| |
| Author: Martin Renters |
| Date: Dec/93 |
| |
| **************************************************************************/ |
| |
| /* #define TFTP_DEBUG 1 */ |
| |
| #include <filesys.h> |
| |
| #include <etherboot.h> |
| #include <nic.h> |
| |
| static int retry; |
| static unsigned short iport = 2000; |
| static unsigned short oport; |
| static unsigned short block, prevblock; |
| static int bcounter; |
| static struct tftp_t tp, saved_tp; |
| static int packetsize; |
| static int buf_eof, buf_read; |
| static int saved_filepos; |
| static unsigned short len, saved_len; |
| static char *buf; |
| |
| /* Fill the buffer by receiving the data via the TFTP protocol. */ |
| static int |
| buf_fill (int abort) |
| { |
| #ifdef TFTP_DEBUG |
| grub_printf ("buf_fill (%d)\n", abort); |
| #endif |
| |
| while (! buf_eof && (buf_read + packetsize <= FSYS_BUFLEN)) |
| { |
| struct tftp_t *tr; |
| |
| #ifdef CONGESTED |
| if (! await_reply (AWAIT_TFTP, iport, NULL, |
| block ? TFTP_REXMT : TIMEOUT)) |
| #else |
| if (! await_reply (AWAIT_TFTP, iport, NULL, TIMEOUT)) |
| #endif |
| { |
| if (ip_abort) |
| return 0; |
| |
| if (! block && retry++ < MAX_TFTP_RETRIES) |
| { |
| /* Maybe initial request was lost. */ |
| #ifdef TFTP_DEBUG |
| grub_printf ("Maybe initial request was lost.\n"); |
| #endif |
| rfc951_sleep (retry); |
| if (! udp_transmit (arptable[ARP_SERVER].ipaddr.s_addr, |
| ++iport, TFTP_PORT, len, &tp)) |
| return 0; |
| |
| continue; |
| } |
| |
| #ifdef CONGESTED |
| if (block && ((retry += TFTP_REXMT) < TFTP_TIMEOUT)) |
| { |
| /* We resend our last ack. */ |
| # ifdef TFTP_DEBUG |
| grub_printf ("<REXMT>\n"); |
| # endif |
| udp_transmit (arptable[ARP_SERVER].ipaddr.s_addr, |
| iport, oport, |
| TFTP_MIN_PACKET, &tp); |
| continue; |
| } |
| #endif |
| /* Timeout. */ |
| return 0; |
| } |
| |
| tr = (struct tftp_t *) &nic.packet[ETHER_HDR_SIZE]; |
| if (tr->opcode == ntohs (TFTP_ERROR)) |
| { |
| grub_printf ("TFTP error %d (%s)\n", |
| ntohs (tr->u.err.errcode), |
| tr->u.err.errmsg); |
| return 0; |
| } |
| |
| if (tr->opcode == ntohs (TFTP_OACK)) |
| { |
| char *p = tr->u.oack.data, *e; |
| |
| #ifdef TFTP_DEBUG |
| grub_printf ("OACK "); |
| #endif |
| /* Shouldn't happen. */ |
| if (prevblock) |
| /* Ignore it. */ |
| continue; |
| |
| len = ntohs (tr->udp.len) - sizeof (struct udphdr) - 2; |
| if (len > TFTP_MAX_PACKET) |
| goto noak; |
| |
| e = p + len; |
| while (*p != '\000' && p < e) |
| { |
| if (! grub_strcmp ("blksize", p)) |
| { |
| p += 8; |
| if ((packetsize = getdec (&p)) < TFTP_DEFAULTSIZE_PACKET) |
| goto noak; |
| #ifdef TFTP_DEBUG |
| grub_printf ("blksize = %d\n", packetsize); |
| #endif |
| } |
| else if (! grub_strcmp ("tsize", p)) |
| { |
| p += 6; |
| if ((filemax = getdec (&p)) < 0) |
| { |
| filemax = -1; |
| goto noak; |
| } |
| #ifdef TFTP_DEBUG |
| grub_printf ("tsize = %d\n", filemax); |
| #endif |
| } |
| else |
| { |
| noak: |
| #ifdef TFTP_DEBUG |
| grub_printf ("NOAK\n"); |
| #endif |
| tp.opcode = htons (TFTP_ERROR); |
| tp.u.err.errcode = 8; |
| len = (grub_sprintf ((char *) tp.u.err.errmsg, |
| "RFC1782 error") |
| + TFTP_MIN_PACKET + 1); |
| udp_transmit (arptable[ARP_SERVER].ipaddr.s_addr, |
| iport, ntohs (tr->udp.src), |
| len, &tp); |
| return 0; |
| } |
| |
| while (p < e && *p) |
| p++; |
| |
| if (p < e) |
| p++; |
| } |
| |
| if (p > e) |
| goto noak; |
| |
| /* This ensures that the packet does not get processed as |
| data! */ |
| block = tp.u.ack.block = 0; |
| } |
| else if (tr->opcode == ntohs (TFTP_DATA)) |
| { |
| #ifdef TFTP_DEBUG |
| grub_printf ("DATA "); |
| #endif |
| len = ntohs (tr->udp.len) - sizeof (struct udphdr) - 4; |
| |
| /* Shouldn't happen. */ |
| if (len > packetsize) |
| /* Ignore it. */ |
| continue; |
| |
| block = ntohs (tp.u.ack.block = tr->u.data.block); |
| } |
| else |
| /* Neither TFTP_OACK nor TFTP_DATA. */ |
| break; |
| |
| if ((block || bcounter) && (block != prevblock + 1)) |
| /* Block order should be continuous */ |
| tp.u.ack.block = htons (block = prevblock); |
| |
| /* Should be continuous. */ |
| tp.opcode = abort ? htons (TFTP_ERROR) : htons (TFTP_ACK); |
| oport = ntohs (tr->udp.src); |
| |
| #ifdef TFTP_DEBUG |
| grub_printf ("ACK\n"); |
| #endif |
| /* Ack. */ |
| udp_transmit (arptable[ARP_SERVER].ipaddr.s_addr, iport, |
| oport, TFTP_MIN_PACKET, &tp); |
| |
| if (abort) |
| { |
| buf_eof = 1; |
| break; |
| } |
| |
| /* Retransmission or OACK. */ |
| if ((unsigned short) (block - prevblock) != 1) |
| /* Don't process. */ |
| continue; |
| |
| prevblock = block; |
| /* Is it the right place to zero the timer? */ |
| retry = 0; |
| |
| /* In GRUB, this variable doesn't play any important role at all, |
| but use it for consistency with Etherboot. */ |
| bcounter++; |
| |
| /* Copy the downloaded data to the buffer. */ |
| grub_memmove (buf + buf_read, tr->u.data.download, len); |
| buf_read += len; |
| |
| /* End of data. */ |
| if (len < packetsize) |
| buf_eof = 1; |
| } |
| |
| return 1; |
| } |
| |
| /* Send the RRQ whose length is LEN. */ |
| static int |
| send_rrq (void) |
| { |
| /* Initialize some variables. */ |
| retry = 0; |
| block = 0; |
| prevblock = 0; |
| packetsize = TFTP_DEFAULTSIZE_PACKET; |
| bcounter = 0; |
| |
| buf = (char *) FSYS_BUF; |
| buf_eof = 0; |
| buf_read = 0; |
| saved_filepos = 0; |
| |
| /* Clear out the Rx queue first. It contains nothing of interest, |
| * except possibly ARP requests from the DHCP/TFTP server. We use |
| * polling throughout Etherboot, so some time may have passed since we |
| * last polled the receive queue, which may now be filled with |
| * broadcast packets. This will cause the reply to the packets we are |
| * about to send to be lost immediately. Not very clever. */ |
| await_reply (AWAIT_QDRAIN, 0, NULL, 0); |
| |
| #ifdef TFTP_DEBUG |
| grub_printf ("send_rrq ()\n"); |
| #endif |
| /* Send the packet. */ |
| return udp_transmit (arptable[ARP_SERVER].ipaddr.s_addr, ++iport, |
| TFTP_PORT, len, &tp); |
| } |
| |
| /* Mount the network drive. If the drive is ready, return one, otherwise |
| return zero. */ |
| int |
| tftp_mount (void) |
| { |
| /* Check if the current drive is the network drive. */ |
| if (current_drive != NETWORK_DRIVE) |
| return 0; |
| |
| /* If the drive is not initialized yet, abort. */ |
| if (! network_ready) |
| return 0; |
| |
| return 1; |
| } |
| |
| /* Read up to SIZE bytes, returned in ADDR. */ |
| int |
| tftp_read (char *addr, int size) |
| { |
| /* How many bytes is read? */ |
| int ret = 0; |
| |
| #ifdef TFTP_DEBUG |
| grub_printf ("tftp_read (0x%x, %d)\n", (int) addr, size); |
| #endif |
| |
| if (filepos < saved_filepos) |
| { |
| /* Uggh.. FILEPOS has been moved backwards. So reopen the file. */ |
| buf_read = 0; |
| buf_fill (1); |
| grub_memmove ((char *) &tp, (char *) &saved_tp, saved_len); |
| len = saved_len; |
| #ifdef TFTP_DEBUG |
| { |
| int i; |
| grub_printf ("opcode = 0x%x, rrq = ", (unsigned long) tp.opcode); |
| for (i = 0; i < TFTP_DEFAULTSIZE_PACKET; i++) |
| { |
| if (tp.u.rrq[i] >= ' ' && tp.u.rrq[i] <= '~') |
| grub_putchar (tp.u.rrq[i]); |
| else |
| grub_putchar ('*'); |
| } |
| grub_putchar ('\n'); |
| } |
| #endif |
| |
| if (! send_rrq ()) |
| { |
| errnum = ERR_WRITE; |
| return 0; |
| } |
| } |
| |
| while (size > 0) |
| { |
| int amt = buf_read + saved_filepos - filepos; |
| |
| /* If the length that can be copied from the buffer is over the |
| requested size, cut it down. */ |
| if (amt > size) |
| amt = size; |
| |
| if (amt > 0) |
| { |
| /* Copy the buffer to the supplied memory space. */ |
| grub_memmove (addr, buf + filepos - saved_filepos, amt); |
| size -= amt; |
| addr += amt; |
| filepos += amt; |
| ret += amt; |
| } |
| |
| /* If the size of the empty space becomes small, move the unused |
| data forwards. */ |
| if (filepos - saved_filepos > FSYS_BUFLEN / 2) |
| { |
| grub_memmove (buf, buf + FSYS_BUFLEN / 2, FSYS_BUFLEN / 2); |
| buf_read -= FSYS_BUFLEN / 2; |
| saved_filepos += FSYS_BUFLEN / 2; |
| } |
| |
| /* Read the data. */ |
| if (! buf_fill (0)) |
| { |
| errnum = ERR_READ; |
| return 0; |
| } |
| |
| /* Sanity check. */ |
| if (size > 0 && buf_read == 0) |
| { |
| errnum = ERR_READ; |
| return 0; |
| } |
| } |
| |
| return ret; |
| } |
| |
| /* Check if the file DIRNAME really exists. Get the size and save it in |
| FILEMAX. */ |
| int |
| tftp_dir (char *dirname) |
| { |
| int ch; |
| |
| #ifdef TFTP_DEBUG |
| grub_printf ("tftp_dir (%s)\n", dirname); |
| #endif |
| |
| /* In TFTP, there is no way to know what files exis. */ |
| if (print_possibilities) |
| return 1; |
| |
| /* Don't know the size yet. */ |
| filemax = -1; |
| |
| reopen: |
| /* Construct the TFTP request packet. */ |
| tp.opcode = htons (TFTP_RRQ); |
| /* Terminate the filename. */ |
| ch = nul_terminate (dirname); |
| /* Make the request string (octet, blksize and tsize). */ |
| len = (grub_sprintf ((char *) tp.u.rrq, |
| "%s%coctet%cblksize%c%d%ctsize%c0", |
| dirname, 0, 0, 0, TFTP_MAX_PACKET, 0, 0) |
| + TFTP_MIN_PACKET + 1); |
| /* Restore the original DIRNAME. */ |
| dirname[grub_strlen (dirname)] = ch; |
| /* Save the TFTP packet so that we can reopen the file later. */ |
| grub_memmove ((char *) &saved_tp, (char *) &tp, len); |
| saved_len = len; |
| if (! send_rrq ()) |
| { |
| errnum = ERR_WRITE; |
| return 0; |
| } |
| |
| /* Read the data. */ |
| if (! buf_fill (0)) |
| { |
| errnum = ERR_FILE_NOT_FOUND; |
| return 0; |
| } |
| |
| if (filemax == -1) |
| { |
| /* The server doesn't support the "tsize" option, so we must read |
| the file twice... */ |
| |
| /* Zero the size of the file. */ |
| filemax = 0; |
| do |
| { |
| /* Add the length of the downloaded data. */ |
| filemax += buf_read; |
| /* Reset the offset. Just discard the contents of the buffer. */ |
| buf_read = 0; |
| /* Read the data. */ |
| if (! buf_fill (0)) |
| { |
| errnum = ERR_READ; |
| return 0; |
| } |
| } |
| while (! buf_eof); |
| |
| /* Maybe a few amounts of data remains. */ |
| filemax += buf_read; |
| |
| /* Retry the open instruction. */ |
| goto reopen; |
| } |
| |
| return 1; |
| } |
| |
| /* Close the file. */ |
| void |
| tftp_close (void) |
| { |
| #ifdef TFTP_DEBUG |
| grub_printf ("tftp_close ()\n"); |
| #endif |
| |
| buf_read = 0; |
| buf_fill (1); |
| } |