Code

libmpdclient: added resolver library
authorMax Kellermann <max@duempel.org>
Thu, 18 Sep 2008 09:17:17 +0000 (11:17 +0200)
committerMax Kellermann <max@duempel.org>
Thu, 18 Sep 2008 09:17:17 +0000 (11:17 +0200)
The resolver library provides unified access to all resolvers
(getaddrinfo(), gethostbyname(), Unix domain sockets).  Like
getaddrinfo(), it can return more than one address for a host name.
This fixes bug 1517 (http://www.musicpd.org/mantis/view.php?id=1517).

src/Makefile.am
src/libmpdclient.c
src/resolver.c [new file with mode: 0644]
src/resolver.h [new file with mode: 0644]

index 31cabfda0be43b21acde8836d2248176f49837e8..b652ede98b89d723b5049bb276e48de3d96cacd9 100644 (file)
@@ -12,6 +12,7 @@ ncmpc_LDADD = \
 
 ncmpc_headers = \
   libmpdclient.h\
+       resolver.h \
        song.h \
   mpdclient.h\
        playlist.h \
@@ -40,6 +41,7 @@ lyr_src=${lyr_src_fixed}
 
 ncmpc_SOURCES = \
   libmpdclient.c\
+       resolver.c \
        song.c \
   main.c\
   mpdclient.c\
index b2c907b49e6d078a3504bf3234bf1036030aa4be..0f2b1b503d73b101c6a68cd23d6a2c2a53a43b4e 100644 (file)
@@ -31,6 +31,7 @@
 */
 
 #include "libmpdclient.h"
+#include "resolver.h"
 #include "str_pool.h"
 
 #include <assert.h>
 #  define MSG_DONTWAIT 0
 #endif
 
-#ifndef MPD_NO_GAI
-#  ifdef AI_ADDRCONFIG
-#    define MPD_HAVE_GAI
-#  endif
-#endif
-
 #define COMMAND_LIST    1
 #define COMMAND_LIST_OK 2
 
@@ -115,130 +110,6 @@ static int do_connect_fail(mpd_Connection *connection,
 }
 #endif /* !WIN32 */
 
-#ifdef MPD_HAVE_GAI
-static int mpd_connect(mpd_Connection * connection, const char * host, int port,
-                       float timeout)
-{
-       int error;
-       char service[20];
-       struct addrinfo hints;
-       struct addrinfo *res = NULL;
-       struct addrinfo *addrinfo = NULL;
-
-       /**
-        * Setup hints
-        */
-       hints.ai_flags     = AI_ADDRCONFIG;
-       hints.ai_family    = PF_UNSPEC;
-       hints.ai_socktype  = SOCK_STREAM;
-       hints.ai_protocol  = IPPROTO_TCP;
-       hints.ai_addrlen   = 0;
-       hints.ai_addr      = NULL;
-       hints.ai_canonname = NULL;
-       hints.ai_next      = NULL;
-
-       snprintf(service, sizeof(service), "%d", port);
-
-       error = getaddrinfo(host, service, &hints, &addrinfo);
-
-       if (error) {
-               snprintf(connection->errorStr, sizeof(connection->errorStr),
-                        "host \"%s\" not found: %s",host, gai_strerror(error));
-               connection->error = MPD_ERROR_UNKHOST;
-               return -1;
-       }
-
-       for (res = addrinfo; res; res = res->ai_next) {
-               /* create socket */
-               connection->sock = socket(res->ai_family, SOCK_STREAM, res->ai_protocol);
-               if (connection->sock < 0) {
-                       snprintf(connection->errorStr, sizeof(connection->errorStr),
-                                "problems creating socket: %s",
-                                strerror(errno));
-                       connection->error = MPD_ERROR_SYSTEM;
-                       freeaddrinfo(addrinfo);
-                       return -1;
-               }
-
-               mpd_setConnectionTimeout(connection,timeout);
-
-               /* connect stuff */
-               if (do_connect_fail(connection, res->ai_addr, res->ai_addrlen)) {
-                       /* try the next address family */
-                       closesocket(connection->sock);
-                       connection->sock = -1;
-                       continue;
-               }
-       }
-       freeaddrinfo(addrinfo);
-
-       if (connection->sock < 0) {
-               snprintf(connection->errorStr, sizeof(connection->errorStr),
-                        "problems connecting to \"%s\" on port"
-                        " %i: %s",host,port, strerror(errno));
-               connection->error = MPD_ERROR_CONNPORT;
-
-               return -1;
-       }
-
-       return 0;
-}
-#else /* !MPD_HAVE_GAI */
-static int mpd_connect(mpd_Connection * connection, const char * host, int port,
-                       float timeout)
-{
-       struct hostent * he;
-       struct sockaddr * dest;
-       int destlen;
-       struct sockaddr_in sin;
-
-       if(!(he=gethostbyname(host))) {
-               snprintf(connection->errorStr, sizeof(connection->errorStr),
-                        "host \"%s\" not found",host);
-               connection->error = MPD_ERROR_UNKHOST;
-               return -1;
-       }
-
-       memset(&sin,0,sizeof(struct sockaddr_in));
-       /*dest.sin_family = he->h_addrtype;*/
-       sin.sin_family = AF_INET;
-       sin.sin_port = htons(port);
-
-       switch(he->h_addrtype) {
-       case AF_INET:
-               memcpy((char *)&sin.sin_addr.s_addr,(char *)he->h_addr,
-                               he->h_length);
-               dest = (struct sockaddr *)&sin;
-               destlen = sizeof(struct sockaddr_in);
-               break;
-       default:
-               strcpy(connection->errorStr,"address type is not IPv4\n");
-               connection->error = MPD_ERROR_SYSTEM;
-               return -1;
-               break;
-       }
-
-       if((connection->sock = socket(dest->sa_family,SOCK_STREAM,0))<0) {
-               strcpy(connection->errorStr,"problems creating socket");
-               connection->error = MPD_ERROR_SYSTEM;
-               return -1;
-       }
-
-       mpd_setConnectionTimeout(connection,timeout);
-
-       /* connect stuff */
-       if (do_connect_fail(connection, dest, destlen)) {
-               snprintf(connection->errorStr, sizeof(connection->errorStr),
-                        "problems connecting to \"%s\" on port"
-                        " %i",host,port);
-               connection->error = MPD_ERROR_CONNPORT;
-               return -1;
-       }
-
-       return 0;
-}
-#endif /* !MPD_HAVE_GAI */
-
 const char *const mpdTagItemKeys[MPD_TAG_NUM_OF_ITEM_TYPES] =
 {
        "Artist",
@@ -333,53 +204,6 @@ static int mpd_parseWelcome(mpd_Connection * connection, const char * host, int
        return 0;
 }
 
-#ifndef WIN32
-static int mpd_connect_un(mpd_Connection * connection,
-                         const char * host, float timeout)
-{
-       int error, flags;
-       size_t path_length;
-       struct sockaddr_un sun;
-
-       path_length = strlen(host);
-       if (path_length >= sizeof(sun.sun_path)) {
-               strcpy(connection->errorStr, "unix socket path is too long");
-               connection->error = MPD_ERROR_UNKHOST;
-               return -1;
-       }
-
-       sun.sun_family = AF_UNIX;
-       memcpy(sun.sun_path, host, path_length + 1);
-
-       connection->sock = socket(AF_UNIX, SOCK_STREAM, 0);
-       if (connection->sock < 0) {
-               strcpy(connection->errorStr, "problems creating socket");
-               connection->error = MPD_ERROR_SYSTEM;
-               return -1;
-       }
-
-       mpd_setConnectionTimeout(connection, timeout);
-
-       flags = fcntl(connection->sock, F_GETFL, 0);
-       fcntl(connection->sock, F_SETFL, flags | O_NONBLOCK);
-
-       error = connect(connection->sock, (struct sockaddr*)&sun, sizeof(sun));
-       if (error < 0) {
-               /* try the next address family */
-               close(connection->sock);
-               connection->sock = 0;
-
-               snprintf(connection->errorStr, sizeof(connection->errorStr),
-                        "problems connecting to \"%s\": %s",
-                        host, strerror(errno));
-               connection->error = MPD_ERROR_CONNPORT;
-               return -1;
-       }
-
-       return 0;
-}
-#endif /* WIN32 */
-
 /**
  * Wait for the socket to become readable.
  */
@@ -486,6 +310,76 @@ static int mpd_recv(mpd_Connection *connection)
        }
 }
 
