]> git.tuebingen.mpg.de Git - paraslash.git/commitdiff
Merge branch 'refs/heads/t/clean_server_exit'
authorAndre Noll <maan@tuebingen.mpg.de>
Sun, 20 May 2018 09:26:26 +0000 (11:26 +0200)
committerAndre Noll <maan@tuebingen.mpg.de>
Sun, 20 May 2018 09:33:27 +0000 (11:33 +0200)
This series removes many memory leaks of para_server by refactoring
the shutdown and signal handling code. Most of the leaks happen only
at shutdown and are hence harmless. But it is still good to plug
the leaks because this puts more focus on real memory leaks in the
valgrind output.

The merge conflicted rather badly due to the changes introduced with
the crypt branch that was merged last week. The resolution has been
thoroughly tested, though.

* refs/heads/t/clean_server_exit: (32 commits)
  command.c: Document return value of handle_connect().
  user_list: Make list head static.
  afs: Allow database switching on sighup.
  afs: Free current mood or playlist on exit.
  afs: Free status items on exit.
  afs: Shutdown signals on exit.
  server: Free parse result also in afs.
  afs: Deplete user list at startup.
  server: Free audio file header on exit.
  sender: Deplete ACLs on exit.
  Remove some unused includes from {dccp,http}_send.c.
  server: Make argument of user_list_init() constant.
  server: Deplete user list on exit.
  server: Combine user_list_init() and populate().
  server: Move para_fgets() to user_list.c.
  server: Initialize user list at compile time.
  server: Rename functions related to user lists.
  server: Constify return value of lookup_user().
  server: Let stat command handler perform cleanup on signals.
  server: Have afs process close the current mood on exit().
  ...

26 files changed:
Makefile.real
NEWS.md
acl.c
afs.c
afs.h
aft.c
close_on_fork.c
close_on_fork.h
command.c
command.h
daemon.c
daemon.h
dccp_send.c
error.h
fd.c
fd.h
http_send.c
send.h
send_common.c
server.c
server.h
udp_send.c
user_list.c
user_list.h
vss.c
vss.h

index 7648767732470d0bc0e10adb2a69e981f709fceb..dae48f0e7f85f43984c2be17cdb26d4b8945032d 100644 (file)
@@ -144,6 +144,7 @@ cc-option = $(shell \
 )
 
 STRICT_CFLAGS += $(call cc-option, -Wformat-signedness)
+STRICT_CFLAGS += $(call cc-option, -Wdiscarded-qualifiers)
 
 # To put more focus on warnings, be less verbose as default
 # Use 'make V=1' to see the full commands
diff --git a/NEWS.md b/NEWS.md
index 11f275da582470d97d442d07f9abf6c8a10ff097..d193a1a3072ebc0d71f53691fc589fba3643f04d 100644 (file)
--- a/NEWS.md
+++ b/NEWS.md
@@ -9,6 +9,14 @@ NEWS
   the geometry of the terminal changes.
 - Minor documentation improvements.
 - Improvements to the crypto subsystem.
+- The server subcommand "task" has been deprecated. It still works,
+  but prints nothing. It will be removed in the next major release.
+- Server log output is now serialized, avoiding issues with partial
+  lines.
+- It is now possible to switch to a different afs database by changing
+  the server configuration and sending SIGHUP to the server process.
+
+Download: [tarball](./releases/paraslash-git.tar.xz)
 
 ----------------------------------------
 0.6.1 (2017-09-23) "segmented iteration"
