diff --git a/README b/README index c37c802..266256b 100644 --- a/README +++ b/README @@ -12,6 +12,8 @@ Version 0.8 Added sys/uio.h for platforms (FBSD) which need it in order to define struct iovec. + Added libssh2_poll() to check status of sockets/channels/listeners. + Removed unnecessary inclusion of stdio.h (holdover from debugging) Version 0.7 diff --git a/configure.in b/configure.in index 8071cc6..d78f30e 100644 --- a/configure.in +++ b/configure.in @@ -162,7 +162,8 @@ fi # Checks for header files. # AC_HEADER_STDC -AC_CHECK_HEADERS([errno.h fcntl.h stdio.h stdlib.h unistd.h sys/uio.h]) +AC_CHECK_HEADERS([errno.h fcntl.h stdio.h stdlib.h unistd.h sys/uio.h sys/select.h]) +AC_CHECK_FUNCS(poll gettimeofday select) # Checks for typedefs, structures, and compiler characteristics. AC_C_CONST diff --git a/include/libssh2.h b/include/libssh2.h index ed9acc5..40e0798 100644 --- a/include/libssh2.h +++ b/include/libssh2.h @@ -68,7 +68,7 @@ typedef long long libssh2_int64_t; #endif #define LIBSSH2_VERSION "0.7" -#define LIBSSH2_APINO 200502132140 +#define LIBSSH2_APINO 200503221619 /* Part of every banner, user specified or not */ #define LIBSSH2_SSH_BANNER "SSH-2.0-libssh2_" LIBSSH2_VERSION @@ -143,6 +143,37 @@ typedef struct _LIBSSH2_SESSION LIBSSH2_SESSION; typedef struct _LIBSSH2_CHANNEL LIBSSH2_CHANNEL; typedef struct _LIBSSH2_LISTENER LIBSSH2_LISTENER; +typedef struct _LIBSSH2_POLLFD { + unsigned char type; /* LIBSSH2_POLLFD_* below */ + + union { + int socket; /* File descriptors -- examined with system select() call */ + LIBSSH2_CHANNEL *channel; /* Examined by checking internal state */ + LIBSSH2_LISTENER *listener; /* Read polls only -- are inbound connections waiting to be accepted? */ + } fd; + + unsigned long events; /* Requested Events */ + unsigned long revents; /* Returned Events */ +} LIBSSH2_POLLFD; + +/* Poll FD Descriptor Types */ +#define LIBSSH2_POLLFD_SOCKET 1 +#define LIBSSH2_POLLFD_CHANNEL 2 +#define LIBSSH2_POLLFD_LISTENER 3 + +/* Note: Win32 Doesn't actually have a poll() implementation, so some of these values are faked with select() data */ +/* Poll FD events/revents -- Match sys/poll.h where possible */ +#define LIBSSH2_POLLFD_POLLIN 0x0001 /* Data available to be read or connection available -- All */ +#define LIBSSH2_POLLFD_POLLPRI 0x0002 /* Priority data available to be read -- Socket only */ +#define LIBSSH2_POLLFD_POLLEXT 0x0002 /* Extended data available to be read -- Channel only */ +#define LIBSSH2_POLLFD_POLLOUT 0x0004 /* Can may be written -- Socket/Channel */ +/* revents only */ +#define LIBSSH2_POLLFD_POLLERR 0x0008 /* Error Condition -- Socket */ +#define LIBSSH2_POLLFD_POLLHUP 0x0010 /* HangUp/EOF -- Socket/Channel */ +#define LIBSSH2_POLLFD_POLLNVAL 0x0020 /* Invalid request -- Socket Only */ +#define LIBSSH2_POLLFD_POLLEX 0x0040 /* Exception Condition -- Socket/Win32 */ + +/* Hash Types */ #define LIBSSH2_HOSTKEY_HASH_MD5 1 #define LIBSSH2_HOSTKEY_HASH_SHA1 2 @@ -198,6 +229,7 @@ typedef struct _LIBSSH2_LISTENER LIBSSH2_LISTENER; #define LIBSSH2_ERROR_REQUEST_DENIED -32 #define LIBSSH2_ERROR_METHOD_NOT_SUPPORTED -33 #define LIBSSH2_ERROR_INVAL -34 +#define LIBSSH2_ERROR_INVALID_POLL_TYPE -35 /* Session API */ LIBSSH2_API LIBSSH2_SESSION *libssh2_session_init_ex(LIBSSH2_ALLOC_FUNC((*my_alloc)), LIBSSH2_FREE_FUNC((*my_free)), LIBSSH2_REALLOC_FUNC((*my_realloc)), void *abstract); @@ -239,6 +271,8 @@ LIBSSH2_API int libssh2_userauth_hostbased_fromfile_ex(LIBSSH2_SESSION *session, #define libssh2_userauth_hostbased_fromfile(session, username, publickey, privatekey, passphrase, hostname) \ libssh2_userauth_hostbased_fromfile_ex((session), (username), strlen(username), (publickey), (privatekey), (passphrase), (hostname), strlen(hostname), (username), strlen(username)) +LIBSSH2_API int libssh2_poll(LIBSSH2_POLLFD *fds, unsigned int nfds, long timeout); + /* Channel API */ #define LIBSSH2_CHANNEL_WINDOW_DEFAULT 65536 #define LIBSSH2_CHANNEL_PACKET_DEFAULT 16384 diff --git a/include/libssh2_config.h.in b/include/libssh2_config.h.in index fabd0ea..a7f8722 100644 --- a/include/libssh2_config.h.in +++ b/include/libssh2_config.h.in @@ -6,12 +6,21 @@ /* Define to 1 if you have the header file. */ #undef HAVE_FCNTL_H +/* Define to 1 if you have the `gettimeofday' function. */ +#undef HAVE_GETTIMEOFDAY + /* Define to 1 if you have the header file. */ #undef HAVE_INTTYPES_H /* Define to 1 if you have the header file. */ #undef HAVE_MEMORY_H +/* Define to 1 if you have the `poll' function. */ +#undef HAVE_POLL + +/* Define to 1 if you have the `select' function. */ +#undef HAVE_SELECT + /* Define to 1 if you have the header file. */ #undef HAVE_STDINT_H @@ -27,6 +36,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_STRING_H +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_SELECT_H + /* Define to 1 if you have the header file. */ #undef HAVE_SYS_STAT_H diff --git a/src/session.c b/src/session.c index c4d3994..a6296a3 100644 --- a/src/session.c +++ b/src/session.c @@ -42,6 +42,26 @@ #endif #include +#ifdef HAVE_GETTIMEOFDAY +#include +#include +#endif + +#ifdef HAVE_POLL +# include +#else +# ifdef HAVE_SELECT +# ifdef HAVE_SYS_SELECT_H +# include +# else +# include +# include +# endif +# endif +#endif + + + /* {{{ libssh2_default_alloc */ static LIBSSH2_ALLOC_FUNC(libssh2_default_alloc) @@ -616,3 +636,275 @@ LIBSSH2_API int libssh2_session_flag(LIBSSH2_SESSION *session, int flag, int val return session->flags; } /* }}} */ + +/* {{{ libssh2_poll_channel_read + * Returns 0 if no data is waiting on channel, + * non-0 if data is available + */ +static int libssh2_poll_channel_read(LIBSSH2_CHANNEL *channel, int extended) +{ + LIBSSH2_SESSION *session = channel->session; + LIBSSH2_PACKET *packet = session->packets.head; + + while (packet) { + if (((packet->data[0] == SSH_MSG_CHANNEL_DATA) && (extended == 0) && (channel->local.id == libssh2_ntohu32(packet->data + 1))) || + ((packet->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA) && (extended != 0) && (channel->local.id == libssh2_ntohu32(packet->data + 1)))) { + /* Found data waiting to be read */ + return 1; + } + packet = packet->next; + } + + return 0; +} +/* }}} */ + +/* {{{ libssh2_poll_channel_write + * Returns 0 if writing to channel would block, + * non-0 if data can be written without blocking + */ +inline int libssh2_poll_channel_write(LIBSSH2_CHANNEL *channel) +{ + return channel->local.window_size ? 1 : 0; +} +/* }}} */ + +/* {{{ libssh2_poll_listener_queued + * Returns 0 if no connections are waiting to be accepted + * non-0 if one or more connections are available + */ +inline int libssh2_poll_listener_queued(LIBSSH2_LISTENER *listener) +{ + return listener->queue ? 1 : 0; +} +/* }}} */ + +/* {{{ libssh2_poll + * Poll sockets, channels, and listeners for activity + */ +LIBSSH2_API int libssh2_poll(LIBSSH2_POLLFD *fds, unsigned int nfds, long timeout) +{ + long timeout_remaining; + int i, active_fds; +#ifdef HAVE_POLL + LIBSSH2_SESSION *session = NULL; + struct pollfd sockets[nfds]; + + /* Setup sockets for polling */ + for(i = 0; i < nfds; i++) { + fds[i].revents = 0; + switch (fds[i].type) { + case LIBSSH2_POLLFD_SOCKET: + sockets[i].fd = fds[i].fd.socket; + sockets[i].events = fds[i].events; + sockets[i].revents = 0; + break; + case LIBSSH2_POLLFD_CHANNEL: + sockets[i].fd = fds[i].fd.channel->session->socket_fd; + sockets[i].events = POLLIN; + sockets[i].revents = 0; + if (!session) session = fds[i].fd.channel->session; + break; + case LIBSSH2_POLLFD_LISTENER: + sockets[i].fd = fds[i].fd.listener->session->socket_fd; + sockets[i].events = POLLIN; + sockets[i].revents = 0; + if (!session) session = fds[i].fd.listener->session; + break; + default: + if (session) libssh2_error(session, LIBSSH2_ERROR_INVALID_POLL_TYPE, "Invalid descriptor passed to libssh2_poll()", 0); + return -1; + } + } +#elif defined(HAVE_SELECT) + LIBSSH2_SESSION *session = NULL; + int maxfd = 0; + fd_set rfds,wfds; + struct timeval tv; + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + for(i = 0; i < nfds; i++) { + fds[i].revents = 0; + switch (fds[i].type) { + case LIBSSH2_POLLFD_SOCKET: + if (fds[i].events & LIBSSH2_POLLFD_POLLIN) { + FD_SET(fds[i].fd.socket, &rfds); + if (fds[i].fd.socket > maxfd) maxfd = fds[i].fd.socket; + } + if (fds[i].events & LIBSSH2_POLLFD_POLLOUT) { + FD_SET(fds[i].fd.socket, &wfds); + if (fds[i].fd.socket > maxfd) maxfd = fds[i].fd.socket; + } + break; + case LIBSSH2_POLLFD_CHANNEL: + FD_SET(fds[i].fd.channel->session->socket_fd, &rfds); + if (fds[i].fd.channel->session->socket_fd > maxfd) maxfd = fds[i].fd.channel->session->socket_fd; + if (!session) session = fds[i].fd.channel->session; + break; + case LIBSSH2_POLLFD_LISTENER: + FD_SET(fds[i].fd.listener->session->socket_fd, &rfds); + if (fds[i].fd.listener->session->socket_fd > maxfd) maxfd = fds[i].fd.listener->session->socket_fd; + if (!session) session = fds[i].fd.listener->session; + break; + default: + if (session) libssh2_error(session, LIBSSH2_ERROR_INVALID_POLL_TYPE, "Invalid descriptor passed to libssh2_poll()", 0); + return -1; + } + } +#else + /* No select() or poll() + * no sockets sturcture to setup + */ + + timeout = 0; +#endif /* HAVE_POLL or HAVE_SELECT */ + + timeout_remaining = timeout; + do { +#if defined(HAVE_POLL) || defined(HAVE_SELECT) + int sysret; +#endif + + active_fds = 0; + + for (i = 0; i < nfds; i++) { + if (fds[i].events != fds[i].revents) { + switch (fds[i].type) { + case LIBSSH2_POLLFD_CHANNEL: + if ((fds[i].events & LIBSSH2_POLLFD_POLLIN) && /* Want to be ready for read */ + ((fds[i].revents & LIBSSH2_POLLFD_POLLIN) == 0)) { /* Not yet known to be ready for read */ + fds[i].revents |= libssh2_poll_channel_read(fds[i].fd.channel, 0) ? LIBSSH2_POLLFD_POLLIN : 0; + } + if ((fds[i].events & LIBSSH2_POLLFD_POLLEXT) && /* Want to be ready for extended read */ + ((fds[i].revents & LIBSSH2_POLLFD_POLLEXT) == 0)) { /* Not yet known to be ready for extended read */ + fds[i].revents |= libssh2_poll_channel_read(fds[i].fd.channel, 1) ? LIBSSH2_POLLFD_POLLEXT : 0; + } + if ((fds[i].events & LIBSSH2_POLLFD_POLLOUT) && /* Want to be ready for write */ + ((fds[i].revents & LIBSSH2_POLLFD_POLLOUT) == 0)) { /* Not yet known to be ready for write */ + fds[i].revents |= libssh2_poll_channel_write(fds[i].fd.channel) ? LIBSSH2_POLLFD_POLLOUT : 0; + } + break; + case LIBSSH2_POLLFD_LISTENER: + if ((fds[i].events & LIBSSH2_POLLFD_POLLIN) && /* Want a connection */ + ((fds[i].revents & LIBSSH2_POLLFD_POLLIN) == 0)) { /* No connections known of yet */ + fds[i].revents |= libssh2_poll_listener_queued(fds[i].fd.listener) ? LIBSSH2_POLLFD_POLLIN : 0; + } + break; + } + } + if (fds[i].revents) { + active_fds++; + } + } + + if (active_fds) { + /* Don't block on the sockets if we have channels/listeners which are ready */ + timeout_remaining = 0; + } + +#ifdef HAVE_POLL + +#ifdef HAVE_GETTIMEOFDAY +{ + struct timeval tv_begin, tv_end; + + gettimeofday((struct timeval *)&tv_begin, NULL); + sysret = poll(sockets, nfds, timeout_remaining); + gettimeofday((struct timeval *)&tv_end, NULL); + timeout_remaining -= (tv_end.tv_sec - tv_begin.tv_sec) * 1000; + timeout_remaining -= ceil((tv_end.tv_usec - tv_begin.tv_usec) / 1000); +} +#else + /* If the platform doesn't support gettimeofday, + * then just make the call non-blocking and walk away + */ + sysret = poll(sockets, nfds, timeout_remaining); + timeout_remaining = 0; +#endif /* HAVE_GETTIMEOFDAY */ + + if (sysret > 0) { + for (i = 0; i < nfds; i++) { + switch (fds[i].type) { + case LIBSSH2_POLLFD_SOCKET: + fds[i].revents = sockets[i].revents; + sockets[i].revents = 0; /* In case we loop again, be nice */ + if (fds[i].revents) { + active_fds++; + } + break; + case LIBSSH2_POLLFD_CHANNEL: + if (sockets[i].events & POLLIN) { + /* Spin session until no data available */ + while (libssh2_packet_read(fds[i].fd.channel->session, 0) > 0); + } + sockets[i].revents = 0; + break; + case LIBSSH2_POLLFD_LISTENER: + if (sockets[i].events & POLLIN) { + /* Spin session until no data available */ + while (libssh2_packet_read(fds[i].fd.listener->session, 0) > 0); + } + sockets[i].revents = 0; + break; + } + } + } +#elif defined(HAVE_SELECT) + tv.tv_sec = timeout_remaining / 1000; + tv.tv_usec = (timeout_remaining % 1000) * 1000; +#ifdef HAVE_GETTIMEOFDAY +{ + struct timeval tv_begin, tv_end; + + gettimeofday((struct timeval *)&tv_begin, NULL); + sysret = select(maxfd, &rfds, &wfds, NULL, &tv); + gettimeofday((struct timeval *)&tv_end, NULL); + + timeout_remaining -= (tv_end.tv_sec - tv_begin.tv_sec) * 1000; + timeout_remaining -= ceil((tv_end.tv_usec - tv_begin.tv_usec) / 1000); +} +#else + /* If the platform doesn't support gettimeofday, + * then just make the call non-blocking and walk away + */ + sysret = select(maxfd, &rfds, &wfds, NULL, &tv); + timeout_remaining = 0; +#endif + + if (sysret > 0) { + for (i = 0; i < nfds; i++) { + switch (fds[i].type) { + case LIBSSH2_POLLFD_SOCKET: + if (FD_ISSET(fds[i].fd.socket, &rfds)) { + fds[i].revents |= LIBSSH2_POLLFD_POLLIN; + } + if (FD_ISSET(fds[i].fd.socket, &wfds)) { + fds[i].revents |= LIBSSH2_POLLFD_POLLOUT; + } + if (fds[i].revents) { + active_fds++; + } + break; + case LIBSSH2_POLLFD_CHANNEL: + if (FD_ISSET(fds[i].fd.channel->session->socket_fd, &rfds)) { + /* Spin session until no data available */ + while (libssh2_packet_read(fds[i].fd.channel->session, 0) > 0); + } + break; + case LIBSSH2_POLLFD_LISTENER: + if (FD_ISSET(fds[i].fd.listener->session->socket_fd, &rfds)) { + /* Spin session until no data available */ + while (libssh2_packet_read(fds[i].fd.listener->session, 0) > 0); + } + break; + } + } + } +#endif /* else no select() or poll() -- timeout (and by extension timeout_remaining) will be equal to 0 */ + } while ((timeout_remaining > 0) && !active_fds); + + return active_fds; +} +/* }}} */ +