+static int
+mpd_connect(mpd_Connection *connection, const char * host, int port)
+{
+       struct resolver *resolver;
+       const struct resolver_address *address;
+       int ret;
+
+       resolver = resolver_new(host, port);
+       if (resolver == NULL) {
+               snprintf(connection->errorStr, sizeof(connection->errorStr),
+                        "host \"%s\" not found", host);
+               connection->error = MPD_ERROR_UNKHOST;
+               return -1;
+       }
+
+       while ((address = resolver_next(resolver)) != NULL) {
+               connection->sock = socket(address->family, SOCK_STREAM,
+                                         address->protocol);
+               if (connection->sock < 0) {
+                       snprintf(connection->errorStr,
+                                sizeof(connection->errorStr),
+                                "problems creating socket: %s",
+                                strerror(errno));
+                       connection->error = MPD_ERROR_SYSTEM;
+                       continue;
+               }
+
+               ret = do_connect_fail(connection,
+                                     address->addr, address->addrlen);
+               if (ret != 0) {
+                       snprintf(connection->errorStr,
+                                sizeof(connection->errorStr),
+                                "problems connecting to \"%s\" on port"
+                                " %i: %s", host, port, strerror(errno));
+                       connection->error = MPD_ERROR_CONNPORT;
+
+                       closesocket(connection->sock);
+                       connection->sock = -1;
+                       continue;
+               }
+
+               ret = mpd_wait_connected(connection);
+               if (ret > 0) {
+                       resolver_free(resolver);
+                       mpd_clearError(connection);
+                       return 0;
+               }
+
+               if (ret == 0) {
+                       snprintf(connection->errorStr,
+                                sizeof(connection->errorStr),
+                                "timeout in attempting to get a response from"
+                                " \"%s\" on port %i", host, port);
+                       connection->error = MPD_ERROR_NORESPONSE;
+               } else if (ret < 0) {
+                       snprintf(connection->errorStr,
+                                sizeof(connection->errorStr),
+                                "problems connecting to \"%s\" on port %i: %s",
+                                host, port, strerror(-ret));
+                       connection->error = MPD_ERROR_CONNPORT;
+               }
+
+               closesocket(connection->sock);
+               connection->sock = -1;
+       }
+
+       resolver_free(resolver);
+       return -1;
+}
+
 mpd_Connection * mpd_newConnection(const char * host, int port, float timeout) {
        int err;
        char * rt;
@@ -504,30 +398,11 @@ mpd_Connection * mpd_newConnection(const char * host, int port, float timeout) {
        if (winsock_dll_error(connection))
                return connection;
 
-#ifndef WIN32
-       if (host[0] == '/')
-               err = mpd_connect_un(connection, host, timeout);
-       else
-#endif
-               err = mpd_connect(connection, host, port, timeout);
-       if (err < 0)
-               return connection;
+       mpd_setConnectionTimeout(connection,timeout);
 
-       err = mpd_wait_connected(connection);
-       if (err == 0) {
-               snprintf(connection->errorStr, sizeof(connection->errorStr),
-                        "timeout in attempting to get a response from"
-                        " \"%s\" on port %i",host,port);
-               connection->error = MPD_ERROR_NORESPONSE;
-               return connection;
-       } else if (err < 0) {
-               snprintf(connection->errorStr,
-                        sizeof(connection->errorStr),
-                        "problems connecting to \"%s\" on port %i: %s",
-                        host, port, strerror(-err));
-               connection->error = MPD_ERROR_CONNPORT;
+       err = mpd_connect(connection, host, port);
+       if (err < 0)
                return connection;
-       }
 
        while(!(rt = memchr(connection->buffer, '\n', connection->buflen))) {
                err = mpd_recv(connection);
diff --git a/src/resolver.c b/src/resolver.c
new file mode 100644 (file)
index 0000000..e5df05c
--- /dev/null
@@ -0,0 +1,195 @@
+/* libmpdclient
+   (c) 2008 Max Kellermann <max@duempel.org>
+   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.
+
+   - Neither the name of the Music Player Daemon nor the names of its
+   contributors may be used to endorse or promote products derived from
+   this software without specific prior written permission.
+
+   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 <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef WIN32
+#  include <ws2tcpip.h>
+#  include <winsock.h>
+#else
+#  include <netinet/in.h>
+#  include <arpa/inet.h>
+#  include <sys/socket.h>
+#  include <sys/un.h>
+#  include <netdb.h>
+#endif
+
+#ifndef MPD_NO_GAI
+#  ifdef AI_ADDRCONFIG
+#    define MPD_HAVE_GAI
+#  endif
+#endif
+
+struct resolver {
+       enum {
+               TYPE_ZERO, TYPE_ONE, TYPE_ANY
+       } type;
+
+#ifdef MPD_HAVE_GAI
+       struct addrinfo *ai;
+       const struct addrinfo *next;
+#else
+       struct sockaddr_in sin;
+#endif
+
+       struct resolver_address current;
+
+#ifndef WIN32
+       struct sockaddr_un sun;
+#endif
+};
+
+struct resolver *
+resolver_new(const char *host, int port)
+{
+       struct resolver *resolver;
+
+       resolver = malloc(sizeof(*resolver));
+       if (resolver == NULL)
+               return NULL;
+
+#ifndef WIN32
+       if (host[0] == '/') {
+               size_t path_length = strlen(host);
+               if (path_length >= sizeof(resolver->sun.sun_path)) {
+                       free(resolver);
+                       return NULL;
+               }
+
+               resolver->sun.sun_family = AF_UNIX;
+               memcpy(resolver->sun.sun_path, host, path_length + 1);
+
+               resolver->current.family = PF_UNIX;
+               resolver->current.protocol = 0;
+               resolver->current.addrlen = sizeof(resolver->sun);
+               resolver->current.addr = (const struct sockaddr *)&resolver->sun;
+               resolver->type = TYPE_ONE;
+       } else {
+#endif
+#ifdef MPD_HAVE_GAI
+               struct addrinfo hints;
+               char service[20];
+               int ret;
+
+               memset(&hints, 0, sizeof(hints));
+               hints.ai_flags = AI_ADDRCONFIG;
+               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
+#ifndef WIN32
+       }
+#endif
+
+       return resolver;
+}
+
+void
+resolver_free(struct resolver *resolver)
+{
+#ifdef MPD_HAVE_GAI
+       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;
+       }
+
+#ifdef MPD_HAVE_GAI
+       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/resolver.h b/src/resolver.h
new file mode 100644 (file)
index 0000000..6a79dc4
--- /dev/null
@@ -0,0 +1,56 @@
+/* libmpdclient
+   (c) 2008 Max Kellermann <max@duempel.org>
+   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.
+
+   - Neither the name of the Music Player Daemon nor the names of its
+   contributors may be used to endorse or promote products derived from
+   this software without specific prior written permission.
+
+   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 <stddef.h>
+
+struct resolver;
+
+struct resolver_address {
+       int family;
+       int protocol;
+       size_t addrlen;
+       const struct sockaddr *addr;
+};
+
+struct resolver *
+resolver_new(const char *host, int port);
+
+void
+resolver_free(struct resolver *resolver);
+
+const struct resolver_address *
+resolver_next(struct resolver *resolver);
+
+#endif