]> git.tuebingen.mpg.de Git - paraslash.git/commitdiff
Merge branch 'refs/heads/t/ll'
authorAndre Noll <maan@tuebingen.mpg.de>
Sun, 18 Sep 2022 14:28:33 +0000 (16:28 +0200)
committerAndre Noll <maan@tuebingen.mpg.de>
Sun, 18 Sep 2022 14:35:09 +0000 (16:35 +0200)
Two little cleanups related to the logging facility and two commits
which add the ll command to para_server and para_audiod.

The merge resulted in a conflict in afs.c due to the earlier merge of
the poll topic branch which replaced all calls to select() by calls
to poll(). The implementation of the ll server command introduced a
new caller of select(), afs_select(), which needs to be replaced by
afs_poll() to resolve the conflict.

* refs/heads/t/ll:
  New server command: ll to change the log level at runtime.
  New audiod command: ll to change the log level at runtime.
  daemon: Kill get_loglevel_by_name().
  server/audiod: Don't parse loglevel argument unnecessarily.

13 files changed:
1  2 
NEWS.md
afs.c
audioc.c
audiod.c
audiod_command.c
client.c
command.c
error.h
interactive.c
interactive.h
server.c
string.c
string.h

diff --combined NEWS.md
index 0f2eec0d5e232f5ff11a74bd11860e77ea0e7e56,e9713d9826ff74e25aa01ceaad9a9db13f3113a5..e5b9901054062c92667c3ab403daa5960dd90603
+++ b/NEWS.md
@@@ -5,17 -5,6 +5,17 @@@ NEW
  0.7.1 (to be announced) "digital spindrift"
  -------------------------------------------
  
 +- The autogen.sh script now only creates the autoconf specific files
 +  but no longer runs configure, make and the test suite.
 +- A stripped down copy of the discontinued libmp4ff library has become
 +  part of the paraslash code base. As a result it is no longer necessary
 +  to install faad from source to get support for aac/m4a files. The
 +  faad decoder package must still be installed.
++- The log level of the running daemon can now be changed with the
++  new ll command. It is available for para_server and para_audiod.
 +- All calls to select(2) have been replaced by calls to poll(2)
 +  to avoid known shortcomings of the select API.
 +
  [tarball](./releases/paraslash-git.tar.xz)
  
  ----------------------------------
diff --combined afs.c
index febe13b3e7c561cf2082ab9200c6578806ab8026,21ab9ae87251cce0b88741aecc9b0023755521db..f29080290424d95506ec643df567e24c2d095a74
--- 1/afs.c
--- 2/afs.c
+++ b/afs.c
@@@ -24,6 -24,7 +24,7 @@@
  #include "afs.h"
  #include "net.h"
  #include "server.h"
+ #include "daemon.h"
  #include "ipc.h"
  #include "list.h"
  #include "sched.h"
