From 640a3d7bdf10385c6b9c477e2de594b3f2d478d5 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sat, 13 Dec 2008 00:50:43 +0100 Subject: [PATCH] socket: new socket library Move everything which directly accesses the socket or the input buffer to socket.c / socket.h. --- Makefile.am | 1 + include/mpd/connection.h | 2 - src/connection.c | 301 ++++----------------------------- src/internal.h | 9 +- src/socket.c | 348 +++++++++++++++++++++++++++++++++++++++ src/socket.h | 121 ++++++++++++++ 6 files changed, 505 insertions(+), 277 deletions(-) create mode 100644 src/socket.c create mode 100644 src/socket.h diff --git a/Makefile.am b/Makefile.am index 420ffcf..bec4439 100644 --- a/Makefile.am +++ b/Makefile.am @@ -33,6 +33,7 @@ src_libmpdclient_la_SOURCES = \ src/return_element.c \ src/entity.c \ src/idle.c \ + src/socket.c src/socket.h \ src/song.c \ src/status.c \ src/stats.c \ diff --git a/include/mpd/connection.h b/include/mpd/connection.h index 5ea5ba4..98d84f0 100644 --- a/include/mpd/connection.h +++ b/include/mpd/connection.h @@ -104,8 +104,6 @@ mpd_get_server_version(const struct mpd_connection *connection); void mpd_getNextReturnElement(struct mpd_connection *connection); -int mpd_recv(struct mpd_connection *connection); - void mpd_executeCommand(struct mpd_connection *connection, const char *command); diff --git a/src/connection.c b/src/connection.c index bdb0267..70fc8b3 100644 --- a/src/connection.c +++ b/src/connection.c @@ -86,148 +86,19 @@ static int winsock_dll_error(struct mpd_connection *connection) } return 0; } - -static int do_connect_fail(struct mpd_connection *connection, - const struct sockaddr *serv_addr, int addrlen) -{ - int iMode = 1; /* 0 = blocking, else non-blocking */ - if (connect(connection->sock, serv_addr, addrlen) == SOCKET_ERROR) - return 1; - ioctlsocket(connection->sock, FIONBIO, (u_long FAR*) &iMode); - return 0; -} -#else /* !WIN32 (sane operating systems) */ -static int do_connect_fail(struct mpd_connection *connection, - const struct sockaddr *serv_addr, int addrlen) -{ - int flags; - if (connect(connection->sock, serv_addr, addrlen) < 0) - return 1; - flags = fcntl(connection->sock, F_GETFL, 0); - fcntl(connection->sock, F_SETFL, flags | O_NONBLOCK); - return 0; -} #endif /* !WIN32 */ -/** - * Wait for the socket to become readable. - */ -static int mpd_wait(struct mpd_connection *connection) -{ - struct timeval tv; - fd_set fds; - int ret; - - 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(struct 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; -} - static int mpd_connect(struct mpd_connection *connection, const char * host, int port) { - struct resolver *resolver; - const struct resolver_address *address; - int ret; + bool ret; - resolver = resolver_new(host, port); - if (resolver == NULL) { - mpd_error_code(&connection->error, MPD_ERROR_UNKHOST); - mpd_error_printf(&connection->error, - "host \"%s\" not found", host); + ret = mpd_socket_connect(&connection->socket, host, port, + &connection->error); + if (!ret) return -1; - } - - while ((address = resolver_next(resolver)) != NULL) { - connection->sock = socket(address->family, SOCK_STREAM, - address->protocol); - if (connection->sock < 0) { - mpd_error_clear(&connection->error); - mpd_error_code(&connection->error, MPD_ERROR_SYSTEM); - mpd_error_printf(&connection->error, - "problems creating socket: %s", - strerror(errno)); - continue; - } - - ret = do_connect_fail(connection, - address->addr, address->addrlen); - if (ret != 0) { - mpd_error_clear(&connection->error); - mpd_error_code(&connection->error, MPD_ERROR_CONNPORT); - mpd_error_printf(&connection->error, - "problems connecting to \"%s\" on port %i: %s", - host, port, strerror(errno)); - - 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) { - mpd_error_clear(&connection->error); - mpd_error_code(&connection->error, - MPD_ERROR_NORESPONSE); - mpd_error_printf(&connection->error, - "timeout in attempting to get a response from \"%s\" on port %i", - host, port); - } else if (ret < 0) { - mpd_error_clear(&connection->error); - mpd_error_code(&connection->error, MPD_ERROR_CONNPORT); - mpd_error_printf(&connection->error, - "problems connecting to \"%s\" on port %i: %s", - host, port, strerror(-ret)); - } - - closesocket(connection->sock); - connection->sock = -1; - } - - resolver_free(resolver); - return -1; + return 0; } static int @@ -269,12 +140,14 @@ struct mpd_connection * mpd_newConnection(const char *host, int port, float timeout) { int err; - char * rt; + const char *line; struct mpd_connection *connection = malloc(sizeof(*connection)); + const struct timeval tv = { + .tv_sec = (long)timeout, + .tv_usec = ((long)(timeout * 1e6)) % 1000000, + }; - connection->sock = -1; - connection->buflen = 0; - connection->bufstart = 0; + mpd_socket_init(&connection->socket, &tv); mpd_error_init(&connection->error); connection->doneProcessing = 0; connection->commandList = 0; @@ -299,19 +172,13 @@ mpd_newConnection(const char *host, int port, float timeout) if (err < 0) return connection; - while (!(rt = memchr(connection->buffer, '\n', connection->buflen))) { - err = mpd_recv(connection); - if (err < 0) - return connection; - } + line = mpd_socket_recv_line(&connection->socket, &connection->error); + if (line == NULL) + return connection; - *rt = '\0'; - if (mpd_parseWelcome(connection, host, port, connection->buffer) == 0) + if (mpd_parseWelcome(connection, host, port, line) == 0) connection->doneProcessing = 1; - connection->buflen -= rt + 1 - connection->buffer; - memmove(connection->buffer, rt + 1, connection->buflen); - return connection; } @@ -349,7 +216,8 @@ void mpd_clearError(struct mpd_connection *connection) void mpd_closeConnection(struct mpd_connection *connection) { - closesocket(connection->sock); + mpd_socket_deinit(&connection->socket); + if (connection->returnElement) free(connection->returnElement); if (connection->request) free(connection->request); @@ -362,10 +230,12 @@ void mpd_closeConnection(struct mpd_connection *connection) void mpd_setConnectionTimeout(struct mpd_connection *connection, float timeout) { - connection->timeout.tv_sec = (int)timeout; - connection->timeout.tv_usec = (int)(timeout*1e6 - - connection->timeout.tv_sec*1000000 + - 0.5); + const struct timeval tv = { + .tv_sec = (long)timeout, + .tv_usec = ((long)(timeout * 1e6)) % 1000000, + }; + + mpd_socket_set_timeout(&connection->socket, &tv); } const int * @@ -374,85 +244,12 @@ mpd_get_server_version(const struct mpd_connection *connection) return connection->version; } -/** - * Attempt to read data from the socket into the input buffer. - * Returns 0 on success, -1 on error. - */ -int mpd_recv(struct mpd_connection *connection) -{ - int ret; - ssize_t nbytes; - - assert(connection != NULL); - assert(connection->buflen <= sizeof(connection->buffer)); - assert(connection->bufstart <= connection->buflen); - - if (connection->sock < 0) { - mpd_error_code(&connection->error, MPD_ERROR_CONNCLOSED); - mpd_error_message(&connection->error, "not connected"); - connection->doneProcessing = 1; - connection->doneListOk = 0; - return -1; - } - - 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; - } - - if (connection->buflen >= sizeof(connection->buffer)) { - mpd_error_code(&connection->error, MPD_ERROR_BUFFEROVERRUN); - mpd_error_message(&connection->error, "buffer overrun"); - connection->doneProcessing = 1; - connection->doneListOk = 0; - return -1; - } - - while (1) { - ret = mpd_wait(connection); - if (ret < 0) { - mpd_error_code(&connection->error, MPD_ERROR_TIMEOUT); - mpd_error_message(&connection->error, - "connection 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; - } - - if (nbytes == 0 || !SENDRECV_ERRNO_IGNORE) { - mpd_error_code(&connection->error, - MPD_ERROR_CONNCLOSED); - mpd_error_message(&connection->error, - "connection closed"); - connection->doneProcessing = 1; - connection->doneListOk = 0; - return -1; - } - } -} - void mpd_executeCommand(struct mpd_connection *connection, const char *command) { - int ret; - struct timeval tv; - fd_set fds; - const char *commandPtr = command; - int commandLen = strlen(command); + bool ret; - if (connection->sock < 0) { + if (!mpd_socket_defined(&connection->socket)) { mpd_error_code(&connection->error, MPD_ERROR_CONNCLOSED); mpd_error_message(&connection->error, "connection closed"); return; @@ -470,37 +267,10 @@ mpd_executeCommand(struct mpd_connection *connection, const char *command) mpd_clearError(connection); - FD_ZERO(&fds); - FD_SET(connection->sock,&fds); - tv.tv_sec = connection->timeout.tv_sec; - tv.tv_usec = connection->timeout.tv_usec; - - while ((ret = select(connection->sock+1,NULL,&fds,NULL,&tv)==1) || - (ret==-1 && SELECT_ERRNO_IGNORE)) { - ret = send(connection->sock,commandPtr,commandLen,MSG_DONTWAIT); - if (ret<=0) - { - if (SENDRECV_ERRNO_IGNORE) continue; - mpd_error_code(&connection->error, MPD_ERROR_SENDING); - mpd_error_printf(&connection->error, - "problems giving command \"%s\"", - command); - return; - } - else { - commandPtr+=ret; - commandLen-=ret; - } - - if (commandLen<=0) break; - } - - if (commandLen>0) { - mpd_error_code(&connection->error, MPD_ERROR_TIMEOUT); - mpd_error_printf(&connection->error, - "timeout sending command \"%s\"", command); + ret = mpd_socket_send(&connection->socket, command, strlen(command), + &connection->error); + if (!ret) return; - } if (!connection->commandList) connection->doneProcessing = 0; @@ -511,11 +281,9 @@ mpd_executeCommand(struct mpd_connection *connection, const char *command) void mpd_getNextReturnElement(struct mpd_connection *connection) { char * output = NULL; - char * rt = NULL; char * name = NULL; char * value = NULL; char * tok = NULL; - int err; int pos; if (connection->returnElement) mpd_freeReturnElement(connection->returnElement); @@ -529,16 +297,9 @@ void mpd_getNextReturnElement(struct mpd_connection *connection) return; } - while (!(rt = memchr(connection->buffer + connection->bufstart, '\n', - connection->buflen - connection->bufstart))) { - err = mpd_recv(connection); - if (err < 0) - return; - } - - *rt = '\0'; - output = connection->buffer+connection->bufstart; - connection->bufstart = rt - connection->buffer + 1; + output = mpd_socket_recv_line(&connection->socket, &connection->error); + if (output == NULL) + return; if (strcmp(output, "OK")==0) { if (connection->listOks > 0) { diff --git a/src/internal.h b/src/internal.h index d512858..71561ff 100644 --- a/src/internal.h +++ b/src/internal.h @@ -31,6 +31,7 @@ #include "ierror.h" #include +#include "socket.h" /* mpd_Connection * holds info about connection to mpd @@ -43,16 +44,14 @@ struct mpd_connection { struct mpd_error_info error; /* DON'T TOUCH any of the rest of this stuff */ - int sock; - char buffer[16384]; - size_t buflen; - size_t bufstart; + + struct mpd_socket socket; + int doneProcessing; int listOks; int doneListOk; int commandList; struct mpd_return_element *returnElement; - struct timeval timeout; char *request; int idle; void (*notify_cb)(struct mpd_connection *connection, diff --git a/src/socket.c b/src/socket.c new file mode 100644 index 0000000..17bab44 --- /dev/null +++ b/src/socket.c @@ -0,0 +1,348 @@ +/* libmpdclient + (c) 2003-2008 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 "socket.h" +#include "resolver.h" +#include "ierror.h" + +#include +#include +#include +#include +#include +#include + +#ifdef WIN32 +# include +# include +#else +# include +# include +# include +# include +# include +#endif + +#ifndef MSG_DONTWAIT +# define MSG_DONTWAIT 0 +#endif + +#ifdef WIN32 +# define SELECT_ERRNO_IGNORE (errno == WSAEINTR || errno == WSAEINPROGRESS) +# define SENDRECV_ERRNO_IGNORE SELECT_ERRNO_IGNORE +#else +# define SELECT_ERRNO_IGNORE (errno == EINTR) +# define SENDRECV_ERRNO_IGNORE (errno == EINTR || errno == EAGAIN) +# define winsock_dll_error(c) 0 +# define closesocket(s) close(s) +# define WSACleanup() do { /* nothing */ } while (0) +#endif + +void +mpd_socket_deinit(struct mpd_socket *s) +{ + if (s->fd >= 0) + closesocket(s->fd); +} + +#ifdef WIN32 + +static int do_connect_fail(struct mpd_socket *s, + const struct sockaddr *serv_addr, int addrlen) +{ + int iMode = 1; /* 0 = blocking, else non-blocking */ + if (connect(s->fd, serv_addr, addrlen) == SOCKET_ERROR) + return 1; + ioctlsocket(s->fd, FIONBIO, (u_long FAR*) &iMode); + return 0; +} + +#else /* !WIN32 (sane operating systems) */ + +static int do_connect_fail(struct mpd_socket *s, + const struct sockaddr *serv_addr, int addrlen) +{ + int flags; + if (connect(s->fd, serv_addr, addrlen) < 0) + return 1; + flags = fcntl(s->fd, F_GETFL, 0); + fcntl(s->fd, F_SETFL, flags | O_NONBLOCK); + return 0; +} + +#endif /* !WIN32 */ + +/** + * Wait for the socket to become readable. + */ +static int +mpd_socket_wait(struct mpd_socket *s) +{ + struct timeval tv; + fd_set fds; + int ret; + + assert(s->fd >= 0); + + while (1) { + tv = s->timeout; + FD_ZERO(&fds); + FD_SET(s->fd, &fds); + + ret = select(s->fd + 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_socket_wait_connected(struct mpd_socket *s) +{ + int ret; + int s_err = 0; + socklen_t s_err_size = sizeof(s_err); + + ret = mpd_socket_wait(s); + if (ret < 0) + return 0; + + ret = getsockopt(s->fd, SOL_SOCKET, SO_ERROR, + (char*)&s_err, &s_err_size); + if (ret < 0) + return -errno; + + if (s_err != 0) + return -s_err; + + return 1; +} + +bool +mpd_socket_connect(struct mpd_socket *s, const char *host, int port, + struct mpd_error_info *error) +{ + struct resolver *resolver; + const struct resolver_address *address; + int ret; + + resolver = resolver_new(host, port); + if (resolver == NULL) { + mpd_error_code(error, MPD_ERROR_UNKHOST); + mpd_error_printf(error, "host \"%s\" not found", host); + return false; + } + + while ((address = resolver_next(resolver)) != NULL) { + s->fd = socket(address->family, SOCK_STREAM, + address->protocol); + if (!mpd_socket_defined(s)) { + mpd_error_code(error, MPD_ERROR_SYSTEM); + mpd_error_printf(error, + "problems creating socket: %s", + strerror(errno)); + continue; + } + + ret = do_connect_fail(s, address->addr, address->addrlen); + if (ret != 0) { + mpd_error_code(error, MPD_ERROR_CONNPORT); + mpd_error_printf(error, + "problems connecting to \"%s\" on port %i: %s", + host, port, strerror(errno)); + + closesocket(s->fd); + s->fd = -1; + continue; + } + + ret = mpd_socket_wait_connected(s); + if (ret > 0) { + resolver_free(resolver); + mpd_error_clear(error); + return true; + } + + if (ret == 0) { + mpd_error_code(error, MPD_ERROR_NORESPONSE); + mpd_error_printf(error, + "timeout in attempting to get a response from \"%s\" on port %i", + host, port); + } else if (ret < 0) { + mpd_error_code(error, MPD_ERROR_CONNPORT); + mpd_error_printf(error, + "problems connecting to \"%s\" on port %i: %s", + host, port, strerror(-ret)); + } + + closesocket(s->fd); + s->fd = -1; + } + + resolver_free(resolver); + return false; +} + +static inline bool +mpd_socket_buffer_full(const struct mpd_socket *s) +{ + assert(s->buflen <= sizeof(s->buffer)); + + return s->buflen == sizeof(s->buffer); +} + +/** + * Attempt to read data from the socket into the input buffer. + * + * @return true if data was received, false on error or timeout + */ +static bool +mpd_socket_recv(struct mpd_socket *s, struct mpd_error_info *error) +{ + int ret; + ssize_t nbytes; + + assert(s != NULL); + assert(s->buflen <= sizeof(s->buffer)); + assert(s->bufstart <= s->buflen); + + if (!mpd_socket_defined(s)) { + mpd_error_code(error, MPD_ERROR_CONNCLOSED); + mpd_error_message(error, "not connected"); + return false; + } + + if (mpd_socket_buffer_full(s)) { + /* delete consumed data from beginning of buffer */ + s->buflen -= s->bufstart; + memmove(s->buffer, s->buffer + s->bufstart, s->buflen); + s->bufstart = 0; + } + + if (s->buflen >= sizeof(s->buffer)) { + mpd_error_code(error, MPD_ERROR_BUFFEROVERRUN); + mpd_error_message(error, "buffer overrun"); + return false; + } + + while (1) { + ret = mpd_socket_wait(s); + if (ret < 0) { + mpd_error_code(error, MPD_ERROR_TIMEOUT); + mpd_error_message(error, "connection timeout"); + return false; + } + + nbytes = read(s->fd, s->buffer + s->buflen, + sizeof(s->buffer) - s->buflen); + if (nbytes > 0) { + s->buflen += nbytes; + return true; + } + + if (nbytes == 0 || !SENDRECV_ERRNO_IGNORE) { + mpd_error_code(error, MPD_ERROR_CONNCLOSED); + mpd_error_message(error, "connection closed"); + return false; + } + } +} + +char * +mpd_socket_recv_line(struct mpd_socket *s, struct mpd_error_info *error) +{ + char *newline; + bool ret; + + assert(!mpd_error_is_defined(error)); + + while (true) { + char *start = s->buffer + s->bufstart; + + newline = memchr(start, '\n', s->buflen - s->bufstart); + if (newline != NULL) { + *newline++ = 0; + s->bufstart = newline - s->buffer; + return start; + } + + ret = mpd_socket_recv(s, error); + if (!ret) + return NULL; + } +} + +bool +mpd_socket_send(struct mpd_socket *s, const void *data0, size_t length, + struct mpd_error_info *error) +{ + const unsigned char *data = data0; + struct timeval tv = s->timeout; + ssize_t nbytes; + + while (length > 0) { + nbytes = send(s->fd, data, length, MSG_DONTWAIT); + if (nbytes > 0) { + data += nbytes; + length -= nbytes; + } else if (SENDRECV_ERRNO_IGNORE) { + fd_set fds; + int ret; + + FD_ZERO(&fds); + FD_SET(s->fd, &fds); + + ret = select(s->fd + 1, NULL, &fds, NULL, &tv); + if (ret == 0) { + mpd_error_code(error, MPD_ERROR_TIMEOUT); + mpd_error_message(error, + "timeout while sending to MPD"); + return false; + } else if (ret < 0 && !SELECT_ERRNO_IGNORE) { + mpd_error_code(error, MPD_ERROR_SYSTEM); + mpd_error_printf(error, "select() failed: %s", + strerror(errno)); + return false; + } + } else { + mpd_error_code(error, MPD_ERROR_SENDING); + mpd_error_printf(error, + "Sending to MPD failed: %s", + strerror(errno)); + return false; + } + } + + return true; +} diff --git a/src/socket.h b/src/socket.h new file mode 100644 index 0000000..19ecb03 --- /dev/null +++ b/src/socket.h @@ -0,0 +1,121 @@ +/* libmpdclient + (c) 2003-2008 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 MPD_SOCKET_H +#define MPD_SOCKET_H + +#include +#include +#include + +struct mpd_error_info; + +/** + * A socket connection between the client (that's us) and the MPD + * server. + */ +struct mpd_socket { + /** the socket file descriptor */ + int fd; + + char buffer[16384]; + size_t buflen; + size_t bufstart; + + struct timeval timeout; +}; + +/** + * Checks whether the socket object is defined, i.e. the OS level + * socket was created, but it may not be connected yet. + */ +static inline bool +mpd_socket_defined(const struct mpd_socket *s) +{ + return s->fd >= 0; +} + +/** + * Modifies the timeout for sending and receiving. + */ +static inline void +mpd_socket_set_timeout(struct mpd_socket *s, const struct timeval *timeout) +{ + s->timeout = *timeout; +} + +/** + * Initialize a socket object. This does not create the OS level + * socket, and does not attempt to connect it. + */ +static inline void +mpd_socket_init(struct mpd_socket *s, const struct timeval *timeout) +{ + s->fd = -1; + s->buflen = 0; + s->bufstart = 0; + mpd_socket_set_timeout(s, timeout); +} + +/** + * Free all resources of the socket object. + */ +void +mpd_socket_deinit(struct mpd_socket *s); + +/** + * Connects the socket to the specified host and port. + * + * @return false if an error occured + */ +bool +mpd_socket_connect(struct mpd_socket *s, const char *host, int port, + struct mpd_error_info *error); + +/** + * Attempt to read one line from the socket into the input buffer. + * This function returns a writable string pointer, because callers + * may find it useful to modify the buffer during parsing. + * + * @return a pointer to the beginning of the line; NULL on error or + * timeout + */ +char * +mpd_socket_recv_line(struct mpd_socket *s, struct mpd_error_info *error); + +/** + * Attempt to send data. + * + * @return true if everything was sent; false on error or timeout; + * partial write plus timeout is regarded as an error + */ +bool +mpd_socket_send(struct mpd_socket *s, const void *data, size_t length, + struct mpd_error_info *error); + +#endif