Merge branch 'refs/heads/t/long-help'
authorAndre Noll <maan@tuebingen.mpg.de>
Sun, 10 Jun 2018 16:59:36 +0000 (18:59 +0200)
committerAndre Noll <maan@tuebingen.mpg.de>
Sun, 10 Jun 2018 17:03:23 +0000 (19:03 +0200)
This series introduces lsu.c and lsu.h which contain helpers related to
the lopsub library. These helpers are designed to be shared between
the executables. The series starts by implementing a generic help
command for lopsub suites and converts para_server. Subsequent patches
convert audiod and para_play.

The second part of the series adds another lopsub related helper which
merges command line options and config file options. Each executable
is modified to make use of the new helper, getting rid of quite some
code duplication.

The conflict resolution for server.c has been tested for a while.

Cooking for five weeks.

* refs/heads/t/long-help:
  play: Use lsu_merge_config_file_options().
  mixer: Use lsu_merge_config_file_options().
  gui: Use lsu_merge_config_file_options().
  filter: Use lsu_merge_config_file_options().
  audioc: Use lsu_merge_config_file_options().
  audiod: Use lsu_merge_config_file_options().
  client: Use lsu_merge_config_file_options().
  lsu: Add helper to merge config file options, convert server.
  Trivial: Rename completion_result variables.
  play: Implement help --long.
  audiod: Implement help --long.
  Introduce lsu.{c,h}, implement help --long for para_server.

1  2 
NEWS.md
audiod.c
client.c
client_common.c
command.c
configure.ac
gui.c
m4/lls/server_cmd.suite.m4
server.c

diff --combined NEWS.md
+++ b/NEWS.md
@@@ -1,26 -1,6 +1,30 @@@
  NEWS
  ====
  
- 0.6.2 (to be accounced) "elastic diversity"
 +-------------------------------------------
++0.6.2 (to be announced) "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.
 +- New server options: --listen--address, --http-listen-address and
 +  --dccp-listen-address. These options restrict the set of listening
 +  addresses of the TCP and DCCP sockets of the server process.
++- The help commands of server, audiod, play now support --long. By default,
++  the short help is shown.
++- The code which merges the command line options with the config file
++  options has been consolidated.
 +
 +Download: [tarball](./releases/paraslash-git.tar.xz)
 +
  ----------------------------------------
  0.6.1 (2017-09-23) "segmented iteration"
  ----------------------------------------
diff --combined audiod.c
+++ b/audiod.c
@@@ -17,6 -17,7 +17,7 @@@
  #include "recv_cmd.lsg.h"
  #include "para.h"
  #include "error.h"
+ #include "lsu.h"
  #include "crypt.h"
  #include "list.h"
  #include "sched.h"
@@@ -378,81 -379,36 +379,36 @@@ empty
  
  static void parse_config_or_die(void)
  {
-       int ret;
-       char *cf, *errctx = NULL;
-       void *map;
-       size_t sz;
-       if (OPT_GIVEN(CONFIG_FILE))
-               cf = para_strdup(OPT_STRING_VAL(CONFIG_FILE));
-       else {
-               char *home = para_homedir();
-               cf = make_message("%s/.paraslash/audiod.conf", home);
-               free(home);
-       }
-       ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
+       int i, ret;
+       uint32_t n;
+       ret = lsu_merge_config_file_options(OPT_STRING_VAL(CONFIG_FILE),
+               "audiod.conf", &lpr, CMD_PTR, audiod_suite, 0U /* flags */);
        if (ret < 0) {
-               if (ret != -E_EMPTY && ret != -ERRNO_TO_PARA_ERROR(ENOENT))
-                       goto free_cf;
-               if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) && OPT_GIVEN(CONFIG_FILE))
-                       goto free_cf;
-       } else {
-               int cf_argc;
-               char **cf_argv;
-               struct lls_parse_result *cf_lpr, *merged_lpr;
-               ret = lls(lls_convert_config(map, sz, NULL, &cf_argv, &errctx));
-               para_munmap(map, sz);
-               if (ret < 0)
-                       goto free_cf;
-               cf_argc = ret;
-               ret = lls(lls_parse(cf_argc, cf_argv, CMD_PTR, &cf_lpr, &errctx));
-               lls_free_argv(cf_argv);
-               if (ret < 0)
-                       goto free_cf;
-               ret = lls(lls_merge(lpr, cf_lpr, CMD_PTR, &merged_lpr,
-                       &errctx));
-               lls_free_parse_result(cf_lpr, CMD_PTR);
-               if (ret < 0)
-                       goto free_cf;
-               lls_free_parse_result(lpr, CMD_PTR);
-               lpr = merged_lpr;
+               PARA_EMERG_LOG("failed to parse config file: %s\n",
+                       para_strerror(-ret));
+               exit(EXIT_FAILURE);
        }
        daemon_set_loglevel(ENUM_STRING_VAL(LOGLEVEL));
