changeset 2453:9ccbf76434a0

2007-12-21 Oyvind Harboe <oyvind.harboe@zylin.com> * src/tftp_client.c, include/arpa/tftp.h, cdl/net.cdl: tftp blksize negotiation support. >512 byte block sizes improves tftp GET performance. Switched to memcpy(), which matters for larger blocks.
author asl
date Sun, 06 Jan 2008 11:13:51 +0000
parents 5298598f7174
children 1f046a6b2a6c
files packages/net/common/current/ChangeLog packages/net/common/current/cdl/net.cdl packages/net/common/current/include/arpa/tftp.h packages/net/common/current/src/tftp_client.c
diffstat 4 files changed, 378 insertions(+), 262 deletions(-) [+]
line wrap: on
line diff
--- a/packages/net/common/current/ChangeLog
+++ b/packages/net/common/current/ChangeLog
@@ -1,3 +1,10 @@
+2007-12-21  Oyvind Harboe <oyvind.harboe@zylin.com>
+
+	* src/tftp_client.c, include/arpa/tftp.h, cdl/net.cdl: tftp
+	blksize negotiation support. >512 byte block sizes improves tftp
+	GET performance.  Switched to memcpy(), which matters for larger
+	blocks.
+	 
 2007-01-15  Gary Thomas  <gary@mlbassoc.com>
 
 	* src/dhcp_support.c (dhcp_mgt_entry): Better handling when restarting
--- a/packages/net/common/current/cdl/net.cdl
+++ b/packages/net/common/current/cdl/net.cdl
@@ -167,6 +167,31 @@ cdl_package CYGPKG_NET {
             threads can have precedence over TFTP server processing."
         }
 
