Commit cb895ec3 authored by Daniel Stenberg's avatar Daniel Stenberg
Browse files

Initial fix to make the multi interface return control while waiting for

the initial connect to "come through".

This should work fine for connect and for FTP-PASV connects. Needs massive
testing.
parent 2df4866c
Loading
Loading
Loading
Loading
+88 −3
Original line number Diff line number Diff line
@@ -337,6 +337,70 @@ int socketerror(int sockfd)
  return err;
}

/*
 * Curl_is_connected() is used from the multi interface to check if the
 * firstsocket has connected.
 */

CURLcode Curl_is_connected(struct connectdata *conn,
                           int sockfd,
                           bool *connected)
{
  int rc;
  struct SessionHandle *data = conn->data;

  *connected = FALSE; /* a very negative world view is best */

  if(data->set.timeout || data->set.connecttimeout) {
    /* there is a timeout set */

    /* Evaluate in milliseconds how much time that has passed */
    long has_passed = Curl_tvdiff(Curl_tvnow(), data->progress.start);

    /* subtract the most strict timeout of the ones */
    if(data->set.timeout && data->set.connecttimeout) {
      if (data->set.timeout < data->set.connecttimeout)
        has_passed -= data->set.timeout*1000;
      else 
        has_passed -= data->set.connecttimeout*1000;
    }
    else if(data->set.timeout)
      has_passed -= data->set.timeout*1000;
    else
      has_passed -= data->set.connecttimeout*1000;

    if(has_passed > 0 ) {
      /* time-out, bail out, go home */
      failf(data, "Connection time-out");
      return CURLE_OPERATION_TIMEOUTED;
    }
  }

  /* check for connect without timeout as we want to return immediately */
  rc = waitconnect(sockfd, 0);

  if(0 == rc) {
    int err = socketerror(sockfd);
    if ((0 == err) || (EISCONN == err)) {
      /* we are connected, awesome! */
      *connected = TRUE;
      return CURLE_OK;
    }
    /* nope, not connected for real */
  }

  /*
   * If the connection phase is "done" here, we should attempt to connect
   * to the "next address" in the Curl_hostaddr structure that we resolved
   * before. But we don't have that struct around anymore and we can't just
   * keep a pointer since the cache might in fact have gotten pruned by the
   * time we want to read this... Alas, we don't do this yet.
   */

  return CURLE_OK;
}