diff --git a/acl.c b/acl.c
index 10f56bf1bd90fd62e1bb129140365fbed3c2e136..2c90052658eda947ea586a55f292dc85f24e68f7 100644 (file)
--- a/acl.c
+++ b/acl.c
@@ -31,10 +31,12 @@ struct access_info {
 /**
  * Return true if addr_1 matches addr_2 in the first `netmask' bits.
  */
-static int v4_addr_match(uint32_t addr_1, uint32_t addr_2, uint8_t netmask)
+static bool v4_addr_match(uint32_t addr_1, uint32_t addr_2, uint8_t netmask)
 {
        uint32_t mask = ~0U;
 
+       if (netmask == 0) /* avoid 32-bit shift, which is undefined in C. */
+               return true;
        if (netmask < 32)
                mask <<= (32 - netmask);
        return (htonl(addr_1) & mask) == (htonl(addr_2) & mask);
@@ -99,14 +101,18 @@ static void acl_del_entry(struct list_head *acl, char *addr, unsigned netmask)
        struct access_info *ai, *tmp;
        struct in_addr to_delete;
 
+       PARA_NOTICE_LOG("removing entries matching %s/%u\n", addr, netmask);
        inet_pton(AF_INET, addr, &to_delete);
 
        list_for_each_entry_safe(ai, tmp, acl, node) {
-
                if (v4_addr_match(to_delete.s_addr, ai->addr.s_addr,
                                        PARA_MIN(netmask, ai->netmask))) {
-                       PARA_NOTICE_LOG("removing %s/%u from access list\n",
-                                       addr, ai->netmask);
+                       char dst[INET_ADDRSTRLEN + 1];
+                       const char *p = inet_ntop(AF_INET, &ai->addr.s_addr,
+                               dst, sizeof(dst));
+                       if (p)
+                               PARA_INFO_LOG("removing %s/%u\n", p,
+                                       ai->netmask);
                        list_del(&ai->node);
                        free(ai);
                }
diff --git a/afs.c b/afs.c
index 538606add432aef4710c09d5b2826df3d123c933..4fe2140be607effea9d5c0a17c1eec8f41011b59 100644 (file)
--- a/afs.c
+++ b/afs.c
@@ -638,16 +638,18 @@ static int setup_command_socket_or_die(void)
        return socket_fd;
 }
 
+static char *database_dir;
+
 static void close_afs_tables(void)
 {
        int i;
        PARA_NOTICE_LOG("closing afs_tables\n");
        for (i = 0; i < NUM_AFS_TABLES; i++)
                afs_tables[i].close();
+       free(database_dir);
+       database_dir = NULL;
 }
 
-static char *database_dir;
-
 static void get_database_dir(void)
 {
        if (!database_dir) {
@@ -1002,9 +1004,14 @@ __noreturn void afs_init(int socket_fd)
        }
        ret = schedule(&s);
        sched_shutdown(&s);
+       close_current_mood();
 out_close:
        close_afs_tables();
 out:
+       signal_shutdown(signal_task);
+       free_status_items();
+       free(current_mop);
+       free_lpr();
        if (ret < 0)
                PARA_EMERG_LOG("%s\n", para_strerror(-ret));
        exit(EXIT_FAILURE);
@@ -1016,6 +1023,7 @@ static int com_init_callback(struct afs_callback_arg *aca)
        int i, ret;
 
        close_afs_tables();
+       get_database_dir();
        for (i = 0; i < NUM_AFS_TABLES; i++) {
                struct afs_table *t = &afs_tables[i];
 
diff --git a/afs.h b/afs.h
index 8beca5aefcd0b9d06aed05e915877a2c5104a1a8..b0d283f626af87321bc405bceef8fa50036a9a59 100644 (file)
--- a/afs.h
+++ b/afs.h
@@ -262,6 +262,7 @@ int get_afhi_of_row(const struct osl_row *row, struct afh_info *afhi);
 int get_audio_file_path_of_row(const struct osl_row *row, char **path);
 int audio_file_loop(void *private_data, osl_rbtree_loop_func *func);
 int aft_check_callback(struct afs_callback_arg *aca);
+void free_status_items(void);
 
 /* playlist */
 int playlist_open(const char *name);
diff --git a/aft.c b/aft.c
index 8969e7ac207ac935a83e4a212ee07d09e7317bc1..15769d55498b9b5ad6aa11f86efdebde31afc417 100644 (file)
--- a/aft.c
+++ b/aft.c
@@ -982,6 +982,12 @@ out:
        WRITE_STATUS_ITEM(pb, SI_file_size, "%ld\n", statbuf.st_size / 1024);
 }
 
+void free_status_items(void)
+{
+       freep(&status_items);
+       freep(&parser_friendly_status_items);
+}
+
 static int make_status_items(void)
 {
        const struct lls_command *cmd = SERVER_CMD_CMD_PTR(LS);
@@ -991,6 +997,7 @@ static int make_status_items(void)
        time_t current_time;
        int ret;
 
+       free_status_items();
        if (!status_item_ls_data.path) /* no audio file open */
                return 0;
        ret = lls_parse(ARRAY_SIZE(argv), argv, cmd, &opts.lpr, NULL);
@@ -1000,23 +1007,20 @@ static int make_status_items(void)
        if (ret < 0)
                goto out;
        make_inode_status_items(&pb);
-       free(status_items);
        status_items = pb.buf;
 
        memset(&pb, 0, sizeof(pb));
        pb.max_size = shm_get_shmmax() - 1;
        pb.flags = PBF_SIZE_PREFIX;
        ret = print_list_item(&status_item_ls_data, &opts, &pb, current_time);
-       if (ret < 0) {
-               free(status_items);
-               status_items = NULL;
-               return ret;
-       }
+       if (ret < 0)
+               goto out;
        make_inode_status_items(&pb);
-       free(parser_friendly_status_items);
        parser_friendly_status_items = pb.buf;
        ret = 1;
 out:
+       if (ret < 0)
+               free_status_items();
        lls_free_parse_result(opts.lpr, cmd);
        return ret;
 }
index 7e0c8e6474b48a932ca0c107862391466415ec96..28c5eabb1ae9a4821b60b638be559e837dc54f36 100644 (file)
@@ -62,13 +62,7 @@ void del_close_on_fork_list(int fd)
        }
 }
 
-/**
- * Close all fds in the list and destroy all list entries.
- *
- * This function calls close(3) for each fd in the close-on-fork list
- * and empties the list afterwards.
- */
-void close_listed_fds(void)
+static void deplete_cof_list(bool close_fds)
 {
        struct close_on_fork *cof, *tmp;
 
@@ -76,8 +70,32 @@ void close_listed_fds(void)
                return;
        list_for_each_entry_safe(cof, tmp, &close_on_fork_list, node) {
                PARA_DEBUG_LOG("closing fd %d\n", cof->fd);
-               close(cof->fd);
+               if (close_fds)
+                       close(cof->fd);
                list_del(&cof->node);
                free(cof);
        }
 }
+
+/**
+ * Close all fds in the list and destroy all list entries.
+ *
+ * This function calls close(3) for each fd in the close-on-fork list
+ * and empties the list afterwards.
+ *
+ * \sa \ref deplete_close_on_fork_list().
+ */
+void close_listed_fds(void)
+{
+       deplete_cof_list(true);
+}
+
+/**
+ * Remove all listed fds from the close on fork list.
+ *
+ * This is like \ref close_listed_fds() but does not close the fds.
+ */
+void deplete_close_on_fork_list(void)
+{
+       deplete_cof_list(false);
+}
index 1bd0cd15f72ef129f83db2a1ab56400b2b1580df..0dcc2b6cd7c5337e053aff9abc4433fcd8039d07 100644 (file)
@@ -2,3 +2,4 @@
 void del_close_on_fork_list(int fd);
 void add_close_on_fork_list(int fd);
 void close_listed_fds(void);
+void deplete_close_on_fork_list(void);
index bfce809a310cbe7024386ed61a441f71d3cf4e82..25b92715497d74caa06d2d2b47a94ad473ce0ecd 100644 (file)
--- a/command.c
+++ b/command.c
@@ -48,11 +48,15 @@ static const char * const server_command_perms_txt[] = {LSG_SERVER_CMD_AUX_INFOS
 
 extern int mmd_mutex;
 extern struct misc_meta_data *mmd;
-extern struct sender senders[];
 int send_afs_status(struct command_context *cc, int parser_friendly);
+static bool subcmd_should_die;
 
-static void dummy(__a_unused int s)
+static void command_handler_sighandler(int s)
 {
+       if (s != SIGTERM)
+               return;
+       PARA_EMERG_LOG("terminating on signal %d\n", SIGTERM);
+       subcmd_should_die = true;
 }
 
 /*
@@ -235,10 +239,10 @@ static int check_sender_args(struct command_context *cc,
                return ret;
        }
        arg = lls_input(0, lpr);
-       for (i = 0; senders[i].name; i++)
-               if (!strcmp(senders[i].name, arg))
+       FOR_EACH_SENDER(i)
+               if (strcmp(senders[i]->name, arg) == 0)
                        break;
-       if (!senders[i].name)
+       if (!senders[i])
                return -E_COMMAND_SYNTAX;
        scd->sender_num = i;
        arg = lls_input(1, lpr);
@@ -248,7 +252,7 @@ static int check_sender_args(struct command_context *cc,
        if (i == NUM_SENDER_CMDS)
                return -E_COMMAND_SYNTAX;
        scd->cmd_num = i;
-       if (!senders[scd->sender_num].client_cmds[scd->cmd_num])
+       if (!senders[scd->sender_num]->client_cmds[scd->cmd_num])
                return -E_SENDER_CMD;
        switch (scd->cmd_num) {
        case SENDER_on:
@@ -330,10 +334,10 @@ static int com_sender(struct command_context *cc, struct lls_parse_result *lpr)
        struct sender_command_data scd;
 
        if (lls_num_inputs(lpr) == 0) {
-               for (i = 0; senders[i].name; i++) {
+               FOR_EACH_SENDER(i) {
                        char *tmp;
                        ret = xasprintf(&tmp, "%s%s\n", msg? msg : "",
-                               senders[i].name);
+                               senders[i]->name);
                        free(msg);
                        msg = tmp;
                }
@@ -344,17 +348,17 @@ static int com_sender(struct command_context *cc, struct lls_parse_result *lpr)
                if (scd.sender_num < 0)
                        return ret;
                if (strcmp(lls_input(1, lpr), "status") == 0)
-                       msg = senders[scd.sender_num].status();
+                       msg = senders[scd.sender_num]->status();
                else
-                       msg = senders[scd.sender_num].help();
+                       msg = senders[scd.sender_num]->help();
                return send_sb(&cc->scc, msg, strlen(msg), SBD_OUTPUT, false);
        }
 
        switch (scd.cmd_num) {
        case SENDER_add:
        case SENDER_delete:
-               assert(senders[scd.sender_num].resolve_target);
-               ret = senders[scd.sender_num].resolve_target(lls_input(2, lpr),
+               assert(senders[scd.sender_num]->resolve_target);
+               ret = senders[scd.sender_num]->resolve_target(lls_input(2, lpr),
                        &scd);
                if (ret < 0)
                        return ret;
@@ -493,9 +497,21 @@ static int com_stat(struct command_context *cc, struct lls_parse_result *lpr)
        bool parser_friendly = SERVER_CMD_OPT_GIVEN(STAT, PARSER_FRIENDLY,
                lpr) > 0;
        uint32_t num = SERVER_CMD_UINT32_VAL(STAT, NUM, lpr);
+       const struct timespec ts = {.tv_sec = 50, .tv_nsec = 0};
 
-       para_sigaction(SIGUSR1, dummy);
+       para_sigaction(SIGINT, SIG_IGN);
+       para_sigaction(SIGUSR1, command_handler_sighandler);
+       para_sigaction(SIGTERM, command_handler_sighandler);
+       /*
+        * Simply checking subcmd_should_die is racy because a signal may
+        * arrive after the check but before the subsequent call to sleep(3).
+        * If this happens, sleep(3) would not be interrupted by the signal.
+        * To avoid this we block SIGTERM here and allow it to arrive only
+        * while we sleep.
+        */
+       para_block_signal(SIGTERM);
        for (;;) {
+               sigset_t set;
                /*
                 * Copy the mmd structure to minimize the time we hold the mmd
                 * lock.
@@ -518,7 +534,15 @@ static int com_stat(struct command_context *cc, struct lls_parse_result *lpr)
                ret = 1;
                if (num > 0 && !--num)
                        goto out;
-               sleep(50);
+               sigemptyset(&set); /* empty set means: unblock all signals */
+               /*
+                * pselect(2) allows to atomically unblock signals, then go to
+                * sleep. Calling sigprocmask(2) followed by sleep(3) would
+                * open a race window similar to the one described above.
+                */
+               pselect(1, NULL, NULL, NULL, &ts, &set);
+               if (subcmd_should_die)
+                       goto out;
                ret = -E_SERVER_CRASH;
                if (getppid() == 1)
                        goto out;
@@ -758,7 +782,7 @@ struct connection_features {
        int dummy; /* none at the moment */
 };
 
-static int parse_auth_request(char *buf, int len, struct user **u,
+static int parse_auth_request(char *buf, int len, const struct user **u,
                struct connection_features *cf)
 {
        int ret;
@@ -792,7 +816,7 @@ static int parse_auth_request(char *buf, int len, struct user **u,
                }
        }
        PARA_DEBUG_LOG("received auth request for user %s\n", username);
-       *u = lookup_user(username);
+       *u = user_list_lookup(username);
        ret = 1;
 out:
        free_argv(features);
@@ -874,9 +898,11 @@ static int run_command(struct command_context *cc, struct iovec *iov)
  * the function if the connection was not authenticated when the timeout
  * expires.
  *
+ * \return Standard.
+ *
  * \sa alarm(2), \ref openssl.c, \ref crypt.h.
  */
-__noreturn void handle_connect(int fd)
+int handle_connect(int fd)
 {
        int ret;
        unsigned char rand_buf[APC_CHALLENGE_SIZE + 2 * SESSION_KEY_LEN];
@@ -986,5 +1012,5 @@ out:
        }
        sc_free(cc->scc.recv);
        sc_free(cc->scc.send);
-       exit(ret < 0? EXIT_FAILURE : EXIT_SUCCESS);
+       return ret;
 }
index 0265f056d999ff1322b639493ad5503bc847dd6e..a7fa4673c696fe52f79bcb17dd4ca2a10ae720f9 100644 (file)
--- a/command.h
+++ b/command.h
@@ -3,7 +3,7 @@
 /** Per connection data available to command handlers. */
 struct command_context {
        /** The paraslash user that executes this command. */
-       struct user *u;
+       const struct user *u;
        /** File descriptor and crypto keys. */
        struct stream_cipher_context scc;
 };
index ddfe680cebc63dc1c7af89fde333c665873765eb..bfa81487430f62dc7417db6ba6e0c6d53c008051 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -31,6 +31,12 @@ struct daemon {
        /** Used for colored log messages. */
        char log_colors[NUM_LOGLEVELS][COLOR_MAXLEN];
        char *old_cwd;
+       /*
+        * If these pointers are non-NULL, the functions are called from
+        * daemon_log() before and after writing each log message.
+        */
+       void (*pre_log_hook)(void);
+       void (*post_log_hook)(void);
 };
 
 static struct daemon the_daemon, *me = &the_daemon;
@@ -140,6 +146,12 @@ void daemon_set_loglevel(const char *loglevel)
        me->loglevel = ret;
 }
 
+void daemon_set_hooks(void (*pre_log_hook)(void), void (*post_log_hook)(void))
+{
+       me->pre_log_hook = pre_log_hook;
+       me->post_log_hook = post_log_hook;
+}
+
 /**
  * Set one of the daemon config flags.
  *
@@ -409,6 +421,8 @@ __printf_2_3 void daemon_log(int ll, const char* fmt,...)
                return;
 
        fp = me->logfile? me->logfile : stderr;
+       if (me->pre_log_hook)
+               me->pre_log_hook();
        color = daemon_test_flag(DF_COLOR_LOG)? me->log_colors[ll] : NULL;
        if (color)
                fprintf(fp, "%s", color);
@@ -441,4 +455,6 @@ __printf_2_3 void daemon_log(int ll, const char* fmt,...)
        va_end(argp);
        if (color)
                fprintf(fp, "%s", COLOR_RESET);
+       if (me->post_log_hook)
+               me->post_log_hook();
 }
index 989678df40e6b29a44ae8fb5491db05841d25bca..b530b0d76b48b673fd7ae7626c48a6dd91ab7751 100644 (file)
--- a/daemon.h
+++ b/daemon.h
@@ -11,6 +11,7 @@ void daemon_set_start_time(void);
 time_t daemon_get_uptime(const struct timeval *current_time);
 __malloc char *daemon_get_uptime_str(const struct timeval *current_time);
 void daemon_set_logfile(const char *logfile_name);
+void daemon_set_hooks(void (*pre_log_hook)(void), void (*post_log_hook)(void));
 void daemon_set_flag(unsigned flag);
 void daemon_set_loglevel(const char *loglevel);
 bool daemon_init_colors_or_die(int color_arg, int color_arg_auto,
index 69ba65f5ef864396e81a722d8e69817a0040166f..770ac601047a892025724bbb7e677a7d8bcaed18 100644 (file)
@@ -28,9 +28,6 @@
 #include "sched.h"
 #include "vss.h"
 #include "fd.h"
-#include "close_on_fork.h"
-#include "chunk_queue.h"
-#include "acl.h"
 
 static struct sender_status dccp_sender_status, *dss = &dccp_sender_status;
 
@@ -83,6 +80,12 @@ static void dccp_shutdown_clients(void)
                dccp_shutdown_client(sc);
 }
 
+static void dccp_shutdown(void)
+{
+       dccp_shutdown_clients();
+       generic_acl_deplete(&dss->acl);
+}
+
 /** * Obtain current MPS according to RFC 4340, sec. 14. */
 static int dccp_init_fec(struct sender_client *sc)
 {
@@ -215,32 +218,39 @@ static char *dccp_status(void)
        return result;
 }
 
-/**
- * The init function of the dccp sender.
- *
- * \param s pointer to the dccp sender struct.
- *
- * It initializes all function pointers of \a s and starts
- * listening on the given port.
+/*
+ * Initialize the client list and the access control list and listen on the
+ * dccp port.
  */
-void dccp_send_init(struct sender *s)
+static void dccp_send_init(void)
 {
-       s->status = dccp_status;
-       s->send = NULL;
-       s->pre_select = dccp_pre_select;
-       s->post_select = dccp_post_select;
-       s->shutdown_clients = dccp_shutdown_clients;
-       s->resolve_target = NULL;
-       s->help = generic_sender_help;
-       s->client_cmds[SENDER_on] = dccp_com_on;
-       s->client_cmds[SENDER_off] = dccp_com_off;
-       s->client_cmds[SENDER_deny] = dccp_com_deny;
-       s->client_cmds[SENDER_allow] = dccp_com_allow;
-       s->client_cmds[SENDER_add] = NULL;
-       s->client_cmds[SENDER_delete] = NULL;
-
        init_sender_status(dss, OPT_RESULT(DCCP_ACCESS),
                OPT_UINT32_VAL(DCCP_PORT), OPT_UINT32_VAL(DCCP_MAX_CLIENTS),
                OPT_GIVEN(DCCP_DEFAULT_DENY));
        generic_com_on(dss, IPPROTO_DCCP);
 }
+
+/**
+ * The DCCP sender.
+ *
+ * This sender offers congestion control not available in plain TCP. Most
+ * methods of the sender structure are implemented as simple wrappers for the
+ * generic functions defined in \ref send_common.c. Like UDP streams, DCCP
+ * streams are sent FEC-encoded.
+ */
+const struct sender dccp_sender = {
+       .name = "dccp",
+       .init = dccp_send_init,
+       .shutdown = dccp_shutdown,
+       .pre_select = dccp_pre_select,
+       .post_select = dccp_post_select,
+       .shutdown_clients = dccp_shutdown_clients,
+       .client_cmds = {
+               [SENDER_on] = dccp_com_on,
+               [SENDER_off] = dccp_com_off,
+               [SENDER_deny] = dccp_com_deny,
+               [SENDER_allow] = dccp_com_allow,
+       },
+       .help = generic_sender_help,
+       .status = dccp_status,
+};
diff --git a/error.h b/error.h
index 1904a6d657d3667329dd84b913ce76dd6c68a28c..02f42246c97c9fda934a3b675008566eec351bf3 100644 (file)
--- a/error.h
+++ b/error.h
@@ -70,6 +70,7 @@
        PARA_ERROR(BTR_EOF, "buffer tree: end of file"), \
        PARA_ERROR(BTR_NAVAIL, "btr node: value currently unavailable"), \
        PARA_ERROR(BTR_NO_CHILD, "btr node has no children"), \
+       PARA_ERROR(CHILD_CONTEXT, "now running in child context"), \
        PARA_ERROR(CHMOD, "failed to set socket mode"), \
        PARA_ERROR(CLIENT_SYNTAX, "syntax error"), \
        PARA_ERROR(CLIENT_WRITE, "client write error"), \
        PARA_ERROR(TARGET_EXISTS, "requested target is already present"),\
        PARA_ERROR(TARGET_NOT_FOUND, "requested target not found"), \
        PARA_ERROR(TASK_STARTED, "task started"), \
+       PARA_ERROR(DEADLY_SIGNAL, "termination request by signal"), \
        PARA_ERROR(TOO_MANY_CLIENTS, "maximal number of stat clients exceeded"), \
        PARA_ERROR(UCRED_PERM, "permission denied"), \
        PARA_ERROR(UDP_OVERRUN, "output buffer overrun"), \
diff --git a/fd.c b/fd.c
index 0347fd83b7f4340019811b853e7eab5e9dccce36..33891d2e6c9f1c3568428b1df93b4b92e295ef61 100644 (file)
--- a/fd.c
+++ b/fd.c
@@ -419,34 +419,6 @@ void para_fd_set(int fd, fd_set *fds, int *max_fileno)
        *max_fileno = PARA_MAX(*max_fileno, fd);
 }
 
-/**
- * Paraslash's wrapper for fgets(3).
- *
- * \param line Pointer to the buffer to store the line.
- * \param size The size of the buffer given by \a line.
- * \param f The stream to read from.
- *
- * \return Unlike the standard fgets() function, an integer value
- * is returned. On success, this function returns 1. On errors, -E_FGETS
- * is returned. A zero return value indicates an end of file condition.
- */
-__must_check int para_fgets(char *line, int size, FILE *f)
-{
-again:
-       if (fgets(line, size, f))
-               return 1;
-       if (feof(f))
-               return 0;
-       if (!ferror(f))
-               return -E_FGETS;
-       if (errno != EINTR) {
-               PARA_ERROR_LOG("%s\n", strerror(errno));
-               return -E_FGETS;
-       }
-       clearerr(f);
-       goto again;
-}
-
 /**
  * Paraslash's wrapper for mmap.
  *
diff --git a/fd.h b/fd.h
index 25eea8a27ceaaec24ac24a8e3ab210d5ff798b05..c9e79426f5c27ebf621367ce24b1c073c4e97e23 100644 (file)
--- a/fd.h
+++ b/fd.h
@@ -11,7 +11,6 @@ int para_select(int n, fd_set *readfds, fd_set *writefds,
 __must_check int mark_fd_nonblocking(int fd);
 __must_check int mark_fd_blocking(int fd);
 void para_fd_set(int fd, fd_set *fds, int *max_fileno);
-__must_check int para_fgets(char *line, int size, FILE *f);
 int para_mmap(size_t length, int prot, int flags, int fd, void *map);
 int para_open(const char *path, int flags, mode_t mode);
 int para_mkdir(const char *path, mode_t mode);
index 210f85ac63c9dd718a3f00e6a6e9790ffeeb5d96..4d612285aeff00f7d8d153be5c9f1743f3fa9425 100644 (file)
@@ -26,7 +26,6 @@
 #include "close_on_fork.h"
 #include "fd.h"
 #include "chunk_queue.h"
-#include "acl.h"
 
 /** Message sent to clients that do not send a valid get request. */
 #define HTTP_ERR_MSG "HTTP/1.0 400 Bad Request\n"
@@ -77,6 +76,12 @@ static void http_shutdown_clients(void)
        shutdown_clients(hss);
 }
 
