Merge branch 't/sched_improvements'
authorAndre Noll <maan@systemlinux.org>
Wed, 23 Jul 2014 15:29:29 +0000 (17:29 +0200)
committerAndre Noll <maan@systemlinux.org>
Wed, 23 Jul 2014 15:45:26 +0000 (17:45 +0200)
Cooking for two months. This merge required to also patch gui.c due
to semantic conflics against the changes introduced by the gui_sched
branch which was merged to master in commit d15d8509 two weeks ago.
Also a small fix for server.c is needed to squelch a compiler warning
since the global "now" variable has become a const pointer.

* t/sched_improvements: (36 commits)
  audiod: Fix use after free on exit.
  sched: Mark global now pointer as const.
  sched: Directly pass context pointer to pre/post_select().
  sched: kill task->dead.
  sched: Do not shadow task_info in struct task.
  sched: Dont use fixed-size buffer for task names.
  sched: Rename task->error to tast->status.
  sched: Rename task->status to task->name.
  sched: Make struct task private to sched.c.
  sched: Introduce task_status().
  sched: Remove ->owned_by_sched.
  sched: Remove register_task().
  task_register() conversion: grab client task
  task_register() conversion: audiod status task
  task_register() conversion: audiod command task
  task_register() conversion: client task
  task_register() conversion: client supervisor task
  task_register() conversion: client exec task
  task_register() conversion: afs command task
  task_register() conversion: vss task
  ...

1  2 
NEWS
audioc.c
gui.c
server.c
wmadec_filter.c

diff --cc NEWS
--- 1/NEWS
--- 2/NEWS
+++ b/NEWS
@@@ -5,8 -5,7 +5,11 @@@ NEW
  0.5.3 (to be released) "symbolic synchronization"
  -------------------------------------------------
  
++Not many new features, but lots of fixes and usability improvements.
++
 +      - para_gui has been converted to use the paraslash scheduler.
        - Various alsa-related fixes, mostly for the raspberry pi.
++      - Many scheduler improvements and cleanups.
        - The test suite has been extended to include sanity checks
          for the generated man pages.
        - ao_writer fixes. This writer was in a quite bad shape. Many
