Skip to content
multi.c 85.3 KiB
Newer Older
Daniel Stenberg's avatar
Daniel Stenberg committed
    switch(data->mstate) {
    case CURLM_STATE_INIT:
      /* init this transfer. */
Daniel Stenberg's avatar
Daniel Stenberg committed
      data->result=Curl_pretransfer(data);
Daniel Stenberg's avatar
Daniel Stenberg committed
      if(CURLE_OK == data->result) {
Daniel Stenberg's avatar
Daniel Stenberg committed
        multistate(data, CURLM_STATE_CONNECT);
    case CURLM_STATE_CONNECT_PEND:
      /* We will stay here until there is a connection available. Then
         we try again in the CURLM_STATE_CONNECT state. */
      break;

      /* Connect. We want to get a connection identifier filled in. */
      Curl_pgrsTime(data, TIMER_STARTSINGLE);
Daniel Stenberg's avatar
Daniel Stenberg committed
      data->result = Curl_connect(data, &data->easy_conn,
Daniel Stenberg's avatar
Daniel Stenberg committed
      if(CURLE_NO_CONNECTION_AVAILABLE == data->result) {
        /* There was no connection available. We will go to the pending
           state and wait for an available connection. */
Daniel Stenberg's avatar
Daniel Stenberg committed
        multistate(data, CURLM_STATE_CONNECT_PEND);
        data->result = CURLE_OK;
Daniel Stenberg's avatar
Daniel Stenberg committed
      if(CURLE_OK == data->result) {
        /* Add this handle to the send or pend pipeline */
Daniel Stenberg's avatar
Daniel Stenberg committed
        data->result = Curl_add_handle_to_pipeline(data, data->easy_conn);
        if(CURLE_OK != data->result)
          if(async)
            /* We're now waiting for an asynchronous name lookup */
Daniel Stenberg's avatar
Daniel Stenberg committed
            multistate(data, CURLM_STATE_WAITRESOLVE);
            /* after the connect has been sent off, go WAITCONNECT unless the
               protocol connect is already done and we can go directly to
            result = CURLM_CALL_MULTI_PERFORM;

            if(protocol_connect)
Daniel Stenberg's avatar
Daniel Stenberg committed
              multistate(data, multi->pipelining_enabled?
#ifndef CURL_DISABLE_HTTP
Daniel Stenberg's avatar
Daniel Stenberg committed
              if(data->easy_conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT)
                multistate(data, CURLM_STATE_WAITPROXYCONNECT);
Daniel Stenberg's avatar
Daniel Stenberg committed
                multistate(data, CURLM_STATE_WAITCONNECT);
    case CURLM_STATE_WAITRESOLVE:
      /* awaiting an asynch name resolve to complete */
    {
      struct Curl_dns_entry *dns = NULL;

      /* check if we have the name resolved by now */
Daniel Stenberg's avatar
Daniel Stenberg committed
      data->result = Curl_resolver_is_resolved(data->easy_conn, &dns);
      /* Update sockets here, because the socket(s) may have been
         closed and the application thus needs to be told, even if it
         is likely that the same socket(s) will again be used further
         down.  If the name has not yet been resolved, it is likely
         that new sockets have been opened in an attempt to contact
         another resolver. */
Daniel Stenberg's avatar
Daniel Stenberg committed
      singlesocket(multi, data);
        /* Perform the next step in the connection phase, and then move on
           to the WAITCONNECT state */
Daniel Stenberg's avatar
Daniel Stenberg committed
        data->result = Curl_async_resolved(data->easy_conn,
Daniel Stenberg's avatar
Daniel Stenberg committed
        if(CURLE_OK != data->result)
          /* if Curl_async_resolved() returns failure, the connection struct
             is already freed and gone */
Daniel Stenberg's avatar
Daniel Stenberg committed
          data->easy_conn = NULL;           /* no more connection */
          /* call again please so that we get the next socket setup */
          result = CURLM_CALL_MULTI_PERFORM;
          if(protocol_connect)
Daniel Stenberg's avatar
Daniel Stenberg committed
            multistate(data, multi->pipelining_enabled?
#ifndef CURL_DISABLE_HTTP
Daniel Stenberg's avatar
Daniel Stenberg committed
            if(data->easy_conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT)
              multistate(data, CURLM_STATE_WAITPROXYCONNECT);
Daniel Stenberg's avatar
Daniel Stenberg committed
              multistate(data, CURLM_STATE_WAITCONNECT);
Daniel Stenberg's avatar
Daniel Stenberg committed
      }
Daniel Stenberg's avatar
Daniel Stenberg committed
      if(CURLE_OK != data->result) {
        disconnect_conn = TRUE;
#ifndef CURL_DISABLE_HTTP
    case CURLM_STATE_WAITPROXYCONNECT:
      /* this is HTTP-specific, but sending CONNECT to a proxy is HTTP... */
Daniel Stenberg's avatar
Daniel Stenberg committed
      data->result = Curl_http_connect(data->easy_conn, &protocol_connect);
Daniel Stenberg's avatar
Daniel Stenberg committed
      if(data->easy_conn->bits.proxy_connect_closed) {
        if(data->set.errorbuffer)
          data->set.errorbuffer[0] = '\0';
        data->state.errorbuf = FALSE;
Daniel Stenberg's avatar
Daniel Stenberg committed
        data->result = CURLE_OK;
Daniel Stenberg's avatar
Daniel Stenberg committed
        multistate(data, CURLM_STATE_CONNECT);
Daniel Stenberg's avatar
Daniel Stenberg committed
      else if(CURLE_OK == data->result) {
        if(data->easy_conn->tunnel_state[FIRSTSOCKET] == TUNNEL_COMPLETE)
          multistate(data, CURLM_STATE_WAITCONNECT);
    case CURLM_STATE_WAITCONNECT:
      /* awaiting a completion of an asynch connect */
Daniel Stenberg's avatar
Daniel Stenberg committed
      data->result = Curl_is_connected(data->easy_conn,
Daniel Stenberg's avatar
Daniel Stenberg committed
        if(!data->result)
          /* if everything is still fine we do the protocol-specific connect
             setup */
Daniel Stenberg's avatar
Daniel Stenberg committed
          data->result = Curl_protocol_connect(data->easy_conn,
Daniel Stenberg's avatar
Daniel Stenberg committed
      if(CURLE_OK != data->result) {
        /* Just break, the cleaning up is handled all in one place */
        disconnect_conn = TRUE;
      if(connected) {
        if(!protocol_connect) {
          /* We have a TCP connection, but 'protocol_connect' may be false
             and then we continue to 'STATE_PROTOCONNECT'. If protocol
             connect is TRUE, we move on to STATE_DO.
             BUT if we are using a proxy we must change to WAITPROXYCONNECT
Daniel Stenberg's avatar
Daniel Stenberg committed
          if(data->easy_conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT)
            multistate(data, CURLM_STATE_WAITPROXYCONNECT);
Daniel Stenberg's avatar
Daniel Stenberg committed
            multistate(data, CURLM_STATE_PROTOCONNECT);
          /* after the connect has completed, go WAITDO or DO */
Daniel Stenberg's avatar
Daniel Stenberg committed
          multistate(data, multi->pipelining_enabled?
    case CURLM_STATE_PROTOCONNECT:
      /* protocol-specific connect phase */
Daniel Stenberg's avatar
Daniel Stenberg committed
      data->result = Curl_protocol_connecting(data->easy_conn,
Daniel Stenberg's avatar
Daniel Stenberg committed
      if((data->result == CURLE_OK) && protocol_connect) {
        /* after the connect has completed, go WAITDO or DO */
Daniel Stenberg's avatar
Daniel Stenberg committed
        multistate(data, multi->pipelining_enabled?
Daniel Stenberg's avatar
Daniel Stenberg committed
      else if(data->result) {
        Curl_posttransfer(data);
Daniel Stenberg's avatar
Daniel Stenberg committed
        Curl_done(&data->easy_conn, data->result, TRUE);
        disconnect_conn = TRUE;
    case CURLM_STATE_WAITDO:
      /* Wait for our turn to DO when we're pipelining requests */
#ifdef DEBUGBUILD
      infof(data, "WAITDO: Conn %ld send pipe %zu inuse %s athead %s\n",
Daniel Stenberg's avatar
Daniel Stenberg committed
            data->easy_conn->connection_id,
            data->easy_conn->send_pipe->size,
            data->easy_conn->writechannel_inuse?"TRUE":"FALSE",
Daniel Stenberg's avatar
Daniel Stenberg committed
                           data->easy_conn->send_pipe)?"TRUE":"FALSE");
Daniel Stenberg's avatar
Daniel Stenberg committed
      if(!data->easy_conn->writechannel_inuse &&
Daniel Stenberg's avatar
Daniel Stenberg committed
                        data->easy_conn->send_pipe)) {
Daniel Stenberg's avatar
Daniel Stenberg committed
        data->easy_conn->writechannel_inuse = TRUE;
        multistate(data, CURLM_STATE_DO);
      if(data->set.connect_only) {
        /* keep connection open for application to use the socket */
Daniel Stenberg's avatar
Daniel Stenberg committed
        data->easy_conn->bits.close = FALSE;
        multistate(data, CURLM_STATE_DONE);
        data->result = CURLE_OK;
        result = CURLM_CALL_MULTI_PERFORM;
Daniel Stenberg's avatar
Daniel Stenberg committed
        data->result = Curl_do(&data->easy_conn, &dophase_done);
Daniel Stenberg's avatar
Daniel Stenberg committed
        /* When Curl_do() returns failure, data->easy_conn might be NULL! */
Daniel Stenberg's avatar
Daniel Stenberg committed
        if(CURLE_OK == data->result) {
            /* some steps needed for wildcard matching */
            if(data->set.wildcardmatch) {
              struct WildcardData *wc = &data->wildcard;
              if(wc->state == CURLWC_DONE || wc->state == CURLWC_SKIP) {
                /* skip some states if it is important */
Daniel Stenberg's avatar
Daniel Stenberg committed
                Curl_done(&data->easy_conn, CURLE_OK, FALSE);
                multistate(data, CURLM_STATE_DONE);
                result = CURLM_CALL_MULTI_PERFORM;
                break;
              }
            }
            /* DO was not completed in one function call, we must continue
               DOING... */
Daniel Stenberg's avatar
Daniel Stenberg committed
            multistate(data, CURLM_STATE_DOING);
          /* after DO, go DO_DONE... or DO_MORE */
Daniel Stenberg's avatar
Daniel Stenberg committed
          else if(data->easy_conn->bits.do_more) {
            /* we're supposed to do more, but we need to sit down, relax
               and wait a little while first */
Daniel Stenberg's avatar
Daniel Stenberg committed
            multistate(data, CURLM_STATE_DO_MORE);
            /* we're done with the DO, now DO_DONE */
Daniel Stenberg's avatar
Daniel Stenberg committed
            multistate(data, CURLM_STATE_DO_DONE);
Daniel Stenberg's avatar
Daniel Stenberg committed
        else if((CURLE_SEND_ERROR == data->result) &&
                data->easy_conn->bits.reuse) {
          /*
           * In this situation, a connection that we were trying to use
           * may have unexpectedly died.  If possible, send the connection
           * back to the CONNECT phase so we can try again.
           */
          char *newurl = NULL;
          followtype follow=FOLLOW_NONE;
          CURLcode drc;
Daniel Stenberg's avatar
Daniel Stenberg committed
          drc = Curl_retry_request(data->easy_conn, &newurl);
          if(drc) {
            /* a failure here pretty much implies an out of memory */
Daniel Stenberg's avatar
Daniel Stenberg committed
            data->result = drc;
            retry = (newurl)?TRUE:FALSE;
          Curl_posttransfer(data);
Daniel Stenberg's avatar
Daniel Stenberg committed
          drc = Curl_done(&data->easy_conn, data->result, FALSE);

          /* When set to retry the connection, we must to go back to
           * the CONNECT state */
          if(retry) {
            if((drc == CURLE_OK) || (drc == CURLE_SEND_ERROR)) {
              drc = Curl_follow(data, newurl, follow);
Daniel Stenberg's avatar
Daniel Stenberg committed
                multistate(data, CURLM_STATE_CONNECT);
Daniel Stenberg's avatar
Daniel Stenberg committed
                data->result = CURLE_OK;
Daniel Stenberg's avatar
Daniel Stenberg committed
                data->result = drc;
                free(newurl);
              }
            }
            else {
              /* done didn't return OK or SEND_ERROR */
Daniel Stenberg's avatar
Daniel Stenberg committed
              data->result = drc;
              free(newurl);
            }
          }
          else {
            /* Have error handler disconnect conn if we can't retry */
            disconnect_conn = TRUE;
          }
        }
          Curl_posttransfer(data);
Daniel Stenberg's avatar
Daniel Stenberg committed
          if(data->easy_conn)
            Curl_done(&data->easy_conn, data->result, FALSE);
          disconnect_conn = TRUE;
    case CURLM_STATE_DOING:
      /* we continue DOING until the DO phase is complete */
Daniel Stenberg's avatar
Daniel Stenberg committed
      data->result = Curl_protocol_doing(data->easy_conn,
Daniel Stenberg's avatar
Daniel Stenberg committed
      if(CURLE_OK == data->result) {
          /* after DO, go DO_DONE or DO_MORE */
Daniel Stenberg's avatar
Daniel Stenberg committed
          multistate(data, data->easy_conn->bits.do_more?
                     CURLM_STATE_DO_MORE:
                     CURLM_STATE_DO_DONE);
          result = CURLM_CALL_MULTI_PERFORM;
        Curl_posttransfer(data);
Daniel Stenberg's avatar
Daniel Stenberg committed
        Curl_done(&data->easy_conn, data->result, FALSE);
        disconnect_conn = TRUE;
      /*
       * When we are connected, DO MORE and then go DO_DONE
       */
Daniel Stenberg's avatar
Daniel Stenberg committed
      data->result = Curl_do_more(data->easy_conn, &control);
      /* No need to remove this handle from the send pipeline here since that
         is done in Curl_done() */
Daniel Stenberg's avatar
Daniel Stenberg committed
      if(CURLE_OK == data->result) {
        if(control) {
          /* if positive, advance to DO_DONE
             if negative, go back to DOING */
Daniel Stenberg's avatar
Daniel Stenberg committed
          multistate(data, control==1?
                     CURLM_STATE_DO_DONE:
                     CURLM_STATE_DOING);
        else
          /* stay in DO_MORE */
          result = CURLM_OK;
      }
      else {
        /* failure detected */
        Curl_posttransfer(data);
Daniel Stenberg's avatar
Daniel Stenberg committed
        Curl_done(&data->easy_conn, data->result, FALSE);
        disconnect_conn = TRUE;
      /* Move ourselves from the send to recv pipeline */
Daniel Stenberg's avatar
Daniel Stenberg committed
      Curl_move_handle_from_send_to_recv_pipe(data, data->easy_conn);
      /* Check if we can move pending requests to send pipe */
      Curl_multi_process_pending_handles(multi);

      /* Only perform the transfer if there's a good socket to work with.
         Having both BAD is a signal to skip immediately to DONE */
      if((data->easy_conn->sockfd != CURL_SOCKET_BAD) ||
         (data->easy_conn->writesockfd != CURL_SOCKET_BAD))
        multistate(data, CURLM_STATE_WAITPERFORM);
      else
        multistate(data, CURLM_STATE_DONE);
      result = CURLM_CALL_MULTI_PERFORM;
      break;

    case CURLM_STATE_WAITPERFORM:
      /* Wait for our turn to PERFORM */
Daniel Stenberg's avatar
Daniel Stenberg committed
      if(!data->easy_conn->readchannel_inuse &&
Daniel Stenberg's avatar
Daniel Stenberg committed
                        data->easy_conn->recv_pipe)) {
Daniel Stenberg's avatar
Daniel Stenberg committed
        data->easy_conn->readchannel_inuse = TRUE;
        multistate(data, CURLM_STATE_PERFORM);
#ifdef DEBUGBUILD
        infof(data, "WAITPERFORM: Conn %ld recv pipe %zu inuse %s athead %s\n",
Daniel Stenberg's avatar
Daniel Stenberg committed
              data->easy_conn->connection_id,
              data->easy_conn->recv_pipe->size,
              data->easy_conn->readchannel_inuse?"TRUE":"FALSE",
Daniel Stenberg's avatar
Daniel Stenberg committed
                             data->easy_conn->recv_pipe)?"TRUE":"FALSE");
    case CURLM_STATE_TOOFAST: /* limit-rate exceeded in either direction */
      /* if both rates are within spec, resume transfer */
Daniel Stenberg's avatar
Daniel Stenberg committed
      if(Curl_pgrsUpdate(data->easy_conn))
        data->result = CURLE_ABORTED_BY_CALLBACK;
Daniel Stenberg's avatar
Daniel Stenberg committed
        data->result = Curl_speedcheck(data, now);
      if(( (data->set.max_send_speed == 0) ||
           (data->progress.ulspeed < data->set.max_send_speed ))  &&
         ( (data->set.max_recv_speed == 0) ||
           (data->progress.dlspeed < data->set.max_recv_speed)))
Daniel Stenberg's avatar
Daniel Stenberg committed
        multistate(data, CURLM_STATE_PERFORM);
      {
      char *newurl = NULL;
      bool retry = FALSE;

      /* check if over send speed */
      if((data->set.max_send_speed > 0) &&
         (data->progress.ulspeed > data->set.max_send_speed)) {
Daniel Stenberg's avatar
Daniel Stenberg committed
        multistate(data, CURLM_STATE_TOOFAST);

        /* calculate upload rate-limitation timeout. */
        buffersize = (int)(data->set.buffer_size ?
                           data->set.buffer_size : BUFSIZE);
        timeout_ms = Curl_sleep_time(data->set.max_send_speed,
                                     data->progress.ulspeed, buffersize);
        Curl_expire(data, timeout_ms);
        break;
      }

      /* check if over recv speed */
      if((data->set.max_recv_speed > 0) &&
         (data->progress.dlspeed > data->set.max_recv_speed)) {
Daniel Stenberg's avatar
Daniel Stenberg committed
        multistate(data, CURLM_STATE_TOOFAST);

         /* Calculate download rate-limitation timeout. */
        buffersize = (int)(data->set.buffer_size ?
                           data->set.buffer_size : BUFSIZE);
        timeout_ms = Curl_sleep_time(data->set.max_recv_speed,
                                     data->progress.dlspeed, buffersize);
        Curl_expire(data, timeout_ms);
      /* read/write data if it is ready to do so */
Daniel Stenberg's avatar
Daniel Stenberg committed
      data->result = Curl_readwrite(data->easy_conn, &done);
      if(!(k->keepon & KEEP_RECV)) {
        /* We're done receiving */
Daniel Stenberg's avatar
Daniel Stenberg committed
        data->easy_conn->readchannel_inuse = FALSE;
      if(!(k->keepon & KEEP_SEND)) {
        /* We're done sending */
Daniel Stenberg's avatar
Daniel Stenberg committed
        data->easy_conn->writechannel_inuse = FALSE;
Daniel Stenberg's avatar
Daniel Stenberg committed
      if(done || (data->result == CURLE_RECV_ERROR)) {
        /* If CURLE_RECV_ERROR happens early enough, we assume it was a race
         * condition and the server closed the re-used connection exactly when
         * we wanted to use it, so figure out if that is indeed the case.
         */
Daniel Stenberg's avatar
Daniel Stenberg committed
        CURLcode ret = Curl_retry_request(data->easy_conn, &newurl);
        if(!ret)
          retry = (newurl)?TRUE:FALSE;

        if(retry) {
          /* if we are to retry, set the result to OK and consider the
             request as done */
Daniel Stenberg's avatar
Daniel Stenberg committed
          data->result = CURLE_OK;
Daniel Stenberg's avatar
Daniel Stenberg committed
      if(data->result) {
        /*
         * The transfer phase returned error, we mark the connection to get
         * closed to prevent being re-used. This is because we can't possibly
         * know if the connection is in a good shape or not now.  Unless it is
         * a protocol which uses two "channels" like FTP, as then the error
         * happened in the data connection.
         */
Daniel Stenberg's avatar
Daniel Stenberg committed
        if(!(data->easy_conn->handler->flags & PROTOPT_DUAL))
          data->easy_conn->bits.close = TRUE;
        Curl_posttransfer(data);
Daniel Stenberg's avatar
Daniel Stenberg committed
        Curl_done(&data->easy_conn, data->result, FALSE);
      else if(done) {
        /* call this even if the readwrite function returned error */
        Curl_posttransfer(data);
        /* we're no longer receiving */
Daniel Stenberg's avatar
Daniel Stenberg committed
        Curl_removeHandleFromPipeline(data, data->easy_conn->recv_pipe);
Daniel Stenberg's avatar
Daniel Stenberg committed
        if(data->easy_conn->recv_pipe->head)
          Curl_expire(data->easy_conn->recv_pipe->head->ptr, 1);

        /* Check if we can move pending requests to send pipe */
        Curl_multi_process_pending_handles(multi);
        /* When we follow redirects or is set to retry the connection, we must
           to go back to the CONNECT state */
        if(data->req.newurl || retry) {
          if(!retry) {
            /* if the URL is a follow-location and not just a retried request
               then figure out the URL here */
            newurl = data->req.newurl;
            data->req.newurl = NULL;
Daniel Stenberg's avatar
Daniel Stenberg committed
          data->result = Curl_done(&data->easy_conn, CURLE_OK, FALSE);
          if(CURLE_OK == data->result) {
            data->result = Curl_follow(data, newurl, follow);
            if(CURLE_OK == data->result) {
              multistate(data, CURLM_STATE_CONNECT);
              result = CURLM_CALL_MULTI_PERFORM;
              newurl = NULL; /* handed over the memory ownership to
                                Curl_follow(), make sure we don't free() it
                                here */
            }
        else {
          /* after the transfer is done, go DONE */

          /* but first check to see if we got a location info even though we're
             not following redirects */
          if(data->req.location) {
            newurl = data->req.location;
            data->req.location = NULL;
Daniel Stenberg's avatar
Daniel Stenberg committed
            data->result = Curl_follow(data, newurl, FOLLOW_FAKE);
            if(CURLE_OK == data->result)
Yang Tse's avatar
Yang Tse committed
              newurl = NULL; /* allocation was handed over Curl_follow() */
            else
Daniel Stenberg's avatar
Daniel Stenberg committed
          multistate(data, CURLM_STATE_DONE);
      /* this state is highly transient, so run another loop after this */
      result = CURLM_CALL_MULTI_PERFORM;
Daniel Stenberg's avatar
Daniel Stenberg committed
      if(data->easy_conn) {
        /* Remove ourselves from the receive pipeline, if we are there. */
        Curl_removeHandleFromPipeline(data, data->easy_conn->recv_pipe);
        /* Check if we can move pending requests to send pipe */
        Curl_multi_process_pending_handles(multi);
        res = Curl_done(&data->easy_conn, CURLE_OK, FALSE);

        /* allow a previously set error code take precedence */
        if(!data->result)
          data->result = res;

        /*
         * If there are other handles on the pipeline, Curl_done won't set
         * easy_conn to NULL.  In such a case, curl_multi_remove_handle() can
         * access free'd data, if the connection is free'd and the handle
         * removed before we perform the processing in CURLM_STATE_COMPLETED
         */
Daniel Stenberg's avatar
Daniel Stenberg committed
        if(data->easy_conn)
          data->easy_conn = NULL;
      if(data->set.wildcardmatch) {
        if(data->wildcard.state != CURLWC_DONE) {
          /* if a wildcard is set and we are not ending -> lets start again
             with CURLM_STATE_INIT */
Daniel Stenberg's avatar
Daniel Stenberg committed
          multistate(data, CURLM_STATE_INIT);
      /* after we have DONE what we're supposed to do, go COMPLETED, and
         it doesn't matter what the Curl_done() returned! */
Daniel Stenberg's avatar
Daniel Stenberg committed
      multistate(data, CURLM_STATE_COMPLETED);
    case CURLM_STATE_COMPLETED:
      /* this is a completed transfer, it is likely to still be connected */
      /* This node should be delinked from the list now and we should post
         an information message that we are complete. */

      /* Important: reset the conn pointer so that we don't point to memory
         that could be freed anytime */
Daniel Stenberg's avatar
Daniel Stenberg committed
      data->easy_conn = NULL;

      Curl_expire(data, 0); /* stop all timers */
    case CURLM_STATE_MSGSENT:
      return CURLM_OK; /* do nothing */

Daniel Stenberg's avatar
Daniel Stenberg committed
    if(data->mstate < CURLM_STATE_COMPLETED) {
      if(CURLE_OK != data->result) {
        /*
         * If an error was returned, and we aren't in completed state now,
         * then we go to completed and consider this transfer aborted.
         */

        /* NOTE: no attempt to disconnect connections must be made
           in the case blocks above - cleanup happens only here */

        data->state.pipe_broke = FALSE;
Daniel Stenberg's avatar
Daniel Stenberg committed
        if(data->easy_conn) {
          /* if this has a connection, unsubscribe from the pipelines */
Daniel Stenberg's avatar
Daniel Stenberg committed
          data->easy_conn->writechannel_inuse = FALSE;
          data->easy_conn->readchannel_inuse = FALSE;
          Curl_removeHandleFromPipeline(data,
Daniel Stenberg's avatar
Daniel Stenberg committed
                                        data->easy_conn->send_pipe);
          Curl_removeHandleFromPipeline(data,
Daniel Stenberg's avatar
Daniel Stenberg committed
                                        data->easy_conn->recv_pipe);
          /* Check if we can move pending requests to send pipe */
          Curl_multi_process_pending_handles(multi);
Yang Tse's avatar
Yang Tse committed
          if(disconnect_conn) {
            /* disconnect properly */
Daniel Stenberg's avatar
Daniel Stenberg committed
            Curl_disconnect(data->easy_conn, /* dead_connection */ FALSE);
Yang Tse's avatar
Yang Tse committed
            /* This is where we make sure that the easy_conn pointer is reset.
               We don't have to do this in every case block above where a
               failure is detected */
Daniel Stenberg's avatar
Daniel Stenberg committed
            data->easy_conn = NULL;
Daniel Stenberg's avatar
Daniel Stenberg committed
        else if(data->mstate == CURLM_STATE_CONNECT) {
Yang Tse's avatar
Yang Tse committed
          /* Curl_connect() failed */
          (void)Curl_posttransfer(data);
Daniel Stenberg's avatar
Daniel Stenberg committed
        multistate(data, CURLM_STATE_COMPLETED);
      /* if there's still a connection to use, call the progress function */
Daniel Stenberg's avatar
Daniel Stenberg committed
      else if(data->easy_conn && Curl_pgrsUpdate(data->easy_conn)) {
        /* aborted due to progress callback return code must close the
           connection */
        data->result = CURLE_ABORTED_BY_CALLBACK;
Daniel Stenberg's avatar
Daniel Stenberg committed
        data->easy_conn->bits.close = TRUE;

        /* if not yet in DONE state, go there, otherwise COMPLETED */
Daniel Stenberg's avatar
Daniel Stenberg committed
        multistate(data, (data->mstate < CURLM_STATE_DONE)?
                   CURLM_STATE_DONE: CURLM_STATE_COMPLETED);
        result = CURLM_CALL_MULTI_PERFORM;
  } WHILE_FALSE; /* just to break out from! */
Daniel Stenberg's avatar
Daniel Stenberg committed
  if(CURLM_STATE_COMPLETED == data->mstate) {
    /* now fill in the Curl_message with this info */
Daniel Stenberg's avatar
Daniel Stenberg committed
    msg = &data->msg;
    msg->extmsg.easy_handle = data;
Daniel Stenberg's avatar
Daniel Stenberg committed
    msg->extmsg.data.result = data->result;
    result = multi_addmsg(multi, msg);
Daniel Stenberg's avatar
Daniel Stenberg committed
    multistate(data, CURLM_STATE_MSGSENT);

CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
{
  struct Curl_multi *multi=(struct Curl_multi *)multi_handle;
Daniel Stenberg's avatar
Daniel Stenberg committed
  struct SessionHandle *data;
  CURLMcode returncode=CURLM_OK;
  struct Curl_tree *t;
  struct timeval now = Curl_tvnow();
Daniel Stenberg's avatar
Daniel Stenberg committed
  data=multi->easyp;
  while(data) {
Daniel Stenberg's avatar
Daniel Stenberg committed
    struct WildcardData *wc = &data->wildcard;
Daniel Stenberg's avatar
Daniel Stenberg committed
    if(data->set.wildcardmatch) {
      if(!wc->filelist) {
        CURLcode ret = Curl_wildcard_init(wc); /* init wildcard structures */
        if(ret)
          return CURLM_OUT_OF_MEMORY;
      }
    }
Daniel Stenberg's avatar
Daniel Stenberg committed
      result = multi_runsingle(multi, now, data);
    while(CURLM_CALL_MULTI_PERFORM == result);
Daniel Stenberg's avatar
Daniel Stenberg committed
    if(data->set.wildcardmatch) {
      /* destruct wildcard structures if it is needed */
      if(wc->state == CURLWC_DONE || result)
        Curl_wildcard_dtor(wc);
    }

Daniel Stenberg's avatar
Daniel Stenberg committed
    data = data->next; /* operate on next handle */
  }

  /*
   * Simply remove all expired timers from the splay since handles are dealt
   * with unconditionally by this function and curl_multi_timeout() requires
   * that already passed/handled expire times are removed from the splay.
   *
   * It is important that the 'now' value is set at the entry of this function
   * and not for the current time as it may have ticked a little while since
   * then and then we risk this loop to remove timers that actually have not
   * been handled!
    multi->timetree = Curl_splaygetbest(now, multi->timetree, &t);
    if(t)
      /* the removed may have another timeout in queue */
      (void)add_next_timeout(now, multi, t->payload);
  if(CURLM_OK >= returncode)
static void close_all_connections(struct Curl_multi *multi)
{
  struct connectdata *conn;

  conn = Curl_conncache_find_first_connection(multi->conn_cache);
  while(conn) {
    conn->data = multi->closure_handle;

    /* This will remove the connection from the cache */
    (void)Curl_disconnect(conn, FALSE);

    conn = Curl_conncache_find_first_connection(multi->conn_cache);
  }
}

Daniel Stenberg's avatar
Daniel Stenberg committed
CURLMcode curl_multi_cleanup(CURLM *multi_handle)
{
  struct Curl_multi *multi=(struct Curl_multi *)multi_handle;
Daniel Stenberg's avatar
Daniel Stenberg committed
  struct SessionHandle *data;
  struct SessionHandle *nextdata;
Daniel Stenberg's avatar
Daniel Stenberg committed
  if(GOOD_MULTI_HANDLE(multi)) {
    bool restore_pipe = FALSE;
    SIGPIPE_VARIABLE(pipe_st);
Daniel Stenberg's avatar
Daniel Stenberg committed
    multi->type = 0; /* not good anymore */
    /* Close all the connections in the connection cache */
    close_all_connections(multi);

      sigpipe_ignore(multi->closure_handle, &pipe_st);
      restore_pipe = TRUE;

      multi->closure_handle->dns.hostcache = multi->hostcache;
      Curl_hostcache_clean(multi->closure_handle,
                           multi->closure_handle->dns.hostcache);
      Curl_close(multi->closure_handle);
      multi->closure_handle = NULL;
Yang Tse's avatar
Yang Tse committed
    Curl_hash_destroy(multi->sockhash);
    multi->sockhash = NULL;

    Curl_conncache_destroy(multi->conn_cache);
    multi->conn_cache = NULL;
    /* remove the pending list of messages */
    Curl_llist_destroy(multi->msglist, NULL);
    multi->msglist = NULL;
Daniel Stenberg's avatar
Daniel Stenberg committed
    /* remove all easy handles */
Daniel Stenberg's avatar
Daniel Stenberg committed
    data = multi->easyp;
    while(data) {
      nextdata=data->next;
      if(data->dns.hostcachetype == HCACHE_MULTI) {
        /* clear out the usage of the shared DNS cache */
Daniel Stenberg's avatar
Daniel Stenberg committed
        Curl_hostcache_clean(data, data->dns.hostcache);
        data->dns.hostcache = NULL;
        data->dns.hostcachetype = HCACHE_NONE;

      /* Clear the pointer to the connection cache */
Daniel Stenberg's avatar
Daniel Stenberg committed
      data->state.conn_cache = NULL;
      data->multi = NULL; /* clear the association */
Daniel Stenberg's avatar
Daniel Stenberg committed
      data = nextdata;
    Curl_hash_destroy(multi->hostcache);
    multi->hostcache = NULL;

    /* Free the blacklists by setting them to NULL */
    Curl_pipeline_set_site_blacklist(NULL, &multi->pipelining_site_bl);
    Curl_pipeline_set_server_blacklist(NULL, &multi->pipelining_server_bl);

Daniel Stenberg's avatar
Daniel Stenberg committed
    free(multi);
    if(restore_pipe)
      sigpipe_restore(&pipe_st);
Daniel Stenberg's avatar
Daniel Stenberg committed

    return CURLM_OK;
  }
  else
    return CURLM_BAD_HANDLE;
}
/*
 * curl_multi_info_read()
 *
 * This function is the primary way for a multi/multi_socket application to
 * figure out if a transfer has ended. We MUST make this function as fast as
 * possible as it will be polled frequently and we MUST NOT scan any lists in
 * here to figure out things. We must scale fine to thousands of handles and
 * beyond. The current design is fully O(1).
 */

CURLMsg *curl_multi_info_read(CURLM *multi_handle, int *msgs_in_queue)
{
  struct Curl_multi *multi=(struct Curl_multi *)multi_handle;
  struct Curl_message *msg;
  if(GOOD_MULTI_HANDLE(multi) && Curl_llist_count(multi->msglist)) {
    /* there is one or more messages in the list */
    struct curl_llist_element *e;
    /* extract the head of the list to return */
    e = multi->msglist->head;
    msg = e->ptr;

    /* remove the extracted entry */
    Curl_llist_remove(multi->msglist, e, NULL);
    *msgs_in_queue = curlx_uztosi(Curl_llist_count(multi->msglist));
    return &msg->extmsg;
Daniel Stenberg's avatar
Daniel Stenberg committed
    return NULL;
 * singlesocket() checks what sockets we deal with and their "action state"
 * and if we have a different state in any of those sockets from last time we
 * call the callback accordingly.
 */
static void singlesocket(struct Curl_multi *multi,
Daniel Stenberg's avatar
Daniel Stenberg committed
                         struct SessionHandle *data)
  curl_socket_t socks[MAX_SOCKSPEREASYHANDLE];
  struct Curl_sh_entry *entry;
  curl_socket_t s;
  int num;
  /* Fill in the 'current' struct with the state as it is now: what sockets to
     supervise and for what actions */
Daniel Stenberg's avatar
Daniel Stenberg committed
  curraction = multi_getsock(data, socks, MAX_SOCKSPEREASYHANDLE);
  /* We have 0 .. N sockets already and we get to know about the 0 .. M
     sockets we should have from now on. Detect the differences, remove no
     longer supervised ones and add new ones */
  /* walk over the sockets we got right now */
  for(i=0; (i< MAX_SOCKSPEREASYHANDLE) &&
        (curraction & (GETSOCK_READSOCK(i) | GETSOCK_WRITESOCK(i)));
    /* get it from the hash */
    entry = Curl_hash_pick(multi->sockhash, (char *)&s, sizeof(s));
    if(entry) {
      /* yeps, already present so check if it has the same action set */
      if(entry->action == action)
        /* same, continue */
        continue;
    }
    else {
      /* this is a socket we didn't have before, add it! */
Daniel Stenberg's avatar
Daniel Stenberg committed
      entry = sh_addentry(multi->sockhash, s, data);
    /* we know (entry != NULL) at this point, see the logic above */
Daniel Stenberg's avatar
Daniel Stenberg committed
      multi->socket_cb(data,
                       s,
                       action,
                       multi->socket_userp,
                       entry->socketp);
    entry->action = action; /* store the current action state */
  }
  num = i; /* number of sockets */

  /* when we've walked over all the sockets we should have right now, we must
     make sure to detect sockets that are removed */
Daniel Stenberg's avatar
Daniel Stenberg committed
  for(i=0; i< data->numsocks; i++) {
Daniel Stenberg's avatar
Daniel Stenberg committed
    s = data->sockets[i];
        /* this is still supervised */
        s = CURL_SOCKET_BAD;
        break;

      /* this socket has been removed. Tell the app to remove it */
      remove_sock_from_hash = TRUE;

      entry = Curl_hash_pick(multi->sockhash, (char *)&s, sizeof(s));
      if(entry) {
        /* check if the socket to be removed serves a connection which has
           other easy-s in a pipeline. In this case the socket should not be
           removed. */
Daniel Stenberg's avatar
Daniel Stenberg committed
        struct connectdata *easy_conn = data->easy_conn;