diff options
| author | Daniel Stenberg <daniel@haxx.se> | 2009-11-27 23:46:29 +0000 | 
|---|---|---|
| committer | Daniel Stenberg <daniel@haxx.se> | 2009-11-27 23:46:29 +0000 | 
| commit | 6e38cc9048a2ab2a28dca6cb357b091a3ee4317c (patch) | |
| tree | 5dc2a4956d315213016a90a4781882f1472b0472 | |
| parent | a240f4d1df0775723eadb942a15f3100b44bb2db (diff) | |
- 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.
| -rw-r--r-- | CHANGES | 16 | ||||
| -rw-r--r-- | RELEASE-NOTES | 5 | ||||
| -rw-r--r-- | lib/tftp.c | 685 | ||||
| -rw-r--r-- | lib/urldata.h | 4 | ||||
| -rw-r--r-- | src/main.c | 12 | 
5 files changed, 498 insertions, 224 deletions
@@ -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 diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 92bd6ad4f..439e563a5 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -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) diff --git a/lib/tftp.c b/lib/tftp.c index 9b8595732..bdb345464 100644 --- a/lib/tftp.c +++ b/lib/tftp.c @@ -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: @@ -763,6 +806,59 @@ static CURLcode tftp_tx(tftp_state_data_t *state, tftp_event_t event)  /**********************************************************   * + * 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   *   * The tftp state machine event dispatcher @@ -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(¤t); +  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(¤t); -      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;  } diff --git a/lib/urldata.h b/lib/urldata.h index 40ed8285d..daf6d0e58 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -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 diff --git a/src/main.c b/src/main.c index b9ac7adb4..b5c1d89b7 100644 --- a/src/main.c +++ b/src/main.c @@ -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();  | 