+		cdl_component CYGPKG_NET_TFTPD_CLIENT_BIG_PACKET {
+		      display "Extension to allow negotiation of big packets"
+		      requires CYGINT_ISO_STDIO_FORMATTED_IO
+		      flavor bool
+		      default_value 0
+		      description   "
+                          Implements RFC 2348, an optional extension
+		          to the TFTP protocol to allow the client and
+		          server to negotiate to use bigger
+		          packets. This can make upload/download
+		          faster"
+		
+		      cdl_option  CYGPKG_NET_TFTPD_CLIENT_BIG_PACKET_SIZE {
+		          display  "Packet size to negotiate"
+		          flavor    data
+		          default_value 512
+		          legal_values 512 to 65464
+		          description "
+		              Size of the packets to negotiate. In an error 
+                              free environment, bigger packets will result 
+                              in faster transfers."
+		      }
+		}
+		
+ 
         cdl_option CYGPKG_NET_TFTPD_THREAD_STACK_SIZE {
             display "Stack size for TFTP threads."
             flavor  data
--- a/packages/net/common/current/include/arpa/tftp.h
+++ b/packages/net/common/current/include/arpa/tftp.h
@@ -70,6 +70,7 @@
 #define	DATA	03			/* data packet */
 #define	ACK	04			/* acknowledgement */
 #define	ERROR	05			/* error code */
+#define	OACK	06			/* option acknowledge */
 
 struct	tftphdr {
 	short	th_opcode;		/* packet type */
--- a/packages/net/common/current/src/tftp_client.c
+++ b/packages/net/common/current/src/tftp_client.c
@@ -57,6 +57,8 @@
 #include <network.h>
 #include <arpa/tftp.h>
 #include <tftp_support.h>
+#include <stdlib.h>
+#include <stdio.h>
 
 #define min(x,y) (x<y ? x : y)
 
@@ -66,14 +68,20 @@
 // On error, *err will hold the reason.
 // This version uses the server name. This can be a name for DNS lookup
 // or a dotty or colony number format for IPv4 or IPv6.
-int tftp_client_get(const char * const filename,
-		    const char * const server,
-		    const int port,
-		    char *buf,
-		    int len,
-		    const int mode,
-		    int * const err) {
-		    
+static int tftp_client_get_inner(char *data,
+                    const char * const filename,
+                    const char * const server,
+                    const int port,
+                    char *buf,
+                    int len,
+                    const int mode,
+                    int * const err
+#ifdef CYGPKG_NET_TFTPD_CLIENT_BIG_PACKET
+                    ,int negotiate
+#endif
+                    ) {
+        
+        int blksize=SEGSIZE;
     int result = 0;
     int s=-1;
     int actual_len, data_len;
@@ -85,7 +93,6 @@ int tftp_client_get(const char * const f
     int error;
 
     struct sockaddr local_addr, from_addr;
-    char data[SEGSIZE+sizeof(struct tftphdr)];
     struct tftphdr *hdr = (struct tftphdr *)data;
     const char *fp;
     char *cp, *bp;
@@ -112,6 +119,16 @@ int tftp_client_get(const char * const f
     }
     while (*fp) *cp++ = *fp++;
     *cp++ = '\0';
+#ifdef CYGPKG_NET_TFTPD_CLIENT_BIG_PACKET
+    if (negotiate) {
+        fp="blksize";
+        while (*fp) 
+          *cp++ = *fp++;
+        *cp++ = '\0';
+        cp+=sprintf(cp, "%d", CYGPKG_NET_TFTPD_CLIENT_BIG_PACKET_SIZE);
+        *cp++ = '\0';
+    }
+#endif
 
     memset(&hints,0,sizeof(hints));
     hints.ai_family = PF_UNSPEC;
@@ -124,74 +141,74 @@ int tftp_client_get(const char * const f
     addrinfo = res;
     while (addrinfo) {
       s = socket(addrinfo->ai_family, addrinfo->ai_socktype, 
-		 addrinfo->ai_protocol);
+                 addrinfo->ai_protocol);
       if (s >= 0) {
-	memcpy(&local_addr,addrinfo->ai_addr,addrinfo->ai_addrlen);
-	switch(addrinfo->ai_addr->sa_family) {
-	case AF_INET: {
-	  struct sockaddr_in * saddr = 
-	    (struct sockaddr_in *) addrinfo->ai_addr;
-	  struct sockaddr_in * laddr = 
-	    (struct sockaddr_in *) &local_addr;
-	  if (port) {
-	    saddr->sin_port = htons(port);
-	  }
-	  laddr->sin_port = htons(get_port++);
-	  laddr->sin_addr.s_addr = INADDR_ANY;
-	  break;
-	}
+        memcpy(&local_addr,addrinfo->ai_addr,addrinfo->ai_addrlen);
+        switch(addrinfo->ai_addr->sa_family) {
+        case AF_INET: {
+          struct sockaddr_in * saddr = 
+            (struct sockaddr_in *) addrinfo->ai_addr;
+          struct sockaddr_in * laddr = 
+            (struct sockaddr_in *) &local_addr;
+          if (port) {
+            saddr->sin_port = htons(port);
+          }
+          laddr->sin_port = htons(get_port++);
+          laddr->sin_addr.s_addr = INADDR_ANY;
+          break;
+        }
 #ifdef CYGPKG_NET_INET6
-	case AF_INET6: {
-	  struct sockaddr_in6 * saddr = 
-	    (struct sockaddr_in6 *) addrinfo->ai_addr;
-	  struct sockaddr_in6 * laddr = 
-	    (struct sockaddr_in6 *) &local_addr;
-	  if (port) {
-	    saddr->sin6_port = htons(port);
-	  }
-	  laddr->sin6_port = htons(get_port++);
-	  laddr->sin6_addr = in6addr_any;
-	  break;
-	}
+        case AF_INET6: {
+          struct sockaddr_in6 * saddr = 
+            (struct sockaddr_in6 *) addrinfo->ai_addr;
+          struct sockaddr_in6 * laddr = 
+            (struct sockaddr_in6 *) &local_addr;
+          if (port) {
+            saddr->sin6_port = htons(port);
+          }
+          laddr->sin6_port = htons(get_port++);
+          laddr->sin6_addr = in6addr_any;
+          break;
+        }
 #endif
-	default:
-	  *err = TFTP_NETERR;
-	  goto out;
-	}
-
-	if (bind(s,&local_addr,addrinfo->ai_addrlen) < 0) {
+        default:
           *err = TFTP_NETERR;
           goto out;
         }
-	
-	// Send request
-	if (sendto(s, data, (int)(cp-data), 0, 
-		   addrinfo->ai_addr, 
-		   addrinfo->ai_addrlen) < 0) {
-	  // Problem sending request
-	  *err = TFTP_NETERR;
-	  goto nextaddr;
-	}
 
-	// Read data
-	bp = buf;
-	while (true) {
-	  timeout.tv_sec = TFTP_TIMEOUT_PERIOD;
-	  timeout.tv_usec = 0;
-	  FD_ZERO(&fds);
-	  FD_SET(s, &fds);
-	  if (select(s+1, &fds, 0, 0, &timeout) <= 0) {
+        if (bind(s,&local_addr,addrinfo->ai_addrlen) < 0) {
+          *err = TFTP_NETERR;
+          goto out;
+        }
+        
+        // Send request
+        if (sendto(s, data, (int)(cp-data), 0, 
+                   addrinfo->ai_addr, 
+                   addrinfo->ai_addrlen) < 0) {
+          // Problem sending request
+          *err = TFTP_NETERR;
+          goto nextaddr;
+        }
+
+        // Read data
+        bp = buf;
+        while (true) {
+          timeout.tv_sec = TFTP_TIMEOUT_PERIOD;
+          timeout.tv_usec = 0;
+          FD_ZERO(&fds);
+          FD_SET(s, &fds);
+          if (select(s+1, &fds, 0, 0, &timeout) <= 0) {
             total_timeouts++;
             if ((last_good_block == 0) && (total_timeouts > TFTP_RETRIES_MAX)) {
-	      // Timeout - no data received. Probably no server.
-	      *err = TFTP_TIMEOUT;
-	      goto nextaddr;
+              // Timeout - no data received. Probably no server.
+              *err = TFTP_TIMEOUT;
+              goto nextaddr;
             }
-	    if (total_timeouts > TFTP_TIMEOUT_MAX) {
+            if (total_timeouts > TFTP_TIMEOUT_MAX) {
               // Timeout - have received data. Network problem?
               *err = TFTP_TIMEOUT;
               goto out;
-	    }
+            }
             
             if (last_good_block == 0 ) {
               // Send request
@@ -213,72 +230,92 @@ int tftp_client_get(const char * const f
                 goto out;
               }
             }
-	  } else {
-	    recv_len = sizeof(data);
-	    from_len = sizeof(from_addr);
-	    if ((data_len = recvfrom(s, &data, recv_len, 0, 
-				     &from_addr, &from_len)) < 0) {
-	      // What happened?
-	      *err = TFTP_NETERR;
-	      goto out;
-	    }
-	    if (ntohs(hdr->th_opcode) == DATA) {
-	      actual_len = 0;
-	      if (ntohs(hdr->th_block) == (last_good_block+1)) {
-		// Consume this data
-		cp = hdr->th_data;
-		data_len -= 4;  /* Sizeof TFTP header */
-		actual_len = data_len;
-		result += actual_len;
-		while (data_len-- > 0) {
-		  if (len-- > 0) {
-		    *bp++ = *cp++;
-		  } else {
-		    // Buffer overflow
-		    *err = TFTP_TOOLARGE;
-		    goto out;
-		  }
-		}
-		last_good_block++;
-	      } else {
-                // To prevent an out-of-sequence packet from
-                // terminating transmission prematurely, set
-                // actual_len to a full size packet.
-		actual_len = SEGSIZE;
-	      }
-	      // Send out the ACK
-	      hdr->th_opcode = htons(ACK);
-	      hdr->th_block = htons(last_good_block);
-	      if (sendto(s, data, 4 /* FIXME */, 0, 
-			 &from_addr, from_len) < 0) {
-		// Problem sending request
-		*err = TFTP_NETERR;
-		goto out;
-	      }
-              // A short packet marks the end of the file.
-	      if ((actual_len >= 0) && (actual_len < SEGSIZE)) {
-		// End of data
-		close(s);
-		freeaddrinfo(res);
-		return result;
-	      }
-	    } else 
-	      if (ntohs(hdr->th_opcode) == ERROR) {
-		*err = ntohs(hdr->th_code);
-		goto out;
-	      } else {
-		// What kind of packet is this?
-                *err = TFTP_PROTOCOL;
-		goto out;
-	      }
-	  }
-	}
+          } else {
+            recv_len = blksize+sizeof(struct tftphdr);
+            from_len = sizeof(from_addr);
+            if ((data_len = recvfrom(s, data, recv_len, 0, 
+                                     &from_addr, &from_len)) < 0) {
+              // What happened?
+              *err = TFTP_NETERR;
+              goto out;
+            }
+#ifdef CYGPKG_NET_TFTPD_CLIENT_BIG_PACKET
+            if (ntohs(hdr->th_opcode) == OACK) {
+              // We can have only *one* option, the one we sent..
+              if (strncmp(data+2, "blksize", data_len)==0) {
+                blksize=atol(data+2+strlen("blksize")+1);
+              } else {
+                // option ignored, use default.
+              }
+              // Send out the ACK
+              hdr->th_opcode = htons(ACK);
+              hdr->th_block = htons(last_good_block);
+              if (sendto(s, data, 4 /* FIXME */, 0, 
+                         &from_addr, from_len) < 0) {
+                // Problem sending request
+                *err = TFTP_NETERR;
+                goto out;
+              }
+            } else
+#endif
+              if (ntohs(hdr->th_opcode) == DATA) {
+                actual_len = 0;
+                if (ntohs(hdr->th_block) == (last_good_block+1)) {
+                  // Consume this data
+                  cp = hdr->th_data;
+                  data_len -= 4;  /* Sizeof TFTP header */
+                  actual_len = data_len;
+                  result += actual_len;
+                  if (len<data_len)
+                    {
+                      // Buffer overflow
+                      *err = TFTP_TOOLARGE;
+                      goto out;
+                    }
+                  memcpy(bp, cp, data_len);
+                  bp+=data_len;
+                  len-=data_len;
+                  last_good_block++;
+                } else {
+                  // To prevent an out-of-sequence packet from
+                  // terminating transmission prematurely, set
+                  // actual_len to a full size packet.
+                  actual_len = blksize;
+                }
+                // Send out the ACK
+                hdr->th_opcode = htons(ACK);
+                hdr->th_block = htons(last_good_block);
+                if (sendto(s, data, 4 /* FIXME */, 0, 
+                           &from_addr, from_len) < 0) {
+                  // Problem sending request
+                  *err = TFTP_NETERR;
+                  goto out;
+                }
+                // A short packet marks the end of the file.
+                /* 4 = Sizeof TFTP header */
+                if ((actual_len >= 0) && (actual_len < blksize)) {
+                  // End of data
+                  close(s);
+                  freeaddrinfo(res);
+                  return result;
+                }
+              } else 
+                if (ntohs(hdr->th_opcode) == ERROR) {
+                  *err = ntohs(hdr->th_code);
+                  goto out;
+                } else {
+                  // What kind of packet is this?
+                  *err = TFTP_PROTOCOL;
+                  goto out;
+                }
+          }
+        }
       }
       // If we got here, it means there was a problem connecting to the 
       // server. Try the next address returned by getaddrinfo
     nextaddr:
       if (-1 != s) {
-	close(s);
+        close(s);
       }
       addrinfo=addrinfo->ai_next;
     }
@@ -290,6 +327,52 @@ int tftp_client_get(const char * const f
     freeaddrinfo(res);
     return -1;
 }
+
+
+int tftp_client_get(const char * const filename,
+                    const char * const server,
+                    const int port,
+                    char *buf,
+                    int len,
+                    const int mode,
+                    int * const err) {
+        int result;
+#ifdef CYGPKG_NET_TFTPD_CLIENT_BIG_PACKET
+        char *data = malloc(CYGPKG_NET_TFTPD_CLIENT_BIG_PACKET_SIZE+
+                            sizeof(struct tftphdr));
+        if (data==NULL) {
+          *err=TFTP_ENOSPACE;
+          return -1;
+        }
+#else
+        char data[SEGSIZE+sizeof(struct tftphdr)];
+#endif
+        result=tftp_client_get_inner(data, filename, server, 
+                                     port, buf, len, mode, err
+#ifdef CYGPKG_NET_TFTPD_CLIENT_BIG_PACKET
+                                     ,1
+#endif
+                                     );
+        if (result<0)
+          {
+#ifdef CYGPKG_NET_TFTPD_CLIENT_BIG_PACKET
+            // try without negotiating packet size. The serves that do
+            // not support options negotiation may or may not ignore the
+            // options. If they return an error in the case of options
+            // this code path will try without packet size negotiation.
+            result=tftp_client_get_inner(data, filename, server, 
+                                         port, buf, len, mode, err,
+                                         0);
+#endif
+          }
+        
+#ifdef CYGPKG_NET_TFTPD_CLIENT_BIG_PACKET
+        free(data);
+#endif
+        
+        return result;
+}
+
 //
 // Read a file from a host into a local buffer.  Returns the
 // number of bytes actually read, or (-1) if an error occurs.
@@ -309,7 +392,7 @@ tftp_get(const char * const filename,
   int port;
 
   ret = inet_ntop(AF_INET, (void *)&server->sin_addr, 
-		  server_name, sizeof(server_name));
+                  server_name, sizeof(server_name));
   if (NULL == ret) {
       *err = TFTP_NETERR;
       return -1;
@@ -335,7 +418,7 @@ tftp_put(const char * const filename,
   int port;
 
   ret = inet_ntop(AF_INET, (void *)&server->sin_addr, 
-		  server_name, sizeof(server_name));
+                  server_name, sizeof(server_name));
   if (NULL == ret) {
       *err = TFTP_NETERR;
       return -1;
@@ -352,12 +435,12 @@ tftp_put(const char * const filename,
 // This version uses the server name. This can be a name for DNS lookup
 // or a dotty or colony number format for IPv4 or IPv6.
 int tftp_client_put(const char * const filename,
-		    const char * const server,
-		    const int port,
-		    const char *buf,
-		    int len,
-		    const int mode,
-		    int * const err) {
+                    const char * const server,
+                    const int port,
+                    const char *buf,
+                    int len,
+                    const int mode,
+                    int * const err) {
 
     int result = 0;
     int s = -1, actual_len, data_len;
@@ -390,177 +473,177 @@ int tftp_client_put(const char * const f
     addrinfo = res;
     while (addrinfo) {
       s = socket(addrinfo->ai_family, addrinfo->ai_socktype,
-		 addrinfo->ai_protocol);
+                 addrinfo->ai_protocol);
       if (s >= 0) {
-	memcpy(&local_addr,addrinfo->ai_addr,addrinfo->ai_addrlen);
-	switch(addrinfo->ai_addr->sa_family) {
-	case AF_INET: {
-	  struct sockaddr_in * saddr = 
-	    (struct sockaddr_in *) addrinfo->ai_addr;
-	  struct sockaddr_in * laddr = 
-	    (struct sockaddr_in *) &local_addr;
-	  if (port) {
-	    saddr->sin_port = htons(port);
-	  }
-	  laddr->sin_port = htons(put_port++);
-	  laddr->sin_addr.s_addr = INADDR_ANY;
-	  break;
-	}
+        memcpy(&local_addr,addrinfo->ai_addr,addrinfo->ai_addrlen);
+        switch(addrinfo->ai_addr->sa_family) {
+        case AF_INET: {
+          struct sockaddr_in * saddr = 
+            (struct sockaddr_in *) addrinfo->ai_addr;
+          struct sockaddr_in * laddr = 
+            (struct sockaddr_in *) &local_addr;
+          if (port) {
+            saddr->sin_port = htons(port);
+          }
+          laddr->sin_port = htons(put_port++);
+          laddr->sin_addr.s_addr = INADDR_ANY;
+          break;
+        }
 #ifdef CYGPKG_NET_INET6
-	case AF_INET6: {
-	  struct sockaddr_in6 * saddr = 
-	    (struct sockaddr_in6 *) addrinfo->ai_addr;
-	  struct sockaddr_in6 * laddr = 
-	    (struct sockaddr_in6 *) &local_addr;
-	  if (port) {
-	    saddr->sin6_port = htons(port);
-	  }
-	  laddr->sin6_port = htons(put_port++);
-	  laddr->sin6_addr = in6addr_any;
-	  break;
-	}
+        case AF_INET6: {
+          struct sockaddr_in6 * saddr = 
+            (struct sockaddr_in6 *) addrinfo->ai_addr;
+          struct sockaddr_in6 * laddr = 
+            (struct sockaddr_in6 *) &local_addr;
+          if (port) {
+            saddr->sin6_port = htons(port);
+          }
+          laddr->sin6_port = htons(put_port++);
+          laddr->sin6_addr = in6addr_any;
+          break;
+        }
 #endif
-	default:
-	  *err = TFTP_NETERR;
-	  goto out;
-	}
-	if (bind(s, 
-		 (struct sockaddr *)&local_addr, 
-		 addrinfo->ai_addrlen) < 0) {
-	  // Problem setting up my end
-	  *err = TFTP_NETERR;
-	  goto out;
-	}
+        default:
+          *err = TFTP_NETERR;
+          goto out;
+        }
+        if (bind(s, 
+                 (struct sockaddr *)&local_addr, 
+                 addrinfo->ai_addrlen) < 0) {
+          // Problem setting up my end
+          *err = TFTP_NETERR;
+          goto out;
+        }
 
-	while (1) {
-	  // Create initial request
-	  hdr->th_opcode = htons(WRQ);  // Create/write file
-	  cp = (char *)&hdr->th_stuff;
-	  fp = filename;
-	  while (*fp) *cp++ = *fp++;
-	  *cp++ = '\0';
-	  if (mode == TFTP_NETASCII) {
+        while (1) {
+          // Create initial request
+          hdr->th_opcode = htons(WRQ);  // Create/write file
+          cp = (char *)&hdr->th_stuff;
+          fp = filename;
+          while (*fp) *cp++ = *fp++;
+          *cp++ = '\0';
+          if (mode == TFTP_NETASCII) {
             fp = "NETASCII";
-	  } else if (mode == TFTP_OCTET) {
+          } else if (mode == TFTP_OCTET) {
             fp = "OCTET";
-	  } else {
+          } else {
             *err = TFTP_INVALID;
-	    goto out;
-	  }
-	  while (*fp) *cp++ = *fp++;
-	  *cp++ = '\0';
-	  // Send request
-	  if (sendto(s, data, (int)(cp-data), 0, 
-		     addrinfo->ai_addr,
-		     addrinfo->ai_addrlen) < 0) {
+            goto out;
+          }
+          while (*fp) *cp++ = *fp++;
+          *cp++ = '\0';
+          // Send request
+          if (sendto(s, data, (int)(cp-data), 0, 
+                     addrinfo->ai_addr,
+                     addrinfo->ai_addrlen) < 0) {
             // Problem sending request
             *err = TFTP_NETERR;
-	    goto nextaddr;
-	  }
-	  // Wait for ACK
-	  timeout.tv_sec = TFTP_TIMEOUT_PERIOD;
-	  timeout.tv_usec = 0;
-	  FD_ZERO(&fds);
-	  FD_SET(s, &fds);
-	  if (select(s+1, &fds, 0, 0, &timeout) <= 0) {
+            goto nextaddr;
+          }
+          // Wait for ACK
+          timeout.tv_sec = TFTP_TIMEOUT_PERIOD;
+          timeout.tv_usec = 0;
+          FD_ZERO(&fds);
+          FD_SET(s, &fds);
+          if (select(s+1, &fds, 0, 0, &timeout) <= 0) {
             if (++total_timeouts > TFTP_RETRIES_MAX) {
-	      // Timeout - no ACK received
-	      *err = TFTP_TIMEOUT;
-	      goto nextaddr;
+              // Timeout - no ACK received
+              *err = TFTP_TIMEOUT;
+              goto nextaddr;
             }
-	  } else {
+          } else {
             recv_len = sizeof(data);
             from_len = sizeof(from_addr);
             if ((data_len = recvfrom(s, &data, recv_len, 0, 
                                      &from_addr, &from_len)) < 0) {
-	      // What happened?
-	      *err = TFTP_NETERR;
-	      goto out;
+              // What happened?
+              *err = TFTP_NETERR;
+              goto out;
             }
             if (ntohs(hdr->th_opcode) == ACK) {
-	      // Write request accepted - start sending data
-	      break;
+              // Write request accepted - start sending data
+              break;
             } else 
-	      if (ntohs(hdr->th_opcode) == ERROR) {
+              if (ntohs(hdr->th_opcode) == ERROR) {
                 *err = ntohs(hdr->th_code);
                 goto out;
-	      } else {
+              } else {
                 // What kind of packet is this?
-		goto out;
-	      }
-	  }
-	}
-	
-	// Send data
-	sfp = buf;
-	last_good_block = 1;
-	while (result < len) {
-	  // Build packet of data to send
-	  data_len = min(SEGSIZE, len-result);
-	  hdr->th_opcode = htons(DATA);
-	  hdr->th_block = htons(last_good_block);
-	  cp = hdr->th_data;
-	  fp = sfp;
-	  actual_len = data_len + 4;
-	  // FIXME - what about "netascii" data?
-	  while (data_len-- > 0) *cp++ = *fp++;
-	  // Send data packet
-	  if (sendto(s, data, actual_len, 0, 
-		     &from_addr, from_len) < 0) {
+                goto out;
+              }
+          }
+        }
+        
+        // Send data
+        sfp = buf;
+        last_good_block = 1;
+        while (result < len) {
+          // Build packet of data to send
+          data_len = min(SEGSIZE, len-result);
+          hdr->th_opcode = htons(DATA);
+          hdr->th_block = htons(last_good_block);
+          cp = hdr->th_data;
+          fp = sfp;
+          actual_len = data_len + 4;
+          // FIXME - what about "netascii" data?
+          while (data_len-- > 0) *cp++ = *fp++;
+          // Send data packet
+          if (sendto(s, data, actual_len, 0, 
+                     &from_addr, from_len) < 0) {
             // Problem sending request
             *err = TFTP_NETERR;
-	    goto out;
-	  }
-	  // Wait for ACK
-	  timeout.tv_sec = TFTP_TIMEOUT_PERIOD;
-	  timeout.tv_usec = 0;
-	  FD_ZERO(&fds);
-	  FD_SET(s, &fds);
-	  if (select(s+1, &fds, 0, 0, &timeout) <= 0) {
+            goto out;
+          }
+          // Wait for ACK
+          timeout.tv_sec = TFTP_TIMEOUT_PERIOD;
+          timeout.tv_usec = 0;
+          FD_ZERO(&fds);
+          FD_SET(s, &fds);
+          if (select(s+1, &fds, 0, 0, &timeout) <= 0) {
             if (++total_timeouts > TFTP_TIMEOUT_MAX) {
-	      // Timeout - no data received
-	      *err = TFTP_TIMEOUT;
-	      goto out;
+              // Timeout - no data received
+              *err = TFTP_TIMEOUT;
+              goto out;
             }
-	  } else {
+          } else {
             recv_len = sizeof(data);
             from_len = sizeof(from_addr);
             if ((data_len = recvfrom(s, &data, recv_len, 0, 
                                      &from_addr, &from_len)) < 0) {
-	      // What happened?
-	      *err = TFTP_NETERR;
-	      goto out;
+              // What happened?
+              *err = TFTP_NETERR;
+              goto out;
             }
             if (ntohs(hdr->th_opcode) == ACK) {
-	      if (ntohs(hdr->th_block) == last_good_block) {
-		// Advance pointers, etc
-		sfp = fp;
-		result += (actual_len - 4);
-		last_good_block++;
-	      } else {
-		diag_printf("Send block #%d, got ACK for #%d\n", 
-			    last_good_block, ntohs(hdr->th_block));
-	      }
+              if (ntohs(hdr->th_block) == last_good_block) {
+                // Advance pointers, etc
+                sfp = fp;
+                result += (actual_len - 4);
+                last_good_block++;
+              } else {
+                diag_printf("Send block #%d, got ACK for #%d\n", 
+                            last_good_block, ntohs(hdr->th_block));
+              }
             } else 
-	      if (ntohs(hdr->th_opcode) == ERROR) {
+              if (ntohs(hdr->th_opcode) == ERROR) {
                 *err = ntohs(hdr->th_code);
                 goto out;
-	      } else {
+              } else {
                 // What kind of packet is this?
                 *err = TFTP_PROTOCOL;
-		goto out;
-	      }
-	  }
-	}
-	close (s);
-	return result;
+                goto out;
+              }
+          }
+        }
+        close (s);
+        return result;
       }
 
       // If we got here, it means there was a problem connecting to the 
       // server. Try the next address returned by getaddrinfo
     nextaddr:
       if (-1 != s) {
-	close(s);
+        close(s);
       }
       addrinfo=addrinfo->ai_next;
     }