root/usr/src/grub/grub-0.97/netboot/fsys_tftp.c
/*
 *  GRUB  --  GRand Unified Bootloader
 *  Copyright (C) 2000,2001,2002,2004  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 <shared.h>

#include "grub.h"
#include "tftp.h"
#include "nic.h"

static int tftp_file_read_undi(const char *name,
    int (*fnc)(unsigned char *, unsigned int, unsigned int, int));
static int tftp_read_undi(char *addr, int size);
static int tftp_dir_undi(char *dirname);
static void tftp_close_undi(void);
static int buf_fill_undi(int abort);

extern int use_bios_pxe;

static int retry;
static unsigned short iport = 2000;
static unsigned short oport = 0;
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, *saved_name;

/**
 * tftp_read
 *
 * Read file with _name_, data handled by _fnc_. In fact, grub never
 * use it, we just use it to read dhcp config file.
 */
static int await_tftp(int ival, void *ptr __unused, 
                      unsigned short ptype __unused, struct iphdr *ip, 
                      struct udphdr *udp)
{
        static int tftp_count = 0;

        if (!udp) {
                return 0;
        }
        if (arptable[ARP_CLIENT].ipaddr.s_addr != ip->dest.s_addr)
                return 0;
        if (ntohs(udp->dest) != ival)
                return 0;
        tftp_count++;   /* show progress */
        if ((tftp_count % 1000) == 0)
                printf(".");
        return 1;
}

int tftp_file_read(const char *name, int (*fnc)(unsigned char *, unsigned int, unsigned int, int))
{
        struct tftpreq_t tp;
        struct tftp_t  *tr;
        int             rc;

        if (use_bios_pxe)
                return (tftp_file_read_undi(name, fnc));

        retry = 0;
        block = 0;
        prevblock = 0;
        bcounter = 0;
        

        rx_qdrain();

        tp.opcode = htons(TFTP_RRQ);
        /* Warning: the following assumes the layout of bootp_t.
           But that's fixed by the IP, UDP and BOOTP specs. */
        len = sizeof(tp.ip) + sizeof(tp.udp) + sizeof(tp.opcode) +
                sprintf((char *)tp.u.rrq, "%s%coctet%cblksize%c%d",
                name, 0, 0, 0, TFTP_MAX_PACKET) + 1;
        if (!udp_transmit(arptable[ARP_SERVER].ipaddr.s_addr, ++iport,
                          TFTP_PORT, len, &tp))
                return (0);
        for (;;)
        {
                long timeout;
#ifdef  CONGESTED
                timeout = rfc2131_sleep_interval(block?TFTP_REXMT: TIMEOUT, retry);
#else
                timeout = rfc2131_sleep_interval(TIMEOUT, retry);
#endif
                if (!await_reply(await_tftp, iport, NULL, timeout))
                {
                        if (!block && retry++ < MAX_TFTP_RETRIES)
                        {       /* maybe initial request was lost */
                                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  MDEBUG
                                printf("<REXMT>\n");
#endif
                                udp_transmit(arptable[ARP_SERVER].ipaddr.s_addr,
                                             iport, oport,
                                             TFTP_MIN_PACKET, &tp);
                                continue;
                        }
#endif
                        break;  /* timeout */
                }
                tr = (struct tftp_t *)&nic.packet[ETH_HLEN];
                if (tr->opcode == ntohs(TFTP_ERROR))
                {
                        printf("TFTP error %d (%s)\n",
                               ntohs(tr->u.err.errcode),
                               tr->u.err.errmsg);
                        break;
                }

                if (tr->opcode == ntohs(TFTP_OACK)) {
                        char *p = tr->u.oack.data, *e;

                        if (prevblock)          /* shouldn't happen */
                                continue;       /* ignore it */
                        len = ntohs(tr->udp.len) - sizeof(struct udphdr) - 2;
                        if (len > TFTP_MAX_PACKET)
                                goto noak;
                        e = p + len;
                        while (*p != '\0' && p < e) {
/*                              if (!strcasecmp("blksize", p)) { */
                                if (!grub_strcmp("blksize", p)) {
                                        p += 8;
/*                                      if ((packetsize = strtoul(p, &p, 10)) < */
                                        if ((packetsize = getdec(&p)) < TFTP_DEFAULTSIZE_PACKET)
                                                goto noak;
                                        while (p < e && *p) p++;
                                        if (p < e)
                                                p++;
                                }
                                else {
                                noak:
                                        tp.opcode = htons(TFTP_ERROR);
                                        tp.u.err.errcode = 8;
/*
 *      Warning: the following assumes the layout of bootp_t.
 *      But that's fixed by the IP, UDP and BOOTP specs.
 */
                                        len = sizeof(tp.ip) + sizeof(tp.udp) + sizeof(tp.opcode) + sizeof(tp.u.err.errcode) +
/*
 *      Normally bad form to omit the format string, but in this case
 *      the string we are copying from is fixed. sprintf is just being
 *      used as a strcpy and strlen.
 */
                                                sprintf((char *)tp.u.err.errmsg,
                                                "RFC1782 error") + 1;
                                        udp_transmit(arptable[ARP_SERVER].ipaddr.s_addr,
                                                     iport, ntohs(tr->udp.src),
                                                     len, &tp);
                                        return (0);
                                }
                        }
                        if (p > e)
                                goto noak;
                        block = tp.u.ack.block = 0; /* this ensures, that */
                                                /* the packet does not get */
                                                /* processed as data! */
                }
                else if (tr->opcode == htons(TFTP_DATA)) {
                        len = ntohs(tr->udp.len) - sizeof(struct udphdr) - 4;
                        if (len > packetsize)   /* shouldn't happen */
                                continue;       /* ignore it */
                        block = ntohs(tp.u.ack.block = tr->u.data.block); }
                else {/* neither TFTP_OACK nor TFTP_DATA */
                        break;
                }

                if ((block || bcounter) && (block != (unsigned short)(prevblock+1))) {
                        /* Block order should be continuous */
                        tp.u.ack.block = htons(block = prevblock);
                }
                tp.opcode = htons(TFTP_ACK);
                oport = ntohs(tr->udp.src);
                udp_transmit(arptable[ARP_SERVER].ipaddr.s_addr, iport,
                             oport, TFTP_MIN_PACKET, &tp);      /* ack */
                if ((unsigned short)(block-prevblock) != 1) {
                        /* Retransmission or OACK, don't process via callback
                         * and don't change the value of prevblock.  */
                        continue;
                }
                prevblock = block;
                retry = 0;      /* It's the right place to zero the timer? */
                if ((rc = fnc(tr->u.data.download,
                              ++bcounter, len, len < packetsize)) <= 0)
                        return(rc);
                if (len < packetsize) { /* End of data --- fnc should not have returned */
                        printf("tftp download complete, but\n");
                        return (1);
                }
        }
        return (0);
}

