2004-02-24 00:41:20 +01:00
|
|
|
/* the Music Player Daemon (MPD)
|
2007-04-05 05:22:33 +02:00
|
|
|
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
|
2004-02-24 00:41:20 +01:00
|
|
|
* This project's homepage is: http://www.musicpd.org
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
*/
|
|
|
|
|
2008-08-28 20:02:43 +02:00
|
|
|
#include "client.h"
|
2004-02-24 00:41:20 +01:00
|
|
|
#include "command.h"
|
|
|
|
#include "conf.h"
|
|
|
|
#include "listen.h"
|
|
|
|
#include "permission.h"
|
2006-07-30 05:57:29 +02:00
|
|
|
#include "utils.h"
|
2008-04-12 11:46:11 +02:00
|
|
|
#include "main_notify.h"
|
2008-08-28 20:03:48 +02:00
|
|
|
#include "dlist.h"
|
2008-10-14 22:38:14 +02:00
|
|
|
#include "idle.h"
|
2004-02-24 00:41:20 +01:00
|
|
|
|
2008-04-12 06:19:26 +02:00
|
|
|
#include "../config.h"
|
|
|
|
|
2008-10-31 09:17:56 +01:00
|
|
|
#include <glib.h>
|
2008-10-08 10:49:29 +02:00
|
|
|
#include <assert.h>
|
2008-12-29 17:42:43 +01:00
|
|
|
#include <unistd.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#ifdef WIN32
|
|
|
|
#include <ws2tcpip.h>
|
|
|
|
#include <winsock.h>
|
|
|
|
#else
|
2008-10-08 10:49:29 +02:00
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <netinet/in.h>
|
|
|
|
#include <arpa/inet.h>
|
2008-12-29 17:42:43 +01:00
|
|
|
#endif
|
2008-10-08 10:49:29 +02:00
|
|
|
|
2008-12-29 17:28:56 +01:00
|
|
|
#undef G_LOG_DOMAIN
|
|
|
|
#define G_LOG_DOMAIN "client"
|
|
|
|
#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO
|
|
|
|
|
2006-08-10 23:15:06 +02:00
|
|
|
#define GREETING "OK MPD " PROTOCOL_VERSION "\n"
|
2004-02-24 00:41:20 +01:00
|
|
|
|
2008-08-28 20:02:59 +02:00
|
|
|
#define CLIENT_LIST_MODE_BEGIN "command_list_begin"
|
|
|
|
#define CLIENT_LIST_OK_MODE_BEGIN "command_list_ok_begin"
|
|
|
|
#define CLIENT_LIST_MODE_END "command_list_end"
|
|
|
|
#define CLIENT_TIMEOUT_DEFAULT (60)
|
|
|
|
#define CLIENT_MAX_CONNECTIONS_DEFAULT (10)
|
|
|
|
#define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024)
|
|
|
|
#define CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024)
|
|
|
|
|
|
|
|
/* set this to zero to indicate we have no possible clients */
|
|
|
|
static unsigned int client_max_connections; /*CLIENT_MAX_CONNECTIONS_DEFAULT; */
|
|
|
|
static int client_timeout = CLIENT_TIMEOUT_DEFAULT;
|
|
|
|
static size_t client_max_command_list_size =
|
|
|
|
CLIENT_MAX_COMMAND_LIST_DEFAULT;
|
|
|
|
static size_t client_max_output_buffer_size =
|
|
|
|
CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT;
|
2004-02-24 00:41:20 +01:00
|
|
|
|
2008-10-31 09:18:11 +01:00
|
|
|
struct deferred_buffer {
|
|
|
|
size_t size;
|
|
|
|
char data[sizeof(long)];
|
|
|
|
};
|
|
|
|
|
2008-08-28 20:02:58 +02:00
|
|
|
struct client {
|
2008-08-28 20:03:48 +02:00
|
|
|
struct list_head siblings;
|
|
|
|
|
2008-10-17 23:52:40 +02:00
|
|
|
char buffer[4096];
|
2008-03-26 11:38:07 +01:00
|
|
|
size_t bufferLength;
|
|
|
|
size_t bufferPos;
|
2008-10-15 22:34:21 +02:00
|
|
|
|
2008-08-28 20:20:10 +02:00
|
|
|
int fd; /* file descriptor; -1 if expired */
|
2008-12-30 19:24:39 +01:00
|
|
|
GIOChannel *channel;
|
|
|
|
|
2008-10-17 23:53:28 +02:00
|
|
|
unsigned permission;
|
2008-10-15 22:34:21 +02:00
|
|
|
|
|
|
|
/** the uid of the client process, or -1 if unknown */
|
|
|
|
int uid;
|
|
|
|
|
2004-02-24 00:41:20 +01:00
|
|
|
time_t lastTime;
|
2008-10-31 09:17:56 +01:00
|
|
|
GSList *cmd_list; /* for when in list mode */
|
2006-07-30 05:43:38 +02:00
|
|
|
int cmd_list_OK; /* print OK after each command execution */
|
2008-03-26 11:38:07 +01:00
|
|
|
size_t cmd_list_size; /* mem cmd_list consumes */
|
2008-10-31 09:18:11 +01:00
|
|
|
GQueue *deferred_send; /* for output if client is slow */
|
2008-03-26 11:38:07 +01:00
|
|
|
size_t deferred_bytes; /* mem deferred_send consumes */
|
2008-08-28 20:02:59 +02:00
|
|
|
unsigned int num; /* client number */
|
2006-07-30 05:43:38 +02:00
|
|
|
|
2008-10-17 23:52:47 +02:00
|
|
|
char send_buf[4096];
|
2008-03-26 11:38:07 +01:00
|
|
|
size_t send_buf_used; /* bytes used this instance */
|
2008-10-14 22:38:14 +02:00
|
|
|
|
|
|
|
/** is this client waiting for an "idle" response? */
|
|
|
|
bool idle_waiting;
|
|
|
|
|
|
|
|
/** idle flags pending on this client, to be sent as soon as
|
|
|
|
the client enters "idle" */
|
|
|
|
unsigned idle_flags;
|
2008-11-22 13:26:21 +01:00
|
|
|
|
|
|
|
/** idle flags that the client wants to receive */
|
|
|
|
unsigned idle_subscriptions;
|
2008-08-28 20:02:58 +02:00
|
|
|
};
|
2004-02-24 00:41:20 +01:00
|
|
|
|
2008-08-28 20:03:48 +02:00
|
|
|
static LIST_HEAD(clients);
|
|
|
|
static unsigned num_clients;
|
2004-02-24 00:41:20 +01:00
|
|
|
|
2008-08-28 20:02:59 +02:00
|
|
|
static void client_write_deferred(struct client *client);
|
2004-02-24 00:41:20 +01:00
|
|
|
|
2008-08-28 20:02:59 +02:00
|
|
|
static void client_write_output(struct client *client);
|
2004-02-24 00:41:20 +01:00
|
|
|
|
2008-10-31 13:58:00 +01:00
|
|
|
bool client_is_expired(const struct client *client)
|
2008-08-28 20:20:10 +02:00
|
|
|
{
|
2008-08-28 20:20:10 +02:00
|
|
|
return client->fd < 0;
|
2008-08-28 20:20:10 +02:00
|
|
|
}
|
|
|
|
|
2008-10-15 22:34:21 +02:00
|
|
|
int client_get_uid(const struct client *client)
|
|
|
|
{
|
|
|
|
return client->uid;
|
|
|
|
}
|
|
|
|
|
2008-10-17 23:53:28 +02:00
|
|
|
unsigned client_get_permission(const struct client *client)
|
2008-09-07 19:16:34 +02:00
|
|
|
{
|
|
|
|
return client->permission;
|
|
|
|
}
|
|
|
|
|
2008-10-17 23:53:28 +02:00
|
|
|
void client_set_permission(struct client *client, unsigned permission)
|
2008-09-07 19:16:34 +02:00
|
|
|
{
|
|
|
|
client->permission = permission;
|
|
|
|
}
|
|
|
|
|
2008-08-28 20:20:10 +02:00
|
|
|
static inline void client_set_expired(struct client *client)
|
|
|
|
{
|
2008-12-30 19:24:39 +01:00
|
|
|
if (client->channel != NULL) {
|
|
|
|
g_io_channel_unref(client->channel);
|
|
|
|
client->channel = NULL;
|
|
|
|
}
|
|
|
|
|
2008-08-28 20:20:10 +02:00
|
|
|
if (client->fd >= 0) {
|
|
|
|
xclose(client->fd);
|
|
|
|
client->fd = -1;
|
|
|
|
}
|
2008-08-28 20:20:10 +02:00
|
|
|
}
|
|
|
|
|
2008-12-30 19:24:39 +01:00
|
|
|
static gboolean
|
|
|
|
client_in_event(GIOChannel *source, GIOCondition condition, gpointer data);
|
|
|
|
|
2008-08-28 20:02:59 +02:00
|
|
|
static void client_init(struct client *client, int fd)
|
2006-07-20 18:02:40 +02:00
|
|
|
{
|
2008-08-28 20:03:48 +02:00
|
|
|
static unsigned int next_client_num;
|
2008-08-28 20:02:59 +02:00
|
|
|
|
2008-08-28 20:23:22 +02:00
|
|
|
assert(fd >= 0);
|
|
|
|
|
2008-08-28 20:02:59 +02:00
|
|
|
client->cmd_list_size = 0;
|
|
|
|
client->cmd_list_OK = -1;
|
|
|
|
client->bufferLength = 0;
|
|
|
|
client->bufferPos = 0;
|
|
|
|
client->fd = fd;
|
2008-03-26 11:39:03 +01:00
|
|
|
set_nonblocking(fd);
|
2008-12-30 19:24:39 +01:00
|
|
|
|
|
|
|
client->channel = g_io_channel_unix_new(client->fd);
|
|
|
|
g_io_add_watch(client->channel, G_IO_IN, client_in_event, client);
|
|
|
|
|
2008-08-28 20:02:59 +02:00
|
|
|
client->lastTime = time(NULL);
|
|
|
|
client->cmd_list = NULL;
|
2008-10-31 09:18:11 +01:00
|
|
|
client->deferred_send = g_queue_new();
|
2008-08-28 20:02:59 +02:00
|
|
|
client->deferred_bytes = 0;
|
2008-08-28 20:03:48 +02:00
|
|
|
client->num = next_client_num++;
|
2008-08-28 20:02:59 +02:00
|
|
|
client->send_buf_used = 0;
|
2004-02-24 00:41:20 +01:00
|
|
|
|
2008-08-28 20:02:59 +02:00
|
|
|
client->permission = getDefaultPermissions();
|
2004-02-24 00:41:20 +01:00
|
|
|
|
2006-07-30 05:43:38 +02:00
|
|
|
xwrite(fd, GREETING, strlen(GREETING));
|
|
|
|
}
|
|
|
|
|
2008-10-31 09:17:56 +01:00
|
|
|
static void free_cmd_list(GSList *list)
|
2006-07-30 05:43:38 +02:00
|
|
|
{
|
2008-10-31 09:17:56 +01:00
|
|
|
for (GSList *tmp = list; tmp != NULL; tmp = g_slist_next(tmp))
|
|
|
|
g_free(tmp->data);
|
2006-07-30 05:43:38 +02:00
|
|
|
|
2008-10-31 09:17:56 +01:00
|
|
|
g_slist_free(list);
|
2004-02-24 00:41:20 +01:00
|
|
|
}
|
|
|
|
|
2008-10-31 09:17:56 +01:00
|
|
|
static void new_cmd_list_ptr(struct client *client, char *s)
|
2006-07-20 18:02:40 +02:00
|
|
|
{
|
2008-10-31 09:17:56 +01:00
|
|
|
client->cmd_list = g_slist_prepend(client->cmd_list, g_strdup(s));
|
2006-07-30 05:43:38 +02:00
|
|
|
}
|
2004-02-24 00:41:20 +01:00
|
|
|
|
2008-10-31 09:18:11 +01:00
|
|
|
static void
|
2008-11-24 14:48:32 +01:00
|
|
|
deferred_buffer_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
|
2006-07-30 05:43:38 +02:00
|
|
|
{
|
2008-10-31 09:18:11 +01:00
|
|
|
struct deferred_buffer *buffer = data;
|
|
|
|
g_free(buffer);
|
|
|
|
}
|
2008-08-28 20:03:48 +02:00
|
|
|
|
2008-10-31 09:18:11 +01:00
|
|
|
static void client_close(struct client *client)
|
|
|
|
{
|
2008-08-28 20:03:48 +02:00
|
|
|
assert(num_clients > 0);
|
|
|
|
assert(!list_empty(&clients));
|
|
|
|
list_del(&client->siblings);
|
|
|
|
--num_clients;
|
2004-02-24 00:41:20 +01:00
|
|
|
|
2008-09-06 15:31:55 +02:00
|
|
|
client_set_expired(client);
|
|
|
|
|
2008-08-28 20:02:59 +02:00
|
|
|
if (client->cmd_list) {
|
|
|
|
free_cmd_list(client->cmd_list);
|
|
|
|
client->cmd_list = NULL;
|
2006-07-30 05:43:38 +02:00
|
|
|
}
|
2004-02-24 00:41:20 +01:00
|
|
|
|
2008-10-31 09:18:11 +01:00
|
|
|
g_queue_foreach(client->deferred_send, deferred_buffer_free, NULL);
|
|
|
|
g_queue_free(client->deferred_send);
|
2004-02-24 00:41:20 +01:00
|
|
|
|
2008-12-29 17:28:56 +01:00
|
|
|
g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
|
|
|
|
"client %i: closed", client->num);
|
2008-08-28 20:03:48 +02:00
|
|
|
free(client);
|
2004-02-24 00:41:20 +01:00
|
|
|
}
|
|
|
|
|
2008-08-28 20:20:10 +02:00
|
|
|
static const char *
|
|
|
|
sockaddr_to_tmp_string(const struct sockaddr *addr)
|
2006-07-20 18:02:40 +02:00
|
|
|
{
|
2008-08-28 20:03:03 +02:00
|
|
|
const char *hostname;
|
|
|
|
|
|
|
|
switch (addr->sa_family) {
|
2008-04-12 06:07:24 +02:00
|
|
|
#ifdef HAVE_TCP
|
2008-08-28 20:03:03 +02:00
|
|
|
case AF_INET:
|
|
|
|
hostname = (const char *)inet_ntoa(((const struct sockaddr_in *)
|
|
|
|
addr)->sin_addr);
|
|
|
|
if (!hostname)
|
|
|
|
hostname = "error getting ipv4 address";
|
|
|
|
break;
|
2004-02-24 00:41:20 +01:00
|
|
|
#ifdef HAVE_IPV6
|
2008-08-28 20:03:03 +02:00
|
|
|
case AF_INET6:
|
|
|
|
{
|
|
|
|
static char host[INET6_ADDRSTRLEN + 1];
|
|
|
|
memset(host, 0, INET6_ADDRSTRLEN + 1);
|
|
|
|
if (inet_ntop(AF_INET6, (const void *)
|
|
|
|
&(((const struct sockaddr_in6 *)addr)->
|
|
|
|
sin6_addr), host,
|
|
|
|
INET6_ADDRSTRLEN)) {
|
|
|
|
hostname = (const char *)host;
|
|
|
|
} else {
|
|
|
|
hostname = "error getting ipv6 address";
|
2004-02-24 00:41:20 +01:00
|
|
|
}
|
2008-08-28 20:03:03 +02:00
|
|
|
}
|
|
|
|
break;
|
2004-02-24 00:41:20 +01:00
|
|
|
#endif
|
2008-04-12 06:07:24 +02:00
|
|
|
#endif /* HAVE_TCP */
|
|
|
|
#ifdef HAVE_UN
|
2008-08-28 20:03:03 +02:00
|
|
|
case AF_UNIX:
|
|
|
|
hostname = "local connection";
|
|
|
|
break;
|
2008-04-12 06:07:24 +02:00
|
|
|
#endif /* HAVE_UN */
|
2008-08-28 20:03:03 +02:00
|
|
|
default:
|
|
|
|
hostname = "unknown";
|
2004-02-24 00:41:20 +01:00
|
|
|
}
|
2008-08-28 20:03:48 +02:00
|
|
|
|
2008-08-28 20:20:10 +02:00
|
|
|
return hostname;
|
|
|
|
}
|
|
|
|
|
2008-10-15 22:34:21 +02:00
|
|
|
void client_new(int fd, const struct sockaddr *addr, int uid)
|
2008-08-28 20:20:10 +02:00
|
|
|
{
|
|
|
|
struct client *client;
|
|
|
|
|
|
|
|
if (num_clients >= client_max_connections) {
|
2008-12-29 17:28:56 +01:00
|
|
|
g_warning("Max Connections Reached!");
|
2008-08-28 20:20:10 +02:00
|
|
|
xclose(fd);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2008-08-28 20:03:48 +02:00
|
|
|
client = xcalloc(1, sizeof(*client));
|
|
|
|
list_add(&client->siblings, &clients);
|
|
|
|
++num_clients;
|
|
|
|
client_init(client, fd);
|
2008-10-15 22:34:21 +02:00
|
|
|
client->uid = uid;
|
2008-12-29 17:28:56 +01:00
|
|
|
g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
|
|
|
|
"client %i: opened from %s\n", client->num,
|
|
|
|
sockaddr_to_tmp_string(addr));
|
2004-02-24 00:41:20 +01:00
|
|
|
}
|
|
|
|
|
2008-10-15 18:21:45 +02:00
|
|
|
static int client_process_line(struct client *client, char *line)
|
2006-07-20 18:02:40 +02:00
|
|
|
{
|
2004-11-14 06:38:49 +01:00
|
|
|
int ret = 1;
|
2008-08-28 20:02:59 +02:00
|
|
|
|
2008-10-15 18:21:57 +02:00
|
|
|
if (strcmp(line, "noidle") == 0) {
|
|
|
|
if (client->idle_waiting) {
|
|
|
|
/* send empty idle response and leave idle mode */
|
|
|
|
client->idle_waiting = false;
|
|
|
|
command_success(client);
|
|
|
|
client_write_output(client);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* do nothing if the client wasn't idling: the client
|
|
|
|
has already received the full idle response from
|
|
|
|
client_idle_notify(), which he can now evaluate */
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
} else if (client->idle_waiting) {
|
|
|
|
/* during idle mode, clients must not send anything
|
|
|
|
except "noidle" */
|
2008-12-29 17:28:56 +01:00
|
|
|
g_warning("client %i: command \"%s\" during idle",
|
|
|
|
client->num, line);
|
2008-10-15 18:21:57 +02:00
|
|
|
return COMMAND_RETURN_CLOSE;
|
|
|
|
}
|
|
|
|
|
2008-08-28 20:02:59 +02:00
|
|
|
if (client->cmd_list_OK >= 0) {
|
|
|
|
if (strcmp(line, CLIENT_LIST_MODE_END) == 0) {
|
2008-12-29 17:28:56 +01:00
|
|
|
g_debug("client %i: process command list",
|
|
|
|
client->num);
|
2008-10-31 09:17:56 +01:00
|
|
|
|
|
|
|
/* for scalability reasons, we have prepended
|
|
|
|
each new command; now we have to reverse it
|
|
|
|
to restore the correct order */
|
|
|
|
client->cmd_list = g_slist_reverse(client->cmd_list);
|
|
|
|
|
2008-10-22 21:40:44 +02:00
|
|
|
ret = command_process_list(client,
|
|
|
|
client->cmd_list_OK,
|
|
|
|
client->cmd_list);
|
2008-12-29 17:28:56 +01:00
|
|
|
g_debug("client %i: process command "
|
|
|
|
"list returned %i", client->num, ret);
|
2008-08-28 20:20:10 +02:00
|
|
|
|
2008-08-29 09:36:40 +02:00
|
|
|
if (ret == COMMAND_RETURN_CLOSE ||
|
2008-09-10 11:42:26 +02:00
|
|
|
client_is_expired(client))
|
2008-08-29 09:36:40 +02:00
|
|
|
return COMMAND_RETURN_CLOSE;
|
|
|
|
|
2006-07-20 18:02:40 +02:00
|
|
|
if (ret == 0)
|
2008-09-07 13:51:59 +02:00
|
|
|
command_success(client);
|
2004-11-14 06:38:49 +01:00
|
|
|
|
2008-08-28 20:02:59 +02:00
|
|
|
client_write_output(client);
|
|
|
|
free_cmd_list(client->cmd_list);
|
|
|
|
client->cmd_list = NULL;
|
|
|
|
client->cmd_list_OK = -1;
|
2006-07-20 18:02:40 +02:00
|
|
|
} else {
|
2006-07-30 05:43:38 +02:00
|
|
|
size_t len = strlen(line) + 1;
|
2008-08-28 20:02:59 +02:00
|
|
|
client->cmd_list_size += len;
|
|
|
|
if (client->cmd_list_size >
|
|
|
|
client_max_command_list_size) {
|
2008-12-29 17:28:56 +01:00
|
|
|
g_warning("client %i: command list size (%lu) "
|
|
|
|
"is larger than the max (%lu)",
|
|
|
|
client->num,
|
|
|
|
(unsigned long)client->cmd_list_size,
|
|
|
|
(unsigned long)client_max_command_list_size);
|
2008-09-10 11:42:26 +02:00
|
|
|
return COMMAND_RETURN_CLOSE;
|
2006-07-30 05:43:38 +02:00
|
|
|
} else
|
2008-10-31 09:17:56 +01:00
|
|
|
new_cmd_list_ptr(client, line);
|
2004-02-24 00:41:20 +01:00
|
|
|
}
|
2006-07-20 18:02:40 +02:00
|
|
|
} else {
|
2008-08-28 20:02:59 +02:00
|
|
|
if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) {
|
|
|
|
client->cmd_list_OK = 0;
|
2004-11-14 06:38:49 +01:00
|
|
|
ret = 1;
|
2008-08-28 20:02:59 +02:00
|
|
|
} else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) {
|
|
|
|
client->cmd_list_OK = 1;
|
2004-11-14 06:38:49 +01:00
|
|
|
ret = 1;
|
2006-07-20 18:02:40 +02:00
|
|
|
} else {
|
2008-12-29 17:28:56 +01:00
|
|
|
g_debug("client %i: process command \"%s\"",
|
|
|
|
client->num, line);
|
2008-10-22 21:40:44 +02:00
|
|
|
ret = command_process(client, line);
|
2008-12-29 17:28:56 +01:00
|
|
|
g_debug("client %i: command returned %i",
|
|
|
|
client->num, ret);
|
2008-08-29 09:36:40 +02:00
|
|
|
|
|
|
|
if (ret == COMMAND_RETURN_CLOSE ||
|
2008-09-10 11:42:26 +02:00
|
|
|
client_is_expired(client))
|
2008-08-29 09:36:40 +02:00
|
|
|
return COMMAND_RETURN_CLOSE;
|
|
|
|
|
|
|
|
if (ret == 0)
|
2008-09-07 13:51:59 +02:00
|
|
|
command_success(client);
|
2008-08-29 09:36:40 +02:00
|
|
|
|
2008-08-28 20:02:59 +02:00
|
|
|
client_write_output(client);
|
2004-11-14 06:38:49 +01:00
|
|
|
}
|
2004-02-24 00:41:20 +01:00
|
|
|
}
|
|
|
|
|
2004-11-14 06:38:49 +01:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2008-10-17 23:26:40 +02:00
|
|
|
static int client_input_received(struct client *client, size_t bytesRead)
|
2006-07-20 18:02:40 +02:00
|
|
|
{
|
2008-10-17 23:23:54 +02:00
|
|
|
char *start = client->buffer + client->bufferPos, *end;
|
2008-10-15 18:21:45 +02:00
|
|
|
char *newline, *next;
|
2008-09-10 11:42:30 +02:00
|
|
|
int ret;
|
2004-11-14 06:38:49 +01:00
|
|
|
|
2008-10-17 23:29:41 +02:00
|
|
|
assert(client->bufferPos <= client->bufferLength);
|
|
|
|
assert(client->bufferLength + bytesRead <= sizeof(client->buffer));
|
|
|
|
|
2008-10-15 18:21:45 +02:00
|
|
|
client->bufferLength += bytesRead;
|
2008-10-17 23:23:54 +02:00
|
|
|
end = client->buffer + client->bufferLength;
|
2008-09-17 22:02:13 +02:00
|
|
|
|
2008-10-15 18:21:45 +02:00
|
|
|
/* process all lines */
|
|
|
|
while ((newline = memchr(start, '\n', end - start)) != NULL) {
|
|
|
|
next = newline + 1;
|
|
|
|
|
|
|
|
if (newline > start && newline[-1] == '\r')
|
|
|
|
--newline;
|
|
|
|
*newline = 0;
|
|
|
|
|
|
|
|
ret = client_process_line(client, start);
|
|
|
|
if (ret == COMMAND_RETURN_KILL ||
|
|
|
|
ret == COMMAND_RETURN_CLOSE)
|
|
|
|
return ret;
|
|
|
|
if (client_is_expired(client))
|
|
|
|
return COMMAND_RETURN_CLOSE;
|
|
|
|
|
|
|
|
start = next;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* mark consumed lines */
|
|
|
|
client->bufferPos = start - client->buffer;
|
|
|
|
|
|
|
|
/* if we're have reached the buffer's end, close the gab at
|
|
|
|
the beginning */
|
|
|
|
if (client->bufferLength == sizeof(client->buffer)) {
|
|
|
|
if (client->bufferPos == 0) {
|
2008-12-29 17:28:56 +01:00
|
|
|
g_warning("client %i: buffer overflow",
|
|
|
|
client->num);
|
2008-10-15 18:21:45 +02:00
|
|
|
return COMMAND_RETURN_CLOSE;
|
2004-11-14 06:38:49 +01:00
|
|
|
}
|
2008-10-15 18:21:45 +02:00
|
|
|
assert(client->bufferLength >= client->bufferPos
|
|
|
|
&& "bufferLength >= bufferPos");
|
|
|
|
client->bufferLength -= client->bufferPos;
|
|
|
|
memmove(client->buffer,
|
|
|
|
client->buffer + client->bufferPos,
|
|
|
|
client->bufferLength);
|
|
|
|
client->bufferPos = 0;
|
2004-11-14 06:38:49 +01:00
|
|
|
}
|
|
|
|
|
2008-09-10 11:42:30 +02:00
|
|
|
return 0;
|
2004-11-14 06:38:49 +01:00
|
|
|
}
|
|
|
|
|
2008-08-28 20:02:59 +02:00
|
|
|
static int client_read(struct client *client)
|
2006-07-20 18:02:40 +02:00
|
|
|
{
|
2008-10-17 23:26:40 +02:00
|
|
|
ssize_t bytesRead;
|
2004-11-16 02:41:05 +01:00
|
|
|
|
2008-10-17 23:29:41 +02:00
|
|
|
assert(client->bufferPos <= client->bufferLength);
|
|
|
|
assert(client->bufferLength < sizeof(client->buffer));
|
|
|
|
|
2008-08-28 20:02:59 +02:00
|
|
|
bytesRead = read(client->fd,
|
|
|
|
client->buffer + client->bufferLength,
|
2008-10-17 23:52:40 +02:00
|
|
|
sizeof(client->buffer) - client->bufferLength);
|
2004-11-16 02:41:05 +01:00
|
|
|
|
2006-07-20 18:02:40 +02:00
|
|
|
if (bytesRead > 0)
|
2008-08-28 20:02:59 +02:00
|
|
|
return client_input_received(client, bytesRead);
|
2008-09-10 11:43:09 +02:00
|
|
|
else if (bytesRead < 0 && errno == EINTR)
|
|
|
|
/* try again later, after select() */
|
2006-07-20 18:02:40 +02:00
|
|
|
return 0;
|
2008-09-10 11:43:09 +02:00
|
|
|
else
|
|
|
|
/* peer disconnected or I/O error */
|
|
|
|
return COMMAND_RETURN_CLOSE;
|
2004-02-24 00:41:20 +01:00
|
|
|
}
|
|
|
|
|
2008-12-30 19:24:39 +01:00
|
|
|
static gboolean
|
|
|
|
client_out_event(G_GNUC_UNUSED GIOChannel *source,
|
|
|
|
G_GNUC_UNUSED GIOCondition condition,
|
|
|
|
gpointer data);
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
client_in_event(G_GNUC_UNUSED GIOChannel *source,
|
|
|
|
G_GNUC_UNUSED GIOCondition condition,
|
|
|
|
gpointer data)
|
2006-07-20 18:02:40 +02:00
|
|
|
{
|
2008-12-30 19:24:39 +01:00
|
|
|
struct client *client = data;
|
|
|
|
int ret;
|
2004-02-24 00:41:20 +01:00
|
|
|
|
2008-12-30 19:24:39 +01:00
|
|
|
if (client_is_expired(client))
|
|
|
|
return false;
|
2004-02-24 00:41:20 +01:00
|
|
|
|
2008-12-30 19:24:39 +01:00
|
|
|
client->lastTime = time(NULL);
|
2004-02-24 00:41:20 +01:00
|
|
|
|
2008-12-30 19:24:39 +01:00
|
|
|
ret = client_read(client);
|
|
|
|
switch (ret) {
|
|
|
|
case COMMAND_RETURN_KILL:
|
|
|
|
client_close(client);
|
|
|
|
g_main_loop_quit(NULL);
|
|
|
|
return false;
|
2004-02-24 00:41:20 +01:00
|
|
|
|
2008-12-30 19:24:39 +01:00
|
|
|
case COMMAND_RETURN_CLOSE:
|
|
|
|
client_close(client);
|
|
|
|
return false;
|
|
|
|
}
|
2004-02-24 00:41:20 +01:00
|
|
|
|
2008-12-30 19:24:39 +01:00
|
|
|
if (client_is_expired(client)) {
|
|
|
|
client_close(client);
|
|
|
|
return false;
|
2004-02-24 00:41:20 +01:00
|
|
|
}
|
|
|
|
|
2008-12-30 19:24:39 +01:00
|
|
|
if (!g_queue_is_empty(client->deferred_send)) {
|
|
|
|
/* deferred buffers exist: schedule write */
|
|
|
|
g_io_add_watch(client->channel, G_IO_OUT,
|
|
|
|
client_out_event, client);
|
|
|
|
return false;
|
|
|
|
}
|
2004-02-24 00:41:20 +01:00
|
|
|
|
2008-12-30 19:24:39 +01:00
|
|
|
/* read more */
|
|
|
|
return true;
|
|
|
|
}
|
2004-02-24 00:41:20 +01:00
|
|
|
|
2008-12-30 19:24:39 +01:00
|
|
|
static gboolean
|
|
|
|
client_out_event(G_GNUC_UNUSED GIOChannel *source,
|
|
|
|
G_GNUC_UNUSED GIOCondition condition,
|
|
|
|
gpointer data)
|
|
|
|
{
|
|
|
|
struct client *client = data;
|
2005-03-12 23:38:49 +01:00
|
|
|
|
2008-12-30 19:24:39 +01:00
|
|
|
if (client_is_expired(client))
|
|
|
|
return false;
|
2007-01-11 21:41:17 +01:00
|
|
|
|
2008-12-30 19:24:39 +01:00
|
|
|
client_write_deferred(client);
|
2007-01-11 21:41:17 +01:00
|
|
|
|
2008-12-30 19:24:39 +01:00
|
|
|
if (client_is_expired(client)) {
|
|
|
|
client_close(client);
|
|
|
|
return false;
|
2008-08-28 20:20:10 +02:00
|
|
|
}
|
2005-03-12 23:38:49 +01:00
|
|
|
|
2008-12-30 19:24:39 +01:00
|
|
|
client->lastTime = time(NULL);
|
2008-09-10 11:42:26 +02:00
|
|
|
|
2008-12-30 19:24:39 +01:00
|
|
|
if (g_queue_is_empty(client->deferred_send)) {
|
|
|
|
/* done sending deferred buffers exist: schedule
|
|
|
|
read */
|
|
|
|
g_io_add_watch(client->channel, G_IO_IN,
|
|
|
|
client_in_event, client);
|
|
|
|
return false;
|
2004-02-24 00:41:20 +01:00
|
|
|
}
|
|
|
|
|
2008-12-30 19:24:39 +01:00
|
|
|
/* write more */
|
|
|
|
return true;
|
2004-02-24 00:41:20 +01:00
|
|
|
}
|
|
|
|
|
2008-08-28 20:03:02 +02:00
|
|
|
void client_manager_init(void)
|
2006-07-20 18:02:40 +02:00
|
|
|
{
|
|
|
|
char *test;
|
|
|
|
ConfigParam *param;
|
2004-02-24 00:41:20 +01:00
|
|
|
|
2004-10-28 07:14:55 +02:00
|
|
|
param = getConfigParam(CONF_CONN_TIMEOUT);
|
2004-02-24 00:41:20 +01:00
|
|
|
|
2006-07-20 18:02:40 +02:00
|
|
|
if (param) {
|
2008-08-28 20:02:59 +02:00
|
|
|
client_timeout = strtol(param->value, &test, 10);
|
|
|
|
if (*test != '\0' || client_timeout <= 0) {
|
2008-12-29 17:28:56 +01:00
|
|
|
g_error("connection timeout \"%s\" is not a positive "
|
|
|
|
"integer, line %i",
|
|
|
|
CONF_CONN_TIMEOUT, param->line);
|
2004-10-28 07:14:55 +02:00
|
|
|
}
|
2004-02-24 00:41:20 +01:00
|
|
|
}
|
|
|
|
|
2004-10-28 07:14:55 +02:00
|
|
|
param = getConfigParam(CONF_MAX_CONN);
|
|
|
|
|
2006-07-20 18:02:40 +02:00
|
|
|
if (param) {
|
2008-08-28 20:02:59 +02:00
|
|
|
client_max_connections = strtol(param->value, &test, 10);
|
|
|
|
if (*test != '\0' || client_max_connections <= 0) {
|
2008-12-29 17:28:56 +01:00
|
|
|
g_error("max connections \"%s\" is not a positive integer"
|
|
|
|
", line %i",
|
|
|
|
param->value, param->line);
|
2004-10-28 07:14:55 +02:00
|
|
|
}
|
2006-07-20 18:02:40 +02:00
|
|
|
} else
|
2008-08-28 20:02:59 +02:00
|
|
|
client_max_connections = CLIENT_MAX_CONNECTIONS_DEFAULT;
|
2004-02-24 00:41:20 +01:00
|
|
|
|
2004-10-28 07:14:55 +02:00
|
|
|
param = getConfigParam(CONF_MAX_COMMAND_LIST_SIZE);
|
|
|
|
|
2006-07-20 18:02:40 +02:00
|
|
|
if (param) {
|
2008-03-26 11:38:26 +01:00
|
|
|
long tmp = strtol(param->value, &test, 10);
|
|
|
|
if (*test != '\0' || tmp <= 0) {
|
2008-12-29 17:28:56 +01:00
|
|
|
g_error("max command list size \"%s\" is not a positive "
|
|
|
|
"integer, line %i",
|
|
|
|
param->value, param->line);
|
2004-10-28 07:14:55 +02:00
|
|
|
}
|
2008-08-28 20:02:59 +02:00
|
|
|
client_max_command_list_size = tmp * 1024;
|
2004-02-24 00:41:20 +01:00
|
|
|
}
|
|
|
|
|
2004-10-28 07:14:55 +02:00
|
|
|
param = getConfigParam(CONF_MAX_OUTPUT_BUFFER_SIZE);
|
|
|
|
|
2006-07-20 18:02:40 +02:00
|
|
|
if (param) {
|
2008-03-26 11:38:26 +01:00
|
|
|
long tmp = strtol(param->value, &test, 10);
|
|
|
|
if (*test != '\0' || tmp <= 0) {
|
2008-12-29 17:28:56 +01:00
|
|
|
g_error("max output buffer size \"%s\" is not a positive "
|
|
|
|
"integer, line %i",
|
|
|
|
param->value, param->line);
|
2004-10-28 07:14:55 +02:00
|
|
|
}
|
2008-08-28 20:02:59 +02:00
|
|
|
client_max_output_buffer_size = tmp * 1024;
|
2004-10-28 07:14:55 +02:00
|
|
|
}
|
2004-02-24 00:41:20 +01:00
|
|
|
}
|
|
|
|
|
2008-08-28 20:02:59 +02:00
|
|
|
static void client_close_all(void)
|
2006-07-20 18:02:40 +02:00
|
|
|
{
|
2008-08-28 20:03:48 +02:00
|
|
|
struct client *client, *n;
|
|
|
|
|
|
|
|
list_for_each_entry_safe(client, n, &clients, siblings)
|
|
|
|
client_close(client);
|
|
|
|
num_clients = 0;
|
2004-02-24 00:41:20 +01:00
|
|
|
}
|
|
|
|
|
2008-08-28 20:03:02 +02:00
|
|
|
void client_manager_deinit(void)
|
2006-07-20 18:02:40 +02:00
|
|
|
{
|
2008-08-28 20:02:59 +02:00
|
|
|
client_close_all();
|
2004-02-24 00:41:20 +01:00
|
|
|
|
2008-08-28 20:02:59 +02:00
|
|
|
client_max_connections = 0;
|
2004-02-24 00:41:20 +01:00
|
|
|
}
|
|
|
|
|
2008-08-28 20:03:02 +02:00
|
|
|
void client_manager_expire(void)
|
2006-07-20 18:02:40 +02:00
|
|
|
{
|
2008-08-28 20:03:48 +02:00
|
|
|
struct client *client, *n;
|
|
|
|
|
|
|
|
list_for_each_entry_safe(client, n, &clients, siblings) {
|
2008-08-28 20:20:10 +02:00
|
|
|
if (client_is_expired(client)) {
|
2008-12-29 17:28:56 +01:00
|
|
|
g_debug("client %i: expired", client->num);
|
2008-08-28 20:03:48 +02:00
|
|
|
client_close(client);
|
2008-10-14 22:38:14 +02:00
|
|
|
} else if (!client->idle_waiting && /* idle clients
|
|
|
|
never expire */
|
|
|
|
time(NULL) - client->lastTime >
|
2008-08-28 20:03:48 +02:00
|
|
|
client_timeout) {
|
2008-12-29 17:28:56 +01:00
|
|
|
g_debug("client %i: timeout", client->num);
|
2008-08-28 20:03:48 +02:00
|
|
|
client_close(client);
|
2004-02-24 00:41:20 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-08-28 20:02:59 +02:00
|
|
|
static void client_write_deferred(struct client *client)
|
2006-07-20 18:02:40 +02:00
|
|
|
{
|
2008-03-26 11:38:07 +01:00
|
|
|
ssize_t ret = 0;
|
2004-02-24 00:41:20 +01:00
|
|
|
|
2008-10-31 09:18:11 +01:00
|
|
|
while (!g_queue_is_empty(client->deferred_send)) {
|
|
|
|
struct deferred_buffer *buf =
|
|
|
|
g_queue_peek_head(client->deferred_send);
|
|
|
|
|
2008-08-28 20:23:22 +02:00
|
|
|
assert(buf->size > 0);
|
2008-10-31 09:18:11 +01:00
|
|
|
assert(buf->size <= client->deferred_bytes);
|
2008-08-28 20:23:22 +02:00
|
|
|
|
2008-08-28 20:02:59 +02:00
|
|
|
ret = write(client->fd, buf->data, buf->size);
|
2006-07-30 05:43:38 +02:00
|
|
|
if (ret < 0)
|
2006-07-20 18:02:40 +02:00
|
|
|
break;
|
2008-03-26 11:38:07 +01:00
|
|
|
else if ((size_t)ret < buf->size) {
|
2008-08-28 20:02:59 +02:00
|
|
|
assert(client->deferred_bytes >= (size_t)ret);
|
|
|
|
client->deferred_bytes -= ret;
|
2006-07-30 05:43:38 +02:00
|
|
|
buf->size -= ret;
|
2008-10-31 09:18:11 +01:00
|
|
|
memmove(buf->data, buf->data + ret, buf->size);
|
2008-10-31 09:19:40 +01:00
|
|
|
break;
|
2006-07-20 18:02:40 +02:00
|
|
|
} else {
|
2008-10-31 09:18:11 +01:00
|
|
|
size_t decr = sizeof(*buf) -
|
|
|
|
sizeof(buf->data) + buf->size;
|
2008-03-26 11:38:26 +01:00
|
|
|
|
2008-08-28 20:02:59 +02:00
|
|
|
assert(client->deferred_bytes >= decr);
|
|
|
|
client->deferred_bytes -= decr;
|
2008-10-31 09:18:11 +01:00
|
|
|
free(buf);
|
|
|
|
g_queue_pop_head(client->deferred_send);
|
2004-02-24 00:41:20 +01:00
|
|
|
}
|
2008-08-28 20:02:59 +02:00
|
|
|
client->lastTime = time(NULL);
|
2004-02-24 00:41:20 +01:00
|
|
|
}
|
|
|
|
|
2008-10-31 09:18:11 +01:00
|
|
|
if (g_queue_is_empty(client->deferred_send)) {
|
2008-12-29 17:28:56 +01:00
|
|
|
g_debug("client %i: buffer empty %lu", client->num,
|
|
|
|
(unsigned long)client->deferred_bytes);
|
2008-08-28 20:02:59 +02:00
|
|
|
assert(client->deferred_bytes == 0);
|
2006-07-20 18:02:40 +02:00
|
|
|
} else if (ret < 0 && errno != EAGAIN && errno != EINTR) {
|
2008-08-28 20:02:59 +02:00
|
|
|
/* cause client to close */
|
2008-12-29 17:28:56 +01:00
|
|
|
g_debug("client %i: problems flushing buffer",
|
|
|
|
client->num);
|
2008-08-28 20:20:10 +02:00
|
|
|
client_set_expired(client);
|
2004-02-24 00:41:20 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-08-28 20:03:51 +02:00
|
|
|
static void client_defer_output(struct client *client,
|
|
|
|
const void *data, size_t length)
|
|
|
|
{
|
2008-10-31 09:18:11 +01:00
|
|
|
size_t alloc;
|
|
|
|
struct deferred_buffer *buf;
|
2008-08-28 20:03:51 +02:00
|
|
|
|
2008-08-28 20:23:22 +02:00
|
|
|
assert(length > 0);
|
2008-08-28 20:03:51 +02:00
|
|
|
|
2008-10-31 09:18:11 +01:00
|
|
|
alloc = sizeof(*buf) - sizeof(buf->data) + length;
|
|
|
|
client->deferred_bytes += alloc;
|
2008-08-28 20:03:51 +02:00
|
|
|
if (client->deferred_bytes > client_max_output_buffer_size) {
|
2008-12-29 17:28:56 +01:00
|
|
|
g_warning("client %i: output buffer size (%lu) is "
|
|
|
|
"larger than the max (%lu)",
|
|
|
|
client->num,
|
|
|
|
(unsigned long)client->deferred_bytes,
|
|
|
|
(unsigned long)client_max_output_buffer_size);
|
2008-08-28 20:03:51 +02:00
|
|
|
/* cause client to close */
|
2008-08-28 20:20:10 +02:00
|
|
|
client_set_expired(client);
|
2008-08-28 20:03:54 +02:00
|
|
|
return;
|
2008-08-28 20:03:51 +02:00
|
|
|
}
|
2008-08-28 20:03:54 +02:00
|
|
|
|
2008-10-31 09:18:11 +01:00
|
|
|
buf = g_malloc(alloc);
|
|
|
|
buf->size = length;
|
|
|
|
memcpy(buf->data, data, length);
|
|
|
|
|
|
|
|
g_queue_push_tail(client->deferred_send, buf);
|
2008-08-28 20:03:51 +02:00
|
|
|
}
|
|
|
|
|
2008-09-07 13:24:51 +02:00
|
|
|
static void client_write_direct(struct client *client,
|
|
|
|
const char *data, size_t length)
|
2006-07-20 18:02:40 +02:00
|
|
|
{
|
2008-03-26 11:38:07 +01:00
|
|
|
ssize_t ret;
|
2004-02-24 00:41:20 +01:00
|
|
|
|
2008-08-28 20:23:22 +02:00
|
|
|
assert(length > 0);
|
2008-10-31 09:18:11 +01:00
|
|
|
assert(g_queue_is_empty(client->deferred_send));
|
2008-08-28 20:03:58 +02:00
|
|
|
|
|
|
|
if ((ret = write(client->fd, data, length)) < 0) {
|
|
|
|
if (errno == EAGAIN || errno == EINTR) {
|
2008-08-28 20:20:04 +02:00
|
|
|
client_defer_output(client, data, length);
|
2008-08-28 20:03:58 +02:00
|
|
|
} else {
|
2008-12-29 17:28:56 +01:00
|
|
|
g_debug("client %i: problems writing", client->num);
|
2008-08-28 20:20:10 +02:00
|
|
|
client_set_expired(client);
|
2008-08-28 20:03:58 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else if ((size_t)ret < client->send_buf_used) {
|
2008-08-28 20:20:04 +02:00
|
|
|
client_defer_output(client, data + ret, length - ret);
|
2008-08-28 20:03:58 +02:00
|
|
|
}
|
|
|
|
|
2008-10-31 09:18:11 +01:00
|
|
|
if (!g_queue_is_empty(client->deferred_send))
|
2008-12-29 17:28:56 +01:00
|
|
|
g_debug("client %i: buffer created", client->num);
|
2008-08-28 20:03:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void client_write_output(struct client *client)
|
|
|
|
{
|
2008-08-28 20:20:10 +02:00
|
|
|
if (client_is_expired(client) || !client->send_buf_used)
|
2004-02-24 00:41:20 +01:00
|
|
|
return;
|
|
|
|
|
2008-12-24 03:00:08 +01:00
|
|
|
if (!g_queue_is_empty(client->deferred_send)) {
|
2008-08-28 20:03:51 +02:00
|
|
|
client_defer_output(client, client->send_buf,
|
|
|
|
client->send_buf_used);
|
2008-12-24 03:00:08 +01:00
|
|
|
|
|
|
|
/* try to flush the deferred buffers now; the current
|
|
|
|
server command may take too long to finish, and
|
|
|
|
meanwhile try to feed output to the client,
|
|
|
|
otherwise it will time out. One reason why
|
|
|
|
deferring is slow might be that currently each
|
|
|
|
client_write() allocates a new deferred buffer.
|
|
|
|
This should be optimized after MPD 0.14. */
|
|
|
|
client_write_deferred(client);
|
|
|
|
} else
|
2008-09-07 13:24:51 +02:00
|
|
|
client_write_direct(client, client->send_buf,
|
|
|
|
client->send_buf_used);
|
2004-02-24 00:41:20 +01:00
|
|
|
|
2008-08-28 20:02:59 +02:00
|
|
|
client->send_buf_used = 0;
|
2004-02-24 00:41:20 +01:00
|
|
|
}
|
2007-01-11 21:41:17 +01:00
|
|
|
|
2008-09-07 13:24:51 +02:00
|
|
|
void client_write(struct client *client, const char *buffer, size_t buflen)
|
|
|
|
{
|
|
|
|
/* if the client is going to be closed, do nothing */
|
|
|
|
if (client_is_expired(client))
|
|
|
|
return;
|
|
|
|
|
|
|
|
while (buflen > 0 && !client_is_expired(client)) {
|
2008-10-17 23:53:16 +02:00
|
|
|
size_t copylen;
|
2008-09-07 13:24:51 +02:00
|
|
|
|
2008-10-17 23:52:47 +02:00
|
|
|
assert(client->send_buf_used < sizeof(client->send_buf));
|
2008-09-07 13:24:51 +02:00
|
|
|
|
2008-10-17 23:53:16 +02:00
|
|
|
copylen = sizeof(client->send_buf) - client->send_buf_used;
|
|
|
|
if (copylen > buflen)
|
|
|
|
copylen = buflen;
|
|
|
|
|
2008-09-07 13:24:51 +02:00
|
|
|
memcpy(client->send_buf + client->send_buf_used, buffer,
|
|
|
|
copylen);
|
|
|
|
buflen -= copylen;
|
|
|
|
client->send_buf_used += copylen;
|
|
|
|
buffer += copylen;
|
2008-10-17 23:52:47 +02:00
|
|
|
if (client->send_buf_used >= sizeof(client->send_buf))
|
2008-09-07 13:24:51 +02:00
|
|
|
client_write_output(client);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void client_puts(struct client *client, const char *s)
|
|
|
|
{
|
|
|
|
client_write(client, s, strlen(s));
|
|
|
|
}
|
2008-09-07 13:25:54 +02:00
|
|
|
|
|
|
|
void client_vprintf(struct client *client, const char *fmt, va_list args)
|
|
|
|
{
|
|
|
|
va_list tmp;
|
|
|
|
int length;
|
|
|
|
char *buffer;
|
|
|
|
|
|
|
|
va_copy(tmp, args);
|
|
|
|
length = vsnprintf(NULL, 0, fmt, tmp);
|
|
|
|
va_end(tmp);
|
|
|
|
|
|
|
|
if (length <= 0)
|
|
|
|
/* wtf.. */
|
|
|
|
return;
|
|
|
|
|
|
|
|
buffer = xmalloc(length + 1);
|
|
|
|
vsnprintf(buffer, length + 1, fmt, args);
|
|
|
|
client_write(client, buffer, length);
|
|
|
|
free(buffer);
|
|
|
|
}
|
|
|
|
|
2008-12-02 03:00:02 +01:00
|
|
|
G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...)
|
2008-09-07 13:25:54 +02:00
|
|
|
{
|
|
|
|
va_list args;
|
|
|
|
|
|
|
|
va_start(args, fmt);
|
|
|
|
client_vprintf(client, fmt, args);
|
|
|
|
va_end(args);
|
|
|
|
}
|
2008-10-14 22:38:14 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Send "idle" response to this client.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
client_idle_notify(struct client *client)
|
|
|
|
{
|
|
|
|
unsigned flags, i;
|
2008-11-22 13:26:21 +01:00
|
|
|
const char *const* idle_names;
|
2008-10-14 22:38:14 +02:00
|
|
|
|
|
|
|
assert(client->idle_waiting);
|
|
|
|
assert(client->idle_flags != 0);
|
|
|
|
|
|
|
|
flags = client->idle_flags;
|
|
|
|
client->idle_flags = 0;
|
|
|
|
client->idle_waiting = false;
|
|
|
|
|
2008-11-22 13:26:21 +01:00
|
|
|
idle_names = idle_get_names();
|
|
|
|
for (i = 0; idle_names[i]; ++i) {
|
|
|
|
if (flags & (1 << i) & client->idle_subscriptions)
|
2008-10-14 22:38:14 +02:00
|
|
|
client_printf(client, "changed: %s\n",
|
|
|
|
idle_names[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
client_puts(client, "OK\n");
|
|
|
|
client->lastTime = time(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
void client_manager_idle_add(unsigned flags)
|
|
|
|
{
|
|
|
|
struct client *client;
|
|
|
|
|
|
|
|
assert(flags != 0);
|
|
|
|
|
|
|
|
list_for_each_entry(client, &clients, siblings) {
|
|
|
|
if (client_is_expired(client))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
client->idle_flags |= flags;
|
2008-11-22 13:26:21 +01:00
|
|
|
if (client->idle_waiting
|
|
|
|
&& (client->idle_flags & client->idle_subscriptions)) {
|
2008-10-14 22:38:14 +02:00
|
|
|
client_idle_notify(client);
|
|
|
|
client_write_output(client);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-11-22 13:26:21 +01:00
|
|
|
bool client_idle_wait(struct client *client, unsigned flags)
|
2008-10-14 22:38:14 +02:00
|
|
|
{
|
|
|
|
assert(!client->idle_waiting);
|
|
|
|
|
|
|
|
client->idle_waiting = true;
|
2008-11-22 13:26:21 +01:00
|
|
|
client->idle_subscriptions = flags;
|
2008-10-14 22:38:14 +02:00
|
|
|
|
2008-11-22 13:26:21 +01:00
|
|
|
if (client->idle_flags & client->idle_subscriptions) {
|
2008-10-14 22:38:14 +02:00
|
|
|
client_idle_notify(client);
|
|
|
|
return true;
|
|
|
|
} else
|
|
|
|
return false;
|
|
|
|
}
|