server: Simplify afs socket cookie code.
[paraslash.git] / gui.c
diff --git a/gui.c b/gui.c
index 9d96e70f6949362b39b50fa1dc23e85327a9316a..6882a31cbaca0a39cd5e25a8b32936a094a6352a 100644 (file)
--- a/gui.c
+++ b/gui.c
@@ -1,8 +1,4 @@
-/*
- * Copyright (C) 1998 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 1998 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
 /** \file gui.c Curses-based interface for paraslash. */
 
@@ -12,8 +8,9 @@
 #include <curses.h>
 #include <locale.h>
 #include <sys/time.h>
+#include <lopsub.h>
 
-#include "gui.cmdline.h"
+#include "gui.lsg.h"
 #include "para.h"
 #include "gui.h"
 #include "string.h"
 #include "list.h"
 #include "sched.h"
 #include "signal.h"
-#include "ggo.h"
 #include "version.h"
 
-/** define the array of error lists needed by para_gui */
-INIT_GUI_ERRLISTS;
+/** Array of error strings. */
+DEFINE_PARA_ERRLIST;
+
+static struct lls_parse_result *cmdline_lpr, *lpr;
+
+#define CMD_PTR (lls_cmd(0, gui_suite))
+#define OPT_RESULT(_name) (lls_opt_result(LSG_GUI_PARA_GUI_OPT_ ## _name, 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 FOR_EACH_KEY_MAP(_i) for (_i = 0; _i < OPT_GIVEN(KEY_MAP); _i++)
+
 static char *stat_content[NUM_STAT_ITEMS];
 
 static struct gui_window {
@@ -46,11 +52,8 @@ struct rb_entry {
 static struct ringbuffer *bot_win_rb;
 
 static unsigned scroll_position;
-
 static pid_t exec_pid;
-
 static int exec_fds[2] = {-1, -1};
-static struct gui_args_info conf;
 static int loglevel;
 
 /** Type of the process currently being executed. */
@@ -146,7 +149,7 @@ struct exec_task {
        unsigned flags[2]; /* passed to for_each_line() */
 };
 
-static int find_cmd_byname(char *name)
+static int find_cmd_byname(const char *name)
 {
        int i;
 
@@ -244,10 +247,10 @@ static char *km_keyname(int c)
 }
 
 /* Print given number of spaces to curses window. */
-static void add_spaces(WINDOWwin, unsigned int num)
+static void add_spaces(WINDOW *win, unsigned int num)
 {
-       char space[] = "                                ";
-       unsigned sz = sizeof(space) - 1; /* number of spaces */
+       const char space[] = "                                ";
+       const unsigned sz = sizeof(space) - 1; /* number of spaces */
 
        while (num >= sz)  {
                waddstr(win, space);
@@ -255,50 +258,43 @@ static void add_spaces(WINDOW* win, unsigned int num)
        }
        if (num > 0) {
                assert(num < sz);
-               space[num] = '\0';
-               waddstr(win, space);
+               waddstr(win, space + sz - num);
        }
 }
 
 /*
- * print aligned string to curses window. This function always prints
+ * Print aligned string to curses window. This function always prints
  * exactly len chars.
  */
-static int align_str(WINDOW* win, char *str, unsigned int len,
+static int align_str(WINDOW *win, const char *str, unsigned int len,
                unsigned int align)
 {
-       int ret, i, num; /* of spaces */
+       int ret, num; /* of spaces */
        size_t width;
+       char *sstr; /* sanitized string */
 
        if (!win || !str)
                return 0;
-       ret = strwidth(str, &width);
+       ret = sanitize_str(str, len, &sstr, &width);
        if (ret < 0) {
                PARA_ERROR_LOG("%s\n", para_strerror(-ret));
                width = 0;
-               str[0] = '\0';
+               sstr = para_strdup(NULL);
        }
+       assert(width <= len);
        num = len - width;
-       if (num < 0) {
-               str[len] = '\0';
-               num = 0;
-       }
-       /* replace control characters by spaces */
-       for (i = 0; i < len && str[i]; i++) {
-               if (str[i] == '\n' || str[i] == '\r' || str[i] == '\f')
-                       str[i] = ' ';
-       }
        if (align == LEFT) {
-               waddstr(win, str);
+               waddstr(win, sstr);
                add_spaces(win, num);
        } else if (align == RIGHT) {
                add_spaces(win, num);
-               waddstr(win, str);
+               waddstr(win, sstr);
        } else {
                add_spaces(win, num / 2);
-               waddstr(win, str);
+               waddstr(win, sstr);
                add_spaces(win, num - num / 2);
        }
+       free(sstr);
        return 1;
 }
 
@@ -456,10 +452,8 @@ static void rb_add_entry(int color, char *msg)
        waddstr(bot.win, msg);
 }
 
-/*
- * print formated output to bot win and refresh
- */
-__printf_2_3 static void outputf(int color, const char* fmt,...)
+/* Print formatted output to bot win and refresh. */
+__printf_2_3 static void outputf(int color, const char *fmt,...)
 {
        char *msg;
        va_list ap;
@@ -502,33 +496,50 @@ static __printf_2_3 void curses_log(int ll, const char *fmt,...)
                vfprintf(stderr, fmt, ap);
        va_end(ap);
 }
+
 /** The log function of para_gui, always set to curses_log(). */
-__printf_2_3 void (*para_log)(int, const char*, ...) = curses_log;
+__printf_2_3 void (*para_log)(int, const char *, ...) = curses_log;
 
+/* Call endwin() to reset the terminal into non-visual mode. */
 static void shutdown_curses(void)
 {
-       def_prog_mode();
+       /*
+        * If para_gui received a terminating signal in external mode, the
+        * terminal can be in an unusable state at this point because the child
+        * process might not have caught the signal. In this case endwin() has
+        * already been called and must not be called again. So we first return
+        * to program mode, then call endwin().
+        */
+       if (!curses_active())
+               reset_prog_mode();
        endwin();
 }
 
-/* disable curses, print a message, kill running processes and exit */
-__noreturn __printf_2_3 static void die(int exit_code, const charfmt, ...)
+/* Disable curses, print a message, kill running processes and exit. */
+__noreturn __printf_2_3 static void die(int exit_code, const char *fmt, ...)
 {
        va_list argp;
 
+       /* Kill every process in our process group. */
+       para_sigaction(SIGTERM, SIG_IGN);
+       kill(0, SIGTERM);
+       /* Wait up to two seconds for child processes to die. */
+       alarm(2);
+       while (waitpid(0, NULL, 0) >= 0)
+               ; /* nothing */
+       alarm(0);
+       /* mousemask() exists only in ncurses */
+#ifdef NCURSES_MOUSE_VERSION
+       mousemask(~(mmask_t)0, NULL); /* Avoid bad terminal state with xterm. */
+#endif
        shutdown_curses();
        va_start(argp, fmt);
        vfprintf(stderr, fmt, argp);
        va_end(argp);
-       /* kill every process in the process group and exit */
-       para_sigaction(SIGTERM, SIG_IGN);
-       kill(0, SIGTERM);
        exit(exit_code);
 }
 
-/*
- * Print stat item #i to curses window
- */
+/* Print stat item #i to curses window. */
 static void print_stat_item(int i)
 {
        char *tmp;
@@ -554,19 +565,19 @@ static int update_item(int item_num, char *buf)
        if (buf && buf[0])
                goto dup;
        switch (item_num) {
-       case SI_ARTIST:
+       case SI_artist:
                *c = para_strdup("(artist tag not set)");
                goto print;
-       case SI_TITLE:
+       case SI_title:
                *c = para_strdup("(title tag not set)");
                goto print;
-       case SI_YEAR:
+       case SI_year:
                *c = para_strdup("????");
                goto print;
-       case SI_ALBUM:
+       case SI_album:
                *c = para_strdup("(album tag not set)");
                goto print;
-       case SI_COMMENT:
+       case SI_comment:
                *c = para_strdup("(comment tag not set)");
                goto print;
        }
@@ -632,7 +643,8 @@ static int status_post_select(struct sched *s, void *context)
                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);
+               ret = para_exec_cmdline_pid(&st->pid,
+                       OPT_STRING_VAL(STAT_CMD), fds);
                if (ret < 0)
                        return 0;
                ret = mark_fd_nonblocking(fds[1]);
@@ -664,8 +676,8 @@ static int status_post_select(struct sched *s, void *context)
                close(st->fd);
                st->fd = -1;
                clear_all_items();
-               free(stat_content[SI_BASENAME]);
-               stat_content[SI_BASENAME] =
+               free(stat_content[SI_basename]);
+               stat_content[SI_basename] =
                        para_strdup("stat command terminated!?");
                print_all_items();
                return 0;
@@ -677,9 +689,7 @@ static int status_post_select(struct sched *s, void *context)
        return 0;
 }
 
-/*
- * init all windows
- */
+/* Initialize all windows. */
 static void init_wins(int top_lines)
 {
        int top_y = 0, bot_y = top_lines + 1, sb_y = LINES - 2,
@@ -819,12 +829,13 @@ static void check_key_map_args_or_die(void)
 {
        int i;
        char *tmp = NULL;
+       const struct lls_opt_result *lor = OPT_RESULT(KEY_MAP);
 
-       for (i = 0; i < conf.key_map_given; ++i) {
+       FOR_EACH_KEY_MAP(i) {
                char *handler, *arg;
 
                free(tmp);
-               tmp = para_strdup(conf.key_map_arg[i]);
+               tmp = para_strdup(lls_string_val(i, lor));
                if (!split_key_map(tmp, &handler, &arg))
                        break;
                if (strlen(handler) != 1)
@@ -837,84 +848,90 @@ static void check_key_map_args_or_die(void)
                if (find_cmd_byname(arg) < 0)
                        break;
        }
-       if (i != conf.key_map_given)
-               die(EXIT_FAILURE, "invalid key map: %s\n", conf.key_map_arg[i]);
+       if (i != OPT_GIVEN(KEY_MAP))
+               die(EXIT_FAILURE, "invalid key map: %s\n",
+                       lls_string_val(i, lor));
        free(tmp);
 }
 
-static void parse_config_file_or_die(bool override)
+static void parse_config_file_or_die(bool reload)
 {
-       bool err;
-       char *config_file;
-       struct gui_cmdline_parser_params params = {
-               .override = override,
-               .initialize = 0,
-               .check_required = !override,
-               .check_ambiguity = 0,
-               .print_errors = 1,
-       };
+       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 (conf.config_file_given)
-               config_file = para_strdup(conf.config_file_arg);
+       if (OPT_GIVEN(CONFIG_FILE))
+               cf = para_strdup(OPT_STRING_VAL(CONFIG_FILE));
        else {
                char *home = para_homedir();
-               config_file = make_message("%s/.paraslash/gui.conf", home);
+               cf = make_message("%s/.paraslash/gui.conf", home);
                free(home);
        }
-       if (!file_exists(config_file)) {
-               if (!conf.config_file_given)
-                       err = false;
-               else {
-                       PARA_EMERG_LOG("config file %s does not exist\n",
-                               config_file);
-                       err = true;
-               }
-               goto out;
-       }
-       /*
-        * When the gengetopt config file parser is called more than once, any
-        * key map arguments found in the config file are _appended_ to the old
-        * values, even though we turn on ->override. We want the new arguments
-        * to replace the old ones, so we must empty the key_map_arg array
-        * first. Unfortunately, this also clears any key map arguments given
-        * at the command line.
-        */
-       if (override) {
-               int i;
-               for (i = 0; i < conf.key_map_given; i++) {
-                       free(conf.key_map_arg[i]);
-                       conf.key_map_arg[i] = NULL;
-               }
-               conf.key_map_given = 0;
+       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;
        }
-
-       gui_cmdline_parser_config_file(config_file, &conf, &params);
-       loglevel = get_loglevel_by_name(conf.loglevel_arg);
+       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 (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();
-       err = false;
-out:
-       free(config_file);
-       if (err)
+       theme_init(OPT_STRING_VAL(THEME), &theme);
+free_cf:
+       free(cf);
+       if (ret < 0) {
+               if (errctx)
+                       PARA_ERROR_LOG("%s\n", errctx);
+               free(errctx);
+               PARA_EMERG_LOG("%s\n", para_strerror(-ret));
                exit(EXIT_FAILURE);
-       theme_init(conf.theme_arg, &theme);
+       }
 }
 
-/* reread configuration, terminate on errors */
+/* Reread configuration, terminate on errors. */
 static void reread_conf(void)
 {
        /*
-        * gengetopt might print to stderr and exit on errors. So we have to
-        * shutdown curses first.
+        * If the reload of the config file fails, we are about to exit. In
+        * this case we print the error message to stderr rather than to the
+        * curses window.  So we have to shutdown curses first.
         */
        shutdown_curses();
-       parse_config_file_or_die(true /* override */);
+       parse_config_file_or_die(true);
        init_curses();
        print_in_bar(COLOR_MSG, "config file reloaded\n");
 }
 
-/*
- * React to various signal-related events
- */
+/* React to various signal-related events. */
 static int signal_post_select(struct sched *s, __a_unused void *context)
 {
        int ret = para_next_signal(&s->rfds);
@@ -924,12 +941,7 @@ static int signal_post_select(struct sched *s, __a_unused void *context)
        switch (ret) {
        case SIGTERM:
                die(EXIT_FAILURE, "only the good die young (caught SIGTERM)\n");
-               return 1;
        case SIGINT:
-               PARA_WARNING_LOG("caught SIGINT, reset\n");
-               /* Nothing to do. SIGINT killed our child which gets noticed
-                * by do_select and resets everything.
-                */
                return 1;
        case SIGUSR1:
                PARA_NOTICE_LOG("got SIGUSR1, rereading configuration\n");
@@ -1020,7 +1032,7 @@ static void input_pre_select(struct sched *s, __a_unused void *context)
                sched_min_delay(s);
 }
 
-/* read from command pipe and print data to bot window */
+/* 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};
@@ -1054,9 +1066,7 @@ static void exec_para(const char *args)
        free(file_and_args);
 }
 
-/*
- * shutdown curses and stat pipe before executing external commands
- */
+/* Shutdown curses and stat pipe before executing external commands. */
 static void exec_external(char *file_and_args)
 {
        int fds[3] = {-1, -1, -1};
@@ -1070,12 +1080,13 @@ static void exec_external(char *file_and_args)
 static void handle_command(int c)
 {
        int i;
+       const struct lls_opt_result *lor = OPT_RESULT(KEY_MAP);
 
        /* first check user-defined key bindings */
-       for (i = 0; i < conf.key_map_given; ++i) {
+       FOR_EACH_KEY_MAP(i) {
                char *tmp, *handler, *arg;
 
-               tmp = para_strdup(conf.key_map_arg[i]);
+               tmp = para_strdup(lls_string_val(i, lor));
                if (!split_key_map(tmp, &handler, &arg)) {
                        free(tmp);
                        return;
@@ -1109,7 +1120,8 @@ static void handle_command(int c)
                km_keyname(c));
 }
 
-static int input_post_select(__a_unused struct sched *s, __a_unused void *context)
+static int input_post_select(__a_unused struct sched *s,
+               __a_unused void *context)
 {
        int ret;
        enum exec_status exs = exec_status();
@@ -1154,7 +1166,7 @@ static void print_scroll_msg(void)
        unsigned lines_total, filled = ringbuffer_filled(bot_win_rb);
        int first_rbe = first_visible_rbe(&lines_total);
 
-       print_in_bar(COLOR_MSG, "scrolled view: %d-%d/%d\n", filled - first_rbe,
+       print_in_bar(COLOR_MSG, "scrolled view: %u-%u/%u\n", filled - first_rbe,
                filled - scroll_position, ringbuffer_filled(bot_win_rb));
 }
 
@@ -1322,9 +1334,10 @@ static void com_reread_conf(void)
 static void com_help(void)
 {
        int i;
+       const struct lls_opt_result *lor = OPT_RESULT(KEY_MAP);
 
-       for (i = 0; i < conf.key_map_given; ++i) {
-               char *handler, *arg, *tmp = para_strdup(conf.key_map_arg[i]);
+       FOR_EACH_KEY_MAP(i) {
+               char *handler, *arg, *tmp = para_strdup(lls_string_val(i, lor));
                const char *handler_text = "???", *desc = NULL;
 
                if (!split_key_map(tmp, &handler, &arg)) {
@@ -1408,15 +1421,6 @@ static void com_prev_theme(void)
        com_refresh();
 }
 
-__noreturn static void print_help_and_die(void)
-{
-       struct ggo_help h = DEFINE_GGO_HELP(gui);
-       bool d = conf.detailed_help_given;
-
-       ggo_print_help(&h, d? GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS);
-       exit(0);
-}
-
 static int setup_tasks_and_schedule(void)
 {
        int ret;
@@ -1424,12 +1428,7 @@ static int setup_tasks_and_schedule(void)
        struct status_task status_task = {.fd = -1};
        struct input_task input_task = {.task = NULL};
        struct signal_task *signal_task;
-       struct sched sched = {
-               .default_timeout = {
-                       .tv_sec = conf.timeout_arg  / 1000,
-                       .tv_usec = (conf.timeout_arg % 1000) * 1000,
-               },
-       };
+       struct sched sched = {.default_timeout = {.tv_sec = 1}};
 
        exec_task.task = task_register(&(struct task_info) {
                .name = "exec",
@@ -1469,6 +1468,21 @@ static int setup_tasks_and_schedule(void)
        return ret;
 }
 
+static void handle_help_flags(void)
+{
+       char *help;
+
+       if (OPT_GIVEN(DETAILED_HELP))
+               help = lls_long_help(CMD_PTR);
+       else if (OPT_GIVEN(HELP))
+               help = lls_short_help(CMD_PTR);
+       else
+               return;
+       printf("%s\n", help);
+       free(help);
+       exit(EXIT_SUCCESS);
+}
+
 /**
  * The main function of para_gui.
  *
@@ -1485,9 +1499,9 @@ static int setup_tasks_and_schedule(void)
  * 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 signal task performs various actions according to signals received. For
+ * example, it reloads the configuration file on SIGUSR1, and it shuts down the
+ * curses system on SIGTERM to restore the terminal settings before exit.
  *
  * The input task reads single key strokes from stdin. For each key pressed, it
  * executes the command handler associated with this key.
@@ -1496,15 +1510,31 @@ static int setup_tasks_and_schedule(void)
  */
 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);
-       if (conf.help_given || conf.detailed_help_given)
-               print_help_and_die();
-       parse_config_file_or_die(false /* override */);
+       int ret;
+       char *errctx;
+
+       ret = lls(lls_parse(argc, argv, CMD_PTR, &cmdline_lpr, &errctx));
+       if (ret < 0)
+               goto out;
+       lpr = cmdline_lpr;
+       loglevel = OPT_UINT32_VAL(LOGLEVEL);
+       version_handle_flag("gui", OPT_GIVEN(VERSION));
+       handle_help_flags();
+       parse_config_file_or_die(false);
        bot_win_rb = ringbuffer_new(RINGBUFFER_SIZE);
        setlocale(LC_CTYPE, "");
        initscr(); /* needed only once, always successful */
        init_curses();
-       return setup_tasks_and_schedule() < 0? EXIT_FAILURE : EXIT_SUCCESS;
+       ret = setup_tasks_and_schedule();
+out:
+       lls_free_parse_result(lpr, CMD_PTR);
+       if (lpr != cmdline_lpr)
+               lls_free_parse_result(cmdline_lpr, CMD_PTR);
+       if (ret < 0) {
+               if (errctx)
+                       PARA_ERROR_LOG("%s\n", errctx);
+               free(errctx);
+               PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+       }
+       return ret < 0? EXIT_FAILURE : EXIT_SUCCESS;
 }