]> granicus.if.org Git - curl/commitdiff
- Markus Koetter provided a polished and updated version of Chad Monroe's TFTP
authorDaniel Stenberg <daniel@haxx.se>
Fri, 27 Nov 2009 23:46:29 +0000 (23:46 +0000)
committerDaniel Stenberg <daniel@haxx.se>
Fri, 27 Nov 2009 23:46:29 +0000 (23:46 +0000)
  rework patch that now integrates TFTP properly into libcurl so that it can
  be used non-blocking with the multi interface and more. BLKSIZE also works.

  The --tftp-blksize option was added to allow setting the TFTP BLKSIZE from
  the command line.

CHANGES
RELEASE-NOTES
lib/tftp.c
lib/urldata.h
src/main.c

diff --git a/CHANGES b/CHANGES
index 17e3dcbf88571af8dfc2cbc8da60d957fbf4dac5..3d19944fb0e7f02f8b029e509685c2de0a769eaf 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -8,11 +8,19 @@
 
 
 
+Daniel Stenberg (28 Nov 2009)
+- Markus Koetter provided a polished and updated version of Chad Monroe's TFTP
+  rework patch that now integrates TFTP properly into libcurl so that it can
+  be used non-blocking with the multi interface and more. BLKSIZE also works.
+
+  The --tftp-blksize option was added to allow setting the TFTP BLKSIZE from
+  the command line.
+
 Daniel Stenberg (26 Nov 2009)
- - Extended and fixed the change I did on Dec 11 for the the progress
-   meter/callback during FTP command/response sequences. It turned out it was
-   really lame before and now the progress meter SHOULD get called at least
-   once per second.
+- Extended and fixed the change I did on Dec 11 for the the progress
+  meter/callback during FTP command/response sequences. It turned out it was
+  really lame before and now the progress meter SHOULD get called at least
+  once per second.
 
 Daniel Stenberg (23 Nov 2009)
 - Bjorn Augustsson reported a bug which made curl not report any problems even
index 92bd6ad4f94242273e6c346572ffd734680abaed..439e563a5ec7cd8436eb50882ac204bae4d9d997 100644 (file)
@@ -11,6 +11,7 @@ This release includes the following changes:
 
  o support SSL_FILETYPE_ENGINE for client certificate
  o curl-config can now show the arguments used when building curl
+ o non-blocking TFTP
 
 This release includes the following bugfixes:
 
@@ -26,6 +27,7 @@ This release includes the following bugfixes:
  o HTTP proxy tunnel re-used connection even if tunnel got disabled
  o SSL lib post-close write
  o curl failed to report write errors for tiny failed downloads
+ o TFTP BLKSIZE
 
 This release includes the following known bugs:
 
@@ -36,6 +38,7 @@ advice from friends like these:
 
  Yang Tse, Kamil Dudka, Christian Schmitz, Constantine Sapuntzakis,
  Marco Maggi, Camille Moncelier, Claes Jakobsson, Kevin Baughman,
- Marc Kleine-Budde, Jad Chamcham, Bjorn Augustsson, David Byron
+ Marc Kleine-Budde, Jad Chamcham, Bjorn Augustsson, David Byron,
+ Markus Koetter, Chad Monroe
 
         Thanks! (and sorry if I forgot to mention someone)
index 9b8595732319f3e13c824601ac619c751a4f4c7d..bdb345464d9194ccc3c27e382eebba1fb1941768 100644 (file)
@@ -70,6 +70,7 @@
 #include "connect.h"
 #include "strerror.h"
 #include "sockaddr.h" /* required for Curl_sockaddr_storage */
+#include "multiif.h"
 #include "url.h"
 #include "rawstr.h"
 
