/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- * * Copyright (C) 2007-2010 David Zeuthen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the licence, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. * * Author: David Zeuthen */ #include "config.h" #include #include #include #include #include #include "stc.h" static GMainLoop *loop = NULL; /* Uncomment to get debug traces in /tmp/stc-completion-debug.txt - use tail(1) to * inspect this file */ /* #define COMPLETION_DEBUG */ //#define COMPLETION_DEBUG /* ---------------------------------------------------------------------------------------------------- */ static const gchar * enum_to_str (GType gtype, StcItemType value) { GEnumClass *klass; GEnumValue *enum_value; const gchar *ret; ret = ""; klass = g_type_class_ref (gtype); if (klass == NULL) goto out; ret = ""; enum_value = g_enum_get_value (klass, value); if (enum_value == NULL) goto out; ret = enum_value->value_nick; out: if (klass != NULL) g_type_class_unref (klass); return ret; } /* ---------------------------------------------------------------------------------------------------- */ G_GNUC_UNUSED static void completion_debug (const gchar *format, ...); static void remove_arg (gint num, gint *argc, gchar **argv[]); static void modify_argv0_for_command (gint *argc, gchar **argv[], const gchar *command); /* ---------------------------------------------------------------------------------------------------- */ typedef enum { _COLOR_RESET, _COLOR_BOLD_ON, _COLOR_INVERSE_ON, _COLOR_BOLD_OFF, _COLOR_FG_BLACK, _COLOR_FG_RED, _COLOR_FG_GREEN, _COLOR_FG_YELLOW, _COLOR_FG_BLUE, _COLOR_FG_MAGENTA, _COLOR_FG_CYAN, _COLOR_FG_WHITE, _COLOR_BG_RED, _COLOR_BG_GREEN, _COLOR_BG_YELLOW, _COLOR_BG_BLUE, _COLOR_BG_MAGENTA, _COLOR_BG_CYAN, _COLOR_BG_WHITE } _Color; static gboolean _color_stdin_is_tty = FALSE; static gboolean _color_initialized = FALSE; static FILE *_color_pager_out = NULL; static void _color_init (void) { if (_color_initialized) return; _color_initialized = TRUE; _color_stdin_is_tty = (isatty (STDIN_FILENO) != 0 && isatty (STDOUT_FILENO) != 0); } static void _color_shutdown (void) { if (!_color_initialized) return; _color_initialized = FALSE; if (_color_pager_out != NULL) pclose (_color_pager_out); } static const gchar * _color_get (_Color color) { const gchar *str; _color_init (); if (!_color_stdin_is_tty) return ""; str = NULL; switch (color) { case _COLOR_RESET: str="\x1b[0m"; break; case _COLOR_BOLD_ON: str="\x1b[1m"; break; case _COLOR_INVERSE_ON: str="\x1b[7m"; break; case _COLOR_BOLD_OFF: str="\x1b[22m"; break; case _COLOR_FG_BLACK: str="\x1b[30m"; break; case _COLOR_FG_RED: str="\x1b[31m"; break; case _COLOR_FG_GREEN: str="\x1b[32m"; break; case _COLOR_FG_YELLOW: str="\x1b[33m"; break; case _COLOR_FG_BLUE: str="\x1b[34m"; break; case _COLOR_FG_MAGENTA: str="\x1b[35m"; break; case _COLOR_FG_CYAN: str="\x1b[36m"; break; case _COLOR_FG_WHITE: str="\x1b[37m"; break; case _COLOR_BG_RED: str="\x1b[41m"; break; case _COLOR_BG_GREEN: str="\x1b[42m"; break; case _COLOR_BG_YELLOW: str="\x1b[43m"; break; case _COLOR_BG_BLUE: str="\x1b[44m"; break; case _COLOR_BG_MAGENTA: str="\x1b[45m"; break; case _COLOR_BG_CYAN: str="\x1b[46m"; break; case _COLOR_BG_WHITE: str="\x1b[47m"; break; default: g_assert_not_reached (); break; } return str; } static void _color_run_pager (void) { const gchar *pager_program; _color_init (); if (!_color_stdin_is_tty) goto out; pager_program = g_getenv ("PAGER"); if (pager_program == NULL) pager_program = "less -R"; _color_pager_out = popen (pager_program, "w"); if (_color_pager_out == NULL) { g_printerr ("Error spawning pager `%s': %m\n", pager_program); } else { fclose (stdout); stdout = _color_pager_out; } out: ; } /* ---------------------------------------------------------------------------------------------------- */ static void error_handler (StcMonitor *monitor, const gchar *filename, gint line_no, GError *error, gpointer user_data) { GString *str; str = g_string_new (NULL); g_string_append_printf (str, "%s%s", _color_get (_COLOR_BOLD_ON), _color_get (_COLOR_FG_YELLOW)); if (filename != NULL) { if (line_no >= 0) g_string_append_printf (str, "%s:%d: ", filename, line_no); else g_string_append_printf (str, "%s: ", filename); } g_string_append_printf (str, "%s", _color_get (_COLOR_RESET)); g_string_append_printf (str, "%s%s", _color_get (_COLOR_BOLD_ON), _color_get (_COLOR_FG_RED)); g_string_append_printf (str, "%s", error->message); g_string_append_printf (str, "%s", _color_get (_COLOR_RESET)); g_string_append_printf (str, "%s", _color_get (_COLOR_FG_BLUE)); g_string_append_printf (str, " (%s, %d)", g_quark_to_string (error->domain), error->code); g_string_append_printf (str, "%s", _color_get (_COLOR_RESET)); if (_color_stdin_is_tty) g_print ("%s\n", str->str); else g_printerr ("%s\n", str->str); g_string_free (str, TRUE); } /* ---------------------------------------------------------------------------------------------------- */ static const GOptionEntry command_list_entries[] = { { NULL } }; static gint handle_command_list (gint *argc, gchar **argv[], gboolean request_completion, const gchar *completion_cur, const gchar *completion_prev) { gint ret; GOptionContext *o; gchar *s; StcMonitor *monitor; GList *items; GList *l; guint n; ret = 1; monitor = NULL; modify_argv0_for_command (argc, argv, "list"); o = g_option_context_new (NULL); if (request_completion) g_option_context_set_ignore_unknown_options (o, TRUE); g_option_context_set_help_enabled (o, FALSE); g_option_context_set_summary (o, "List configuration entries."); g_option_context_add_main_entries (o, command_list_entries, NULL /* GETTEXT_PACKAGE*/); if (!g_option_context_parse (o, argc, argv, NULL)) { if (!request_completion) { s = g_option_context_get_help (o, FALSE, NULL); g_printerr ("%s", s); g_free (s); goto out; } } /* done with completion */ if (request_completion) goto out; _color_run_pager (); monitor = stc_monitor_new (error_handler, NULL); items = stc_monitor_get_items (monitor); for (l = items; l != NULL; l = l->next) { StcItem *item = STC_ITEM (l->data); StcItemType type; const gchar *id; const gchar *comment; const gchar *target; const gchar *const *options; const gchar *const *deps; const gchar *const *slave_devices; const gchar *device; const gchar *const *mount_paths; type = stc_item_get_item_type (item); id = stc_item_get_id (item); comment = stc_item_get_comment (item); target = stc_item_get_target (item); g_print ("%s%s%s:%s\n", _color_get (_COLOR_FG_BLUE), _color_get (_COLOR_BOLD_ON), id, _color_get (_COLOR_RESET)); g_print (" %sType:%s %s%s%s\n", _color_get (_COLOR_FG_WHITE), _color_get (_COLOR_RESET), _color_get (_COLOR_FG_GREEN), enum_to_str (STC_TYPE_ITEM_TYPE, type), _color_get (_COLOR_RESET)); g_print (" %sTarget:%s %s%s%s\n", _color_get (_COLOR_FG_WHITE), _color_get (_COLOR_RESET), _color_get (_COLOR_FG_GREEN), target, _color_get (_COLOR_RESET)); if (comment != NULL) { g_print (" %sComment:%s %s%s%s\n", _color_get (_COLOR_FG_WHITE), _color_get (_COLOR_RESET), _color_get (_COLOR_FG_GREEN), comment, _color_get (_COLOR_RESET)); } options = stc_item_get_option_keys (item); if (options != NULL && g_strv_length ((gchar **) options) > 0) { g_print (" %sOptions:%s ", _color_get (_COLOR_FG_WHITE), _color_get (_COLOR_RESET)); for (n = 0; options[n] != NULL; n++) { if (n > 0) g_print ("\n "); g_print ("%s%s%s%s=%s%s%s%s", _color_get (_COLOR_BOLD_ON), _color_get (_COLOR_FG_WHITE), options[n], _color_get (_COLOR_RESET), _color_get (_COLOR_FG_CYAN), _color_get (_COLOR_BOLD_ON), stc_item_get_option (item, options[n]), _color_get (_COLOR_RESET)); } g_print ("\n"); } deps = stc_item_get_dependencies (item); if (deps != NULL) { g_print (" %sDependencies:%s ", _color_get (_COLOR_FG_WHITE), _color_get (_COLOR_RESET)); for (n = 0; deps[n] != NULL; n++) { StcItem *dep_item; if (n != 0) g_print ("\n "); dep_item = stc_monitor_get_item_by_id (monitor, deps[n]); if (dep_item == NULL) { g_print ("%s%s%s (missing)%s", _color_get (_COLOR_BOLD_ON), _color_get (_COLOR_FG_RED), deps[n], _color_get (_COLOR_RESET)); } else { g_print ("%s%s%s%s", _color_get (_COLOR_BOLD_ON), _color_get (_COLOR_FG_BLUE), deps[n], _color_get (_COLOR_RESET)); g_object_unref (dep_item); } } g_print ("\n"); } g_print (" %sState:%s %s%s%s%s\n", _color_get (_COLOR_FG_WHITE), _color_get (_COLOR_RESET), _color_get (_COLOR_FG_YELLOW), _color_get (_COLOR_BOLD_ON), enum_to_str (STC_TYPE_ITEM_STATE, stc_item_get_state (item)), _color_get (_COLOR_RESET)); slave_devices = stc_item_get_slave_devices (item); device = stc_item_get_device (item); mount_paths = stc_item_get_mount_paths (item); if (slave_devices != NULL) { g_print (" %sSlave Devices:%s ", _color_get (_COLOR_FG_WHITE), _color_get (_COLOR_RESET)); for (n = 0; slave_devices[n] != NULL; n++) { if (n != 0) g_print ("\n "); g_print ("%s%s%s%s", _color_get (_COLOR_FG_YELLOW), _color_get (_COLOR_BOLD_ON), slave_devices[n], _color_get (_COLOR_RESET)); } g_print ("\n"); } if (device != NULL) { g_print (" %sDevice:%s %s%s%s%s\n", _color_get (_COLOR_FG_WHITE), _color_get (_COLOR_RESET), _color_get (_COLOR_FG_YELLOW), _color_get (_COLOR_BOLD_ON), device, _color_get (_COLOR_RESET)); } if (mount_paths != NULL) { g_print (" %sMount Paths:%s ", _color_get (_COLOR_FG_WHITE), _color_get (_COLOR_RESET)); for (n = 0; mount_paths[n] != NULL; n++) { if (n != 0) g_print ("\n "); g_print ("%s%s%s%s", _color_get (_COLOR_FG_YELLOW), _color_get (_COLOR_BOLD_ON), mount_paths[n], _color_get (_COLOR_RESET)); } g_print ("\n"); } g_print ("\n"); } g_list_foreach (items, (GFunc) g_object_unref, NULL); g_list_free (items); ret = 0; out: if (monitor != NULL) g_object_unref (monitor); g_option_context_free (o); return ret; } /* ---------------------------------------------------------------------------------------------------- */ static gchar * ask_user (const gchar *info, const gchar *prompt, gboolean echo_on) { gchar *ret; const gchar *tty_name; GString *str; FILE *tty; struct termios ts, ots; ret = NULL; tty = NULL; if (!_color_stdin_is_tty) goto out; tty_name = ctermid (NULL); if (tty_name == NULL) goto out; tty = fopen (tty_name, "r+"); if (tty == NULL) goto out; fprintf (tty, "%s", info); fprintf (tty, "%s", prompt); fflush (tty); setbuf (tty, NULL); /* TODO: We really ought to block SIGINT and STGSTP (and probably * other signals too) so we can restore the terminal (since we * turn off echoing). See e.g. Advanced Programming in the * UNIX Environment 2nd edition (Steves and Rago) section * 18.10, pg 660 where this is suggested. See also various * getpass(3) implementations * * Anyway, On modern Linux not doing this doesn't seem to be a * problem - looks like modern shells restore echoing anyway * on the first input. So maybe it's not even worth solving * the problem. */ if (!echo_on) { tcgetattr (fileno (tty), &ts); ots = ts; ts.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); tcsetattr (fileno (tty), TCSAFLUSH, &ts); } str = g_string_new (NULL); while (TRUE) { gint c; c = getc (tty); if (c == '\n') { /* ok, done */ break; } else if (c == EOF) { if (!echo_on) { tcsetattr (fileno (tty), TCSAFLUSH, &ots); } g_error ("Got unexpected EOF while reading from controlling terminal."); abort (); break; } else { g_string_append_c (str, c); } } if (!echo_on) { tcsetattr (fileno (tty), TCSAFLUSH, &ots); } putc ('\n', tty); ret = g_strdup (str->str); memset (str->str, '\0', str->len); g_string_free (str, TRUE); out: if (tty != NULL) fclose (tty); return ret; } /* ---------------------------------------------------------------------------------------------------- */ static gchar *opt_start_stop_id = NULL; static gboolean opt_start_allow_degraded = FALSE; static gboolean on_may_start_degraded (StcOperation *operation, StcItem *item, gpointer user_data) { gboolean ret; gchar *s; GString *str; const gchar *const *slave_devices; guint num_slave_devices; guint n; ret = FALSE; if (opt_start_allow_degraded) { ret = TRUE; goto out; } slave_devices = stc_item_get_slave_devices (item); g_assert (slave_devices != NULL); num_slave_devices = g_strv_length ((gchar **) slave_devices); g_assert_cmpint (num_slave_devices, >, 0); str = g_string_new (NULL); g_string_append_printf (str, "Item %s%s%s%s of type %s%s%s%s can only start degraded (", _color_get (_COLOR_FG_BLUE), _color_get (_COLOR_BOLD_ON), stc_item_get_id (item), _color_get (_COLOR_RESET), _color_get (_COLOR_FG_CYAN), _color_get (_COLOR_BOLD_ON), enum_to_str (STC_TYPE_ITEM_TYPE, stc_item_get_item_type (item)), _color_get (_COLOR_RESET)); for (n = 0; n < num_slave_devices; n++) { if (n > 0) g_string_append (str, ", "); g_string_append_printf (str, "%s%s%s%s", _color_get (_COLOR_FG_YELLOW), _color_get (_COLOR_BOLD_ON), slave_devices[n], _color_get (_COLOR_RESET)); } g_string_append (str, ")\n"); s = ask_user (str->str, "OK to start degraded (yes/no)? ", TRUE); for (n = 0; s[n] != 0; n++) s[n] = g_ascii_tolower (s[n]); if (g_strcmp0 (s, "yes") == 0) ret = TRUE; g_free (s); g_string_free (str, TRUE); out: return ret; } static gchar * on_request_passphrase (StcOperation *operation, StcItem *item, gpointer user_data) { gchar *ret; GString *str; const gchar *const *slave_devices; guint num_slave_devices; guint n; ret = NULL; slave_devices = stc_item_get_slave_devices (item); g_assert (slave_devices != NULL); num_slave_devices = g_strv_length ((gchar **) slave_devices); g_assert_cmpint (num_slave_devices, >, 0); str = g_string_new (NULL); g_string_append_printf (str, "Passphrase needed for item %s%s%s%s of type %s%s%s%s (", _color_get (_COLOR_FG_BLUE), _color_get (_COLOR_BOLD_ON), stc_item_get_id (item), _color_get (_COLOR_RESET), _color_get (_COLOR_FG_CYAN), _color_get (_COLOR_BOLD_ON), enum_to_str (STC_TYPE_ITEM_TYPE, stc_item_get_item_type (item)), _color_get (_COLOR_RESET)); for (n = 0; n < num_slave_devices; n++) { if (n > 0) g_string_append (str, ", "); g_string_append_printf (str, "%s%s%s%s", _color_get (_COLOR_FG_YELLOW), _color_get (_COLOR_BOLD_ON), slave_devices[n], _color_get (_COLOR_RESET)); } g_string_append (str, ")\n"); ret = ask_user (str->str, "Passphrase: ", FALSE); g_string_free (str, TRUE); return ret; } static const GOptionEntry command_start_entries[] = { { "id", 'i', 0, G_OPTION_ARG_STRING, &opt_start_stop_id, "Configuration item", NULL}, { "allow-degraded", 'd', 0, G_OPTION_ARG_NONE, &opt_start_allow_degraded, "Answer yes to whether it's OK to start degraded", NULL}, { NULL } }; static const GOptionEntry command_stop_entries[] = { { "id", 'i', 0, G_OPTION_ARG_STRING, &opt_start_stop_id, "Configuration item", NULL}, { NULL } }; static gint handle_command_start_stop (gint *argc, gchar **argv[], gboolean request_completion, const gchar *completion_cur, const gchar *completion_prev, gboolean is_start) { gint ret; GOptionContext *o; gchar *s; StcMonitor *monitor; StcItem *item; GList *items; GList *l; gboolean complete_ids; GError *error; StcOperation *operation; ret = 1; monitor = NULL; item = NULL; opt_start_stop_id = NULL; operation = NULL; modify_argv0_for_command (argc, argv, is_start ? "start" : "stop"); o = g_option_context_new (NULL); if (request_completion) g_option_context_set_ignore_unknown_options (o, TRUE); g_option_context_set_help_enabled (o, FALSE); g_option_context_set_summary (o, is_start ? "Start configuration item." : "Stop configuration item"); g_option_context_add_main_entries (o, is_start ? command_start_entries : command_stop_entries, NULL /* GETTEXT_PACKAGE*/); complete_ids = FALSE; if (request_completion && (g_strcmp0 (completion_prev, "--id") == 0 || g_strcmp0 (completion_prev, "-i") == 0)) { complete_ids = TRUE; remove_arg ((*argc) - 1, argc, argv); } if (!g_option_context_parse (o, argc, argv, NULL)) { if (!request_completion) { s = g_option_context_get_help (o, FALSE, NULL); g_printerr ("%s", s); g_free (s); goto out; } } if (request_completion && (opt_start_stop_id == NULL && !complete_ids)) { g_print ("--id \n"); } if (request_completion && is_start && (!opt_start_allow_degraded && !complete_ids)) { g_print ("--allow-degraded \n"); } monitor = stc_monitor_new (error_handler, NULL); if (complete_ids) { items = stc_monitor_get_items (monitor); for (l = items; l != NULL; l = l->next) { StcItem *item = STC_ITEM (l->data); StcItemState state; state = stc_item_get_state (item); if (is_start) { if (state == STC_ITEM_STATE_CAN_START_DEGRADED || state == STC_ITEM_STATE_CAN_START) { g_print ("%s \n", stc_item_get_id (item)); } } else { if (state == STC_ITEM_STATE_STARTED) { g_print ("%s \n", stc_item_get_id (item)); } } } g_list_foreach (items, (GFunc) g_object_unref, NULL); g_list_free (items); } /* done with completion */ if (request_completion) goto out; if (opt_start_stop_id != NULL) { item = stc_monitor_get_item_by_id (monitor, opt_start_stop_id); if (item == NULL) { g_printerr ("Error looking up item with id %s\n", opt_start_stop_id); goto out; } } else { s = g_option_context_get_help (o, FALSE, NULL); g_printerr ("%s", s); g_free (s); goto out; } operation = stc_operation_new (); g_signal_connect (operation, "may-start-degraded", G_CALLBACK (on_may_start_degraded), NULL); g_signal_connect (operation, "request-passphrase", G_CALLBACK (on_request_passphrase), NULL); if (is_start) { error = NULL; if (!stc_item_start_sync (item, operation, NULL, /* GCancellable* */ &error)) { g_printerr ("Error starting configuration item with id %s: %s (%s, %d)\n", stc_item_get_id (item), error->message, g_quark_to_string (error->domain), error->code); g_error_free (error); goto out; } } else { error = NULL; if (!stc_item_stop_sync (item, operation, NULL, /* GCancellable* */ &error)) { g_printerr ("Error stopping configuration item with id %s: %s (%s, %d)\n", stc_item_get_id (item), error->message, g_quark_to_string (error->domain), error->code); g_error_free (error); goto out; } } ret = 0; out: if (operation != NULL) g_object_unref (operation); if (item != NULL) g_object_unref (item); if (monitor != NULL) g_object_unref (monitor); g_option_context_free (o); return ret; } /* ---------------------------------------------------------------------------------------------------- */ static void usage (gint *argc, gchar **argv[], gboolean use_stdout) { GOptionContext *o; gchar *s; gchar *program_name; o = g_option_context_new ("COMMAND"); g_option_context_set_help_enabled (o, FALSE); /* Ignore parsing result */ g_option_context_parse (o, argc, argv, NULL); program_name = g_path_get_basename ((*argv)[0]); s = g_strdup_printf ("Commands:\n" " help Shows this information\n" //" info Shows information about an object\n" " list Lists all configuration entries\n" " start Start (e.g. mount/assemble) a configuration item\n" " stop Stop (e.g. unmount/disassemble) a configuration item\n" "\n" "Use \"%s COMMAND --help\" to get help on each command.\n", program_name); g_free (program_name); g_option_context_set_description (o, s); g_free (s); s = g_option_context_get_help (o, FALSE, NULL); if (use_stdout) g_print ("%s", s); else g_printerr ("%s", s); g_free (s); g_option_context_free (o); } static void remove_arg (gint num, gint *argc, gchar **argv[]) { gint n; g_assert (num <= (*argc)); for (n = num; (*argv)[n] != NULL; n++) (*argv)[n] = (*argv)[n+1]; (*argv)[n] = NULL; (*argc) = (*argc) - 1; } static void modify_argv0_for_command (gint *argc, gchar **argv[], const gchar *command) { gchar *s; gchar *program_name; /* TODO: * 1. get a g_set_prgname() ?; or * 2. save old argv[0] and restore later */ g_assert (g_strcmp0 ((*argv)[1], command) == 0); remove_arg (1, argc, argv); program_name = g_path_get_basename ((*argv)[0]); s = g_strdup_printf ("%s %s", (*argv)[0], command); (*argv)[0] = s; g_free (program_name); } static gchar * pick_word_at (const gchar *s, gint cursor, gint *out_word_begins_at) { gint begin; gint end; if (s[0] == '\0') { if (out_word_begins_at != NULL) *out_word_begins_at = -1; return NULL; } if (g_ascii_isspace (s[cursor]) && ((cursor > 0 && g_ascii_isspace(s[cursor-1])) || cursor == 0)) { if (out_word_begins_at != NULL) *out_word_begins_at = cursor; return g_strdup (""); } while (!g_ascii_isspace (s[cursor - 1]) && cursor > 0) cursor--; begin = cursor; end = begin; while (!g_ascii_isspace (s[end]) && s[end] != '\0') end++; if (out_word_begins_at != NULL) *out_word_begins_at = begin; return g_strndup (s + begin, end - begin); } /* TODO: would be nice with generic options that can be used before any verb such as * * -n, --no-color Turn colorization off always. * -C, --color Turn colorization on always. */ int main (int argc, char *argv[]) { gint ret; const gchar *command; gboolean request_completion; gchar *completion_cur; gchar *completion_prev; ret = 1; completion_cur = NULL; completion_prev = NULL; loop = NULL; g_type_init (); _color_init (); if (argc < 2) { usage (&argc, &argv, FALSE); goto out; } loop = g_main_loop_new (NULL, FALSE); request_completion = FALSE; completion_debug ("========================================================================"); completion_debug ("---- argc=%d --------------------------------------------------------", argc); again: command = argv[1]; if (g_strcmp0 (command, "help") == 0) { if (request_completion) { /* do nothing */ } else { usage (&argc, &argv, TRUE); ret = 0; } goto out; } #if 0 else if (g_strcmp0 (command, "info") == 0) { ret = handle_command_info (&argc, &argv, request_completion, completion_cur, completion_prev); goto out; } #endif else if (g_strcmp0 (command, "list") == 0) { ret = handle_command_list (&argc, &argv, request_completion, completion_cur, completion_prev); goto out; } else if (g_strcmp0 (command, "start") == 0 || g_strcmp0 (command, "stop") == 0) { ret = handle_command_start_stop (&argc, &argv, request_completion, completion_cur, completion_prev, g_strcmp0 (command, "start") == 0); goto out; } else if (g_strcmp0 (command, "complete") == 0 && argc == 4 && !request_completion) { const gchar *completion_line; gchar **completion_argv; gint completion_argc; gint completion_point; gchar *endp; gint cur_begin; request_completion = TRUE; completion_line = argv[2]; completion_point = strtol (argv[3], &endp, 10); if (endp == argv[3] || *endp != '\0') goto out; completion_debug ("completion_point=%d", completion_point); completion_debug ("----"); completion_debug (" 0123456789012345678901234567890123456789012345678901234567890123456789"); completion_debug ("`%s'", completion_line); completion_debug (" %*s^", completion_point, ""); completion_debug ("----"); if (!g_shell_parse_argv (completion_line, &completion_argc, &completion_argv, NULL)) { /* it's very possible the command line can't be parsed (for * example, missing quotes etc) - in that case, we just * don't autocomplete at all */ goto out; } /* compute cur and prev */ completion_prev = NULL; completion_cur = pick_word_at (completion_line, completion_point, &cur_begin); if (cur_begin > 0) { gint prev_end; for (prev_end = cur_begin - 1; prev_end >= 0; prev_end--) { if (!g_ascii_isspace (completion_line[prev_end])) { completion_prev = pick_word_at (completion_line, prev_end, NULL); break; } } } completion_debug (" cur=`%s'", completion_cur); completion_debug ("prev=`%s'", completion_prev); argc = completion_argc; argv = completion_argv; ret = 0; goto again; } else { if (request_completion) { g_print ("help \n" //"info \n" "list \n" "start \n" "stop \n" ); ret = 0; goto out; } else { g_printerr ("Unknown command `%s'\n", command); usage (&argc, &argv, FALSE); goto out; } } out: if (loop != NULL) g_main_loop_unref (loop); _color_shutdown (); return ret; } #ifdef COMPLETION_DEBUG G_GNUC_UNUSED static void completion_debug (const gchar *format, ...) { va_list var_args; gchar *s; static FILE *f = NULL; va_start (var_args, format); s = g_strdup_vprintf (format, var_args); if (f == NULL) { f = fopen ("/tmp/stc-completion-debug.txt", "a+"); } fprintf (f, "%s\n", s); g_free (s); } #else static void completion_debug (const gchar *format, ...) { } #endif