-       if (OPT_GIVEN(USER_ALLOW)) {
-               uint32_t n = OPT_GIVEN(USER_ALLOW);
-               int i;
-               uid_whitelist = para_malloc(n * sizeof(uid_t));
-               for (i = 0; i < n; i++) {
-                       const char *arg = lls_string_val(i,
-                               OPT_RESULT(USER_ALLOW));
-                       int32_t val;
-                       struct passwd *pw;
-                       ret = para_atoi32(arg, &val);
-                       if (ret >= 0) {
-                               uid_whitelist[i] = val;
-                               continue;
-                       }
-                       errno = 0; /* see getpwnam(3) */
-                       pw = getpwnam(arg);
-                       if (!pw) {
-                               PARA_EMERG_LOG("invalid username: %s\n", arg);
-                               free(uid_whitelist);
-                               goto free_cf;
-                       }
-                       uid_whitelist[i] = pw->pw_uid;
+       n = OPT_GIVEN(USER_ALLOW);
+       if (n == 0)
+               return;
+       uid_whitelist = para_malloc(n * sizeof(uid_t));
+       for (i = 0; i < n; i++) {
+               const char *arg = lls_string_val(i, OPT_RESULT(USER_ALLOW));
+               int32_t val;
+               struct passwd *pw;
+               ret = para_atoi32(arg, &val);
+               if (ret >= 0) {
+                       uid_whitelist[i] = val;
+                       continue;
                }
-       }
-       ret = 0;
- free_cf:
-       free(cf);
-       if (ret < 0) {
-               if (errctx)
-                       PARA_ERROR_LOG("%s\n", errctx);
-               free(errctx);
-               lls_free_parse_result(lpr, CMD_PTR);
-               PARA_EMERG_LOG("%s\n", para_strerror(-ret));
-               exit(EXIT_FAILURE);
+               pw = getpwnam(arg);
+               if (!pw) {
+                       PARA_EMERG_LOG("invalid username: %s\n", arg);
+                       exit(EXIT_FAILURE);
+               }
+               uid_whitelist[i] = pw->pw_uid;
        }
  }
  
@@@ -1493,7 -1449,7 +1449,7 @@@ int main(int argc, char *argv[]
        version_handle_flag("audiod", OPT_GIVEN(VERSION));
        handle_help_flags();
        parse_config_or_die();
 -      init_random_seed_or_die();
 +      crypt_init();
        daemon_set_priority(OPT_UINT32_VAL(PRIORITY));
        recv_init();
        if (daemon_init_colors_or_die(OPT_UINT32_VAL(COLOR), COLOR_AUTO,
        audiod_cleanup();
        sched_shutdown(&sched);
        signal_shutdown(signal_task);
 -
 +      crypt_shutdown();
  out:
        lls_free_parse_result(lpr, CMD_PTR);
        if (errctx)
diff --combined client.c
+++ b/client.c
@@@ -248,9 -248,15 +248,15 @@@ I9E_DUMMY_COMPLETER(tasks)
  static struct i9e_completer completers[];
  
  static void help_completer(struct i9e_completion_info *ci,
-               struct i9e_completion_result *result)
+               struct i9e_completion_result *cr)
  {
-       result->matches = i9e_complete_commands(ci->word, completers);
+       char *opts[] = {LSG_SERVER_CMD_HELP_OPTS, NULL};
+       if (ci->word[0] == '-') {
+               i9e_complete_option(opts, ci, cr);
+               return;
+       }
+       cr->matches = i9e_complete_commands(ci->word, completers);
  }
  
  static void stat_completer(struct i9e_completion_info *ci,
@@@ -618,7 -624,7 +624,7 @@@ int main(int argc, char *argv[]
  {
        int ret;
  
 -      init_random_seed_or_die();
 +      crypt_init();
        sched.default_timeout.tv_sec = 1;
  
        ret = client_parse_config(argc, argv, &ct, &client_loglevel);
                }
        }
        sched_shutdown(&sched);
 +      crypt_shutdown();
  out:
        if (ret < 0)
                PARA_ERROR_LOG("%s\n", para_strerror(-ret));
diff --combined client_common.c
@@@ -15,6 -15,7 +15,7 @@@
  #include "para.h"
  #include "error.h"
  #include "list.h"
+ #include "lsu.h"
  #include "sched.h"
  #include "crypt.h"
  #include "net.h"
@@@ -317,15 -318,15 +318,15 @@@ static int client_post_select(struct sc
                }
                n = sbb.iov.iov_len;
                PARA_INFO_LOG("<-- [challenge] (%zu bytes)\n", n);
 -              ret = priv_decrypt(ct->key_file, crypt_buf,
 +              ret = apc_priv_decrypt(ct->key_file, crypt_buf,
                        sbb.iov.iov_base, n);
                free(sbb.iov.iov_base);
                if (ret < 0)
                        goto out;
                ct->challenge_hash = para_malloc(HASH_SIZE);
 -              hash_function((char *)crypt_buf, CHALLENGE_SIZE, ct->challenge_hash);
 -              ct->scc.send = sc_new(crypt_buf + CHALLENGE_SIZE, SESSION_KEY_LEN);
 -              ct->scc.recv = sc_new(crypt_buf + CHALLENGE_SIZE + SESSION_KEY_LEN,
 +              hash_function((char *)crypt_buf, APC_CHALLENGE_SIZE, ct->challenge_hash);
 +              ct->scc.send = sc_new(crypt_buf + APC_CHALLENGE_SIZE, SESSION_KEY_LEN);
 +              ct->scc.recv = sc_new(crypt_buf + APC_CHALLENGE_SIZE + SESSION_KEY_LEN,
                        SESSION_KEY_LEN);
                hash_to_asc(ct->challenge_hash, buf);
                PARA_INFO_LOG("--> %s\n", buf);
@@@ -525,12 -526,10 +526,10 @@@ int client_parse_config(int argc, char 
                int *loglevel)
  {
        const struct lls_command *cmd = CLIENT_CMD_PTR;
-       void *map;
-       size_t sz;
        struct lls_parse_result *lpr;
        int ret, ll;
        struct client_task *ct;
-       char *cf = NULL, *kf = NULL, *user, *errctx, *home = para_homedir();
+       char *kf = NULL, *user, *errctx, *home = para_homedir();
  
        ret = lls(lls_parse(argc, argv, cmd, &lpr, &errctx));
        if (ret < 0)
        version_handle_flag("client", CLIENT_OPT_GIVEN(VERSION, lpr));
        handle_help_flag(lpr);
  
-       if (CLIENT_OPT_GIVEN(CONFIG_FILE, lpr))
-               cf = para_strdup(CLIENT_OPT_STRING_VAL(CONFIG_FILE, lpr));
-       else
-               cf = make_message("%s/.paraslash/client.conf", home);
-       ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
-       if (ret < 0) {
-               if (ret != -E_EMPTY && ret != -ERRNO_TO_PARA_ERROR(ENOENT))
-                       goto out;
-               if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) &&
-                               CLIENT_OPT_GIVEN(CONFIG_FILE, lpr))
-                       goto out;
-       } else {
-               int cf_argc;
-               char **cf_argv;
-               struct lls_parse_result *cf_lpr, *merged_lpr;
-               ret = lls(lls_convert_config(map, sz, NULL, &cf_argv, &errctx));
-               para_munmap(map, sz);
-               if (ret < 0)
-                       goto out;
-               cf_argc = ret;
-               ret = lls(lls_parse(cf_argc, cf_argv, cmd, &cf_lpr, &errctx));
-               lls_free_argv(cf_argv);
-               if (ret < 0)
-                       goto out;
-               ret = lls(lls_merge(lpr, cf_lpr, cmd, &merged_lpr,
-                       &errctx));
-               lls_free_parse_result(cf_lpr, cmd);
-               if (ret < 0)
-                       goto out;
-               lls_free_parse_result(lpr, cmd);
-               lpr = merged_lpr;
-       }
+       ret = lsu_merge_config_file_options(CLIENT_OPT_STRING_VAL(CONFIG_FILE, lpr),
+               "client.conf", &lpr, cmd, client_suite, 0U /* default flags */);
+       if (ret < 0)
+               goto out;
        /* success */
        ll = CLIENT_OPT_UINT32_VAL(LOGLEVEL, lpr);
        if (loglevel)
                }
        }
        PARA_INFO_LOG("user: %s\n", user);
-       PARA_INFO_LOG("config file: %s\n", cf);
        PARA_INFO_LOG("key file: %s\n", kf);
        PARA_INFO_LOG("loglevel: %d\n", ll);
        ct = para_calloc(sizeof(*ct));
        ret = lls_num_inputs(lpr);
  out:
        free(home);
-       free(cf);
        if (ret < 0) {
                if (errctx)
                        PARA_ERROR_LOG("%s\n", errctx);
diff --combined command.c
+++ b/command.c
@@@ -16,6 -16,7 +16,7 @@@
  #include "server.lsg.h"
  #include "para.h"
  #include "error.h"
+ #include "lsu.h"
  #include "crypt.h"
  #include "sideband.h"
  #include "command.h"
@@@ -48,15 -49,11 +49,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;
  }
  
  /*
@@@ -239,10 -236,10 +240,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:
@@@ -334,10 -331,10 +335,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;
@@@ -497,21 -494,9 +498,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;
@@@ -552,63 -529,35 +553,35 @@@ out
  }
  EXPORT_SERVER_CMD_HANDLER(stat);
  
- /* fixed-length, human readable permission string */
- const char *server_cmd_perms_str(unsigned int perms)
+ const char *aux_info_cb(unsigned cmd_num, bool verbose)
  {
-       static char result[5];
-       result[0] = perms & AFS_READ? 'a' : '-';
-       result[1] = perms & AFS_WRITE? 'A' : '-';
-       result[2] = perms & VSS_READ? 'v' : '-';
-       result[3] = perms & VSS_WRITE? 'V' : '-';
-       result[4] = '\0';
-       return result;
- }
- static int send_list_of_commands(struct command_context *cc)
- {
-       int i;
-       const struct lls_command *cmd;
-       char *msg = para_strdup("");
-       for (i = 1; (cmd = lls_cmd(i, server_cmd_suite)); i++) {
-               const char *perms = server_cmd_perms_str(server_command_perms[i]);
-               char *tmp = make_message("%s%s\t%s\t%s\n", msg,
-                       lls_command_name(cmd), perms, lls_purpose(cmd));
-               free(msg);
-               msg = tmp;
+       static char result[80];
+       unsigned perms = server_command_perms[cmd_num];
+       if (verbose) {
+               /* permissions: VSS_READ | VSS_WRITE */
+               sprintf(result, "permissions: %s",
+                       server_command_perms_txt[cmd_num]);
+       } else {
+               result[0] = perms & AFS_READ? 'a' : '-';
+               result[1] = perms & AFS_WRITE? 'A' : '-';
+               result[2] = perms & VSS_READ? 'v' : '-';
+               result[3] = perms & VSS_WRITE? 'V' : '-';
+               result[4] = '\0';
        }
-       return send_sb(&cc->scc, msg, strlen(msg), SBD_OUTPUT, false);
+       return result;
  }
  
  static int com_help(struct command_context *cc, struct lls_parse_result *lpr)
  {
-       const char *perms;
-       char *long_help, *buf, *errctx;
+       char *buf;
        int ret;
-       const struct lls_command *cmd;
+       unsigned n;
+       bool long_help = SERVER_CMD_OPT_GIVEN(HELP, LONG, lpr);
  
-       ret = lls(lls_check_arg_count(lpr, 0, 1, &errctx));
-       if (ret < 0) {
-               send_errctx(cc, errctx);
-               return ret;
-       }
-       if (lls_num_inputs(lpr) == 0)
-               return send_list_of_commands(cc);
-       /* argument given for help */
-       ret = lls(lls_lookup_subcmd(lls_input(0, lpr), server_cmd_suite,
-               &errctx));
-       if (ret < 0) {
-               send_errctx(cc, errctx);
-               return ret;
-       }
-       cmd = lls_cmd(ret, server_cmd_suite);
-       perms = server_command_perms_txt[ret];
-       long_help = lls_long_help(cmd);
-       assert(long_help);
-       ret = xasprintf(&buf, "%spermissions: %s\n", long_help, perms);
-       free(long_help);
-       return send_sb(&cc->scc, buf, ret, SBD_OUTPUT, false);
+       lsu_com_help(long_help, lpr, server_cmd_suite, aux_info_cb, &buf, &n);
+       ret = send_sb(&cc->scc, buf, n, SBD_OUTPUT, false);
+       return ret;
  }
  EXPORT_SERVER_CMD_HANDLER(help);
  
@@@ -687,7 -636,8 +660,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;
@@@ -782,7 -733,7 +755,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);
@@@ -898,14 -849,12 +871,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)
@@@ -1012,5 -961,5 +985,5 @@@ out
        }
        sc_free(cc->scc.recv);
        sc_free(cc->scc.send);
 -      exit(ret < 0? EXIT_FAILURE : EXIT_SUCCESS);
 +      return ret;
  }
