Code

screen: don't pass mpdclient pointer to method paint()
[ncmpc.git] / src / libmpdclient.c
index 1b0851ff125c08b4d8d86dc9806b5af676a6345d..dfca0a347a0ae5b0de341f3ba214580427b056fe 100644 (file)
 */
 
 #include "libmpdclient.h"
+#include "resolver.h"
 #include "str_pool.h"
 
+#include <assert.h>
 #include <errno.h>
 #include <sys/types.h>
 #include <stdio.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
 
@@ -114,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",
@@ -332,59 +204,196 @@ 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)
+/**
+ * Wait for the socket to become readable.
+ */
+static int mpd_wait(mpd_Connection *connection)
 {
-       int error, flags;
-       size_t path_length;
-       struct sockaddr_un sun;
+       struct timeval tv;
+       fd_set fds;
+       int ret;
 
-       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;
+       assert(connection->sock >= 0);
+
+       while (1) {
+               tv = connection->timeout;
+               FD_ZERO(&fds);
+               FD_SET(connection->sock, &fds);
+
+               ret = select(connection->sock + 1, &fds, NULL, NULL, &tv);
+               if (ret > 0)
+                       return 0;
+
+               if (ret == 0 || !SELECT_ERRNO_IGNORE)
+                       return -1;
        }
+}
+
+/**
+ * Wait until the socket is connected and check its result.  Returns 1
+ * on success, 0 on timeout, -errno on error.
+ */
+static int mpd_wait_connected(mpd_Connection *connection)
+{
+       int ret;
+       int s_err = 0;
+       socklen_t s_err_size = sizeof(s_err);
+
+       ret = mpd_wait(connection);
+       if (ret < 0)
+               return 0;
+
+       ret = getsockopt(connection->sock, SOL_SOCKET, SO_ERROR,
+                        (char*)&s_err, &s_err_size);
+       if (ret < 0)
+               return -errno;
+
+       if (s_err != 0)
+               return -s_err;
+
+       return 1;
+}
 
-       sun.sun_family = AF_UNIX;
-       memcpy(sun.sun_path, host, path_length + 1);
+/**
+ * Attempt to read data from the socket into the input buffer.
+ * Returns 0 on success, -1 on error.
+ */
+static int mpd_recv(mpd_Connection *connection)
+{
+       int ret;
+       ssize_t nbytes;
+
+       assert(connection != NULL);
+       assert(connection->buflen <= sizeof(connection->buffer));
+       assert(connection->bufstart <= connection->buflen);
 
-       connection->sock = socket(AF_UNIX, SOCK_STREAM, 0);
        if (connection->sock < 0) {
-               strcpy(connection->errorStr, "problems creating socket");
-               connection->error = MPD_ERROR_SYSTEM;
+               strcpy(connection->errorStr, "not connected");
+               connection->error = MPD_ERROR_CONNCLOSED;
+               connection->doneProcessing = 1;
+               connection->doneListOk = 0;
                return -1;
        }
 
-       mpd_setConnectionTimeout(connection, timeout);
+       if (connection->buflen >= sizeof(connection->buffer)) {
+               /* delete consumed data from beginning of buffer */
+               connection->buflen -= connection->bufstart;
+               memmove(connection->buffer,
+                       connection->buffer + connection->bufstart,
+                       connection->buflen);
+               connection->bufstart = 0;
+       }
 
-       flags = fcntl(connection->sock, F_GETFL, 0);
-       fcntl(connection->sock, F_SETFL, flags | O_NONBLOCK);
+       if (connection->buflen >= sizeof(connection->buffer)) {
+               strcpy(connection->errorStr, "buffer overrun");
+               connection->error = MPD_ERROR_BUFFEROVERRUN;
+               connection->doneProcessing = 1;
+               connection->doneListOk = 0;
+               return -1;
+       }
+
+       while (1) {
+               ret = mpd_wait(connection);
+               if (ret < 0) {
+                       strcpy(connection->errorStr, "connection timeout");
+                       connection->error = MPD_ERROR_TIMEOUT;
+                       connection->doneProcessing = 1;
+                       connection->doneListOk = 0;
+                       return -1;
+               }
+
+               nbytes = read(connection->sock,
+                             connection->buffer + connection->buflen,
+                             sizeof(connection->buffer) - connection->buflen);
+               if (nbytes > 0) {
+                       connection->buflen += nbytes;
+                       return 0;
+               }
 
-       error = connect(connection->sock, (struct sockaddr*)&sun, sizeof(sun));
-       if (error < 0) {
-               /* try the next address family */
-               close(connection->sock);
-               connection->sock = 0;
+               if (nbytes == 0 || !SENDRECV_ERRNO_IGNORE) {
+                       strcpy(connection->errorStr, "connection closed");
+                       connection->error = MPD_ERROR_CONNCLOSED;
+                       connection->doneProcessing = 1;
+                       connection->doneListOk = 0;
+                       return -1;
+               }
+       }
+}
 
+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),
-                        "problems connecting to \"%s\": %s",
-                        host, strerror(errno));
-               connection->error = MPD_ERROR_CONNPORT;
+                        "host \"%s\" not found", host);
+               connection->error = MPD_ERROR_UNKHOST;
                return -1;
        }
 
-       return 0;
+       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;
 }