+static void http_shutdown(void)
+{
+       http_shutdown_clients();
+       generic_acl_deplete(&hss->acl);
+}
+
 static int queue_chunk_or_shutdown(struct sender_client *sc,
                struct sender_status *ss, const char *buf, size_t num_bytes)
 {
@@ -236,30 +241,12 @@ static char *http_status(void)
        return generic_sender_status(hss, "http");
 }
 
-/**
- * The init function of the http sender.
- *
- * \param s Pointer to the http sender struct.
- *
- * It initializes all function pointers of \a s, the client list and the access
- * control list. If the autostart option was given, the tcp port is opened.
+/*
+ * Initialize the client list and the access control list, and optionally
+ * listen on the tcp port.
  */
-void http_send_init(struct sender *s)
+static void http_send_init(void)
 {
-       s->status = http_status;
-       s->send = http_send;
-       s->pre_select = http_pre_select;
-       s->post_select = http_post_select;
-       s->shutdown_clients = http_shutdown_clients;
-       s->resolve_target = NULL;
-       s->help = generic_sender_help;
-       s->client_cmds[SENDER_on] = http_com_on;
-       s->client_cmds[SENDER_off] = http_com_off;
-       s->client_cmds[SENDER_deny] = http_com_deny;
-       s->client_cmds[SENDER_allow] = http_com_allow;
-       s->client_cmds[SENDER_add] = NULL;
-       s->client_cmds[SENDER_delete] = NULL;
-
        init_sender_status(hss, OPT_RESULT(HTTP_ACCESS),
                OPT_UINT32_VAL(HTTP_PORT), OPT_UINT32_VAL(HTTP_MAX_CLIENTS),
                OPT_GIVEN(HTTP_DEFAULT_DENY));
@@ -267,3 +254,31 @@ void http_send_init(struct sender *s)
                return;
        generic_com_on(hss, IPPROTO_TCP);
 }