diff --combined configure.ac
@@@ -392,9 -392,10 +392,10 @@@ if test -n "$CRYPTOLIB" && test $HAVE_O
                wma_common
                sideband
                version
+               lsu
        "
        if test "$CRYPTOLIB" = openssl; then
 -              server_errlist_objs="$server_errlist_objs crypt"
 +              server_errlist_objs="$server_errlist_objs openssl"
        else
                server_errlist_objs="$server_errlist_objs gcrypt"
        fi
@@@ -420,6 -421,7 +421,7 @@@ if test -n "$CRYPTOLIB"; the
                net
                string
                fd
+               lsu
                sched
                stdin
                stdout
                version
        "
        if test "$CRYPTOLIB" = openssl; then
 -              client_errlist_objs="$client_errlist_objs crypt"
 +              client_errlist_objs="$client_errlist_objs openssl"
        else
                client_errlist_objs="$client_errlist_objs gcrypt"
        fi
@@@ -486,9 -488,10 +488,10 @@@ if test -n "$CRYPTOLIB"; the
                wmadec_filter
                buffer_tree
                sync_filter
+               lsu
        "
        if test "$CRYPTOLIB" = openssl; then
 -              audiod_errlist_objs="$audiod_errlist_objs crypt"
 +              audiod_errlist_objs="$audiod_errlist_objs openssl"
        else
                audiod_errlist_objs="$audiod_errlist_objs gcrypt"
        fi
