#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;
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++;
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);
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)
{
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))
{
#ifdef MDEBUG
printf("<REXMT>\n");
#endif
udp_transmit(arptable[ARP_SERVER].ipaddr.s_addr,
iport, oport,
TFTP_MIN_PACKET, &tp);
continue;
}
#endif
break;
}
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)
continue;
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 (!grub_strcmp("blksize", p)) {
p += 8;
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;
len = sizeof(tp.ip) + sizeof(tp.udp) + sizeof(tp.opcode) + sizeof(tp.u.err.errcode) +
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;
}
else if (tr->opcode == htons(TFTP_DATA)) {
len = ntohs(tr->udp.len) - sizeof(struct udphdr) - 4;
if (len > packetsize)
continue;
block = ntohs(tp.u.ack.block = tr->u.data.block); }
else {
break;
}
if ((block || bcounter) && (block != (unsigned short)(prevblock+1))) {
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);
if ((unsigned short)(block-prevblock) != 1) {
continue;
}
prevblock = block;
retry = 0;
if ((rc = fnc(tr->u.data.download,
++bcounter, len, len < packetsize)) <= 0)
return(rc);
if (len < packetsize) {
printf("tftp download complete, but\n");
return (1);
}
}
return (0);
}
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)
{
#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))
{
# ifdef TFTP_DEBUG
grub_printf ("<REXMT>\n");
# endif
udp_transmit (arptable[ARP_SERVER].ipaddr.s_addr,
iport, oport,
TFTP_MIN_PACKET, &tp);
continue;
}
#endif
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
if (prevblock)
{
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;
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;
if (len > packetsize)
{
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
break;
if ((block || bcounter) && (block != (unsigned short) (prevblock + 1)))
tp.u.ack.block = htons (block = prevblock);
tp.opcode = abort ? htons (TFTP_ERROR) : htons (TFTP_ACK);
oport = ntohs (tr->udp.src);
#ifdef TFTP_DEBUG
grub_printf ("ACK\n");
#endif
udp_transmit (arptable[ARP_SERVER].ipaddr.s_addr, iport,
oport, TFTP_MIN_PACKET, &tp);
if (abort)
{
buf_eof = 1;
break;
}
if ((unsigned short) (block - prevblock) != 1)
continue;
prevblock = block;
retry = 0;
bcounter++;
grub_memmove (buf + buf_read, tr->u.data.download, len);
buf_read += len;
if (len < packetsize)
buf_eof = 1;
}
return 1;
}
static int
send_rrq (void)
{
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
return udp_transmit (arptable[ARP_SERVER].ipaddr.s_addr, ++iport,
TFTP_PORT, len, &tp);
}
int
tftp_mount (void)
{
if (current_drive != NETWORK_DRIVE)
return 0;
if (! network_ready)
return 0;
return 1;
}
int
tftp_read (char *addr, int size)
{
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)
{
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 (amt > size)
amt = size;
if (amt > 0)
{
grub_memmove (addr, buf + filepos - saved_filepos, amt);
size -= amt;
addr += amt;
filepos += amt;
ret += amt;
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
{
saved_filepos += buf_read;
buf_read = 0;
}
if (size > 0 && ! buf_fill (0))
{
errnum = ERR_READ;
return 0;
}
if (size > 0 && buf_read == 0)
{
errnum = ERR_READ;
return 0;
}
}
return ret;
}
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));
if (print_possibilities)
return 1;
filemax = -1;
reopen:
tp.opcode = htons (TFTP_RRQ);
ch = nul_terminate (dirname);
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);
dirname[grub_strlen (dirname)] = ch;
grub_memmove ((char *) &saved_tp, (char *) &tp, len);
saved_len = len;
if (! send_rrq ())
{
errnum = ERR_WRITE;
return 0;
}
if (! buf_fill (0))
{
errnum = ERR_FILE_NOT_FOUND;
return 0;
}
if (filemax == -1)
{
filemax = 0;
do
{
filemax += buf_read;
buf_read = 0;
if (! buf_fill (0))
{
errnum = ERR_READ;
return 0;
}
}
while (! buf_eof);
filemax += buf_read;
goto reopen;
}
return 1;
}
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);
}
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;
if (eb_pxenv_tftp_open(name, arptable[ARP_SERVER].ipaddr,
arptable[ARP_GATEWAY].ipaddr, &packetsize) == 0)
return (0);
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);
}
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;
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;
}
static int
tftp_read_undi(char *addr, int size)
{
int ret = 0;
if (filepos < saved_filepos) {
tftp_reopen_undi();
}
while (size > 0) {
int amt = buf_read + saved_filepos - filepos;
if (amt > size)
amt = size;
if (amt > 0) {
grub_memmove (addr, buf + filepos - saved_filepos, amt);
size -= amt;
addr += amt;
filepos += amt;
ret += amt;
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 {
saved_filepos += buf_read;
buf_read = 0;
}
if (size > 0 && ! buf_fill (0)) {
errnum = ERR_READ;
return 0;
}
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;
if (print_possibilities)
return 1;
ch = nul_terminate(dirname);
saved_name = (char *)&saved_tp;
sprintf(saved_name, "%s", dirname);
dirname[grub_strlen (dirname)] = ch;
rc = eb_pxenv_tftp_get_fsize(saved_name, arptable[ARP_SERVER].ipaddr,
arptable[ARP_GATEWAY].ipaddr, &filemax);
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) {
filemax = 0;
do {
filemax += buf_read;
buf_read = 0;
if (! buf_fill (0)) {
errnum = ERR_READ;
return 0;
}
} while (! buf_eof);
filemax += buf_read;
tftp_reopen_undi();
}
return (1);
}
static void
tftp_close_undi(void)
{
buf_read = 0;
buf_fill (1);
(void) eb_pxenv_tftp_close();
}