+
+/**
+ * The HTTP sender.
+ *
+ * This is the only sender which does not FEC-encode the stream. This is not
+ * necessary because HTTP sits on top of TCP, a reliable transport which
+ * retransmits lost packets automatically. The sender employs per-client queues
+ * which queue chunks of audio data if they can not be sent immediately because
+ * the write operation would block. Most methods of the sender are implemented
+ * as wrappers for the generic functions defined in \ref send_common.c.
+ */
+const struct sender http_sender = {
+       .name = "http",
+       .init = http_send_init,
+       .shutdown = http_shutdown,
+       .pre_select = http_pre_select,
+       .post_select = http_post_select,
+       .send = http_send,
+       .shutdown_clients = http_shutdown_clients,
+       .client_cmds = {
+               [SENDER_on] = http_com_on,
+               [SENDER_off] = http_com_off,
+               [SENDER_deny] = http_com_deny,
+               [SENDER_allow] = http_com_allow,
+       },
+       .help = generic_sender_help,
+       .status = http_status,
+};
diff --git a/send.h b/send.h
index 84f35f9228cd05c62f9430063123d3e49761a06c..7a4c01bcb6cc1c80c77e06bc14b164dce8b3304d 100644 (file)
--- a/send.h
+++ b/send.h
@@ -27,14 +27,11 @@ struct sender {
        /** The name of the sender. */
        const char *name;
        /**
-        * The init function of this sender.
-        *
-        * It must fill in all function pointers of \a s as well as the \a
-        * client_cmds array, see below. It should also do all necessary
-        * preparations to init this sending facility, for example it could
-        * open a tcp port.
+        * Parse the command line options and initialize this sender (e.g.,
+        * initialize target or access control lists, listen on a network
+        * socket, etc.).
         */
-       void (*init)(struct sender *s);
+       void (*init)(void);
        /**
         * Return the help text of this sender.
         *
@@ -88,6 +85,8 @@ struct sender {
         * the clients aware of the end-of-file condition.
         */
        void (*shutdown_clients)(void);
+       /** Dellocate all resources. Only called on exit. */
+       void (*shutdown)(void);
        /**
         * Array of function pointers for the sender subcommands.
         *
@@ -107,6 +106,11 @@ struct sender {
        int (*resolve_target)(const char *, struct sender_command_data *);
 };
 
+/** NULL-terminated list, defined in \ref vss.c. */
+extern const struct sender * const senders[];
+/** Iterate over all senders. */
+#define FOR_EACH_SENDER(_i) for ((_i) = 0; senders[(_i)]; (_i)++)
+
 /** Describes one client, connected to a paraslash sender. */
 struct sender_client {
        /** The file descriptor of the client. */
@@ -187,6 +191,7 @@ void generic_com_allow(struct sender_command_data *scd,
 void generic_com_deny(struct sender_command_data *scd,
                struct sender_status *ss);
 void generic_com_on(struct sender_status *ss, unsigned protocol);
+void generic_acl_deplete(struct list_head *acl);
 void generic_com_off(struct sender_status *ss);
 char *generic_sender_help(void);
 struct sender_client *accept_sender_client(struct sender_status *ss, fd_set *rfds);
index 16820a5380395fabd841287448108a346a2fb4ca..61a12c827342758fa97949db669872e8b2d504b8 100644 (file)
@@ -46,8 +46,10 @@ void shutdown_client(struct sender_client *sc, struct sender_status *ss)
 {
        PARA_INFO_LOG("shutting down %s on fd %d\n", sc->name, sc->fd);
        free(sc->name);
-       close(sc->fd);
-       del_close_on_fork_list(sc->fd);
+       if (!process_is_command_handler()) {
+               close(sc->fd);
+               del_close_on_fork_list(sc->fd);
+       }
        cq_destroy(sc->cq);
        list_del(&sc->node);
        free(sc->private_data);
@@ -188,6 +190,22 @@ void generic_com_allow(struct sender_command_data *scd,
        acl_allow(scd->host, scd->netmask, &ss->acl, ss->default_deny);
 }
 
+/**
+ * Empty the access control list of a sender.
+ *
+ * \param acl The access control list of the sender.
+ *
+ * This is called from the ->shutdown methods of the http and the dccp sender.
+ */
+void generic_acl_deplete(struct list_head *acl)
+{
+       /*
+        * Since default_deny is false, the ACL is considered a blacklist. A
+        * netmask of zero matches any IP address, so this call empties the ACL.
+        */
+       acl_allow("0.0.0.0", 0 /* netmask */, acl, 0 /* default_deny */);
+}
+
 /**
  * Deny connections from the given range of IP addresses.
  *
index 011367e3481af17e10f6390fb36192a136c271fd..f19fc996ecc7355ddf24379e242fcec4da287dd6 100644 (file)
--- a/server.c
+++ b/server.c
@@ -100,16 +100,39 @@ 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;
+       /* 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. */
@@ -131,9 +154,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;
@@ -152,6 +183,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;
@@ -161,6 +196,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);
@@ -246,7 +283,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);
@@ -274,8 +311,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;
@@ -285,7 +326,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)
@@ -301,27 +342,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;
 }