/*
 * TCP connect to the given host with timeout, proxy or remote doesn't matter.
 * There might be more than one IP address to try out. Fill in the passed
@@ -347,7 +411,8 @@ CURLcode Curl_connecthost(struct connectdata *conn, /* context */
                          Curl_addrinfo *remotehost, /* use one in here */
                          int port,                  /* connect to this */
                          int *sockconn,             /* the connected socket */
                          Curl_ipconnect **addr)     /* the one we used */
                          Curl_ipconnect **addr,     /* the one we used */
                          bool *connected)           /* really connected? */
{
  struct SessionHandle *data = conn->data;
  int rc;
@@ -437,8 +502,11 @@ CURLcode Curl_connecthost(struct connectdata *conn, /* context */
        case EAGAIN:
#endif
        case EINTR:

          /* asynchronous connect, wait for connect or timeout */
          if(data->state.used_interface == Curl_if_multi)
            /* don't hang when doing multi */
            timeout_ms = 0;

          rc = waitconnect(sockfd, timeout_ms);
          break;
        case ECONNREFUSED: /* no one listening */
@@ -448,6 +516,7 @@ CURLcode Curl_connecthost(struct connectdata *conn, /* context */
          break;
        }
      }

      if(0 == rc) {
        /* we might be connected, if the socket says it is OK! Ask it! */
        int err;
@@ -455,11 +524,17 @@ CURLcode Curl_connecthost(struct connectdata *conn, /* context */
        err = socketerror(sockfd);
        if ((0 == err) || (EISCONN == err)) {
          /* we are connected, awesome! */
          *connected = TRUE; /* this is truly a connect */
          break;
	}
        failf(data, "socket error: %d", err);
        /* we are _not_ connected, it was a false alert, continue please */
      }
      else if(data->state.used_interface == Curl_if_multi) {
        /* When running the multi interface, we bail out here */
        rc = 0;
        break;
      }

      /* connect failed or timed out */
      sclose(sockfd);
@@ -542,8 +617,11 @@ CURLcode Curl_connecthost(struct connectdata *conn, /* context */
         */
      case EAGAIN:
#endif

        /* asynchronous connect, wait for connect or timeout */
        if(data->state.used_interface == Curl_if_multi)
          /* don't hang when doing multi */
          timeout_ms = 0;

        rc = waitconnect(sockfd, timeout_ms);
        break;
      default:
@@ -558,6 +636,7 @@ CURLcode Curl_connecthost(struct connectdata *conn, /* context */
      int err = socketerror(sockfd);
      if ((0 == err) || (EISCONN == err)) {
        /* we are connected, awesome! */
        *connected = TRUE; /* this is a true connect */
        break;
      }
      /* nope, not connected for real */
@@ -565,6 +644,12 @@ CURLcode Curl_connecthost(struct connectdata *conn, /* context */
    }

    if(0 != rc) {
      if(data->state.used_interface == Curl_if_multi) {
        /* When running the multi interface, we bail out here */
        rc = 0;
        break;
      }

      /* get a new timeout for next attempt */
      after = Curl_tvnow();
      timeout_ms -= Curl_tvdiff(after, before);
+7 −2
Original line number Diff line number Diff line
@@ -26,10 +26,15 @@
int Curl_nonblock(int socket,    /* operate on this */
                  int nonblock   /* TRUE or FALSE */);

CURLcode Curl_is_connected(struct connectdata *conn,
                           int sockfd,
                           bool *connected);

CURLcode Curl_connecthost(struct connectdata *conn,
                          Curl_addrinfo *host, /* connect to this */
                          int port,       /* connect to this port number */
                          int *sockconn,  /* not set if error is returned */
                          Curl_ipconnect **addr /* the one we used */
                          ); /*  index we used */
                          Curl_ipconnect **addr, /* the one we used */
                          bool *connected /* truly connected? */
                          );
#endif
+159 −123
Original line number Diff line number Diff line
@@ -387,8 +387,10 @@ int Curl_GetFTPResponse(char *buf,
  return nread; /* total amount of bytes read */
}

/* ftp_connect() should do everything that is to be considered a part
   of the connection phase. */
/*
 * Curl_ftp_connect() should do everything that is to be considered a part of
 * the connection phase.
 */
CURLcode Curl_ftp_connect(struct connectdata *conn)
{
  /* this is FTP and no proxy */
@@ -1321,7 +1323,8 @@ CURLcode ftp_use_port(struct connectdata *conn)
 */

static
CURLcode ftp_use_pasv(struct connectdata *conn)
CURLcode ftp_use_pasv(struct connectdata *conn,
                      bool *connected)
{
  struct SessionHandle *data = conn->data;
  ssize_t nread;
@@ -1473,7 +1476,14 @@ CURLcode ftp_use_pasv(struct connectdata *conn)
                            addr,
                            connectport,
                            &conn->secondarysocket,
                            &conninfo);
                            &conninfo,
                            connected);

  /*
   * When this is used from the multi interface, this might've returned with
   * the 'connected' set to FALSE and thus we are now awaiting a non-blocking
   * connect to connect and we should not be "hanging" here waiting.
   */
  
  if((CURLE_OK == result) &&       
     data->set.verbose)
@@ -1494,127 +1504,24 @@ CURLcode ftp_use_pasv(struct connectdata *conn)
  return CURLE_OK;
}

/***********************************************************************
 *
 * ftp_perform()
/*
 * Curl_ftp_nextconnect()
 *
 * This is the actual DO function for FTP. Get a file/directory according to
 * the options previously setup.
 * This function shall be called when the second FTP connection has been
 * established and is confirmed connected.
 */

static
CURLcode ftp_perform(struct connectdata *conn)
CURLcode Curl_ftp_nextconnect(struct connectdata *conn)
{
  /* this is FTP and no proxy */
  ssize_t nread;
  CURLcode result=CURLE_OK;
  struct SessionHandle *data=conn->data;
  char *buf = data->state.buffer; /* this is our buffer */
  CURLcode result;
  ssize_t nread;
  int ftpcode; /* for ftp status */

  /* the ftp struct is already inited in ftp_connect() */
  struct FTP *ftp = conn->proto.ftp;

  long *bytecountp = ftp->bytecountp;
  int ftpcode; /* for ftp status */

  /* Send any QUOTE strings? */
  if(data->set.quote) {
    if ((result = ftp_sendquote(conn, data->set.quote)) != CURLE_OK)
      return result;
  }
    
  /* This is a re-used connection. Since we change directory to where the
     transfer is taking place, we must now get back to the original dir
     where we ended up after login: */
  if (conn->bits.reuse) {
    if ((result = ftp_cwd(conn, ftp->entrypath)) != CURLE_OK)
      return result;
  }

  /* change directory first! */
  if(ftp->dir && ftp->dir[0]) {
    if ((result = ftp_cwd(conn, ftp->dir)) != CURLE_OK)
        return result;
  }

  /* Requested time of file? */
  if(data->set.get_filetime && ftp->file) {
    result = ftp_getfiletime(conn, ftp->file);
    if(result)
      return result;
  }

  /* If we have selected NOBODY and HEADER, it means that we only want file
     information. Which in FTP can't be much more than the file size and
     date. */
  if(data->set.no_body && data->set.include_header && ftp->file) {
    /* The SIZE command is _not_ RFC 959 specified, and therefor many servers
       may not support it! It is however the only way we have to get a file's
       size! */
    ssize_t filesize;

    ftp->no_transfer = TRUE; /* this means no actual transfer is made */
    
    /* Some servers return different sizes for different modes, and thus we
       must set the proper type before we check the size */
    result = ftp_transfertype(conn, data->set.ftp_ascii);
    if(result)
      return result;

    /* failing to get size is not a serious error */
    result = ftp_getsize(conn, ftp->file, &filesize);

    if(CURLE_OK == result) {
      sprintf(buf, "Content-Length: %d\r\n", filesize);
      result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0);
      if(result)
        return result;
    }

    /* If we asked for a time of the file and we actually got one as
       well, we "emulate" a HTTP-style header in our output. */

#ifdef HAVE_STRFTIME
    if(data->set.get_filetime && data->info.filetime) {
      struct tm *tm;
#ifdef HAVE_LOCALTIME_R
      struct tm buffer;
      tm = (struct tm *)localtime_r(&data->info.filetime, &buffer);
#else
      tm = localtime((unsigned long *)&data->info.filetime);
#endif
      /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
      strftime(buf, BUFSIZE-1, "Last-Modified: %a, %d %b %Y %H:%M:%S %Z\r\n",
               tm);
      result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0);
      if(result)
        return result;
    }
#endif

    return CURLE_OK;
  }

  if(data->set.no_body)
    /* doesn't really transfer any data */
    ftp->no_transfer = TRUE;
  /* Get us a second connection up and connected */
  else if(data->set.ftp_use_port) {
    /* We have chosen to use the PORT command */
    result = ftp_use_port(conn);
    if(CURLE_OK == result)
      /* we have the data connection ready */
      infof(data, "Connected the data stream with PORT!\n");
  }
  else {
    /* We have chosen (this is default) to use the PASV command */
    result = ftp_use_pasv(conn);
    if(CURLE_OK == result)
      infof(data, "Connected the data stream with PASV!\n");
  }
  
  if(result)
    return result;

  if(data->set.upload) {

@@ -1991,6 +1898,128 @@ CURLcode ftp_perform(struct connectdata *conn)
  return CURLE_OK;
}

/***********************************************************************
 *
 * ftp_perform()
 *
 * This is the actual DO function for FTP. Get a file/directory according to
 * the options previously setup.
 */

static
CURLcode ftp_perform(struct connectdata *conn,
                     bool *connected)  /* for the TCP connect status after
                                          PASV / PORT */
{
  /* this is FTP and no proxy */
  CURLcode result=CURLE_OK;
  struct SessionHandle *data=conn->data;
  char *buf = data->state.buffer; /* this is our buffer */

  /* the ftp struct is already inited in ftp_connect() */
  struct FTP *ftp = conn->proto.ftp;

  /* Send any QUOTE strings? */
  if(data->set.quote) {
    if ((result = ftp_sendquote(conn, data->set.quote)) != CURLE_OK)
      return result;
  }
    
  /* This is a re-used connection. Since we change directory to where the
     transfer is taking place, we must now get back to the original dir
     where we ended up after login: */
  if (conn->bits.reuse) {
    if ((result = ftp_cwd(conn, ftp->entrypath)) != CURLE_OK)
      return result;
  }

  /* change directory first! */
  if(ftp->dir && ftp->dir[0]) {
    if ((result = ftp_cwd(conn, ftp->dir)) != CURLE_OK)
        return result;
  }

  /* Requested time of file? */
  if(data->set.get_filetime && ftp->file) {
    result = ftp_getfiletime(conn, ftp->file);
    if(result)
      return result;
  }

  /* If we have selected NOBODY and HEADER, it means that we only want file
     information. Which in FTP can't be much more than the file size and
     date. */
  if(data->set.no_body && data->set.include_header && ftp->file) {
    /* The SIZE command is _not_ RFC 959 specified, and therefor many servers
       may not support it! It is however the only way we have to get a file's
       size! */
    ssize_t filesize;

    ftp->no_transfer = TRUE; /* this means no actual transfer is made */
    
    /* Some servers return different sizes for different modes, and thus we
       must set the proper type before we check the size */
    result = ftp_transfertype(conn, data->set.ftp_ascii);
    if(result)
      return result;

    /* failing to get size is not a serious error */
    result = ftp_getsize(conn, ftp->file, &filesize);

    if(CURLE_OK == result) {
      sprintf(buf, "Content-Length: %d\r\n", filesize);
      result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0);
      if(result)
        return result;
    }

    /* If we asked for a time of the file and we actually got one as
       well, we "emulate" a HTTP-style header in our output. */

#ifdef HAVE_STRFTIME
    if(data->set.get_filetime && data->info.filetime) {
      struct tm *tm;
#ifdef HAVE_LOCALTIME_R
      struct tm buffer;
      tm = (struct tm *)localtime_r(&data->info.filetime, &buffer);
#else
      tm = localtime((unsigned long *)&data->info.filetime);
#endif
      /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
      strftime(buf, BUFSIZE-1, "Last-Modified: %a, %d %b %Y %H:%M:%S %Z\r\n",
               tm);
      result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0);
      if(result)
        return result;
    }
#endif

    return CURLE_OK;
  }

  if(data->set.no_body)
    /* doesn't really transfer any data */
    ftp->no_transfer = TRUE;
  /* Get us a second connection up and connected */
  else if(data->set.ftp_use_port) {
    /* We have chosen to use the PORT command */
    result = ftp_use_port(conn);
    if(CURLE_OK == result) {
      /* we have the data connection ready */
      infof(data, "Ordered connect of the data stream with PORT!\n");
      *connected = TRUE; /* mark us "still connected" */
    }
  }
  else {
    /* We have chosen (this is default) to use the PASV command */
    result = ftp_use_pasv(conn, connected);
    if(connected)
      infof(data, "Connected the data stream with PASV!\n");
  }
  
  return result;
}

