From 53a8a6e5a69ad72ad6684875272babd24c3a93fd Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 24 Nov 2008 13:59:51 +0000 Subject: [PATCH] - Based on a patch by Vlad Grachov, libcurl now uses a new libssh2 0.19 function when built to support SCP and SFTP that helps the library to know in which direction a particular libssh2 operation would return EAGAIN so that libcurl knows what socket conditions to wait for before trying the function call again. Previously (and still when using libssh2 0.18 or earlier), libcurl will busy-loop in this situation when the easy interface is used! --- CHANGES | 9 ++++++ RELEASE-NOTES | 7 ++-- configure.ac | 4 +++ lib/ssh.c | 89 +++++++++++++++++++++++++++++++++++++++++---------- 4 files changed, 87 insertions(+), 22 deletions(-) diff --git a/CHANGES b/CHANGES index 3f25a54df..cd08f7dd7 100644 --- a/CHANGES +++ b/CHANGES @@ -6,6 +6,15 @@ Changelog +Daniel Stenberg (24 Nov 2008) +- Based on a patch by Vlad Grachov, libcurl now uses a new libssh2 0.19 + function when built to support SCP and SFTP that helps the library to know + in which direction a particular libssh2 operation would return EAGAIN so + that libcurl knows what socket conditions to wait for before trying the + function call again. Previously (and still when using libssh2 0.18 or + earlier), libcurl will busy-loop in this situation when the easy interface + is used! + Daniel Fandrich (20 Nov 2008) - Automatically detect OpenBSD's CA cert bundle. diff --git a/RELEASE-NOTES b/RELEASE-NOTES index a33b3c9e7..2a7f82d26 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -21,19 +21,16 @@ This release includes the following bugfixes: the same server o memory leak with HTTP GSS/kerberos authentication o removed the default use of "Pragma: no-cache" + o fix SCP/SFTP busyloop by using a new libssh2 0.19 function This release includes the following known bugs: o see docs/KNOWN_BUGS (http://curl.haxx.se/docs/knownbugs.html) -Other curl-related news: - - o - This release would not have looked like this without help, code, reports and advice from friends like these: Yang Tse, Daniel Fandrich, Jim Meyering, Christian Krause, Andreas Wurf, - Markus Koetter, Josef Wolf + Markus Koetter, Josef Wolf, Vlad Grachov Thanks! (and sorry if I forgot to mention someone) diff --git a/configure.ac b/configure.ac index f98bded54..b8eb848b3 100644 --- a/configure.ac +++ b/configure.ac @@ -1476,6 +1476,10 @@ if test X"$OPT_LIBSSH2" != Xno; then LIBSSH2_ENABLED=1 AC_DEFINE(USE_LIBSSH2, 1, [if libSSH2 is in use]) AC_SUBST(USE_LIBSSH2, [1]) + + dnl check for this function only present in libssh2 0.19+ + AC_CHECK_FUNCS( libssh2_session_block_directions ) + ) if test X"$OPT_LIBSSH2" != Xoff && diff --git a/lib/ssh.c b/lib/ssh.c index 6d2108bef..1f1650e20 100644 --- a/lib/ssh.c +++ b/lib/ssh.c @@ -40,6 +40,13 @@ #error "this requires libssh2 0.16 or later" #endif +#if !defined(HAVE_LIBSSH2_SESSION_BLOCK_DIRECTION) && \ + (LIBSSH2_VERSION_NUM >= 0x001300) +/* this is just a check for non-configure based systems to get this properly + setup if libssh2 0.19+ is used */ +#define HAVE_LIBSSH2_SESSION_BLOCK_DIRECTION 1 +#endif + #ifdef HAVE_UNISTD_H #include #endif @@ -103,6 +110,7 @@ #include "sockaddr.h" /* required for Curl_sockaddr_storage */ #include "strtoofft.h" #include "multiif.h" +#include "select.h" #define _MPRINTF_REPLACE /* use our functions only */ #include @@ -421,7 +429,14 @@ static CURLcode ssh_getworkingpath(struct connectdata *conn, return CURLE_OK; } -static CURLcode ssh_statemach_act(struct connectdata *conn) +/* + * ssh_statemach_act() runs the SSH statemachine "one round" and returns. The + * data the pointer 'block' points to will be set to TRUE if the libssh2 + * function returns LIBSSH2_ERROR_EAGAIN meaning it wants to be called again + * when the socket is ready + */ + +static CURLcode ssh_statemach_act(struct connectdata *conn, bool *block) { CURLcode result = CURLE_OK; struct SessionHandle *data = conn->data; @@ -432,8 +447,9 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) const char *fingerprint; #endif /* CURL_LIBSSH2_DEBUG */ const char *host_public_key_md5; - int rc,i; + int rc = LIBSSH2_ERROR_NONE, i; int err; + *block = 0; /* we're not blocking by default */ switch(sshc->state) { case SSH_S_STARTUP: @@ -519,6 +535,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) if(!sshc->authlist) { if((err = libssh2_session_last_errno(sshc->ssh_session)) == LIBSSH2_ERROR_EAGAIN) { + rc = LIBSSH2_ERROR_EAGAIN; break; } else { @@ -729,6 +746,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) if(!sshc->sftp_session) { if(libssh2_session_last_errno(sshc->ssh_session) == LIBSSH2_ERROR_EAGAIN) { + rc = LIBSSH2_ERROR_EAGAIN; break; } else { @@ -996,21 +1014,21 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) * This takes an extra protocol round trip. */ rc = libssh2_sftp_stat(sshc->sftp_session, sshc->quote_path2, - &sshc->quote_attrs); + &sshc->quote_attrs); if(rc == LIBSSH2_ERROR_EAGAIN) { - break; + break; } else if(rc != 0) { /* get those attributes */ - err = libssh2_sftp_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - sshc->quote_path1 = NULL; - Curl_safefree(sshc->quote_path2); - sshc->quote_path2 = NULL; - failf(data, "Attempt to get SFTP stats failed: %s", - sftp_libssh2_strerror(err)); - state(conn, SSH_SFTP_CLOSE); - sshc->actualcode = CURLE_QUOTE_ERROR; - break; + err = libssh2_sftp_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + sshc->quote_path1 = NULL; + Curl_safefree(sshc->quote_path2); + sshc->quote_path2 = NULL; + failf(data, "Attempt to get SFTP stats failed: %s", + sftp_libssh2_strerror(err)); + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_QUOTE_ERROR; + break; } } @@ -1233,6 +1251,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) if(!sshc->sftp_handle) { if(libssh2_session_last_errno(sshc->ssh_session) == LIBSSH2_ERROR_EAGAIN) { + rc = LIBSSH2_ERROR_EAGAIN; break; } else { @@ -1387,6 +1406,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) if(!sshc->sftp_handle) { if(libssh2_session_last_errno(sshc->ssh_session) == LIBSSH2_ERROR_EAGAIN) { + rc = LIBSSH2_ERROR_EAGAIN; break; } else { @@ -1421,6 +1441,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) PATH_MAX, &sshc->readdir_attrs); if(sshc->readdir_len == LIBSSH2_ERROR_EAGAIN) { + rc = LIBSSH2_ERROR_EAGAIN; break; } if(sshc->readdir_len > 0) { @@ -1521,6 +1542,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) sshc->readdir_filename, PATH_MAX); if(sshc->readdir_len == LIBSSH2_ERROR_EAGAIN) { + rc = LIBSSH2_ERROR_EAGAIN; break; } Curl_safefree(sshc->readdir_linkPath); @@ -1578,6 +1600,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) case SSH_SFTP_READDIR_DONE: if(libssh2_sftp_closedir(sshc->sftp_handle) == LIBSSH2_ERROR_EAGAIN) { + rc = LIBSSH2_ERROR_EAGAIN; break; } sshc->sftp_handle = NULL; @@ -1601,6 +1624,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) if(!sshc->sftp_handle) { if(libssh2_session_last_errno(sshc->ssh_session) == LIBSSH2_ERROR_EAGAIN) { + rc = LIBSSH2_ERROR_EAGAIN; break; } else { @@ -1814,6 +1838,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) if(!sshc->ssh_channel) { if(libssh2_session_last_errno(sshc->ssh_session) == LIBSSH2_ERROR_EAGAIN) { + rc = LIBSSH2_ERROR_EAGAIN; break; } else { @@ -1860,6 +1885,7 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) if(!sshc->ssh_channel) { if(libssh2_session_last_errno(sshc->ssh_session) == LIBSSH2_ERROR_EAGAIN) { + rc = LIBSSH2_ERROR_EAGAIN; break; } else { @@ -2011,6 +2037,12 @@ static CURLcode ssh_statemach_act(struct connectdata *conn) break; } + if(rc == LIBSSH2_ERROR_EAGAIN) { + /* we would block, we need to wait for the socket to be ready (in the + right direction too)! */ + *block = TRUE; + } + return result; } @@ -2019,8 +2051,11 @@ static CURLcode ssh_multi_statemach(struct connectdata *conn, bool *done) { struct ssh_conn *sshc = &conn->proto.sshc; CURLcode result = CURLE_OK; + bool block_we_ignore; /* we don't care about EAGAIN at this point, but TODO: + we _should_ store the status and use that to + provide a ssh_getsock() implementation */ - result = ssh_statemach_act(conn); + result = ssh_statemach_act(conn, &block_we_ignore); *done = (bool)(sshc->state == SSH_STOP); return result; @@ -2031,8 +2066,28 @@ static CURLcode ssh_easy_statemach(struct connectdata *conn) struct ssh_conn *sshc = &conn->proto.sshc; CURLcode result = CURLE_OK; - while((sshc->state != SSH_STOP) && !result) - result = ssh_statemach_act(conn); + while((sshc->state != SSH_STOP) && !result) { + bool block; + result = ssh_statemach_act(conn, &block); + +#ifdef HAVE_LIBSSH2_SESSION_BLOCK_DIRECTION + if((CURLE_OK == result) && block) { + int dir = libssh2_session_block_directions(sshc->ssh_session); + curl_socket_t sock = conn->sock[FIRSTSOCKET]; + curl_socket_t fd_read = CURL_SOCKET_BAD; + curl_socket_t fd_write = CURL_SOCKET_BAD; + if (LIBSSH2_SESSION_BLOCK_INBOUND & dir) { + fd_read = sock; + } + if (LIBSSH2_SESSION_BLOCK_OUTBOUND & dir) { + fd_write = sock; + } + /* wait for the socket to become ready */ + Curl_socket_ready(fd_read, fd_write, 1000); /* ignore result */ + } +#endif + + } return result; }