From 0ed117e41692a6ee764984e6855492e5945f7883 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 17 Mar 2017 15:53:09 +0100 Subject: [PATCH 1/1] mpdclient: connect to MPD asynchronously This way, the user can really cancel the connection attempt at any time, and does not need to wait for a timeout. This duplicates some code from libmpdclient, but unfortunately libmpdclient doesn't expose a usable API for connecting asynchronously yet. --- Makefile.am | 10 ++ NEWS | 1 + configure.ac | 21 ++++ src/aconnect.c | 164 ++++++++++++++++++++++++++++++++ src/aconnect.h | 50 ++++++++++ src/mpdclient.c | 69 +++++++++++++- src/mpdclient.h | 17 +++- src/net/async_connect.c | 128 +++++++++++++++++++++++++ src/net/async_connect.h | 56 +++++++++++ src/net/async_rconnect.c | 145 ++++++++++++++++++++++++++++ src/net/async_rconnect.h | 52 ++++++++++ src/net/resolver.c | 200 +++++++++++++++++++++++++++++++++++++++ src/net/resolver.h | 52 ++++++++++ src/net/socket.c | 76 +++++++++++++++ src/net/socket.h | 75 +++++++++++++++ src/net/types.h | 44 +++++++++ 16 files changed, 1156 insertions(+), 4 deletions(-) create mode 100644 src/aconnect.c create mode 100644 src/aconnect.h create mode 100644 src/net/async_connect.c create mode 100644 src/net/async_connect.h create mode 100644 src/net/async_rconnect.c create mode 100644 src/net/async_rconnect.h create mode 100644 src/net/resolver.c create mode 100644 src/net/resolver.h create mode 100644 src/net/socket.c create mode 100644 src/net/socket.h create mode 100644 src/net/types.h diff --git a/Makefile.am b/Makefile.am index cc8431b..5f1abba 100644 --- a/Makefile.am +++ b/Makefile.am @@ -61,6 +61,16 @@ src_ncmpc_SOURCES = \ src/strfsong.c src/strfsong.h \ src/utils.c src/utils.h +if ENABLE_ASYNC_CONNECT +src_ncmpc_SOURCES += \ + src/net/types.h \ + src/net/socket.c src/net/socket.h \ + src/net/resolver.c src/net/resolver.h \ + src/net/async_connect.c src/net/async_connect.h \ + src/net/async_rconnect.c src/net/async_rconnect.h \ + src/aconnect.c src/aconnect.h +endif + # # Windows resource file # diff --git a/NEWS b/NEWS index 58f1b88..be34c12 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,7 @@ ncmpc 0.26 - not yet released * adapt to lirc 0.9.4 * lyricswiki: update regex * screen_song: show "f" and "dsd" formats +* connect asynchronously * fix gcc 7 warnings ncmpc 0.25 - (2016-08-18) diff --git a/configure.ac b/configure.ac index cbd3989..2a78958 100644 --- a/configure.ac +++ b/configure.ac @@ -149,6 +149,27 @@ else AM_PO_SUBDIRS fi +dnl Networking + +AC_ARG_ENABLE(tcp, + AS_HELP_STRING([--disable-tcp], + [Disable TCP support @<:@default=enabled@:>@]),, + [enable_tcp=$disable_mini]) +if test "x$enable_tcp" = xyes; then + AC_DEFINE([ENABLE_TCP], 1, [Define to enable TCP support]) + AC_SEARCH_LIBS([gethostbyname], [nsl]) + AC_CHECK_FUNCS([getaddrinfo]) +fi + +AC_ARG_ENABLE([async-connect], + AS_HELP_STRING([--enable-async-connect], + [Disable asynchronous connect @<:@default=yes@:>@]),, + [enable_async_connect=$disable_mini]) +AM_CONDITIONAL(ENABLE_ASYNC_CONNECT, test x$enable_async_connect = xyes) +if test "x$enable_async_connect" = xyes; then + AC_DEFINE([ENABLE_ASYNC_CONNECT], [1], [Enable asynchronous connect?]) +fi + dnl enable colors AC_ARG_ENABLE([colors], AS_HELP_STRING([--enable-colors], diff --git a/src/aconnect.c b/src/aconnect.c new file mode 100644 index 0000000..345bc52 --- /dev/null +++ b/src/aconnect.c @@ -0,0 +1,164 @@ +/* ncmpc (Ncurses MPD Client) + (c) 2004-2017 The Music Player Daemon Project + Project homepage: http://musicpd.org + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "aconnect.h" +#include "net/async_rconnect.h" +#include "net/socket.h" +#include "Compiler.h" + +#include +#include + +#include + +#include +#include +#include +#include +#include + +struct aconnect { + const struct aconnect_handler *handler; + void *handler_ctx; + + struct async_rconnect *rconnect; + + int fd; + guint source_id; +}; + +static gboolean +aconnect_source_callback(gcc_unused GIOChannel *source, + gcc_unused GIOCondition condition, + gpointer data) +{ + struct aconnect *ac = data; + assert(ac->source_id != 0); + ac->source_id = 0; + + char buffer[256]; + ssize_t nbytes = recv(ac->fd, buffer, sizeof(buffer) - 1, 0); + if (nbytes < 0) { + snprintf(buffer, sizeof(buffer), + "Failed to receive from MPD: %s", + strerror(errno)); + close_socket(ac->fd); + ac->handler->error(buffer, ac->handler_ctx); + g_free(ac); + return false; + } + + if (nbytes == 0) { + close_socket(ac->fd); + ac->handler->error("MPD closed the connection", + ac->handler_ctx); + g_free(ac); + return false; + } + + buffer[nbytes] = 0; + + struct mpd_async *async = mpd_async_new(ac->fd); + if (async == NULL) { + close_socket(ac->fd); + ac->handler->error("Out of memory", ac->handler_ctx); + g_free(ac); + return false; + } + + struct mpd_connection *c = mpd_connection_new_async(async, buffer); + if (c == NULL) { + mpd_async_free(async); + ac->handler->error("Out of memory", ac->handler_ctx); + g_free(ac); + return false; + } + + ac->handler->success(c, ac->handler_ctx); + g_free(ac); + return false; +} + +static void +aconnect_rconnect_success(int fd, void *ctx) +{ + struct aconnect *ac = ctx; + assert(ac->rconnect != NULL); + ac->rconnect = NULL; + + ac->fd = fd; + + GIOChannel *channel = g_io_channel_unix_new(fd); + ac->source_id = g_io_add_watch(channel, G_IO_IN, + aconnect_source_callback, ac); + g_io_channel_unref(channel); +} + +static void +aconnect_rconnect_error(const char *message, void *ctx) +{ + struct aconnect *ac = ctx; + assert(ac->rconnect != NULL); + ac->rconnect = NULL; + + ac->handler->error(message, ac->handler_ctx); + g_free(ac); +} + +static const struct async_rconnect_handler aconnect_rconnect_handler = { + .success = aconnect_rconnect_success, + .error = aconnect_rconnect_error, +}; + +void +aconnect_start(struct aconnect **acp, + const char *host, unsigned port, + const struct aconnect_handler *handler, void *ctx) +{ + struct aconnect *ac = g_new(struct aconnect, 1); + ac->handler = handler; + ac->handler_ctx = ctx; + + async_rconnect_start(&ac->rconnect, host, port, + &aconnect_rconnect_handler, ac); + + *acp = ac; +} + +void +aconnect_cancel(struct aconnect *ac) +{ + if (ac->rconnect != NULL) + async_rconnect_cancel(ac->rconnect); + else { + g_source_remove(ac->source_id); + close_socket(ac->fd); + } + + g_free(ac); +} diff --git a/src/aconnect.h b/src/aconnect.h new file mode 100644 index 0000000..7c62e32 --- /dev/null +++ b/src/aconnect.h @@ -0,0 +1,50 @@ +/* ncmpc (Ncurses MPD Client) + (c) 2004-2017 The Music Player Daemon Project + Project homepage: http://musicpd.org + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef ACONNECT_H +#define ACONNECT_H + +#include + +struct mpd_connection; +struct aconnect; + +struct aconnect_handler { + void (*success)(struct mpd_connection *c, void *ctx); + void (*error)(const char *message, void *ctx); +}; + +void +aconnect_start(struct aconnect **acp, + const char *host, unsigned port, + const struct aconnect_handler *handler, void *ctx); + +void +aconnect_cancel(struct aconnect *ac); + +#endif diff --git a/src/mpdclient.c b/src/mpdclient.c index a7d1ef8..5903f14 100644 --- a/src/mpdclient.c +++ b/src/mpdclient.c @@ -24,6 +24,10 @@ #include "gidle.h" #include "charset.h" +#ifdef ENABLE_ASYNC_CONNECT +#include "aconnect.h" +#endif + #include #include @@ -155,8 +159,16 @@ mpdclient_new(const gchar *host, unsigned port, { struct mpdclient *c = g_new0(struct mpdclient, 1); +#ifdef ENABLE_ASYNC_CONNECT + c->settings = mpd_settings_new(host, port, timeout_ms, + NULL, NULL); + if (c->settings == NULL) + g_error("Out of memory"); +#else c->host = host; c->port = port; +#endif + c->timeout_ms = timeout_ms; c->password = password; @@ -175,6 +187,10 @@ mpdclient_free(struct mpdclient *c) mpdclient_playlist_free(&c->playlist); +#ifdef ENABLE_ASYNC_CONNECT + mpd_settings_free(c->settings); +#endif + g_free(c); } @@ -194,6 +210,13 @@ mpdclient_status_free(struct mpdclient *c) void mpdclient_disconnect(struct mpdclient *c) { +#ifdef ENABLE_ASYNC_CONNECT + if (c->async_connect != NULL) { + aconnect_cancel(c->async_connect); + c->async_connect = NULL; + } +#endif + mpdclient_cancel_enter_idle(c); if (c->source != NULL) { @@ -232,6 +255,11 @@ mpdclient_connected(struct mpdclient *c, return false; } +#ifdef ENABLE_ASYNC_CONNECT + if (c->timeout_ms > 0) + mpd_connection_set_timeout(connection, c->timeout_ms); +#endif + /* send password */ if (c->password != NULL && !mpd_run_password(connection, c->password)) { @@ -251,19 +279,56 @@ mpdclient_connected(struct mpdclient *c, return true; } -bool +#ifdef ENABLE_ASYNC_CONNECT + +static void +mpdclient_connect_success(struct mpd_connection *connection, void *ctx) +{ + struct mpdclient *c = ctx; + assert(c->async_connect != NULL); + c->async_connect = NULL; + + mpdclient_connected(c, connection); +} + +static void +mpdclient_connect_error(const char *message, void *ctx) +{ + struct mpdclient *c = ctx; + assert(c->async_connect != NULL); + c->async_connect = NULL; + + mpdclient_error_callback(message); + mpdclient_failed_callback(); +} + +static const struct aconnect_handler mpdclient_connect_handler = { + .success = mpdclient_connect_success, + .error = mpdclient_connect_error, +}; + +#endif + +void mpdclient_connect(struct mpdclient *c) { /* close any open connection */ mpdclient_disconnect(c); +#ifdef ENABLE_ASYNC_CONNECT + aconnect_start(&c->async_connect, + mpd_settings_get_host(c->settings), + mpd_settings_get_port(c->settings), + &mpdclient_connect_handler, c); +#else /* connect to MPD */ struct mpd_connection *connection = mpd_connection_new(c->host, c->port, c->timeout_ms); if (connection == NULL) g_error("Out of memory"); - return mpdclient_connected(c, connection); + mpdclient_connected(c, connection); +#endif } bool diff --git a/src/mpdclient.h b/src/mpdclient.h index bac3338..b007097 100644 --- a/src/mpdclient.h +++ b/src/mpdclient.h @@ -1,6 +1,7 @@ #ifndef MPDCLIENT_H #define MPDCLIENT_H +#include "config.h" #include "playlist.h" #include "Compiler.h" @@ -9,8 +10,12 @@ struct filelist; struct mpdclient { +#ifdef ENABLE_ASYNC_CONNECT + struct mpd_settings *settings; +#else const char *host; unsigned port; +#endif unsigned timeout_ms; @@ -19,6 +24,10 @@ struct mpdclient { /* playlist */ struct mpdclient_playlist playlist; +#ifdef ENABLE_ASYNC_CONNECT + struct aconnect *async_connect; +#endif + struct mpd_connection *connection; /** @@ -112,7 +121,11 @@ gcc_pure static inline bool mpdclient_is_dead(const struct mpdclient *c) { - return c->connection == NULL; + return c->connection == NULL +#ifdef ENABLE_ASYNC_CONNECT + && c->async_connect == NULL +#endif + ; } gcc_pure @@ -133,7 +146,7 @@ mpdclient_get_current_song(const struct mpdclient *c) : NULL; } -bool +void mpdclient_connect(struct mpdclient *c); void diff --git a/src/net/async_connect.c b/src/net/async_connect.c new file mode 100644 index 0000000..c375ea3 --- /dev/null +++ b/src/net/async_connect.c @@ -0,0 +1,128 @@ +/* ncmpc (Ncurses MPD Client) + (c) 2004-2017 The Music Player Daemon Project + Project homepage: http://musicpd.org + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "async_connect.h" +#include "../Compiler.h" + +#include + +#include +#include +#include +#include + +struct async_connect { + const struct async_connect_handler *handler; + void *handler_ctx; + + socket_t fd; + + guint source_id; +}; + +static gboolean +async_connect_source_callback(gcc_unused GIOChannel *source, + gcc_unused GIOCondition condition, + gpointer data) +{ + struct async_connect *ac = data; + + const int fd = ac->fd; + const struct async_connect_handler *const handler = ac->handler; + void *const ctx = ac->handler_ctx; + g_free(ac); + + int s_err = 0; + socklen_t s_err_size = sizeof(s_err); + + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, + (char*)&s_err, &s_err_size) < 0) + s_err = -last_socket_error(); + + if (s_err == 0) { + handler->success(fd, ctx); + } else { + close_socket(fd); + char msg[256]; + snprintf(msg, sizeof(msg), "Failed to connect socket: %s", + strerror(-s_err)); + handler->error(msg, ctx); + } + + return false; +} + +void +async_connect_start(struct async_connect **acp, + const struct sockaddr *address, size_t address_size, + const struct async_connect_handler *handler, void *ctx) +{ + socket_t fd = create_socket(address->sa_family, SOCK_STREAM, 0); + if (fd == INVALID_SOCKET) { + char msg[256]; + snprintf(msg, sizeof(msg), "Failed to create socket: %s", + strerror(errno)); + handler->error(msg, ctx); + return; + } + + if (connect(fd, address, address_size) == 0) { + handler->success(fd, ctx); + return; + } + + const int e = last_socket_error(); + if (!would_block(e)) { + close_socket(fd); + char msg[256]; + snprintf(msg, sizeof(msg), "Failed to connect socket: %s", + strerror(e)); + handler->error(msg, ctx); + return; + } + + struct async_connect *ac = g_new(struct async_connect, 1); + ac->handler = handler; + ac->handler_ctx = ctx; + ac->fd = fd; + + GIOChannel *channel = g_io_channel_unix_new(fd); + ac->source_id = g_io_add_watch(channel, G_IO_OUT, + async_connect_source_callback, ac); + g_io_channel_unref(channel); + + *acp = ac; +} + +void +async_connect_cancel(struct async_connect *ac) +{ + g_source_remove(ac->source_id); + close_socket(ac->fd); + g_free(ac); +} diff --git a/src/net/async_connect.h b/src/net/async_connect.h new file mode 100644 index 0000000..f56868c --- /dev/null +++ b/src/net/async_connect.h @@ -0,0 +1,56 @@ +/* ncmpc (Ncurses MPD Client) + (c) 2004-2017 The Music Player Daemon Project + Project homepage: http://musicpd.org + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef NET_ASYNC_CONNECT_H +#define NET_ASYNC_CONNECT_H + +#include "socket.h" + +#include + +struct sockaddr; +struct async_connect; + +struct async_connect_handler { + void (*success)(socket_t fd, void *ctx); + void (*error)(const char *message, void *ctx); +}; + +/** + * Create a socket and connect it to the given address. + */ +void +async_connect_start(struct async_connect **acp, + const struct sockaddr *address, + size_t address_size, + const struct async_connect_handler *handler, void *ctx); + +void +async_connect_cancel(struct async_connect *ac); + +#endif diff --git a/src/net/async_rconnect.c b/src/net/async_rconnect.c new file mode 100644 index 0000000..8986daa --- /dev/null +++ b/src/net/async_rconnect.c @@ -0,0 +1,145 @@ +/* ncmpc (Ncurses MPD Client) + (c) 2004-2017 The Music Player Daemon Project + Project homepage: http://musicpd.org + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "async_rconnect.h" +#include "async_connect.h" +#include "resolver.h" +#include "../Compiler.h" + +#include + +#include +#include +#include + +struct async_rconnect { + const struct async_rconnect_handler *handler; + void *handler_ctx; + + const char *host; + struct resolver *resolver; + + struct async_connect *connect; + + char *last_error; +}; + +static void +async_rconnect_next(struct async_rconnect *rc); + +static void +async_rconnect_success(socket_t fd, void *ctx) +{ + struct async_rconnect *rc = ctx; + + rc->handler->success(fd, rc->handler_ctx); + g_free(rc->last_error); + resolver_free(rc->resolver); + g_free(rc); +} + +static void +async_rconnect_error(const char *message, void *ctx) +{ + struct async_rconnect *rc = ctx; + + g_free(rc->last_error); + rc->last_error = g_strdup(message); + + async_rconnect_next(rc); +} + +static const struct async_connect_handler async_rconnect_connect_handler = { + .success = async_rconnect_success, + .error = async_rconnect_error, +}; + +static void +async_rconnect_next(struct async_rconnect *rc) +{ + const struct resolver_address *a = resolver_next(rc->resolver); + if (a == NULL) { + char msg[256]; + + if (rc->last_error == 0) { + snprintf(msg, sizeof(msg), + "Host '%s' has no address", + rc->host); + } else { + snprintf(msg, sizeof(msg), + "Failed to connect to host '%s': %s", + rc->host, rc->last_error); + g_free(rc->last_error); + } + + rc->handler->error(msg, rc->handler_ctx); + resolver_free(rc->resolver); + g_free(rc); + return; + } + + async_connect_start(&rc->connect, a->addr, a->addrlen, + &async_rconnect_connect_handler, rc); +} + +void +async_rconnect_start(struct async_rconnect **rcp, + const char *host, unsigned port, + const struct async_rconnect_handler *handler, void *ctx) +{ + struct resolver *r = resolver_new(host, port); + if (host == NULL) + host = "[default]"; + + if (r == NULL) { + char msg[256]; + snprintf(msg, sizeof(msg), "Failed to resolve host '%s'", + host); + handler->error(msg, ctx); + return; + } + + struct async_rconnect *rc = g_new(struct async_rconnect, 1); + rc->handler = handler; + rc->handler_ctx = ctx; + rc->host = host; + rc->resolver = r; + rc->last_error = NULL; + *rcp = rc; + + async_rconnect_next(rc); +} + +void +async_rconnect_cancel(struct async_rconnect *rc) +{ + g_free(rc->last_error); + async_connect_cancel(rc->connect); + resolver_free(rc->resolver); + g_free(rc); +} diff --git a/src/net/async_rconnect.h b/src/net/async_rconnect.h new file mode 100644 index 0000000..47db3a9 --- /dev/null +++ b/src/net/async_rconnect.h @@ -0,0 +1,52 @@ +/* ncmpc (Ncurses MPD Client) + (c) 2004-2017 The Music Player Daemon Project + Project homepage: http://musicpd.org + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef NET_ASYNC_RCONNECT_H +#define NET_ASYNC_RCONNECT_H + +#include + +struct async_rconnect; + +struct async_rconnect_handler { + void (*success)(int fd, void *ctx); + void (*error)(const char *message, void *ctx); +}; + +/** + * Resolve a host name and connect to it asynchronously. + */ +void +async_rconnect_start(struct async_rconnect **rcp, + const char *host, unsigned port, + const struct async_rconnect_handler *handler, void *ctx); + +void +async_rconnect_cancel(struct async_rconnect *rc); + +#endif diff --git a/src/net/resolver.c b/src/net/resolver.c new file mode 100644 index 0000000..952c2c9 --- /dev/null +++ b/src/net/resolver.c @@ -0,0 +1,200 @@ +/* libmpdclient + (c) 2003-2017 The Music Player Daemon Project + This project's homepage is: http://www.musicpd.org + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "resolver.h" +#include "config.h" + +#include +#include +#include + +#ifdef WIN32 +# include +# include +#else +# include +# include +#ifdef ENABLE_TCP +# include +# include +# include +#endif +#endif + +struct resolver { + enum { + TYPE_ZERO, TYPE_ONE, TYPE_ANY + } type; + +#ifdef ENABLE_TCP +#ifdef HAVE_GETADDRINFO + struct addrinfo *ai; + const struct addrinfo *next; +#else + struct sockaddr_in sin; +#endif +#endif + + struct resolver_address current; + +#ifndef WIN32 + struct sockaddr_un saun; +#endif +}; + +struct resolver * +resolver_new(const char *host, unsigned port) +{ + struct resolver *resolver; + + resolver = malloc(sizeof(*resolver)); + if (resolver == NULL) + return NULL; + + if (host[0] == '/' || host[0] == '@') { +#ifndef WIN32 + size_t path_length = strlen(host); + if (path_length >= sizeof(resolver->saun.sun_path)) { + free(resolver); + return NULL; + } + + resolver->saun.sun_family = AF_UNIX; + memcpy(resolver->saun.sun_path, host, path_length + 1); + + if (host[0] == '@') + /* abstract socket */ + resolver->saun.sun_path[0] = 0; + + resolver->current.family = PF_UNIX; + resolver->current.protocol = 0; + resolver->current.addrlen = sizeof(resolver->saun); + resolver->current.addr = (const struct sockaddr *)&resolver->saun; + resolver->type = TYPE_ONE; +#else /* WIN32 */ + /* there are no UNIX domain sockets on Windows */ + free(resolver); + return NULL; +#endif /* WIN32 */ + } else { +#ifdef ENABLE_TCP +#ifdef HAVE_GETADDRINFO + struct addrinfo hints; + char service[20]; + int ret; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + snprintf(service, sizeof(service), "%d", port); + + ret = getaddrinfo(host, service, &hints, &resolver->ai); + if (ret != 0) { + free(resolver); + return NULL; + } + + resolver->next = resolver->ai; + resolver->type = TYPE_ANY; +#else + const struct hostent *he; + + he = gethostbyname(host); + if (he == NULL) { + free(resolver); + return NULL; + } + + if (he->h_addrtype != AF_INET) { + free(resolver); + return NULL; + } + + + memset(&resolver->sin, 0, sizeof(resolver->sin)); + resolver->sin.sin_family = AF_INET; + resolver->sin.sin_port = htons(port); + memcpy((char *)&resolver->sin.sin_addr.s_addr, + (char *)he->h_addr, he->h_length); + + resolver->current.family = PF_INET; + resolver->current.protocol = 0; + resolver->current.addrlen = sizeof(resolver->sin); + resolver->current.addr = (const struct sockaddr *)&resolver->sin; + + resolver->type = TYPE_ONE; +#endif +#else /* !ENABLE_TCP */ + (void)port; + free(resolver); + return NULL; +#endif + } + + return resolver; +} + +void +resolver_free(struct resolver *resolver) +{ +#if defined(ENABLE_TCP) && defined(HAVE_GETADDRINFO) + if (resolver->type == TYPE_ANY) + freeaddrinfo(resolver->ai); +#endif + free(resolver); +} + +const struct resolver_address * +resolver_next(struct resolver *resolver) +{ + if (resolver->type == TYPE_ZERO) + return NULL; + + if (resolver->type == TYPE_ONE) { + resolver->type = TYPE_ZERO; + return &resolver->current; + } + +#if defined(ENABLE_TCP) && defined(HAVE_GETADDRINFO) + if (resolver->next == NULL) + return NULL; + + resolver->current.family = resolver->next->ai_family; + resolver->current.protocol = resolver->next->ai_protocol; + resolver->current.addrlen = resolver->next->ai_addrlen; + resolver->current.addr = resolver->next->ai_addr; + + resolver->next = resolver->next->ai_next; + + return &resolver->current; +#else + return NULL; +#endif +} diff --git a/src/net/resolver.h b/src/net/resolver.h new file mode 100644 index 0000000..1e3ec78 --- /dev/null +++ b/src/net/resolver.h @@ -0,0 +1,52 @@ +/* libmpdclient + (c) 2003-2017 The Music Player Daemon Project + This project's homepage is: http://www.musicpd.org + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef LIBMPDCLIENT_RESOLVER_H +#define LIBMPDCLIENT_RESOLVER_H + +#include + +struct resolver; + +struct resolver_address { + int family; + int protocol; + size_t addrlen; + const struct sockaddr *addr; +}; + +struct resolver * +resolver_new(const char *host, unsigned port); + +void +resolver_free(struct resolver *resolver); + +const struct resolver_address * +resolver_next(struct resolver *resolver); + +#endif diff --git a/src/net/socket.c b/src/net/socket.c new file mode 100644 index 0000000..f47770b --- /dev/null +++ b/src/net/socket.c @@ -0,0 +1,76 @@ +/* ncmpc (Ncurses MPD Client) + (c) 2004-2017 The Music Player Daemon Project + Project homepage: http://musicpd.org + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "socket.h" + +#ifndef WIN32 +#include +#endif + +static void +socket_set_cloexec(socket_t fd) +{ +#ifndef WIN32 + fcntl(fd, F_SETFD, FD_CLOEXEC); +#else + (void)fd; +#endif +} + +static void +socket_set_nonblock(socket_t fd) +{ +#ifdef WIN32 + u_long val = 1; + ioctlsocket(fd, FIONBIO, &val); +#else + int flags = fcntl(fd, F_GETFL); + if (flags >= 0) + fcntl(fd, F_SETFL, flags | O_NONBLOCK); +#endif +} + +socket_t +create_socket(int domain, int type, int protocol) +{ + socket_t fd; + +#if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK) + fd = socket(domain, type | SOCK_CLOEXEC | SOCK_NONBLOCK, protocol); + if (fd != INVALID_SOCKET || errno != EINVAL) + return fd; +#endif + + fd = socket(domain, type, protocol); + if (fd != INVALID_SOCKET) { + socket_set_cloexec(fd); + socket_set_nonblock(fd); + } + + return fd; +} diff --git a/src/net/socket.h b/src/net/socket.h new file mode 100644 index 0000000..099fcaa --- /dev/null +++ b/src/net/socket.h @@ -0,0 +1,75 @@ +/* ncmpc (Ncurses MPD Client) + (c) 2004-2017 The Music Player Daemon Project + Project homepage: http://musicpd.org + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef NET_SOCKET_H +#define NET_SOCKET_H + +#include "types.h" + +#include + +#ifndef WIN32 +#include +#include +#include +#endif + +socket_t +create_socket(int domain, int type, int protocol); + +static inline void +close_socket(socket_t s) +{ +#ifdef WIN32 + closesocket(s); +#else + close(s); +#endif +} + +static inline int +last_socket_error(void) +{ +#ifdef WIN32 + return WSAGetLastError(); +#else + return errno; +#endif +} + +static inline bool +would_block(int e) +{ +#ifdef WIN32 + return e == WSAEINPROGRESS || e == WSAEWOULDBLOCK; +#else + return e == EINPROGRESS || e == EAGAIN; +#endif +} + +#endif diff --git a/src/net/types.h b/src/net/types.h new file mode 100644 index 0000000..bd22f9e --- /dev/null +++ b/src/net/types.h @@ -0,0 +1,44 @@ +/* ncmpc (Ncurses MPD Client) + (c) 2004-2017 The Music Player Daemon Project + Project homepage: http://musicpd.org + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef NET_TYPES_H +#define NET_TYPES_H + +#ifdef WIN32 + +#include +typedef SOCKET socket_t; + +#else + +typedef int socket_t; +#define INVALID_SOCKET -1 + +#endif + +#endif -- 2.30.2