static CURLcode wssh_statemach_act()

in libs/curl/lib/vssh/wolfssh.c [441:898]


static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block)
{
  CURLcode result = CURLE_OK;
  struct connectdata *conn = data->conn;
  struct ssh_conn *sshc = &conn->proto.sshc;
  struct SSHPROTO *sftp_scp = data->req.p.ssh;
  WS_SFTPNAME *name;
  int rc = 0;
  *block = FALSE; /* we are not blocking by default */

  do {
    switch(sshc->state) {
    case SSH_INIT:
      state(data, SSH_S_STARTUP);
      break;

    case SSH_S_STARTUP:
      rc = wolfSSH_connect(sshc->ssh_session);
      if(rc != WS_SUCCESS)
        rc = wolfSSH_get_error(sshc->ssh_session);
      if(rc == WS_WANT_READ) {
        *block = TRUE;
        conn->waitfor = KEEP_RECV;
        return CURLE_OK;
      }
      else if(rc == WS_WANT_WRITE) {
        *block = TRUE;
        conn->waitfor = KEEP_SEND;
        return CURLE_OK;
      }
      else if(rc != WS_SUCCESS) {
        state(data, SSH_STOP);
        return CURLE_SSH;
      }
      infof(data, "wolfssh connected");
      state(data, SSH_STOP);
      break;
    case SSH_STOP:
      break;

    case SSH_SFTP_INIT:
      rc = wolfSSH_SFTP_connect(sshc->ssh_session);
      if(rc != WS_SUCCESS)
        rc = wolfSSH_get_error(sshc->ssh_session);
      if(rc == WS_WANT_READ) {
        *block = TRUE;
        conn->waitfor = KEEP_RECV;
        return CURLE_OK;
      }
      else if(rc == WS_WANT_WRITE) {
        *block = TRUE;
        conn->waitfor = KEEP_SEND;
        return CURLE_OK;
      }
      else if(rc == WS_SUCCESS) {
        infof(data, "wolfssh SFTP connected");
        state(data, SSH_SFTP_REALPATH);
      }
      else {
        failf(data, "wolfssh SFTP connect error %d", rc);
        return CURLE_SSH;
      }
      break;
    case SSH_SFTP_REALPATH:
      name = wolfSSH_SFTP_RealPath(sshc->ssh_session, (char *)".");
      rc = wolfSSH_get_error(sshc->ssh_session);
      if(rc == WS_WANT_READ) {
        *block = TRUE;
        conn->waitfor = KEEP_RECV;
        return CURLE_OK;
      }
      else if(rc == WS_WANT_WRITE) {
        *block = TRUE;
        conn->waitfor = KEEP_SEND;
        return CURLE_OK;
      }
      else if(name && (rc == WS_SUCCESS)) {
        sshc->homedir = Curl_memdup0(name->fName, name->fSz);
        if(!sshc->homedir)
          sshc->actualcode = CURLE_OUT_OF_MEMORY;
        wolfSSH_SFTPNAME_list_free(name);
        state(data, SSH_STOP);
        return CURLE_OK;
      }
      failf(data, "wolfssh SFTP realpath %d", rc);
      return CURLE_SSH;

    case SSH_SFTP_QUOTE_INIT:
      result = Curl_getworkingpath(data, sshc->homedir, &sftp_scp->path);
      if(result) {
        sshc->actualcode = result;
        state(data, SSH_STOP);
        break;
      }

      if(data->set.quote) {
        infof(data, "Sending quote commands");
        sshc->quote_item = data->set.quote;
        state(data, SSH_SFTP_QUOTE);
      }
      else {
        state(data, SSH_SFTP_GETINFO);
      }
      break;
    case SSH_SFTP_GETINFO:
      if(data->set.get_filetime) {
        state(data, SSH_SFTP_FILETIME);
      }
      else {
        state(data, SSH_SFTP_TRANS_INIT);
      }
      break;
    case SSH_SFTP_TRANS_INIT:
      if(data->state.upload)
        state(data, SSH_SFTP_UPLOAD_INIT);
      else {
        if(sftp_scp->path[strlen(sftp_scp->path)-1] == '/')
          state(data, SSH_SFTP_READDIR_INIT);
        else
          state(data, SSH_SFTP_DOWNLOAD_INIT);
      }
      break;
    case SSH_SFTP_UPLOAD_INIT: {
      word32 flags;
      WS_SFTP_FILEATRB createattrs;
      if(data->state.resume_from) {
        WS_SFTP_FILEATRB attrs;
        if(data->state.resume_from < 0) {
          rc = wolfSSH_SFTP_STAT(sshc->ssh_session, sftp_scp->path,
                                 &attrs);
          if(rc != WS_SUCCESS)
            break;

          if(rc) {
            data->state.resume_from = 0;
          }
          else {
            curl_off_t size = ((curl_off_t)attrs.sz[1] << 32) | attrs.sz[0];
            if(size < 0) {
              failf(data, "Bad file size (%" CURL_FORMAT_CURL_OFF_T ")", size);
              return CURLE_BAD_DOWNLOAD_RESUME;
            }
            data->state.resume_from = size;
          }
        }
      }

      if(data->set.remote_append)
        /* Try to open for append, but create if nonexisting */
        flags = WOLFSSH_FXF_WRITE|WOLFSSH_FXF_CREAT|WOLFSSH_FXF_APPEND;
      else if(data->state.resume_from > 0)
        /* If we have restart position then open for append */
        flags = WOLFSSH_FXF_WRITE|WOLFSSH_FXF_APPEND;
      else
        /* Clear file before writing (normal behavior) */
        flags = WOLFSSH_FXF_WRITE|WOLFSSH_FXF_CREAT|WOLFSSH_FXF_TRUNC;

      memset(&createattrs, 0, sizeof(createattrs));
      createattrs.per = (word32)data->set.new_file_perms;
      sshc->handleSz = sizeof(sshc->handle);
      rc = wolfSSH_SFTP_Open(sshc->ssh_session, sftp_scp->path,
                             flags, &createattrs,
                             sshc->handle, &sshc->handleSz);
      if(rc == WS_FATAL_ERROR)
        rc = wolfSSH_get_error(sshc->ssh_session);
      if(rc == WS_WANT_READ) {
        *block = TRUE;
        conn->waitfor = KEEP_RECV;
        return CURLE_OK;
      }
      else if(rc == WS_WANT_WRITE) {
        *block = TRUE;
        conn->waitfor = KEEP_SEND;
        return CURLE_OK;
      }
      else if(rc == WS_SUCCESS) {
        infof(data, "wolfssh SFTP open succeeded");
      }
      else {
        failf(data, "wolfssh SFTP upload open failed: %d", rc);
        return CURLE_SSH;
      }
      state(data, SSH_SFTP_DOWNLOAD_STAT);

      /* If we have a restart point then we need to seek to the correct
         position. */
      if(data->state.resume_from > 0) {
        /* Let's read off the proper amount of bytes from the input. */
        int seekerr = CURL_SEEKFUNC_OK;
        if(data->set.seek_func) {
          Curl_set_in_callback(data, true);
          seekerr = data->set.seek_func(data->set.seek_client,
                                        data->state.resume_from, SEEK_SET);
          Curl_set_in_callback(data, false);
        }

        if(seekerr != CURL_SEEKFUNC_OK) {
          curl_off_t passed = 0;

          if(seekerr != CURL_SEEKFUNC_CANTSEEK) {
            failf(data, "Could not seek stream");
            return CURLE_FTP_COULDNT_USE_REST;
          }
          /* seekerr == CURL_SEEKFUNC_CANTSEEK (cannot seek to offset) */
          do {
            char scratch[4*1024];
            size_t readthisamountnow =
              (data->state.resume_from - passed >
                (curl_off_t)sizeof(scratch)) ?
              sizeof(scratch) : curlx_sotouz(data->state.resume_from - passed);

            size_t actuallyread;
            Curl_set_in_callback(data, true);
            actuallyread = data->state.fread_func(scratch, 1,
                                                  readthisamountnow,
                                                  data->state.in);
            Curl_set_in_callback(data, false);

            passed += actuallyread;
            if((actuallyread == 0) || (actuallyread > readthisamountnow)) {
              /* this checks for greater-than only to make sure that the
                 CURL_READFUNC_ABORT return code still aborts */
              failf(data, "Failed to read data");
              return CURLE_FTP_COULDNT_USE_REST;
            }
          } while(passed < data->state.resume_from);
        }

        /* now, decrease the size of the read */
        if(data->state.infilesize > 0) {
          data->state.infilesize -= data->state.resume_from;
          data->req.size = data->state.infilesize;
          Curl_pgrsSetUploadSize(data, data->state.infilesize);
        }

        sshc->offset += data->state.resume_from;
      }
      if(data->state.infilesize > 0) {
        data->req.size = data->state.infilesize;
        Curl_pgrsSetUploadSize(data, data->state.infilesize);
      }
      /* upload data */
      Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE);

      /* not set by Curl_xfer_setup to preserve keepon bits */
      conn->sockfd = conn->writesockfd;

      if(result) {
        state(data, SSH_SFTP_CLOSE);
        sshc->actualcode = result;
      }
      else {
        /* store this original bitmask setup to use later on if we cannot
           figure out a "real" bitmask */
        sshc->orig_waitfor = data->req.keepon;

        /* we want to use the _sending_ function even when the socket turns
           out readable as the underlying libssh2 sftp send function will deal
           with both accordingly */
        data->state.select_bits = CURL_CSELECT_OUT;

        /* since we do not really wait for anything at this point, we want the
           state machine to move on as soon as possible so we set a very short
           timeout here */
        Curl_expire(data, 0, EXPIRE_RUN_NOW);

        state(data, SSH_STOP);
      }
      break;
    }
    case SSH_SFTP_DOWNLOAD_INIT:
      sshc->handleSz = sizeof(sshc->handle);
      rc = wolfSSH_SFTP_Open(sshc->ssh_session, sftp_scp->path,
                             WOLFSSH_FXF_READ, NULL,
                             sshc->handle, &sshc->handleSz);
      if(rc == WS_FATAL_ERROR)
        rc = wolfSSH_get_error(sshc->ssh_session);
      if(rc == WS_WANT_READ) {
        *block = TRUE;
        conn->waitfor = KEEP_RECV;
        return CURLE_OK;
      }
      else if(rc == WS_WANT_WRITE) {
        *block = TRUE;
        conn->waitfor = KEEP_SEND;
        return CURLE_OK;
      }
      else if(rc == WS_SUCCESS) {
        infof(data, "wolfssh SFTP open succeeded");
        state(data, SSH_SFTP_DOWNLOAD_STAT);
        return CURLE_OK;
      }

      failf(data, "wolfssh SFTP open failed: %d", rc);
      return CURLE_SSH;

    case SSH_SFTP_DOWNLOAD_STAT: {
      WS_SFTP_FILEATRB attrs;
      curl_off_t size;

      rc = wolfSSH_SFTP_STAT(sshc->ssh_session, sftp_scp->path, &attrs);
      if(rc == WS_FATAL_ERROR)
        rc = wolfSSH_get_error(sshc->ssh_session);
      if(rc == WS_WANT_READ) {
        *block = TRUE;
        conn->waitfor = KEEP_RECV;
        return CURLE_OK;
      }
      else if(rc == WS_WANT_WRITE) {
        *block = TRUE;
        conn->waitfor = KEEP_SEND;
        return CURLE_OK;
      }
      else if(rc == WS_SUCCESS) {
        infof(data, "wolfssh STAT succeeded");
      }
      else {
        failf(data, "wolfssh SFTP open failed: %d", rc);
        data->req.size = -1;
        data->req.maxdownload = -1;
        Curl_pgrsSetDownloadSize(data, -1);
        return CURLE_SSH;
      }

      size = ((curl_off_t)attrs.sz[1] <<32) | attrs.sz[0];

      data->req.size = size;
      data->req.maxdownload = size;
      Curl_pgrsSetDownloadSize(data, size);

      infof(data, "SFTP download %" CURL_FORMAT_CURL_OFF_T " bytes", size);

      /* We cannot seek with wolfSSH so resuming and range requests are not
         possible */
      if(data->state.use_range || data->state.resume_from) {
        infof(data, "wolfSSH cannot do range/seek on SFTP");
        return CURLE_BAD_DOWNLOAD_RESUME;
      }

      /* Setup the actual download */
      if(data->req.size == 0) {
        /* no data to transfer */
        Curl_xfer_setup_nop(data);
        infof(data, "File already completely downloaded");
        state(data, SSH_STOP);
        break;
      }
      Curl_xfer_setup1(data, CURL_XFER_RECV, data->req.size, FALSE);

      /* not set by Curl_xfer_setup to preserve keepon bits */
      conn->writesockfd = conn->sockfd;

      /* we want to use the _receiving_ function even when the socket turns
         out writableable as the underlying libssh2 recv function will deal
         with both accordingly */
      data->state.select_bits = CURL_CSELECT_IN;

      if(result) {
        /* this should never occur; the close state should be entered
           at the time the error occurs */
        state(data, SSH_SFTP_CLOSE);
        sshc->actualcode = result;
      }
      else {
        state(data, SSH_STOP);
      }
      break;
    }
    case SSH_SFTP_CLOSE:
      if(sshc->handleSz)
        rc = wolfSSH_SFTP_Close(sshc->ssh_session, sshc->handle,
                                sshc->handleSz);
      else
        rc = WS_SUCCESS; /* directory listing */
      if(rc == WS_WANT_READ) {
        *block = TRUE;
        conn->waitfor = KEEP_RECV;
        return CURLE_OK;
      }
      else if(rc == WS_WANT_WRITE) {
        *block = TRUE;
        conn->waitfor = KEEP_SEND;
        return CURLE_OK;
      }
      else if(rc == WS_SUCCESS) {
        state(data, SSH_STOP);
        return CURLE_OK;
      }

      failf(data, "wolfssh SFTP CLOSE failed: %d", rc);
      return CURLE_SSH;

    case SSH_SFTP_READDIR_INIT:
      Curl_pgrsSetDownloadSize(data, -1);
      if(data->req.no_body) {
        state(data, SSH_STOP);
        break;
      }
      state(data, SSH_SFTP_READDIR);
      break;

    case SSH_SFTP_READDIR:
      name = wolfSSH_SFTP_LS(sshc->ssh_session, sftp_scp->path);
      if(!name)
        rc = wolfSSH_get_error(sshc->ssh_session);
      else
        rc = WS_SUCCESS;

      if(rc == WS_WANT_READ) {
        *block = TRUE;
        conn->waitfor = KEEP_RECV;
        return CURLE_OK;
      }
      else if(rc == WS_WANT_WRITE) {
        *block = TRUE;
        conn->waitfor = KEEP_SEND;
        return CURLE_OK;
      }
      else if(name && (rc == WS_SUCCESS)) {
        WS_SFTPNAME *origname = name;
        result = CURLE_OK;
        while(name) {
          char *line = aprintf("%s\n",
                               data->set.list_only ?
                               name->fName : name->lName);
          if(!line) {
            state(data, SSH_SFTP_CLOSE);
            sshc->actualcode = CURLE_OUT_OF_MEMORY;
            break;
          }
          result = Curl_client_write(data, CLIENTWRITE_BODY,
                                     line, strlen(line));
          free(line);
          if(result) {
            sshc->actualcode = result;
            break;
          }
          name = name->next;
        }
        wolfSSH_SFTPNAME_list_free(origname);
        state(data, SSH_STOP);
        return result;
      }
      failf(data, "wolfssh SFTP ls failed: %d", rc);
      return CURLE_SSH;

    case SSH_SFTP_SHUTDOWN:
      Curl_safefree(sshc->homedir);
      wolfSSH_free(sshc->ssh_session);
      wolfSSH_CTX_free(sshc->ctx);
      state(data, SSH_STOP);
      return CURLE_OK;
    default:
      break;
    }
  } while(!rc && (sshc->state != SSH_STOP));
  return result;
}