-#endif /* WIN32 */
 
 mpd_Connection * mpd_newConnection(const char * host, int port, float timeout) {
        int err;
        char * rt;
        mpd_Connection * connection = malloc(sizeof(mpd_Connection));
-       struct timeval tv;
-       fd_set fds;
 
        connection->buflen = 0;
        connection->bufstart = 0;
@@ -399,52 +408,16 @@ 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);
+       mpd_setConnectionTimeout(connection,timeout);
+
+       err = mpd_connect(connection, host, port);
        if (err < 0)
                return connection;
 
        while(!(rt = memchr(connection->buffer, '\n', connection->buflen))) {
-               tv.tv_sec = connection->timeout.tv_sec;
-               tv.tv_usec = connection->timeout.tv_usec;
-               FD_ZERO(&fds);
-               FD_SET(connection->sock,&fds);
-               if((err = select(connection->sock+1,&fds,NULL,NULL,&tv)) == 1) {
-                       ssize_t readed;
-                       readed = recv(connection->sock,
-                                     &(connection->buffer[connection->buflen]),
-                                     sizeof(connection->buffer) - connection->buflen, 0);
-                       if(readed<=0) {
-                               snprintf(connection->errorStr, sizeof(connection->errorStr),
-                                        "problems getting a response from"
-                                        " \"%s\" on port %i : %s",host,
-                                        port, strerror(errno));
-                               connection->error = MPD_ERROR_NORESPONSE;
-                               return connection;
-                       }
-                       connection->buflen+=readed;
-               }
-               else if(err<0) {
-                       if (SELECT_ERRNO_IGNORE)
-                               continue;
-                       snprintf(connection->errorStr,
-                                sizeof(connection->errorStr),
-                                "problems connecting to \"%s\" on port"
-                                " %i",host,port);
-                       connection->error = MPD_ERROR_CONNPORT;
-                       return connection;
-               }
-               else {
-                       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;
+               err = mpd_recv(connection);
+               if (err < 0)
                        return connection;
-               }
        }
 
        *rt = '\0';
@@ -478,6 +451,12 @@ static void mpd_executeCommand(mpd_Connection *connection,
        const char *commandPtr = command;
        int commandLen = strlen(command);
 
+       if (connection->sock < 0) {
+               strcpy(connection->errorStr, "not connected");
+               connection->error = MPD_ERROR_CONNCLOSED;
+               return;
+       }
+
        if (!connection->doneProcessing && !connection->commandList) {
                strcpy(connection->errorStr,
                       "not done processing current command");
@@ -530,11 +509,7 @@ static void mpd_getNextReturnElement(mpd_Connection * connection) {
        char * rt = NULL;
        char * name = NULL;
        char * value = NULL;
-       fd_set fds;
-       struct timeval tv;
        char * tok = NULL;
-       ssize_t readed;
-       char * bufferCheck = NULL;
        int err;
        int pos;
 
@@ -548,56 +523,11 @@ static void mpd_getNextReturnElement(mpd_Connection * connection) {
                return;
        }
 
-       bufferCheck = connection->buffer+connection->bufstart;
-       while (connection->bufstart >= connection->buflen ||
-              !(rt = memchr(bufferCheck, '\n',
-                            connection->buffer + connection->buflen -
-                            bufferCheck))) {
-               if (connection->buflen >= sizeof(connection->buffer)) {
-                       memmove(connection->buffer,
-                               connection->buffer + connection->bufstart,
-                               connection->buflen - connection->bufstart);
-                       connection->buflen -= connection->bufstart;
-                       connection->bufstart = 0;
-               }
-               if (connection->buflen >= sizeof(connection->buffer)) {
-                       strcpy(connection->errorStr,"buffer overrun");
-                       connection->error = MPD_ERROR_BUFFEROVERRUN;
-                       connection->doneProcessing = 1;
-                       connection->doneListOk = 0;
+       while (!(rt = memchr(connection->buffer + connection->bufstart, '\n',
+                            connection->buflen - connection->bufstart))) {
+               err = mpd_recv(connection);
+               if (err < 0)
                        return;
-               }
-               bufferCheck = connection->buffer+connection->buflen;
-               tv.tv_sec = connection->timeout.tv_sec;
-               tv.tv_usec = connection->timeout.tv_usec;
-               FD_ZERO(&fds);
-               FD_SET(connection->sock,&fds);
-               if((err = select(connection->sock+1,&fds,NULL,NULL,&tv) == 1)) {
-                       readed = recv(connection->sock,
-                                     connection->buffer+connection->buflen,
-                                     sizeof(connection->buffer) - connection->buflen,
-                                     MSG_DONTWAIT);
-                       if(readed<0 && SENDRECV_ERRNO_IGNORE) {
-                               continue;
-                       }
-                       if(readed<=0) {
-                               strcpy(connection->errorStr,"connection"
-                                      " closed");
-                               connection->error = MPD_ERROR_CONNCLOSED;
-                               connection->doneProcessing = 1;
-                               connection->doneListOk = 0;
-                               return;
-                       }
-                       connection->buflen+=readed;
-               }
-               else if(err<0 && SELECT_ERRNO_IGNORE) continue;
-               else {
-                       strcpy(connection->errorStr,"connection timeout");
-                       connection->error = MPD_ERROR_TIMEOUT;
-                       connection->doneProcessing = 1;
-                       connection->doneListOk = 0;
-                       return;
-               }
        }
 
        *rt = '\0';