client: splitted client.c into several pieces
The soure file client.c has nearly 1000 lines, time for splitting it into smaller pieces to improve readability.
This commit is contained in:
parent
c426bbcf95
commit
f78366910e
@ -204,6 +204,15 @@ src_mpd_SOURCES = \
|
|||||||
src/filter_registry.c \
|
src/filter_registry.c \
|
||||||
src/update.c \
|
src/update.c \
|
||||||
src/client.c \
|
src/client.c \
|
||||||
|
src/client_event.c \
|
||||||
|
src/client_expire.c \
|
||||||
|
src/client_global.c \
|
||||||
|
src/client_idle.c \
|
||||||
|
src/client_list.c \
|
||||||
|
src/client_new.c \
|
||||||
|
src/client_process.c \
|
||||||
|
src/client_read.c \
|
||||||
|
src/client_write.c \
|
||||||
src/listen.c \
|
src/listen.c \
|
||||||
src/log.c \
|
src/log.c \
|
||||||
src/ls.c \
|
src/ls.c \
|
||||||
|
875
src/client.c
875
src/client.c
@ -18,99 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "client_internal.h"
|
#include "client_internal.h"
|
||||||
#include "fifo_buffer.h"
|
|
||||||
#include "command.h"
|
|
||||||
#include "conf.h"
|
|
||||||
#include "listen.h"
|
|
||||||
#include "socket_util.h"
|
|
||||||
#include "permission.h"
|
|
||||||
#include "event_pipe.h"
|
|
||||||
#include "idle.h"
|
|
||||||
#include "main.h"
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <glib.h>
|
|
||||||
#include <assert.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
#undef G_LOG_DOMAIN
|
|
||||||
#define G_LOG_DOMAIN "client"
|
|
||||||
#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO
|
|
||||||
|
|
||||||
static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n";
|
|
||||||
|
|
||||||
#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;
|
|
||||||
static size_t client_max_command_list_size;
|
|
||||||
static size_t client_max_output_buffer_size;
|
|
||||||
|
|
||||||
static GList *clients;
|
|
||||||
static unsigned num_clients;
|
|
||||||
static guint expire_source_id;
|
|
||||||
|
|
||||||
static bool
|
|
||||||
client_list_is_empty(void)
|
|
||||||
{
|
|
||||||
return num_clients == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
client_list_is_full(void)
|
|
||||||
{
|
|
||||||
return num_clients >= client_max_connections;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct client *
|
|
||||||
client_list_get_first(void)
|
|
||||||
{
|
|
||||||
assert(clients != NULL);
|
|
||||||
|
|
||||||
return clients->data;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
client_list_foreach(GFunc func, gpointer user_data)
|
|
||||||
{
|
|
||||||
g_list_foreach(clients, func, user_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
client_list_add(struct client *client)
|
|
||||||
{
|
|
||||||
clients = g_list_prepend(clients, client);
|
|
||||||
++num_clients;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
client_list_remove(struct client *client)
|
|
||||||
{
|
|
||||||
assert(num_clients > 0);
|
|
||||||
assert(clients != NULL);
|
|
||||||
|
|
||||||
clients = g_list_remove(clients, client);
|
|
||||||
--num_clients;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void client_write_deferred(struct client *client);
|
|
||||||
|
|
||||||
static void client_write_output(struct client *client);
|
|
||||||
|
|
||||||
static void client_manager_expire(void);
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
client_in_event(GIOChannel *source, GIOCondition condition, gpointer data);
|
|
||||||
|
|
||||||
bool client_is_expired(const struct client *client)
|
bool client_is_expired(const struct client *client)
|
||||||
{
|
{
|
||||||
@ -131,785 +38,3 @@ void client_set_permission(struct client *client, unsigned permission)
|
|||||||
{
|
{
|
||||||
client->permission = permission;
|
client->permission = permission;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* An idle event which calls client_manager_expire().
|
|
||||||
*/
|
|
||||||
static gboolean
|
|
||||||
client_manager_expire_event(G_GNUC_UNUSED gpointer data)
|
|
||||||
{
|
|
||||||
expire_source_id = 0;
|
|
||||||
client_manager_expire();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void client_set_expired(struct client *client)
|
|
||||||
{
|
|
||||||
if (expire_source_id == 0 && !client_is_expired(client))
|
|
||||||
/* delayed deletion */
|
|
||||||
expire_source_id = g_idle_add(client_manager_expire_event,
|
|
||||||
NULL);
|
|
||||||
|
|
||||||
if (client->source_id != 0) {
|
|
||||||
g_source_remove(client->source_id);
|
|
||||||
client->source_id = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (client->channel != NULL) {
|
|
||||||
g_io_channel_unref(client->channel);
|
|
||||||
client->channel = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void client_init(struct client *client, int fd)
|
|
||||||
{
|
|
||||||
static unsigned int next_client_num;
|
|
||||||
|
|
||||||
assert(fd >= 0);
|
|
||||||
|
|
||||||
client->cmd_list_size = 0;
|
|
||||||
client->cmd_list_OK = -1;
|
|
||||||
|
|
||||||
#ifndef G_OS_WIN32
|
|
||||||
client->channel = g_io_channel_unix_new(fd);
|
|
||||||
#else
|
|
||||||
client->channel = g_io_channel_win32_new_socket(fd);
|
|
||||||
#endif
|
|
||||||
/* GLib is responsible for closing the file descriptor */
|
|
||||||
g_io_channel_set_close_on_unref(client->channel, true);
|
|
||||||
/* NULL encoding means the stream is binary safe; the MPD
|
|
||||||
protocol is UTF-8 only, but we are doing this call anyway
|
|
||||||
to prevent GLib from messing around with the stream */
|
|
||||||
g_io_channel_set_encoding(client->channel, NULL, NULL);
|
|
||||||
/* we prefer to do buffering */
|
|
||||||
g_io_channel_set_buffered(client->channel, false);
|
|
||||||
|
|
||||||
client->source_id = g_io_add_watch(client->channel,
|
|
||||||
G_IO_IN|G_IO_ERR|G_IO_HUP,
|
|
||||||
client_in_event, client);
|
|
||||||
|
|
||||||
client->input = fifo_buffer_new(4096);
|
|
||||||
|
|
||||||
client->cmd_list = NULL;
|
|
||||||
client->deferred_send = g_queue_new();
|
|
||||||
client->deferred_bytes = 0;
|
|
||||||
client->num = next_client_num++;
|
|
||||||
client->send_buf_used = 0;
|
|
||||||
|
|
||||||
client->permission = getDefaultPermissions();
|
|
||||||
|
|
||||||
(void)write(fd, GREETING, sizeof(GREETING) - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void free_cmd_list(GSList *list)
|
|
||||||
{
|
|
||||||
for (GSList *tmp = list; tmp != NULL; tmp = g_slist_next(tmp))
|
|
||||||
g_free(tmp->data);
|
|
||||||
|
|
||||||
g_slist_free(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void new_cmd_list_ptr(struct client *client, char *s)
|
|
||||||
{
|
|
||||||
client->cmd_list = g_slist_prepend(client->cmd_list, g_strdup(s));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
deferred_buffer_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
|
|
||||||
{
|
|
||||||
struct deferred_buffer *buffer = data;
|
|
||||||
g_free(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void client_close(struct client *client)
|
|
||||||
{
|
|
||||||
client_list_remove(client);
|
|
||||||
|
|
||||||
client_set_expired(client);
|
|
||||||
|
|
||||||
g_timer_destroy(client->last_activity);
|
|
||||||
|
|
||||||
if (client->cmd_list) {
|
|
||||||
free_cmd_list(client->cmd_list);
|
|
||||||
client->cmd_list = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_queue_foreach(client->deferred_send, deferred_buffer_free, NULL);
|
|
||||||
g_queue_free(client->deferred_send);
|
|
||||||
|
|
||||||
fifo_buffer_free(client->input);
|
|
||||||
|
|
||||||
g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
|
|
||||||
"[%u] closed", client->num);
|
|
||||||
g_free(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid)
|
|
||||||
{
|
|
||||||
struct client *client;
|
|
||||||
char *remote;
|
|
||||||
|
|
||||||
if (client_list_is_full()) {
|
|
||||||
g_warning("Max Connections Reached!");
|
|
||||||
close(fd);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
client = g_new0(struct client, 1);
|
|
||||||
client_list_add(client);
|
|
||||||
|
|
||||||
client_init(client, fd);
|
|
||||||
client->uid = uid;
|
|
||||||
|
|
||||||
client->last_activity = g_timer_new();
|
|
||||||
|
|
||||||
remote = sockaddr_to_string(sa, sa_length, NULL);
|
|
||||||
g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
|
|
||||||
"[%u] opened from %s", client->num, remote);
|
|
||||||
g_free(remote);
|
|
||||||
}
|
|
||||||
|
|
||||||
static enum command_return
|
|
||||||
client_process_line(struct client *client, char *line)
|
|
||||||
{
|
|
||||||
enum command_return ret;
|
|
||||||
|
|
||||||
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 COMMAND_RETURN_OK;
|
|
||||||
} else if (client->idle_waiting) {
|
|
||||||
/* during idle mode, clients must not send anything
|
|
||||||
except "noidle" */
|
|
||||||
g_warning("[%u] command \"%s\" during idle",
|
|
||||||
client->num, line);
|
|
||||||
return COMMAND_RETURN_CLOSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (client->cmd_list_OK >= 0) {
|
|
||||||
if (strcmp(line, CLIENT_LIST_MODE_END) == 0) {
|
|
||||||
g_debug("[%u] process command list",
|
|
||||||
client->num);
|
|
||||||
|
|
||||||
/* 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);
|
|
||||||
|
|
||||||
ret = command_process_list(client,
|
|
||||||
client->cmd_list_OK,
|
|
||||||
client->cmd_list);
|
|
||||||
g_debug("[%u] process command "
|
|
||||||
"list returned %i", client->num, ret);
|
|
||||||
|
|
||||||
if (ret == COMMAND_RETURN_CLOSE ||
|
|
||||||
client_is_expired(client))
|
|
||||||
return COMMAND_RETURN_CLOSE;
|
|
||||||
|
|
||||||
if (ret == COMMAND_RETURN_OK)
|
|
||||||
command_success(client);
|
|
||||||
|
|
||||||
client_write_output(client);
|
|
||||||
free_cmd_list(client->cmd_list);
|
|
||||||
client->cmd_list = NULL;
|
|
||||||
client->cmd_list_OK = -1;
|
|
||||||
} else {
|
|
||||||
size_t len = strlen(line) + 1;
|
|
||||||
client->cmd_list_size += len;
|
|
||||||
if (client->cmd_list_size >
|
|
||||||
client_max_command_list_size) {
|
|
||||||
g_warning("[%u] 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);
|
|
||||||
return COMMAND_RETURN_CLOSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
new_cmd_list_ptr(client, line);
|
|
||||||
ret = COMMAND_RETURN_OK;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) {
|
|
||||||
client->cmd_list_OK = 0;
|
|
||||||
ret = COMMAND_RETURN_OK;
|
|
||||||
} else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) {
|
|
||||||
client->cmd_list_OK = 1;
|
|
||||||
ret = COMMAND_RETURN_OK;
|
|
||||||
} else {
|
|
||||||
g_debug("[%u] process command \"%s\"",
|
|
||||||
client->num, line);
|
|
||||||
ret = command_process(client, line);
|
|
||||||
g_debug("[%u] command returned %i",
|
|
||||||
client->num, ret);
|
|
||||||
|
|
||||||
if (ret == COMMAND_RETURN_CLOSE ||
|
|
||||||
client_is_expired(client))
|
|
||||||
return COMMAND_RETURN_CLOSE;
|
|
||||||
|
|
||||||
if (ret == COMMAND_RETURN_OK)
|
|
||||||
command_success(client);
|
|
||||||
|
|
||||||
client_write_output(client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char *
|
|
||||||
client_read_line(struct client *client)
|
|
||||||
{
|
|
||||||
const char *p, *newline;
|
|
||||||
size_t length;
|
|
||||||
char *line;
|
|
||||||
|
|
||||||
p = fifo_buffer_read(client->input, &length);
|
|
||||||
if (p == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
newline = memchr(p, '\n', length);
|
|
||||||
if (newline == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
line = g_strndup(p, newline - p);
|
|
||||||
fifo_buffer_consume(client->input, newline - p + 1);
|
|
||||||
|
|
||||||
return g_strchomp(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
static enum command_return
|
|
||||||
client_input_received(struct client *client, size_t bytesRead)
|
|
||||||
{
|
|
||||||
char *line;
|
|
||||||
|
|
||||||
fifo_buffer_append(client->input, bytesRead);
|
|
||||||
|
|
||||||
/* process all lines */
|
|
||||||
|
|
||||||
while ((line = client_read_line(client)) != NULL) {
|
|
||||||
enum command_return ret = client_process_line(client, line);
|
|
||||||
g_free(line);
|
|
||||||
|
|
||||||
if (ret == COMMAND_RETURN_KILL ||
|
|
||||||
ret == COMMAND_RETURN_CLOSE)
|
|
||||||
return ret;
|
|
||||||
if (client_is_expired(client))
|
|
||||||
return COMMAND_RETURN_CLOSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return COMMAND_RETURN_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static enum command_return
|
|
||||||
client_read(struct client *client)
|
|
||||||
{
|
|
||||||
char *p;
|
|
||||||
size_t max_length;
|
|
||||||
GError *error = NULL;
|
|
||||||
GIOStatus status;
|
|
||||||
gsize bytes_read;
|
|
||||||
|
|
||||||
assert(client != NULL);
|
|
||||||
assert(client->channel != NULL);
|
|
||||||
|
|
||||||
p = fifo_buffer_write(client->input, &max_length);
|
|
||||||
if (p == NULL) {
|
|
||||||
g_warning("[%u] buffer overflow", client->num);
|
|
||||||
return COMMAND_RETURN_CLOSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
status = g_io_channel_read_chars(client->channel, p, max_length,
|
|
||||||
&bytes_read, &error);
|
|
||||||
switch (status) {
|
|
||||||
case G_IO_STATUS_NORMAL:
|
|
||||||
return client_input_received(client, bytes_read);
|
|
||||||
|
|
||||||
case G_IO_STATUS_AGAIN:
|
|
||||||
/* try again later, after select() */
|
|
||||||
return COMMAND_RETURN_OK;
|
|
||||||
|
|
||||||
case G_IO_STATUS_EOF:
|
|
||||||
/* peer disconnected */
|
|
||||||
return COMMAND_RETURN_CLOSE;
|
|
||||||
|
|
||||||
case G_IO_STATUS_ERROR:
|
|
||||||
/* I/O error */
|
|
||||||
g_warning("failed to read from client %d: %s",
|
|
||||||
client->num, error->message);
|
|
||||||
g_error_free(error);
|
|
||||||
return COMMAND_RETURN_CLOSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* unreachable */
|
|
||||||
return COMMAND_RETURN_CLOSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
|
|
||||||
gpointer data);
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
client_in_event(G_GNUC_UNUSED GIOChannel *source,
|
|
||||||
GIOCondition condition,
|
|
||||||
gpointer data)
|
|
||||||
{
|
|
||||||
struct client *client = data;
|
|
||||||
enum command_return ret;
|
|
||||||
|
|
||||||
assert(!client_is_expired(client));
|
|
||||||
|
|
||||||
if (condition != G_IO_IN) {
|
|
||||||
client_set_expired(client);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_timer_start(client->last_activity);
|
|
||||||
|
|
||||||
ret = client_read(client);
|
|
||||||
switch (ret) {
|
|
||||||
case COMMAND_RETURN_OK:
|
|
||||||
case COMMAND_RETURN_ERROR:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case COMMAND_RETURN_KILL:
|
|
||||||
client_close(client);
|
|
||||||
g_main_loop_quit(main_loop);
|
|
||||||
return false;
|
|
||||||
|
|
||||||
case COMMAND_RETURN_CLOSE:
|
|
||||||
client_close(client);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (client_is_expired(client)) {
|
|
||||||
client_close(client);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!g_queue_is_empty(client->deferred_send)) {
|
|
||||||
/* deferred buffers exist: schedule write */
|
|
||||||
client->source_id = g_io_add_watch(client->channel,
|
|
||||||
G_IO_OUT|G_IO_ERR|G_IO_HUP,
|
|
||||||
client_out_event, client);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* read more */
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
|
|
||||||
gpointer data)
|
|
||||||
{
|
|
||||||
struct client *client = data;
|
|
||||||
|
|
||||||
assert(!client_is_expired(client));
|
|
||||||
|
|
||||||
if (condition != G_IO_OUT) {
|
|
||||||
client_set_expired(client);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
client_write_deferred(client);
|
|
||||||
|
|
||||||
if (client_is_expired(client)) {
|
|
||||||
client_close(client);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_timer_start(client->last_activity);
|
|
||||||
|
|
||||||
if (g_queue_is_empty(client->deferred_send)) {
|
|
||||||
/* done sending deferred buffers exist: schedule
|
|
||||||
read */
|
|
||||||
client->source_id = g_io_add_watch(client->channel,
|
|
||||||
G_IO_IN|G_IO_ERR|G_IO_HUP,
|
|
||||||
client_in_event, client);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* write more */
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_manager_init(void)
|
|
||||||
{
|
|
||||||
client_timeout = config_get_positive(CONF_CONN_TIMEOUT,
|
|
||||||
CLIENT_TIMEOUT_DEFAULT);
|
|
||||||
client_max_connections =
|
|
||||||
config_get_positive(CONF_MAX_CONN,
|
|
||||||
CLIENT_MAX_CONNECTIONS_DEFAULT);
|
|
||||||
client_max_command_list_size =
|
|
||||||
config_get_positive(CONF_MAX_COMMAND_LIST_SIZE,
|
|
||||||
CLIENT_MAX_COMMAND_LIST_DEFAULT / 1024)
|
|
||||||
* 1024;
|
|
||||||
|
|
||||||
client_max_output_buffer_size =
|
|
||||||
config_get_positive(CONF_MAX_OUTPUT_BUFFER_SIZE,
|
|
||||||
CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT / 1024)
|
|
||||||
* 1024;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void client_close_all(void)
|
|
||||||
{
|
|
||||||
while (!client_list_is_empty()) {
|
|
||||||
struct client *client = client_list_get_first();
|
|
||||||
|
|
||||||
client_close(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(client_list_is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_manager_deinit(void)
|
|
||||||
{
|
|
||||||
client_close_all();
|
|
||||||
|
|
||||||
client_max_connections = 0;
|
|
||||||
|
|
||||||
if (expire_source_id != 0)
|
|
||||||
g_source_remove(expire_source_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
client_check_expired_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
|
|
||||||
{
|
|
||||||
struct client *client = data;
|
|
||||||
|
|
||||||
if (client_is_expired(client)) {
|
|
||||||
g_debug("[%u] expired", client->num);
|
|
||||||
client_close(client);
|
|
||||||
} else if (!client->idle_waiting && /* idle clients
|
|
||||||
never expire */
|
|
||||||
(int)g_timer_elapsed(client->last_activity, NULL) >
|
|
||||||
client_timeout) {
|
|
||||||
g_debug("[%u] timeout", client->num);
|
|
||||||
client_close(client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
client_manager_expire(void)
|
|
||||||
{
|
|
||||||
client_list_foreach(client_check_expired_callback, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static size_t
|
|
||||||
client_write_deferred_buffer(struct client *client,
|
|
||||||
const struct deferred_buffer *buffer)
|
|
||||||
{
|
|
||||||
GError *error = NULL;
|
|
||||||
GIOStatus status;
|
|
||||||
gsize bytes_written;
|
|
||||||
|
|
||||||
assert(client != NULL);
|
|
||||||
assert(client->channel != NULL);
|
|
||||||
assert(buffer != NULL);
|
|
||||||
|
|
||||||
status = g_io_channel_write_chars
|
|
||||||
(client->channel, buffer->data, buffer->size,
|
|
||||||
&bytes_written, &error);
|
|
||||||
switch (status) {
|
|
||||||
case G_IO_STATUS_NORMAL:
|
|
||||||
return bytes_written;
|
|
||||||
|
|
||||||
case G_IO_STATUS_AGAIN:
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
case G_IO_STATUS_EOF:
|
|
||||||
/* client has disconnected */
|
|
||||||
|
|
||||||
client_set_expired(client);
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
case G_IO_STATUS_ERROR:
|
|
||||||
/* I/O error */
|
|
||||||
|
|
||||||
client_set_expired(client);
|
|
||||||
g_warning("failed to flush buffer for %i: %s",
|
|
||||||
client->num, error->message);
|
|
||||||
g_error_free(error);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* unreachable */
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void client_write_deferred(struct client *client)
|
|
||||||
{
|
|
||||||
size_t ret;
|
|
||||||
|
|
||||||
while (!g_queue_is_empty(client->deferred_send)) {
|
|
||||||
struct deferred_buffer *buf =
|
|
||||||
g_queue_peek_head(client->deferred_send);
|
|
||||||
|
|
||||||
assert(buf->size > 0);
|
|
||||||
assert(buf->size <= client->deferred_bytes);
|
|
||||||
|
|
||||||
ret = client_write_deferred_buffer(client, buf);
|
|
||||||
if (ret == 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (ret < buf->size) {
|
|
||||||
assert(client->deferred_bytes >= (size_t)ret);
|
|
||||||
client->deferred_bytes -= ret;
|
|
||||||
buf->size -= ret;
|
|
||||||
memmove(buf->data, buf->data + ret, buf->size);
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
size_t decr = sizeof(*buf) -
|
|
||||||
sizeof(buf->data) + buf->size;
|
|
||||||
|
|
||||||
assert(client->deferred_bytes >= decr);
|
|
||||||
client->deferred_bytes -= decr;
|
|
||||||
g_free(buf);
|
|
||||||
g_queue_pop_head(client->deferred_send);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_timer_start(client->last_activity);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (g_queue_is_empty(client->deferred_send)) {
|
|
||||||
g_debug("[%u] buffer empty %lu", client->num,
|
|
||||||
(unsigned long)client->deferred_bytes);
|
|
||||||
assert(client->deferred_bytes == 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void client_defer_output(struct client *client,
|
|
||||||
const void *data, size_t length)
|
|
||||||
{
|
|
||||||
size_t alloc;
|
|
||||||
struct deferred_buffer *buf;
|
|
||||||
|
|
||||||
assert(length > 0);
|
|
||||||
|
|
||||||
alloc = sizeof(*buf) - sizeof(buf->data) + length;
|
|
||||||
client->deferred_bytes += alloc;
|
|
||||||
if (client->deferred_bytes > client_max_output_buffer_size) {
|
|
||||||
g_warning("[%u] output buffer size (%lu) is "
|
|
||||||
"larger than the max (%lu)",
|
|
||||||
client->num,
|
|
||||||
(unsigned long)client->deferred_bytes,
|
|
||||||
(unsigned long)client_max_output_buffer_size);
|
|
||||||
/* cause client to close */
|
|
||||||
client_set_expired(client);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = g_malloc(alloc);
|
|
||||||
buf->size = length;
|
|
||||||
memcpy(buf->data, data, length);
|
|
||||||
|
|
||||||
g_queue_push_tail(client->deferred_send, buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void client_write_direct(struct client *client,
|
|
||||||
const char *data, size_t length)
|
|
||||||
{
|
|
||||||
GError *error = NULL;
|
|
||||||
GIOStatus status;
|
|
||||||
gsize bytes_written;
|
|
||||||
|
|
||||||
assert(client != NULL);
|
|
||||||
assert(client->channel != NULL);
|
|
||||||
assert(data != NULL);
|
|
||||||
assert(length > 0);
|
|
||||||
assert(g_queue_is_empty(client->deferred_send));
|
|
||||||
|
|
||||||
status = g_io_channel_write_chars(client->channel, data, length,
|
|
||||||
&bytes_written, &error);
|
|
||||||
switch (status) {
|
|
||||||
case G_IO_STATUS_NORMAL:
|
|
||||||
case G_IO_STATUS_AGAIN:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case G_IO_STATUS_EOF:
|
|
||||||
/* client has disconnected */
|
|
||||||
|
|
||||||
client_set_expired(client);
|
|
||||||
return;
|
|
||||||
|
|
||||||
case G_IO_STATUS_ERROR:
|
|
||||||
/* I/O error */
|
|
||||||
|
|
||||||
client_set_expired(client);
|
|
||||||
g_warning("failed to write to %i: %s",
|
|
||||||
client->num, error->message);
|
|
||||||
g_error_free(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bytes_written < length)
|
|
||||||
client_defer_output(client, data + bytes_written,
|
|
||||||
length - bytes_written);
|
|
||||||
|
|
||||||
if (!g_queue_is_empty(client->deferred_send))
|
|
||||||
g_debug("[%u] buffer created", client->num);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void client_write_output(struct client *client)
|
|
||||||
{
|
|
||||||
if (client_is_expired(client) || !client->send_buf_used)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!g_queue_is_empty(client->deferred_send)) {
|
|
||||||
client_defer_output(client, client->send_buf,
|
|
||||||
client->send_buf_used);
|
|
||||||
|
|
||||||
if (client_is_expired(client))
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* 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
|
|
||||||
client_write_direct(client, client->send_buf,
|
|
||||||
client->send_buf_used);
|
|
||||||
|
|
||||||
client->send_buf_used = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write a block of data to the client.
|
|
||||||
*/
|
|
||||||
static 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)) {
|
|
||||||
size_t copylen;
|
|
||||||
|
|
||||||
assert(client->send_buf_used < sizeof(client->send_buf));
|
|
||||||
|
|
||||||
copylen = sizeof(client->send_buf) - client->send_buf_used;
|
|
||||||
if (copylen > buflen)
|
|
||||||
copylen = buflen;
|
|
||||||
|
|
||||||
memcpy(client->send_buf + client->send_buf_used, buffer,
|
|
||||||
copylen);
|
|
||||||
buflen -= copylen;
|
|
||||||
client->send_buf_used += copylen;
|
|
||||||
buffer += copylen;
|
|
||||||
if (client->send_buf_used >= sizeof(client->send_buf))
|
|
||||||
client_write_output(client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_puts(struct client *client, const char *s)
|
|
||||||
{
|
|
||||||
client_write(client, s, strlen(s));
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = g_malloc(length + 1);
|
|
||||||
vsnprintf(buffer, length + 1, fmt, args);
|
|
||||||
client_write(client, buffer, length);
|
|
||||||
g_free(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...)
|
|
||||||
{
|
|
||||||
va_list args;
|
|
||||||
|
|
||||||
va_start(args, fmt);
|
|
||||||
client_vprintf(client, fmt, args);
|
|
||||||
va_end(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send "idle" response to this client.
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
client_idle_notify(struct client *client)
|
|
||||||
{
|
|
||||||
unsigned flags, i;
|
|
||||||
const char *const* idle_names;
|
|
||||||
|
|
||||||
assert(client->idle_waiting);
|
|
||||||
assert(client->idle_flags != 0);
|
|
||||||
|
|
||||||
flags = client->idle_flags;
|
|
||||||
client->idle_flags = 0;
|
|
||||||
client->idle_waiting = false;
|
|
||||||
|
|
||||||
idle_names = idle_get_names();
|
|
||||||
for (i = 0; idle_names[i]; ++i) {
|
|
||||||
if (flags & (1 << i) & client->idle_subscriptions)
|
|
||||||
client_printf(client, "changed: %s\n",
|
|
||||||
idle_names[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
client_puts(client, "OK\n");
|
|
||||||
g_timer_start(client->last_activity);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
client_idle_callback(gpointer data, gpointer user_data)
|
|
||||||
{
|
|
||||||
struct client *client = data;
|
|
||||||
unsigned flags = GPOINTER_TO_UINT(user_data);
|
|
||||||
|
|
||||||
if (client_is_expired(client))
|
|
||||||
return;
|
|
||||||
|
|
||||||
client->idle_flags |= flags;
|
|
||||||
if (client->idle_waiting
|
|
||||||
&& (client->idle_flags & client->idle_subscriptions)) {
|
|
||||||
client_idle_notify(client);
|
|
||||||
client_write_output(client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_manager_idle_add(unsigned flags)
|
|
||||||
{
|
|
||||||
assert(flags != 0);
|
|
||||||
|
|
||||||
client_list_foreach(client_idle_callback, GUINT_TO_POINTER(flags));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool client_idle_wait(struct client *client, unsigned flags)
|
|
||||||
{
|
|
||||||
assert(!client->idle_waiting);
|
|
||||||
|
|
||||||
client->idle_waiting = true;
|
|
||||||
client->idle_subscriptions = flags;
|
|
||||||
|
|
||||||
if (client->idle_flags & client->idle_subscriptions) {
|
|
||||||
client_idle_notify(client);
|
|
||||||
return true;
|
|
||||||
} else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
107
src/client_event.c
Normal file
107
src/client_event.c
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "client_internal.h"
|
||||||
|
#include "main.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
|
||||||
|
gpointer data)
|
||||||
|
{
|
||||||
|
struct client *client = data;
|
||||||
|
|
||||||
|
assert(!client_is_expired(client));
|
||||||
|
|
||||||
|
if (condition != G_IO_OUT) {
|
||||||
|
client_set_expired(client);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
client_write_deferred(client);
|
||||||
|
|
||||||
|
if (client_is_expired(client)) {
|
||||||
|
client_close(client);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_timer_start(client->last_activity);
|
||||||
|
|
||||||
|
if (g_queue_is_empty(client->deferred_send)) {
|
||||||
|
/* done sending deferred buffers exist: schedule
|
||||||
|
read */
|
||||||
|
client->source_id = g_io_add_watch(client->channel,
|
||||||
|
G_IO_IN|G_IO_ERR|G_IO_HUP,
|
||||||
|
client_in_event, client);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* write more */
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
|
||||||
|
gpointer data)
|
||||||
|
{
|
||||||
|
struct client *client = data;
|
||||||
|
enum command_return ret;
|
||||||
|
|
||||||
|
assert(!client_is_expired(client));
|
||||||
|
|
||||||
|
if (condition != G_IO_IN) {
|
||||||
|
client_set_expired(client);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_timer_start(client->last_activity);
|
||||||
|
|
||||||
|
ret = client_read(client);
|
||||||
|
switch (ret) {
|
||||||
|
case COMMAND_RETURN_OK:
|
||||||
|
case COMMAND_RETURN_ERROR:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case COMMAND_RETURN_KILL:
|
||||||
|
client_close(client);
|
||||||
|
g_main_loop_quit(main_loop);
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case COMMAND_RETURN_CLOSE:
|
||||||
|
client_close(client);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client_is_expired(client)) {
|
||||||
|
client_close(client);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!g_queue_is_empty(client->deferred_send)) {
|
||||||
|
/* deferred buffers exist: schedule write */
|
||||||
|
client->source_id = g_io_add_watch(client->channel,
|
||||||
|
G_IO_OUT|G_IO_ERR|G_IO_HUP,
|
||||||
|
client_out_event, client);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* read more */
|
||||||
|
return true;
|
||||||
|
}
|
89
src/client_expire.c
Normal file
89
src/client_expire.c
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "client_internal.h"
|
||||||
|
|
||||||
|
static guint expire_source_id;
|
||||||
|
|
||||||
|
void
|
||||||
|
client_set_expired(struct client *client)
|
||||||
|
{
|
||||||
|
if (!client_is_expired(client))
|
||||||
|
client_schedule_expire();
|
||||||
|
|
||||||
|
if (client->source_id != 0) {
|
||||||
|
g_source_remove(client->source_id);
|
||||||
|
client->source_id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client->channel != NULL) {
|
||||||
|
g_io_channel_unref(client->channel);
|
||||||
|
client->channel = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
client_check_expired_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
|
||||||
|
{
|
||||||
|
struct client *client = data;
|
||||||
|
|
||||||
|
if (client_is_expired(client)) {
|
||||||
|
g_debug("[%u] expired", client->num);
|
||||||
|
client_close(client);
|
||||||
|
} else if (!client->idle_waiting && /* idle clients
|
||||||
|
never expire */
|
||||||
|
(int)g_timer_elapsed(client->last_activity, NULL) >
|
||||||
|
client_timeout) {
|
||||||
|
g_debug("[%u] timeout", client->num);
|
||||||
|
client_close(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
client_manager_expire(void)
|
||||||
|
{
|
||||||
|
client_list_foreach(client_check_expired_callback, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An idle event which calls client_manager_expire().
|
||||||
|
*/
|
||||||
|
static gboolean
|
||||||
|
client_manager_expire_event(G_GNUC_UNUSED gpointer data)
|
||||||
|
{
|
||||||
|
expire_source_id = 0;
|
||||||
|
client_manager_expire();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
client_schedule_expire(void)
|
||||||
|
{
|
||||||
|
if (expire_source_id == 0)
|
||||||
|
/* delayed deletion */
|
||||||
|
expire_source_id = g_idle_add(client_manager_expire_event,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
client_deinit_expire(void)
|
||||||
|
{
|
||||||
|
if (expire_source_id != 0)
|
||||||
|
g_source_remove(expire_source_id);
|
||||||
|
}
|
72
src/client_global.c
Normal file
72
src/client_global.c
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "client_internal.h"
|
||||||
|
#include "conf.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#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 */
|
||||||
|
unsigned int client_max_connections;
|
||||||
|
int client_timeout;
|
||||||
|
size_t client_max_command_list_size;
|
||||||
|
size_t client_max_output_buffer_size;
|
||||||
|
|
||||||
|
void client_manager_init(void)
|
||||||
|
{
|
||||||
|
client_timeout = config_get_positive(CONF_CONN_TIMEOUT,
|
||||||
|
CLIENT_TIMEOUT_DEFAULT);
|
||||||
|
client_max_connections =
|
||||||
|
config_get_positive(CONF_MAX_CONN,
|
||||||
|
CLIENT_MAX_CONNECTIONS_DEFAULT);
|
||||||
|
client_max_command_list_size =
|
||||||
|
config_get_positive(CONF_MAX_COMMAND_LIST_SIZE,
|
||||||
|
CLIENT_MAX_COMMAND_LIST_DEFAULT / 1024)
|
||||||
|
* 1024;
|
||||||
|
|
||||||
|
client_max_output_buffer_size =
|
||||||
|
config_get_positive(CONF_MAX_OUTPUT_BUFFER_SIZE,
|
||||||
|
CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT / 1024)
|
||||||
|
* 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void client_close_all(void)
|
||||||
|
{
|
||||||
|
while (!client_list_is_empty()) {
|
||||||
|
struct client *client = client_list_get_first();
|
||||||
|
|
||||||
|
client_close(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(client_list_is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
void client_manager_deinit(void)
|
||||||
|
{
|
||||||
|
client_close_all();
|
||||||
|
|
||||||
|
client_max_connections = 0;
|
||||||
|
|
||||||
|
client_deinit_expire();
|
||||||
|
}
|
88
src/client_idle.c
Normal file
88
src/client_idle.c
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "client_internal.h"
|
||||||
|
#include "idle.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send "idle" response to this client.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
client_idle_notify(struct client *client)
|
||||||
|
{
|
||||||
|
unsigned flags, i;
|
||||||
|
const char *const* idle_names;
|
||||||
|
|
||||||
|
assert(client->idle_waiting);
|
||||||
|
assert(client->idle_flags != 0);
|
||||||
|
|
||||||
|
flags = client->idle_flags;
|
||||||
|
client->idle_flags = 0;
|
||||||
|
client->idle_waiting = false;
|
||||||
|
|
||||||
|
idle_names = idle_get_names();
|
||||||
|
for (i = 0; idle_names[i]; ++i) {
|
||||||
|
if (flags & (1 << i) & client->idle_subscriptions)
|
||||||
|
client_printf(client, "changed: %s\n",
|
||||||
|
idle_names[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
client_puts(client, "OK\n");
|
||||||
|
g_timer_start(client->last_activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
client_idle_callback(gpointer data, gpointer user_data)
|
||||||
|
{
|
||||||
|
struct client *client = data;
|
||||||
|
unsigned flags = GPOINTER_TO_UINT(user_data);
|
||||||
|
|
||||||
|
if (client_is_expired(client))
|
||||||
|
return;
|
||||||
|
|
||||||
|
client->idle_flags |= flags;
|
||||||
|
if (client->idle_waiting
|
||||||
|
&& (client->idle_flags & client->idle_subscriptions)) {
|
||||||
|
client_idle_notify(client);
|
||||||
|
client_write_output(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void client_manager_idle_add(unsigned flags)
|
||||||
|
{
|
||||||
|
assert(flags != 0);
|
||||||
|
|
||||||
|
client_list_foreach(client_idle_callback, GUINT_TO_POINTER(flags));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool client_idle_wait(struct client *client, unsigned flags)
|
||||||
|
{
|
||||||
|
assert(!client->idle_waiting);
|
||||||
|
|
||||||
|
client->idle_waiting = true;
|
||||||
|
client->idle_subscriptions = flags;
|
||||||
|
|
||||||
|
if (client->idle_flags & client->idle_subscriptions) {
|
||||||
|
client_idle_notify(client);
|
||||||
|
return true;
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
}
|
@ -21,6 +21,10 @@
|
|||||||
#define MPD_CLIENT_INTERNAL_H
|
#define MPD_CLIENT_INTERNAL_H
|
||||||
|
|
||||||
#include "client.h"
|
#include "client.h"
|
||||||
|
#include "command.h"
|
||||||
|
|
||||||
|
#undef G_LOG_DOMAIN
|
||||||
|
#define G_LOG_DOMAIN "client"
|
||||||
|
|
||||||
struct deferred_buffer {
|
struct deferred_buffer {
|
||||||
size_t size;
|
size_t size;
|
||||||
@ -65,4 +69,77 @@ struct client {
|
|||||||
unsigned idle_subscriptions;
|
unsigned idle_subscriptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
extern unsigned int client_max_connections;
|
||||||
|
extern int client_timeout;
|
||||||
|
extern size_t client_max_command_list_size;
|
||||||
|
extern size_t client_max_output_buffer_size;
|
||||||
|
|
||||||
|
bool
|
||||||
|
client_list_is_empty(void);
|
||||||
|
|
||||||
|
bool
|
||||||
|
client_list_is_full(void);
|
||||||
|
|
||||||
|
struct client *
|
||||||
|
client_list_get_first(void);
|
||||||
|
|
||||||
|
void
|
||||||
|
client_list_add(struct client *client);
|
||||||
|
|
||||||
|
void
|
||||||
|
client_list_foreach(GFunc func, gpointer user_data);
|
||||||
|
|
||||||
|
void
|
||||||
|
client_list_remove(struct client *client);
|
||||||
|
|
||||||
|
void
|
||||||
|
client_close(struct client *client);
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
new_cmd_list_ptr(struct client *client, const char *s)
|
||||||
|
{
|
||||||
|
client->cmd_list = g_slist_prepend(client->cmd_list, g_strdup(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
free_cmd_list(GSList *list)
|
||||||
|
{
|
||||||
|
for (GSList *tmp = list; tmp != NULL; tmp = g_slist_next(tmp))
|
||||||
|
g_free(tmp->data);
|
||||||
|
|
||||||
|
g_slist_free(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
client_set_expired(struct client *client);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule an "expired" check for all clients: permanently delete
|
||||||
|
* clients which have been set "expired" with client_set_expired().
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
client_schedule_expire(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a scheduled "expired" check.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
client_deinit_expire(void);
|
||||||
|
|
||||||
|
enum command_return
|
||||||
|
client_read(struct client *client);
|
||||||
|
|
||||||
|
enum command_return
|
||||||
|
client_process_line(struct client *client, char *line);
|
||||||
|
|
||||||
|
void
|
||||||
|
client_write_deferred(struct client *client);
|
||||||
|
|
||||||
|
void
|
||||||
|
client_write_output(struct client *client);
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
client_in_event(GIOChannel *source, GIOCondition condition,
|
||||||
|
gpointer data);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
68
src/client_list.c
Normal file
68
src/client_list.c
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "client_internal.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
static GList *clients;
|
||||||
|
static unsigned num_clients;
|
||||||
|
|
||||||
|
bool
|
||||||
|
client_list_is_empty(void)
|
||||||
|
{
|
||||||
|
return num_clients == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
client_list_is_full(void)
|
||||||
|
{
|
||||||
|
return num_clients >= client_max_connections;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct client *
|
||||||
|
client_list_get_first(void)
|
||||||
|
{
|
||||||
|
assert(clients != NULL);
|
||||||
|
|
||||||
|
return clients->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
client_list_add(struct client *client)
|
||||||
|
{
|
||||||
|
clients = g_list_prepend(clients, client);
|
||||||
|
++num_clients;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
client_list_foreach(GFunc func, gpointer user_data)
|
||||||
|
{
|
||||||
|
g_list_foreach(clients, func, user_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
client_list_remove(struct client *client)
|
||||||
|
{
|
||||||
|
assert(num_clients > 0);
|
||||||
|
assert(clients != NULL);
|
||||||
|
|
||||||
|
clients = g_list_remove(clients, client);
|
||||||
|
--num_clients;
|
||||||
|
}
|
123
src/client_new.c
Normal file
123
src/client_new.c
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "client_internal.h"
|
||||||
|
#include "fifo_buffer.h"
|
||||||
|
#include "socket_util.h"
|
||||||
|
#include "permission.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO
|
||||||
|
|
||||||
|
static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n";
|
||||||
|
|
||||||
|
void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid)
|
||||||
|
{
|
||||||
|
static unsigned int next_client_num;
|
||||||
|
struct client *client;
|
||||||
|
char *remote;
|
||||||
|
|
||||||
|
assert(fd >= 0);
|
||||||
|
|
||||||
|
if (client_list_is_full()) {
|
||||||
|
g_warning("Max Connections Reached!");
|
||||||
|
close(fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client = g_new0(struct client, 1);
|
||||||
|
|
||||||
|
#ifndef G_OS_WIN32
|
||||||
|
client->channel = g_io_channel_unix_new(fd);
|
||||||
|
#else
|
||||||
|
client->channel = g_io_channel_win32_new_socket(fd);
|
||||||
|
#endif
|
||||||
|
/* GLib is responsible for closing the file descriptor */
|
||||||
|
g_io_channel_set_close_on_unref(client->channel, true);
|
||||||
|
/* NULL encoding means the stream is binary safe; the MPD
|
||||||
|
protocol is UTF-8 only, but we are doing this call anyway
|
||||||
|
to prevent GLib from messing around with the stream */
|
||||||
|
g_io_channel_set_encoding(client->channel, NULL, NULL);
|
||||||
|
/* we prefer to do buffering */
|
||||||
|
g_io_channel_set_buffered(client->channel, false);
|
||||||
|
|
||||||
|
client->source_id = g_io_add_watch(client->channel,
|
||||||
|
G_IO_IN|G_IO_ERR|G_IO_HUP,
|
||||||
|
client_in_event, client);
|
||||||
|
|
||||||
|
client->input = fifo_buffer_new(4096);
|
||||||
|
|
||||||
|
client->permission = getDefaultPermissions();
|
||||||
|
client->uid = uid;
|
||||||
|
|
||||||
|
client->last_activity = g_timer_new();
|
||||||
|
|
||||||
|
client->cmd_list = NULL;
|
||||||
|
client->cmd_list_OK = -1;
|
||||||
|
client->cmd_list_size = 0;
|
||||||
|
|
||||||
|
client->deferred_send = g_queue_new();
|
||||||
|
client->deferred_bytes = 0;
|
||||||
|
client->num = next_client_num++;
|
||||||
|
|
||||||
|
client->send_buf_used = 0;
|
||||||
|
|
||||||
|
(void)write(fd, GREETING, sizeof(GREETING) - 1);
|
||||||
|
|
||||||
|
client_list_add(client);
|
||||||
|
|
||||||
|
remote = sockaddr_to_string(sa, sa_length, NULL);
|
||||||
|
g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
|
||||||
|
"[%u] opened from %s", client->num, remote);
|
||||||
|
g_free(remote);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
deferred_buffer_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
|
||||||
|
{
|
||||||
|
struct deferred_buffer *buffer = data;
|
||||||
|
g_free(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
client_close(struct client *client)
|
||||||
|
{
|
||||||
|
client_list_remove(client);
|
||||||
|
|
||||||
|
client_set_expired(client);
|
||||||
|
|
||||||
|
g_timer_destroy(client->last_activity);
|
||||||
|
|
||||||
|
if (client->cmd_list) {
|
||||||
|
free_cmd_list(client->cmd_list);
|
||||||
|
client->cmd_list = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_queue_foreach(client->deferred_send, deferred_buffer_free, NULL);
|
||||||
|
g_queue_free(client->deferred_send);
|
||||||
|
|
||||||
|
fifo_buffer_free(client->input);
|
||||||
|
|
||||||
|
g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
|
||||||
|
"[%u] closed", client->num);
|
||||||
|
g_free(client);
|
||||||
|
}
|
123
src/client_process.c
Normal file
123
src/client_process.c
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "client_internal.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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"
|
||||||
|
|
||||||
|
enum command_return
|
||||||
|
client_process_line(struct client *client, char *line)
|
||||||
|
{
|
||||||
|
enum command_return ret;
|
||||||
|
|
||||||
|
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 COMMAND_RETURN_OK;
|
||||||
|
} else if (client->idle_waiting) {
|
||||||
|
/* during idle mode, clients must not send anything
|
||||||
|
except "noidle" */
|
||||||
|
g_warning("[%u] command \"%s\" during idle",
|
||||||
|
client->num, line);
|
||||||
|
return COMMAND_RETURN_CLOSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client->cmd_list_OK >= 0) {
|
||||||
|
if (strcmp(line, CLIENT_LIST_MODE_END) == 0) {
|
||||||
|
g_debug("[%u] process command list",
|
||||||
|
client->num);
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
|
||||||
|
ret = command_process_list(client,
|
||||||
|
client->cmd_list_OK,
|
||||||
|
client->cmd_list);
|
||||||
|
g_debug("[%u] process command "
|
||||||
|
"list returned %i", client->num, ret);
|
||||||
|
|
||||||
|
if (ret == COMMAND_RETURN_CLOSE ||
|
||||||
|
client_is_expired(client))
|
||||||
|
return COMMAND_RETURN_CLOSE;
|
||||||
|
|
||||||
|
if (ret == COMMAND_RETURN_OK)
|
||||||
|
command_success(client);
|
||||||
|
|
||||||
|
client_write_output(client);
|
||||||
|
free_cmd_list(client->cmd_list);
|
||||||
|
client->cmd_list = NULL;
|
||||||
|
client->cmd_list_OK = -1;
|
||||||
|
} else {
|
||||||
|
size_t len = strlen(line) + 1;
|
||||||
|
client->cmd_list_size += len;
|
||||||
|
if (client->cmd_list_size >
|
||||||
|
client_max_command_list_size) {
|
||||||
|
g_warning("[%u] 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);
|
||||||
|
return COMMAND_RETURN_CLOSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_cmd_list_ptr(client, line);
|
||||||
|
ret = COMMAND_RETURN_OK;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) {
|
||||||
|
client->cmd_list_OK = 0;
|
||||||
|
ret = COMMAND_RETURN_OK;
|
||||||
|
} else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) {
|
||||||
|
client->cmd_list_OK = 1;
|
||||||
|
ret = COMMAND_RETURN_OK;
|
||||||
|
} else {
|
||||||
|
g_debug("[%u] process command \"%s\"",
|
||||||
|
client->num, line);
|
||||||
|
ret = command_process(client, line);
|
||||||
|
g_debug("[%u] command returned %i",
|
||||||
|
client->num, ret);
|
||||||
|
|
||||||
|
if (ret == COMMAND_RETURN_CLOSE ||
|
||||||
|
client_is_expired(client))
|
||||||
|
return COMMAND_RETURN_CLOSE;
|
||||||
|
|
||||||
|
if (ret == COMMAND_RETURN_OK)
|
||||||
|
command_success(client);
|
||||||
|
|
||||||
|
client_write_output(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
112
src/client_read.c
Normal file
112
src/client_read.c
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "client_internal.h"
|
||||||
|
#include "fifo_buffer.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static char *
|
||||||
|
client_read_line(struct client *client)
|
||||||
|
{
|
||||||
|
const char *p, *newline;
|
||||||
|
size_t length;
|
||||||
|
char *line;
|
||||||
|
|
||||||
|
p = fifo_buffer_read(client->input, &length);
|
||||||
|
if (p == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
newline = memchr(p, '\n', length);
|
||||||
|
if (newline == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
line = g_strndup(p, newline - p);
|
||||||
|
fifo_buffer_consume(client->input, newline - p + 1);
|
||||||
|
|
||||||
|
return g_strchomp(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum command_return
|
||||||
|
client_input_received(struct client *client, size_t bytesRead)
|
||||||
|
{
|
||||||
|
char *line;
|
||||||
|
|
||||||
|
fifo_buffer_append(client->input, bytesRead);
|
||||||
|
|
||||||
|
/* process all lines */
|
||||||
|
|
||||||
|
while ((line = client_read_line(client)) != NULL) {
|
||||||
|
enum command_return ret = client_process_line(client, line);
|
||||||
|
g_free(line);
|
||||||
|
|
||||||
|
if (ret == COMMAND_RETURN_KILL ||
|
||||||
|
ret == COMMAND_RETURN_CLOSE)
|
||||||
|
return ret;
|
||||||
|
if (client_is_expired(client))
|
||||||
|
return COMMAND_RETURN_CLOSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return COMMAND_RETURN_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum command_return
|
||||||
|
client_read(struct client *client)
|
||||||
|
{
|
||||||
|
char *p;
|
||||||
|
size_t max_length;
|
||||||
|
GError *error = NULL;
|
||||||
|
GIOStatus status;
|
||||||
|
gsize bytes_read;
|
||||||
|
|
||||||
|
assert(client != NULL);
|
||||||
|
assert(client->channel != NULL);
|
||||||
|
|
||||||
|
p = fifo_buffer_write(client->input, &max_length);
|
||||||
|
if (p == NULL) {
|
||||||
|
g_warning("[%u] buffer overflow", client->num);
|
||||||
|
return COMMAND_RETURN_CLOSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = g_io_channel_read_chars(client->channel, p, max_length,
|
||||||
|
&bytes_read, &error);
|
||||||
|
switch (status) {
|
||||||
|
case G_IO_STATUS_NORMAL:
|
||||||
|
return client_input_received(client, bytes_read);
|
||||||
|
|
||||||
|
case G_IO_STATUS_AGAIN:
|
||||||
|
/* try again later, after select() */
|
||||||
|
return COMMAND_RETURN_OK;
|
||||||
|
|
||||||
|
case G_IO_STATUS_EOF:
|
||||||
|
/* peer disconnected */
|
||||||
|
return COMMAND_RETURN_CLOSE;
|
||||||
|
|
||||||
|
case G_IO_STATUS_ERROR:
|
||||||
|
/* I/O error */
|
||||||
|
g_warning("failed to read from client %d: %s",
|
||||||
|
client->num, error->message);
|
||||||
|
g_error_free(error);
|
||||||
|
return COMMAND_RETURN_CLOSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* unreachable */
|
||||||
|
return COMMAND_RETURN_CLOSE;
|
||||||
|
}
|
270
src/client_write.c
Normal file
270
src/client_write.c
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "client_internal.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
client_write_deferred_buffer(struct client *client,
|
||||||
|
const struct deferred_buffer *buffer)
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
GIOStatus status;
|
||||||
|
gsize bytes_written;
|
||||||
|
|
||||||
|
assert(client != NULL);
|
||||||
|
assert(client->channel != NULL);
|
||||||
|
assert(buffer != NULL);
|
||||||
|
|
||||||
|
status = g_io_channel_write_chars
|
||||||
|
(client->channel, buffer->data, buffer->size,
|
||||||
|
&bytes_written, &error);
|
||||||
|
switch (status) {
|
||||||
|
case G_IO_STATUS_NORMAL:
|
||||||
|
return bytes_written;
|
||||||
|
|
||||||
|
case G_IO_STATUS_AGAIN:
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case G_IO_STATUS_EOF:
|
||||||
|
/* client has disconnected */
|
||||||
|
|
||||||
|
client_set_expired(client);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case G_IO_STATUS_ERROR:
|
||||||
|
/* I/O error */
|
||||||
|
|
||||||
|
client_set_expired(client);
|
||||||
|
g_warning("failed to flush buffer for %i: %s",
|
||||||
|
client->num, error->message);
|
||||||
|
g_error_free(error);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* unreachable */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
client_write_deferred(struct client *client)
|
||||||
|
{
|
||||||
|
size_t ret;
|
||||||
|
|
||||||
|
while (!g_queue_is_empty(client->deferred_send)) {
|
||||||
|
struct deferred_buffer *buf =
|
||||||
|
g_queue_peek_head(client->deferred_send);
|
||||||
|
|
||||||
|
assert(buf->size > 0);
|
||||||
|
assert(buf->size <= client->deferred_bytes);
|
||||||
|
|
||||||
|
ret = client_write_deferred_buffer(client, buf);
|
||||||
|
if (ret == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (ret < buf->size) {
|
||||||
|
assert(client->deferred_bytes >= (size_t)ret);
|
||||||
|
client->deferred_bytes -= ret;
|
||||||
|
buf->size -= ret;
|
||||||
|
memmove(buf->data, buf->data + ret, buf->size);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
size_t decr = sizeof(*buf) -
|
||||||
|
sizeof(buf->data) + buf->size;
|
||||||
|
|
||||||
|
assert(client->deferred_bytes >= decr);
|
||||||
|
client->deferred_bytes -= decr;
|
||||||
|
g_free(buf);
|
||||||
|
g_queue_pop_head(client->deferred_send);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_timer_start(client->last_activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_queue_is_empty(client->deferred_send)) {
|
||||||
|
g_debug("[%u] buffer empty %lu", client->num,
|
||||||
|
(unsigned long)client->deferred_bytes);
|
||||||
|
assert(client->deferred_bytes == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void client_defer_output(struct client *client,
|
||||||
|
const void *data, size_t length)
|
||||||
|
{
|
||||||
|
size_t alloc;
|
||||||
|
struct deferred_buffer *buf;
|
||||||
|
|
||||||
|
assert(length > 0);
|
||||||
|
|
||||||
|
alloc = sizeof(*buf) - sizeof(buf->data) + length;
|
||||||
|
client->deferred_bytes += alloc;
|
||||||
|
if (client->deferred_bytes > client_max_output_buffer_size) {
|
||||||
|
g_warning("[%u] output buffer size (%lu) is "
|
||||||
|
"larger than the max (%lu)",
|
||||||
|
client->num,
|
||||||
|
(unsigned long)client->deferred_bytes,
|
||||||
|
(unsigned long)client_max_output_buffer_size);
|
||||||
|
/* cause client to close */
|
||||||
|
client_set_expired(client);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = g_malloc(alloc);
|
||||||
|
buf->size = length;
|
||||||
|
memcpy(buf->data, data, length);
|
||||||
|
|
||||||
|
g_queue_push_tail(client->deferred_send, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void client_write_direct(struct client *client,
|
||||||
|
const char *data, size_t length)
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
GIOStatus status;
|
||||||
|
gsize bytes_written;
|
||||||
|
|
||||||
|
assert(client != NULL);
|
||||||
|
assert(client->channel != NULL);
|
||||||
|
assert(data != NULL);
|
||||||
|
assert(length > 0);
|
||||||
|
assert(g_queue_is_empty(client->deferred_send));
|
||||||
|
|
||||||
|
status = g_io_channel_write_chars(client->channel, data, length,
|
||||||
|
&bytes_written, &error);
|
||||||
|
switch (status) {
|
||||||
|
case G_IO_STATUS_NORMAL:
|
||||||
|
case G_IO_STATUS_AGAIN:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case G_IO_STATUS_EOF:
|
||||||
|
/* client has disconnected */
|
||||||
|
|
||||||
|
client_set_expired(client);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case G_IO_STATUS_ERROR:
|
||||||
|
/* I/O error */
|
||||||
|
|
||||||
|
client_set_expired(client);
|
||||||
|
g_warning("failed to write to %i: %s",
|
||||||
|
client->num, error->message);
|
||||||
|
g_error_free(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes_written < length)
|
||||||
|
client_defer_output(client, data + bytes_written,
|
||||||
|
length - bytes_written);
|
||||||
|
|
||||||
|
if (!g_queue_is_empty(client->deferred_send))
|
||||||
|
g_debug("[%u] buffer created", client->num);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
client_write_output(struct client *client)
|
||||||
|
{
|
||||||
|
if (client_is_expired(client) || !client->send_buf_used)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!g_queue_is_empty(client->deferred_send)) {
|
||||||
|
client_defer_output(client, client->send_buf,
|
||||||
|
client->send_buf_used);
|
||||||
|
|
||||||
|
if (client_is_expired(client))
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* 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
|
||||||
|
client_write_direct(client, client->send_buf,
|
||||||
|
client->send_buf_used);
|
||||||
|
|
||||||
|
client->send_buf_used = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a block of data to the client.
|
||||||
|
*/
|
||||||
|
static 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)) {
|
||||||
|
size_t copylen;
|
||||||
|
|
||||||
|
assert(client->send_buf_used < sizeof(client->send_buf));
|
||||||
|
|
||||||
|
copylen = sizeof(client->send_buf) - client->send_buf_used;
|
||||||
|
if (copylen > buflen)
|
||||||
|
copylen = buflen;
|
||||||
|
|
||||||
|
memcpy(client->send_buf + client->send_buf_used, buffer,
|
||||||
|
copylen);
|
||||||
|
buflen -= copylen;
|
||||||
|
client->send_buf_used += copylen;
|
||||||
|
buffer += copylen;
|
||||||
|
if (client->send_buf_used >= sizeof(client->send_buf))
|
||||||
|
client_write_output(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void client_puts(struct client *client, const char *s)
|
||||||
|
{
|
||||||
|
client_write(client, s, strlen(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = g_malloc(length + 1);
|
||||||
|
vsnprintf(buffer, length + 1, fmt, args);
|
||||||
|
client_write(client, buffer, length);
|
||||||
|
g_free(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list args;
|
||||||
|
|
||||||
|
va_start(args, fmt);
|
||||||
|
client_vprintf(client, fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user