@@ -103,7 +104,8 @@ typedef enum {
 } tftp_state_t;
 
 typedef enum {
-  TFTP_EVENT_INIT=0,
+  TFTP_EVENT_NONE = -1,
+  TFTP_EVENT_INIT = 0,
   TFTP_EVENT_RRQ = 1,
   TFTP_EVENT_WRQ = 2,
   TFTP_EVENT_DATA = 3,
@@ -137,20 +139,22 @@ typedef struct tftp_state_data {
   tftp_state_t    state;
   tftp_mode_t     mode;
   tftp_error_t    error;
+  tftp_event_t    event;
   struct connectdata      *conn;
   curl_socket_t   sockfd;
   int             retries;
-  time_t          retry_time;
-  time_t          retry_max;
+  int             retry_time;
+  int             retry_max;
   time_t          start_time;
   time_t          max_time;
+  time_t          rx_time;
   unsigned short  block;
   struct Curl_sockaddr_storage   local_addr;
   struct Curl_sockaddr_storage   remote_addr;
-  curl_socklen_t  remote_addrlen;
-  ssize_t         rbytes;
-  size_t          sbytes;
-  size_t          blksize;
+  socklen_t       remote_addrlen;
+  int             rbytes;
+  int             sbytes;
+  int             blksize;
   int             requested_blksize;
   tftp_packet_t   rpacket;
   tftp_packet_t   spacket;
@@ -166,6 +170,11 @@ static CURLcode tftp_do(struct connectdata *conn, bool *done);
 static CURLcode tftp_done(struct connectdata *conn,
                                CURLcode, bool premature);
 static CURLcode tftp_setup_connection(struct connectdata * conn);
+static CURLcode tftp_multi_statemach(struct connectdata *conn, bool *done);
+static CURLcode tftp_doing(struct connectdata *conn, bool *dophase_done);
+static int tftp_getsock(struct connectdata *conn, curl_socket_t *socks,
+                        int numsocks);
+CURLcode tftp_translate_code(tftp_error_t error);
 
 
 /*
@@ -179,17 +188,16 @@ const struct Curl_handler Curl_handler_tftp = {
   tftp_done,                            /* done */
   ZERO_NULL,                            /* do_more */
   tftp_connect,                         /* connect_it */
-  ZERO_NULL,                            /* connecting */
-  ZERO_NULL,                            /* doing */
-  ZERO_NULL,                            /* proto_getsock */
-  ZERO_NULL,                            /* doing_getsock */
+  tftp_multi_statemach,                 /* connecting */
+  tftp_doing,                           /* doing */
+  tftp_getsock,                         /* proto_getsock */
+  tftp_getsock,                         /* doing_getsock */
   ZERO_NULL,                            /* perform_getsock */
   tftp_disconnect,                      /* disconnect */
   PORT_TFTP,                            /* defport */
   PROT_TFTP                             /* protocol */
 };
 
-
 /**********************************************************
  *
  * tftp_set_timeouts -
@@ -226,14 +234,14 @@ static CURLcode tftp_set_timeouts(tftp_state_data_t *state)
     timeout = maxtime ;
 
     /* Average restart after 5 seconds */
-    state->retry_max = timeout/5;
+    state->retry_max = (int)timeout/5;
 
     if(state->retry_max < 1)
       /* avoid division by zero below */
       state->retry_max = 1;
 
     /* Compute the re-start interval to suit the timeout */
-    state->retry_time = timeout/state->retry_max;
+    state->retry_time = (int)timeout/state->retry_max;
     if(state->retry_time<1)
       state->retry_time=1;
 
@@ -250,9 +258,9 @@ static CURLcode tftp_set_timeouts(tftp_state_data_t *state)
     timeout = maxtime/10 ;
 
     /* Average reposting an ACK after 15 seconds */
-    state->retry_max = timeout/15;
+    state->retry_max = (int)timeout/15;
   }
-  /* But bound the total number  */
+  /* But bound the total number */
   if(state->retry_max<3)
     state->retry_max=3;
 
@@ -269,6 +277,9 @@ static CURLcode tftp_set_timeouts(tftp_state_data_t *state)
         state->state, (state->max_time-state->start_time),
         state->retry_time, state->retry_max);
 
+  /* init RX time */
+  time(&state->rx_time);
+
   return CURLE_OK;
 }
 
