Merge branch 'refs/heads/t/listen-address'
[paraslash.git] / server.c
index 2018b8eece1988206e2992f560b65fa73059ff5e..a344b77440751a6e7017d9d7e641390a36eb157f 100644 (file)
--- a/server.c
+++ b/server.c
@@ -45,6 +45,7 @@
 #include "afh.h"
 #include "string.h"
 #include "afs.h"
+#include "net.h"
 #include "server.h"
 #include "list.h"
 #include "send.h"
@@ -52,7 +53,6 @@
 #include "vss.h"
 #include "config.h"
 #include "close_on_fork.h"
-#include "net.h"
 #include "daemon.h"
 #include "ipc.h"
 #include "fd.h"
@@ -86,22 +86,54 @@ struct lls_parse_result *server_lpr = NULL;
 /* Command line options (no config file options). Used in handle_sighup(). */
 static struct lls_parse_result *cmdline_lpr;
 
-/** A random value used in child context for authentication. */
+/**
+ * A random number used to "authenticate" the afs connection.
+ *
+ * para_server picks this number by random before it forks the afs process. The
+ * command handlers know this number as well and write it to the afs socket,
+ * together with the id of the shared memory area which contains the payload of
+ * the afs command. A local process has to know this number to abuse the afs
+ * service provided by the local socket.
+ */
 uint32_t afs_socket_cookie;
 
 /** The mutex protecting the shared memory area containing the mmd struct. */
 int mmd_mutex;
 
+/* Serializes log output. */
+static int log_mutex;
+
 static struct sched sched;
 static struct signal_task *signal_task;
 
 /** The process id of the audio file selector process. */
 pid_t afs_pid = 0;
 
+/* The the main server process (parent of afs and the command handlers). */
+static pid_t server_pid;
+
+/**
+ * Tell whether the executing process is a command handler.
+ *
+ * Cleanup on exit must be performed differently for command handlers.
+ *
+ * \return True if the pid of the executing process is neither the server pid
+ * nor the afs pid.
+ */
+bool process_is_command_handler(void)
+{
+       pid_t pid = getpid();
+
+       return pid != afs_pid && pid != server_pid;
+}
+
 /** The task responsible for server command handling. */
 struct server_command_task {
-       /** TCP port on which para_server listens for connections. */
-       int listen_fd;
+       unsigned num_listen_fds; /* only one by default */
+       /** TCP socket(s) on which para_server listens for connections. */
+       int *listen_fds;
+       /* File descriptor for the accepted socket. */
+       int child_fd;
        /** Copied from para_server's main function. */
        int argc;
        /** Argument vector passed to para_server's main function. */
@@ -123,9 +155,17 @@ char *server_get_tasks(void)
        return get_task_list(&sched);
 }
 
