Add command line options --mood and --playlist.
[paraslash.git] / afs.c
diff --git a/afs.c b/afs.c
index f40e022..33bc6e2 100644 (file)
--- a/afs.c
+++ b/afs.c
@@ -6,6 +6,7 @@
 
 /** \file afs.c Paraslash's audio file selector. */
 
 
 /** \file afs.c Paraslash's audio file selector. */
 
+#include <signal.h>
 #include <fnmatch.h>
 #include "server.cmdline.h"
 #include "para.h"
 #include <fnmatch.h>
 #include "server.cmdline.h"
 #include "para.h"
@@ -473,22 +474,18 @@ static void play_loop(enum play_mode current_play_mode)
 static enum play_mode init_admissible_files(void)
 {
        int ret;
 static enum play_mode init_admissible_files(void)
 {
        int ret;
-       char *given_mood, *given_playlist;
 
 
-       given_mood = "mood_that_was_given_at_the_command_line";
-       given_playlist = "given_playlist";
-
-       if (given_mood) {
-               ret = change_current_mood(given_mood);
+       if (conf.mood_given) {
+               ret = change_current_mood(conf.mood_arg);
                if (ret >= 0) {
                if (ret >= 0) {
-                       if (given_playlist)
+                       if (conf.playlist_given)
                                PARA_WARNING_LOG("ignoring playlist %s\n",
                                PARA_WARNING_LOG("ignoring playlist %s\n",
-                                       given_playlist);
+                                       conf.playlist_arg);
                        return PLAY_MODE_MOOD;
                }
        }
                        return PLAY_MODE_MOOD;
                }
        }
-       if (given_playlist) {
-               ret = playlist_open(given_playlist);
+       if (conf.playlist_given) {
+               ret = playlist_open(conf.playlist_arg);
                if (ret >= 0)
                        return PLAY_MODE_PLAYLIST;
        }
                if (ret >= 0)
                        return PLAY_MODE_PLAYLIST;
        }
@@ -583,11 +580,23 @@ static void register_signal_task(void)
        register_task(&st->task);
 }
 
        register_task(&st->task);
 }
 
+static struct list_head afs_client_list;
+
+struct afs_client {
+       struct list_head node;
+       int fd;
+       struct timeval connect_time;
+};
+
 static void command_pre_select(struct sched *s, struct task *t)
 {
        struct command_task *ct = t->private_data;
 static void command_pre_select(struct sched *s, struct task *t)
 {
        struct command_task *ct = t->private_data;
-       t->ret = 1;
+       struct afs_client *client;
+
        para_fd_set(ct->fd, &s->rfds, &s->max_fileno);
        para_fd_set(ct->fd, &s->rfds, &s->max_fileno);
+       list_for_each_entry(client, &afs_client_list, node)
+               para_fd_set(client->fd, &s->rfds, &s->max_fileno);
+       t->ret = 1;
 }
 
 /*
 }
 
 /*
@@ -645,54 +654,75 @@ out:
        return ret;
 }
 
        return ret;
 }
 
-static void command_post_select(struct sched *s, struct task *t)
+static void execute_afs_command(int fd, uint32_t expected_cookie)
 {
 {
-       struct command_task *ct = t->private_data;
-       struct sockaddr_un unix_addr;
-       char buf[sizeof(uint32_t) + sizeof(int)];
        uint32_t cookie;
        uint32_t cookie;
-       int query_shmid, fd;
+       int query_shmid;
+       char buf[sizeof(cookie) + sizeof(query_shmid)];
+       int ret = recv_bin_buffer(fd, buf, sizeof(buf));
 
 
-       t->ret = 1;
-       if (!FD_ISSET(ct->fd, &s->rfds))
-               return;
-       t->ret = para_accept(ct->fd, &unix_addr, sizeof(unix_addr));
-       if (t->ret < 0)
+       if (ret < 0) {
+               PARA_NOTICE_LOG("%s\n", PARA_STRERROR(-ret));
                return;
                return;
-       /*
-        * The following errors may be caused by a malicious local user. So do
-        * not return an error in this case as this would terminate  para_afs
-        * and para_server.
-        */
-       fd = t->ret;
-       /* FIXME: This is easily dosable (peer doesn't send data) */
-       t->ret = recv_bin_buffer(fd, buf, sizeof(buf));
-       if (t->ret < 0) {
-               PARA_NOTICE_LOG("%s (%d)\n", PARA_STRERROR(-t->ret), t->ret);
-               goto out;
        }
        }
