From c5ec1678815f4cefce9620371f98f9b645dd07f8 Mon Sep 17 00:00:00 2001 From: Matt Lilley Date: Tue, 3 May 2011 11:20:24 +1200 Subject: [PATCH] adds a timeout to blocking calls Fixes bug #160 as per Daniel's suggestion Adds libssh2_session_set_timeout() and libssh2_session_get_timeout() --- include/libssh2.h | 4 ++++ src/libssh2_priv.h | 3 +++ src/session.c | 53 ++++++++++++++++++++++++++++++++++++++++------ src/session.h | 44 ++++++++++++++++++++------------------ 4 files changed, 77 insertions(+), 27 deletions(-) diff --git a/include/libssh2.h b/include/libssh2.h index e207411..9462360 100644 --- a/include/libssh2.h +++ b/include/libssh2.h @@ -705,6 +705,10 @@ LIBSSH2_API int libssh2_session_get_blocking(LIBSSH2_SESSION* session); LIBSSH2_API void libssh2_channel_set_blocking(LIBSSH2_CHANNEL *channel, int blocking); +LIBSSH2_API void libssh2_session_set_timeout(LIBSSH2_SESSION* session, + long timeout); +LIBSSH2_API long libssh2_session_get_timeout(LIBSSH2_SESSION* session); + /* libssh2_channel_handle_extended_data is DEPRECATED, do not use! */ LIBSSH2_API void libssh2_channel_handle_extended_data(LIBSSH2_CHANNEL *channel, int ignore_mode); diff --git a/src/libssh2_priv.h b/src/libssh2_priv.h index 231401e..5a697c7 100644 --- a/src/libssh2_priv.h +++ b/src/libssh2_priv.h @@ -559,6 +559,9 @@ struct _LIBSSH2_SESSION /* this is set to TRUE if a blocking API behavior is requested */ int api_block_mode; + /* Timeout used when blocking API behavior is active */ + long api_timeout; + /* Server's public key */ const LIBSSH2_HOSTKEY_METHOD *hostkey; void *server_hostkey_abstract; diff --git a/src/session.c b/src/session.c index e4714e8..07d6c8e 100644 --- a/src/session.c +++ b/src/session.c @@ -481,6 +481,7 @@ libssh2_session_init_ex(LIBSSH2_ALLOC_FUNC((*my_alloc)), session->free = local_free; session->realloc = local_realloc; session->abstract = abstract; + session->api_timeout = 0; /* timeout-free API by default */ session->api_block_mode = 1; /* blocking API by default */ _libssh2_debug(session, LIBSSH2_TRACE_TRANS, "New session resource allocated"); @@ -542,11 +543,12 @@ libssh2_session_callback_set(LIBSSH2_SESSION * session, * Utility function that waits for action on the socket. Returns 0 when ready * to run again or error on timeout. */ -int _libssh2_wait_socket(LIBSSH2_SESSION *session) +int _libssh2_wait_socket(LIBSSH2_SESSION *session, time_t start_time) { int rc; int seconds_to_next; int dir; + int has_timeout; /* since libssh2 often sets EAGAIN internally before this function is called, we can decrease some amount of confusion in user programs by @@ -592,8 +594,27 @@ int _libssh2_wait_socket(LIBSSH2_SESSION *session) fd_set *readfd = NULL; struct timeval tv; - tv.tv_sec = seconds_to_next; - tv.tv_usec = 0; + if (session->api_timeout > 0 && + (seconds_to_next == 0 || + seconds_to_next > session->api_timeout)) { + time_t now = time (NULL); + long elapsed_ms = (long)(1000*difftime(start_time, now)); + if (elapsed_ms > session->api_timeout) { + session->err_code = LIBSSH2_ERROR_TIMEOUT; + return LIBSSH2_ERROR_TIMEOUT; + } + tv.tv_sec = (session->api_timeout - elapsed_ms) / 1000; + tv.tv_usec = ((session->api_timeout - elapsed_ms) % 1000) * + 1000; + has_timeout = 1; + } + else if (seconds_to_next > 0) { + tv.tv_sec = seconds_to_next; + tv.tv_usec = 0; + has_timeout = 1; + } + else + has_timeout = 0; if(dir & LIBSSH2_SESSION_BLOCK_INBOUND) { FD_ZERO(&rfd); @@ -607,10 +628,8 @@ int _libssh2_wait_socket(LIBSSH2_SESSION *session) writefd = &wfd; } - /* Note that this COULD be made to use a timeout that perhaps - could be customizable by the app or something... */ rc = select(session->socket_fd + 1, readfd, writefd, NULL, - seconds_to_next ? &tv : NULL); + has_timeout ? &tv : NULL); #endif } } @@ -1281,6 +1300,28 @@ libssh2_session_get_blocking(LIBSSH2_SESSION * session) return session->api_block_mode; } + +/* libssh2_session_set_timeout + * + * Set a session's timeout (in msec) for blocking mode, + * or 0 to disable timeouts. + */ +LIBSSH2_API void +libssh2_session_set_timeout(LIBSSH2_SESSION * session, long timeout) +{ + session->api_timeout = timeout; +} + +/* libssh2_session_get_timeout + * + * Returns a session's timeout, or 0 if disabled + */ +LIBSSH2_API long +libssh2_session_get_timeout(LIBSSH2_SESSION * session) +{ + return session->api_timeout; +} + /* * libssh2_poll_channel_read * diff --git a/src/session.h b/src/session.h index 4eac3a6..2866fcb 100644 --- a/src/session.h +++ b/src/session.h @@ -53,15 +53,16 @@ */ #define BLOCK_ADJUST(rc,sess,x) \ do { \ - rc = x; \ - /* the order of the check below is important to properly deal with the - case when the 'sess' is freed */ \ - if((rc != LIBSSH2_ERROR_EAGAIN) || !sess->api_block_mode) \ - break; \ - rc = _libssh2_wait_socket(sess); \ - if(rc) \ - break; \ - } while(1) + time_t entry_time = time (NULL); \ + do { \ + rc = x; \ + /* the order of the check below is important to properly deal with the + case when the 'sess' is freed */ \ + if((rc != LIBSSH2_ERROR_EAGAIN) || !sess->api_block_mode) \ + break; \ + rc = _libssh2_wait_socket(sess, entry_time); \ + } while(!rc); \ + } while(0) /* * For functions that returns a pointer, we need to check if the API is @@ -69,21 +70,22 @@ * immediately. If the API is blocking and we get a NULL we check the errno * and *only* if that is EAGAIN we loop and wait for socket action. */ -#define BLOCK_ADJUST_ERRNO(ptr,sess,x) \ +#define BLOCK_ADJUST_ERRNO(ptr,sess,x) \ do { \ - int rc; \ - ptr = x; \ - if(!sess->api_block_mode || \ - (ptr != NULL) || \ - (libssh2_session_last_errno(sess) != LIBSSH2_ERROR_EAGAIN) ) \ - break; \ - rc = _libssh2_wait_socket(sess); \ - if(rc) \ - break; \ - } while(1) + time_t entry_time = time (NULL); \ + do { \ + int rc; \ + ptr = x; \ + if(!sess->api_block_mode || \ + (ptr != NULL) || \ + (libssh2_session_last_errno(sess) != LIBSSH2_ERROR_EAGAIN) ) \ + break; \ + rc = _libssh2_wait_socket(sess, entry_time); \ + } while(!rc); \ + } while(0) -int _libssh2_wait_socket(LIBSSH2_SESSION *session); +int _libssh2_wait_socket(LIBSSH2_SESSION *session, time_t entry_time); /* this is the lib-internal set blocking function */ int _libssh2_session_set_blocking(LIBSSH2_SESSION * session, int blocking);