-/*
- * setup shared memory area and get mutex for locking
- */
+static void pre_log_hook(void)
+{
+       mutex_lock(log_mutex);
+}
+
+static void post_log_hook(void)
+{
+       mutex_unlock(log_mutex);
+}
+
+/* Setup shared memory area and init mutexes */
 static void init_ipc_or_die(void)
 {
        void *shm;
@@ -144,6 +184,10 @@ static void init_ipc_or_die(void)
        if (ret < 0)
                goto err_out;
        mmd_mutex = ret;
+       ret = mutex_new();
+       if (ret < 0)
+               goto destroy_mmd_mutex;
+       log_mutex = ret;
 
        mmd->num_played = 0;
        mmd->num_commands = 0;
@@ -153,6 +197,8 @@ static void init_ipc_or_die(void)
        mmd->vss_status_flags = VSS_NEXT;
        mmd->new_vss_status_flags = VSS_NEXT;
        return;
+destroy_mmd_mutex:
+       mutex_destroy(mmd_mutex);
 err_out:
        PARA_EMERG_LOG("%s\n", para_strerror(-ret));
        exit(EXIT_FAILURE);
@@ -177,7 +223,6 @@ void parse_config_or_die(bool reload)
        struct lls_parse_result *cf_lpr, *merged_lpr;
        char *home = para_homedir();
 
-       daemon_close_log();
        if (OPT_GIVEN(CONFIG_FILE))
                cf = para_strdup(OPT_STRING_VAL(CONFIG_FILE));
        else
@@ -239,7 +284,7 @@ success:
                daemon_set_flag(DF_LOG_TIMING);
        daemon_set_priority(OPT_UINT32_VAL(PRIORITY));
        if (user_list_file)
-               init_user_list(user_list_file);
+               user_list_init(user_list_file);
        ret = 1;
 free_cf:
        free(cf);
@@ -267,8 +312,12 @@ static void handle_sighup(void)
 
 static int signal_post_select(struct sched *s, __a_unused void *context)
 {
-       int signum = para_next_signal(&s->rfds);
+       int ret, signum;
 
+       ret = task_get_notification(signal_task->task);
+       if (ret < 0)
+               return ret;
+       signum = para_next_signal(&s->rfds);
        switch (signum) {
        case 0:
                return 0;
@@ -278,7 +327,7 @@ static int signal_post_select(struct sched *s, __a_unused void *context)
        case SIGCHLD:
                for (;;) {
                        pid_t pid;
-                       int ret = para_reap_child(&pid);
+                       ret = para_reap_child(&pid);
                        if (ret <= 0)
                                break;
                        if (pid != afs_pid)
@@ -294,27 +343,22 @@ static int signal_post_select(struct sched *s, __a_unused void *context)
                PARA_EMERG_LOG("terminating on signal %d\n", signum);
                kill(0, SIGTERM);
                /*
-                * We must wait for afs because afs catches SIGINT/SIGTERM.
-                * Before reacting to the signal, afs might want to use the
+                * We must wait for all of our children to die. For the afs
+                * process or a command handler might want to use the
                 * shared memory area and the mmd mutex.  If we destroy this
                 * mutex too early and afs tries to lock the shared memory
                 * area, the call to mutex_lock() will fail and terminate the
                 * afs process. This leads to dirty osl tables.
-                *
-                * There's no such problem with the other children of the
-                * server process (the command handlers) as these reset their
-                * SIGINT/SIGTERM handlers to the default action, i.e.  these
-                * processes get killed immediately by the above kill().
                 */
-               PARA_INFO_LOG("waiting for afs (pid %d) to die\n",
-                       (int)afs_pid);
-               waitpid(afs_pid, NULL, 0);
+               PARA_INFO_LOG("waiting for child processes to die\n");
+               mutex_unlock(mmd_mutex);
+               while (wait(NULL) != -1 || errno != ECHILD)
+                       ; /* still at least one child alive */
+               mutex_lock(mmd_mutex);
 cleanup:
                free(mmd->afd.afhi.chunk_table);
-               close_listed_fds();
-               mutex_destroy(mmd_mutex);
-               shm_detach(mmd);
-               exit(EXIT_FAILURE);
+               task_notify_all(s, E_DEADLY_SIGNAL);
+               return -E_DEADLY_SIGNAL;
        }
        return 0;
 }
@@ -339,20 +383,22 @@ static void init_signal_task(void)
 
 static void command_pre_select(struct sched *s, void *context)
 {
+       unsigned n;
        struct server_command_task *sct = context;
-       para_fd_set(sct->listen_fd, &s->rfds, &s->max_fileno);
+
+       for (n = 0; n < sct->num_listen_fds; n++)
+               para_fd_set(sct->listen_fds[n], &s->rfds, &s->max_fileno);
 }
 
-static int command_post_select(struct sched *s, void *context)
+static int command_task_accept(unsigned listen_idx, struct sched *s,
+               struct server_command_task *sct)
 {
-       struct server_command_task *sct = context;
-
        int new_fd, ret, i;
        char *peer_name;
        pid_t child_pid;
        uint32_t *chunk_table;
 
-       ret = para_accept(sct->listen_fd, &s->rfds, NULL, 0, &new_fd);
+       ret = para_accept(sct->listen_fds[listen_idx], &s->rfds, NULL, 0, &new_fd);
        if (ret <= 0)
                goto out;
        mmd->num_connects++;
@@ -384,9 +430,7 @@ static int command_post_select(struct sched *s, void *context)
        PARA_INFO_LOG("accepted connection from %s\n", peer_name);
        /* mmd might already have changed at this point */
        free(chunk_table);
-       alarm(ALARM_TIMEOUT);
-       close_listed_fds();
-       signal_shutdown(signal_task);
+       sct->child_fd = new_fd;
        /*
         * put info on who we are serving into argv[0] to make
         * client ip visible in top/ps
@@ -395,31 +439,84 @@ static int command_post_select(struct sched *s, void *context)
                memset(sct->argv[i], 0, strlen(sct->argv[i]));
        i = sct->argc - 1 - lls_num_inputs(cmdline_lpr);
        sprintf(sct->argv[i], "para_server (serving %s)", peer_name);
-       handle_connect(new_fd);
-       /* never reached*/
+       /* ask other tasks to terminate */
+       task_notify_all(s, E_CHILD_CONTEXT);
+       /*
+        * After we return, the scheduler calls server_select() with a minimal
+        * timeout value, because the remaining tasks have a notification
+        * pending. Next it calls the ->post_select method of these tasks,
+        * which will return negative in view of the notification. This causes
+        * schedule() to return as there are no more runnable tasks.
+        *
+        * Note that semaphores are not inherited across a fork(), so we don't
+        * hold the lock at this point. Since server_select() drops the lock
+        * prior to calling para_select(), we need to acquire it here.
+        */
+       mutex_lock(mmd_mutex);
+       return -E_CHILD_CONTEXT;
 out:
        if (ret < 0)
                PARA_CRIT_LOG("%s\n", para_strerror(-ret));
        return 0;
 }
 
-static void init_server_command_task(int argc, char **argv)
+static int command_post_select(struct sched *s, void *context)
+{
+       struct server_command_task *sct = context;
+       unsigned n;
+       int ret;
+
+       ret = task_get_notification(sct->task);
+       if (ret < 0)
+               return ret;
+       for (n = 0; n < sct->num_listen_fds; n++) {
+               ret = command_task_accept(n, s, sct);
+               if (ret < 0) {
+                       free(sct->listen_fds);
+                       return ret;
+               }
+       }
+       return 0;
+}
+
+static void init_server_command_task(struct server_command_task *sct,
+               int argc, char **argv)
 {
        int ret;
-       static struct server_command_task server_command_task_struct,
-               *sct = &server_command_task_struct;
+       unsigned n;
+       uint32_t port = OPT_UINT32_VAL(PORT);
 
        PARA_NOTICE_LOG("initializing tcp command socket\n");
+       sct->child_fd = -1;
        sct->argc = argc;
        sct->argv = argv;
-       ret = para_listen_simple(IPPROTO_TCP, OPT_UINT32_VAL(PORT));
-       if (ret < 0)
-               goto err;
-       sct->listen_fd = ret;
-       ret = mark_fd_nonblocking(sct->listen_fd);
-       if (ret < 0)
-               goto err;
-       add_close_on_fork_list(sct->listen_fd); /* child doesn't need the listener */
+       if (!OPT_GIVEN(LISTEN_ADDRESS)) {
+               sct->num_listen_fds = 1;
+               sct->listen_fds = para_malloc(sizeof(int));
+               ret = para_listen_simple(IPPROTO_TCP, port);
+               if (ret < 0)
+                       goto err;
+               sct->listen_fds[0] = ret;
+       } else {
+               sct->num_listen_fds = OPT_GIVEN(LISTEN_ADDRESS);
+               sct->listen_fds = para_malloc(sct->num_listen_fds * sizeof(int));
+               for (n = 0; n < OPT_GIVEN(LISTEN_ADDRESS); n++) {
+                       const char *arg;
+                       arg = lls_string_val(n, OPT_RESULT(LISTEN_ADDRESS));
+                       ret = para_listen(IPPROTO_TCP, arg, port);
+                       if (ret < 0)
+                               goto err;
+                       sct->listen_fds[n] = ret;
+               }
+       }
+       for (n = 0; n < sct->num_listen_fds; n++) {
+               ret = mark_fd_nonblocking(sct->listen_fds[n]);
+               if (ret < 0)
+                       goto err;
+               /* child doesn't need the listener */
+               add_close_on_fork_list(sct->listen_fds[n]);
+       }
+
        sct->task = task_register(&(struct task_info) {
                .name = "server command",
                .pre_select = command_pre_select,
@@ -449,12 +546,14 @@ static int init_afs(int argc, char **argv)
                int i;
 
                afs_pid = getpid();
+               crypt_shutdown();
+               user_list_deplete();
                for (i = argc - 1; i >= 0; i--)
                        memset(argv[i], 0, strlen(argv[i]));
                i = argc - lls_num_inputs(cmdline_lpr) - 1;
                sprintf(argv[i], "para_server (afs)");
                close(afs_server_socket[0]);
-               afs_init(afs_socket_cookie, afs_server_socket[1]);
+               afs_init(afs_server_socket[1]);
        }
        close(afs_server_socket[1]);
        if (read(afs_server_socket[0], &c, 1) <= 0) {
@@ -486,7 +585,7 @@ static void handle_help_flags(void)
        exit(EXIT_SUCCESS);
 }
 
-static void server_init(int argc, char **argv)
+static void server_init(int argc, char **argv, struct server_command_task *sct)
 {
        int ret, afs_socket, daemon_pipe = -1;
        char *errctx;
@@ -506,10 +605,12 @@ static void server_init(int argc, char **argv)
        /* become daemon */
        if (OPT_GIVEN(DAEMON))
                daemon_pipe = daemonize(true /* parent waits for SIGTERM */);
-       init_random_seed_or_die();
+       server_pid = getpid();
+       crypt_init();
        daemon_log_welcome("server");
-       init_ipc_or_die(); /* init mmd struct and mmd->lock */
+       init_ipc_or_die(); /* init mmd struct, mmd and log mutex */
        daemon_set_start_time();
+       daemon_set_hooks(pre_log_hook, post_log_hook);
        PARA_NOTICE_LOG("initializing audio format handlers\n");
        afh_init();
 
@@ -532,7 +633,7 @@ static void server_init(int argc, char **argv)
        para_unblock_signal(SIGCHLD);
        PARA_NOTICE_LOG("initializing virtual streaming system\n");
        vss_init(afs_socket, &sched);
-       init_server_command_task(argc, argv);
+       init_server_command_task(sct, argc, argv);
        if (daemon_pipe >= 0) {
                if (write(daemon_pipe, "\0", 1) < 0) {
                        PARA_EMERG_LOG("daemon_pipe: %s", strerror(errno));
@@ -584,6 +685,21 @@ static int server_select(int max_fileno, fd_set *readfds, fd_set *writefds,
        return ret;
 }
 
+/**
+ * Deallocate all lopsub parse results.
+ *
+ * The server allocates a parse result for command line options and optionally
+ * a second parse result for the effective configuration, defined by merging
+ * the command line options with the options stored in the configuration file.
+ * This function frees both structures.
+ */
+void free_lpr(void)
+{
+       lls_free_parse_result(server_lpr, CMD_PTR);
+       if (server_lpr != cmdline_lpr)
+               lls_free_parse_result(cmdline_lpr, CMD_PTR);
+}
+
 /**
  * The main function of para_server.
  *
@@ -595,18 +711,38 @@ static int server_select(int max_fileno, fd_set *readfds, fd_set *writefds,
 int main(int argc, char *argv[])
 {
        int ret;
+       struct server_command_task server_command_task_struct,
+               *sct = &server_command_task_struct;
 
        sched.default_timeout.tv_sec = 1;
        sched.select_function = server_select;
 
-       server_init(argc, argv);
+       server_init(argc, argv, sct);
        mutex_lock(mmd_mutex);
        ret = schedule(&sched);
+       /*
+        * We hold the mmd lock: it was re-acquired in server_select()
+        * after the select call.
+        */
+       mutex_unlock(mmd_mutex);
        sched_shutdown(&sched);
-       lls_free_parse_result(server_lpr, CMD_PTR);
-       if (server_lpr != cmdline_lpr)
-               lls_free_parse_result(cmdline_lpr, CMD_PTR);
-       if (ret < 0)
-               PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+       crypt_shutdown();
+       signal_shutdown(signal_task);
+       if (!process_is_command_handler()) { /* parent (server) */
+               mutex_destroy(mmd_mutex);
+               daemon_set_hooks(NULL, NULL); /* only one process remaining */
+               mutex_destroy(log_mutex);
+               deplete_close_on_fork_list();
+               if (ret < 0)
+                       PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+       } else {
+               alarm(ALARM_TIMEOUT);
+               close_listed_fds();
+               ret = handle_connect(sct->child_fd);
+       }
+       vss_shutdown();
+       shm_detach(mmd);
+       user_list_deplete();
+       free_lpr();
        exit(ret < 0? EXIT_FAILURE : EXIT_SUCCESS);
 }