From dde2b0949682e1e72a782ee4d3b21384450988af Mon Sep 17 00:00:00 2001 From: James Housley Date: Tue, 12 Jun 2007 18:27:37 +0000 Subject: [PATCH] libssh2_channel_free() actually can return PACKET_EAGAIN. Update all calling functions to support that with the following API notes: * libssh2_publickey_shutdown(), libssh2_session_free() changed to return an "int" to allow signaling of LIBSSH2_ERROR_EAGAIN. * libssh2_scp_recv(), libssh2_scp_send_ex() and libssh2_sftp_init() will loop in on libssh2_channel_free() when there is an error. It is not possible to return LIBSSH2_ERROR_EAGAIN in this condition in these 3 functions and not lose the original error code. --- ChangeLog | 10 +++ include/libssh2.h | 20 ++--- include/libssh2_publickey.h | 2 +- src/channel.c | 55 ++++++++---- src/libssh2_priv.h | 6 ++ src/publickey.c | 12 ++- src/scp.c | 165 +++++++++++------------------------- src/session.c | 55 ++++++++---- src/sftp.c | 63 ++++++++------ 9 files changed, 197 insertions(+), 191 deletions(-) diff --git a/ChangeLog b/ChangeLog index 00f1302..61f701e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2007-07-12 James Housley + + * libssh2_publickey_shutdown(), libssh2_session_free() changed + to return an "int" to allow signaling of LIBSSH2_ERROR_EAGAIN. + + * libssh2_scp_recv(), libssh2_scp_send_ex() and libssh2_sftp_init() + will loop in on libssh2_channel_free() when there is an error. + It is not possible to return LIBSSH2_ERROR_EAGAIN in this condition + in these 3 functions and not lose the original error code. + 2007-06-10 James Housley * The list of supported authentication types returned by diff --git a/include/libssh2.h b/include/libssh2.h index d856bbb..df27c4a 100644 --- a/include/libssh2.h +++ b/include/libssh2.h @@ -273,7 +273,7 @@ LIBSSH2_API int libssh2_banner_set(LIBSSH2_SESSION *session, const char *banner) LIBSSH2_API int libssh2_session_startup(LIBSSH2_SESSION *session, int socket); LIBSSH2_API int libssh2_session_disconnect_ex(LIBSSH2_SESSION *session, int reason, const char *description, const char *lang); #define libssh2_session_disconnect(session, description) libssh2_session_disconnect_ex((session), SSH_DISCONNECT_BY_APPLICATION, (description), "") -LIBSSH2_API void libssh2_session_free(LIBSSH2_SESSION *session); +LIBSSH2_API int libssh2_session_free(LIBSSH2_SESSION *session); LIBSSH2_API const char *libssh2_hostkey_hash(LIBSSH2_SESSION *session, int hash_type); @@ -358,13 +358,11 @@ LIBSSH2_API int libssh2_channel_process_startup(LIBSSH2_CHANNEL *channel, const #define libssh2_channel_exec(channel, command) libssh2_channel_process_startup((channel), "exec", sizeof("exec") - 1, (command), strlen(command)) #define libssh2_channel_subsystem(channel, subsystem) libssh2_channel_process_startup((channel), "subsystem", sizeof("subsystem") - 1, (subsystem), strlen(subsystem)) -LIBSSH2_API ssize_t libssh2_channel_read_ex(LIBSSH2_CHANNEL *channel, - int stream_id, char *buf, - size_t buflen); +LIBSSH2_API ssize_t libssh2_channel_read_ex(LIBSSH2_CHANNEL *channel, int stream_id, char *buf, size_t buflen); #define libssh2_channel_read(channel, buf, buflen) \ - libssh2_channel_read_ex((channel), 0, (buf), (buflen)) + libssh2_channel_read_ex((channel), 0, (buf), (buflen)) #define libssh2_channel_read_stderr(channel, buf, buflen) \ - libssh2_channel_read_ex((channel), SSH_EXTENDED_DATA_STDERR, (buf), (buflen)) + libssh2_channel_read_ex((channel), SSH_EXTENDED_DATA_STDERR, (buf), (buflen)) LIBSSH2_API int libssh2_poll_channel_read(LIBSSH2_CHANNEL *channel, int extended); @@ -374,14 +372,12 @@ LIBSSH2_API unsigned long libssh2_channel_window_read_ex(LIBSSH2_CHANNEL *channe LIBSSH2_API unsigned long libssh2_channel_receive_window_adjust(LIBSSH2_CHANNEL *channel, unsigned long adjustment, unsigned char force); -LIBSSH2_API size_t libssh2_channel_write_ex(LIBSSH2_CHANNEL *channel, - int stream_id, const char *buf, - size_t buflen); +LIBSSH2_API ssize_t libssh2_channel_write_ex(LIBSSH2_CHANNEL *channel, int stream_id, const char *buf, size_t buflen); #define libssh2_channel_write(channel, buf, buflen) \ - libssh2_channel_write_ex((channel), 0, (buf), (buflen)) -#define libssh2_channel_write_stderr(channel, buf, buflen) \ - libssh2_channel_write_ex((channel), SSH_EXTENDED_DATA_STDERR, (buf), (buflen)) + libssh2_channel_write_ex((channel), 0, (buf), (buflen)) +#define libssh2_channel_write_stderr(channel, buf, buflen) \ + libssh2_channel_write_ex((channel), SSH_EXTENDED_DATA_STDERR, (buf), (buflen)) LIBSSH2_API unsigned long libssh2_channel_window_write_ex(LIBSSH2_CHANNEL *channel, unsigned long *window_size_initial); #define libssh2_channel_window_write(channel) libssh2_channel_window_write_ex((channel), NULL) diff --git a/include/libssh2_publickey.h b/include/libssh2_publickey.h index 92fc5a4..1adc3ce 100644 --- a/include/libssh2_publickey.h +++ b/include/libssh2_publickey.h @@ -92,7 +92,7 @@ LIBSSH2_API int libssh2_publickey_remove_ex(LIBSSH2_PUBLICKEY *pkey, const unsig LIBSSH2_API int libssh2_publickey_list_fetch(LIBSSH2_PUBLICKEY *pkey, unsigned long *num_keys, libssh2_publickey_list **pkey_list); LIBSSH2_API void libssh2_publickey_list_free(LIBSSH2_PUBLICKEY *pkey, libssh2_publickey_list *pkey_list); -LIBSSH2_API void libssh2_publickey_shutdown(LIBSSH2_PUBLICKEY *pkey); +LIBSSH2_API int libssh2_publickey_shutdown(LIBSSH2_PUBLICKEY *pkey); #ifdef __cplusplus } /* extern "C" */ diff --git a/src/channel.c b/src/channel.c index 70d5045..dc33313 100644 --- a/src/channel.c +++ b/src/channel.c @@ -505,23 +505,29 @@ LIBSSH2_API int libssh2_channel_forward_cancel(LIBSSH2_LISTENER *listener) packet = listener->chanFwdCncl_data; } - rc = libssh2_packet_write(session, packet, packet_len); - if (rc == PACKET_EAGAIN) { - listener->chanFwdCncl_data = packet; - } - else if (rc) { - libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send global-request packet for forward listen request", 0); + if (listener->chanFwdCncl_state == libssh2_NB_state_created) { + rc = libssh2_packet_write(session, packet, packet_len); + if (rc == PACKET_EAGAIN) { + listener->chanFwdCncl_data = packet; + } + else if (rc) { + libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send global-request packet for forward listen request", 0); + LIBSSH2_FREE(session, packet); + listener->chanFwdCncl_state = libssh2_NB_state_idle; + return -1; + } LIBSSH2_FREE(session, packet); - listener->chanFwdCncl_state = libssh2_NB_state_idle; - return -1; + + listener->chanFwdCncl_state = libssh2_NB_state_sent; } - LIBSSH2_FREE(session, packet); - listener->chanFwdCncl_state = libssh2_NB_state_idle; while (queued) { LIBSSH2_CHANNEL *next = queued->next; - libssh2_channel_free(queued); + rc = libssh2_channel_free(queued); + if (rc == PACKET_EAGAIN) { + return PACKET_EAGAIN; + } queued = next; } LIBSSH2_FREE(session, listener->host); @@ -537,6 +543,8 @@ LIBSSH2_API int libssh2_channel_forward_cancel(LIBSSH2_LISTENER *listener) LIBSSH2_FREE(session, listener); + listener->chanFwdCncl_state = libssh2_NB_state_idle; + return 0; } /* }}} */ @@ -1319,7 +1327,7 @@ channel_read_ex_point1: /* {{{ libssh2_channel_write_ex * Send data to a channel */ -LIBSSH2_API size_t libssh2_channel_write_ex(LIBSSH2_CHANNEL *channel, int stream_id, const char *buf, size_t buflen) +LIBSSH2_API ssize_t libssh2_channel_write_ex(LIBSSH2_CHANNEL *channel, int stream_id, const char *buf, size_t buflen) { LIBSSH2_SESSION *session = channel->session; libssh2pack_t rc; @@ -1660,12 +1668,27 @@ LIBSSH2_API int libssh2_channel_free(LIBSSH2_CHANNEL *channel) unsigned char channel_id[4]; unsigned char *data; unsigned long data_len; + int rc; - _libssh2_debug(session, LIBSSH2_DBG_CONN, "Freeing channel %lu/%lu resources", channel->local.id, channel->remote.id); - /* Allow channel freeing even when the socket has lost its connection */ - if (!channel->local.close && (session->socket_state == LIBSSH2_SOCKET_CONNECTED) && libssh2_channel_close(channel)) { - return -1; + if (channel->free_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_DBG_CONN, "Freeing channel %lu/%lu resources", channel->local.id, channel->remote.id); + + channel->free_state = libssh2_NB_state_created; } + + /* Allow channel freeing even when the socket has lost its connection */ + if (!channel->local.close && (session->socket_state == LIBSSH2_SOCKET_CONNECTED)) { + while ((rc = libssh2_channel_close(channel)) == PACKET_EAGAIN); + if (rc == PACKET_EAGAIN) { + return PACKET_EAGAIN; + } + else if (rc) { + channel->free_state = libssh2_NB_state_idle; + return -1; + } + } + + channel->free_state = libssh2_NB_state_idle; /* * channel->remote.close *might* not be set yet, Well... diff --git a/src/libssh2_priv.h b/src/libssh2_priv.h index 04f3427..39a1c32 100644 --- a/src/libssh2_priv.h +++ b/src/libssh2_priv.h @@ -337,6 +337,9 @@ struct _LIBSSH2_CHANNEL { /* State variables used in libssh2_channel_wait_closed() */ libssh2_nonblocking_states wait_closed_state; + /* State variables used in libssh2_channel_free() */ + libssh2_nonblocking_states free_state; + /* State variables used in libssh2_channel_handle_extended_data2() */ libssh2_nonblocking_states extData2_state; }; @@ -648,6 +651,9 @@ struct _LIBSSH2_SESSION { packet_require_state_t startup_req_state; key_exchange_state_t startup_key_state; + /* State variables used in libssh2_session_free() */ + libssh2_nonblocking_states free_state; + /* State variables used in libssh2_session_disconnect_ex() */ libssh2_nonblocking_states disconnect_state; unsigned char *disconnect_data; diff --git a/src/publickey.c b/src/publickey.c index 507df15..7587546 100644 --- a/src/publickey.c +++ b/src/publickey.c @@ -860,7 +860,7 @@ LIBSSH2_API void libssh2_publickey_list_free(LIBSSH2_PUBLICKEY *pkey, libssh2_pu /* {{{ libssh2_publickey_shutdown * Shutdown the publickey subsystem */ -LIBSSH2_API void libssh2_publickey_shutdown(LIBSSH2_PUBLICKEY *pkey) +LIBSSH2_API int libssh2_publickey_shutdown(LIBSSH2_PUBLICKEY *pkey) { LIBSSH2_SESSION *session = pkey->channel->session; @@ -869,18 +869,26 @@ LIBSSH2_API void libssh2_publickey_shutdown(LIBSSH2_PUBLICKEY *pkey) */ if (pkey->receive_packet) { LIBSSH2_FREE(session, pkey->receive_packet); + pkey->receive_packet = NULL; } if (pkey->add_packet) { LIBSSH2_FREE(session, pkey->add_packet); + pkey->add_packet = NULL; } if (pkey->remove_packet) { LIBSSH2_FREE(session, pkey->remove_packet); + pkey->remove_packet = NULL; } if (pkey->listFetch_data) { LIBSSH2_FREE(session, pkey->listFetch_data); + pkey->listFetch_data = NULL; + } + + if (libssh2_channel_free(pkey->channel) == PACKET_EAGAIN) { + return PACKET_EAGAIN; } - libssh2_channel_free(pkey->channel); LIBSSH2_FREE(session, pkey); + return 0; } /* }}} */ diff --git a/src/scp.c b/src/scp.c index 5dbf7ef..4c3586b 100644 --- a/src/scp.c +++ b/src/scp.c @@ -41,6 +41,10 @@ /* {{{ libssh2_scp_recv * Open a channel and request a remote file via SCP + * + * NOTE: Will block in a busy loop on error. This has to be done, + * otherwise the blocking error code would erase the true + * cause of the error. */ LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session, const char *path, struct stat *sb) { @@ -111,10 +115,7 @@ LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session, const ch else if (rc) { LIBSSH2_FREE(session, session->scpRecv_command); session->scpRecv_command = NULL; - libssh2_channel_free(session->scpRecv_channel); - session->scpRecv_channel = NULL; - session->scpRecv_state = libssh2_NB_state_idle; - return NULL; + goto scp_recv_error; } LIBSSH2_FREE(session, session->scpRecv_command); session->scpRecv_command = NULL; @@ -133,10 +134,7 @@ LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session, const ch return NULL; } else if (rc != 1) { - libssh2_channel_free(session->scpRecv_channel); - session->scpRecv_channel = NULL; - session->scpRecv_state = libssh2_NB_state_idle; - return NULL; + goto scp_recv_error; } /* Parse SCP response */ @@ -159,19 +157,13 @@ LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session, const ch else if (rc <= 0) { /* Timeout, give up */ libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Timed out waiting for SCP response", 0); - libssh2_channel_free(session->scpRecv_channel); - session->scpRecv_channel = NULL; - session->scpRecv_state = libssh2_NB_state_idle; - return NULL; + goto scp_recv_error; } session->scpRecv_response_len++; if (session->scpRecv_response[0] != 'T') { libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid data in SCP response, missing Time data", 0); - libssh2_channel_free(session->scpRecv_channel); - session->scpRecv_channel = NULL; - session->scpRecv_state = libssh2_NB_state_idle; - return NULL; + goto scp_recv_error; } if ((session->scpRecv_response_len > 1) && @@ -181,20 +173,14 @@ LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session, const ch (session->scpRecv_response[session->scpRecv_response_len-1] != '\r') && (session->scpRecv_response[session->scpRecv_response_len-1] != '\n')) { libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid data in SCP response", 0); - libssh2_channel_free(session->scpRecv_channel); - session->scpRecv_channel = NULL; - session->scpRecv_state = libssh2_NB_state_idle; - return NULL; + goto scp_recv_error; } if ((session->scpRecv_response_len < 9) || (session->scpRecv_response[session->scpRecv_response_len-1] != '\n')) { if (session->scpRecv_response_len == LIBSSH2_SCP_RESPONSE_BUFLEN) { /* You had your chance */ libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Unterminated response from SCP server", 0); - libssh2_channel_free(session->scpRecv_channel); - session->scpRecv_channel = NULL; - session->scpRecv_state = libssh2_NB_state_idle; - return NULL; + goto scp_recv_error; } /* Way too short to be an SCP response, or not done yet, short circuit */ continue; @@ -207,10 +193,7 @@ LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session, const ch if (session->scpRecv_response_len < 8) { /* EOL came too soon */ libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, too short", 0); - libssh2_channel_free(session->scpRecv_channel); - session->scpRecv_channel = NULL; - session->scpRecv_state = libssh2_NB_state_idle; - return NULL; + goto scp_recv_error; } s = session->scpRecv_response + 1; @@ -219,10 +202,7 @@ LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session, const ch if (!p || ((p - s) <= 0)) { /* No spaces or space in the wrong spot */ libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, malformed mtime", 0); - libssh2_channel_free(session->scpRecv_channel); - session->scpRecv_channel = NULL; - session->scpRecv_state = libssh2_NB_state_idle; - return NULL; + goto scp_recv_error; } *(p++) = '\0'; @@ -231,19 +211,13 @@ LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session, const ch session->scpRecv_mtime = strtol((char *)s, NULL, 10); if (errno) { libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, invalid mtime", 0); - libssh2_channel_free(session->scpRecv_channel); - session->scpRecv_channel = NULL; - session->scpRecv_state = libssh2_NB_state_idle; - return NULL; + goto scp_recv_error; } s = (unsigned char *)strchr((char *)p, ' '); if (!s || ((s - p) <= 0)) { /* No spaces or space in the wrong spot */ libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, malformed mtime.usec", 0); - libssh2_channel_free(session->scpRecv_channel); - session->scpRecv_channel = NULL; - session->scpRecv_state = libssh2_NB_state_idle; - return NULL; + goto scp_recv_error; } /* Ignore mtime.usec */ @@ -252,10 +226,7 @@ LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session, const ch if (!p || ((p - s) <= 0)) { /* No spaces or space in the wrong spot */ libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, too short or malformed", 0); - libssh2_channel_free(session->scpRecv_channel); - session->scpRecv_channel = NULL; - session->scpRecv_state = libssh2_NB_state_idle; - return NULL; + goto scp_recv_error; } *(p++) = '\0'; @@ -264,10 +235,7 @@ LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session, const ch session->scpRecv_atime = strtol((char *)s, NULL, 10); if (errno) { libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, invalid atime", 0); - libssh2_channel_free(session->scpRecv_channel); - session->scpRecv_channel = NULL; - session->scpRecv_state = libssh2_NB_state_idle; - return NULL; + goto scp_recv_error; } /* SCP ACK */ @@ -283,10 +251,7 @@ LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session, const ch return NULL; } else if (rc != 1) { - libssh2_channel_free(session->scpRecv_channel); - session->scpRecv_channel = NULL; - session->scpRecv_state = libssh2_NB_state_idle; - return NULL; + goto scp_recv_error; } _libssh2_debug(session, LIBSSH2_DBG_SCP, "mtime = %ld, atime = %ld", session->scpRecv_mtime, session->scpRecv_atime); @@ -319,19 +284,13 @@ LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session, const ch else if (rc <= 0) { /* Timeout, give up */ libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Timed out waiting for SCP response", 0); - libssh2_channel_free(session->scpRecv_channel); - session->scpRecv_channel = NULL; - session->scpRecv_state = libssh2_NB_state_idle; - return NULL; + goto scp_recv_error; } session->scpRecv_response_len++; if (session->scpRecv_response[0] != 'C') { libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server", 0); - libssh2_channel_free(session->scpRecv_channel); - session->scpRecv_channel = NULL; - session->scpRecv_state = libssh2_NB_state_idle; - return NULL; + goto scp_recv_error; } if ((session->scpRecv_response_len > 1) && @@ -340,20 +299,14 @@ LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session, const ch ((session->scpRecv_response[session->scpRecv_response_len-1] < 32) || (session->scpRecv_response[session->scpRecv_response_len-1] > 126))) { libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid data in SCP response", 0); - libssh2_channel_free(session->scpRecv_channel); - session->scpRecv_channel = NULL; - session->scpRecv_state = libssh2_NB_state_idle; - return NULL; + goto scp_recv_error; } if ((session->scpRecv_response_len < 7) || (session->scpRecv_response[session->scpRecv_response_len-1] != '\n')) { if (session->scpRecv_response_len == LIBSSH2_SCP_RESPONSE_BUFLEN) { /* You had your chance */ libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Unterminated response from SCP server", 0); - libssh2_channel_free(session->scpRecv_channel); - session->scpRecv_channel = NULL; - session->scpRecv_state = libssh2_NB_state_idle; - return NULL; + goto scp_recv_error; } /* Way too short to be an SCP response, or not done yet, short circuit */ continue; @@ -369,10 +322,7 @@ LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session, const ch if (session->scpRecv_response_len < 6) { /* EOL came too soon */ libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, too short", 0); - libssh2_channel_free(session->scpRecv_channel); - session->scpRecv_channel = NULL; - session->scpRecv_state = libssh2_NB_state_idle; - return NULL; + goto scp_recv_error; } s = (char *)session->scpRecv_response + 1; @@ -381,10 +331,7 @@ LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session, const ch if (!p || ((p - s) <= 0)) { /* No spaces or space in the wrong spot */ libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, malformed mode", 0); - libssh2_channel_free(session->scpRecv_channel); - session->scpRecv_channel = NULL; - session->scpRecv_state = libssh2_NB_state_idle; - return NULL; + goto scp_recv_error; } *(p++) = '\0'; @@ -393,10 +340,7 @@ LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session, const ch session->scpRecv_mode = strtol(s, &e, 8); if ((e && *e) || errno) { libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, invalid mode", 0); - libssh2_channel_free(session->scpRecv_channel); - session->scpRecv_channel = NULL; - session->scpRecv_state = libssh2_NB_state_idle; - return NULL; + goto scp_recv_error; } s = strchr(p, ' '); @@ -404,10 +348,7 @@ LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session, const ch /* No spaces or space in the wrong spot */ libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, too short or malformed", 0); - libssh2_channel_free(session->scpRecv_channel); - session->scpRecv_channel = NULL; - session->scpRecv_state = libssh2_NB_state_idle; - return NULL; + goto scp_recv_error; } *(s++) = '\0'; @@ -416,10 +357,7 @@ LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session, const ch session->scpRecv_size = strtol(p, &e, 10); if ((e && *e) || errno) { libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid response from SCP server, invalid size", 0); - libssh2_channel_free(session->scpRecv_channel); - session->scpRecv_channel = NULL; - session->scpRecv_state = libssh2_NB_state_idle; - return NULL; + goto scp_recv_error; } /* SCP ACK */ @@ -435,10 +373,7 @@ LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session, const ch return NULL; } else if (rc != 1) { - libssh2_channel_free(session->scpRecv_channel); - session->scpRecv_channel = NULL; - session->scpRecv_state = libssh2_NB_state_idle; - return NULL; + goto scp_recv_error; } _libssh2_debug(session, LIBSSH2_DBG_SCP, "mode = 0%lo size = %ld", session->scpRecv_mode, session->scpRecv_size); @@ -461,11 +396,21 @@ LIBSSH2_API LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session, const ch session->scpRecv_state = libssh2_NB_state_idle; return session->scpRecv_channel; + +scp_recv_error: + while (libssh2_channel_free(session->scpRecv_channel) == PACKET_EAGAIN); + session->scpRecv_channel = NULL; + session->scpRecv_state = libssh2_NB_state_idle; + return NULL; } /* }}} */ /* {{{ libssh2_scp_send_ex * Send a file using SCP + * + * NOTE: Will block in a busy loop on error. This has to be done, + * otherwise the blocking error code would erase the true + * cause of the error. */ LIBSSH2_API LIBSSH2_CHANNEL * libssh2_scp_send_ex(LIBSSH2_SESSION *session, const char *path, int mode, size_t size, long mtime, long atime) @@ -534,10 +479,7 @@ libssh2_scp_send_ex(LIBSSH2_SESSION *session, const char *path, int mode, size_t /* previous call set libssh2_session_last_error(), pass it through */ LIBSSH2_FREE(session, session->scpSend_command); session->scpSend_command = NULL; - libssh2_channel_free(session->scpSend_channel); - session->scpSend_channel = NULL; - session->scpSend_state = libssh2_NB_state_idle; - return NULL; + goto scp_send_error; } LIBSSH2_FREE(session, session->scpSend_command); session->scpSend_command = NULL; @@ -554,10 +496,7 @@ libssh2_scp_send_ex(LIBSSH2_SESSION *session, const char *path, int mode, size_t } else if ((rc <= 0) || (session->scpSend_response[0] != 0)) { libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid ACK response from remote", 0); - libssh2_channel_free(session->scpSend_channel); - session->scpSend_channel = NULL; - session->scpSend_state = libssh2_NB_state_idle; - return NULL; + goto scp_send_error; } if (mtime || atime) { @@ -581,10 +520,7 @@ libssh2_scp_send_ex(LIBSSH2_SESSION *session, const char *path, int mode, size_t } else if (rc != session->scpSend_response_len) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send time data for SCP file", 0); - libssh2_channel_free(session->scpSend_channel); - session->scpSend_channel = NULL; - session->scpSend_state = libssh2_NB_state_idle; - return NULL; + goto scp_send_error; } session->scpSend_state = libssh2_NB_state_sent3; @@ -599,10 +535,7 @@ libssh2_scp_send_ex(LIBSSH2_SESSION *session, const char *path, int mode, size_t } else if ((rc <= 0) || (session->scpSend_response[0] != 0)) { libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid ACK response from remote", 0); - libssh2_channel_free(session->scpSend_channel); - session->scpSend_channel = NULL; - session->scpSend_state = libssh2_NB_state_idle; - return NULL; + goto scp_send_error; } session->scpSend_state = libssh2_NB_state_sent4; @@ -638,10 +571,7 @@ libssh2_scp_send_ex(LIBSSH2_SESSION *session, const char *path, int mode, size_t } else if (rc != session->scpSend_response_len) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send core file data for SCP file", 0); - libssh2_channel_free(session->scpSend_channel); - session->scpSend_channel = NULL; - session->scpSend_state = libssh2_NB_state_idle; - return NULL; + goto scp_send_error; } session->scpSend_state = libssh2_NB_state_sent6; @@ -655,15 +585,18 @@ libssh2_scp_send_ex(LIBSSH2_SESSION *session, const char *path, int mode, size_t } else if ((rc <= 0) || (session->scpSend_response[0] != 0)) { libssh2_error(session, LIBSSH2_ERROR_SCP_PROTOCOL, "Invalid ACK response from remote", 0); - libssh2_channel_free(session->scpSend_channel); - session->scpSend_channel = NULL; - session->scpSend_state = libssh2_NB_state_idle; - return NULL; + goto scp_send_error; } session->scpSend_state = libssh2_NB_state_idle; return session->scpSend_channel; + +scp_send_error: + while (libssh2_channel_free(session->scpSend_channel) == PACKET_EAGAIN); + session->scpSend_channel = NULL; + session->scpSend_state = libssh2_NB_state_idle; + return NULL; } /* }}} */ diff --git a/src/session.c b/src/session.c index 4003b43..3d8369d 100644 --- a/src/session.c +++ b/src/session.c @@ -597,31 +597,52 @@ LIBSSH2_API int libssh2_session_startup(LIBSSH2_SESSION *session, int socket) * Frees the memory allocated to the session * Also closes and frees any channels attached to this session */ -LIBSSH2_API void libssh2_session_free(LIBSSH2_SESSION *session) +LIBSSH2_API int libssh2_session_free(LIBSSH2_SESSION *session) { - _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Freeing session resource", session->remote.banner); - while (session->channels.head) { - LIBSSH2_CHANNEL *tmp = session->channels.head; + int rc; + + if (session->free_state == libssh2_NB_state_idle) { + _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Freeing session resource", session->remote.banner); + + session->state = libssh2_NB_state_created; + } + + if (session->free_state == libssh2_NB_state_created) { + while (session->channels.head) { + LIBSSH2_CHANNEL *tmp = session->channels.head; - libssh2_channel_free(session->channels.head); - if (tmp == session->channels.head) { - /* channel_free couldn't do it's job, perform a messy cleanup */ - tmp = session->channels.head; + rc = libssh2_channel_free(session->channels.head); + if (rc == PACKET_EAGAIN) { + return PACKET_EAGAIN; + } + if (tmp == session->channels.head) { + /* channel_free couldn't do it's job, perform a messy cleanup */ + tmp = session->channels.head; - /* unlink */ - session->channels.head = tmp->next; + /* unlink */ + session->channels.head = tmp->next; - /* free */ - LIBSSH2_FREE(session, tmp); + /* free */ + LIBSSH2_FREE(session, tmp); - /* reverse linking isn't important here, we're killing the structure */ + /* reverse linking isn't important here, we're killing the structure */ + } } + + session->state = libssh2_NB_state_sent; } - while (session->listeners) { - libssh2_channel_forward_cancel(session->listeners); + if (session->state == libssh2_NB_state_sent) { + while (session->listeners) { + rc = libssh2_channel_forward_cancel(session->listeners); + if (rc == PACKET_EAGAIN) { + return PACKET_EAGAIN; + } + } + + session->state = libssh2_NB_state_sent1; } - + if (session->state & LIBSSH2_STATE_NEWKEYS) { /* hostkey */ if (session->hostkey && session->hostkey->dtor) { @@ -784,6 +805,8 @@ LIBSSH2_API void libssh2_session_free(LIBSSH2_SESSION *session) } LIBSSH2_FREE(session, session); + + return 0; } /* }}} */ diff --git a/src/sftp.c b/src/sftp.c index bcfc649..f1b8bbc 100644 --- a/src/sftp.c +++ b/src/sftp.c @@ -498,16 +498,22 @@ LIBSSH2_CHANNEL_CLOSE_FUNC(libssh2_sftp_dtor) /* {{{ libssh2_sftp_init * Startup an SFTP session + * + * NOTE: Will block in a busy loop on error. This has to be done, + * otherwise the blocking error code would erase the true + * cause of the error. */ LIBSSH2_API LIBSSH2_SFTP *libssh2_sftp_init(LIBSSH2_SESSION *session) { unsigned char *data, *s; unsigned long data_len; int rc; - + if (session->sftpInit_state == libssh2_NB_state_idle) { _libssh2_debug(session, LIBSSH2_DBG_SFTP, "Initializing SFTP subsystem"); - + + session->sftpInit_sftp = NULL; + session->sftpInit_state = libssh2_NB_state_created; } @@ -539,10 +545,7 @@ LIBSSH2_API LIBSSH2_SFTP *libssh2_sftp_init(LIBSSH2_SESSION *session) } else if (rc) { libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, "Unable to request SFTP subsystem", 0); - libssh2_channel_free(session->sftpInit_channel); - session->sftpInit_channel = NULL; - session->sftpInit_state = libssh2_NB_state_idle; - return NULL; + goto sftp_init_error; } session->sftpInit_state = libssh2_NB_state_sent1; @@ -558,10 +561,7 @@ LIBSSH2_API LIBSSH2_SFTP *libssh2_sftp_init(LIBSSH2_SESSION *session) session->sftpInit_sftp = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_SFTP)); if (!session->sftpInit_sftp) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate a new SFTP structure", 0); - libssh2_channel_free(session->sftpInit_channel); - session->sftpInit_channel = NULL; - session->sftpInit_state = libssh2_NB_state_idle; - return NULL; + goto sftp_init_error; } memset(session->sftpInit_sftp, 0, sizeof(LIBSSH2_SFTP)); session->sftpInit_sftp->channel = session->sftpInit_channel; @@ -585,12 +585,7 @@ LIBSSH2_API LIBSSH2_SFTP *libssh2_sftp_init(LIBSSH2_SESSION *session) } else if (9 != rc) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send SSH_FXP_INIT", 0); - libssh2_channel_free(session->sftpInit_channel); - session->sftpInit_channel = NULL; - LIBSSH2_FREE(session, session->sftpInit_sftp); - session->sftpInit_sftp = NULL; - session->sftpInit_state = libssh2_NB_state_idle; - return NULL; + goto sftp_init_error; } session->sftpInit_state = libssh2_NB_state_sent3; @@ -604,21 +599,11 @@ LIBSSH2_API LIBSSH2_SFTP *libssh2_sftp_init(LIBSSH2_SESSION *session) } else if (rc) { libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for response from SFTP subsystem", 0); - libssh2_channel_free(session->sftpInit_channel); - session->sftpInit_channel = NULL; - LIBSSH2_FREE(session, session->sftpInit_sftp); - session->sftpInit_sftp = NULL; - session->sftpInit_state = libssh2_NB_state_idle; - return NULL; + goto sftp_init_error; } if (data_len < 5) { libssh2_error(session, LIBSSH2_ERROR_SFTP_PROTOCOL, "Invalid SSH_FXP_VERSION response", 0); - libssh2_channel_free(session->sftpInit_channel); - session->sftpInit_channel = NULL; - LIBSSH2_FREE(session, session->sftpInit_sftp); - session->sftpInit_sftp = NULL; - session->sftpInit_state = libssh2_NB_state_idle; - return NULL; + goto sftp_init_error; } s = data + 1; @@ -648,6 +633,16 @@ LIBSSH2_API LIBSSH2_SFTP *libssh2_sftp_init(LIBSSH2_SESSION *session) session->sftpInit_state = libssh2_NB_state_idle; return session->sftpInit_sftp; + +sftp_init_error: + while (libssh2_channel_free(session->sftpInit_channel) == PACKET_EAGAIN); + session->sftpInit_channel = NULL; + if (session->sftpInit_sftp) { + LIBSSH2_FREE(session, session->sftpInit_sftp); + session->sftpInit_sftp = NULL; + } + session->sftpInit_state = libssh2_NB_state_idle; + return NULL; } /* }}} */ @@ -661,39 +656,51 @@ LIBSSH2_API int libssh2_sftp_shutdown(LIBSSH2_SFTP *sftp) */ if (sftp->partial_packet) { LIBSSH2_FREE(sftp->channel->session, sftp->partial_packet); + sftp->partial_packet = NULL; } if (sftp->open_packet) { LIBSSH2_FREE(sftp->channel->session, sftp->open_packet); + sftp->open_packet = NULL; } if (sftp->read_packet) { LIBSSH2_FREE(sftp->channel->session, sftp->read_packet); + sftp->read_packet = NULL; } if (sftp->readdir_packet) { LIBSSH2_FREE(sftp->channel->session, sftp->readdir_packet); + sftp->readdir_packet = NULL; } if (sftp->write_packet) { LIBSSH2_FREE(sftp->channel->session, sftp->write_packet); + sftp->write_packet = NULL; } if (sftp->fstat_packet) { LIBSSH2_FREE(sftp->channel->session, sftp->fstat_packet); + sftp->fstat_packet = NULL; } if (sftp->unlink_packet) { LIBSSH2_FREE(sftp->channel->session, sftp->unlink_packet); + sftp->unlink_packet = NULL; } if (sftp->rename_packet) { LIBSSH2_FREE(sftp->channel->session, sftp->rename_packet); + sftp->rename_packet = NULL; } if (sftp->mkdir_packet) { LIBSSH2_FREE(sftp->channel->session, sftp->mkdir_packet); + sftp->mkdir_packet = NULL; } if (sftp->rmdir_packet) { LIBSSH2_FREE(sftp->channel->session, sftp->rmdir_packet); + sftp->rmdir_packet = NULL; } if (sftp->stat_packet) { LIBSSH2_FREE(sftp->channel->session, sftp->stat_packet); + sftp->stat_packet = NULL; } if (sftp->symlink_packet) { LIBSSH2_FREE(sftp->channel->session, sftp->symlink_packet); + sftp->symlink_packet = NULL; } return libssh2_channel_free(sftp->channel);