@@ -353,12 +389,14 @@ static void command_pre_select(struct sched *s, void *context)
 static int command_post_select(struct sched *s, void *context)
 {
        struct server_command_task *sct = context;
-
        int new_fd, ret, i;
        char *peer_name;
        pid_t child_pid;
        uint32_t *chunk_table;
 
+       ret = task_get_notification(sct->task);
+       if (ret < 0)
+               return ret;
        ret = para_accept(sct->listen_fd, &s->rfds, NULL, 0, &new_fd);
        if (ret <= 0)
                goto out;
@@ -391,9 +429,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
@@ -402,21 +438,34 @@ 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 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;
 
        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));
@@ -457,6 +506,7 @@ static int init_afs(int argc, char **argv)
 
                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;
@@ -494,7 +544,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;
@@ -514,10 +564,12 @@ static void server_init(int argc, char **argv)
        /* become daemon */
        if (OPT_GIVEN(DAEMON))
                daemon_pipe = daemonize(true /* parent waits for SIGTERM */);
+       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();
 
@@ -540,7 +592,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));
@@ -592,6 +644,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.
  *
@@ -603,19 +670,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);
        crypt_shutdown();
-       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));
+       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);
 }
index 988a98d8e17723817907787016cf9b58e771f545..da75d86bdf191b130d02da12f49172ac5e0482d7 100644 (file)
--- a/server.h
+++ b/server.h
@@ -109,6 +109,8 @@ extern struct lls_parse_result *server_lpr;
 #define ENUM_STRING_VAL(_name) (lls_enum_string_val(OPT_UINT32_VAL(_name), \
        lls_opt(LSG_SERVER_PARA_SERVER_OPT_ ## _name, CMD_PTR)))
 
-__noreturn void handle_connect(int fd);
+int handle_connect(int fd);
 void parse_config_or_die(bool reload);
 char *server_get_tasks(void);
+bool process_is_command_handler(void);
+void free_lpr(void);
index 38d49e3e70e42bc72d9316a21a5ca5e5441106f3..04e2982f86bf125c5b4ce8ab2d6daa9280ea0bce 100644 (file)
@@ -72,8 +72,11 @@ static void udp_delete_target(struct sender_client *sc, const char *msg)
 
        PARA_NOTICE_LOG("deleting %s (%s) from list\n", sc->name, msg);
        udp_close_target(sc);
-       close(sc->fd);
-       del_close_on_fork_list(sc->fd);
+       /* command handlers already called close_listed_fds() */
+       if (!process_is_command_handler()) {
+               close(sc->fd);
+               del_close_on_fork_list(sc->fd);
+       }
        vss_del_fec_client(ut->fc);
        list_del(&sc->node);
        free(sc->name);
@@ -164,6 +167,13 @@ static void udp_shutdown_targets(void)
                udp_close_target(sc);
 }
 