/***********************************************************************
 *
 * Curl_ftp()
@@ -2003,6 +2032,7 @@ CURLcode ftp_perform(struct connectdata *conn)
CURLcode Curl_ftp(struct connectdata *conn)
{
  CURLcode retcode;
  bool connected;

  struct SessionHandle *data = conn->data;
  struct FTP *ftp;
@@ -2049,15 +2079,15 @@ CURLcode Curl_ftp(struct connectdata *conn)
  else
    ftp->dir = NULL;

  retcode = ftp_perform(conn);

  /* clean up here, success or error doesn't matter */
  if(ftp->file)
    free(ftp->file);
  if(ftp->dir)
    free(ftp->dir);
  retcode = ftp_perform(conn, &connected);

  ftp->file = ftp->dir = NULL; /* zero */
  if(CURLE_OK == retcode) {
    if(connected)
      retcode = Curl_ftp_nextconnect(conn);
    else
      /* since we didn't connect now, we want do_more to get called */
      conn->do_more = TRUE;
  }

  return retcode;
}
@@ -2128,6 +2158,12 @@ CURLcode Curl_ftp_disconnect(struct connectdata *conn)
      free(ftp->entrypath);
    if(ftp->cache)
      free(ftp->cache);
    if(ftp->file)
      free(ftp->file);
    if(ftp->dir)
      free(ftp->dir);

    ftp->file = ftp->dir = NULL; /* zero */
  }
  return CURLE_OK;
}
+1 −9
Original line number Diff line number Diff line
#ifndef __FTP_H
#define __FTP_H