/* 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
  
  if (use_bios_pxe)
        return (buf_fill_undi(abort));

  while (! buf_eof && (buf_read + packetsize <= FSYS_BUFLEN))
    {
      struct tftp_t *tr;
      long timeout;

#ifdef CONGESTED
      timeout = rfc2131_sleep_interval (block ? TFTP_REXMT : TIMEOUT, retry);
#else
      timeout = rfc2131_sleep_interval (TIMEOUT, retry);
#endif
  
      if (! await_reply (await_tftp, iport, NULL, timeout))
        {
          if (user_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
              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[ETH_HLEN];
      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.  */
              grub_printf ("%s:%d: warning: PREVBLOCK != 0 (0x%x)\n",
                           __FILE__, __LINE__, prevblock);
              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")
                         + sizeof (tp.ip) + sizeof (tp.udp)
                         + sizeof (tp.opcode) + sizeof (tp.u.err.errcode)
                         + 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.  */
              grub_printf ("%s:%d: warning: LEN > PACKETSIZE (0x%x > 0x%x)\n",
                           __FILE__, __LINE__, len, packetsize);
              continue;
            }
          
          block = ntohs (tp.u.ack.block = tr->u.data.block);
        }
      else
        /* Neither TFTP_OACK nor TFTP_DATA.  */
        break;

      if ((block || bcounter) && (block != (unsigned short) (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;

  rx_qdrain();
  
#ifdef TFTP_DEBUG
  grub_printf ("send_rrq ()\n");
  {
    int i;
    char *p;

    for (i = 0, p = (char *) &tp; i < len; i++)
      if (p[i] >= ' ' && p[i] <= '~')
        grub_putchar (p[i]);
      else
        grub_printf ("\\%x", (unsigned) p[i]);

    grub_putchar ('\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 (use_bios_pxe)
        return (tftp_read_undi(addr, size));

  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;
            }
        }
      else
        {
          /* Skip the whole buffer.  */
          saved_filepos += buf_read;
          buf_read = 0;
        }

      /* Read the data.  */
      if (size > 0 && ! 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
  
  if (use_bios_pxe)
        return (tftp_dir_undi(dirname));

  /* In TFTP, there is no way to know what files exist.  */
  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)
         + sizeof (tp.ip) + sizeof (tp.udp) + sizeof (tp.opcode) + 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
  
  if (use_bios_pxe) {
        tftp_close_undi();
        return;
  }

  buf_read = 0;
  buf_fill (1);
}

/* tftp implementation using BIOS established PXE stack */

static int tftp_file_read_undi(const char *name,
    int (*fnc)(unsigned char *, unsigned int, unsigned int, int))
{
        int rc;
        uint16_t len;
        
        buf = (char *)&nic.packet;
        /* open tftp session */
        if (eb_pxenv_tftp_open(name, arptable[ARP_SERVER].ipaddr,
            arptable[ARP_GATEWAY].ipaddr, &packetsize) == 0)
                return (0);

        /* read blocks and invoke fnc for each block */
        for (;;) {
                rc = eb_pxenv_tftp_read(buf, &len);
                if (rc == 0)
                        break;
                rc = fnc(buf, ++block, len, len < packetsize);
                if (rc <= 0 || len < packetsize)
                        break;
        }

        (void) eb_pxenv_tftp_close();
        return (rc > 0 ? 1 : 0);
}

/* Fill the buffer by reading the data via the TFTP protocol.  */
static int
buf_fill_undi(int abort)
{
        int rc;
        uint8_t *tmpbuf;

        while (! buf_eof && (buf_read + packetsize <= FSYS_BUFLEN)) {
                poll_interruptions();
                if (user_abort)
                        return 0;
                if (abort) {
                        buf_eof = 1;
                        break;
                }

                if (eb_pxenv_tftp_read(buf + buf_read, &len) == 0)
                        return (0);

                buf_read += len;

                /* End of data.  */
                if (len < packetsize)           
                        buf_eof = 1;
        }
        return 1;
}

static void
tftp_reopen_undi(void)
{
        tftp_close();
        (void) eb_pxenv_tftp_open(saved_name, arptable[ARP_SERVER].ipaddr,
            arptable[ARP_GATEWAY].ipaddr, &packetsize);

        buf_eof = 0;
        buf_read = 0;
        saved_filepos = 0;
}

/* Read up to SIZE bytes, returned in ADDR.  */
static int
tftp_read_undi(char *addr, int size)
{
        int ret = 0;

        if (filepos < saved_filepos) {
                /* Uggh.. FILEPOS has been moved backwards. reopen the file. */
                tftp_reopen_undi();
        }

        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;
                        }
                } else {
                        /* Skip the whole buffer.  */
                        saved_filepos += buf_read;
                        buf_read = 0;
                }

                /* Read the data.  */
                if (size > 0 && ! buf_fill (0)) {
                        errnum = ERR_READ;
                        return 0;
                }

                /* Sanity check.  */
                if (size > 0 && buf_read == 0) {
                        errnum = ERR_READ;
                        return 0;
                }
        }

        return ret;
}