+static void udp_shutdown(void)
+{
+       struct sender_client *sc, *tmp;
+       list_for_each_entry_safe(sc, tmp, &targets, node)
+               udp_delete_target(sc, "shutdown");
+}
+
 static int udp_resolve_target(const char *url, struct sender_command_data *scd)
 {
        const char *result;
@@ -412,32 +422,38 @@ static char *udp_help(void)
        );
 }
 
-/**
- * The init function of para_server's udp sender.
- *
- * \param s Pointer to the udp sender struct.
- *
- * It initializes all function pointers of \a s and the list of udp targets.
- */
-void udp_send_init(struct sender *s)
+/* Initialize the list of udp targets. */
+static void udp_send_init(void)
 {
        INIT_LIST_HEAD(&targets);
-       s->status = udp_status;
-       s->help = udp_help;
-       s->send = NULL;
-       s->pre_select = NULL;
-       s->post_select = NULL;
-       s->shutdown_clients = udp_shutdown_targets;
-       s->resolve_target = udp_resolve_target;
-       s->client_cmds[SENDER_on] = udp_com_on;
-       s->client_cmds[SENDER_off] = udp_com_off;
-       s->client_cmds[SENDER_deny] = NULL;
-       s->client_cmds[SENDER_allow] = NULL;
-       s->client_cmds[SENDER_add] = udp_com_add;
-       s->client_cmds[SENDER_delete] = udp_com_delete;
        sender_status = SENDER_off;
        udp_init_target_list();
        if (!OPT_GIVEN(UDP_NO_AUTOSTART))
                sender_status = SENDER_on;
        PARA_DEBUG_LOG("udp sender init complete\n");
 }