-       if (t->ret != sizeof(buf)) {
+       if (ret != sizeof(buf)) {
                PARA_NOTICE_LOG("short read (%d bytes, expected %lu)\n",
                PARA_NOTICE_LOG("short read (%d bytes, expected %lu)\n",
-                       t->ret, (long unsigned) sizeof(buf));
-               goto out;
+                       ret, (long unsigned) sizeof(buf));
+               return;
        }
        cookie = *(uint32_t *)buf;
        }
        cookie = *(uint32_t *)buf;
-       if (cookie != ct->cookie) {
+       if (cookie != expected_cookie) {
                PARA_NOTICE_LOG("received invalid cookie(got %u, expected %u)\n",
                PARA_NOTICE_LOG("received invalid cookie(got %u, expected %u)\n",
-                       (unsigned)cookie, (unsigned)ct->cookie);
-               goto out;
+                       (unsigned)cookie, (unsigned)expected_cookie);
+               return;
        }
        query_shmid = *(int *)(buf + sizeof(cookie));
        if (query_shmid < 0) {
                PARA_WARNING_LOG("received invalid query shmid %d)\n",
                        query_shmid);
        }
        query_shmid = *(int *)(buf + sizeof(cookie));
        if (query_shmid < 0) {
                PARA_WARNING_LOG("received invalid query shmid %d)\n",
                        query_shmid);
-               goto out;
+               return;
        }
        /* Ignore return value: Errors might be ok here. */
        call_callback(fd, query_shmid);
        }
        /* Ignore return value: Errors might be ok here. */
        call_callback(fd, query_shmid);
+}
+
+#define AFS_CLIENT_TIMEOUT 3
+
+static void command_post_select(struct sched *s, struct task *t)
+{
+       struct command_task *ct = t->private_data;
+       struct sockaddr_un unix_addr;
+       struct afs_client *client, *tmp;
+
+       /* First, check the list of connected clients. */
+       list_for_each_entry_safe(client, tmp, &afs_client_list, node) {
+               if (FD_ISSET(client->fd, &s->rfds))
+                       execute_afs_command(client->fd, ct->cookie);
+               else { /* prevent bogus connection flodding */
+                       struct timeval diff;
+                       tv_diff(now, &client->connect_time, &diff);
+                       if (diff.tv_sec < AFS_CLIENT_TIMEOUT)
+                               continue;
+                       PARA_WARNING_LOG("connection timeout\n");
+               }
+               close(client->fd);
+               list_del(&client->node);
+               free(client);
+       }
+       /* Next, accept connections on the local socket. */
+       if (!FD_ISSET(ct->fd, &s->rfds))
+               goto out;
+       t->ret = para_accept(ct->fd, &unix_addr, sizeof(unix_addr));
+       if (t->ret < 0) {
+               PARA_NOTICE_LOG("%s\n", PARA_STRERROR(-t->ret));
+               goto out;
+       }
+       client = para_malloc(sizeof(*client));
+       client->fd = t->ret;
+       client->connect_time = *now;
+       para_list_add(&client->node, &afs_client_list);
 out:
        t->ret = 1;
 out:
        t->ret = 1;
-       close(fd);
 }
 
 static void register_command_task(uint32_t cookie)
 }
 
 static void register_command_task(uint32_t cookie)
@@ -787,7 +817,10 @@ __noreturn int afs_init(uint32_t cookie, int socket_fd)
 {
        enum play_mode current_play_mode;
        struct sched s;
 {
        enum play_mode current_play_mode;
        struct sched s;
-       int ret = open_afs_tables();
+       int ret;
+
+       INIT_LIST_HEAD(&afs_client_list);
+       ret = open_afs_tables();
 
        if (ret < 0) {
                PARA_EMERG_LOG("%s\n", PARA_STRERROR(-ret));
 
        if (ret < 0) {
                PARA_EMERG_LOG("%s\n", PARA_STRERROR(-ret));