| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2017 Intel Corporation. All rights reserved. |
| * |
| * |
| * 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.1 of the License, 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <errno.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <stdarg.h> |
| #include <stdbool.h> |
| #include <signal.h> |
| #include <sys/signalfd.h> |
| #include <wordexp.h> |
| #include <getopt.h> |
| |
| #include <readline/readline.h> |
| #include <readline/history.h> |
| |
| #include "src/shared/mainloop.h" |
| #include "src/shared/timeout.h" |
| #include "src/shared/io.h" |
| #include "src/shared/util.h" |
| #include "src/shared/queue.h" |
| #include "src/shared/shell.h" |
| |
| #define CMD_LENGTH 48 |
| #define print_text(color, fmt, args...) \ |
| printf(color fmt COLOR_OFF "\n", ## args) |
| #define print_menu(cmd, args, desc) \ |
| printf(COLOR_HIGHLIGHT "%s %-*s " COLOR_OFF "%s\n", \ |
| cmd, (int)(CMD_LENGTH - strlen(cmd)), args, desc) |
| #define print_submenu(cmd, desc) \ |
| printf(COLOR_BLUE "%s %-*s " COLOR_OFF "%s\n", \ |
| cmd, (int)(CMD_LENGTH - strlen(cmd)), "", desc) |
| |
| struct bt_shell_env { |
| char *name; |
| void *value; |
| }; |
| |
| static struct { |
| bool init; |
| int argc; |
| char **argv; |
| bool mode; |
| int timeout; |
| struct io *input; |
| |
| bool saved_prompt; |
| bt_shell_prompt_input_func saved_func; |
| void *saved_user_data; |
| |
| const struct bt_shell_menu *menu; |
| const struct bt_shell_menu *main; |
| struct queue *submenus; |
| const struct bt_shell_menu_entry *exec; |
| |
| struct queue *envs; |
| } data; |
| |
| static void shell_print_menu(void); |
| |
| static void cmd_version(int argc, char *argv[]) |
| { |
| bt_shell_printf("Version %s\n", VERSION); |
| |
| return bt_shell_noninteractive_quit(EXIT_SUCCESS); |
| } |
| |
| static void cmd_quit(int argc, char *argv[]) |
| { |
| mainloop_quit(); |
| } |
| |
| static void cmd_help(int argc, char *argv[]) |
| { |
| shell_print_menu(); |
| |
| return bt_shell_noninteractive_quit(EXIT_SUCCESS); |
| } |
| |
| static const struct bt_shell_menu *find_menu(const char *name) |
| { |
| const struct queue_entry *entry; |
| |
| for (entry = queue_get_entries(data.submenus); entry; |
| entry = entry->next) { |
| struct bt_shell_menu *menu = entry->data; |
| |
| if (!strcmp(menu->name, name)) |
| return menu; |
| } |
| |
| return NULL; |
| } |
| |
| static char *menu_generator(const char *text, int state) |
| { |
| static unsigned int index, len; |
| static struct queue_entry *entry; |
| |
| if (!state) { |
| index = 0; |
| len = strlen(text); |
| entry = (void *) queue_get_entries(data.submenus); |
| } |
| |
| for (; entry; entry = entry->next) { |
| struct bt_shell_menu *menu = entry->data; |
| |
| index++; |
| |
| if (!strncmp(menu->name, text, len)) { |
| entry = entry->next; |
| return strdup(menu->name); |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static void cmd_menu(int argc, char *argv[]) |
| { |
| const struct bt_shell_menu *menu; |
| |
| if (argc < 2 || !strlen(argv[1])) { |
| bt_shell_printf("Missing name argument\n"); |
| return bt_shell_noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| menu = find_menu(argv[1]); |
| if (!menu) { |
| bt_shell_printf("Unable find menu with name: %s\n", argv[1]); |
| return bt_shell_noninteractive_quit(EXIT_FAILURE); |
| } |
| |
| bt_shell_set_menu(menu); |
| |
| shell_print_menu(); |
| |
| return bt_shell_noninteractive_quit(EXIT_SUCCESS); |
| } |
| |
| static bool cmd_menu_exists(const struct bt_shell_menu *menu) |
| { |
| /* Skip menu command if not on main menu or if there are no |
| * submenus. |
| */ |
| if (menu != data.main || queue_isempty(data.submenus)) |
| return false; |
| |
| return true; |
| } |
| |
| static void cmd_back(int argc, char *argv[]) |
| { |
| if (data.menu == data.main) { |
| bt_shell_printf("Already on main menu\n"); |
| return; |
| } |
| |
| bt_shell_set_menu(data.main); |
| |
| shell_print_menu(); |
| } |
| |
| static bool cmd_back_exists(const struct bt_shell_menu *menu) |
| { |
| /* Skip back command if on main menu */ |
| if (menu == data.main) |
| return false; |
| |
| return true; |
| } |
| |
| static const struct bt_shell_menu_entry default_menu[] = { |
| { "back", NULL, cmd_back, "Return to main menu", NULL, |
| NULL, cmd_back_exists }, |
| { "menu", "<name>", cmd_menu, "Select submenu", |
| menu_generator, NULL, |
| cmd_menu_exists}, |
| { "version", NULL, cmd_version, "Display version" }, |
| { "quit", NULL, cmd_quit, "Quit program" }, |
| { "exit", NULL, cmd_quit, "Quit program" }, |
| { "help", NULL, cmd_help, |
| "Display help about this program" }, |
| { } |
| }; |
| |
| static void shell_print_help(void) |
| { |
| print_text(COLOR_HIGHLIGHT, |
| "\n" |
| "Use \"help\" for a list of available commands in a menu.\n" |
| "Use \"menu <submenu>\" if you want to enter any submenu.\n" |
| "Use \"back\" if you want to return to menu main."); |
| } |
| |
| static void shell_print_menu(void) |
| { |
| const struct bt_shell_menu_entry *entry; |
| const struct queue_entry *submenu; |
| |
| if (!data.menu) |
| return; |
| |
| print_text(COLOR_HIGHLIGHT, "Menu %s:", data.menu->name); |
| print_text(COLOR_HIGHLIGHT, "Available commands:"); |
| print_text(COLOR_HIGHLIGHT, "-------------------"); |
| |
| if (data.menu == data.main) { |
| for (submenu = queue_get_entries(data.submenus); submenu; |
| submenu = submenu->next) { |
| struct bt_shell_menu *menu = submenu->data; |
| |
| print_submenu(menu->name, menu->desc ? menu->desc : |
| "Submenu"); |
| } |
| } |
| |
| for (entry = data.menu->entries; entry->cmd; entry++) { |
| print_menu(entry->cmd, entry->arg ? : "", entry->desc ? : ""); |
| } |
| |
| for (entry = default_menu; entry->cmd; entry++) { |
| if (entry->exists && !entry->exists(data.menu)) |
| continue; |
| |
| print_menu(entry->cmd, entry->arg ? : "", entry->desc ? : ""); |
| } |
| } |
| |
| static int parse_args(char *arg, wordexp_t *w, char *del, int flags) |
| { |
| char *str; |
| |
| str = strdelimit(arg, del, '"'); |
| |
| if (wordexp(str, w, flags)) { |
| free(str); |
| return -EINVAL; |
| } |
| |
| /* If argument ends with ,,, set we_offs bypass strict checks */ |
| if (w->we_wordc && strsuffix(w->we_wordv[w->we_wordc -1], "...")) |
| w->we_offs = 1; |
| |
| free(str); |
| |
| return 0; |
| } |
| |
| static int cmd_exec(const struct bt_shell_menu_entry *entry, |
| int argc, char *argv[]) |
| { |
| wordexp_t w; |
| size_t len; |
| char *man, *opt; |
| int flags = WRDE_NOCMD; |
| |
| if (!entry->arg || entry->arg[0] == '\0') { |
| if (argc > 1) { |
| print_text(COLOR_HIGHLIGHT, "Too many arguments"); |
| return -EINVAL; |
| } |
| goto exec; |
| } |
| |
| /* Find last mandatory arguments */ |
| man = strrchr(entry->arg, '>'); |
| if (!man) { |
| opt = strdup(entry->arg); |
| goto optional; |
| } |
| |
| len = man - entry->arg; |
| if (entry->arg[0] == '<') |
| man = strndup(entry->arg, len + 1); |
| else { |
| /* Find where mandatory arguments start */ |
| opt = strrchr(entry->arg, '<'); |
| /* Skip if mandatory arguments are not in the right format */ |
| if (!opt || opt > man) { |
| opt = strdup(entry->arg); |
| goto optional; |
| } |
| man = strndup(opt, man - opt + 1); |
| } |
| |
| if (parse_args(man, &w, "<>", flags) < 0) { |
| print_text(COLOR_HIGHLIGHT, |
| "Unable to parse mandatory command arguments: %s", man ); |
| return -EINVAL; |
| } |
| |
| /* Check if there are enough arguments */ |
| if ((unsigned) argc - 1 < w.we_wordc) { |
| print_text(COLOR_HIGHLIGHT, "Missing %s argument", |
| w.we_wordv[argc - 1]); |
| goto fail; |
| } |
| |
| flags |= WRDE_APPEND; |
| opt = strdup(entry->arg + len + 1); |
| |
| optional: |
| if (parse_args(opt, &w, "[]", flags) < 0) { |
| print_text(COLOR_HIGHLIGHT, |
| "Unable to parse optional command arguments: %s", opt); |
| return -EINVAL; |
| } |
| |
| /* Check if there are too many arguments */ |
| if ((unsigned) argc - 1 > w.we_wordc && !w.we_offs) { |
| print_text(COLOR_HIGHLIGHT, "Too many arguments: %d > %zu", |
| argc - 1, w.we_wordc); |
| goto fail; |
| } |
| |
| wordfree(&w); |
| |
| exec: |
| data.exec = entry; |
| |
| if (entry->func) |
| entry->func(argc, argv); |
| |
| data.exec = NULL; |
| |
| return 0; |
| |
| fail: |
| wordfree(&w); |
| return -EINVAL; |
| } |
| |
| static int menu_exec(const struct bt_shell_menu_entry *entry, |
| int argc, char *argv[]) |
| { |
| for (; entry->cmd; entry++) { |
| if (strcmp(argv[0], entry->cmd)) |
| continue; |
| |
| /* Skip menu command if not on main menu */ |
| if (data.menu != data.main && !strcmp(entry->cmd, "menu")) |
| continue; |
| |
| /* Skip back command if on main menu */ |
| if (data.menu == data.main && !strcmp(entry->cmd, "back")) |
| continue; |
| |
| return cmd_exec(entry, argc, argv); |
| } |
| |
| return -ENOENT; |
| } |
| |
| static int submenu_exec(int argc, char *argv[]) |
| { |
| char *name; |
| int len, tlen; |
| const struct bt_shell_menu *submenu; |
| |
| if (data.menu != data.main) |
| return -ENOENT; |
| |
| name = strchr(argv[0], '.'); |
| if (!name) |
| return -ENOENT; |
| |
| tlen = strlen(argv[0]); |
| len = name - argv[0]; |
| name[0] = '\0'; |
| |
| submenu = find_menu(argv[0]); |
| if (!submenu) |
| return -ENOENT; |
| |
| /* Replace submenu.command with command */ |
| memmove(argv[0], argv[0] + len + 1, tlen - len - 1); |
| memset(argv[0] + tlen - len - 1, 0, len + 1); |
| |
| return menu_exec(submenu->entries, argc, argv); |
| } |
| |
| static int shell_exec(int argc, char *argv[]) |
| { |
| int err; |
| |
| if (!data.menu || !argv[0]) |
| return -EINVAL; |
| |
| err = menu_exec(default_menu, argc, argv); |
| if (err == -ENOENT) { |
| err = menu_exec(data.menu->entries, argc, argv); |
| if (err == -ENOENT) { |
| err = submenu_exec(argc, argv); |
| if (err == -ENOENT) { |
| print_text(COLOR_HIGHLIGHT, |
| "Invalid command in menu %s: %s", |
| data.menu->name , argv[0]); |
| shell_print_help(); |
| } |
| } |
| } |
| |
| return err; |
| } |
| |
| void bt_shell_printf(const char *fmt, ...) |
| { |
| va_list args; |
| bool save_input; |
| char *saved_line; |
| int saved_point; |
| |
| if (!data.input) |
| return; |
| |
| if (data.mode) { |
| va_start(args, fmt); |
| vprintf(fmt, args); |
| va_end(args); |
| return; |
| } |
| |
| save_input = !RL_ISSTATE(RL_STATE_DONE); |
| |
| if (save_input) { |
| saved_point = rl_point; |
| saved_line = rl_copy_text(0, rl_end); |
| rl_save_prompt(); |
| rl_replace_line("", 0); |
| rl_redisplay(); |
| } |
| |
| va_start(args, fmt); |
| vprintf(fmt, args); |
| va_end(args); |
| |
| if (save_input) { |
| rl_restore_prompt(); |
| rl_replace_line(saved_line, 0); |
| rl_point = saved_point; |
| rl_forced_update_display(); |
| free(saved_line); |
| } |
| } |
| |
| static void print_string(const char *str, void *user_data) |
| { |
| bt_shell_printf("%s\n", str); |
| } |
| |
| void bt_shell_hexdump(const unsigned char *buf, size_t len) |
| { |
| util_hexdump(' ', buf, len, print_string, NULL); |
| } |
| |
| void bt_shell_usage() |
| { |
| if (!data.exec) |
| return; |
| |
| bt_shell_printf("Usage: %s %s\n", data.exec->cmd, |
| data.exec->arg ? data.exec->arg : ""); |
| } |
| |
| void bt_shell_prompt_input(const char *label, const char *msg, |
| bt_shell_prompt_input_func func, void *user_data) |
| { |
| if (!data.init || data.mode) |
| return; |
| |
| /* Normal use should not prompt for user input to the value a second |
| * time before it releases the prompt, but we take a safe action. */ |
| if (data.saved_prompt) |
| return; |
| |
| rl_save_prompt(); |
| rl_message(COLOR_RED "[%s]" COLOR_OFF " %s ", label, msg); |
| |
| data.saved_prompt = true; |
| data.saved_func = func; |
| data.saved_user_data = user_data; |
| } |
| |
| int bt_shell_release_prompt(const char *input) |
| { |
| bt_shell_prompt_input_func func; |
| void *user_data; |
| |
| if (!data.saved_prompt) |
| return -1; |
| |
| data.saved_prompt = false; |
| |
| rl_restore_prompt(); |
| |
| func = data.saved_func; |
| user_data = data.saved_user_data; |
| |
| data.saved_func = NULL; |
| data.saved_user_data = NULL; |
| |
| func(input, user_data); |
| |
| return 0; |
| } |
| |
| static void rl_handler(char *input) |
| { |
| wordexp_t w; |
| |
| if (!input) { |
| rl_insert_text("quit"); |
| rl_redisplay(); |
| rl_crlf(); |
| mainloop_quit(); |
| return; |
| } |
| |
| if (!strlen(input)) |
| goto done; |
| |
| if (!bt_shell_release_prompt(input)) |
| goto done; |
| |
| if (history_search(input, -1)) |
| add_history(input); |
| |
| if (wordexp(input, &w, WRDE_NOCMD)) |
| goto done; |
| |
| if (w.we_wordc == 0) { |
| wordfree(&w); |
| goto done; |
| } |
| |
| shell_exec(w.we_wordc, w.we_wordv); |
| wordfree(&w); |
| done: |
| free(input); |
| } |
| |
| static char *find_cmd(const char *text, |
| const struct bt_shell_menu_entry *entry, int *index) |
| { |
| const struct bt_shell_menu_entry *tmp; |
| int len; |
| |
| len = strlen(text); |
| |
| while ((tmp = &entry[*index])) { |
| (*index)++; |
| |
| if (!tmp->cmd) |
| break; |
| |
| if (tmp->exists && !tmp->exists(data.menu)) |
| continue; |
| |
| if (!strncmp(tmp->cmd, text, len)) |
| return strdup(tmp->cmd); |
| } |
| |
| return NULL; |
| } |
| |
| static char *cmd_generator(const char *text, int state) |
| { |
| static int index; |
| static bool default_menu_enabled; |
| char *cmd; |
| |
| if (!state) { |
| index = 0; |
| default_menu_enabled = true; |
| } |
| |
| if (default_menu_enabled) { |
| cmd = find_cmd(text, default_menu, &index); |
| if (cmd) { |
| return cmd; |
| } else { |
| index = 0; |
| default_menu_enabled = false; |
| } |
| } |
| |
| return find_cmd(text, data.menu->entries, &index); |
| } |
| |
| static wordexp_t args; |
| |
| static char *arg_generator(const char *text, int state) |
| { |
| static unsigned int index, len; |
| const char *arg; |
| |
| if (!state) { |
| index = 0; |
| len = strlen(text); |
| } |
| |
| while (index < args.we_wordc) { |
| arg = args.we_wordv[index]; |
| index++; |
| |
| if (!strncmp(arg, text, len)) |
| return strdup(arg); |
| } |
| |
| return NULL; |
| } |
| |
| static char **args_completion(const struct bt_shell_menu_entry *entry, int argc, |
| const char *text) |
| { |
| char **matches = NULL; |
| char *str; |
| int index; |
| |
| index = text[0] == '\0' ? argc - 1 : argc - 2; |
| if (index < 0) |
| return NULL; |
| |
| if (!entry->arg) |
| goto done; |
| |
| str = strdup(entry->arg); |
| |
| if (parse_args(str, &args, "<>[]", WRDE_NOCMD)) |
| goto done; |
| |
| /* Check if argument is valid */ |
| if ((unsigned) index > args.we_wordc - 1) |
| goto done; |
| |
| /* Check if there are multiple values */ |
| if (!strrchr(entry->arg, '/')) |
| goto done; |
| |
| /* Split values separated by / */ |
| str = strdelimit(args.we_wordv[index], "/", ' '); |
| |
| if (wordexp(str, &args, WRDE_NOCMD)) |
| goto done; |
| |
| rl_completion_display_matches_hook = NULL; |
| matches = rl_completion_matches(text, arg_generator); |
| |
| done: |
| if (!matches && text[0] == '\0') |
| bt_shell_printf("Usage: %s %s\n", entry->cmd, |
| entry->arg ? entry->arg : ""); |
| |
| wordfree(&args); |
| return matches; |
| } |
| |
| static char **menu_completion(const struct bt_shell_menu_entry *entry, |
| const char *text, int argc, char *input_cmd) |
| { |
| char **matches = NULL; |
| |
| for (; entry->cmd; entry++) { |
| if (strcmp(entry->cmd, input_cmd)) |
| continue; |
| |
| if (!entry->gen) { |
| matches = args_completion(entry, argc, text); |
| break; |
| } |
| |
| rl_completion_display_matches_hook = entry->disp; |
| matches = rl_completion_matches(text, entry->gen); |
| break; |
| } |
| |
| return matches; |
| } |
| |
| static char **shell_completion(const char *text, int start, int end) |
| { |
| char **matches = NULL; |
| |
| if (!data.menu) |
| return NULL; |
| |
| if (start > 0) { |
| wordexp_t w; |
| |
| if (wordexp(rl_line_buffer, &w, WRDE_NOCMD)) |
| return NULL; |
| |
| matches = menu_completion(default_menu, text, w.we_wordc, |
| w.we_wordv[0]); |
| if (!matches) |
| matches = menu_completion(data.menu->entries, text, |
| w.we_wordc, |
| w.we_wordv[0]); |
| |
| wordfree(&w); |
| } else { |
| rl_completion_display_matches_hook = NULL; |
| matches = rl_completion_matches(text, cmd_generator); |
| } |
| |
| if (!matches) |
| rl_attempted_completion_over = 1; |
| |
| return matches; |
| } |
| |
| static bool io_hup(struct io *io, void *user_data) |
| { |
| mainloop_quit(); |
| |
| return false; |
| } |
| |
| static bool signal_read(struct io *io, void *user_data) |
| { |
| static bool terminated = false; |
| struct signalfd_siginfo si; |
| ssize_t result; |
| int fd; |
| |
| fd = io_get_fd(io); |
| |
| result = read(fd, &si, sizeof(si)); |
| if (result != sizeof(si)) |
| return false; |
| |
| switch (si.ssi_signo) { |
| case SIGINT: |
| if (data.input && !data.mode) { |
| rl_replace_line("", 0); |
| rl_crlf(); |
| rl_on_new_line(); |
| rl_redisplay(); |
| return true; |
| } |
| |
| /* |
| * If input was not yet setup up that means signal was received |
| * while daemon was not yet running. Since user is not able |
| * to terminate client by CTRL-D or typing exit treat this as |
| * exit and fall through. |
| */ |
| |
| /* fall through */ |
| case SIGTERM: |
| if (!terminated) { |
| if (!data.mode) { |
| rl_replace_line("", 0); |
| rl_crlf(); |
| } |
| mainloop_quit(); |
| } |
| |
| terminated = true; |
| break; |
| } |
| |
| return false; |
| } |
| |
| static struct io *setup_signalfd(void) |
| { |
| struct io *io; |
| sigset_t mask; |
| int fd; |
| |
| sigemptyset(&mask); |
| sigaddset(&mask, SIGINT); |
| sigaddset(&mask, SIGTERM); |
| |
| if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) { |
| perror("Failed to set signal mask"); |
| return 0; |
| } |
| |
| fd = signalfd(-1, &mask, 0); |
| if (fd < 0) { |
| perror("Failed to create signal descriptor"); |
| return 0; |
| } |
| |
| io = io_new(fd); |
| |
| io_set_close_on_destroy(io, true); |
| io_set_read_handler(io, signal_read, NULL, NULL); |
| io_set_disconnect_handler(io, io_hup, NULL, NULL); |
| |
| return io; |
| } |
| |
| static void rl_init(void) |
| { |
| if (data.mode) |
| return; |
| |
| setlinebuf(stdout); |
| rl_attempted_completion_function = shell_completion; |
| |
| rl_erase_empty_line = 1; |
| rl_callback_handler_install(NULL, rl_handler); |
| } |
| |
| static const struct option main_options[] = { |
| { "version", no_argument, 0, 'v' }, |
| { "help", no_argument, 0, 'h' }, |
| { "timeout", required_argument, 0, 't' }, |
| }; |
| |
| static void usage(int argc, char **argv, const struct bt_shell_opt *opt) |
| { |
| unsigned int i; |
| |
| printf("%s ver %s\n", argv[0], VERSION); |
| printf("Usage:\n" |
| "\t%s [options]\n", argv[0]); |
| |
| printf("Options:\n"); |
| |
| for (i = 0; opt && opt->options[i].name; i++) |
| printf("\t--%s \t%s\n", opt->options[i].name, opt->help[i]); |
| |
| printf("\t--timeout \tTimeout in seconds for non-interactive mode\n" |
| "\t--version \tDisplay version\n" |
| "\t--help \t\tDisplay help\n"); |
| } |
| |
| void bt_shell_init(int argc, char **argv, const struct bt_shell_opt *opt) |
| { |
| int c, index = -1; |
| struct option options[256]; |
| char optstr[256]; |
| size_t offset; |
| |
| offset = sizeof(main_options) / sizeof(struct option); |
| |
| memcpy(options, main_options, sizeof(struct option) * offset); |
| |
| if (opt) { |
| memcpy(options + offset, opt->options, |
| sizeof(struct option) * opt->optno); |
| snprintf(optstr, sizeof(optstr), "+hvt:%s", opt->optstr); |
| } else |
| snprintf(optstr, sizeof(optstr), "+hvt:"); |
| |
| while ((c = getopt_long(argc, argv, optstr, options, &index)) != -1) { |
| switch (c) { |
| case 'v': |
| printf("%s: %s\n", argv[0], VERSION); |
| exit(EXIT_SUCCESS); |
| return; |
| case 'h': |
| usage(argc, argv, opt); |
| exit(EXIT_SUCCESS); |
| return; |
| case 't': |
| data.timeout = atoi(optarg); |
| break; |
| default: |
| if (index < 0) { |
| for (index = 0; options[index].val; index++) { |
| if (c == options[index].val) |
| break; |
| } |
| } |
| |
| if (c != opt->options[index - offset].val) { |
| usage(argc, argv, opt); |
| exit(EXIT_SUCCESS); |
| return; |
| } |
| |
| *opt->optarg[index - offset] = optarg; |
| } |
| } |
| |
| data.argc = argc - optind; |
| data.argv = argv + optind; |
| optind = 0; |
| data.mode = (data.argc > 0); |
| |
| if (data.mode) |
| bt_shell_set_env("NON_INTERACTIVE", &data.mode); |
| |
| mainloop_init(); |
| |
| rl_init(); |
| |
| data.init = true; |
| } |
| |
| static void rl_cleanup(void) |
| { |
| if (data.mode) |
| return; |
| |
| rl_message(""); |
| rl_callback_handler_remove(); |
| } |
| |
| static void env_destroy(void *data) |
| { |
| struct bt_shell_env *env = data; |
| |
| free(env->name); |
| free(env); |
| } |
| |
| void bt_shell_run(void) |
| { |
| struct io *signal; |
| |
| signal = setup_signalfd(); |
| |
| mainloop_run(); |
| |
| bt_shell_release_prompt(""); |
| bt_shell_detach(); |
| |
| io_destroy(signal); |
| |
| if (data.envs) { |
| queue_destroy(data.envs, env_destroy); |
| data.envs = NULL; |
| } |
| |
| rl_cleanup(); |
| |
| data.init = false; |
| } |
| |
| void bt_shell_quit(int status) |
| { |
| if (status == EXIT_SUCCESS) |
| mainloop_exit_success(); |
| else |
| mainloop_exit_failure(); |
| } |
| |
| void bt_shell_noninteractive_quit(int status) |
| { |
| if (!data.mode || data.timeout) |
| return; |
| |
| bt_shell_quit(status); |
| } |
| |
| bool bt_shell_set_menu(const struct bt_shell_menu *menu) |
| { |
| if (!menu) |
| return false; |
| |
| data.menu = menu; |
| |
| if (!data.main) |
| data.main = menu; |
| |
| return true; |
| } |
| |
| bool bt_shell_add_submenu(const struct bt_shell_menu *menu) |
| { |
| if (!menu) |
| return false; |
| |
| if (!data.submenus) |
| data.submenus = queue_new(); |
| |
| queue_push_tail(data.submenus, (void *) menu); |
| |
| return true; |
| } |
| |
| void bt_shell_set_prompt(const char *string) |
| { |
| if (!data.init || data.mode) |
| return; |
| |
| rl_set_prompt(string); |
| printf("\r"); |
| rl_on_new_line(); |
| rl_redisplay(); |
| } |
| |
| static bool input_read(struct io *io, void *user_data) |
| { |
| rl_callback_read_char(); |
| return true; |
| } |
| |
| static bool shell_quit(void *data) |
| { |
| mainloop_quit(); |
| |
| return false; |
| } |
| |
| bool bt_shell_attach(int fd) |
| { |
| struct io *io; |
| |
| /* TODO: Allow more than one input? */ |
| if (data.input) |
| return false; |
| |
| io = io_new(fd); |
| |
| if (!data.mode) |
| io_set_read_handler(io, input_read, NULL, NULL); |
| |
| io_set_disconnect_handler(io, io_hup, NULL, NULL); |
| |
| data.input = io; |
| |
| if (data.mode) { |
| if (shell_exec(data.argc, data.argv) < 0) { |
| bt_shell_noninteractive_quit(EXIT_FAILURE); |
| return true; |
| } |
| |
| if (data.timeout) |
| timeout_add(data.timeout * 1000, shell_quit, NULL, |
| NULL); |
| } |
| |
| return true; |
| } |
| |
| bool bt_shell_detach(void) |
| { |
| if (!data.input) |
| return false; |
| |
| io_destroy(data.input); |
| data.input = NULL; |
| |
| return true; |
| } |
| |
| static bool match_env(const void *data, const void *user_data) |
| { |
| const struct bt_shell_env *env = data; |
| const char *name = user_data; |
| |
| return !strcmp(env->name, name); |
| } |
| |
| void bt_shell_set_env(const char *name, void *value) |
| { |
| struct bt_shell_env *env; |
| |
| if (!data.envs) { |
| data.envs = queue_new(); |
| goto done; |
| } |
| |
| env = queue_remove_if(data.envs, match_env, (void *) name); |
| if (env) |
| env_destroy(env); |
| |
| done: |
| env = new0(struct bt_shell_env, 1); |
| env->name = strdup(name); |
| env->value = value; |
| |
| queue_push_tail(data.envs, env); |
| } |
| |
| void *bt_shell_get_env(const char *name) |
| { |
| const struct bt_shell_env *env; |
| |
| if (!data.envs) |
| return NULL; |
| |
| env = queue_find(data.envs, match_env, name); |
| if (!env) |
| return NULL; |
| |
| return env->value; |
| } |