+
+/**
+ * The UDP sender.
+ *
+ * In contrast to the other senders the UDP sender is active in the sense that
+ * it initiates the network connection according to its list of targets rather
+ * than passively waiting for clients to connect. Like DCCP streams, UDP
+ * streams are always sent FEC-encoded. The UDP sender is the only sender which
+ * supports IP multicasting.
+ */
+const struct sender udp_sender = {
+       .name = "udp",
+       .init = udp_send_init,
+       .shutdown = udp_shutdown,
+       .shutdown_clients = udp_shutdown_targets,
+       .resolve_target = udp_resolve_target,
+       .client_cmds = {
+               [SENDER_on] = udp_com_on,
+               [SENDER_off] = udp_com_off,
+               [SENDER_add] = udp_com_add,
+               [SENDER_delete] = udp_com_delete,
+       },
+       .help = udp_help,
+       .status = udp_status,
+};
index 9db08715348f2e7df954d5371a106e4b5b347d40..32a4309d4360fa73a8e7d0bbef622a7928001bb0 100644 (file)
 #include "list.h"
 #include "user_list.h"
 
-static struct list_head user_list;
+static INITIALIZED_LIST_HEAD(user_list);
 
 /*
- * Fill the list of users known to para_server.
+ * Wrapper for fgets(3).
  *
- * Populates a linked list of all users in \a user_list_file.  Returns on
- * success, calls exit() on errors.
+ * Unlike fgets(3), an integer value is returned. On success, this function
+ * returns 1. On errors, -E_FGETS is returned. A zero return value indicates an
+ * end of file condition.
  */