@@@ -707,7 -708,7 +708,7 @@@ static int open_afs_tables(void
        return ret;
  }
  
 -static int afs_signal_post_select(struct sched *s, __a_unused void *context)
 +static int afs_signal_post_monitor(struct sched *s, __a_unused void *context)
  {
        int signum, ret;
  
                PARA_EMERG_LOG("para_server died\n");
                goto shutdown;
        }
 -      signum = para_next_signal(&s->rfds);
 +      signum = para_next_signal();
        if (signum == 0)
                return 0;
        if (signum == SIGHUP) {
@@@ -743,8 -744,8 +744,8 @@@ static void register_signal_task(struc
  
        signal_task->task = task_register(&(struct task_info) {
                .name = "signal",
 -              .pre_select = signal_pre_select,
 -              .post_select = afs_signal_post_select,
 +              .pre_monitor = signal_pre_monitor,
 +              .post_monitor = afs_signal_post_monitor,
                .context = signal_task,
  
        }, s);
@@@ -762,15 -763,15 +763,15 @@@ struct afs_client 
        struct timeval connect_time;
  };
  
 -static void command_pre_select(struct sched *s, void *context)
 +static void command_pre_monitor(struct sched *s, void *context)
  {
        struct command_task *ct = context;
        struct afs_client *client;
  
 -      para_fd_set(server_socket, &s->rfds, &s->max_fileno);
 -      para_fd_set(ct->fd, &s->rfds, &s->max_fileno);
 +      sched_monitor_readfd(server_socket, s);
 +      sched_monitor_readfd(ct->fd, s);
        list_for_each_entry(client, &afs_client_list, node)
 -              para_fd_set(client->fd, &s->rfds, &s->max_fileno);
 +              sched_monitor_readfd(client->fd, s);
  }
  
  /**
@@@ -862,11 -863,11 +863,11 @@@ static int call_callback(int fd, int qu
        return ret;
  }
  
 -static int execute_server_command(fd_set *rfds)
 +static int execute_server_command(void)
  {
        char buf[8];
        size_t n;
 -      int ret = read_nonblock(server_socket, buf, sizeof(buf) - 1, rfds, &n);
 +      int ret = read_nonblock(server_socket, buf, sizeof(buf) - 1, &n);
  
        if (ret < 0 || n == 0)
                return ret;
  }
  
  /* returns 0 if no data available, 1 else */
 -static int execute_afs_command(int fd, fd_set *rfds)
 +static int execute_afs_command(int fd)
  {
        uint32_t cookie;
        int query_shmid;
        char buf[sizeof(cookie) + sizeof(query_shmid)];
        size_t n;
 -      int ret = read_nonblock(fd, buf, sizeof(buf), rfds, &n);
 +      int ret = read_nonblock(fd, buf, sizeof(buf), &n);
  
        if (ret < 0)
                goto err;
@@@ -917,7 -918,7 +918,7 @@@ err
  /** Shutdown connection if query has not arrived until this many seconds. */
  #define AFS_CLIENT_TIMEOUT 3
  
 -static int command_post_select(struct sched *s, void *context)
 +static int command_post_monitor(struct sched *s, void *context)
  {
        struct command_task *ct = context;
        struct sockaddr_un unix_addr;
        ret = task_get_notification(ct->task);
        if (ret < 0)
                return ret;
 -      ret = execute_server_command(&s->rfds);
 +      ret = execute_server_command();
        if (ret < 0) {
                PARA_EMERG_LOG("%s\n", para_strerror(-ret));
                task_notify_all(s, -ret);
        }
        /* Check the list of connected clients. */
        list_for_each_entry_safe(client, tmp, &afs_client_list, node) {
 -              ret = execute_afs_command(client->fd, &s->rfds);
 +              ret = execute_afs_command(client->fd);
                if (ret == 0) { /* prevent bogus connection flooding */
                        struct timeval diff;
                        tv_diff(now, &client->connect_time, &diff);
                free(client);
        }
        /* Accept connections on the local socket. */
 -      ret = para_accept(ct->fd, &s->rfds, &unix_addr, sizeof(unix_addr), &fd);
 +      ret = para_accept(ct->fd, &unix_addr, sizeof(unix_addr), &fd);
        if (ret < 0)
                PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
        if (ret <= 0)
@@@ -973,12 -974,21 +974,20 @@@ static void register_command_task(struc
  
        ct->task = task_register(&(struct task_info) {
                .name = "afs command",
 -              .pre_select = command_pre_select,
 -              .post_select = command_post_select,
 +              .pre_monitor = command_pre_monitor,
 +              .post_monitor = command_post_monitor,
                .context = ct,
        }, s);
  }
  
 -static int afs_select(int max_fileno, fd_set *readfds, fd_set *writefds,
 -              struct timeval *timeout_tv)
++static int afs_poll(struct pollfd *fds, nfds_t nfds, int timeout)
+ {
+       mutex_lock(mmd_mutex);
+       daemon_set_loglevel(mmd->loglevel);
+       mutex_unlock(mmd_mutex);
 -      return para_select(max_fileno + 1, readfds, writefds, timeout_tv);
++      return xpoll(fds, nfds, timeout);
+ }
  /**
   * Initialize the audio file selector process.
   *
@@@ -1003,7 -1013,9 +1012,8 @@@ __noreturn void afs_init(int socket_fd
        PARA_INFO_LOG("server_socket: %d\n", server_socket);
        init_admissible_files(OPT_STRING_VAL(AFS_INITIAL_MODE));
        register_command_task(&s);
 -      s.select_function = afs_select;
 -      s.default_timeout.tv_sec = 0;
 -      s.default_timeout.tv_usec = 999 * 1000;
 +      s.default_timeout = 1000;
++      s.poll_function = afs_poll;
        ret = write(socket_fd, "\0", 1);
        if (ret != 1) {
                if (ret == 0)
diff --combined audioc.c
index 2506c3f8ea6149df47bc84fcb6e5b1b608e70cc5,f96922252ea91190bc647f77bd41506765e48149..5f91e3b7d925bf4bd34f67ef0cfeb059a60cef8f
+++ b/audioc.c
@@@ -107,6 -107,12 +107,12 @@@ static void help_completer(struct i9e_c
        cr->matches = i9e_complete_commands(ci->word, audiod_completers);
  }
  
+ static void ll_completer(struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr)
+ {
+       i9e_ll_completer(ci, cr);
+ }
  static void version_completer(struct i9e_completion_info *ci,
                struct i9e_completion_result *cr)
  {
@@@ -143,17 -149,17 +149,17 @@@ static struct i9e_completer audiod_comp
        {.name = NULL}
  };
  
 -static void audioc_pre_select(struct sched *s, void *context)
 +static void audioc_pre_monitor(struct sched *s, void *context)
  {
        struct audioc_task *at = context;
        int ret = btr_node_status(at->btrn, 0, BTR_NT_ROOT);
  
        if (ret < 0)
                sched_min_delay(s);
 -      para_fd_set(at->fd, &s->rfds, &s->max_fileno);
 +      sched_monitor_readfd(at->fd, s);
  }
  
 -static int audioc_post_select(struct sched *s, void *context)
 +static int audioc_post_monitor(struct sched *s, void *context)
  {
        char *buf = NULL;
        struct audioc_task *at = context;
  
        if (ret < 0)
                goto out;
 -      if (!FD_ISSET(at->fd, &s->rfds))
 +      if (!sched_read_ok(at->fd, s))
                return 0;
        bufsize = PARA_MAX(1024U, OPT_UINT32_VAL(BUFSIZE));
        buf = para_malloc(bufsize);
@@@ -211,8 -217,8 +217,8 @@@ static int audioc_i9e_line_handler(cha
                EMBRACE(.name = "audioc line handler"));
        at->task = task_register(&(struct task_info) {
                .name = "audioc",
 -              .pre_select = audioc_pre_select,
 -              .post_select = audioc_post_select,
 +              .pre_monitor = audioc_pre_monitor,
 +              .post_monitor = audioc_post_monitor,
                .context = at,
        }, &sched);
        i9e_attach_to_stdout(at->btrn);
@@@ -250,9 -256,9 +256,9 @@@ __noreturn static void interactive_sess
        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;
        sigaction(SIGINT, &act, NULL);
 -      sched.select_function = i9e_select;
 +      sched.poll_function = i9e_poll;
  
 -      sched.default_timeout.tv_sec = 1;
 +      sched.default_timeout = 1000;
        ret = i9e_open(&ici, &sched);
        if (ret < 0)
                goto out;
diff --combined audiod.c
index a084558b167af829d2c0f6a0fbd71a2610c1f3b0,85c14a59074db417057a6ea7ed607cf37406b616..c29cf3ad472dc56a3b5328d2ca9e85e8ea45e288
+++ b/audiod.c
@@@ -44,8 -44,6 +44,6 @@@ static struct lls_parse_result *lpr
  #define OPT_GIVEN(_name) (lls_opt_given(OPT_RESULT(_name)))
  #define OPT_STRING_VAL(_name) (lls_string_val(0, OPT_RESULT(_name)))
  #define OPT_UINT32_VAL(_name) (lls_uint32_val(0, OPT_RESULT(_name)))
- #define ENUM_STRING_VAL(_name) (lls_enum_string_val(OPT_UINT32_VAL(_name), \
-       lls_opt(LSG_AUDIOD_PARA_AUDIOD_OPT_ ## _name, CMD_PTR)))
  
  __printf_2_3 void (*para_log)(int, const char*, ...) = daemon_log;
  /** define the array containing all supported audio formats */
@@@ -123,7 -121,7 +121,7 @@@ enum vss_status_flags 
   * This is needed also in audiod_command.c (for the tasks command), so it can
   * not be made static.
   */
 -struct sched sched = {.max_fileno = 0};
 +struct sched sched = {.timeout = 0};
  
  /* The task for obtaining para_server's status (para_client stat). */
  struct status_task {
@@@ -389,7 -387,7 +387,7 @@@ static void parse_config_or_die(void
                        para_strerror(-ret));
                exit(EXIT_FAILURE);
        }
-       daemon_set_loglevel(ENUM_STRING_VAL(LOGLEVEL));
+       daemon_set_loglevel(OPT_UINT32_VAL(LOGLEVEL));
        n = OPT_GIVEN(USER_ALLOW);
        if (n == 0)
                return;
@@@ -584,8 -582,8 +582,8 @@@ static void open_filters(struct slot_in
                sprintf(buf, "%s (slot %d)", name, (int)(s - slot));
                fn->task = task_register(&(struct task_info) {
                        .name = buf,
 -                      .pre_select = f->pre_select,
 -                      .post_select = f->post_select,
 +                      .pre_monitor = f->pre_monitor,
 +                      .post_monitor = f->post_monitor,
                        .context = fn,
                }, &sched);
                parent = fn->btrn;
@@@ -648,8 -646,8 +646,8 @@@ static int open_receiver(int format
                audio_formats[format], name, slot_num);
        rn->task = task_register(&(struct task_info) {
                .name = name,
 -              .pre_select = r->pre_select,
 -              .post_select = r->post_select,
 +              .pre_monitor = r->pre_monitor,
 +              .post_monitor = r->post_monitor,
                .context = rn,
        }, &sched);
        return slot_num;
@@@ -749,8 -747,8 +747,8 @@@ static void compute_time_diff(const str
        if (count > 5) {
                int s = tv_diff(&diff, &stat_task->sa_time_diff, &tmp);
                if (tv_diff(&max_deviation, &tmp, NULL) < 0)
 -                      PARA_WARNING_LOG("time diff jump: %lums\n",
 -                              s * tv2ms(&tmp));
 +                      PARA_WARNING_LOG("time diff jump: %c%lums\n",
 +                              s < 0? '-' : '+', tv2ms(&tmp));
        }
        count++;
        sa_time_diff_sign = tv_convex_combination(
@@@ -1055,7 -1053,7 +1053,7 @@@ static void init_local_socket(struct co
        exit(EXIT_FAILURE);
  }
  
 -static int signal_post_select(struct sched *s, void *context)
 +static int signal_post_monitor(struct sched *s, void *context)
  {
        struct signal_task *st = context;
        int ret, signum;
        ret = task_get_notification(st->task);
        if (ret < 0)
                return ret;
 -      signum = para_next_signal(&s->rfds);
 +      signum = para_next_signal();
        switch (signum) {
        case SIGINT:
        case SIGTERM:
        return 0;
  }
  
 -static void command_pre_select(struct sched *s, void *context)
 +static void command_pre_monitor(struct sched *s, void *context)
  {
        struct command_task *ct = context;
 -      para_fd_set(ct->fd, &s->rfds, &s->max_fileno);
 +      sched_monitor_readfd(ct->fd, s);
  }
  
 -static int command_post_select(struct sched *s, void *context)
 +static int command_post_monitor(struct sched *s, void *context)
  {
        int ret;
        struct command_task *ct = context;
        ret = task_get_notification(ct->task);
        if (ret < 0)
                return ret;
 -      ret = handle_connect(ct->fd, &s->rfds);
 +      ret = dispatch_local_connection(ct->fd);
        if (ret < 0) {
 -              PARA_ERROR_LOG("%s\n", para_strerror(-ret));
 +              PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
                if (ret == -E_AUDIOD_TERM) {
                        task_notify_all(s, -ret);
                        return ret;
@@@ -1132,8 -1130,8 +1130,8 @@@ static void init_command_task(struct co
  
        ct->task = task_register(&(struct task_info) {
                .name = "command",
 -              .pre_select = command_pre_select,
 -              .post_select = command_post_select,
 +              .pre_monitor = command_pre_monitor,
 +              .post_monitor = command_post_monitor,
                .context = ct,
        }, &sched);
  }
@@@ -1254,7 -1252,7 +1252,7 @@@ static void start_stop_decoders(void
        audiod_status_dump(true);
  }
  
 -static void status_pre_select(struct sched *s, void *context)
 +static void status_pre_monitor(struct sched *s, void *context)
  {
        struct status_task *st = context;
        int i, ret, cafn = stat_task->current_audio_format_num;
@@@ -1286,7 -1284,7 +1284,7 @@@ min_delay
  }
  
  /* restart the client task if necessary */
 -static int status_post_select(struct sched *s, void *context)
 +static int status_post_monitor(struct sched *s, void *context)
  {
        struct status_task *st = context;
        int ret;
@@@ -1377,8 -1375,8 +1375,8 @@@ static void init_status_task(struct sta
  
        stat_task->task = task_register(&(struct task_info) {
                .name = "stat",
 -              .pre_select = status_pre_select,
 -              .post_select = status_post_select,
 +              .pre_monitor = status_pre_monitor,
 +              .post_monitor = status_post_monitor,
                .context = stat_task,
        }, &sched);
  }
@@@ -1462,7 -1460,7 +1460,7 @@@ int main(int argc, char *argv[]
        ret = lls(lls_parse(argc, argv, CMD_PTR, &lpr, &errctx));
        if (ret < 0)
                goto out;
-       daemon_set_loglevel(ENUM_STRING_VAL(LOGLEVEL));
+       daemon_set_loglevel(OPT_UINT32_VAL(LOGLEVEL));
        daemon_drop_privileges_or_die(OPT_STRING_VAL(USER),
                OPT_STRING_VAL(GROUP));
        version_handle_flag("audiod", OPT_GIVEN(VERSION));
  
        signal_task->task = task_register(&(struct task_info) {
                .name = "signal",
 -              .pre_select = signal_pre_select,
 -              .post_select = signal_post_select,
 +              .pre_monitor = signal_pre_monitor,
 +              .post_monitor = signal_post_monitor,
                .context = signal_task,
        }, &sched);
  
 -      sched.default_timeout.tv_sec = 2;
 -      sched.default_timeout.tv_usec = 999 * 1000;
 +      sched.default_timeout = 2999;
        ret = schedule(&sched);
        audiod_cleanup();
        sched_shutdown(&sched);
diff --combined audiod_command.c
index 795e2ac84657150a1a8d64947e30a7ba4a217796,40f918569f389239b72d96b4968edde2b915334e..29c330f36a526d1004cb8595248fb75008033438
@@@ -240,6 -240,42 +240,42 @@@ static int com_help(int fd, struct lls_
  }
  EXPORT_AUDIOD_CMD_HANDLER(help)
  
+ static int com_ll(int fd, struct lls_parse_result *lpr)
+ {
+       unsigned ll;
+       char *errctx;
+       const char *sev[] = {SEVERITIES};
+       const char *arg;
+       int ret = lls(lls_check_arg_count(lpr, 0, 1, &errctx));
+       if (ret < 0) {
+               char *tmp = make_message("%s\n", errctx);
+               free(errctx);
+               client_write(fd, tmp);
+               free(tmp);
+               return ret;
+       }
+       if (lls_num_inputs(lpr) == 0) {
+               char *msg;
+               ll = daemon_get_loglevel();
+               msg = make_message("%s\n", sev[ll]);
+               ret = client_write(fd, msg);
+               free(msg);
+               return ret;
+       }
+       arg = lls_input(0, lpr);
+       for (ll = 0; ll < NUM_LOGLEVELS; ll++) {
+               if (!strcmp(arg, sev[ll]))
+                       break;
+       }
+       if (ll >= NUM_LOGLEVELS)
+               return -ERRNO_TO_PARA_ERROR(EINVAL);
+       PARA_INFO_LOG("new log level: %s\n", sev[ll]);
+       daemon_set_loglevel(ll);
+       return 1;
+ }
+ EXPORT_AUDIOD_CMD_HANDLER(ll)
  static int com_tasks(int fd, __a_unused struct lls_parse_result *lpr)
  {
        int ret;
@@@ -360,9 -396,10 +396,9 @@@ EXPORT_AUDIOD_CMD_HANDLER(version
   * Handle arriving connections on the local socket.
   *
   * \param accept_fd The fd to accept connections on.
 - * \param rfds If \a accept_fd is not set in \a rfds, do nothing.
   *
 - * This is called in each iteration of the select loop. If there is an incoming
 - * connection on \a accept_fd, this function reads the command sent by the peer,
 + * This is called in each iteration of the main loop of the scheduler. If there
 + * is an incoming connection, the function reads the command sent by the peer,
   * checks the connecting user's permissions by using unix socket credentials
   * (if supported by the OS) and calls the corresponding command handler if
   * permissions are OK.
   * connection to accept.
   *
   * \sa \ref para_accept(), \ref recv_cred_buffer().
 - * */
 -int handle_connect(int accept_fd, fd_set *rfds)
 + */
 +int dispatch_local_connection(int accept_fd)
  {
        int argc, ret, clifd;
        char buf[MAXLINE], **argv = NULL;
        char *errctx = NULL;
        const struct audiod_command_info *aci;
  
 -      ret = para_accept(accept_fd, rfds, &unix_addr, sizeof(struct sockaddr_un), &clifd);
 +      ret = para_accept(accept_fd, &unix_addr, sizeof(struct sockaddr_un), &clifd);
        if (ret <= 0)
                return ret;
        ret = recv_cred_buffer(clifd, buf, sizeof(buf) - 1);
diff --combined client.c
index ed0d5e02f7881409b4f5c5ce9735a5a42e486555,63f09659cff6a1875d1fcbe62e2867d1d61ce5d1..36e851f4b571a2dc095290f18f5e0d96afc5331e
+++ b/client.c
@@@ -42,7 -42,7 +42,7 @@@ struct exec_task 
        size_t result_size;
  };
  
 -static void exec_pre_select(struct sched *s, void *context)
 +static void exec_pre_monitor(struct sched *s, void *context)
  {
        struct exec_task *et = context;
        int ret = btr_node_status(et->btrn, 0, BTR_NT_LEAF);
@@@ -51,7 -51,7 +51,7 @@@
                sched_min_delay(s);
  }
  
 -static int exec_post_select(__a_unused struct sched *s, void *context)
 +static int exec_post_monitor(__a_unused struct sched *s, void *context)
  {
        struct exec_task *et = context;
        struct btr_node *btrn = et->btrn;
@@@ -123,7 -123,7 +123,7 @@@ fail
  static int execute_client_command(const char *cmd, char **result)
  {
        int ret;
 -      struct sched command_sched = {.default_timeout = {.tv_sec = 1}};
 +      struct sched command_sched = {.default_timeout = 1000};
        struct exec_task exec_task = {
                .result_buf = para_strdup(""),
                .result_size = 1,
                EMBRACE(.name = "exec_collect"));
        exec_task.task = task_register(&(struct task_info) {
                .name = "client exec",
 -              .pre_select = exec_pre_select,
 -              .post_select = exec_post_select,
 +              .pre_monitor = exec_pre_monitor,
 +              .post_monitor = exec_post_monitor,
                .context = &exec_task,
        }, &command_sched);
        ret = client_connect(ct, &command_sched, NULL, exec_task.btrn);
@@@ -246,6 -246,12 +246,12 @@@ I9E_DUMMY_COMPLETER(init)
  
  static struct i9e_completer completers[];
  
+ static void ll_completer(struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr)
+ {
+       i9e_ll_completer(ci, cr);
+ }
  static void help_completer(struct i9e_completion_info *ci,
                struct i9e_completion_result *cr)
  {
@@@ -532,7 -538,7 +538,7 @@@ __noreturn static void interactive_sess
        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;
        sigaction(SIGINT, &act, NULL);
 -      sched.select_function = i9e_select;
 +      sched.poll_function = i9e_poll;
  
        ret = i9e_open(&ici, &sched);
        if (ret < 0)
@@@ -578,7 -584,7 +584,7 @@@ struct supervisor_task 
        struct task *task;
  };
  
 -static int supervisor_post_select(struct sched *s, void *context)
 +static int supervisor_post_monitor(struct sched *s, void *context)
  {
        struct supervisor_task *svt = context;
        int ret = task_status(ct->task);
@@@ -624,7 -630,7 +630,7 @@@ int main(int argc, char *argv[]
        int ret;
  
        crypt_init();
 -      sched.default_timeout.tv_sec = 1;
 +      sched.default_timeout = 1000;
  
        ret = client_parse_config(argc, argv, &ct, &client_loglevel);
        if (ret < 0)
                EMBRACE(.name = "stdout", .parent = ct->btrn[0]));
        supervisor_task.task = task_register(&(struct task_info) {
                .name = "supervisor",
 -              .post_select = supervisor_post_select,
 +              .post_monitor = supervisor_post_monitor,
                .context = &supervisor_task,
        }, &sched);
  
diff --combined command.c
index 200ff054b35f0735cdb41cf09eda14b8b9b77826,bb276a89b4acebf391a0875d8f81e1033d2130de..0f47110e54b7e661e59b8895e7c79847ad35bae4
+++ b/command.c
@@@ -10,7 -10,6 +10,6 @@@
  #include <netdb.h>
  #include <lopsub.h>
  
- #include "server.lsg.h"
  #include "para.h"
  #include "error.h"
  #include "lsu.h"
@@@ -22,8 -21,8 +21,8 @@@
  #include "net.h"
  #include "server.h"
  #include "list.h"
 -#include "send.h"
  #include "sched.h"
 +#include "send.h"
  #include "vss.h"
  #include "daemon.h"
  #include "fd.h"
@@@ -391,7 -390,6 +390,6 @@@ static int com_si(struct command_contex
                "server_pid: %d\n"
                "afs_pid: %d\n"
                "connections (active/accepted/total): %u/%u/%u\n"
-               "current loglevel: %s\n"
                "supported audio formats: %s\n",
                ut, mmd->num_played,
                (int)getppid(),
                mmd->active_connections,
                mmd->num_commands,
                mmd->num_connects,
-               ENUM_STRING_VAL(LOGLEVEL),
                AUDIO_FORMAT_HANDLERS
        );
        mutex_unlock(mmd_mutex);
@@@ -590,6 -587,46 +587,46 @@@ static int com_hup(__a_unused struct co
  }
  EXPORT_SERVER_CMD_HANDLER(hup);
  
+ static int com_ll(struct command_context *cc, struct lls_parse_result *lpr)
+ {
+       unsigned ll, perms;
+       char *errctx;
+       const char *sev[] = {SEVERITIES}, *arg;
+       int 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) { /* reporting is an unprivileged op. */
+               const char *severity;
+               mutex_lock(mmd_mutex);
+               severity = sev[mmd->loglevel];
+               mutex_unlock(mmd_mutex);
+               return send_sb_va(&cc->scc, SBD_OUTPUT, "%s\n", severity);
+       }
+       /*
+        * Changing the loglevel changes the state of both the afs and the vss,
+        * so we require both AFS_WRITE and VSS_WRITE.
+        */
+       perms = AFS_WRITE | VSS_WRITE;
+       if ((cc->u->perms & perms) != perms)
+               return -ERRNO_TO_PARA_ERROR(EPERM);
+       arg = lls_input(0, lpr);
+       for (ll = 0; ll < NUM_LOGLEVELS; ll++)
+               if (!strcmp(arg, sev[ll]))
+                       break;
+       if (ll >= NUM_LOGLEVELS)
+               return -ERRNO_TO_PARA_ERROR(EINVAL);
+       PARA_INFO_LOG("new log level: %s\n", sev[ll]);
+       /* Ask the server and afs processes to adjust their log level. */
+       mutex_lock(mmd_mutex);
+       mmd->loglevel = ll;
+       mutex_unlock(mmd_mutex);
+       return 1;
+ }
+ EXPORT_SERVER_CMD_HANDLER(ll);
  static int com_term(__a_unused struct command_context *cc,
                __a_unused struct lls_parse_result *lpr)
  {
diff --combined error.h
index 40081b6b3551712f051ed3ecc4d1ca40ebf2d4f8,468910d00a50af2416e895087445794c85a1b2be..f119b87ec828b0d99a1dcddd6ff2f85dbb244b2f
+++ b/error.h
@@@ -51,7 -51,6 +51,6 @@@
        PARA_ERROR(BAD_CT, "invalid chunk table or bad FEC configuration"), \
        PARA_ERROR(BAD_FEATURE, "invalid feature request"), \
        PARA_ERROR(BAD_FEC_HEADER, "invalid fec header"), \
-       PARA_ERROR(BAD_LL, "invalid loglevel"), \
        PARA_ERROR(BAD_PATH, "invalid path"), \
        PARA_ERROR(BAD_PRIVATE_KEY, "invalid private key"), \
        PARA_ERROR(BAD_SAMPLE_FORMAT, "sample format not supported"), \
        PARA_ERROR(MP3DEC_CORRUPT, "too many corrupt frames"), \
        PARA_ERROR(MP3DEC_EOF, "mp3dec: end of file"), \
        PARA_ERROR(MP3_INFO, "could not read mp3 info"), \
 -      PARA_ERROR(MP4FF_BAD_CHANNEL_COUNT, "mp4ff: invalid number of channels"), \
 -      PARA_ERROR(MP4FF_BAD_SAMPLE, "mp4ff: invalid sample number"), \
 -      PARA_ERROR(MP4FF_BAD_SAMPLERATE, "mp4ff: invalid sample rate"), \
 -      PARA_ERROR(MP4FF_BAD_SAMPLE_COUNT, "mp4ff: invalid number of samples"), \
 -      PARA_ERROR(MP4FF_META_READ, "mp4ff: could not read mp4 metadata"), \
 -      PARA_ERROR(MP4FF_META_WRITE, "mp4ff: could not update mp4 metadata"), \
 -      PARA_ERROR(MP4FF_OPEN, "mp4ff: open failed"), \
 -      PARA_ERROR(MP4FF_TRACK, "mp4ff: no audio track"), \
 +      PARA_ERROR(MP4_READ, "mp4: read error or unexpected end of file"), \
 +      PARA_ERROR(MP4_CORRUPT, "invalid/corrupt mp4 file"), \
 +      PARA_ERROR(MP4_BAD_SAMPLE, "mp4: invalid sample number"), \
 +      PARA_ERROR(MP4_BAD_SAMPLERATE, "mp4: invalid sample rate"), \
 +      PARA_ERROR(MP4_BAD_SAMPLE_COUNT, "mp4: invalid number of samples"), \
 +      PARA_ERROR(MP4_TRACK, "mp4: no audio track"), \
 +      PARA_ERROR(MP4_MISSING_ATOM, "mp4: essential atom not found"), \
        PARA_ERROR(MPI_SCAN, "could not scan multi-precision integer"), \
        PARA_ERROR(NAME_TOO_LONG, "name too long for struct sockaddr_un"), \
        PARA_ERROR(NO_AFHI, "audio format handler info required"), \
diff --combined interactive.c
index 812a7d5bef8a37bd3ebdbd5a2484b735827a3395,069c003944ae1801d9ce9e73570ea6f1cea64353..baacdc00bbdc3029052b5f2709e32673fb634557
@@@ -258,6 -258,18 +258,6 @@@ static void clear_bottom_line(void
        rl_point = point;
  }
  
 -static bool input_available(void)
 -{
 -      fd_set rfds;
 -      struct timeval tv = {0, 0};
 -      int ret;
 -
 -      FD_ZERO(&rfds);
 -      FD_SET(i9ep->ici->fds[0], &rfds);
 -      ret = para_select(1, &rfds, NULL, &tv);
 -      return ret > 0;
 -}
 -
  static void i9e_line_handler(char *line)
  {
        int ret;
@@@ -282,7 -294,7 +282,7 @@@ free_line
        free(line);
  }
  
 -static int i9e_post_select(__a_unused struct sched *s, __a_unused void *context)
 +static int i9e_post_monitor(__a_unused struct sched *s, __a_unused void *context)
  {
        int ret;
        struct i9e_client_info *ici = i9ep->ici;
        ret = 0;
        if (i9ep->caught_sigint)
                goto rm_btrn;
 -      while (input_available()) {
 +      while (read_ok(i9ep->ici->fds[0]) > 0) {
                if (i9ep->stdout_btrn) {
 -                      unsigned len = i9ep->key_sequence_length;
 -                      assert(len < sizeof(i9ep->key_sequence) - 1);
 -                      buf = i9ep->key_sequence + len;
 -                      ret = read(i9ep->ici->fds[0], buf, 1);
 -                      if (ret < 0) {
 -                              ret = -ERRNO_TO_PARA_ERROR(errno);
 -                              goto rm_btrn;
 +                      while (i9ep->key_sequence_length < sizeof(i9ep->key_sequence) - 1) {
 +                              buf = i9ep->key_sequence + i9ep->key_sequence_length;
 +                              ret = read(i9ep->ici->fds[0], buf, 1);
 +                              if (ret < 0) {
 +                                      ret = -ERRNO_TO_PARA_ERROR(errno);
 +                                      goto rm_btrn;
 +                              }
 +                              if (ret == 0) {
 +                                      ret = -E_I9E_EOF;
 +                                      goto rm_btrn;
 +                              }
 +                              buf[1] = '\0';
 +                              i9ep->key_sequence_length++;
 +                              rl_stuff_char((int)(unsigned char)*buf);
 +                              rl_callback_read_char();
 +                              if (read_ok(i9ep->ici->fds[0]) <= 0)
 +                                      break;
                        }
 -                      ret = -E_I9E_EOF;
 -                      if (ret == 0)
 -                              goto rm_btrn;
 -                      buf[1] = '\0';
 -                      i9ep->key_sequence_length++;
 -                      rl_stuff_char((int)(unsigned char)*buf);
 -              }
 -              rl_callback_read_char();
 +                      i9ep->key_sequence_length = 0;
 +              } else
 +                      rl_callback_read_char();
                ret = 0;
        }
        if (!i9ep->stdout_btrn)
@@@ -362,7 -369,7 +362,7 @@@ out
        return ret;
  }
  
 -static void i9e_pre_select(struct sched *s, __a_unused void *context)
 +static void i9e_pre_monitor(struct sched *s, __a_unused void *context)
  {
        int ret;
  
                        return;
                }
                if (ret > 0)
 -                      para_fd_set(i9ep->ici->fds[1], &s->wfds, &s->max_fileno);
 +                      sched_monitor_writefd(i9ep->ici->fds[1], s);
        }
        /*
         * fd[0] might have been reset to blocking mode if our job was moved to
        if (ret < 0)
                PARA_WARNING_LOG("set to nonblock failed: (fd0 %d, %s)\n",
                        i9ep->ici->fds[0], para_strerror(-ret));
 -      para_fd_set(i9ep->ici->fds[0], &s->rfds, &s->max_fileno);
 +      sched_monitor_readfd(i9ep->ici->fds[0], s);
  }
  
  static void update_winsize(void)
@@@ -465,8 -472,8 +465,8 @@@ int i9e_open(struct i9e_client_info *ic
                return ret;
        i9ep->task = task_register(&(struct task_info) {
                .name = "i9e",
 -              .pre_select = i9e_pre_select,
 -              .post_select = i9e_post_select,
 +              .pre_monitor = i9e_pre_monitor,
 +              .post_monitor = i9e_post_monitor,
                .context = i9ep,
        }, s);
  
@@@ -593,21 -600,23 +593,21 @@@ void i9e_signal_dispatch(int sig_num
  }
  
  /**
 - * Wrapper for select(2) which does not restart on interrupts.
 + * Wrapper for poll(2) which handles EINTR and returns paraslash error codes.
   *
 - * \param n \sa \ref para_select().
 - * \param readfds \sa \ref para_select().
 - * \param writefds \sa \ref para_select().
 - * \param timeout_tv \sa \ref para_select().
 + * \param fds See poll(2).
 + * \param nfds See poll(2).
 + * \param timeout See poll(2).
   *
 - * \return \sa \ref para_select().
 + * \return See poll(2).
   *
 - * The only difference between this function and \ref para_select() is that
 - * \ref i9e_select() returns zero if the select call returned \p EINTR.
 + * The only difference between this function and \ref xpoll() is that \ref
 + * i9e_poll() returns zero if the system call was interrupted while xpoll()
 + * restarts the system call in this case.
   */
 -int i9e_select(int n, fd_set *readfds, fd_set *writefds,
 -              struct timeval *timeout_tv)
 +int i9e_poll(struct pollfd *fds, nfds_t nfds, int timeout)
  {
 -      int ret = select(n, readfds, writefds, NULL, timeout_tv);
 -
 +      int ret = poll(fds, nfds, timeout);
        if (ret < 0) {
                if (errno == EINTR)
                        ret = 0;
@@@ -796,3 -805,25 +796,25 @@@ create_matches
        free(ci.word);
        return ret;
  }
+ /**
+  * Complete on severity strings.
+  *
+  * \param ci See struct \ref i9e_completer.
+  * \param cr See struct \ref i9e_completer.
+  *
+  * This is used by para_client and para_audioc which need the same completion
+  * primitive for the ll server/audiod command. Both define their own completer
+  * which is implemented as a trivial wrapper that calls this function.
+  */
+ void i9e_ll_completer(struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr)
+ {
+       char *sev[] = {SEVERITIES, NULL};
+       if (ci->word_num != 1) {
+               cr->matches = NULL;
+               return;
+       }
+       i9e_extract_completions(ci->word, sev, &cr->matches);
+ }
diff --combined interactive.h
index 53b1ad347e6f5b108814e8db309c5434272e9ab7,18c007b0b39172ca2443a86165119888213d4c32..6ef7f8e259b0045a5c808c01560b5a81166b01fd
@@@ -84,7 -84,8 +84,7 @@@ void i9e_print_status_bar(char *buf, un
  void i9e_close(void);
  void i9e_signal_dispatch(int sig_num);
  __printf_2_3 void i9e_log(int ll, const char* fmt,...);
 -int i9e_select(int n, fd_set *readfds, fd_set *writefds,
 -              struct timeval *timeout_tv);
 +int i9e_poll(struct pollfd *fds, nfds_t nfds, int timeout);
  int i9e_extract_completions(const char *word, char **string_list,
                char ***result);
  char **i9e_complete_commands(const char *word, struct i9e_completer *completers);
@@@ -92,3 -93,5 +92,5 @@@ void i9e_complete_option(char **opts, s
                struct i9e_completion_result *cr);
  int i9e_print_completions(struct i9e_completer *completers);
  int i9e_get_error(void);
+ void i9e_ll_completer(struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr);
diff --combined server.c
index 2c66cc279c9faa99e009ae747fd4d8a54738e2e8,b9026ad6713e665fe461a6b563f364186dc3e1cc..2852ee1f10f7a9d8d4f5a93e2233b8b6fd4a4ecc
+++ b/server.c
@@@ -24,8 -24,8 +24,8 @@@
  #include "net.h"
  #include "server.h"
  #include "list.h"
 -#include "send.h"
  #include "sched.h"
 +#include "send.h"
  #include "vss.h"
  #include "config.h"
  #include "close_on_fork.h"
@@@ -172,6 -172,7 +172,7 @@@ static void init_ipc_or_die(void
        mmd->active_connections = 0;
        mmd->vss_status_flags = VSS_NEXT;
        mmd->new_vss_status_flags = VSS_NEXT;
+       mmd->loglevel = OPT_UINT32_VAL(LOGLEVEL);
        return;
  destroy_mmd_mutex:
        mutex_destroy(mmd_mutex);
@@@ -180,6 -181,9 +181,9 @@@ err_out
        exit(EXIT_FAILURE);
  }
  
+ /** Get a reference to the supercommand of para_server. */
+ #define CMD_PTR (lls_cmd(0, server_suite))
  /**
   * (Re-)read the server configuration files.
   *
@@@ -205,7 -209,7 +209,7 @@@ void parse_config_or_die(bool reload
                        para_strerror(-ret));
                exit(EXIT_FAILURE);
        }
-       daemon_set_loglevel(ENUM_STRING_VAL(LOGLEVEL));
+       daemon_set_loglevel(OPT_UINT32_VAL(LOGLEVEL));
        if (OPT_GIVEN(LOGFILE)) {
                daemon_set_logfile(OPT_STRING_VAL(LOGFILE));
                daemon_open_log_or_die();
@@@ -250,14 -254,14 +254,14 @@@ static void handle_sighup(void
                kill(afs_pid, SIGHUP);
  }
  
 -static int signal_post_select(struct sched *s, __a_unused void *context)
 +static int signal_post_monitor(struct sched *s, __a_unused void *context)
  {
        int ret, signum;
  
        ret = task_get_notification(signal_task->task);
        if (ret < 0)
                return ret;
 -      signum = para_next_signal(&s->rfds);
 +      signum = para_next_signal();
        switch (signum) {
        case 0:
                return 0;
@@@ -313,20 -317,20 +317,20 @@@ static void init_signal_task(void
        add_close_on_fork_list(signal_task->fd);
        signal_task->task = task_register(&(struct task_info) {
                .name = "signal",
 -              .pre_select = signal_pre_select,
 -              .post_select = signal_post_select,
 +              .pre_monitor = signal_pre_monitor,
 +              .post_monitor = signal_post_monitor,
                .context = signal_task,
  
        }, &sched);
  }
  
 -static void command_pre_select(struct sched *s, void *context)
 +static void command_pre_monitor(struct sched *s, void *context)
  {
        unsigned n;
        struct server_command_task *sct = context;
  
        for (n = 0; n < sct->num_listen_fds; n++)
 -              para_fd_set(sct->listen_fds[n], &s->rfds, &s->max_fileno);
 +              sched_monitor_readfd(sct->listen_fds[n], s);
  }
  
  static int command_task_accept(unsigned listen_idx, struct sched *s,
        pid_t child_pid;
        uint32_t *chunk_table;
  
 -      ret = para_accept(sct->listen_fds[listen_idx], &s->rfds, NULL, 0, &new_fd);
 +      ret = para_accept(sct->listen_fds[listen_idx], NULL, 0, &new_fd);
        if (ret <= 0)
                goto out;
        mmd->num_connects++;
        /*
         * 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,
 +       * pending. Next it calls the ->post_monitor 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.
 +       * hold the lock at this point. Since server_poll() drops the lock
 +       * prior to calling poll(), we need to acquire it here.
         */
        mutex_lock(mmd_mutex);
        return -E_CHILD_CONTEXT;
@@@ -399,7 -403,7 +403,7 @@@ out
        return 0;
  }
  
 -static int command_post_select(struct sched *s, void *context)
 +static int command_post_monitor(struct sched *s, void *context)
  {
        struct server_command_task *sct = context;
        unsigned n;
@@@ -459,8 -463,8 +463,8 @@@ static void init_server_command_task(st
  
        sct->task = task_register(&(struct task_info) {
                .name = "server command",
 -              .pre_select = command_pre_select,
 -              .post_select = command_post_select,
 +              .pre_monitor = command_pre_monitor,
 +              .post_monitor = command_post_monitor,
                .context = sct,
        }, &sched);
        /*
@@@ -543,7 -547,7 +547,7 @@@ static void server_init(int argc, char 
        if (ret < 0)
                goto fail;
        server_lpr = cmdline_lpr;
-       daemon_set_loglevel(ENUM_STRING_VAL(LOGLEVEL));
+       daemon_set_loglevel(OPT_UINT32_VAL(LOGLEVEL));
        daemon_drop_privileges_or_die(OPT_STRING_VAL(USER),
                OPT_STRING_VAL(GROUP));
        version_handle_flag("server", OPT_GIVEN(VERSION));
@@@ -617,13 -621,15 +621,14 @@@ out
        killpg(0, SIGUSR1);
  }
  
 -static int server_select(int max_fileno, fd_set *readfds, fd_set *writefds,
 -              struct timeval *timeout_tv)
 +static int server_poll(struct pollfd *fds, nfds_t nfds, int timeout)
  {
        int ret;
  
+       daemon_set_loglevel(mmd->loglevel);
        status_refresh();
        mutex_unlock(mmd_mutex);
 -      ret = para_select(max_fileno + 1, readfds, writefds, timeout_tv);
 +      ret = xpoll(fds, nfds, timeout);
        mutex_lock(mmd_mutex);
        return ret;
  }
@@@ -657,15 -663,15 +662,15 @@@ int main(int argc, char *argv[]
        struct server_command_task server_command_task_struct,
                *sct = &server_command_task_struct;
  
 -      sched.default_timeout.tv_sec = 1;
 -      sched.select_function = server_select;
 +      sched.default_timeout = 1000;
 +      sched.poll_function = server_poll;
  
        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.
 +       * We hold the mmd lock: it was re-acquired in server_poll()
 +       * after the poll(2) call.
         */
        mutex_unlock(mmd_mutex);
        sched_shutdown(&sched);
diff --combined string.c
index f80331900de09fccf573c18666c3fa007f8ca6bf,2c69c40ab3ce79f5147ba34bded5948af3c225f1..0e04d3740b499b3db6721690d3c30419758b8371
+++ b/string.c
@@@ -245,6 -245,56 +245,6 @@@ __must_check __malloc char *para_strcat
        return tmp;
  }
  
 -/**
 - * Paraslash's version of dirname().
 - *
 - * \param name Pointer to the full path.
 - *
 - * Compute the directory component of \p name.
 - *
 - * \return If \a name is \p NULL or the empty string, return \p NULL.
 - * Otherwise, Make a copy of \a name and return its directory component. Caller
 - * is responsible to free the result.
 - */
 -__must_check __malloc char *para_dirname(const char *name)
 -{
 -      char *p, *ret;
 -
 -      if (!name || !*name)
 -              return NULL;
 -      ret = para_strdup(name);
 -      p = strrchr(ret, '/');
 -      if (!p)
 -              *ret = '\0';
 -      else
 -              *p = '\0';
 -      return ret;
 -}
 -
 -/**
 - * Paraslash's version of basename().
 - *
 - * \param name Pointer to the full path.
 - *
 - * Compute the filename component of \a name.
 - *
 - * \return \p NULL if (a) \a name is the empty string or \p NULL, or (b) name
 - * ends with a slash.  Otherwise, a pointer within \a name is returned.  Caller
 - * must not free the result.
 - */
 -__must_check char *para_basename(const char *name)
 -{
 -      char *ret;
 -
 -      if (!name || !*name)
 -              return NULL;
 -      ret = strrchr(name, '/');
 -      if (!ret)
 -              return (char *)name;
 -      ret++;
 -      return ret;
 -}
 -
  /**
   * Get the logname of the current user.
   *
@@@ -552,37 -602,6 +552,6 @@@ int para_atoi32(const char *str, int32_
        return 1;
  }
  
- static inline int loglevel_equal(const char *arg, const char * const ll)
- {
-       return !strncasecmp(arg, ll, strlen(ll));
- }
- /**
-  * Compute the loglevel number from its name.
-  *
-  * \param txt The name of the loglevel (debug, info, ...).
-  *
-  * \return The numeric representation of the loglevel name.
-  */
- int get_loglevel_by_name(const char *txt)
- {
-       if (loglevel_equal(txt, "debug"))
-               return LL_DEBUG;
-       if (loglevel_equal(txt, "info"))
-               return LL_INFO;
-       if (loglevel_equal(txt, "notice"))
-               return LL_NOTICE;
-       if (loglevel_equal(txt, "warning"))
-               return LL_WARNING;
-       if (loglevel_equal(txt, "error"))
-               return LL_ERROR;
-       if (loglevel_equal(txt, "crit"))
-               return LL_CRIT;
-       if (loglevel_equal(txt, "emerg"))
-               return LL_EMERG;
-       return -E_BAD_LL;
- }
  static int get_next_word(const char *buf, const char *delim, char **word)
  {
        enum line_state_flags {LSF_HAVE_WORD = 1, LSF_BACKSLASH = 2,
diff --combined string.h
index 08b9965f57d94b52a19dd231c38b2d53aabb824a,79ccb67eacf6c4604223f92a120c72b3f698ea52..84b6f787924f18a805eea6b5d2730a4ffa230159
+++ b/string.h
@@@ -76,13 -76,14 +76,12 @@@ __printf_2_0 unsigned xvasprintf(char *
  __printf_2_3 unsigned xasprintf(char **result, const char *fmt, ...);
  __must_check __malloc __printf_1_2 char *make_message(const char *fmt, ...);
  __must_check __malloc char *para_strcat(char *a, const char *b);
 -__must_check __malloc char *para_dirname(const char *name);
 -__must_check char *para_basename(const char *name);
  __must_check __malloc char *para_logname(void);
  __must_check __malloc char *para_homedir(void);
  __malloc char *para_hostname(void);
  __printf_2_3 int para_printf(struct para_buffer *b, const char *fmt, ...);
  int para_atoi64(const char *str, int64_t *result);
  int para_atoi32(const char *str, int32_t *value);
- int get_loglevel_by_name(const char *txt);
  int read_size_header(const char *buf);
  int create_argv(const char *buf, const char *delim, char ***result);
  int create_shifted_argv(const char *buf, const char *delim, char ***result);