@@ -352,9 +363,9 @@ static CURLcode tftp_parse_option_ack(tftp_state_data_t *state,
     infof(data, "got option=(%s) value=(%s)\n", option, value);
 
     if(checkprefix(option, TFTP_OPTION_BLKSIZE)) {
-      int blksize;
+      long blksize;
 
-      blksize = (int)strtol( value, NULL, 10 );
+      blksize = strtol( value, NULL, 10 );
 
       if(!blksize) {
         failf(data, "invalid blocksize value in OACK packet");
@@ -379,7 +390,7 @@ static CURLcode tftp_parse_option_ack(tftp_state_data_t *state,
         return CURLE_TFTP_ILLEGAL;
       }
 
-      state->blksize = blksize;
+      state->blksize = (int)blksize;
       infof(data, "%s (%d) %s (%d)\n", "blksize parsed from OACK",
         state->blksize, "requested", state->requested_blksize);
     }
@@ -402,13 +413,14 @@ static CURLcode tftp_parse_option_ack(tftp_state_data_t *state,
 static size_t tftp_option_add(tftp_state_data_t *state, size_t csize,
                               char *buf, const char *option)
 {
-  if( ( strlen(option) + csize + 1U ) > state->blksize )
-        return 0;
+  if( ( strlen(option) + csize + 1 ) > (size_t)state->blksize )
+    return 0;
   strcpy(buf, option);
   return( strlen(option) + 1 );
 }
 
-static CURLcode tftp_connect_for_tx(tftp_state_data_t *state, tftp_event_t event)
+static CURLcode tftp_connect_for_tx(tftp_state_data_t *state,
+                                    tftp_event_t event)
 {
   CURLcode res;
 #ifndef CURL_DISABLE_VERBOSE_STRINGS
@@ -423,7 +435,8 @@ static CURLcode tftp_connect_for_tx(tftp_state_data_t *state, tftp_event_t event
   return tftp_tx(state, event);
 }
 
-static CURLcode tftp_connect_for_rx(tftp_state_data_t *state, tftp_event_t event)
+static CURLcode tftp_connect_for_rx(tftp_state_data_t *state,
+                                    tftp_event_t event)
 {
   CURLcode res;
 #ifndef CURL_DISABLE_VERBOSE_STRINGS
@@ -441,6 +454,7 @@ static CURLcode tftp_connect_for_rx(tftp_state_data_t *state, tftp_event_t event
 static CURLcode tftp_send_first(tftp_state_data_t *state, tftp_event_t event)
 {
   size_t sbytes;
+  ssize_t senddata;
   const char *mode = "octet";
   char *filename;
   char buf[64];
@@ -506,18 +520,19 @@ static CURLcode tftp_send_first(tftp_state_data_t *state, tftp_event_t event)
                               TFTP_OPTION_BLKSIZE);
     sbytes += tftp_option_add(state, sbytes,
                               (char *)state->spacket.data+sbytes, buf );
-    /* add timeout option */
-    snprintf( buf, sizeof(buf), "%d", state->retry_time );
+    /* add timeout option, this is the max time the session may live */
+    snprintf( buf, sizeof(buf), "%d", state->retry_time*state->retry_max );
     sbytes += tftp_option_add(state, sbytes,
                               (char *)state->spacket.data+sbytes,
                               TFTP_OPTION_INTERVAL);
     sbytes += tftp_option_add(state, sbytes,
                               (char *)state->spacket.data+sbytes, buf );
 
-    if (sendto(state->sockfd, (void *)state->spacket.data,
-               sbytes, 0,
-               state->conn->ip_addr->ai_addr,
-               state->conn->ip_addr->ai_addrlen) < 0) {
+    senddata = sendto(state->sockfd, (void *)state->spacket.data,
+                    sbytes, 0,
+                    state->conn->ip_addr->ai_addr,
+                    state->conn->ip_addr->ai_addrlen);
+    if(senddata != (ssize_t)sbytes) {
       failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO));
     }
     Curl_safefree(filename);
@@ -560,6 +575,7 @@ static CURLcode tftp_send_first(tftp_state_data_t *state, tftp_event_t event)
  **********************************************************/
 static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event)
 {
+  ssize_t sbytes;
   int rblock;
   struct SessionHandle *data = state->conn->data;
 
@@ -584,10 +600,11 @@ static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event)
     state->retries = 0;
     setpacketevent(&state->spacket, TFTP_EVENT_ACK);
     setpacketblock(&state->spacket, state->block);
-    if(sendto(state->sockfd, (void *)state->spacket.data,
-              4, SEND_4TH_ARG,
-              (struct sockaddr *)&state->remote_addr,
-              state->remote_addrlen) < 0) {
+    sbytes = sendto(state->sockfd, (void *)state->spacket.data,
+                    4, SEND_4TH_ARG,
+                    (struct sockaddr *)&state->remote_addr,
+                    state->remote_addrlen);
+    if(sbytes < 0) {
       failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO));
       return CURLE_SEND_ERROR;
     }
@@ -599,6 +616,7 @@ static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event)
     else {
       state->state = TFTP_STATE_RX;
     }
+    time(&state->rx_time);
     break;
 
   case TFTP_EVENT_OACK:
@@ -607,16 +625,18 @@ static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event)
     state->retries = 0;
     setpacketevent(&state->spacket, TFTP_EVENT_ACK);
     setpacketblock(&state->spacket, state->block);
-    if(sendto(state->sockfd, (void *)state->spacket.data,
-              4, SEND_4TH_ARG,
-              (struct sockaddr *)&state->remote_addr,
-              state->remote_addrlen) < 0) {
+    sbytes = sendto(state->sockfd, (void *)state->spacket.data,
+                    4, SEND_4TH_ARG,
+                    (struct sockaddr *)&state->remote_addr,
+                    state->remote_addrlen);
+    if(sbytes < 0) {
       failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO));
       return CURLE_SEND_ERROR;
     }
 
     /* we're ready to RX data */
     state->state = TFTP_STATE_RX;
+    time(&state->rx_time);
     break;
 
   case TFTP_EVENT_TIMEOUT:
@@ -629,11 +649,13 @@ static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event)
       state->state = TFTP_STATE_FIN;
     }
     else {
-      /* Resend the previous ACK and check all sbytes were sent */
-      if(sendto(state->sockfd, (void *)state->spacket.data,
-                4, SEND_4TH_ARG,
-                (struct sockaddr *)&state->remote_addr,
-                state->remote_addrlen) < 0) {
+      /* Resend the previous ACK */
+      sbytes = sendto(state->sockfd, (void *)state->spacket.data,
+                      4, SEND_4TH_ARG,
+                      (struct sockaddr *)&state->remote_addr,
+                      state->remote_addrlen);
+      /* Check all sbytes were sent */
+      if(sbytes<0) {
         failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO));
         return CURLE_SEND_ERROR;
       }
@@ -641,6 +663,14 @@ static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event)
     break;
 
   case TFTP_EVENT_ERROR:
+    setpacketevent(&state->spacket, TFTP_EVENT_ERROR);
+    setpacketblock(&state->spacket, state->block);
+    sbytes = sendto(state->sockfd, (void *)state->spacket.data,
+                    4, SEND_4TH_ARG,
+                    (struct sockaddr *)&state->remote_addr,
+                    state->remote_addrlen);
+    /* don't bother with the return code, but if the socket is still up we
+     * should be a good TFTP client and let the server know we're done */
     state->state = TFTP_STATE_FIN;
     break;
 
@@ -662,8 +692,8 @@ static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event)
 static CURLcode tftp_tx(tftp_state_data_t *state, tftp_event_t event)
 {
   struct SessionHandle *data = state->conn->data;
+  ssize_t sbytes;
   int rblock;
-  int readcount;
   CURLcode res = CURLE_OK;
   struct SingleRequest *k = &data->req;
 
@@ -685,38 +715,40 @@ static CURLcode tftp_tx(tftp_state_data_t *state, tftp_event_t event)
         res = CURLE_SEND_ERROR;
       }
       else {
-        /* Re-send the data packet and check all sbytes were sent */
-        if(sendto(state->sockfd, (void *)&state->spacket,
-                  4+state->sbytes, SEND_4TH_ARG,
-                  (struct sockaddr *)&state->remote_addr,
-                  state->remote_addrlen) < 0) {
+        /* Re-send the data packet */
+        sbytes = sendto(state->sockfd, (void *)&state->spacket,
+                        4+state->sbytes, SEND_4TH_ARG,
+                        (struct sockaddr *)&state->remote_addr,
+                        state->remote_addrlen);
+        /* Check all sbytes were sent */
+        if(sbytes<0) {
           failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO));
           res = CURLE_SEND_ERROR;
         }
       }
       return res;
     }