diff --cc audioc.c
Simple merge
diff --cc gui.c
--- 1/gui.c
--- 2/gui.c
+++ b/gui.c
@@@ -84,66 -80,130 +84,66 @@@ struct gui_command 
        void (*handler)(void);
  };
  
 -struct stat_item {
 -      char name[MAXLINE];
 -      char prefix[MAXLINE];
 -      char postfix[MAXLINE];
 -      unsigned y;
 -      unsigned x;
 -      unsigned len;
 -      int fg, bg;
 -      int align;
 -      char content[MAXLINE];
 +static struct gui_theme theme;
 +
 +#define GUI_COMMANDS \
 +      GUI_COMMAND(help, "?", "print help") \
 +      GUI_COMMAND(enlarge_top_win, "+", "enlarge the top window") \
 +      GUI_COMMAND(shrink_top_win, "-", "shrink the top window") \
 +      GUI_COMMAND(reread_conf, "r", "reread configuration file") \
 +      GUI_COMMAND(quit, "q", "exit para_gui") \
 +      GUI_COMMAND(refresh, "^L", "redraw the screen") \
 +      GUI_COMMAND(next_theme, ".", "switch to next theme") \
 +      GUI_COMMAND(prev_theme, ",", "switch to previous theme") \
 +      GUI_COMMAND(ll_incr, ">", "increase loglevel (decreases verbosity)") \
 +      GUI_COMMAND(ll_decr, "<", "decrease loglevel (increases verbosity)") \
 +      GUI_COMMAND(version, "V", "show the para_gui version") \
 +      GUI_COMMAND(scroll_up, "<up>", "scroll up one line") \
 +      GUI_COMMAND(scroll_down, "<down>", "scroll_down") \
 +      GUI_COMMAND(page_up, "<ppage>", "scroll up one page") \
 +      GUI_COMMAND(page_down, "<npage>", "scroll down one page") \
 +      GUI_COMMAND(scroll_top, "<home>", "scroll to top of buffer") \
 +      GUI_COMMAND(cancel_scroll, "<end>", "deactivate scroll mode") \
 +
 +/* declare command handlers */
 +#define GUI_COMMAND(_c, _k, _d) \
 +      static void com_ ## _c(void);
 +GUI_COMMANDS
 +
 +#undef GUI_COMMAND
 +
 +/* define command array */
 +#define GUI_COMMAND(_c, _k, _d) \
 +      { \
 +              .key = _k, \
 +              .name = #_c, \
 +              .description = _d, \
 +              .handler = com_ ## _c \
 +      },
 +
 +static struct gui_command command_list[] = {GUI_COMMANDS {.name = NULL}};
 +
 +struct input_task {
-       struct task task;
++      struct task *task;
  };
  
 -static struct gui_theme theme;
 +struct status_task {
-       struct task task;
++      struct task *task;
 +      pid_t pid;
 +      char *buf;
 +      int bufsize, loaded;
 +      struct timeval next_exec;
 +      int fd;
 +};
  
 -static int _argc;
 -static char **_argv;
 -
 -static void com_help(void);
 -static void com_reread_conf(void);
 -static void com_enlarge_top_win(void);
 -static void com_shrink_top_win(void);
 -static void com_version(void);
 -__noreturn static void com_quit(void);
 -static void com_refresh(void);
 -static void com_ll_incr(void);
 -static void com_ll_decr(void);
 -static void com_prev_theme(void);
 -static void com_next_theme(void);
 -static void com_scroll_up(void);
 -static void com_scroll_down(void);
 -static void com_page_up(void);
 -static void com_page_down(void);
 -static void com_cancel_scrolling(void);
 -static void com_scroll_top(void);
 -
 -static struct gui_command command_list[] = {
 -      {
 -              .key = "?",
 -              .name = "help",
 -              .description = "print help",
 -              .handler = com_help
 -      }, {
 -              .key = "+",
 -              .name = "enlarge_win",
 -              .description = "enlarge the top window",
 -              .handler = com_enlarge_top_win
 -      }, {
 -              .key = "-",
 -              .name = "shrink_win",
 -              .description = "shrink the top window",
 -              .handler = com_shrink_top_win
 -      }, {
 -              .key = "r",
 -              .name = "reread_conf",
 -              .description = "reread configuration file",
 -              .handler = com_reread_conf
 -      }, {
 -              .key = "q",
 -              .name = "quit",
 -              .description = "exit para_gui",
 -              .handler = com_quit
 -      }, {
 -              .key = "^L",
 -              .name = "refresh",
 -              .description = "redraw the screen",
 -              .handler = com_refresh
 -      }, {
 -              .key = ".",
 -              .name = "next_theme",
 -              .description = "switch to next theme",
 -              .handler = com_next_theme
 -      }, {
 -              .key = ",",
 -              .name = "prev_theme",
 -              .description = "switch to previous stream",
 -              .handler = com_prev_theme
 -      }, {
 -              .key = ">",
 -              .name = "ll_incr",
 -              .description = "increase loglevel (decreases verbosity)",
 -              .handler = com_ll_incr
 -      }, {
 -              .key = "<",
 -              .name = "ll_decr",
 -              .description = "decrease loglevel (increases verbosity)",
 -              .handler = com_ll_decr
 -      }, {
 -              .key = "V",
 -              .name = "version",
 -              .description = "show the para_gui version",
 -              .handler = com_version
 -      }, {
 -              .key = "<up>",
 -              .name = "scroll_up",
 -              .description = "scroll up one line",
 -              .handler = com_scroll_up
 -      }, {
 -              .key = "<down>",
 -              .name = "scroll_down",
 -              .description = "scroll down one line",
 -              .handler = com_scroll_down
 -      }, {
 -              .key = "<ppage>",
 -              .name = "page_up",
 -              .description = "scroll up one page",
 -              .handler = com_page_up
 -      }, {
 -              .key = "<npage>",
 -              .name = "page_down",
 -              .description = "scroll down one page",
 -              .handler = com_page_down
 -      }, {
 -              .key = "<home>",
 -              .name = "scroll_top",
 -              .description = "scroll to top of buffer",
 -              .handler = com_scroll_top
 -      }, {
 -              .key = "<end>",
 -              .name = "cancel_scroll",
 -              .description = "deactivate scroll mode",
 -              .handler = com_cancel_scrolling
 -      }, {
 -              .handler = NULL
 -      }
 +/** Stdout/stderr of the executing process is read in chunks of this size. */
 +#define COMMAND_BUF_SIZE 32768
 +
 +struct exec_task {
-       struct task task;
++      struct task *task;
 +      char command_buf[2][COMMAND_BUF_SIZE]; /* stdout/stderr of command */
 +      int cbo[2]; /* command buf offsets */
 +      unsigned flags[2]; /* passed to for_each_line() */
  };
  
  static int find_cmd_byname(char *name)
@@@ -597,151 -802,6 +597,151 @@@ static void clear_all_items(void
        }
  }
  
- static void status_pre_select(struct sched *s, struct task *t)
++static void status_pre_select(struct sched *s, void *context)
 +{
-       struct status_task *st = container_of(t, struct status_task, task);
++      struct status_task *st = context;
 +
 +      if (st->fd >= 0)
 +              para_fd_set(st->fd, &s->rfds, &s->max_fileno);
-       if (task_get_notification(t) < 0)
++      if (task_get_notification(st->task) < 0)
 +              return sched_min_delay(s);
 +      if (st->fd < 0)
 +              sched_request_barrier_or_min_delay(&st->next_exec, s);
 +}
 +
- static int status_post_select(struct sched *s, struct task *t)
++static int status_post_select(struct sched *s, void *context)
 +{
-       struct status_task *st = container_of(t, struct status_task, task);
++      struct status_task *st = context;
 +      size_t sz;
 +      int ret, ret2;
 +
-       ret = task_get_notification(t);
++      ret = task_get_notification(st->task);
 +      if (ret == -E_GUI_SIGCHLD && st->pid > 0) {
 +              int exit_status;
 +              if (waitpid(st->pid, &exit_status, WNOHANG) == st->pid) {
 +                      st->pid = 0;
 +                      PARA_ERROR_LOG("stat command exit status: %d",
 +                              exit_status);
 +              }
 +      }
 +      if (st->fd < 0) {
 +              int fds[3] = {0, 1, 0};
 +              if (st->pid > 0)
 +                      return 0;
 +              /* Avoid busy loop */
 +              if (tv_diff(&st->next_exec, now, NULL) > 0)
 +                      return 0;
 +              st->next_exec.tv_sec = now->tv_sec + 2;
 +              ret = para_exec_cmdline_pid(&st->pid, conf.stat_cmd_arg, fds);
 +              if (ret < 0)
 +                      return 0;
 +              ret = mark_fd_nonblocking(fds[1]);
 +              if (ret < 0) {
 +                      close(fds[1]);
 +                      return 0;
 +              }
 +              st->fd = fds[1];
 +              return 0;
 +      }
 +
 +      if (st->loaded >= st->bufsize) {
 +              if (st->bufsize > 1000 * 1000) {
 +                      st->loaded = 0;
 +                      return 0;
 +              }
 +              st->bufsize += st->bufsize + 1000;
 +              st->buf = para_realloc(st->buf, st->bufsize);
 +      }
 +      assert(st->loaded < st->bufsize);
 +      ret = read_nonblock(st->fd, st->buf + st->loaded,
 +              st->bufsize - st->loaded, &s->rfds, &sz);
 +      st->loaded += sz;
 +      ret2 = for_each_stat_item(st->buf, st->loaded, update_item);
 +      if (ret < 0 || ret2 < 0) {
 +              st->loaded = 0;
 +              PARA_NOTICE_LOG("closing stat pipe: %s\n", para_strerror(-ret));
 +              close(st->fd);
 +              st->fd = -1;
 +              clear_all_items();
 +              free(stat_content[SI_BASENAME]);
 +              stat_content[SI_BASENAME] =
 +                      para_strdup("stat command terminated!?");
 +              print_all_items();
 +              return 0;
 +      }
 +      sz = ret2; /* what is left */
 +      if (sz > 0 && sz < st->loaded)
 +              memmove(st->buf, st->buf + st->loaded - sz, sz);
 +      st->loaded = sz;
 +      return 0;
 +}
 +
 +/*
 + * init all windows
 + */
 +static void init_wins(int top_lines)
 +{
 +      int top_y = 0, bot_y = top_lines + 1, sb_y = LINES - 2,
 +              in_y = LINES - 1, sep_y = top_lines;
 +      int bot_lines = LINES - top_lines - 3, sb_lines = 1, in_lines = 1,
 +              sep_lines = 1;
 +
 +      assume_default_colors(theme.dflt.fg, theme.dflt.bg);
 +      if (top.win) {
 +              wresize(top.win, top_lines, COLS);
 +              mvwin(top.win, top_y, 0);
 +
 +              wresize(sb.win, sb_lines, COLS);
 +              mvwin(sb.win, sb_y, 0);
 +
 +              wresize(sep.win, sep_lines, COLS);
 +              mvwin(sep.win, sep_y, 0);
 +
 +              wresize(bot.win, bot_lines, COLS);
 +              mvwin(bot.win, bot_y, 0);
 +
 +              wresize(in.win, in_lines, COLS);
 +              mvwin(in.win, in_y, 0);
 +      } else {
 +              sep.win = newwin(sep_lines, COLS, sep_y, 0);
 +              top.win = newwin(top_lines, COLS, top_y, 0);
 +              bot.win = newwin(bot_lines, COLS, bot_y, 0);
 +              sb.win = newwin(sb_lines, COLS, sb_y, 0);
 +              in.win = newwin(in_lines, COLS, in_y, 0);
 +              if (!top.win || !bot.win || !sb.win || !in.win || !sep.win)
 +                      die(EXIT_FAILURE, "Error: Cannot create curses windows\n");
 +              wclear(bot.win);
 +              wclear(sb.win);
 +              wclear(in.win);
 +              scrollok(bot.win, 1);
 +              wattron(sb.win, COLOR_PAIR(COLOR_STATUSBAR));
 +              wattron(sep.win, COLOR_PAIR(COLOR_SEPARATOR));
 +              wattron(bot.win, COLOR_PAIR(COLOR_BOT));
 +              wattron(top.win, COLOR_PAIR(COLOR_TOP));
 +              nodelay(top.win, 1);
 +              nodelay(bot.win, 1);
 +              nodelay(sb.win, 1);
 +              nodelay(in.win, 0);
 +
 +              keypad(top.win, 1);
 +              keypad(bot.win, 1);
 +              keypad(sb.win, 1);
 +              keypad(in.win, 1);
 +      }
 +      wmove(sep.win, 0, 0);
 +      whline(sep.win, theme.sep_char, COLS);
 +      wclear(top.win);
 +      print_all_items();
 +      //wclear(bot.win);
 +      wnoutrefresh(top.win);
 +      wnoutrefresh(bot.win);
 +      print_status_bar();
 +      wnoutrefresh(sb.win);
 +      wnoutrefresh(in.win);
 +      wnoutrefresh(sep.win);
 +      doupdate();
 +}
 +
  static void init_pair_or_die(short pair, short f, short b)
  {
        if (init_pair(pair, f, b) == ERR)
@@@ -897,128 -923,206 +897,129 @@@ static void reread_conf(void
  /*
   * React to various signal-related events
   */
- static int signal_post_select(struct sched *s, __a_unused struct task *t)
 -static void handle_signal(int sig)
++static int signal_post_select(struct sched *s, __a_unused void *context)
  {
 -      switch (sig) {
 +      int ret = para_next_signal(&s->rfds);
 +
 +      if (ret <= 0)
 +              return 0;
 +      switch (ret) {
        case SIGTERM:
 -              msg_n_exit(EXIT_FAILURE,
 -                      "only the good die young (caught SIGTERM))\n");
 -              return;
 -      case SIGWINCH:
 -              if (curses_active) {
 -                      shutdown_curses();
 -                      init_curses();
 -                      redraw_bot_win();
 -              }
 -              return;
 +              die(EXIT_FAILURE, "only the good die young (caught SIGTERM)\n");
 +              return 1;
        case SIGINT:
 -              PARA_WARNING_LOG("caught SIGINT, reset");
 +              PARA_WARNING_LOG("caught SIGINT, reset\n");
                /* Nothing to do. SIGINT killed our child which gets noticed
                 * by do_select and resets everything.
                 */
 -              return;
 +              return 1;
        case SIGUSR1:
 -              PARA_NOTICE_LOG("got SIGUSR1, rereading configuration");
 -              com_reread_conf();
 -              return;
 +              PARA_NOTICE_LOG("got SIGUSR1, rereading configuration\n");
 +              reread_conf();
 +              return 1;
        case SIGCHLD:
 -              check_sigchld();
 -              return;
 +              task_notify_all(s, E_GUI_SIGCHLD);
 +              return 1;
        }
 +      return 1;
  }
  
 -static void status_pre_select(fd_set *rfds, int *max_fileno, struct timeval *tv)
 +static enum exec_status exec_status(void)
  {
 -      static struct timeval next_exec, atm, diff;
 -      int ret, fds[3] = {0, 1, 0};
 -      pid_t pid;
 -
 -      if (stat_pipe >= 0)
 -              goto success;
 -      /* Avoid busy loop */
 -      gettimeofday(&atm, NULL);
 -      if (tv_diff(&next_exec, &atm, &diff) > 0) {
 -              if (tv_diff(&diff, tv, NULL) < 0)
 -                      *tv = diff;
 -              return;
 -      }
 -      next_exec.tv_sec = atm.tv_sec + 2;
 -      ret = para_exec_cmdline_pid(&pid, conf.stat_cmd_arg, fds);
 -      if (ret < 0)
 -              return;
 -      ret = mark_fd_nonblocking(fds[1]);
 -      if (ret < 0) {
 -              close(fds[1]);
 -              return;
 -      }
 -      stat_pipe = fds[1];
 -success:
 -      para_fd_set(stat_pipe, rfds, max_fileno);
 +      if (exec_fds[0] >= 0 || exec_fds[1] >= 0)
 +              return EXEC_DCMD;
 +      if (exec_pid > 0)
 +              return EXEC_XCMD;
 +      return EXEC_IDLE;
  }
  
- static void exec_pre_select(struct sched *s, struct task *t)
 -#define COMMAND_BUF_SIZE 32768
++static void exec_pre_select(struct sched *s, void *context)
 +{
++      struct exec_task *et = context;
 +      if (exec_fds[0] >= 0)
 +              para_fd_set(exec_fds[0], &s->rfds, &s->max_fileno);
 +      if (exec_fds[1] >= 0)
 +              para_fd_set(exec_fds[1], &s->rfds, &s->max_fileno);
-       if (task_get_notification(t) < 0)
++      if (task_get_notification(et->task) < 0)
 +              sched_min_delay(s);
 +}
  
- static int exec_post_select(struct sched *s, struct task *t)
 -/*
 - * This is the core select loop. Besides the (internal) signal
 - * pipe, the following other fds are checked according to the mode:
 - *
 - * GETCH_MODE: check stdin, return when key is pressed
 - *
 - * COMMAND_MODE: check command fds and stdin. Return when peer has closed both
 - * stdout and stderr or when any key is pressed.
 - *
 - * EXTERNAL_MODE: Check only signal pipe. Used when an external command
 - * is running. During that time curses is disabled.  Returns when
 - * cmd_pid == 0.
 - */
 -static int do_select(int mode)
 -{
 -      fd_set rfds;
 -      int ret, i, max_fileno;
 -      char command_buf[2][COMMAND_BUF_SIZE] = {"", ""};
 -      int cbo[2] = {0, 0}; /* command buf offsets */
 -      struct timeval tv;
 -      unsigned flags[2] = {0, 0}; /* for for_each_line() */
 -
 -repeat:
 -      tv.tv_sec = conf.timeout_arg  / 1000;
 -      tv.tv_usec = (conf.timeout_arg % 1000) * 1000;
 -//    ret = refresh_status();
 -      FD_ZERO(&rfds);
 -      max_fileno = 0;
 -      status_pre_select(&rfds, &max_fileno, &tv);
 -      /* signal pipe */
 -      para_fd_set(signal_pipe, &rfds, &max_fileno);
 -      /* command pipe only for COMMAND_MODE */
 -      if (mode == COMMAND_MODE) {
 -              if (command_fds[0] >= 0)
 -                      para_fd_set(command_fds[0], &rfds, &max_fileno);
 -              if (command_fds[1] >= 0)
 -                      para_fd_set(command_fds[1], &rfds, &max_fileno);
++static int exec_post_select(struct sched *s, void *context)
 +{
-       struct exec_task *ct = container_of(t, struct exec_task, task);
++      struct exec_task *ct = context;
 +      int i, ret;
 +
-       ret = task_get_notification(t);
++      ret = task_get_notification(ct->task);
 +      if (ret == -E_GUI_SIGCHLD && exec_pid > 0) {
 +              int exit_status;
 +              if (waitpid(exec_pid, &exit_status, WNOHANG) == exec_pid) {
 +                      exec_pid = 0;
 +                      init_curses();
 +                      PARA_INFO_LOG("command exit status: %d", exit_status);
 +                      print_in_bar(COLOR_MSG, " ");
 +              }
        }
 -      if (mode == GETCH_MODE || mode == COMMAND_MODE)
 -              para_fd_set(STDIN_FILENO, &rfds, &max_fileno);
 -      ret = para_select(max_fileno + 1, &rfds, NULL, &tv);
 -      if (ret <= 0)
 -              goto check_return; /* skip fd checks */
 -      /* signals */
 -      ret = para_next_signal(&rfds);
 -      if (ret > 0)
 -              handle_signal(ret);
 -      /* read command pipe if ready */
 -      if (mode == COMMAND_MODE) {
 -              for (i = 0; i < 2; i++) {
 -                      size_t sz;
 -                      if (command_fds[i] < 0)
 -                              continue;
 -                      ret = read_nonblock(command_fds[i],
 -                              command_buf[i] + cbo[i],
 -                              COMMAND_BUF_SIZE - 1 - cbo[i], &rfds, &sz);
 -                      cbo[i] += sz;
 -                      sz = cbo[i];
 -                      cbo[i] = for_each_line(flags[i], command_buf[i], cbo[i],
 -                              add_output_line, &i);
 -                      if (sz != cbo[i]) { /* at least one line found */
 -                              wrefresh(bot.win);
 -                              flags[i] = 0;
 -                      }
 -                      if (ret < 0) {
 +      for (i = 0; i < 2; i++) {
 +              size_t sz;
 +              if (exec_fds[i] < 0)
 +                      continue;
 +              ret = read_nonblock(exec_fds[i],
 +                      ct->command_buf[i] + ct->cbo[i],
 +                      COMMAND_BUF_SIZE - 1 - ct->cbo[i], &s->rfds, &sz);
 +              ct->cbo[i] += sz;
 +              sz = ct->cbo[i];
 +              ct->cbo[i] = for_each_line(ct->flags[i], ct->command_buf[i],
 +                      ct->cbo[i], add_output_line, &i);
 +              if (sz != ct->cbo[i]) { /* at least one line found */
 +                      refresh_window(&bot);
 +                      ct->flags[i] = 0;
 +              }
 +              if (ret < 0 || exec_pid == 0) {
 +                      if (ret < 0)
                                PARA_NOTICE_LOG("closing command fd %d: %s",
                                        i, para_strerror(-ret));
 -                              close(command_fds[i]);
 -                              command_fds[i] = -1;
 -                              flags[i] = 0;
 -                              if (command_fds[!i] < 0) /* both fds closed */
 -                                      return 0;
 -                      }
 -                      if (cbo[i] == COMMAND_BUF_SIZE - 1) {
 -                              PARA_NOTICE_LOG("discarding overlong line");
 -                              cbo[i] = 0;
 -                              flags[i] = FELF_DISCARD_FIRST;
 -                      }
 +                      close(exec_fds[i]);
 +                      exec_fds[i] = -1;
 +                      ct->flags[i] = 0;
 +                      ct->cbo[i] = 0;
 +                      if (exec_fds[!i] < 0) /* both fds closed */
 +                              return 1;
                }
 -      }
 -      ret = read_stat_pipe(&rfds);
 -      if (ret < 0) {
 -              PARA_NOTICE_LOG("closing stat pipe: %s\n", para_strerror(-ret));
 -              close(stat_pipe);
 -              stat_pipe = -1;
 -              clear_all_items();
 -              free(stat_content[SI_BASENAME]);
 -              stat_content[SI_BASENAME] =
 -                      para_strdup("stat command terminated!?");
 -              print_all_items();
 -      }
 -check_return:
 -      switch (mode) {
 -      case COMMAND_MODE:
 -              ret = wgetch(top.win);
 -              if (ret != ERR && ret != KEY_RESIZE) {
 -                      if (command_fds[0] >= 0) {
 -                              close(command_fds[0]);
 -                              command_fds[0] = -1;
 -                      }
 -                      if (command_fds[1] >= 0) {
 -                              close(command_fds[1]);
 -                              command_fds[1] = -1;
 -                      }
 -                      if (cmd_pid)
 -                              kill(cmd_pid, SIGTERM);
 -                      return -1;
 +              if (ct->cbo[i] == COMMAND_BUF_SIZE - 1) {
 +                      PARA_NOTICE_LOG("discarding overlong line");
 +                      ct->cbo[i] = 0;
 +                      ct->flags[i] = FELF_DISCARD_FIRST;
                }
 -              break;
 -      case GETCH_MODE:
 -              ret = wgetch(top.win);
 -              if (ret != ERR && ret != KEY_RESIZE)
 -                      return ret;
 -              break;
 -      case EXTERNAL_MODE:
 -              if (cmd_pid == 0)
 -                      return 0;
        }
 -      goto repeat;
 +      return 0;
  }
  
- static void input_pre_select(struct sched *s, __a_unused struct task *t)
 -/*
 - * read from command pipe and print data to bot window
 - */
 -static void send_output(void)
++static void input_pre_select(struct sched *s, __a_unused void *context)
  {
 -      int ret;
 +      if (exec_status() != EXEC_XCMD)
 +              para_fd_set(STDIN_FILENO, &s->rfds, &s->max_fileno);
 +      if (window_update_needed())
 +              sched_min_delay(s);
 +}
 +
 +/* read from command pipe and print data to bot window */
 +static void exec_and_display(const char *file_and_args)
 +{
 +      int ret, fds[3] = {0, 1, 1};
  
 -      ret = mark_fd_nonblocking(command_fds[0]);
 +      outputf(COLOR_COMMAND, "%s", file_and_args);
 +      ret = para_exec_cmdline_pid(&exec_pid, file_and_args, fds);
 +      if (ret < 0)
 +              return;
 +      ret = mark_fd_nonblocking(fds[1]);
        if (ret < 0)
                goto fail;
 -      ret = mark_fd_nonblocking(command_fds[1]);
 +      ret = mark_fd_nonblocking(fds[2]);
        if (ret < 0)
                goto fail;
 -      if (do_select(COMMAND_MODE) >= 0)
 -              PARA_INFO_LOG("command complete");
 -      else
 -              PARA_NOTICE_LOG("command aborted");
 -      print_in_bar(COLOR_MSG, " ");
 +      exec_fds[0] = fds[1];
 +      exec_fds[1] = fds[2];
 +      print_in_bar(COLOR_MSG, "hit any key to abort\n");
        return;
  fail:
        PARA_ERROR_LOG("%s\n", para_strerror(-ret));
@@@ -1036,104 -1147,35 +1037,104 @@@ static void exec_para(const char *args
  }
  
  /*
 - * exec command and print output to bot win
 + * shutdown curses and stat pipe before executing external commands
   */
 -static void display_cmd(char *cmd)
 +static void exec_external(char *file_and_args)
  {
 -      int fds[3] = {0, 1, 1};
 +      int fds[3] = {-1, -1, -1};
  
 -      print_in_bar(COLOR_MSG, "executing display command, hit any key to abort");
 -      outputf(COLOR_COMMAND, "%s", cmd);
 -      if (para_exec_cmdline_pid(&cmd_pid, cmd, fds) < 0)
 +      if (exec_pid)
                return;
 -      command_fds[0] = fds[1];
 -      command_fds[1] = fds[2];
 -      send_output();
 +      shutdown_curses();
 +      para_exec_cmdline_pid(&exec_pid, file_and_args, fds);
  }
  
 -/*
 - * shutdown curses and stat pipe before executing external commands
 - */
 -static void external_cmd(char *cmd)
 +static void handle_command(int c)
  {
 -      int fds[3] = {-1, -1, -1};
 +      int i;
  
 -      if (cmd_pid)
 -              return;
 -      shutdown_curses();
 -      if (para_exec_cmdline_pid(&cmd_pid, cmd, fds) < 0)
 +      /* first check user-defined key bindings */
 +      for (i = 0; i < conf.key_map_given; ++i) {
 +              char *tmp, *handler, *arg;
 +
 +              tmp = para_strdup(conf.key_map_arg[i]);
 +              if (!split_key_map(tmp, &handler, &arg)) {
 +                      free(tmp);
 +                      return;
 +              }
 +              if (strcmp(tmp, km_keyname(c))) {
 +                      free(tmp);
 +                      continue;
 +              }
 +              if (*handler == 'd')
 +                      exec_and_display(arg);
 +              else if (*handler == 'x')
 +                      exec_external(arg);
 +              else if (*handler == 'p')
 +                      exec_para(arg);
 +              else if (*handler == 'i') {
 +                      int num = find_cmd_byname(arg);
 +                      if (num >= 0)
 +                              command_list[num].handler();
 +              }
 +              free(tmp);
                return;
 -      do_select(EXTERNAL_MODE);
 -      init_curses();
 +      }
 +      /* not found, check internal key bindings */
 +      for (i = 0; command_list[i].handler; i++) {
 +              if (!strcmp(km_keyname(c), command_list[i].key)) {
 +                      command_list[i].handler();
 +                      return;
 +              }
 +      }
 +      print_in_bar(COLOR_ERRMSG, "key '%s' is not bound, press ? for help",
 +              km_keyname(c));
 +}
 +
- static int input_post_select(__a_unused struct sched *s, __a_unused struct task *t)
++static int input_post_select(__a_unused struct sched *s, __a_unused void *context)
 +{
 +      int ret;
 +      enum exec_status exs = exec_status();
 +
 +      if (exs == EXEC_XCMD)
 +              return 0;
 +      if (window_update_needed()) {
 +              if (top.needs_update)
 +                      assert(wnoutrefresh(top.win) == OK);
 +              if (bot.needs_update)
 +                      assert(wnoutrefresh(bot.win) == OK);
 +              if (sep.needs_update)
 +                      assert(wnoutrefresh(sep.win) == OK);
 +              if (sb.needs_update)
 +                      assert(wnoutrefresh(sb.win) == OK);
 +              if (in.needs_update)
 +                      assert(wnoutrefresh(in.win) == OK);
 +              doupdate();
 +              top.needs_update = bot.needs_update = sb.needs_update =
 +                      in.needs_update = sep.needs_update = false;
 +      }
 +      ret = wgetch(top.win);
 +      if (ret == ERR)
 +              return 0;
 +      if (ret == KEY_RESIZE) {
 +              if (curses_active()) {
 +                      shutdown_curses();
 +                      init_curses();
 +                      redraw_bot_win();
 +              }
 +              return 0;
 +      }
 +      if (exs == EXEC_IDLE)
 +              handle_command(ret);
 +      else if (exec_pid > 0)
 +              kill(exec_pid, SIGTERM);
 +      return 0;
 +}
 +
- static void signal_pre_select(struct sched *s, struct task *t)
++static void signal_pre_select(struct sched *s, void *context)
 +{
-       struct signal_task *st = container_of(t, struct signal_task, task);
++      struct signal_task *st = context;
 +      para_fd_set(st->fd, &s->rfds, &s->max_fileno);
  }
  
  static void print_scroll_msg(void)
@@@ -1404,83 -1519,14 +1405,81 @@@ __noreturn static void print_help_and_d
        exit(0);
  }
  
 -int main(int argc, char *argv[])
 +static int setup_tasks_and_schedule(void)
  {
 -      int ret;
 -      char *cf;
++      struct exec_task exec_task = {.task = NULL};
++      struct status_task status_task = {.fd = -1};
++      struct input_task input_task = {.task = NULL};
++      struct signal_task signal_task = {.task = NULL};
 +      struct sched sched = {
 +              .default_timeout = {
 +                      .tv_sec = conf.timeout_arg  / 1000,
 +                      .tv_usec = (conf.timeout_arg % 1000) * 1000,
 +              },
 +      };
-       struct exec_task exec_task = {
-               .task = {
-                       .status = "exec",
-                       .pre_select = exec_pre_select,
-                       .post_select = exec_post_select,
-               },
-       };
-       struct status_task status_task = {
-               .task = {
-                       .status = "status",
-                       .pre_select = status_pre_select,
-                       .post_select = status_post_select,
-               },
-               .fd = -1
-       };
-       struct input_task input_task = {
-               .task = {
-                       .status = "input",
-                       .pre_select = input_pre_select,
-                       .post_select = input_post_select,
-               },
-       };
-       struct signal_task signal_task = {
-               .task = {
-                       .status = "signal",
-                       .pre_select = signal_pre_select,
-                       .post_select = signal_post_select,
-               },
-       };
 -      _argc = argc;
 -      _argv = argv;
++      exec_task.task = task_register(&(struct task_info) {
++              .name = "exec",
++              .pre_select = exec_pre_select,
++              .post_select = exec_post_select,
++              .context = &exec_task,
++      }, &sched);
++
++      status_task.task = task_register(&(struct task_info) {
++              .name = "status",
++              .pre_select = status_pre_select,
++              .post_select = status_post_select,
++              .context = &status_task,
++      }, &sched);
++
++      input_task.task = task_register(&(struct task_info) {
++              .name = "input",
++              .pre_select = input_pre_select,
++              .post_select = input_post_select,
++              .context = &input_task,
++      }, &sched);
++
 +      signal_task.fd = para_signal_init();
 +      para_install_sighandler(SIGINT);
 +      para_install_sighandler(SIGTERM);
 +      para_install_sighandler(SIGCHLD);
 +      para_install_sighandler(SIGUSR1);
-       register_task(&sched, &exec_task.task);
-       register_task(&sched, &status_task.task);
-       register_task(&sched, &input_task.task);
-       register_task(&sched, &signal_task.task);
++      signal_task.task = task_register(&(struct task_info) {
++              .name = "signal",
++              .pre_select = signal_pre_select,
++              .post_select = signal_post_select,
++              .context = &signal_task,
++      }, &sched);
 +      return schedule(&sched);
 +}
  
 +/**
 + * The main function of para_gui.
 + *
 + * \param argc Usual argument count.
 + * \param argv Usual argument vector.
 + *
 + * After initialization para_gui registers the following tasks to the paraslash
 + * scheduler: status, exec, signal, input.
 + *
 + * The status task executes the para_audioc stat command to obtain the status
 + * of para_server and para_audiod, and displays this information in the top
 + * window of para_gui.
 + *
 + * The exec task is responsible for printing the output of the currently
 + * running executable to the bottom window.
 + *
 + * The signal task performs suitable actions according to any signals received.
 + * For example it refreshes all windows on terminal size changes and resets the
 + * terminal on \p SIGTERM.
 + *
 + * The input task reads single key strokes from stdin. For each key pressed, it
 + * executes the command handler associated with this key.
 + *
 + * \return \p EXIT_SUCCESS or \p EXIT_FAILURE.
 + */
 +int main(int argc, char *argv[])
 +{
        gui_cmdline_parser(argc, argv, &conf); /* exits on errors */
        loglevel = get_loglevel_by_name(conf.loglevel_arg);
        version_handle_flag("gui", conf.version_given);
diff --cc server.c
+++ b/server.c
@@@ -484,9 -488,9 +488,7 @@@ static void server_init(int argc, char 
        parse_config_or_die(0);
        log_welcome("para_server");
        init_ipc_or_die(); /* init mmd struct and mmd->lock */
--      /* make sure, the global now pointer is uptodate */
--      clock_get_realtime(now);
--      set_server_start_time(now);
++      set_server_start_time(NULL);
        init_user_list(user_list_file);
        /* become daemon */
        if (conf.daemon_given)
diff --cc wmadec_filter.c
Simple merge