-static void populate_user_list(char *user_list_file)
+static int xfgets(char *line, int size, FILE *f)
+{
+again:
+       if (fgets(line, size, f))
+               return 1;
+       if (feof(f))
+               return 0;
+       if (!ferror(f))
+               return -E_FGETS;
+       if (errno != EINTR) {
+               PARA_ERROR_LOG("%s\n", strerror(errno));
+               return -E_FGETS;
+       }
+       clearerr(f);
+       goto again;
+}
+
+/**
+ * Remove all entries from the user list.
+ *
+ * This is called on shutdown and when the user list is reloaded because the
+ * server received SIGHUP.
+ */
+void user_list_deplete(void)
+{
+       struct user *u, *tmpu;
+
+       list_for_each_entry_safe(u, tmpu, &user_list, node) {
+               list_del(&u->node);
+               free(u->name);
+               apc_free_pubkey(u->pubkey);
+               free(u);
+       }
+}
+
+/**
+ * Initialize the list of users allowed to connect to para_server.
+ *
+ * \param user_list_file The file containing access information.
+ *
+ * If this function is called for the second time, the contents of the
+ * previous call are discarded, i.e. the user list is reloaded.
+ *
+ * This function either succeeds or calls exit(3).
+ */
+void user_list_init(const char *user_list_file)
 {
        int ret = -E_USERLIST;
        FILE *file_ptr = fopen(user_list_file, "r");
+       struct user *u;
 
        if (!file_ptr)
                goto err;
+
+       user_list_deplete();
        for (;;) {
                int num;
                char line[255];
                /* keyword, name, key, perms */
                char w[255], n[255], k[255], p[255], tmp[4][255];
-               struct user *u;
                struct asymmetric_key *pubkey;
 
-               ret = para_fgets(line, sizeof(line), file_ptr);
+               ret = xfgets(line, sizeof(line), file_ptr);
                if (ret <= 0)
                        break;
                if (sscanf(line,"%200s %200s %200s %200s", w, n, k, p) < 3)
@@ -93,32 +141,6 @@ err:
        exit(EXIT_FAILURE);
 }
 
-/**
- * Initialize the list of users allowed to connect to para_server.
- *
- * \param user_list_file The file containing access information.
- *
- * If this function is called for the second time, the contents of the
- * previous call are discarded, i.e. the user list is reloaded.
- */
-void init_user_list(char *user_list_file)
-{
-       struct user *u, *tmp;
-       static int initialized;
-
-       if (initialized) {
-               list_for_each_entry_safe(u, tmp, &user_list, node) {
-                       list_del(&u->node);
-                       free(u->name);
-                       apc_free_pubkey(u->pubkey);
-                       free(u);
-               }
-       } else
-               INIT_LIST_HEAD(&user_list);
-       initialized = 1;
-       populate_user_list(user_list_file);
-}
-
 /**
  * Lookup a user in the user list.
  *
@@ -127,9 +149,9 @@ void init_user_list(char *user_list_file)
  * \return A pointer to the corresponding user struct if the user was found, \p
  * NULL otherwise.
  */
-struct user *lookup_user(const char *name)
+const struct user *user_list_lookup(const char *name)
 {
-       struct user *u;
+       const struct user *u;
        list_for_each_entry(u, &user_list, node) {
                if (strcmp(u->name, name))
                        continue;
index 41fad16464ae10eaea54f21ef5f2ec5bad6ce6ec..1cb94764cca42341e9a48b576491e12a5630d888 100644 (file)
@@ -32,5 +32,6 @@ struct user {
        unsigned int perms;
 };
 
-void init_user_list(char *user_list_file);
-struct user *lookup_user(const char *name);
+void user_list_init(const char *user_list_file);
+void user_list_deplete(void);
+const struct user *user_list_lookup(const char *name);
diff --git a/vss.c b/vss.c
index 8b850fe1e56c27d3cebda6db2d0eb6984575c20e..2cd0a1632922b0b2272aa183c0ddba8c4b4a3d30 100644 (file)
--- a/vss.c
+++ b/vss.c
@@ -40,24 +40,9 @@ extern void dccp_send_init(struct sender *);
 extern void http_send_init(struct sender *);
 extern void udp_send_init(struct sender *);
 
-/** The list of supported senders. */
-struct sender senders[] = {
-       {
-               .name = "http",
-               .init = http_send_init,
-       },
-       {
-               .name = "dccp",
-               .init = dccp_send_init,
-       },
-       {
-               .name = "udp",
-               .init = udp_send_init,
-       },
-       {
-               .name = NULL,
-       }
-};
+extern const struct sender udp_sender, dccp_sender, http_sender;
+const struct sender * const senders[] = {
+       &http_sender, &dccp_sender, &udp_sender, NULL};
 
 /** The possible states of the afs socket. */
 enum afs_socket_status {
@@ -918,10 +903,10 @@ static void vss_pre_select(struct sched *s, void *context)
                vsst->afsss = AFS_SOCKET_CHECK_FOR_WRITE;
        } else
                para_fd_set(vsst->afs_socket, &s->rfds, &s->max_fileno);
-       for (i = 0; senders[i].name; i++) {
-               if (!senders[i].pre_select)
+       FOR_EACH_SENDER(i) {
+               if (!senders[i]->pre_select)
                        continue;
-               senders[i].pre_select(&s->max_fileno, &s->rfds, &s->wfds);
+               senders[i]->pre_select(&s->max_fileno, &s->rfds, &s->wfds);
        }
        vss_compute_timeout(s, vsst);
 }
@@ -1082,10 +1067,10 @@ static void vss_send(struct vss_task *vsst)
                 * We call ->send() even if len is zero because senders might
                 * have data queued which can be sent now.
                 */
-               for (i = 0; senders[i].name; i++) {
-                       if (!senders[i].send)
+               FOR_EACH_SENDER(i) {
+                       if (!senders[i]->send)
                                continue;
-                       senders[i].send(mmd->current_chunk, mmd->chunks_sent,
+                       senders[i]->send(mmd->current_chunk, mmd->chunks_sent,
                                buf, len, vsst->header_buf, vsst->header_len);
                }
        }
@@ -1098,12 +1083,17 @@ static int vss_post_select(struct sched *s, void *context)
        int ret, i;
        struct vss_task *vsst = context;
 
+       ret = task_get_notification(vsst->task);
+       if (ret < 0) {
+               afh_free_header(vsst->header_buf, mmd->afd.audio_format_id);
+               return ret;
+       }
        if (!vsst->map || vss_next() || vss_paused() || vss_repos()) {
                /* shut down senders and fec clients */
                struct fec_client *fc, *tmp;
-               for (i = 0; senders[i].name; i++)
-                       if (senders[i].shutdown_clients)
-                               senders[i].shutdown_clients();
+               FOR_EACH_SENDER(i)
+                       if (senders[i]->shutdown_clients)
+                               senders[i]->shutdown_clients();
                list_for_each_entry_safe(fc, tmp, &fec_client_list, node)
                        fc->state = FEC_STATE_NONE;
                mmd->stream_start.tv_sec = 0;
@@ -1129,8 +1119,8 @@ static int vss_post_select(struct sched *s, void *context)
                int num = mmd->sender_cmd_data.cmd_num,
                        sender_num = mmd->sender_cmd_data.sender_num;
 
-               if (senders[sender_num].client_cmds[num]) {
-                       ret = senders[sender_num].client_cmds[num]
+               if (senders[sender_num]->client_cmds[num]) {
+                       ret = senders[sender_num]->client_cmds[num]
                                (&mmd->sender_cmd_data);
                        if (ret < 0)
                                PARA_ERROR_LOG("%s\n", para_strerror(-ret));
@@ -1147,10 +1137,10 @@ static int vss_post_select(struct sched *s, void *context)
                else
                        vsst->afsss = AFS_SOCKET_AFD_PENDING;
        }
-       for (i = 0; senders[i].name; i++) {
-               if (!senders[i].post_select)
+       FOR_EACH_SENDER(i) {
+               if (!senders[i]->post_select)
                        continue;
-               senders[i].post_select(&s->rfds, &s->wfds);
+               senders[i]->post_select(&s->rfds, &s->wfds);
        }
        if ((vss_playing() && !(mmd->vss_status_flags & VSS_PLAYING)) ||
                        (vss_next() && vss_playing()))
@@ -1179,9 +1169,9 @@ void vss_init(int afs_socket, struct sched *s)
        ms2tv(announce_time, &vsst->announce_tv);
        PARA_INFO_LOG("announce timeval: %lums\n", tv2ms(&vsst->announce_tv));
        INIT_LIST_HEAD(&fec_client_list);
-       for (i = 0; senders[i].name; i++) {
-               PARA_NOTICE_LOG("initializing %s sender\n", senders[i].name);
-               senders[i].init(&senders[i]);
+       FOR_EACH_SENDER(i) {
+               PARA_NOTICE_LOG("initializing %s sender\n", senders[i]->name);
+               senders[i]->init();
        }
        mmd->sender_cmd_data.cmd_num = -1;
        if (OPT_GIVEN(AUTOPLAY)) {
@@ -1200,3 +1190,20 @@ void vss_init(int afs_socket, struct sched *s)
                .context = vsst,
        }, s);
 }
+
+/**
+ * Turn off the virtual streaming system.
+ *
+ * This is only executed on exit. It calls the ->shutdowwn method of all senders.
+ */
+void vss_shutdown(void)
+{
+       int i;
+
+       FOR_EACH_SENDER(i) {
+               if (!senders[i]->shutdown)
+                       continue;
+               PARA_NOTICE_LOG("shutting down %s sender\n", senders[i]->name);
+               senders[i]->shutdown();
+       }
+}
diff --git a/vss.h b/vss.h
index 5ebcc4e41644f82a02f4e1519057e2b738de2061..46bb0e7359bf82acc4736dbb061eda5d7faf24f8 100644 (file)
--- a/vss.h
+++ b/vss.h
@@ -9,6 +9,7 @@ unsigned int vss_repos(void);
 unsigned int vss_paused(void);
 unsigned int vss_stopped(void);
 struct timeval *vss_chunk_time(void);
+void vss_shutdown(void);
 
 /** Stop playing after current audio file. */
 #define VSS_NOMORE 1