-    /* fall-through */
-  case TFTP_EVENT_OACK:
     /* This is the expected packet.  Reset the counters and send the next
        block */
+    time(&state->rx_time);
     state->block++;
     state->retries = 0;
     setpacketevent(&state->spacket, TFTP_EVENT_DATA);
     setpacketblock(&state->spacket, state->block);
-    if(state->block > 1 && state->sbytes < state->blksize) {
+    if(state->block > 1 && state->sbytes < (int)state->blksize) {
       state->state = TFTP_STATE_FIN;
       return CURLE_OK;
     }
-    res = Curl_fillreadbuffer(state->conn, (int)state->blksize, &readcount);
-    state->sbytes = readcount;
+    res = Curl_fillreadbuffer(state->conn, (size_t)state->blksize,
+                              &state->sbytes);
     if(res)
       return res;
-    /* Send the data packet and check all sbytes were sent */
-    if(sendto(state->sockfd, (void *)state->spacket.data,
-              4+state->sbytes, SEND_4TH_ARG,
-              (struct sockaddr *)&state->remote_addr,
-              state->remote_addrlen) < 0) {
+    sbytes = sendto(state->sockfd, (void *)state->spacket.data,
+                    4+state->sbytes, SEND_4TH_ARG,
+                    (struct sockaddr *)&state->remote_addr,
+                    state->remote_addrlen);
+    /* Check all sbytes were sent */
+    if(sbytes<0) {
       failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO));
       return CURLE_SEND_ERROR;
     }
@@ -736,11 +768,13 @@ static CURLcode tftp_tx(tftp_state_data_t *state, tftp_event_t event)
       state->state = TFTP_STATE_FIN;
     }
     else {
-      /* Re-send the data packet and check all sbytes were sent */
-      if(sendto(state->sockfd, (void *)state->spacket.data,
-                4+state->sbytes, SEND_4TH_ARG,
-                (struct sockaddr *)&state->remote_addr,
-                state->remote_addrlen) < 0) {
+      /* Re-send the data packet */
+      sbytes = sendto(state->sockfd, (void *)state->spacket.data,
+                      4+state->sbytes, SEND_4TH_ARG,
+                      (struct sockaddr *)&state->remote_addr,
+                      state->remote_addrlen);
+      /* Check all sbytes were sent */
+      if(sbytes<0) {
         failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO));
         return CURLE_SEND_ERROR;
       }
@@ -751,6 +785,15 @@ static CURLcode tftp_tx(tftp_state_data_t *state, tftp_event_t event)
 
   case TFTP_EVENT_ERROR:
     state->state = TFTP_STATE_FIN;
+    setpacketevent(&state->spacket, TFTP_EVENT_ERROR);
+    setpacketblock(&state->spacket, state->block);
+    sbytes = sendto(state->sockfd, (void *)state->spacket.data,
+                    4, SEND_4TH_ARG,
+                    (struct sockaddr *)&state->remote_addr,
+                    state->remote_addrlen);
+    /* don't bother with the return code, but if the socket is still up we
+     * should be a good TFTP client and let the server know we're done */
+    state->state = TFTP_STATE_FIN;
     break;
 
   default:
@@ -761,6 +804,59 @@ static CURLcode tftp_tx(tftp_state_data_t *state, tftp_event_t event)
   return res;
 }
 
