Patch from John Powers, adds multicast (rfc2090) and timeout (rfc2349)
options
diff --git a/patches/tftp_timeout_multicast.diff b/patches/tftp_timeout_multicast.diff
new file mode 100644
index 0000000..a76a18c
--- /dev/null
+++ b/patches/tftp_timeout_multicast.diff
@@ -0,0 +1,1053 @@
+Index: AUTHORS
+===================================================================
+RCS file: /var/cvs/busybox/AUTHORS,v
+retrieving revision 1.40
+diff -u -r1.40 AUTHORS
+--- a/AUTHORS	9 Oct 2003 21:19:21 -0000	1.40
++++ b/AUTHORS	5 Mar 2004 15:45:47 -0000
+@@ -92,6 +92,9 @@
+     Original author of BusyBox in 1995, 1996. Some of his code can 
+     still be found hiding here and there...
+ 
++John Powers <jpp@ti.com>
++    Added multicast option (rfc2090) and timeout option (rfc2349) to tftp.
++
+ Tim Riker <Tim@Rikers.org>
+     bug fixes, member of fan club
+ 
+Index: include/usage.h
+===================================================================
+RCS file: /var/cvs/busybox/include/usage.h,v
+retrieving revision 1.191
+diff -u -r1.191 usage.h
+--- a/include/usage.h	25 Feb 2004 10:35:55 -0000	1.191
++++ b/include/usage.h	5 Mar 2004 15:45:59 -0000
+@@ -2492,6 +2492,21 @@
+ #else
+   #define USAGE_TFTP_BS(a)
+ #endif
++#ifdef CONFIG_FEATURE_TFTP_TIMEOUT
++  #define USAGE_TFTP_TIMEOUT(a) a
++#else
++  #define USAGE_TFTP_TIMEOUT(a)
++#endif
++#ifdef CONFIG_FEATURE_TFTP_MULTICAST
++  #define USAGE_TFTP_MULTICAST(a) a
++#else
++  #define USAGE_TFTP_MULTICAST(a)
++#endif
++#ifdef CONFIG_FEATURE_TFTP_DEBUG
++  #define USAGE_TFTP_DEBUG(a) a
++#else
++  #define USAGE_TFTP_DEBUG(a)
++#endif
+ 
+ #define tftp_trivial_usage \
+ 	"[OPTION]... HOST [PORT]"
+@@ -2508,6 +2523,16 @@
+ 	) \
+ 	USAGE_TFTP_BS( \
+ 	"\t-b SIZE\tTransfer blocks of SIZE octets.\n" \
++	) \
++	USAGE_TFTP_TIMEOUT( \
++	"\t-T SEC\tClient timeout SEC seconds (default: 5).\n" \
++	"\t-t SEC\tServer timeout SEC seconds\n" \
++	) \
++	USAGE_TFTP_MULTICAST( \
++	"\t-m\tMulticast get file.\n" \
++	) \
++	USAGE_TFTP_DEBUG( \
++	"\t-D\tPrint debug messages.\n" \
+ 	)
+ #define time_trivial_usage \
+ 	"[OPTION]... COMMAND [ARGS...]"
+Index: networking/Config.in
+===================================================================
+RCS file: /var/cvs/busybox/networking/Config.in,v
+retrieving revision 1.27
+diff -u -r1.27 Config.in
+--- a/networking/Config.in	22 Feb 2004 12:25:47 -0000	1.27
++++ b/networking/Config.in	5 Mar 2004 15:45:59 -0000
+@@ -522,6 +522,13 @@
+ 	  Add support for the GET command within the TFTP client.  This allows
+ 	  a client to retrieve a file from a TFTP server.
+ 
++config CONFIG_FEATURE_TFTP_MULTICAST
++	bool "  Enable \"multicast\" option"
++	default n
++	depends on CONFIG_FEATURE_TFTP_GET
++	help
++	  Allow the client to receive multicast file transfers.
++
+ config CONFIG_FEATURE_TFTP_PUT
+ 	bool "  Enable \"put\" command"
+ 	default y
+@@ -531,12 +538,19 @@
+ 	  a client to transfer a file to a TFTP server.
+ 
+ config CONFIG_FEATURE_TFTP_BLOCKSIZE
+-	bool "  Enable \"blocksize\" command"
++	bool "  Enable \"blksize\" option"
+ 	default n
+ 	depends on CONFIG_TFTP
+ 	help
+ 	  Allow the client to specify the desired block size for transfers.
+ 
++config CONFIG_FEATURE_TFTP_TIMEOUT
++	bool "  Enable \"timeout\" option"
++	default n
++	depends on CONFIG_TFTP
++	help
++	  Allow the client to negotiate timeout option with server.
++
+ config CONFIG_FEATURE_TFTP_DEBUG
+ 	bool "  Enable debug"
+ 	default n
+Index: networking/tftp.c
+===================================================================
+RCS file: /var/cvs/busybox/networking/tftp.c,v
+retrieving revision 1.25
+diff -u -r1.25 tftp.c
+--- a/networking/tftp.c	5 Mar 2004 13:04:39 -0000	1.25
++++ b/networking/tftp.c	5 Mar 2004 15:46:00 -0000
+@@ -1,11 +1,26 @@
++/* vi: set sw=4 ts=4: */
+ /* ------------------------------------------------------------------------- */
+ /* tftp.c                                                                    */
++/* Copyright (c) 2003, 2004 Texas Instruments                                */
++/*                                                                           */
++/* This package is free software;  you can redistribute it and/or            */
++/* modify it under the terms of the license found in the file                */
++/* named COPYING that should have accompanied this file.                     */
++/*                                                                           */
++/* THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR             */
++/* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED            */
++/* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.       */
+ /*                                                                           */
+ /* A simple tftp client for busybox.                                         */
+ /* Tries to follow RFC1350.                                                  */
+ /* Only "octet" mode supported.                                              */
+ /* Optional blocksize negotiation (RFC2347 + RFC2348)                        */
+ /*                                                                           */
++/* New features added at Texas Instruments, October 2003                     */
++/* Author: John Powers                                                       */
++/* Multicast option: rfc2090                                                 */
++/* Timeout option: rfc2349                                                   */
++/*                                                                           */
+ /* Copyright (C) 2001 Magnus Damm <damm@opensource.se>                       */
+ /*                                                                           */
+ /* Parts of the code based on:                                               */
+@@ -46,8 +61,20 @@
+ 
+ #include "busybox.h"
+ 
++#if defined(CONFIG_FEATURE_TFTP_BLOCKSIZE) || defined(CONFIG_FEATURE_TFTP_MULTICAST) || defined(CONFIG_FEATURE_TFTP_TIMEOUT)
++  #define TFTP_OPTIONS
++#endif
++
+ //#define CONFIG_FEATURE_TFTP_DEBUG
+ 
++#ifdef CONFIG_FEATURE_TFTP_DEBUG
++	static void printtime(void);
++	#define dprintf(fmt...) if (debug) {printtime(); printf(fmt);}
++	int debug = 0;
++#else
++	#define dprintf(fmt...)
++#endif
++
+ #define TFTP_BLOCKSIZE_DEFAULT 512 /* according to RFC 1350, don't change */
+ #define TFTP_TIMEOUT 5             /* seconds */
+ 
+@@ -68,12 +95,24 @@
+ 	"Illegal TFTP operation",
+ 	"Unknown transfer ID",
+ 	"File already exists",
+-	"No such user"
++	"No such user",
++#ifdef TFTP_OPTIONS
++	"Unsupported option",
++#endif
+ };
+ 
+ const int tftp_cmd_get = 1;
+ const int tftp_cmd_put = 2;
+ 
++
++struct tftp_option {
++	int multicast;
++	int blksize;
++	int client_timeout;
++	int server_timeout;
++};
++
++
+ #ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
+ 
+ static int tftp_blocksize_check(int blocksize, int bufsize)  
+@@ -93,16 +132,158 @@
+ 	return blocksize;
+ }
+ 
++#endif
++
++#ifdef CONFIG_FEATURE_TFTP_TIMEOUT
++
++static int
++tftp_timeout_check(int timeout)
++{
++	/* Check if timeout seconds is valid:
++	 * RFC2349 says between 1 and 255.
++	 */
++
++	if (timeout < 1 || timeout > 255) {
++		bb_error_msg("bad timeout value");
++		return 0;
++	}
++	return timeout;
++}
++
++#endif
++
++#ifdef CONFIG_FEATURE_TFTP_MULTICAST
++static int
++tftp_multicast_check(const char *opt, char **phost, unsigned short *pport, int *pactive)
++{
++	/* Option string contains comma delimited addr,port,active.
++	 * addr = multicast IP address
++	 * port = port number
++	 * active = 1 if active client
++	 *          0 if passive client
++	 *
++	 * Addr and port will be empty fields when the server notifies a
++	 * passive client that it is now the active client.
++	 *
++	 * The host address string must be freed by the caller. Neither host
++	 * nor port will be set/changed if the input fields are empty.
++	 *
++	 * If any tokenization errors occur in the opt string, the host
++	 * address string is automatically freed.
++	 *
++	 * Return 0 if any tokenization error, 1 if all parameters are good.
++	 */
++
++	char *token = NULL;
++	char *parse_buf = NULL;
++	char *tokenv = NULL;
++	char *host = NULL;
++	int port;
++	int active;
++
++	parse_buf = bb_xstrdup(opt);
++
++	dprintf("multicast option=%s\n", opt);
++
++	/* IP address */
++	if ((token = strtok_r(parse_buf, ",", &tokenv)) == NULL) {
++		dprintf("tftp_multicast_check: cannot parse IP address from %s\n", parse_buf);
++		free(parse_buf);
++		return 0;
++	}
++	if (strlen(token) > 0)
++		*phost = host = bb_xstrdup(token);
++
++	/* Port */
++	if ((token = strtok_r(NULL, ",", &tokenv)) == NULL) {
++		dprintf("tftp_multicast_check: cannot parse port number from %s\n", tokenv);
++		goto token_error;
++	}
++	if (strlen(token) > 0) {
++		port = atoi(token);
++		if (port < 0 || port > 0xFFFF) {
++			dprintf("tftp_multicast_check: bad port number (%d)\n", port);
++			goto token_error;
++		}
++		*pport = htons(port);
++	}
++
++	/* Active/passive */
++	if ((token = strtok_r(NULL, ",", &tokenv)) == NULL) {
++		dprintf("tftp_multicast_check: cannot parse active/passive from %s\n", tokenv);
++		goto token_error;
++	}
++	active = atoi(token);
++	if (active != 0 && active != 1) {
++		dprintf("tftp_multicast_check: bad active/passive flag (%d)\n", active);
++		goto token_error;
++	}
++	*pactive = active;
++
++	free(parse_buf);
++	return 1;
++
++token_error:
++	free(parse_buf);
++	if (host != NULL)
++		free(host);
++	*phost = NULL;
++	return 0;
++
++}
++
++#define VECTOR_QUANTUM_WIDTH 8
++#define VECTOR_QUANTUM_ALL_ONES ((1<<VECTOR_QUANTUM_WIDTH)-1)
++
++static void inline
++bit_set(int bit, unsigned char *vector)
++{
++	int offset = bit / VECTOR_QUANTUM_WIDTH;
++	int mask = 1 << (bit % VECTOR_QUANTUM_WIDTH);
++	vector[offset] |= mask;
++}
++
++static int inline
++bit_isset(int bit, const unsigned char *vector)
++{
++	int offset = bit / VECTOR_QUANTUM_WIDTH;
++	int mask = 1 << (bit % VECTOR_QUANTUM_WIDTH);
++	return vector[offset] & mask ? 1 : 0;
++}
++
++static int inline
++bit_lmz(const unsigned char *vector)
++{
++	/* Return number of left-most zero in bit vector */
++	const unsigned char *vp = vector;
++	int i;
++	unsigned char velem;
++
++	while (*vp == VECTOR_QUANTUM_ALL_ONES)
++		vp++;
++	velem = *vp;
++	for (i = 0; i < VECTOR_QUANTUM_WIDTH; i++) {
++		if ((velem & (1 << i)) == 0)
++			break;
++	}
++	dprintf("bit_lmz: block=%d\n", (vp - vector)*VECTOR_QUANTUM_WIDTH + i);
++	return (vp - vector)*VECTOR_QUANTUM_WIDTH + i;
++}
++
++#endif
++
++
++
++#ifdef TFTP_OPTIONS
++
+ static char *tftp_option_get(char *buf, int len, char *option)  
+ {
+-        int opt_val = 0;
++	int opt_val = 0;
+ 	int opt_found = 0;
+ 	int k;
+-  
+-	while (len > 0) {
+ 
++	while (len > 0) {
+ 	        /* Make sure the options are terminated correctly */
+-
+ 	        for (k = 0; k < len; k++) {
+ 		        if (buf[k] == '\0') {
+ 			        break;
+@@ -117,9 +298,8 @@
+ 			if (strcasecmp(buf, option) == 0) {
+ 			        opt_found = 1;
+ 			}
+-		}      
+-		else {
+-		        if (opt_found) {
++		} else {
++			if (opt_found) {
+ 				return buf;
+ 			}
+ 		}
+@@ -138,7 +318,8 @@
+ #endif
+ 
+ static inline int tftp(const int cmd, const struct hostent *host,
+-	const char *remotefile, int localfd, const unsigned short port, int tftp_bufsize)
++	const char *remotefile, int localfd, const unsigned short port,
++	struct tftp_option *option)
+ {
+ 	const int cmd_get = cmd & tftp_cmd_get;
+ 	const int cmd_put = cmd & tftp_cmd_put;
+@@ -155,18 +336,29 @@
+ 	int len;
+ 	int opcode = 0;
+ 	int finished = 0;
+-	int timeout = bb_tftp_num_retries;
++	int retry = bb_tftp_num_retries;
+ 	unsigned short block_nr = 1;
+ 
+-#ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
+-	int want_option_ack = 0;
++#ifdef CONFIG_FEATURE_TFTP_MULTICAST
++	struct hostent *mchost;
++	struct sockaddr_in mcsa;
++	char *mchostname;
++	unsigned short mcport;
++	unsigned char *mcblockmap = NULL;
++	int master_client = 1;
++	int mcfd = -1;
++	int mcmaxblock = 0x10000;
++	int ack_oack = 0;
++#else
++	#define master_client 1
++    #define ack_oack 0
+ #endif
+ 
+ 	/* Can't use RESERVE_CONFIG_BUFFER here since the allocation
+ 	 * size varies meaning BUFFERS_GO_ON_STACK would fail */
+-	char *buf=xmalloc(tftp_bufsize + 4);
++	char *buf=xmalloc(option->blksize + 4);
+ 
+-	tftp_bufsize += 4;
++	int tftp_bufsize = option->blksize + 4;
+ 
+ 	if ((socketfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
+ 		bb_perror_msg("socket");
+@@ -183,15 +375,21 @@
+ 	memcpy(&sa.sin_addr, (struct in_addr *) host->h_addr,
+ 		   sizeof(sa.sin_addr));
+ 
+-	/* build opcode */
+-
+-	if (cmd_get) {
+-		opcode = TFTP_RRQ;
++#ifdef CONFIG_FEATURE_TFTP_MULTICAST
++	if (option->multicast) {
++		const int bmsize = 0x10000 / VECTOR_QUANTUM_WIDTH;
++		if ((mcfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
++			bb_perror_msg("multicast socket");
++			return EXIT_FAILURE;
++		}
++		mcblockmap = xmalloc(bmsize+1);
++		memset(mcblockmap, 0, bmsize+1);
+ 	}
++#endif
+ 
+-	if (cmd_put) {
+-		opcode = TFTP_WRQ;
+-	}
++	/* build opcode */
++
++	opcode = cmd_get ? TFTP_RRQ : TFTP_WRQ;
+ 
+ 	while (1) {
+ 
+@@ -203,7 +401,7 @@
+ 
+ 		cp += 2;
+ 
+-		/* add filename and mode */
++		/* First packet of file transfer includes file name, mode, and options */
+ 
+ 		if ((cmd_get && (opcode == TFTP_RRQ)) ||
+ 			(cmd_put && (opcode == TFTP_WRQ))) {
+@@ -223,7 +421,7 @@
+ 			}
+ 
+ 			if (too_long || ((&buf[tftp_bufsize - 1] - cp) < 6)) {
+-				bb_error_msg("too long remote-filename");
++				bb_error_msg("too long: remote filename");
+ 				break;
+ 			}
+ 
+@@ -238,8 +436,8 @@
+ 
+ 			if (len != TFTP_BLOCKSIZE_DEFAULT) {
+ 
+-			        if ((&buf[tftp_bufsize - 1] - cp) < 15) {
+-				        bb_error_msg("too long remote-filename");
++				if ((&buf[tftp_bufsize - 1] - cp) < 15) {
++					bb_error_msg("buffer too small for blksize option");
+ 					break;
+ 				}
+ 
+@@ -249,16 +447,65 @@
+ 				cp += 8;
+ 
+ 				cp += snprintf(cp, 6, "%d", len) + 1;
++			}
++#endif
++
++
++
++#ifdef CONFIG_FEATURE_TFTP_MULTICAST
++
++			if (option->multicast) {
++				if ((&buf[tftp_bufsize - 1] - cp) < 12) {
++					bb_error_msg("buffer too small for multicast option");
++					break;
++				}
++
++				/* add "multicast" option */
+ 
+-				want_option_ack = 1;
++				memcpy(cp, "multicast\0", 11);
++				cp += 11;
++
++				option->multicast = 0;	/* turn back on when server accepts option */
++				ack_oack = 1;	/* acknowledge OACK */
+ 			}
++
+ #endif
++
++#ifdef CONFIG_FEATURE_TFTP_TIMEOUT
++
++			if (option->server_timeout != TFTP_TIMEOUT) {
++				if ((&buf[tftp_bufsize - 1] - cp) < 12) {
++					bb_error_msg("buffer too small for timeout option");
++					break;
++				}
++
++				/* add "timeout" option */
++
++				memcpy(cp, "timeout", 8);
++				cp += 8;
++
++				cp += snprintf(cp, 4, "%d", option->server_timeout) + 1;
++			}
++#endif
++
+ 		}
+ 
+ 		/* add ack and data */
+ 
+-		if ((cmd_get && (opcode == TFTP_ACK)) ||
+-			(cmd_put && (opcode == TFTP_DATA))) {
++#ifdef CONFIG_FEATURE_TFTP_MULTICAST
++		else if (option->multicast && opcode == TFTP_ACK) {
++			if (master_client || ack_oack) {
++				int blocknum = bit_lmz(mcblockmap);
++				*((unsigned short *) cp) = htons(blocknum);
++				cp += 2;
++				if (blocknum >= mcmaxblock)
++					finished = 1;
++				dprintf("ack block %d/%d %s\n", blocknum, mcmaxblock, finished? "finished": "");
++			}
++		}
++#endif
++		else if ((cmd_get && opcode == TFTP_ACK) ||
++			(cmd_put && opcode == TFTP_DATA)) {
+ 
+ 			*((unsigned short *) cp) = htons(block_nr);
+ 
+@@ -275,7 +522,7 @@
+ 				}
+ 
+ 				if (len != (tftp_bufsize - 4)) {
+-					finished++;
++					finished = 1;
+ 				}
+ 
+ 				cp += len;
+@@ -283,82 +530,119 @@
+ 		}
+ 
+ 
+-		/* send packet */
++		/* send packet and receive reply */
+ 
+ 
+-		timeout = bb_tftp_num_retries;  /* re-initialize */
++		retry = bb_tftp_num_retries;  /* re-initialize */
+ 		do {
+-
++			int selectrc;
+ 			len = cp - buf;
+ 
+-#ifdef CONFIG_FEATURE_TFTP_DEBUG
+-			fprintf(stderr, "sending %u bytes\n", len);
+-			for (cp = buf; cp < &buf[len]; cp++)
+-				fprintf(stderr, "%02x ", (unsigned char)*cp);
+-			fprintf(stderr, "\n");
+-#endif
+-			if (sendto(socketfd, buf, len, 0,
+-					(struct sockaddr *) &sa, sizeof(sa)) < 0) {
+-				bb_perror_msg("send");
+-				len = -1;
+-				break;
+-			}
+-
++			/* send packet */
++			if ((len > 2) && (! option->multicast || master_client || ack_oack)) {
+ 
+-			if (finished && (opcode == TFTP_ACK)) {
+-				break;
++#ifdef CONFIG_FEATURE_TFTP_DEBUG
++				dprintf("sending %u bytes\n", len);
++				for (cp = buf; cp < &buf[len]; cp++)
++					if (debug)
++						printf("%02x ", *(unsigned char *)cp);
++				if (debug)
++					printf("\n");
++#endif
++#ifdef CONFIG_FEATURE_TFTP_MULTICAST
++				ack_oack = 0;
++#endif
++				if (sendto(socketfd, buf, len, 0, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
++					bb_perror_msg("send");
++					len = -1;
++					break;
++				}
++				if (finished && opcode == TFTP_ACK) {
++					break;
++				}
+ 			}
+ 
+-			/* receive packet */
++			/* receive reply packet */
+ 
+ 			memset(&from, 0, sizeof(from));
+ 			fromlen = sizeof(from);
+ 
+-			tv.tv_sec = TFTP_TIMEOUT;
++			tv.tv_sec = option->client_timeout;
+ 			tv.tv_usec = 0;
+ 
+ 			FD_ZERO(&rfds);
+ 			FD_SET(socketfd, &rfds);
++			dprintf("set to receive from socketfd (%d)\n", socketfd);
++#ifdef CONFIG_FEATURE_TFTP_MULTICAST
++			if (option->multicast) {
++				FD_SET(mcfd, &rfds);
++				dprintf("set to receive from mcfd (%d)\n", mcfd);
++			}
++#endif
+ 
+-			switch (select(FD_SETSIZE, &rfds, NULL, NULL, &tv)) {
+-			case 1:
+-				len = recvfrom(socketfd, buf, tftp_bufsize, 0,
+-						(struct sockaddr *) &from, &fromlen);
+-
+-				if (len < 0) {
+-					bb_perror_msg("recvfrom");
+-					break;
++			dprintf("select\n");
++			selectrc = select(FD_SETSIZE, &rfds, NULL, NULL, &tv);
++			if (selectrc > 0) {
++				/* A packet was received */
++				if (FD_ISSET(socketfd, &rfds)) { /* Unicast packet */
++					dprintf("from socketfd\n");
++					len = recvfrom(socketfd, buf, tftp_bufsize, 0, (struct sockaddr *) &from, &fromlen);
++
++					if (len < 0) {
++						bb_perror_msg("recvfrom");
++					} else {
++						if (sa.sin_port == port) {
++							sa.sin_port = from.sin_port;
++						}
++						if (sa.sin_port == from.sin_port) {
++							retry = 0;
++						} else {
++							/* bad packet */
++							/* discard the packet - treat as timeout */
++							retry = bb_tftp_num_retries;
++							bb_error_msg("timeout");
++						}
++					}
+ 				}
+ 
+-				timeout = 0;
+-
+-				if (sa.sin_port == port) {
+-					sa.sin_port = from.sin_port;
++#ifdef CONFIG_FEATURE_TFTP_MULTICAST
++				else if (option->multicast && FD_ISSET(mcfd, &rfds)) { /* Multicast packet */
++					dprintf("from mcfd\n");
++					len = recvfrom(mcfd, buf, tftp_bufsize, 0, (struct sockaddr *) &from, &fromlen);
++					if (len < 0) {
++						bb_perror_msg("multicast recvfrom");
++					} else {
++						if (mcsa.sin_port == mcport) {
++							mcsa.sin_port = from.sin_port;
++						}
++						if (mcsa.sin_port == from.sin_port) {
++							retry = 0;
++						} else {
++							retry = bb_tftp_num_retries;
++							bb_error_msg("multicast timeout");
++						}
++					}
+ 				}
+-				if (sa.sin_port == from.sin_port) {
+-					break;
+-				}
+-
+-				/* fall-through for bad packets! */
+-				/* discard the packet - treat as timeout */
+-				timeout = bb_tftp_num_retries;
++#endif
+ 
+-			case 0:
++			} else if (selectrc == 0) {
++				/* Time out */
++				dprintf("timeout\n");
+ 				bb_error_msg("timeout");
+ 
+-				timeout--;
+-				if (timeout == 0) {
++				retry--;
++				if (retry == 0) {
+ 					len = -1;
+ 					bb_error_msg("last timeout");
+ 				}
+-				break;
+-
+-			default:
++			} else {
++				/* Error condition */
++				dprintf("error\n");
+ 				bb_perror_msg("select");
+ 				len = -1;
+ 			}
+ 
+-		} while (timeout && (len >= 0));
++		} while (retry && len >= 0);
+ 
+ 		if ((finished) || (len < 0)) {
+ 			break;
+@@ -370,9 +654,8 @@
+ 		opcode = ntohs(*((unsigned short *) buf));
+ 		tmp = ntohs(*((unsigned short *) &buf[2]));
+ 
+-#ifdef CONFIG_FEATURE_TFTP_DEBUG
+-		fprintf(stderr, "received %d bytes: %04x %04x\n", len, opcode, tmp);
+-#endif
++		dprintf("received %d bytes: %04x %04x\n", len, opcode, tmp);
++		dprintf("master_client=%d\n", master_client);
+ 
+ 		if (opcode == TFTP_ERROR) {
+ 			char *msg = NULL;
+@@ -393,55 +676,116 @@
+ 			break;
+ 		}
+ 
+-#ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
+-		if (want_option_ack) {
++#ifdef TFTP_OPTIONS
+ 
+-			 want_option_ack = 0;
++		if (opcode == TFTP_OACK) {
+ 
+-		         if (opcode == TFTP_OACK) {
++			/* server seems to support options */
+ 
+-			         /* server seems to support options */
++			char *res;
++			
++			block_nr = 0;		/* acknowledge option packet with block number 0 */
++			opcode = cmd_put ? TFTP_DATA : TFTP_ACK;
+ 
+-			         char *res;
+ 
+-				 res = tftp_option_get(&buf[2], len-2, 
+-						       "blksize");
++#ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
++			res = tftp_option_get(&buf[2], len-2, "blksize");
+ 
+-				 if (res) {
+-				         int blksize = atoi(res);
+-			     
+-					 if (tftp_blocksize_check(blksize,
+-							   tftp_bufsize - 4)) {
++			if (res) {
++				int blksize = atoi(res);
+ 
+-					         if (cmd_put) {
+-				                         opcode = TFTP_DATA;
+-						 }
+-						 else {
+-				                         opcode = TFTP_ACK;
+-						 }
+-#ifdef CONFIG_FEATURE_TFTP_DEBUG
+-						 fprintf(stderr, "using blksize %u\n", blksize);
++				if (tftp_blocksize_check(blksize, tftp_bufsize - 4)) {
++					dprintf("using blksize %d\n", blksize);
++					tftp_bufsize = blksize + 4;
++					free(buf);
++					buf = xmalloc(tftp_bufsize);
++				} else {
++					bb_error_msg("bad blksize %d", blksize);
++					break;
++				}
++			}
+ #endif
+-					         tftp_bufsize = blksize + 4;
+-						 block_nr = 0;
+-						 continue;
+-					 }
+-				 }
+-				 /* FIXME:
+-				  * we should send ERROR 8 */
+-				 bb_error_msg("bad server option");
+-				 break;
+-			 }
+ 
+-			 bb_error_msg("warning: blksize not supported by server"
+-				   " - reverting to 512");
+ 
+-			 tftp_bufsize = TFTP_BLOCKSIZE_DEFAULT + 4;
++#ifdef CONFIG_FEATURE_TFTP_MULTICAST
++			res = tftp_option_get(&buf[2], len-2, "multicast");
++
++			if (res) {
++				ack_oack = 1;
++				if (tftp_multicast_check(res, &mchostname, &mcport, &master_client)) {
++					struct ip_mreq mreq;
++					struct in_addr mcaddr;
++
++					dprintf("using multicast\n");
++
++					mchost = xgethostbyname(mchostname);
++					if (mchost) {
++						memcpy(&mcaddr, mchost->h_addr, mchost->h_length);
++						if (! IN_MULTICAST(ntohl(mcaddr.s_addr))) {
++							bb_error_msg("bad multicast address: %s", mchostname);
++							break;
++						}
++					} else {
++						bb_error_msg("bad multicast address: %s", mchostname);
++						break;
++					}
++
++					memset(&mcsa, 0, sizeof(mcsa));
++					mcsa.sin_family = AF_INET;
++					mcsa.sin_addr.s_addr = htonl(INADDR_ANY);
++					mcsa.sin_port = mcport;
++
++					bind(mcfd, (struct sockaddr *)&mcsa, sizeof(mcsa));
++
++					mreq.imr_multiaddr.s_addr = mcaddr.s_addr;
++					mreq.imr_interface.s_addr = htonl(INADDR_ANY);
++
++					if (setsockopt(mcfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
++					{
++						bb_error_msg("setsockopt");
++						break;
++					}
++
++					option->multicast = 1;
++				} else {
++					bb_error_msg("bad multicast option value: %s", res);
++					break;
++				}
++			}
++#endif
++
+ 		}
++		else
+ #endif
+ 
+ 		if (cmd_get && (opcode == TFTP_DATA)) {
+ 
++#ifdef CONFIG_FEATURE_TFTP_MULTICAST
++			if (option->multicast) {
++				int bn = tmp - 1;
++				/* Do I need this block? */
++				if (! bit_isset(bn, mcblockmap)) {
++					lseek(localfd, bn*(tftp_bufsize-4), SEEK_SET);
++					len = write(localfd, &buf[4], len-4);
++					if (len < 0) {
++						bb_perror_msg("write");
++						break;
++					}
++					bit_set(bn, mcblockmap);
++					if (len != (tftp_bufsize-4)) {
++						mcmaxblock = tmp;
++						dprintf("mcmaxblock=%d, (len(%d) != tftp_bufsize-4(%d))\n", mcmaxblock, len, tftp_bufsize-4);
++					}
++					opcode = TFTP_ACK;
++				}
++				/* Do not acknowledge block if I already have a copy of the block. A situation can arise when the server
++				 * and client timeout nearly simultaneously. The server retransmits the block at the same time the client
++				 * re-requests the block. From then on out, each block is transmitted twice--not a good use of bandwidth.
++				 */
++			}
++			else
++#endif
++
+ 			if (tmp == block_nr) {
+ 			    
+ 				len = write(localfd, &buf[4], len - 4);
+@@ -452,15 +796,14 @@
+ 				}
+ 
+ 				if (len != (tftp_bufsize - 4)) {
+-					finished++;
++					finished = 1;
+ 				}
+ 
+ 				opcode = TFTP_ACK;
+-				continue;
+ 			}
+ 		}
+ 
+-		if (cmd_put && (opcode == TFTP_ACK)) {
++		else if (cmd_put && opcode == TFTP_ACK) {
+ 
+ 			if (tmp == (unsigned short)(block_nr - 1)) {
+ 				if (finished) {
+@@ -468,15 +811,19 @@
+ 				}
+ 
+ 				opcode = TFTP_DATA;
+-				continue;
+ 			}
+ 		}
+ 	}
+ 
+ #ifdef CONFIG_FEATURE_CLEAN_UP
+ 	close(socketfd);
++	free(buf);
++
++#ifdef CONFIG_FEATURE_TFTP_MULTICAST
++	if (mcblockmap != NULL)
++		free(mcblockmap);
++#endif
+ 
+-        free(buf);
+ #endif
+ 
+ 	return finished ? EXIT_SUCCESS : EXIT_FAILURE;
+@@ -487,13 +834,18 @@
+ 	struct hostent *host = NULL;
+ 	char *localfile = NULL;
+ 	char *remotefile = NULL;
+-	int port;
++	unsigned short port;
+ 	int cmd = 0;
+ 	int fd = -1;
+ 	int flags = 0;
+ 	int opt;
+ 	int result;
+-	int blocksize = TFTP_BLOCKSIZE_DEFAULT;
++	struct tftp_option option = {
++		.multicast		= 0,
++		.blksize		= TFTP_BLOCKSIZE_DEFAULT,
++		.client_timeout	= TFTP_TIMEOUT,
++		.server_timeout	= TFTP_TIMEOUT,
++	};
+ 
+ 	/* figure out what to pass to getopt */
+ 
+@@ -515,13 +867,45 @@
+ #define PUT 
+ #endif
+ 
+-	while ((opt = getopt(argc, argv, BS GET PUT "l:r:")) != -1) {
++#ifdef CONFIG_FEATURE_TFTP_TIMEOUT
++#define TO "T:t:"
++#else
++#define TO
++#endif
++
++#ifdef CONFIG_FEATURE_TFTP_MULTICAST
++#define MC "m"
++#else
++#define MC
++#endif
++
++#ifdef CONFIG_FEATURE_TFTP_DEBUG
++#define DB "D"
++#else
++#define DB
++#endif
++
++	while ((opt = getopt(argc, argv, BS GET PUT TO MC DB "l:r:")) != -1) {
+ 		switch (opt) {
+ #ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
+ 		case 'b':
+-			blocksize = atoi(optarg);
+-			if (!tftp_blocksize_check(blocksize, 0)) {
+-                                return EXIT_FAILURE;
++			option.blksize = atoi(optarg);
++			if (!tftp_blocksize_check(option.blksize, 0)) {
++				return EXIT_FAILURE;
++			}
++			break;
++#endif
++#ifdef CONFIG_FEATURE_TFTP_TIMEOUT
++		case 'T':
++			option.client_timeout = atoi(optarg);
++			if (!tftp_timeout_check(option.client_timeout)) {
++				return EXIT_FAILURE;
++			}
++			break;
++		case 't':
++			option.server_timeout = atoi(optarg);
++			if (!tftp_timeout_check(option.server_timeout)) {
++				return EXIT_FAILURE;
+ 			}
+ 			break;
+ #endif
+@@ -537,18 +921,34 @@
+ 			flags = O_RDONLY;
+ 			break;
+ #endif
++#ifdef CONFIG_FEATURE_TFTP_MULTICAST
++		case 'm':
++			option.multicast = 1;	/* receive multicast file */
++			break;
++#endif
++#ifdef CONFIG_FEATURE_TFTP_DEBUG
++		case 'D':
++			debug = 1;
++			break;
++#endif
+ 		case 'l': 
+ 			localfile = bb_xstrdup(optarg);
+ 			break;
+ 		case 'r':
+ 			remotefile = bb_xstrdup(optarg);
+ 			break;
++		default:
++			bb_show_usage();
+ 		}
+ 	}
+ 
+ 	if ((cmd == 0) || (optind == argc)) {
+ 		bb_show_usage();
+ 	}
++	if (cmd == tftp_cmd_put && option.multicast) {
++		fprintf(stderr, "Multicast (-m) invalid option with put (-p) command\n");
++		exit(EXIT_FAILURE);
++	}
+ 	if(localfile && strcmp(localfile, "-") == 0) {
+ 	    fd = fileno((cmd==tftp_cmd_get)? stdout : stdin);
+ 	}
+@@ -566,14 +966,12 @@
+ 	host = xgethostbyname(argv[optind]);
+ 	port = bb_lookup_port(argv[optind + 1], "udp", 69);
+ 
+-#ifdef CONFIG_FEATURE_TFTP_DEBUG
+-	fprintf(stderr, "using server \"%s\", remotefile \"%s\", "
++	dprintf("using server \"%s\", remotefile \"%s\", "
+ 		"localfile \"%s\".\n",
+ 		inet_ntoa(*((struct in_addr *) host->h_addr)),
+ 		remotefile, localfile);
+-#endif
+ 
+-	result = tftp(cmd, host, remotefile, fd, port, blocksize);
++	result = tftp(cmd, host, remotefile, fd, port, &option);
+ 
+ #ifdef CONFIG_FEATURE_CLEAN_UP
+ 	if (!(fd == fileno(stdout) || fd == fileno(stdin))) {
+@@ -582,3 +980,18 @@
+ #endif
+ 	return(result);
+ }
++
++
++#ifdef CONFIG_FEATURE_TFTP_DEBUG
++
++#include <sys/time.h>
++
++static void
++printtime(void)
++{
++	struct timeval tv;
++	gettimeofday(&tv, NULL);
++	printf("%11lu.%06lu ", tv.tv_sec, tv.tv_usec);
++}
++
++#endif