@@@ -543,7 -546,7 +546,7 @@@ f
  if test $HAVE_OSS = yes -o $HAVE_ALSA = yes; then
        build_mixer="yes"
        executables="$executables mixer"
-       mixer_errlist_objs="mixer exec string fd version"
+       mixer_errlist_objs="mixer exec string fd lsu version"
        if test $HAVE_OSS = yes; then
                mixer_errlist_objs="$mixer_errlist_objs oss_mix"
        fi
@@@ -569,6 -572,7 +572,7 @@@ if test $HAVE_CURSES = yes; the
                fd
                gui
                gui_theme
+               lsu
                time
                sched
                version
@@@ -593,6 -597,7 +597,7 @@@ filter_errlist_objs=
        amp_filter
        fecdec_filter
        fec
+       lsu
        version
        prebuffer_filter
        time
@@@ -722,6 -727,7 +727,7 @@@ play_errlist_objs=
        file_write
        version
        sync_filter
+       lsu
  "
  NEED_OGG_OBJECTS && play_errlist_objs="$play_errlist_objs ogg_afh_common"
  NEED_VORBIS_OBJECTS && {
@@@ -793,6 -799,7 +799,7 @@@ AC_SUBST(write_objs, add_dot_o($write_o
  audioc_errlist_objs="
        audioc
        string
+       lsu
        net
        fd
        version
diff --combined gui.c
--- 1/gui.c
--- 2/gui.c
+++ b/gui.c
@@@ -13,6 -13,7 +13,7 @@@
  #include "gui.lsg.h"
  #include "para.h"
  #include "gui.h"
+ #include "lsu.h"
  #include "string.h"
  #include "ringbuffer.h"
  #include "fd.h"
@@@ -857,64 -858,23 +858,23 @@@ static void check_key_map_args_or_die(v
  static void parse_config_file_or_die(bool reload)
  {
        int ret;
-       char *cf = NULL, *errctx = NULL;
-       void *map;
-       size_t sz;
-       int cf_argc;
-       char **cf_argv;
-       struct lls_parse_result *cf_lpr, *merged_lpr;
-       if (OPT_GIVEN(CONFIG_FILE))
-               cf = para_strdup(OPT_STRING_VAL(CONFIG_FILE));
-       else {
-               char *home = para_homedir();
-               cf = make_message("%s/.paraslash/gui.conf", home);
-               free(home);
-       }
-       ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
-       if (ret < 0) {
-               if (ret != -E_EMPTY && ret != -ERRNO_TO_PARA_ERROR(ENOENT))
-                       goto free_cf;
-               if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) && OPT_GIVEN(CONFIG_FILE))
-                       goto free_cf;
-               ret = 0;
-               lpr = cmdline_lpr;
-               goto success;
-       }
-       ret = lls(lls_convert_config(map, sz, NULL, &cf_argv, &errctx));
-       para_munmap(map, sz);
-       if (ret < 0)
-               goto free_cf;
-       cf_argc = ret;
-       ret = lls(lls_parse(cf_argc, cf_argv, CMD_PTR, &cf_lpr, &errctx));
-       lls_free_argv(cf_argv);
-       if (ret < 0)
-               goto free_cf;
-       if (reload) /* config file overrides command line */
-               ret = lls(lls_merge(cf_lpr, cmdline_lpr, CMD_PTR, &merged_lpr,
-                       &errctx));
-       else /* command line options override config file options */
-               ret = lls(lls_merge(cmdline_lpr, cf_lpr, CMD_PTR, &merged_lpr,
-                       &errctx));
-       lls_free_parse_result(cf_lpr, CMD_PTR);
-       if (ret < 0)
-               goto free_cf;
+       unsigned flags = MCF_DONT_FREE;
        if (lpr != cmdline_lpr)
                lls_free_parse_result(lpr, CMD_PTR);
-       lpr = merged_lpr;
- success:
-       loglevel = OPT_UINT32_VAL(LOGLEVEL);
-       check_key_map_args_or_die();
-       theme_init(OPT_STRING_VAL(THEME), &theme);
- free_cf:
-       free(cf);
+       lpr = cmdline_lpr;
+       if (reload)
+               flags |= MCF_OVERRIDE;
+       ret = lsu_merge_config_file_options(OPT_STRING_VAL(CONFIG_FILE),
+               "gui.conf", &lpr, CMD_PTR, gui_suite, flags);
        if (ret < 0) {
-               if (errctx)
-                       PARA_ERROR_LOG("%s\n", errctx);
-               free(errctx);
-               PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+               PARA_EMERG_LOG("failed to parse config file: %s\n",
+                       para_strerror(-ret));
                exit(EXIT_FAILURE);
        }
+       loglevel = OPT_UINT32_VAL(LOGLEVEL);
+       check_key_map_args_or_die();
+       theme_init(OPT_STRING_VAL(THEME), &theme);
  }
  
  /* Reread configuration, terminate on errors. */
@@@ -941,14 -901,6 +901,14 @@@ static int signal_post_select(struct sc
        switch (ret) {
        case SIGTERM:
                die(EXIT_FAILURE, "only the good die young (caught SIGTERM)\n");
 +      case SIGWINCH:
 +              PARA_NOTICE_LOG("got SIGWINCH\n");
 +              if (curses_active()) {
 +                      shutdown_curses();
 +                      init_curses();
 +                      redraw_bot_win();
 +              }
 +              return 1;
        case SIGINT:
                return 1;
        case SIGUSR1:
@@@ -1154,8 -1106,14 +1114,8 @@@ static int input_post_select(__a_unuse
        ret = wgetch(top.win);
        if (ret == ERR)
                return 0;
 -      if (ret == KEY_RESIZE) {
 -              if (curses_active()) {
 -                      shutdown_curses();
 -                      init_curses();
 -                      redraw_bot_win();
 -              }
 +      if (ret == KEY_RESIZE) /* already handled in signal_post_select() */
                return 0;
 -      }
        if (exs == EXEC_IDLE)
                handle_command(ret);
        else if (exec_pid > 0)
@@@ -1458,7 -1416,6 +1418,7 @@@ static int setup_tasks_and_schedule(voi
        para_install_sighandler(SIGTERM);
        para_install_sighandler(SIGCHLD);
        para_install_sighandler(SIGUSR1);
 +      para_install_sighandler(SIGWINCH);
        signal_task->task = task_register(&(struct task_info) {
                .name = "signal",
                .pre_select = signal_pre_select,
@@@ -144,10 -144,11 +144,11 @@@ aux_info_prefix = Permissions
        non-opts-name = [command]
        aux_info = NO_PERMISSION_REQUIRED
        [description]
-               Without any arguments, help prints a list of available commands. When
-               called with a command name as first argument, it prints the description
-               of this command.
+               When executed without any arguments, the available server commands
+               are listed. Otherwise, if the first argument is the name of a server
+               command, the description of this command is shown.
        [/description]
+       m4_include(`long-help.m4')
  
  [subcommand hup]
        purpose = reload config file, log file and user list
  
  [subcommand sender]
        purpose = control paraslash senders
 -      synopsis = [sender cmd [arguments]]
 +      synopsis = [sender subcmd [arguments]]
        aux_info = VSS_READ | VSS_WRITE
        [description]
 -              Send a command to a specific sender. The following commands are
 -              available, but not all senders support every command.
 +              This command executes a subcommand for the given sender, which is
 +              one of "http", "dccp" or "udp". Various subcommands exist to print
 +              information about the sender, to activate and deactivate the sender,
 +              and to change the access permissions and targets. The following
 +              subcommands are available:
  
 -                     help, on, off, add, delete, allow, deny, status.
 +                     help, status, on, off, allow, deny, add, delete.
  
 -              The help command prints the help text of the given sender. If no
 -              command is given the list of available senders is shown.
 +              All senders support the first four commands. The "allow" and "deny"
 +              commands are supported by the http and the dccp senders while "add"
 +              and "delete" are only supported by the udp sender. If no sender is
 +              given, the list of available senders is shown.
  
 -              Example:
 +              Examples:
 +
 +              Get help for the udp sender (contains further examples):
 +
 +                      sender udp help
 +
 +              Show the access control list and the number of connected clients of
 +              the http sender:
 +
 +                      sender http status
 +
 +              Senders may be activated and deactivated independently of each
 +              other. The following command switches off the dccp sender:
 +
 +                      sender dccp off
 +
 +              Add an UDP unicast for a client to the target list of the UDP sender:
 +
 +                      sender udp add client.foo.org
 +
 +              Start UDP multicast, using the default multicast address:
  
 -                      para_client sender http help
 +                      sender udp add 224.0.1.38
  
        [/description]
  
diff --combined server.c
+++ b/server.c
@@@ -41,6 -41,7 +41,7 @@@
  #include "server.lsg.h"
  #include "para.h"
  #include "error.h"
+ #include "lsu.h"
  #include "crypt.h"
  #include "afh.h"
  #include "string.h"
@@@ -100,40 -101,16 +101,40 @@@ uint32_t afs_socket_cookie
  /** The mutex protecting the shared memory area containing the mmd struct. */
  int mmd_mutex;
  
 +/* Serializes log output. */
 +static int log_mutex;
 +
  static struct sched sched;
  static struct signal_task *signal_task;
  
  /** The process id of the audio file selector process. */
  pid_t afs_pid = 0;
  
 +/* The the main server process (parent of afs and the command handlers). */
 +static pid_t server_pid;
 +
 +/**
 + * Tell whether the executing process is a command handler.
 + *
 + * Cleanup on exit must be performed differently for command handlers.
 + *
 + * \return True if the pid of the executing process is neither the server pid
 + * nor the afs pid.
 + */
 +bool process_is_command_handler(void)
 +{
 +      pid_t pid = getpid();
 +
 +      return pid != afs_pid && pid != server_pid;
 +}
 +
  /** The task responsible for server command handling. */
  struct server_command_task {
 -      /** TCP port on which para_server listens for connections. */
 -      int listen_fd;
 +      unsigned num_listen_fds; /* only one by default */
 +      /** TCP socket(s) on which para_server listens for connections. */
 +      int *listen_fds;
 +      /* File descriptor for the accepted socket. */
 +      int child_fd;
        /** Copied from para_server's main function. */
        int argc;
        /** Argument vector passed to para_server's main function. */
@@@ -155,17 -132,9 +156,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);
  void parse_config_or_die(bool reload)
  {
        int ret;
-       char *cf = NULL, *errctx = NULL, *user_list_file = NULL;
-       void *map;
-       size_t sz;
-       int cf_argc;
-       char **cf_argv;
-       struct lls_parse_result *cf_lpr, *merged_lpr;
-       char *home = para_homedir();
+       unsigned flags = MCF_DONT_FREE;
  
-       if (OPT_GIVEN(CONFIG_FILE))
-               cf = para_strdup(OPT_STRING_VAL(CONFIG_FILE));
-       else
-               cf = make_message("%s/.paraslash/server.conf", home);
-       if (!mmd || getpid() != afs_pid) {
-               if (OPT_GIVEN(USER_LIST))
-                       user_list_file = para_strdup(OPT_STRING_VAL(USER_LIST));
-               else
-                       user_list_file = make_message("%s/.paraslash/server.users", home);
-       }
-       free(home);
-       ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
-       if (ret < 0) {
-               if (ret != -E_EMPTY && ret != -ERRNO_TO_PARA_ERROR(ENOENT))
-                       goto free_cf;
-               if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) && OPT_GIVEN(CONFIG_FILE))
-                       goto free_cf;
-               server_lpr = cmdline_lpr;
-               goto success;
-       }
-       ret = lls(lls_convert_config(map, sz, NULL, &cf_argv, &errctx));
-       para_munmap(map, sz);
-       if (ret < 0)
-               goto free_cf;
-       cf_argc = ret;
-       ret = lls(lls_parse(cf_argc, cf_argv, CMD_PTR, &cf_lpr, &errctx));
-       lls_free_argv(cf_argv);
-       if (ret < 0)
-               goto free_cf;
-       if (reload) /* config file overrides command line */
-               ret = lls(lls_merge(cf_lpr, cmdline_lpr, CMD_PTR, &merged_lpr,
-                       &errctx));
-       else /* command line options override config file options */
-               ret = lls(lls_merge(cmdline_lpr, cf_lpr, CMD_PTR, &merged_lpr,
-                       &errctx));
-       lls_free_parse_result(cf_lpr, CMD_PTR);
-       if (ret < 0)
-               goto free_cf;
        if (server_lpr != cmdline_lpr)
                lls_free_parse_result(server_lpr, CMD_PTR);
-       server_lpr = merged_lpr;
- success:
+       server_lpr = cmdline_lpr;
+       if (reload)
+               flags |= MCF_OVERRIDE;
+       ret = lsu_merge_config_file_options(OPT_STRING_VAL(CONFIG_FILE),
+               "server.conf", &server_lpr, CMD_PTR, server_suite, flags);
+       if (ret < 0) {
+               PARA_EMERG_LOG("failed to parse config file: %s\n",
+                       para_strerror(-ret));
+               exit(EXIT_FAILURE);
+       }
        daemon_set_loglevel(ENUM_STRING_VAL(LOGLEVEL));
        if (OPT_GIVEN(LOGFILE)) {
                daemon_set_logfile(OPT_STRING_VAL(LOGFILE));
        if (OPT_GIVEN(LOG_TIMING))
                daemon_set_flag(DF_LOG_TIMING);
        daemon_set_priority(OPT_UINT32_VAL(PRIORITY));
-       if (user_list_file)
+       if (!reload || getpid() != afs_pid) {
+               char *user_list_file;
+               if (OPT_GIVEN(USER_LIST))
+                       user_list_file = para_strdup(OPT_STRING_VAL(USER_LIST));
+               else {
+                       char *home = para_homedir();
+                       user_list_file = make_message("%s/.paraslash/server.users", home);
+                       free(home);
+               }
 -              init_user_list(user_list_file);
 +              user_list_init(user_list_file);
-       ret = 1;
- free_cf:
-       free(cf);
-       free(user_list_file);
-       if (ret < 0) {
-               if (errctx)
-                       PARA_ERROR_LOG("%s\n", errctx);
-               free(errctx);
-               PARA_EMERG_LOG("%s\n", para_strerror(-ret));
-               exit(EXIT_FAILURE);
+               free(user_list_file);
        }
+       return;
  }
  
  /*
@@@ -312,12 -239,8 +277,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;
  }
@@@ -383,22 -311,20 +348,22 @@@ static void init_signal_task(void
  
  static void command_pre_select(struct sched *s, void *context)
  {
 +      unsigned n;
        struct server_command_task *sct = context;
 -      para_fd_set(sct->listen_fd, &s->rfds, &s->max_fileno);
 +
 +      for (n = 0; n < sct->num_listen_fds; n++)
 +              para_fd_set(sct->listen_fds[n], &s->rfds, &s->max_fileno);
  }
  
 -static int command_post_select(struct sched *s, void *context)
 +static int command_task_accept(unsigned listen_idx, struct sched *s,
 +              struct server_command_task *sct)
  {
 -      struct server_command_task *sct = context;
 -
        int new_fd, ret, i;
        char *peer_name;
        pid_t child_pid;
        uint32_t *chunk_table;
  
 -      ret = para_accept(sct->listen_fd, &s->rfds, NULL, 0, &new_fd);
 +      ret = para_accept(sct->listen_fds[listen_idx], &s->rfds, NULL, 0, &new_fd);
        if (ret <= 0)
                goto out;
        mmd->num_connects++;
        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 int command_post_select(struct sched *s, void *context)
  {
 +      struct server_command_task *sct = context;
 +      unsigned n;
        int ret;
 -      static struct server_command_task server_command_task_struct,
 -              *sct = &server_command_task_struct;
 +
 +      ret = task_get_notification(sct->task);
 +      if (ret < 0)
 +              return ret;
 +      for (n = 0; n < sct->num_listen_fds; n++) {
 +              ret = command_task_accept(n, s, sct);
 +              if (ret < 0) {
 +                      free(sct->listen_fds);
 +                      return ret;
 +              }
 +      }
 +      return 0;
 +}
 +
 +static void init_server_command_task(struct server_command_task *sct,
 +              int argc, char **argv)
 +{
 +      int ret;
 +      unsigned n;
 +      uint32_t port = OPT_UINT32_VAL(PORT);
  
        PARA_NOTICE_LOG("initializing tcp command socket\n");
 +      sct->child_fd = -1;
        sct->argc = argc;
        sct->argv = argv;
 -      ret = para_listen_simple(IPPROTO_TCP, OPT_UINT32_VAL(PORT));
 -      if (ret < 0)
 -              goto err;
 -      sct->listen_fd = ret;
 -      ret = mark_fd_nonblocking(sct->listen_fd);
 -      if (ret < 0)
 -              goto err;
 -      add_close_on_fork_list(sct->listen_fd); /* child doesn't need the listener */
 +      if (!OPT_GIVEN(LISTEN_ADDRESS)) {
 +              sct->num_listen_fds = 1;
 +              sct->listen_fds = para_malloc(sizeof(int));
 +              ret = para_listen_simple(IPPROTO_TCP, port);
 +              if (ret < 0)
 +                      goto err;
 +              sct->listen_fds[0] = ret;
 +      } else {
 +              sct->num_listen_fds = OPT_GIVEN(LISTEN_ADDRESS);
 +              sct->listen_fds = para_malloc(sct->num_listen_fds * sizeof(int));
 +              for (n = 0; n < OPT_GIVEN(LISTEN_ADDRESS); n++) {
 +                      const char *arg;
 +                      arg = lls_string_val(n, OPT_RESULT(LISTEN_ADDRESS));
 +                      ret = para_listen(IPPROTO_TCP, arg, port);
 +                      if (ret < 0)
 +                              goto err;
 +                      sct->listen_fds[n] = ret;
 +              }
 +      }
 +      for (n = 0; n < sct->num_listen_fds; n++) {
 +              ret = mark_fd_nonblocking(sct->listen_fds[n]);
 +              if (ret < 0)
 +                      goto err;
 +              /* child doesn't need the listener */
 +              add_close_on_fork_list(sct->listen_fds[n]);
 +      }
 +
        sct->task = task_register(&(struct task_info) {
                .name = "server command",
                .pre_select = command_pre_select,
@@@ -546,8 -421,6 +511,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;
@@@ -585,7 -458,7 +550,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));
@@@ -685,21 -556,6 +650,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();
 +      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);
  }