+/**********************************************************
+ *
+ * tftp_translate_code
+ *
+ * Translate internal error codes to CURL error codes
+ *
+ **********************************************************/
+CURLcode tftp_translate_code(tftp_error_t error)
+{
+  CURLcode code = CURLE_OK;
+
+  if(error != TFTP_ERR_NONE) {
+    switch(error) {
+    case TFTP_ERR_NOTFOUND:
+      code = CURLE_TFTP_NOTFOUND;
+      break;
+    case TFTP_ERR_PERM:
+      code = CURLE_TFTP_PERM;
+      break;
+    case TFTP_ERR_DISKFULL:
+      code = CURLE_REMOTE_DISK_FULL;
+      break;
+    case TFTP_ERR_UNDEF:
+    case TFTP_ERR_ILLEGAL:
+      code = CURLE_TFTP_ILLEGAL;
+      break;
+    case TFTP_ERR_UNKNOWNID:
+      code = CURLE_TFTP_UNKNOWNID;
+      break;
+    case TFTP_ERR_EXISTS:
+      code = CURLE_REMOTE_FILE_EXISTS;
+      break;
+    case TFTP_ERR_NOSUCHUSER:
+      code = CURLE_TFTP_NOSUCHUSER;
+      break;
+    case TFTP_ERR_TIMEOUT:
+      code = CURLE_OPERATION_TIMEDOUT;
+      break;
+    case TFTP_ERR_NORESPONSE:
+      code = CURLE_COULDNT_CONNECT;
+      break;
+    default:
+      code= CURLE_ABORTED_BY_CALLBACK;
+      break;
+    }
+  }
+  else {
+    code = CURLE_OK;
+  }
+
+  return(code);
+}
+
 /**********************************************************
  *
  * tftp_state_machine
@@ -883,11 +979,11 @@ static CURLcode tftp_connect(struct connectdata *conn, bool *done)
     /* If not already bound, bind to any interface, random UDP port. If it is
      * reused or a custom local port was desired, this has already been done!
      *
-     * We once used the size of the local_addr struct as the third argument for
-     * bind() to better work with IPv6 or whatever size the struct could have,
-     * but we learned that at least Tru64, AIX and IRIX *requires* the size of
-     * that argument to match the exact size of a 'sockaddr_in' struct when
-     * running IPv4-only.
+     * We once used the size of the local_addr struct as the third argument
+     * for bind() to better work with IPv6 or whatever size the struct could
+     * have, but we learned that at least Tru64, AIX and IRIX *requires* the
+     * size of that argument to match the exact size of a 'sockaddr_in' struct
+     * when running IPv4-only.
      *
      * Therefore we use the size from the address we connected to, which we
      * assume uses the same IP version and thus hopefully this works for both
@@ -920,204 +1016,363 @@ static CURLcode tftp_connect(struct connectdata *conn, bool *done)
 static CURLcode tftp_done(struct connectdata *conn, CURLcode status,
                                bool premature)
 {
+  CURLcode code = CURLE_OK;
+  tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc;
+
   (void)status; /* unused */
   (void)premature; /* not used */
 
   Curl_pgrsDone(conn);
 
-  return CURLE_OK;
-}
+  /* If we have encountered an error */
+  code = tftp_translate_code(state->error);
 
+  return code;
+}
 
 /**********************************************************
  *
- * tftp
- *
- * The do callback
+ * tftp_getsock
  *
- * This callback handles the entire TFTP transfer
+ * The getsock callback
  *
  **********************************************************/
+static int tftp_getsock(struct connectdata *conn, curl_socket_t *socks,
+                       int numsocks)
+{
+  if(!numsocks)
+    return GETSOCK_BLANK;
 
-static CURLcode tftp_do(struct connectdata *conn, bool *done)
+  socks[0] = conn->sock[FIRSTSOCKET];
+
+  return GETSOCK_READSOCK(0);
+}
+
+/**********************************************************
+ *
+ * tftp_receive_packet
+ *
+ * Called once select fires and data is ready on the socket
+ *
+ **********************************************************/
+static CURLcode tftp_receive_packet(struct connectdata *conn)
 {
-  struct SessionHandle  *data = conn->data;
-  tftp_state_data_t     *state;
-  tftp_event_t          event;
-  CURLcode              code;
-  int                   rc;
   struct Curl_sockaddr_storage fromaddr;
-  curl_socklen_t        fromlen;
-  int                   check_time = 0;
-  struct SingleRequest *k = &data->req;
+  socklen_t             fromlen;
+  CURLcode              result = CURLE_OK;
+  struct SessionHandle  *data = conn->data;
+  tftp_state_data_t     *state = (tftp_state_data_t *)conn->proto.tftpc;
+  struct SingleRequest  *k = &data->req;
+
+  /* Receive the packet */
+  fromlen = sizeof(fromaddr);
+  state->rbytes = (int)recvfrom(state->sockfd,
+                                   (void *)state->rpacket.data,
+                                   state->blksize+4,
+                                   0,
+                                   (struct sockaddr *)&fromaddr,
+                                   &fromlen);
+  if(state->remote_addrlen==0) {
+    memcpy(&state->remote_addr, &fromaddr, fromlen);
+    state->remote_addrlen = fromlen;
+  }
 
-  *done = TRUE;
+  /* Sanity check packet length */
+  if(state->rbytes < 4) {
+    failf(data, "Received too short packet");
+    /* Not a timeout, but how best to handle it? */
+    state->event = TFTP_EVENT_TIMEOUT;
+  }
+  else {
+    /* The event is given by the TFTP packet time */
+    state->event = (tftp_event_t)getrpacketevent(&state->rpacket);
+
+    switch(state->event) {
+    case TFTP_EVENT_DATA:
+      /* Don't pass to the client empty or retransmitted packets */
+      if(state->rbytes > 4 &&
+          ((state->block+1) == getrpacketblock(&state->rpacket))) {
+        result = Curl_client_write(conn, CLIENTWRITE_BODY,
+                                 (char *)state->rpacket.data+4,
+                                 state->rbytes-4);
+        if(result) {
+          tftp_state_machine(state, TFTP_EVENT_ERROR);
+          return result;
+        }
+        k->bytecount += state->rbytes-4;
+        Curl_pgrsSetDownloadCounter(data, (curl_off_t) k->bytecount);
+      }
+      break;
+    case TFTP_EVENT_ERROR:
+      state->error = (tftp_error_t)getrpacketblock(&state->rpacket);
+      infof(data, "%s\n", (const char *)state->rpacket.data+4);
+      break;
+    case TFTP_EVENT_ACK:
+      break;
+    case TFTP_EVENT_OACK:
+      result = tftp_parse_option_ack(state,
+                                   (const char *)state->rpacket.data+2,
+                                   state->rbytes-2);
+      if(result)
+        return result;
+      break;
+    case TFTP_EVENT_RRQ:
+    case TFTP_EVENT_WRQ:
+    default:
+      failf(data, "%s", "Internal error: Unexpected packet");
+      break;
+    }
 
-  /*
-    Since connections can be re-used between SessionHandles, this might be a
-    connection already existing but on a fresh SessionHandle struct so we must
-    make sure we have a good 'struct TFTP' to play with. For new connections,
-    the struct TFTP is allocated and setup in the tftp_connect() function.
-  */
-  Curl_reset_reqproto(conn);
+    /* Update the progress meter */
+    if(Curl_pgrsUpdate(conn)) {
+      tftp_state_machine(state, TFTP_EVENT_ERROR);
+      return CURLE_ABORTED_BY_CALLBACK;
+    }
+  }
+  return result;
+}
 
