]> 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().
  ...

15 files changed:
1  2 
NEWS.md
afs.c
afs.h
aft.c
command.c
dccp_send.c
fd.c
http_send.c
send.h
send_common.c
server.c
server.h
udp_send.c
user_list.c
vss.c

diff --combined NEWS.md
index 11f275da582470d97d442d07f9abf6c8a10ff097,d0bd6585a3a2f04be5ec262d580bfb8800733498..d193a1a3072ebc0d71f53691fc589fba3643f04d
+++ b/NEWS.md
@@@ -1,15 -1,6 +1,23 @@@
  NEWS
  ====
  
 +-------------------------------------------
 +0.6.2 (to be accounced) "elastic diversity"
 +-------------------------------------------
 +
 +- para_gui no longer waits up to one second to update the screen when
 +  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 --combined afs.c
index 538606add432aef4710c09d5b2826df3d123c933,00143799c1c729cfd6fb744a204b46a76e2fee3e..4fe2140be607effea9d5c0a17c1eec8f41011b59
--- 1/afs.c
--- 2/afs.c
+++ b/afs.c
@@@ -22,8 -22,8 +22,8 @@@
  #include "string.h"
  #include "afh.h"
  #include "afs.h"
 -#include "server.h"
  #include "net.h"
 +#include "server.h"
  #include "ipc.h"
  #include "list.h"
  #include "sched.h"