static int
tftp_dir_undi(char *dirname)
{
        int rc, ch;
        uint16_t len;

        /* In TFTP, there is no way to know what files exist.  */
        if (print_possibilities)
                return 1;

        /* name may be space terminated */
        ch = nul_terminate(dirname);
        saved_name = (char *)&saved_tp;
        sprintf(saved_name, "%s", dirname);

        /* Restore the original dirname */
        dirname[grub_strlen (dirname)] = ch;

        /* get the file size; must call before tftp_open */
        rc = eb_pxenv_tftp_get_fsize(saved_name, arptable[ARP_SERVER].ipaddr,
            arptable[ARP_GATEWAY].ipaddr, &filemax);

        /* open tftp session */
        if (eb_pxenv_tftp_open(saved_name, arptable[ARP_SERVER].ipaddr,
            arptable[ARP_GATEWAY].ipaddr, &packetsize) == 0)
                return (0);

        buf = (char *) FSYS_BUF;
        buf_eof = 0;
        buf_read = 0;
        saved_filepos = 0;

        if (rc == 0) {
                /* Read the entire file to get filemax */
                filemax = 0;
                do {
                        /* Add the length of the downloaded data.  */
                        filemax += buf_read;
                        buf_read = 0;
                        if (! buf_fill (0)) {
                                errnum = ERR_READ;
                                return 0;
                        }
                } while (! buf_eof);

                /* Maybe a few amounts of data remains.  */
                filemax += buf_read;

                tftp_reopen_undi(); /* reopen file to read from beginning */
        }

        return (1);
}

static void
tftp_close_undi(void)
{
        buf_read = 0;
        buf_fill (1);
        (void) eb_pxenv_tftp_close();
}