From 533a6b0240c10755b9c1e47ab20611f289dac412 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sun, 28 Aug 2011 21:52:16 +0200 Subject: tcp_connect: generic library for establishing TCP connections --- src/tcp_connect.c | 252 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/tcp_connect.h | 96 +++++++++++++++++++++ 2 files changed, 348 insertions(+) create mode 100644 src/tcp_connect.c create mode 100644 src/tcp_connect.h (limited to 'src') diff --git a/src/tcp_connect.c b/src/tcp_connect.c new file mode 100644 index 000000000..bea823955 --- /dev/null +++ b/src/tcp_connect.c @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "tcp_connect.h" +#include "fd_util.h" +#include "io_thread.h" +#include "glib_compat.h" +#include "glib_socket.h" + +#include +#include + +#ifdef WIN32 +#define WINVER 0x0501 +#include +#include +#else +#include +#include +#endif + +struct tcp_connect { + const struct tcp_connect_handler *handler; + void *handler_ctx; + + int fd; + GSource *source; + + unsigned timeout_ms; + GSource *timeout_source; +}; + +static bool +is_in_progress_errno(int e) +{ +#ifdef WIN32 + return e == WSAEINPROGRESS || e == WSAEWOULDBLOCK; +#else + return e == EINPROGRESS; +#endif +} + +static gboolean +tcp_connect_event(G_GNUC_UNUSED GIOChannel *source, + G_GNUC_UNUSED GIOCondition condition, + gpointer data) +{ + struct tcp_connect *c = data; + + assert(c->source != NULL); + assert(c->timeout_source != NULL); + + /* clear the socket source */ + g_source_unref(c->source); + c->source = NULL; + + /* delete the timeout source */ + g_source_destroy(c->timeout_source); + g_source_unref(c->timeout_source); + c->timeout_source = NULL; + + /* obtain the connect result */ + int s_err = 0; + socklen_t s_err_size = sizeof(s_err); + if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, + (char*)&s_err, &s_err_size) < 0) + s_err = errno; + + if (s_err == 0) { + /* connection established successfully */ + + c->handler->success(c->fd, c->handler_ctx); + } else { + /* there was an I/O error; close the socket and pass + the error to the handler */ + + close_socket(c->fd); + + GError *error = + g_error_new_literal(g_file_error_quark(), s_err, + g_strerror(s_err)); + c->handler->error(error, c->handler_ctx); + } + + return false; +} + +static gboolean +tcp_connect_timeout(gpointer data) +{ + struct tcp_connect *c = data; + + assert(c->source != NULL); + assert(c->timeout_source != NULL); + + /* clear the timeout source */ + g_source_unref(c->timeout_source); + c->timeout_source = NULL; + + /* delete the socket source */ + g_source_destroy(c->source); + g_source_unref(c->source); + c->source = NULL; + + /* report timeout to handler */ + c->handler->timeout(c->handler_ctx); + + return false; +} + +static gpointer +tcp_connect_init(gpointer data) +{ + struct tcp_connect *c = data; + + /* create a connect source */ + GIOChannel *channel = g_io_channel_new_socket(c->fd); + c->source = g_io_create_watch(channel, G_IO_OUT); + g_io_channel_unref(channel); + + g_source_set_callback(c->source, (GSourceFunc)tcp_connect_event, c, + NULL); + g_source_attach(c->source, io_thread_context()); + + /* create a timeout source */ + if (c->timeout_ms > 0) + c->timeout_source = + io_thread_timeout_add(c->timeout_ms, + tcp_connect_timeout, c); + + return NULL; +} + +void +tcp_connect_address(const struct sockaddr *address, size_t address_length, + unsigned timeout_ms, + const struct tcp_connect_handler *handler, void *ctx, + struct tcp_connect **handle_r) +{ + assert(address != NULL); + assert(address_length > 0); + assert(handler != NULL); + assert(handler->success != NULL); + assert(handler->error != NULL); + assert(handler->canceled != NULL); + assert(handler->timeout != NULL || timeout_ms == 0); + assert(handle_r != NULL); + assert(*handle_r == NULL); + + int fd = socket_cloexec_nonblock(address->sa_family, SOCK_STREAM, 0); + if (fd < 0) { + GError *error = + g_error_new_literal(g_file_error_quark(), errno, + g_strerror(errno)); + handler->error(error, ctx); + return; + } + + int ret = connect(fd, address, address_length); + if (ret >= 0) { + /* quick connect, no I/O thread */ + handler->success(fd, ctx); + return; + } + + if (!is_in_progress_errno(errno)) { + GError *error = + g_error_new_literal(g_file_error_quark(), errno, + g_strerror(errno)); + close_socket(fd); + handler->error(error, ctx); + return; + } + + /* got EINPROGRESS, use the I/O thread to wait for the + operation to finish */ + + struct tcp_connect *c = g_new(struct tcp_connect, 1); + c->handler = handler; + c->handler_ctx = ctx; + c->fd = fd; + c->source = NULL; + c->timeout_ms = timeout_ms; + c->timeout_source = NULL; + + *handle_r = c; + + io_thread_call(tcp_connect_init, c); +} + +static gpointer +tcp_connect_cancel_callback(gpointer data) +{ + struct tcp_connect *c = data; + + assert((c->source == NULL) == (c->timeout_source == NULL)); + + if (c->source == NULL) + return NULL; + + /* delete the socket source */ + g_source_destroy(c->source); + g_source_unref(c->source); + c->source = NULL; + + /* delete the timeout source */ + g_source_destroy(c->timeout_source); + g_source_unref(c->timeout_source); + c->timeout_source = NULL; + + /* close the socket */ + close_socket(c->fd); + + /* notify the handler */ + c->handler->canceled(c->handler_ctx); + + return NULL; +} + +void +tcp_connect_cancel(struct tcp_connect *c) +{ + if (c->source == NULL) + return; + + io_thread_call(tcp_connect_cancel_callback, c); +} + +void +tcp_connect_free(struct tcp_connect *c) +{ + assert(c->source == NULL); + + g_free(c); +} diff --git a/src/tcp_connect.h b/src/tcp_connect.h new file mode 100644 index 000000000..bdbe85c14 --- /dev/null +++ b/src/tcp_connect.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_TCP_CONNECT_H +#define MPD_TCP_CONNECT_H + +#include + +struct sockaddr; + +struct tcp_connect_handler { + /** + * The connection was established successfully. + * + * @param fd a file descriptor that must be closed with + * close_socket() when finished + */ + void (*success)(int fd, void *ctx); + + /** + * An error has occurred. The method is responsible for + * freeing the GError. + */ + void (*error)(GError *error, void *ctx); + + /** + * The connection could not be established in the specified + * time span. + */ + void (*timeout)(void *ctx); + + /** + * The operation was canceled before a result was available. + */ + void (*canceled)(void *ctx); +}; + +struct tcp_connect; + +/** + * Establish a TCP connection to the specified address. + * + * Note that the result may be available before this function returns. + * + * The caller must free this object with tcp_connect_free(). + * + * @param timeout_ms time out after this number of milliseconds; 0 + * means no timeout + * @param handle_r a handle that can be used to cancel the operation; + * the caller must initialize it to NULL + */ +void +tcp_connect_address(const struct sockaddr *address, size_t address_length, + unsigned timeout_ms, + const struct tcp_connect_handler *handler, void *ctx, + struct tcp_connect **handle_r); + +/** + * Cancel the operation. It is possible that the result is delivered + * before the operation has been canceled; in that case, the + * canceled() handler method will not be invoked. + * + * Even after calling this function, tcp_connect_free() must still be + * called to free memory. + */ +void +tcp_connect_cancel(struct tcp_connect *handle); + +/** + * Free memory used by this object. + * + * This function is not thread safe. It must not be called while + * other threads are still working with it. If no callback has been + * invoked so far, then you must call tcp_connect_cancel() to release + * I/O thread resources, before calling this function. + */ +void +tcp_connect_free(struct tcp_connect *handle); + +#endif -- cgit v1.2.3