@@@ -638,16 -638,18 +638,18 @@@ static int setup_command_socket_or_die(
        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) {
@@@ -685,7 -687,8 +687,7 @@@ static int open_afs_tables(void
                ret = afs_tables[i].open(database_dir);
                if (ret >= 0)
                        continue;
 -              PARA_ERROR_LOG("%s open: %s\n", afs_tables[i].name,
 -                      para_strerror(-ret));
 +              PARA_ERROR_LOG("could not open %s\n", afs_tables[i].name);
                break;
        }
        if (ret >= 0)
@@@ -1002,9 -1005,14 +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 -1024,7 +1023,7 @@@ static int com_init_callback(struct afs
        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 --combined afs.h
index 8beca5aefcd0b9d06aed05e915877a2c5104a1a8,0bdd76d0f6d8be82c7a0cdb1579c94d7622fb44b..b0d283f626af87321bc405bceef8fa50036a9a59
--- 1/afs.h
--- 2/afs.h
+++ b/afs.h
@@@ -161,14 -161,6 +161,14 @@@ struct afs_callback_arg 
        struct osl_object query;
        /** Will be written on band SBD_OUTPUT, fully buffered. */
        struct para_buffer pbout;
 +      /**
 +       * Convenience pointer for the deserialized parse result.
 +       *
 +       * Most afs command handlers call \ref send_lls_callback_request() to
 +       * serialize the parse result of the subcommand and pass it to the
 +       * callback. In afs context a pointer to the deserialized parse result
 +       * is stored here.
 +       */
        struct lls_parse_result *lpr;
  };
  
@@@ -262,6 -254,7 +262,7 @@@ int get_afhi_of_row(const struct osl_ro
  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 --combined aft.c
index 8969e7ac207ac935a83e4a212ee07d09e7317bc1,f9512c7a8ba8111220c73b8ef931d8a49f6efde1..15769d55498b9b5ad6aa11f86efdebde31afc417
--- 1/aft.c
--- 2/aft.c
+++ b/aft.c
  #include "sideband.h"
  #include "command.h"
  
 -static struct osl_table *audio_file_table;
 +/* Data about one audio file. Needed for ls and stat output. */
 +struct ls_data {
 +      /* Usual audio format handler information. */
 +      struct afh_info afhi;
 +      /* Audio file selector information. */
 +      struct afs_info afsi;
 +      /* The full path of the audio file. */
 +      char *path;
 +      /* The score value (if -a was given). */
 +      long score;
 +      /* The hash value of the audio file data. */
 +      unsigned char *hash;
 +};
 +
 +/*
 + * The internal state of the audio file table is described by the following
 + * variables which are private to aft.c.
 + */
 +static struct osl_table *audio_file_table; /* NULL if table not open */
 +static struct osl_row *current_aft_row; /* NULL if no audio file open */
 +
  static char *status_items;
  static char *parser_friendly_status_items;
 +static struct ls_data status_item_ls_data;
  
  /** The different sorting methods of the ls command. */
  enum ls_sorting_method {
@@@ -91,6 -70,20 +91,6 @@@ enum ls_listing_mode 
        LS_MODE_PARSER,
  };
  
 -/* Data about one audio file. Needed for ls and stat output. */
 -struct ls_data {
 -      /* Usual audio format handler information. */
 -      struct afh_info afhi;
 -      /* Audio file selector information. */
 -      struct afs_info afsi;
 -      /* The full path of the audio file. */
 -      char *path;
 -      /* The score value (if -a was given). */
 -      long score;
 -      /* The hash value of the audio file data. */
 -      unsigned char *hash;
 -};
 -
  /**
   * The size of the individual output fields of the ls command.
   *
@@@ -961,6 -954,9 +961,6 @@@ out
        return ret;
  }
  
 -static struct ls_data status_item_ls_data;
 -static struct osl_row *current_aft_row;
 -
  static void make_inode_status_items(struct para_buffer *pb)
  {
        struct stat statbuf = {.st_size = 0};
@@@ -982,6 -978,12 +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);
        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);
        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;
  }
@@@ -1594,7 -1594,7 +1598,7 @@@ ACTION: Table modifications to be done 
  +----+----+---+------+---------------------------------------------------+
  | N  |  N | Y |  Y   | (new file) create new entry (force has no effect)
  +----+----+---+------+---------------------------------------------------+
 -|  N |  N | N |  Y   | (new file) create new entry
 +|  |  N | N |  Y   | (new file) create new entry
  +----+----+---+------+---------------------------------------------------+
  
  Notes:
diff --combined command.c
index bfce809a310cbe7024386ed61a441f71d3cf4e82,6b9f91de7a9b2acf76dd1e719fa6c88af96310ea..25b92715497d74caa06d2d2b47a94ad473ce0ecd
+++ b/command.c
  #include "string.h"
  #include "afh.h"
  #include "afs.h"
 +#include "net.h"
  #include "server.h"
  #include "list.h"
  #include "send.h"
  #include "sched.h"
  #include "vss.h"
 -#include "net.h"
  #include "daemon.h"
  #include "fd.h"
  #include "ipc.h"
@@@ -48,11 -48,15 +48,15 @@@ static const char * const server_comman
  
  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 +239,10 @@@ static int check_sender_args(struct com
                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);
        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 +334,10 @@@ static int com_sender(struct command_co
        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;
                }
                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 +497,21 @@@ static int com_stat(struct command_cont
        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.
                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;
@@@ -663,7 -687,8 +687,7 @@@ static int com_nomore(__a_unused struc
  }
  EXPORT_SERVER_CMD_HANDLER(nomore);
  
 -static int com_ff(__a_unused struct command_context *cc,
 -              struct lls_parse_result *lpr)
 +static int com_ff(struct command_context *cc, struct lls_parse_result *lpr)
  {
        long promille;
        int ret, backwards = 0;
        ret = -E_NO_AUDIO_FILE;
        if (!mmd->afd.afhi.chunks_total || !mmd->afd.afhi.seconds_total)
                goto out;
 +      ret = 1;
        promille = (1000 * mmd->current_chunk) / mmd->afd.afhi.chunks_total;
        if (backwards)
                promille -= 1000 * i / mmd->afd.afhi.seconds_total;
        mmd->new_vss_status_flags |= VSS_REPOS;
        mmd->new_vss_status_flags &= ~VSS_NEXT;
        mmd->events++;
 -      ret = 1;
  out:
        mutex_unlock(mmd_mutex);
        return ret;
  }
  EXPORT_SERVER_CMD_HANDLER(ff);
  
 -static int com_jmp(__a_unused struct command_context *cc,
 -              struct lls_parse_result *lpr)
 +static int com_jmp(struct command_context *cc, struct lls_parse_result *lpr)
  {
        long unsigned int i;
        int ret;
@@@ -758,7 -784,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;
                }
        }
        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,12 -900,14 +898,14 @@@ static int run_command(struct command_c
   * the function if the connection was not authenticated when the timeout
   * expires.
   *
 - * \sa alarm(2), \ref crypt.c, \ref crypt.h.
+  * \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[CHALLENGE_SIZE + 2 * SESSION_KEY_LEN];
 +      unsigned char rand_buf[APC_CHALLENGE_SIZE + 2 * SESSION_KEY_LEN];
        unsigned char challenge_hash[HASH_SIZE];
        char *command = NULL, *buf = para_malloc(HANDSHAKE_BUFSIZE) /* must be on the heap */;
        size_t numbytes;
                goto net_err;
        if (cc->u) {
                get_random_bytes_or_die(rand_buf, sizeof(rand_buf));
 -              ret = pub_encrypt(cc->u->pubkey, rand_buf, sizeof(rand_buf),
 +              ret = apc_pub_encrypt(cc->u->pubkey, rand_buf, sizeof(rand_buf),
                        (unsigned char *)buf);
                if (ret < 0)
                        goto net_err;
                get_random_bytes_or_die((unsigned char *)buf, numbytes);
        }
        PARA_DEBUG_LOG("sending %d byte challenge + session key (%zu bytes)\n",
 -              CHALLENGE_SIZE, numbytes);
 +              APC_CHALLENGE_SIZE, numbytes);
        ret = send_sb(&cc->scc, buf, numbytes, SBD_CHALLENGE, false);
        buf = NULL;
        if (ret < 0)
        if (!cc->u)
                goto net_err;
        /*
 -       * The correct response is the hash of the first CHALLENGE_SIZE bytes
 +       * The correct response is the hash of the first APC_CHALLENGE_SIZE bytes
         * of the random data.
         */
        ret = -E_BAD_AUTH;
        if (numbytes != HASH_SIZE)
                goto net_err;
 -      hash_function((char *)rand_buf, CHALLENGE_SIZE, challenge_hash);
 +      hash_function((char *)rand_buf, APC_CHALLENGE_SIZE, challenge_hash);
        if (memcmp(challenge_hash, buf, HASH_SIZE))
                goto net_err;
        /* auth successful */
        alarm(0);
        PARA_INFO_LOG("good auth for %s\n", cc->u->name);
        /* init stream cipher keys with the second part of the random buffer */
 -      cc->scc.recv = sc_new(rand_buf + CHALLENGE_SIZE, SESSION_KEY_LEN);
 -      cc->scc.send = sc_new(rand_buf + CHALLENGE_SIZE + SESSION_KEY_LEN,
 +      cc->scc.recv = sc_new(rand_buf + APC_CHALLENGE_SIZE, SESSION_KEY_LEN);
 +      cc->scc.send = sc_new(rand_buf + APC_CHALLENGE_SIZE + SESSION_KEY_LEN,
                SESSION_KEY_LEN);
        ret = send_sb(&cc->scc, NULL, 0, SBD_PROCEED, false);
        if (ret < 0)
@@@ -986,5 -1014,5 +1012,5 @@@ out
        }
        sc_free(cc->scc.recv);
        sc_free(cc->scc.send);
-       exit(ret < 0? EXIT_FAILURE : EXIT_SUCCESS);
+       return ret;
  }
diff --combined dccp_send.c
index 69ba65f5ef864396e81a722d8e69817a0040166f,0b454e792a84d679ec322561a00d7b383b1ef5b3..770ac601047a892025724bbb7e677a7d8bcaed18
  #include "error.h"
  #include "string.h"
  #include "afh.h"
 -#include "server.h"
  #include "net.h"
 +#include "server.h"
  #include "list.h"
  #include "send.h"
  #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 +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)
  {
@@@ -161,8 -164,7 +164,8 @@@ static void dccp_post_select(fd_set *rf
  
  static int dccp_com_on(__a_unused struct sender_command_data *scd)
  {
 -      return generic_com_on(dss, IPPROTO_DCCP);
 +      generic_com_on(dss, IPPROTO_DCCP);
 +      return 1;
  }
  
  static int dccp_com_off(__a_unused struct sender_command_data *scd)
@@@ -215,32 -217,43 +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;
 -      int ret;
--
        init_sender_status(dss, OPT_RESULT(DCCP_ACCESS),
                OPT_UINT32_VAL(DCCP_PORT), OPT_UINT32_VAL(DCCP_MAX_CLIENTS),
                OPT_GIVEN(DCCP_DEFAULT_DENY));
 -      ret = generic_com_on(dss, IPPROTO_DCCP);
 -      if (ret < 0)
 -              PARA_ERROR_LOG("%s\n", para_strerror(-ret));
 +      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 --combined fd.c
index 0347fd83b7f4340019811b853e7eab5e9dccce36,ae5ef41379d94ea009e6647e62989c74e84cf2b8..33891d2e6c9f1c3568428b1df93b4b92e295ef61
--- 1/fd.c
--- 2/fd.c
+++ b/fd.c
@@@ -179,15 -179,15 +179,15 @@@ __printf_2_3 int write_va_buffer(int fd
   * \param rfds An optional fd set pointer.
   * \param num_bytes Result pointer. Contains the number of bytes read from \a fd.
   *
 - * If \a rfds is not \p NULL and the (non-blocking) file descriptor \a fd is
 - * not set in \a rfds, this function returns early without doing anything.
 - * Otherwise The function tries to read up to \a sz bytes from \a fd, where \a
 - * sz is the sum of the lengths of all vectors in \a iov. As for xwrite(),
 - * \p EAGAIN is not considered an error condition. However, \p EOF is.
 + * If rfds is not NULL and the (non-blocking) file descriptor fd is not set in
 + * rfds, this function returns early without doing anything. Otherwise it tries
 + * to read up to sz bytes from fd, where sz is the sum of the lengths of all
 + * vectors in iov. Like \ref xwrite(), EAGAIN and EINTR are not considered
 + * error conditions. However, EOF is.
   *
   * \return Zero or a negative error code. If the underlying call to readv(2)
   * returned zero (indicating an end of file condition) or failed for some
 - * reason other than \p EAGAIN, a negative error code is returned.
 + * reason other than EAGAIN or EINTR, a negative error code is returned.
   *
   * In any case, \a num_bytes contains the number of bytes that have been
   * successfully read from \a fd (zero if the first readv() call failed with
@@@ -226,7 -226,7 +226,7 @@@ int readv_nonblock(int fd, struct iove
                if (ret == 0)
                        return -E_EOF;
                if (ret < 0) {
 -                      if (errno == EAGAIN)
 +                      if (errno == EAGAIN || errno == EINTR)
                                return 0;
                        return -ERRNO_TO_PARA_ERROR(errno);
                }
@@@ -419,34 -419,6 +419,6 @@@ void para_fd_set(int fd, fd_set *fds, i
        *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 --combined http_send.c
index 210f85ac63c9dd718a3f00e6a6e9790ffeeb5d96,9a35fc99f920beed52e19c12bab9f9dc308027e4..4d612285aeff00f7d8d153be5c9f1743f3fa9425
  #include "error.h"
  #include "string.h"
  #include "afh.h"
 +#include "net.h"
  #include "server.h"
  #include "http.h"
  #include "list.h"
  #include "send.h"
  #include "sched.h"
  #include "vss.h"
 -#include "net.h"
 +#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 -75,12 +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)
  {
@@@ -209,8 -213,7 +214,8 @@@ static void http_pre_select(int *max_fi
  
  static int http_com_on(__a_unused struct sender_command_data *scd)
  {
 -      return generic_com_on(hss, IPPROTO_TCP);
 +      generic_com_on(hss, IPPROTO_TCP);
 +      return 1;
  }
  
  static int http_com_off(__a_unused struct sender_command_data *scd)
@@@ -236,34 -239,48 +241,44 @@@ 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;
 -      int ret;
--
        init_sender_status(hss, OPT_RESULT(HTTP_ACCESS),
                OPT_UINT32_VAL(HTTP_PORT), OPT_UINT32_VAL(HTTP_MAX_CLIENTS),
                OPT_GIVEN(HTTP_DEFAULT_DENY));
        if (OPT_GIVEN(HTTP_NO_AUTOSTART))
                return;
 -      ret = generic_com_on(hss, IPPROTO_TCP);
 -      if (ret < 0)
 -              PARA_ERROR_LOG("%s\n", para_strerror(-ret));
 +      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 --combined send.h
index 84f35f9228cd05c62f9430063123d3e49761a06c,212fb4a12582610034593398c3d1dca93dc77de4..7a4c01bcb6cc1c80c77e06bc14b164dce8b3304d
--- 1/send.h
--- 2/send.h
+++ b/send.h
@@@ -27,14 -27,11 +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 +85,8 @@@
         * 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.
         *
        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. */
@@@ -186,7 -190,8 +190,8 @@@ void generic_com_allow(struct sender_co
                struct sender_status *ss);
  void generic_com_deny(struct sender_command_data *scd,
                struct sender_status *ss);
 -int generic_com_on(struct sender_status *ss, unsigned protocol);
 +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);
diff --combined send_common.c
index 16820a5380395fabd841287448108a346a2fb4ca,3e8a7c0dc85049dcfc52a5982083a69d3e1babc6..61a12c827342758fa97949db669872e8b2d504b8
  /** Clients will be kicked if there are more than that many bytes pending. */
  #define MAX_CQ_BYTES 40000
  
 -/**
 - * Open a passive socket of given layer4 type.
 - *
 - * Set the resulting file descriptor to nonblocking mode and add it to the list
 - * of fds that are being closed in the child process when the server calls
 - * fork().
 - *
 - * \param l4type The transport-layer protocol.
 - * \param port The port number.
 - *
 - * \return The listening fd on success, negative on errors.
 - */
 -static int open_sender(unsigned l4type, int port)
 -{
 -      int fd, ret = para_listen_simple(l4type, port);
 -
 -      if (ret < 0)
 -              return ret;
 -      fd = ret;
 -      ret = mark_fd_nonblocking(fd);
 -      if (ret < 0) {
 -              close(fd);
 -              return ret;
 -      }
 -      add_close_on_fork_list(fd);
 -      return fd;
 -}
 -
  /**
   * Shut down a client connected to a paraslash sender.
   *
@@@ -46,8 -74,10 +46,10 @@@ void shutdown_client(struct sender_clie
  {
        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 -218,22 +190,22 @@@ void generic_com_allow(struct sender_co
        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.
   *
@@@ -206,38 -252,21 +224,38 @@@ void generic_com_deny(struct sender_com
   * Activate a paraslash sender.
   *
   * \param ss The sender to activate.
 - * \param protocol The symbolic name of the transport-layer protocol.
 + * \param protocol layer4 type (IPPROTO_TCP or IPPROTO_DCCP).
 + *
 + * This opens a passive socket of given layer4 type, sets the resulting file
 + * descriptor to nonblocking mode and adds it to the close on fork list.
   *
 - * \return Standard.
 + * Errors are logged but otherwise ignored.
   */
 -int generic_com_on(struct sender_status *ss, unsigned protocol)
 +void generic_com_on(struct sender_status *ss, unsigned protocol)
  {
 -      int ret;
 +      int fd, ret;
  
        if (ss->listen_fd >= 0)
 -              return 1;
 -      ret = open_sender(protocol, ss->port);
 -      if (ret < 0)
 -              return ret;
 -      ss->listen_fd = ret;
 -      return 1;
 +              return;
 +      ret = para_listen_simple(protocol, ss->port);
 +      if (ret < 0) {
 +              PARA_ERROR_LOG("could not listen on port %d: %s\n", ss->port,
 +                      para_strerror(-ret));
 +              return;
 +      }
 +      fd = ret;
 +      ret = mark_fd_nonblocking(fd);
 +      if (ret < 0) {
 +              PARA_ERROR_LOG("could not set %s socket fd for port %d to "
 +                      "nonblocking mode: %s\n",
 +                      protocol == IPPROTO_TCP? "TCP" : "DCCP", ss->port,
 +                      para_strerror(-ret));
 +              close(fd);
 +              return;
 +      }
 +      add_close_on_fork_list(fd);
 +      ss->listen_fd = fd;
 +      return;
  }
  
  /**
@@@ -266,8 -295,9 +284,8 @@@ void generic_com_off(struct sender_stat
   * \param ss The sender whose listening fd is ready for reading.
   * \param rfds Passed to para_accept(),
   *
 - * This must be called only if the socket fd of \a ss is ready for reading.  It
 - * calls para_accept() to accept the connection and performs the following
 - * actions on the resulting file descriptor \a fd:
 + * This calls para_accept() and performs the following actions on the resulting
 + * file descriptor fd:
   *
   *    - Checks whether the maximal number of connections are exceeded.
   *    - Sets \a fd to nonblocking mode.
diff --combined server.c
index 011367e3481af17e10f6390fb36192a136c271fd,076bf4fabbf2b0900e759d5cf4fb1427fa18bc99..f19fc996ecc7355ddf24379e242fcec4da287dd6
+++ b/server.c
@@@ -45,7 -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"
@@@ -53,6 -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"
@@@ -100,16 -100,39 +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 +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;
        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;
        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 +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 +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;
        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)
                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 +389,14 @@@ static void command_pre_select(struct s
  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;
        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
                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));
@@@ -456,7 -505,7 +505,8 @@@ static int init_afs(int argc, char **ar
                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;
@@@ -494,7 -543,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;
        /* 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();
  
        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 -643,21 +644,21 @@@ static int server_select(int max_fileno
        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.
   *
  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();
 -              shm_detach(mmd);
+       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);
  }
diff --combined server.h
index 988a98d8e17723817907787016cf9b58e771f545,d56da7eb0db8d74c78e139484653e84e51ba684e..da75d86bdf191b130d02da12f49172ac5e0482d7
+++ b/server.h
@@@ -5,6 -5,10 +5,6 @@@
  /** Size of the selector_info and audio_file info strings of struct misc_meta_data. */
  #define MMD_INFO_SIZE 16384
  
 -/** The maximum length of the host component in an URL */
 -#define MAX_HOSTLEN 256
 -
 -
  /** Arguments for the sender command. */
  struct sender_command_data {
        /** Greater than zero indicates that a sender cmd is already queued. */
@@@ -109,6 -113,8 +109,8 @@@ extern struct lls_parse_result *server_
  #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);
diff --combined udp_send.c
index 38d49e3e70e42bc72d9316a21a5ca5e5441106f3,52947b20610a6b26280755cf0ddb5125ac5ae8a2..04e2982f86bf125c5b4ce8ab2d6daa9280ea0bce
  #include "error.h"
  #include "string.h"
  #include "afh.h"
 +#include "net.h"
  #include "server.h"
  #include "list.h"
  #include "send.h"
  #include "sched.h"
  #include "vss.h"
  #include "portable_io.h"
 -#include "net.h"
  #include "fd.h"
  #include "close_on_fork.h"
  
@@@ -72,8 -72,11 +72,11 @@@ static void udp_delete_target(struct se
  
        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 +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 +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,
+ };
diff --combined user_list.c
index 9db08715348f2e7df954d5371a106e4b5b347d40,d54b619a2d58ef98519f83bd3a88dd331be447b6..32a4309d4360fa73a8e7d0bbef622a7928001bb0
  #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);
 -              free_public_key(u->pubkey);
++              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)
                if (strcmp(w, "user"))
                        continue;
                PARA_DEBUG_LOG("found entry for user %s\n", n);
 -              ret = get_public_key(k, &pubkey);
 +              ret = apc_get_pubkey(k, &pubkey);
                if (ret < 0) {
                        PARA_NOTICE_LOG("skipping entry for user %s: %s\n", n,
                                para_strerror(-ret));
                        continue;
                }
                /*
 -               * In order to encrypt len := CHALLENGE_SIZE + 2 * SESSION_KEY_LEN
 +               * In order to encrypt len := APC_CHALLENGE_SIZE + 2 * SESSION_KEY_LEN
                 * bytes using RSA_public_encrypt() with EME-OAEP padding mode,
                 * RSA_size(rsa) must be greater than len + 41. So ignore keys
                 * which are too short. For details see RSA_public_encrypt(3).
                 */
 -              if (ret <= CHALLENGE_SIZE + 2 * SESSION_KEY_LEN + 41) {
 +              if (ret <= APC_CHALLENGE_SIZE + 2 * SESSION_KEY_LEN + 41) {
                        PARA_WARNING_LOG("public key %s too short (%d)\n",
                                k, ret);
 -                      free_public_key(pubkey);
 +                      apc_free_pubkey(pubkey);
                        continue;
                }
                u = para_malloc(sizeof(*u));
@@@ -93,32 -141,6 +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.
   *
   * \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;
diff --combined vss.c
index 8b850fe1e56c27d3cebda6db2d0eb6984575c20e,0a1ac525abc8389ef6d7f1ed1817d9fbc0dd664a..2cd0a1632922b0b2272aa183c0ddba8c4b4a3d30
--- 1/vss.c
--- 2/vss.c
+++ b/vss.c
@@@ -25,8 -25,8 +25,8 @@@
  #include "string.h"
  #include "afh.h"
  #include "afs.h"
 -#include "server.h"
  #include "net.h"
 +#include "server.h"
  #include "list.h"
  #include "send.h"
  #include "sched.h"
@@@ -40,24 -40,9 +40,9 @@@ extern void dccp_send_init(struct sende
  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 {
@@@ -298,7 -283,7 +283,7 @@@ static int initialize_fec_client(struc
        if (fcp->init_fec) {
                /*
                 * Set the maximum slice size to the Maximum Packet Size if the
 -               * transport protocol allows to determine this value. The user
 +               * transport protocol allows determination of this value. The user
                 * can specify a slice size up to this value.
                 */
                ret = fcp->init_fec(fc->sc);
@@@ -918,10 -903,10 +903,10 @@@ static void vss_pre_select(struct sche
                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 +1067,10 @@@ static void vss_send(struct vss_task *v
                 * 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 +1083,17 @@@ static int vss_post_select(struct sche
        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;
                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));
                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 +1169,9 @@@ void vss_init(int afs_socket, struct sc
        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)) {
                .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();
+       }
+ }