-  if(!conn->proto.tftpc) {
-    code = tftp_connect(conn, done);
-    if(code)
-      return code;
+/**********************************************************
+ *
+ * tftp_state_timeout
+ *
+ * Check if timeouts have been reached
+ *
+ **********************************************************/
+static long tftp_state_timeout(struct connectdata *conn, tftp_event_t *event)
+{
+  time_t                current;
+  struct SessionHandle  *data = conn->data;
+  tftp_state_data_t     *state = (tftp_state_data_t *)conn->proto.tftpc;
+
+  if (event)
+    *event = TFTP_EVENT_NONE;
+
+  time(&current);
+  if(current > state->max_time) {
+    DEBUGF(infof(data, "timeout: %d > %d\n",
+                 current, state->max_time));
+    state->error = TFTP_ERR_TIMEOUT;
+    state->state = TFTP_STATE_FIN;
+    return(0);
   }
-  state = (tftp_state_data_t *)conn->proto.tftpc;
+  else if (current > state->rx_time+state->retry_time) {
+    if (event)
+      *event = TFTP_EVENT_TIMEOUT;
+    time(&state->rx_time); /* update even though we received nothing */
+    return(state->max_time-current);
+  }
+  else {
+    return(state->max_time-current);
+  }
+}
+
+/**********************************************************
+ *
+ * tftp_easy_statemach
+ *
+ * Handle easy request until completion
+ *
+ **********************************************************/
+static CURLcode tftp_easy_statemach(struct connectdata *conn)
+{
+  int                   rc;
+  int                   check_time = 0;
+  CURLcode              result = CURLE_OK;
+  struct SessionHandle  *data = conn->data;
+  tftp_state_data_t     *state = (tftp_state_data_t *)conn->proto.tftpc;
 
   /* Run the TFTP State Machine */
-  for(code=tftp_state_machine(state, TFTP_EVENT_INIT);
-      (state->state != TFTP_STATE_FIN) && (code == CURLE_OK);
-      code=tftp_state_machine(state, event) ) {
+  for(;
+      (state->state != TFTP_STATE_FIN) && (result == CURLE_OK);
+      result=tftp_state_machine(state, state->event) ) {
 
     /* Wait until ready to read or timeout occurs */
     rc=Curl_socket_ready(state->sockfd, CURL_SOCKET_BAD,
-                         (int)(state->retry_time * 1000));
+                         state->retry_time * 1000);
 
     if(rc == -1) {
       /* bail out */
       int error = SOCKERRNO;
       failf(data, "%s", Curl_strerror(conn, error));
-      event = TFTP_EVENT_ERROR;
+      state->event = TFTP_EVENT_ERROR;
     }
     else if(rc==0) {
       /* A timeout occured */
-      event = TFTP_EVENT_TIMEOUT;
+      state->event = TFTP_EVENT_TIMEOUT;
 
       /* Force a look at transfer timeouts */
       check_time = 0;
 
     }
     else {
-
-      /* Receive the packet */
-      fromlen = sizeof(fromaddr);
-      state->rbytes = (ssize_t)recvfrom(state->sockfd,
-                                        (void *)state->rpacket.data,
-                                        state->blksize+4,
-                                        0,
-                                        (struct sockaddr *)&fromaddr,
-                                        &fromlen);
-      if(state->remote_addrlen==0) {
-        memcpy(&state->remote_addr, &fromaddr, fromlen);
-        state->remote_addrlen = fromlen;
-      }
-
-      /* Sanity check packet length */
-      if(state->rbytes < 4) {
-        failf(data, "Received too short packet");
-        /* Not a timeout, but how best to handle it? */
-        event = TFTP_EVENT_TIMEOUT;
-      }
-      else {
-
-        /* The event is given by the TFTP packet time */
-        event = (tftp_event_t)getrpacketevent(&state->rpacket);
-
-        switch(event) {
-        case TFTP_EVENT_DATA:
-          /* Don't pass to the client empty or retransmitted packets */
-          if(state->rbytes > 4 &&
-              ((state->block+1) == getrpacketblock(&state->rpacket))) {
-            code = Curl_client_write(conn, CLIENTWRITE_BODY,
-                                     (char *)state->rpacket.data+4,
-                                     state->rbytes-4);
-            if(code)
-              return code;
-            k->bytecount += state->rbytes-4;
-            Curl_pgrsSetDownloadCounter(data, (curl_off_t) k->bytecount);
-          }
-          break;
-        case TFTP_EVENT_ERROR:
-          state->error = (tftp_error_t)getrpacketblock(&state->rpacket);
-          infof(data, "%s\n", (const char *)state->rpacket.data+4);
-          break;
-        case TFTP_EVENT_ACK:
-          break;
-        case TFTP_EVENT_OACK:
-          code = tftp_parse_option_ack(state,
-                                       (const char *)state->rpacket.data+2,
-                                       (int)state->rbytes-2);
-          if(code)
-            return code;
-          break;
-        case TFTP_EVENT_RRQ:
-        case TFTP_EVENT_WRQ:
-        default:
-          failf(data, "%s", "Internal error: Unexpected packet");
-          break;
-        }
-
-        /* Update the progress meter */
-        if(Curl_pgrsUpdate(conn))
-          return CURLE_ABORTED_BY_CALLBACK;
-      }
+        result = tftp_receive_packet(conn);
     }
 
     /* Check for transfer timeout every 10 blocks, or after timeout */
     if(check_time%10==0) {
-      time_t current;
-      time(&current);
-      if(current>state->max_time) {
-        DEBUGF(infof(data, "timeout: %d > %d\n",
-                     current, state->max_time));
-        state->error = TFTP_ERR_TIMEOUT;
-        state->state = TFTP_STATE_FIN;
-      }
+      /* ignore the event here as Curl_socket_ready() handles
+       * retransmission timeouts inside the easy state mach */
+      tftp_state_timeout(conn, NULL);
     }
 
+    if(result)
+      return(result);
   }
-  if(code)
-    return code;
 
   /* Tell curl we're done */
-  code = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
-  if(code)
-    return code;
+  result = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
 
-  /* If we have encountered an error */
-  if(state->error != TFTP_ERR_NONE) {
+  return(result);
+}
 
-    /* Translate internal error codes to curl error codes */
-    switch(state->error) {
-    case TFTP_ERR_NOTFOUND:
-      code = CURLE_TFTP_NOTFOUND;
-      break;
-    case TFTP_ERR_PERM:
-      code = CURLE_TFTP_PERM;
-      break;
-    case TFTP_ERR_DISKFULL:
-      code = CURLE_REMOTE_DISK_FULL;
-      break;
-    case TFTP_ERR_UNDEF:
-    case TFTP_ERR_ILLEGAL:
-      code = CURLE_TFTP_ILLEGAL;
-      break;
-    case TFTP_ERR_UNKNOWNID:
-      code = CURLE_TFTP_UNKNOWNID;
-      break;
-    case TFTP_ERR_EXISTS:
-      code = CURLE_REMOTE_FILE_EXISTS;
-      break;
-    case TFTP_ERR_NOSUCHUSER:
-      code = CURLE_TFTP_NOSUCHUSER;
-      break;
-    case TFTP_ERR_TIMEOUT:
-      code = CURLE_OPERATION_TIMEDOUT;
-      break;
-    case TFTP_ERR_NORESPONSE:
-      code = CURLE_COULDNT_CONNECT;
-      break;
-    default:
-      code= CURLE_ABORTED_BY_CALLBACK;
-      break;
+/**********************************************************
+ *
+ * tftp_multi_statemach
+ *
+ * Handle single RX socket event and return
+ *
+ **********************************************************/
+static CURLcode tftp_multi_statemach(struct connectdata *conn, bool *done)
+{
+  int                   rc;
+  tftp_event_t          event;
+  CURLcode              result = CURLE_OK;
+  struct SessionHandle  *data = conn->data;
+  tftp_state_data_t     *state = (tftp_state_data_t *)conn->proto.tftpc;
+  long                  timeout_ms = tftp_state_timeout(conn, &event);
+
+  *done = FALSE;
+
+  if(timeout_ms <= 0) {
+    failf(data, "TFTP response timeout");
+    return CURLE_OPERATION_TIMEDOUT;
+  }
+  else if (event != TFTP_EVENT_NONE) {
+    result = tftp_state_machine(state, event);
+    if(result != CURLE_OK)
+      return(result);
+    *done = (bool)(state->state == TFTP_STATE_FIN);
+    if(*done)
+      /* Tell curl we're done */
+      result = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
+  }
+  else {
+    /* no timeouts to handle, check our socket */
+    rc = Curl_socket_ready(state->sockfd, CURL_SOCKET_BAD, 0);
+
+    if(rc == -1) {
+      /* bail out */
+      int error = SOCKERRNO;
+      failf(data, "%s", Curl_strerror(conn, error));
+      state->event = TFTP_EVENT_ERROR;
     }
+    else if(rc != 0) {
+      result = tftp_receive_packet(conn);
+      if(result != CURLE_OK)
+        return(result);
+      result = tftp_state_machine(state, state->event);
+      if(result != CURLE_OK)
+        return(result);
+      *done = (bool)(state->state == TFTP_STATE_FIN);
+      if(*done)
+        /* Tell curl we're done */
+        result = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
+    }
+    /* if rc == 0, then select() timed out */
   }
-  else
-    code = CURLE_OK;
+
+  return result;
+}
+
+/**********************************************************
+ *
+ * tftp_doing
+ *
+ * Called from multi.c while DOing
+ *
+ **********************************************************/
+static CURLcode tftp_doing(struct connectdata *conn, bool *dophase_done)
+{
+  CURLcode result;
+  result = tftp_multi_statemach(conn, dophase_done);
+
+  if(*dophase_done) {
+    DEBUGF(infof(conn->data, "DO phase is complete\n"));
+  }
+  return result;
+}
+
+/**********************************************************
+ *
+ * tftp_peform
+ *
+ * Entry point for transfer from tftp_do, sarts state mach
+ *
+ **********************************************************/
+static CURLcode tftp_perform(struct connectdata *conn, bool *dophase_done)
+{
+  CURLcode              result = CURLE_OK;
+  tftp_state_data_t     *state = (tftp_state_data_t *)conn->proto.tftpc;
+
+  *dophase_done = FALSE;
+
+  result = tftp_state_machine(state, TFTP_EVENT_INIT);
+
+  if(state->state == TFTP_STATE_FIN || result != CURLE_OK)
+    return(result);
+
+  if(conn->data->state.used_interface == Curl_if_multi)
+    tftp_multi_statemach(conn, dophase_done);
+  else {
+    result = tftp_easy_statemach(conn);
+    *dophase_done = TRUE; /* with the easy interface we are done here */
+  }
+
+  if(*dophase_done)
+    DEBUGF(infof(conn->data, "DO phase is complete\n"));
+
+  return result;
+}
+
+
+/**********************************************************
+ *
+ * tftp_do
+ *
+ * The do callback
+ *
+ * This callback initiates the TFTP transfer
+ *
+ **********************************************************/
+
+static CURLcode tftp_do(struct connectdata *conn, bool *done)
+{
+  tftp_state_data_t     *state;
+  CURLcode              code;
+
+  *done = FALSE;
+
+  /*
+    Since connections can be re-used between SessionHandles, this might be a
+    connection already existing but on a fresh SessionHandle struct so we must
+    make sure we have a good 'struct TFTP' to play with. For new connections,
+    the struct TFTP is allocated and setup in the tftp_connect() function.
+  */
+  Curl_reset_reqproto(conn);
+
+  if(!conn->proto.tftpc) {
+    code = tftp_connect(conn, done);
+    if(code)
+      return code;
+  }
+  state = (tftp_state_data_t *)conn->proto.tftpc;
+
+  code = tftp_perform(conn, done);
+
+  /* If we have encountered an error */
+  code = tftp_translate_code(state->error);
+
   return code;
 }
 
index 40ed8285dface82924ed893892f5767e50cd8cce..daf6d0e580fd52a21fa5161a22a80e42425d3bf4 100644 (file)
@@ -932,8 +932,8 @@ struct connectdata {
 #define PROT_SSL     (1<<22) /* protocol requires SSL */
 #define PROT_MISSING (1<<23)
 
-#define PROT_CLOSEACTION PROT_FTP /* these ones need action before socket
-                                     close */
+/* these ones need action before socket close */
+#define PROT_CLOSEACTION (PROT_FTP | PROT_TFTP) 
 #define PROT_DUALCHANNEL PROT_FTP /* these protocols use two connections */
 
   /* 'dns_entry' is the particular host we use. This points to an entry in the
index b9ac7adb4a61f84e2d6b6ca2d110c46df5eeaf88..b5c1d89b776344fda1ce70fec726c3282aa1ab5f 100644 (file)
@@ -602,7 +602,7 @@ struct Configurable {
   char *ftp_account; /* for ACCT */
   char *ftp_alternative_to_user; /* send command if USER/PASS fails */
   int ftp_filemethod;
-
+  long tftp_blksize; /* TFTP BLKSIZE option */
   bool ignorecl; /* --ignore-content-length */
   bool disable_sessionid;
 
@@ -877,6 +877,7 @@ static void help(void)
     "    --stderr <file> Where to redirect stderr. - means stdout",
     "    --tcp-nodelay   Use the TCP_NODELAY option",
     " -t/--telnet-option <OPT=val> Set telnet option",
+    "    --tftp-blksize <value> Set TFTP BLKSIZE option (must be >512)",
     " -z/--time-cond <time> Transfer based on a time condition",
     " -1/--tlsv1         Use TLSv1 (SSL)",
     "    --trace <file>  Write a debug trace to the given file",
@@ -1732,12 +1733,13 @@ static ParameterError getparameter(char *flag, /* f or -long-flag */
     {"$3", "keepalive-time",  TRUE},
     {"$4", "post302",    FALSE},
     {"$5", "noproxy",    TRUE},
+
 #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
     {"$6", "socks5-gssapi-service",  TRUE},
     {"$7", "socks5-gssapi-nec",  FALSE},
 #endif
     {"$8", "proxy1.0",   TRUE},
-
+    {"$9", "tftp-blksize", TRUE},
     {"0", "http1.0",     FALSE},
     {"1", "tlsv1",       FALSE},
     {"2", "sslv2",       FALSE},
@@ -2264,6 +2266,9 @@ static ParameterError getparameter(char *flag, /* f or -long-flag */
         GetStr(&config->proxy, nextarg);
         config->proxyver = CURLPROXY_HTTP_1_0;
         break;
+      case '9': /* --tftp-blksize */
+        str2num(&config->tftp_blksize, nextarg);
+        break;
       }
       break;
     case '#': /* --progress-bar */
@@ -5000,6 +5005,9 @@ operate(struct Configurable *config, int argc, argv_item_t argv[])
         my_setopt(curl, CURLOPT_POSTREDIR, config->post301 |
                   (config->post302 ? CURL_REDIR_POST_302 : FALSE));
 
+        if(config->tftp_blksize)
+          my_setopt(curl, CURLOPT_TFTP_BLKSIZE, config->tftp_blksize);
+
         retry_numretries = config->req_retry;
 
         retrystart = cutil_tvnow();