diff --git a/configure.ac b/configure.ac index 08002509..e04d581a 100644 --- a/configure.ac +++ b/configure.ac @@ -63,6 +63,13 @@ AC_CHECK_LIB([pthread], [pthread_create], [PTHREAD_LIBS=-lpthread AC_DEFINE([HAVE_LIBPTHREAD],, [Whether libpthread was found])]) +# Include libdl for dlopen() if necessary +AC_CHECK_LIB([dl], [dlopen], + [DL_LIBS=-ldl], + [AC_CHECK_DECL([dlopen],, + AC_MSG_ERROR("libdl is required on systems which do not otherwise provide dlopen()"), + [#include ])]) + # OSSP UUID AC_CHECK_LIB([ossp-uuid], [uuid_make], [UUID_LIBS=-lossp-uuid], AC_CHECK_LIB([uuid], [uuid_make], [UUID_LIBS=-luuid], @@ -82,10 +89,7 @@ AC_CHECK_DECL([uuid_make],, # cunit AC_CHECK_LIB([cunit], [CU_run_test], [CUNIT_LIBS=-lcunit]) -# WinSock -AC_CHECK_LIB([wsock32], [main]) - -AC_SUBST(LIBADD_DLOPEN) +AC_SUBST(DL_LIBS) AC_SUBST(MATH_LIBS) AC_SUBST(PNG_LIBS) AC_SUBST(JPEG_LIBS) @@ -107,6 +111,11 @@ AC_CHECK_DECL([cairo_format_stride_for_width], [Whether cairo_format_stride_for_width() is defined])],, [#include ]) +AC_CHECK_DECL([poll], + [AC_DEFINE([HAVE_POLL],, + [Whether poll() is defined])],, + [#include ]) + # Typedefs AC_TYPE_SIZE_T AC_TYPE_SSIZE_T @@ -283,6 +292,30 @@ fi AM_CONDITIONAL([ENABLE_SSL], [test "x${have_ssl}" = "xyes"]) AC_SUBST(SSL_LIBS) +# +# Winsock +# + +have_winsock=disabled +WINSOCK_LIBS= +AC_ARG_WITH([winsock], + [AS_HELP_STRING([--with-winsock], + [support Windows Sockets API @<:@default=check@:>@])], + [], + [with_winsock=check]) + +if test "x$with_winsock" != "xno" +then + have_winsock=yes + AC_CHECK_LIB([wsock32], [main], + [WINSOCK_LIBS="-lwsock32"] + [AC_DEFINE([ENABLE_WINSOCK],, + [Whether Windows Socket API support is enabled])], + [have_winsock=no]) +fi + +AM_CONDITIONAL([ENABLE_WINSOCK], [test "x${have_winsock}" = "xyes"]) +AC_SUBST(WINSOCK_LIBS) # # Ogg Vorbis @@ -1222,6 +1255,7 @@ $PACKAGE_NAME version $PACKAGE_VERSION libvorbis ........... ${have_vorbis} libpulse ............ ${have_pulse} libwebp ............. ${have_webp} + wsock32 ............. ${have_winsock} Protocol support: diff --git a/src/common/recording.c b/src/common/recording.c index 2c953b5a..6b1dcccc 100644 --- a/src/common/recording.c +++ b/src/common/recording.c @@ -22,6 +22,10 @@ #include #include +#ifdef __MINGW32__ +#include +#endif + #include #include #include @@ -107,6 +111,8 @@ static int guac_common_recording_open(const char* path, } /* end if open succeeded */ +/* Explicit file locks are required only on POSIX platforms */ +#ifndef __MINGW32__ /* Lock entire output file for writing by the current process */ struct flock file_lock = { .l_type = F_WRLCK, @@ -121,6 +127,7 @@ static int guac_common_recording_open(const char* path, close(fd); return -1; } +#endif return fd; @@ -132,7 +139,11 @@ int guac_common_recording_create(guac_client* client, const char* path, char filename[GUAC_COMMON_RECORDING_MAX_NAME_LENGTH]; /* Create path if it does not exist, fail if impossible */ +#ifndef __MINGW32__ if (create_path && mkdir(path, S_IRWXU) && errno != EEXIST) { +#else + if (create_path && _mkdir(path) && errno != EEXIST) { +#endif guac_client_log(client, GUAC_LOG_ERROR, "Creation of recording failed: %s", strerror(errno)); return 1; diff --git a/src/libguac/Makefile.am b/src/libguac/Makefile.am index a1f47edc..0d30f638 100644 --- a/src/libguac/Makefile.am +++ b/src/libguac/Makefile.am @@ -68,7 +68,8 @@ noinst_HEADERS = \ encode-png.h \ palette.h \ user-handlers.h \ - raw_encoder.h + raw_encoder.h \ + wait-fd.h libguac_la_SOURCES = \ audio.c \ @@ -92,7 +93,8 @@ libguac_la_SOURCES = \ unicode.c \ user.c \ user-handlers.c \ - user-handshake.c + user-handshake.c \ + wait-fd.c # Compile WebP support if available if ENABLE_WEBP @@ -106,19 +108,26 @@ libguac_la_SOURCES += socket-ssl.c libguacinc_HEADERS += guacamole/socket-ssl.h endif +# Winsock support +if ENABLE_WINSOCK +libguac_la_SOURCES += socket-wsa.c +libguacinc_HEADERS += guacamole/socket-wsa.h +endif + libguac_la_CFLAGS = \ -Werror -Wall -pedantic -Iguacamole libguac_la_LDFLAGS = \ -version-info 14:0:2 \ + -no-undefined \ @CAIRO_LIBS@ \ + @DL_LIBS@ \ @JPEG_LIBS@ \ @PNG_LIBS@ \ @PTHREAD_LIBS@ \ @SSL_LIBS@ \ @UUID_LIBS@ \ @VORBIS_LIBS@ \ - @WEBP_LIBS@ + @WEBP_LIBS@ \ + @WINSOCK_LIBS@ -libguac_la_LIBADD = \ - @LIBADD_DLOPEN@ diff --git a/src/libguac/guacamole/socket-wsa.h b/src/libguac/guacamole/socket-wsa.h new file mode 100644 index 00000000..9eb1d525 --- /dev/null +++ b/src/libguac/guacamole/socket-wsa.h @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_SOCKET_WSA_H +#define GUAC_SOCKET_WSA_H + +/** + * Provides an implementation of guac_socket specific to the Windows Socket API + * (aka WSA or "winsock"). This header will only be available if libguac was + * built with WSA support. + * + * @file socket-wsa.h + */ + +#include "socket-types.h" + +#include + +/** + * Creates a new guac_socket which will use the Windows Socket API (aka WSA or + * "winsock") for all communication. Freeing this guac_socket will + * automatically close the associated SOCKET handle. + * + * @param sock + * The WSA SOCKET handle to use for the connection underlying the created + * guac_socket. + * + * @return + * A newly-allocated guac_socket which will transparently use the Windows + * Socket API for all communication. + */ +guac_socket* guac_socket_open_wsa(SOCKET sock); + +#endif + diff --git a/src/libguac/socket-fd.c b/src/libguac/socket-fd.c index 0ed57485..e9cce0fb 100644 --- a/src/libguac/socket-fd.c +++ b/src/libguac/socket-fd.c @@ -21,8 +21,8 @@ #include "error.h" #include "socket.h" +#include "wait-fd.h" -#include #include #include #include @@ -31,6 +31,10 @@ #include #include +#ifdef ENABLE_WINSOCK +#include +#endif + /** * Data associated with an open socket which writes to a file descriptor. */ @@ -96,8 +100,8 @@ ssize_t guac_socket_fd_write(guac_socket* socket, int retval; -#ifdef __MINGW32__ - /* MINGW32 WINSOCK only works with send() */ +#ifdef ENABLE_WINSOCK + /* WSA only works with send() */ retval = send(data->fd, buf, count, 0); #else /* Use write() for all other platforms */ @@ -142,8 +146,15 @@ static ssize_t guac_socket_fd_read_handler(guac_socket* socket, guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data; - /* Read from socket */ - int retval = read(data->fd, buf, count); + int retval; + +#ifdef ENABLE_WINSOCK + /* Winsock only works with recv() */ + retval = recv(data->fd, buf, count, 0); +#else + /* Use read() for all other platforms */ + retval = read(data->fd, buf, count); +#endif /* Record errors in guac_error */ if (retval < 0) { @@ -330,24 +341,9 @@ static ssize_t guac_socket_fd_write_handler(guac_socket* socket, static int guac_socket_fd_select_handler(guac_socket* socket, int usec_timeout) { + /* Wait for data on socket */ guac_socket_fd_data* data = (guac_socket_fd_data*) socket->data; - - int retval; - - /* Initialize with single underlying file descriptor */ - struct pollfd fds[1] = {{ - .fd = data->fd, - .events = POLLIN, - .revents = 0, - }}; - - /* No timeout if usec_timeout is negative */ - if (usec_timeout < 0) - retval = poll(fds, 1, -1); - - /* Handle timeout if specified, rounding up to poll()'s granularity */ - else - retval = poll(fds, 1, (usec_timeout + 999) / 1000); + int retval = guac_wait_for_fd(data->fd, usec_timeout); /* Properly set guac_error */ if (retval < 0) { @@ -355,7 +351,7 @@ static int guac_socket_fd_select_handler(guac_socket* socket, guac_error_message = "Error while waiting for data on socket"; } - if (retval == 0) { + else if (retval == 0) { guac_error = GUAC_STATUS_TIMEOUT; guac_error_message = "Timeout while waiting for data on socket"; } diff --git a/src/libguac/socket-ssl.c b/src/libguac/socket-ssl.c index f8f3f87b..1a631fe1 100644 --- a/src/libguac/socket-ssl.c +++ b/src/libguac/socket-ssl.c @@ -22,8 +22,8 @@ #include "error.h" #include "socket-ssl.h" #include "socket.h" +#include "wait-fd.h" -#include #include #include @@ -69,23 +69,7 @@ static ssize_t __guac_socket_ssl_write_handler(guac_socket* socket, static int __guac_socket_ssl_select_handler(guac_socket* socket, int usec_timeout) { guac_socket_ssl_data* data = (guac_socket_ssl_data*) socket->data; - - int retval; - - /* Initialize with single underlying file descriptor */ - struct pollfd fds[1] = {{ - .fd = data->fd, - .events = POLLIN, - .revents = 0, - }}; - - /* No timeout if usec_timeout is negative */ - if (usec_timeout < 0) - retval = poll(fds, 1, -1); - - /* Handle timeout if specified, rounding up to poll()'s granularity */ - else - retval = poll(fds, 1, (usec_timeout + 999) / 1000); + int retval = guac_wait_for_fd(data->fd, usec_timeout); /* Properly set guac_error */ if (retval < 0) { diff --git a/src/libguac/socket-wsa.c b/src/libguac/socket-wsa.c new file mode 100644 index 00000000..1b59176a --- /dev/null +++ b/src/libguac/socket-wsa.c @@ -0,0 +1,448 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "error.h" +#include "socket.h" + +#include +#include +#include +#include +#include +#include + +#include + +/** + * Data associated with an open socket which uses the Windows Socket API. + */ +typedef struct guac_socket_wsa_data { + + /** + * The associated Windows socket handle. + */ + SOCKET sock; + + /** + * The number of bytes currently in the main write buffer. + */ + int written; + + /** + * The main write buffer. Bytes written go here before being flushed + * to the open socket. + */ + char out_buf[GUAC_SOCKET_OUTPUT_BUFFER_SIZE]; + + /** + * Lock which is acquired when an instruction is being written, and + * released when the instruction is finished being written. + */ + pthread_mutex_t socket_lock; + + /** + * Lock which protects access to the internal buffer of this socket, + * guaranteeing atomicity of writes and flushes. + */ + pthread_mutex_t buffer_lock; + +} guac_socket_wsa_data; + +/** + * Writes the entire contents of the given buffer to the SOCKET handle + * associated with the given socket, retrying as necessary until the whole + * buffer is written, and aborting if an error occurs. + * + * @param socket + * The guac_socket associated with the SOCKET handle to which the given + * buffer should be written. + * + * @param buf + * The buffer of data to write to the given guac_socket. + * + * @param count + * The number of bytes within the given buffer. + * + * @return + * The number of bytes written, which will be exactly the size of the given + * buffer, or a negative value if an error occurs. + */ +ssize_t guac_socket_wsa_write(guac_socket* socket, + const void* buf, size_t count) { + + guac_socket_wsa_data* data = (guac_socket_wsa_data*) socket->data; + const char* buffer = buf; + + /* Write until completely written */ + while (count > 0) { + + int retval = send(data->sock, buffer, count, 0); + + /* Record errors in guac_error */ + if (retval < 0) { + guac_error = GUAC_STATUS_SEE_ERRNO; + guac_error_message = "Error writing data to socket"; + return retval; + } + + /* Advance buffer as data retval */ + buffer += retval; + count -= retval; + + } + + return 0; + +} + +/** + * Attempts to read from the underlying SOCKET handle of the given guac_socket, + * populating the given buffer. + * + * @param socket + * The guac_socket being read from. + * + * @param buf + * The arbitrary buffer which we must populate with data. + * + * @param count + * The maximum number of bytes to read into the buffer. + * + * @return + * The number of bytes read, or -1 if an error occurs. + */ +static ssize_t guac_socket_wsa_read_handler(guac_socket* socket, + void* buf, size_t count) { + + guac_socket_wsa_data* data = (guac_socket_wsa_data*) socket->data; + + /* Read from socket */ + int retval = recv(data->sock, buf, count, 0); + + /* Record errors in guac_error */ + if (retval < 0) { + guac_error = GUAC_STATUS_SEE_ERRNO; + guac_error_message = "Error reading data from socket"; + } + + return retval; + +} + +/** + * Flushes the contents of the output buffer of the given socket immediately, + * without first locking access to the output buffer. This function must ONLY + * be called if the buffer lock has already been acquired. + * + * @param socket + * The guac_socket to flush. + * + * @return + * Zero if the flush operation was successful, non-zero otherwise. + */ +static ssize_t guac_socket_wsa_flush(guac_socket* socket) { + + guac_socket_wsa_data* data = (guac_socket_wsa_data*) socket->data; + + /* Flush remaining bytes in buffer */ + if (data->written > 0) { + + /* Write ALL bytes in buffer immediately */ + if (guac_socket_wsa_write(socket, data->out_buf, data->written)) + return 1; + + data->written = 0; + } + + return 0; + +} + +/** + * Flushes the internal buffer of the given guac_socket, writing all data + * to the underlying SOCKET handle. + * + * @param socket + * The guac_socket to flush. + * + * @return + * Zero if the flush operation was successful, non-zero otherwise. + */ +static ssize_t guac_socket_wsa_flush_handler(guac_socket* socket) { + + int retval; + guac_socket_wsa_data* data = (guac_socket_wsa_data*) socket->data; + + /* Acquire exclusive access to buffer */ + pthread_mutex_lock(&(data->buffer_lock)); + + /* Flush contents of buffer */ + retval = guac_socket_wsa_flush(socket); + + /* Relinquish exclusive access to buffer */ + pthread_mutex_unlock(&(data->buffer_lock)); + + return retval; + +} + +/** + * Writes the contents of the buffer to the output buffer of the given socket, + * flushing the output buffer as necessary, without first locking access to the + * output buffer. This function must ONLY be called if the buffer lock has + * already been acquired. + * + * @param socket + * The guac_socket to write the given buffer to. + * + * @param buf + * The buffer to write to the given socket. + * + * @param count + * The number of bytes in the given buffer. + * + * @return + * The number of bytes written, or a negative value if an error occurs + * during write. + */ +static ssize_t guac_socket_wsa_write_buffered(guac_socket* socket, + const void* buf, size_t count) { + + size_t original_count = count; + const char* current = buf; + guac_socket_wsa_data* data = (guac_socket_wsa_data*) socket->data; + + /* Append to buffer, flush if necessary */ + while (count > 0) { + + int chunk_size; + int remaining = sizeof(data->out_buf) - data->written; + + /* If no space left in buffer, flush and retry */ + if (remaining == 0) { + + /* Abort if error occurs during flush */ + if (guac_socket_wsa_flush(socket)) + return -1; + + /* Retry buffer append */ + continue; + + } + + /* Calculate size of chunk to be written to buffer */ + chunk_size = count; + if (chunk_size > remaining) + chunk_size = remaining; + + /* Update output buffer */ + memcpy(data->out_buf + data->written, current, chunk_size); + data->written += chunk_size; + + /* Update provided buffer */ + current += chunk_size; + count -= chunk_size; + + } + + /* All bytes have been written, possibly some to the internal buffer */ + return original_count; + +} + +/** + * Appends the provided data to the internal buffer for future writing. The + * actual write attempt will occur only upon flush, or when the internal buffer + * is full. + * + * @param socket + * The guac_socket being write to. + * + * @param buf + * The arbitrary buffer containing the data to be written. + * + * @param count + * The number of bytes contained within the buffer. + * + * @return + * The number of bytes written, or -1 if an error occurs. + */ +static ssize_t guac_socket_wsa_write_handler(guac_socket* socket, + const void* buf, size_t count) { + + int retval; + guac_socket_wsa_data* data = (guac_socket_wsa_data*) socket->data; + + /* Acquire exclusive access to buffer */ + pthread_mutex_lock(&(data->buffer_lock)); + + /* Write provided data to buffer */ + retval = guac_socket_wsa_write_buffered(socket, buf, count); + + /* Relinquish exclusive access to buffer */ + pthread_mutex_unlock(&(data->buffer_lock)); + + return retval; + +} + +/** + * Waits for data on the underlying SOCKET handle of the given socket to + * become available such that the next read operation will not block. + * + * @param socket + * The guac_socket to wait for. + * + * @param usec_timeout + * The maximum amount of time to wait for data, in microseconds, or -1 to + * potentially wait forever. + * + * @return + * A positive value on success, zero if the timeout elapsed and no data is + * available, or a negative value if an error occurs. + */ +static int guac_socket_wsa_select_handler(guac_socket* socket, + int usec_timeout) { + + guac_socket_wsa_data* data = (guac_socket_wsa_data*) socket->data; + + fd_set sockets; + struct timeval timeout; + int retval; + + /* Initialize fd_set with single underlying socket handle */ + FD_ZERO(&sockets); + FD_SET(data->sock, &sockets); + + /* No timeout if usec_timeout is negative */ + if (usec_timeout < 0) + retval = select(0, &sockets, NULL, NULL, NULL); + + /* Handle timeout if specified */ + else { + timeout.tv_sec = usec_timeout / 1000000; + timeout.tv_usec = usec_timeout % 1000000; + retval = select(0, &sockets, NULL, NULL, &timeout); + } + + /* Properly set guac_error */ + if (retval < 0) { + guac_error = GUAC_STATUS_SEE_ERRNO; + guac_error_message = "Error while waiting for data on socket"; + } + + if (retval == 0) { + guac_error = GUAC_STATUS_TIMEOUT; + guac_error_message = "Timeout while waiting for data on socket"; + } + + return retval; + +} + +/** + * Frees all implementation-specific data associated with the given socket, but + * not the socket object itself. + * + * @param socket + * The guac_socket whose associated data should be freed. + * + * @return + * Zero if the data was successfully freed, non-zero otherwise. This + * implementation always succeeds, and will always return zero. + */ +static int guac_socket_wsa_free_handler(guac_socket* socket) { + + guac_socket_wsa_data* data = (guac_socket_wsa_data*) socket->data; + + /* Destroy locks */ + pthread_mutex_destroy(&(data->socket_lock)); + pthread_mutex_destroy(&(data->buffer_lock)); + + /* Close socket */ + closesocket(data->sock); + + free(data); + return 0; + +} + +/** + * Acquires exclusive access to the given socket. + * + * @param socket + * The guac_socket to which exclusive access is required. + */ +static void guac_socket_wsa_lock_handler(guac_socket* socket) { + + guac_socket_wsa_data* data = (guac_socket_wsa_data*) socket->data; + + /* Acquire exclusive access to socket */ + pthread_mutex_lock(&(data->socket_lock)); + +} + +/** + * Relinquishes exclusive access to the given socket. + * + * @param socket + * The guac_socket to which exclusive access is no longer required. + */ +static void guac_socket_wsa_unlock_handler(guac_socket* socket) { + + guac_socket_wsa_data* data = (guac_socket_wsa_data*) socket->data; + + /* Relinquish exclusive access to socket */ + pthread_mutex_unlock(&(data->socket_lock)); + +} + +guac_socket* guac_socket_open_wsa(SOCKET sock) { + + pthread_mutexattr_t lock_attributes; + + /* Allocate socket and associated data */ + guac_socket* socket = guac_socket_alloc(); + guac_socket_wsa_data* data = malloc(sizeof(guac_socket_wsa_data)); + + /* Store socket as socket data */ + data->sock = sock; + data->written = 0; + socket->data = data; + + pthread_mutexattr_init(&lock_attributes); + pthread_mutexattr_setpshared(&lock_attributes, PTHREAD_PROCESS_SHARED); + + /* Init locks */ + pthread_mutex_init(&(data->socket_lock), &lock_attributes); + pthread_mutex_init(&(data->buffer_lock), &lock_attributes); + + /* Set read/write handlers */ + socket->read_handler = guac_socket_wsa_read_handler; + socket->write_handler = guac_socket_wsa_write_handler; + socket->select_handler = guac_socket_wsa_select_handler; + socket->lock_handler = guac_socket_wsa_lock_handler; + socket->unlock_handler = guac_socket_wsa_unlock_handler; + socket->flush_handler = guac_socket_wsa_flush_handler; + socket->free_handler = guac_socket_wsa_free_handler; + + return socket; + +} + diff --git a/src/libguac/wait-fd.c b/src/libguac/wait-fd.c new file mode 100644 index 00000000..d6079be3 --- /dev/null +++ b/src/libguac/wait-fd.c @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" + +#ifdef ENABLE_WINSOCK +# include +#else +# ifdef HAVE_POLL +# include +# else +# include +# endif +#endif + +#ifdef HAVE_POLL +int guac_wait_for_fd(int fd, int usec_timeout) { + + /* Initialize with single underlying file descriptor */ + struct pollfd fds[1] = {{ + .fd = fd, + .events = POLLIN, + .revents = 0 + }}; + + /* No timeout if usec_timeout is negative */ + if (usec_timeout < 0) + return poll(fds, 1, -1); + + /* Handle timeout if specified, rounding up to poll()'s granularity */ + return poll(fds, 1, (usec_timeout + 999) / 1000); + +} +#else +int guac_wait_for_fd(int fd, int usec_timeout) { + + fd_set fds; + + /* Initialize fd_set with single underlying file descriptor */ + FD_ZERO(&fds); + FD_SET(fd, &fds); + + /* No timeout if usec_timeout is negative */ + if (usec_timeout < 0) + return select(fd + 1, &fds, NULL, NULL, NULL); + + /* Handle timeout if specified */ + struct timeval timeout = { + .tv_sec = usec_timeout / 1000000, + .tv_usec = usec_timeout % 1000000 + }; + + return select(fd + 1, &fds, NULL, NULL, &timeout); + +} +#endif diff --git a/src/libguac/wait-fd.h b/src/libguac/wait-fd.h new file mode 100644 index 00000000..b5e2e5f2 --- /dev/null +++ b/src/libguac/wait-fd.h @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_WAIT_FD_H +#define GUAC_WAIT_FD_H + +/** + * Waits for data to be available for reading on a given file descriptor, + * similar to the POSIX select() and poll() functions. + * + * @param fd + * The file descriptor to wait for. + * + * @param usec_timeout + * The maximum number of microseconds to wait for data, or -1 to + * potentially wait forever. + * + * @return + * Positive if data is available for reading, zero if the timeout elapsed + * and no data is available, negative if an error occurs, in which case + * errno will also be set. + */ +int guac_wait_for_fd(int fd, int usec_timeout); + +#endif