/*****************************************************************************
 *                                  _   _ ____  _     
 *  Project                     ___| | | |  _ \| |    
@@ -24,22 +23,15 @@
 * $Id$
 *****************************************************************************/

/* MN 06/07/02 */
#ifndef CURL_DISABLE_FTP

CURLcode Curl_ftp(struct connectdata *conn);
CURLcode Curl_ftp_done(struct connectdata *conn);
CURLcode Curl_ftp_connect(struct connectdata *conn);
CURLcode Curl_ftp_disconnect(struct connectdata *conn);

CURLcode Curl_ftpsendf(struct connectdata *, const char *fmt, ...);

/* The kerberos stuff needs this: */
int Curl_GetFTPResponse(char *buf, struct connectdata *conn,
                        int *ftpcode);

/* MN 06/07/02 */
CURLcode Curl_ftp_nextconnect(struct connectdata *conn);
#endif


#endif
+102 −9
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@
#include "urldata.h"
#include "transfer.h"
#include "url.h"
#include "connect.h"

/* The last #include file should be: */
#ifdef MALLOCDEBUG
@@ -43,11 +44,13 @@ struct Curl_message {

typedef enum {
  CURLM_STATE_INIT,
  CURLM_STATE_CONNECT,
  CURLM_STATE_DO,
  CURLM_STATE_PERFORM,
  CURLM_STATE_DONE,
  CURLM_STATE_COMPLETED,
  CURLM_STATE_CONNECT,     /* connect has been sent off */
  CURLM_STATE_WAITCONNECT, /* we're awaiting the connect to finalize */
  CURLM_STATE_DO,          /* send off the request (part 1) */
  CURLM_STATE_DO_MORE,     /* send off the request (part 2) */
  CURLM_STATE_PERFORM,     /* transfer data */
  CURLM_STATE_DONE,        /* post data transfer operation */
  CURLM_STATE_COMPLETED,   /* operation complete */

  CURLM_STATE_LAST /* not a true state, never use this */
} CURLMstate;
@@ -224,6 +227,32 @@ CURLMcode curl_multi_fdset(CURLM *multi_handle,
    switch(easy->state) {
    default:
      break;
    case CURLM_STATE_WAITCONNECT:
    case CURLM_STATE_DO_MORE:
      {
        /* when we're waiting for a connect, we wait for the socket to
           become writable */
        struct connectdata *conn = easy->easy_conn;
        int sockfd;

        if(CURLM_STATE_WAITCONNECT == easy->state) {
          sockfd = conn->firstsocket;
          FD_SET(sockfd, write_fd_set);
        }
        else {
          /* When in DO_MORE state, we could be either waiting for us
             to connect to a remote site, or we could wait for that site
             to connect to us. It makes a difference in the way: if we
             connect to the site we wait for the socket to become writable, if 
             the site connects to us we wait for it to become readable */
          sockfd = conn->secondarysocket;
          FD_SET(sockfd, write_fd_set);
        }

        if(sockfd > *max_fd)
          *max_fd = sockfd;
      }
      break;
    case CURLM_STATE_PERFORM:
      /* This should have a set of file descriptors for us to set.  */
      /* after the transfer is done, go DONE */
@@ -251,6 +280,7 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
  bool done;
  CURLMcode result=CURLM_OK;
  struct Curl_message *msg = NULL;
  bool connected;

  *running_handles = 0; /* bump this once for every living handle */

@@ -259,6 +289,12 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)

  easy=multi->easy.next;
  while(easy) {

#ifdef MALLOCDEBUG
    fprintf(stderr, "HANDLE %p: State: %x\n",
            (char *)easy, easy->state);
#endif

    switch(easy->state) {
    case CURLM_STATE_INIT:
      /* init this transfer. */
@@ -287,23 +323,80 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
      /* Connect. We get a connection identifier filled in. */
      easy->result = Curl_connect(easy->easy_handle, &easy->easy_conn);

      /* after connect, go DO */
      /* after the connect has been sent off, go WAITCONNECT */
      if(CURLE_OK == easy->result) {
        easy->state = CURLM_STATE_WAITCONNECT;
        result = CURLM_CALL_MULTI_PERFORM; 
      }
      break;

    case CURLM_STATE_WAITCONNECT:
      {
        bool connected;
        easy->result = Curl_is_connected(easy->easy_conn,
                                         easy->easy_conn->firstsocket,
                                         &connected);
        if(connected)
          easy->result = Curl_protocol_connect(easy->easy_conn, NULL);

        if(CURLE_OK != easy->result)
          /* failure detected */
          break;

        if(connected) {
          /* after the connect has completed, go DO */
          easy->state = CURLM_STATE_DO;
          result = CURLM_CALL_MULTI_PERFORM; 
        }
      }
      break;

    case CURLM_STATE_DO:
      /* Do the fetch or put request */
      easy->result = Curl_do(&easy->easy_conn);
      /* after do, go PERFORM */
      if(CURLE_OK == easy->result) {
        if(CURLE_OK == Curl_readwrite_init(easy->easy_conn)) {

        /* after do, go PERFORM... or DO_MORE */
        if(easy->easy_conn->do_more) {
          /* we're supposed to do more, but we need to sit down, relax
             and wait a little while first */
          easy->state = CURLM_STATE_DO_MORE;
          result = CURLM_OK;
        }
        else {
          /* we're done with the DO, now PERFORM */
          easy->result = Curl_readwrite_init(easy->easy_conn);
          if(CURLE_OK == easy->result) {
            easy->state = CURLM_STATE_PERFORM;
            result = CURLM_CALL_MULTI_PERFORM; 
          }
        }
      }
      break;

    case CURLM_STATE_DO_MORE:
      /*
       * First, check if we really are ready to do more.
       */
      easy->result = Curl_is_connected(easy->easy_conn,
                                       easy->easy_conn->secondarysocket,
                                       &connected);
      if(connected) {
        /*
         * When we are connected, DO MORE and then go PERFORM
         */
        easy->result = Curl_do_more(easy->easy_conn);

        if(CURLE_OK == easy->result)
          easy->result = Curl_readwrite_init(easy->easy_conn);

        if(CURLE_OK == easy->result) {
          easy->state = CURLM_STATE_PERFORM;
          result = CURLM_CALL_MULTI_PERFORM; 
        }
      }
      break;

    case CURLM_STATE_PERFORM:
      /* read/write data if it is ready to do so */
      easy->result = Curl_readwrite(easy->easy_conn, &done);
Loading