This patch set addresses the need of using bt-shell functionality when running with ell mainloop. Specifically, need this for implementation of mesh profisioning tool: mesh daemon is implemented using ell library and is driven by ell main loop. Inga Stotland (2): shared/shell: Add ell based shell implementation shared/shell: Add "clear-history" API Makefile.am | 13 +- src/shared/shell-ell.c | 1325 ++++++++++++++++++++++++++++++++++++++++ src/shared/shell.c | 7 +- src/shared/shell.h | 1 + 4 files changed, 1342 insertions(+), 4 deletions(-) create mode 100644 src/shared/shell-ell.c -- 2.21.0
This adds the functionality of bt_shell that uses ell mainloop. --- Makefile.am | 13 +- src/shared/shell-ell.c | 1320 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1330 insertions(+), 3 deletions(-) create mode 100644 src/shared/shell-ell.c diff --git a/Makefile.am b/Makefile.am index 9d25a815b..0ce3048ad 100644 --- a/Makefile.am +++ b/Makefile.am @@ -187,9 +187,6 @@ shared_sources = src/shared/io.h src/shared/timeout.h \ src/shared/log.h src/shared/log.c \ src/shared/tty.h -if READLINE -shared_sources += src/shared/shell.c src/shared/shell.h -endif src_libshared_glib_la_SOURCES = $(shared_sources) \ src/shared/io-glib.c \ @@ -205,11 +202,21 @@ src_libshared_mainloop_la_SOURCES = $(shared_sources) \ src/shared/mainloop-notify.h \ src/shared/mainloop-notify.c +if READLINE +shared_sources += src/shared/shell.h +src_libshared_glib_la_SOURCES += src/shared/shell.c +src_libshared_mainloop_la_SOURCES += src/shared/shell.c +endif + if LIBSHARED_ELL noinst_LTLIBRARIES += src/libshared-ell.la src_libshared_ell_la_SOURCES = $(shared_sources) \ src/shared/io-ell.c + +if READLINE +src_libshared_ell_la_SOURCES += src/shared/shell-ell.c +endif endif attrib_sources = attrib/att.h attrib/att-database.h attrib/att.c \ diff --git a/src/shared/shell-ell.c b/src/shared/shell-ell.c new file mode 100644 index 000000000..1b481e04e --- /dev/null +++ b/src/shared/shell-ell.c @@ -0,0 +1,1320 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2017-2019 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. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define _GNU_SOURCE +#include <stdio.h> +#include <errno.h> +#include <syslog.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 "ell/ell.h" + +#include "src/shared/util.h" +#include "src/shared/log.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 char *cmplt = "help"; + +struct bt_shell_prompt_input { + char *str; + bt_shell_prompt_input_func func; + void *user_data; +}; + +static struct { + bool init; + char *name; + char history[256]; + int argc; + char **argv; + bool mode; + bool monitor; + int timeout; + struct l_io *input; + + bool saved_prompt; + bt_shell_prompt_input_func saved_func; + void *saved_user_data; + + struct l_queue *prompts; + + const struct bt_shell_menu *menu; + const struct bt_shell_menu *main; + struct l_queue *submenus; + const struct bt_shell_menu_entry *exec; + + struct l_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[]) +{ + l_main_quit(); +} + +static void print_cmds(void) +{ + const struct bt_shell_menu_entry *entry; + const struct l_queue_entry *submenu; + + if (!data.menu) + return; + + printf("Commands:\n"); + + for (entry = data.menu->entries; entry->cmd; entry++) { + printf("\t%s%s\t%s\n", entry->cmd, + strlen(entry->cmd) < 8 ? "\t" : "", entry->desc); + } + + for (submenu = l_queue_get_entries(data.submenus); submenu; + submenu = submenu->next) { + struct bt_shell_menu *menu = submenu->data; + + printf("\n\t%s.:\n", menu->name); + + for (entry = menu->entries; entry->cmd; entry++) { + printf("\t\t%s%s\t%s\n", entry->cmd, + strlen(entry->cmd) < 8 ? "\t" : "", + entry->desc); + } + } +} + +static void cmd_help(int argc, char *argv[]) +{ + if (argv[0] == cmplt) + print_cmds(); + else + shell_print_menu(); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static const struct bt_shell_menu *find_menu(const char *name, size_t len) +{ + const struct l_queue_entry *entry; + + for (entry = l_queue_get_entries(data.submenus); entry; + entry = entry->next) { + struct bt_shell_menu *menu = entry->data; + + if (!strncmp(menu->name, name, len)) + return menu; + } + + return NULL; +} + +static char *menu_generator(const char *text, int state) +{ + static unsigned int index, len; + static struct l_queue_entry *entry; + + if (!state) { + index = 0; + len = strlen(text); + entry = (void *) l_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], strlen(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 || l_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 void cmd_export(int argc, char *argv[]) +{ + const struct l_queue_entry *entry; + + entry = l_queue_get_entries(data.envs); + + for (; entry; entry = entry->next) { + struct bt_shell_env *env = entry->data; + + print_text(COLOR_HIGHLIGHT, "%s=%p", env->name, env->value); + } +} + +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" }, + { "export", NULL, cmd_export, + "Print evironment variables" }, + { } +}; + +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 l_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 = l_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; + bool optargs = false; + + 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); + optargs = true; + } + + if (parse_args(man, &w, "<>", flags) < 0) { + print_text(COLOR_HIGHLIGHT, + "Unable to parse mandatory command arguments: %s", man); + free(man); + return -EINVAL; + } + + free(man); + + /* Check if there are enough arguments */ + if ((unsigned int) 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); + free(opt); + return -EINVAL; + } + + free(opt); + + /* Check if there are too many arguments */ + if (!optargs && ((unsigned int) argc - 1 > w.we_wordc && !w.we_offs)) { + print_text(COLOR_HIGHLIGHT, "Too many arguments: %d > %zu", + argc - 1, w.we_wordc); + goto fail; + } + + w.we_offs = 0; + wordfree(&w); + +exec: + data.exec = entry; + + if (entry->func) + entry->func(argc, argv); + + data.exec = NULL; + + return 0; + +fail: + w.we_offs = 0; + 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], strlen(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); + if (!data.saved_prompt) + rl_save_prompt(); + rl_replace_line("", 0); + rl_redisplay(); + } + + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + + if (data.monitor) { + va_start(args, fmt); + bt_log_vprintf(0xffff, data.name, LOG_INFO, fmt, args); + va_end(args); + } + + if (save_input) { + if (!data.saved_prompt) + 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(void) +{ + if (!data.exec) + return; + + bt_shell_printf("Usage: %s %s\n", data.exec->cmd, + data.exec->arg ? data.exec->arg : ""); +} + +static void prompt_input(const char *str, bt_shell_prompt_input_func func, + void *user_data) +{ + data.saved_prompt = true; + data.saved_func = func; + data.saved_user_data = user_data; + + rl_save_prompt(); + bt_shell_set_prompt(str); +} + +void bt_shell_prompt_input(const char *label, const char *msg, + bt_shell_prompt_input_func func, void *user_data) +{ + char *str; + + if (!data.init || data.mode) + return; + + if (data.saved_prompt) { + struct bt_shell_prompt_input *prompt; + + prompt = l_new(struct bt_shell_prompt_input, 1); + + if (asprintf(&str, COLOR_HIGHLIGHT "[%s] %s " COLOR_OFF, label, + msg) < 0) { + free(prompt); + return; + } + + prompt->func = func; + prompt->user_data = user_data; + + l_queue_push_tail(data.prompts, prompt); + + return; + } + + if (asprintf(&str, COLOR_HIGHLIGHT "[%s] %s " COLOR_OFF, label, + msg) < 0) + return; + + prompt_input(str, func, user_data); + + free(str); +} + +static void prompt_free(void *data) +{ + struct bt_shell_prompt_input *prompt = data; + + free(prompt->str); + free(prompt); +} + +int bt_shell_release_prompt(const char *input) +{ + struct bt_shell_prompt_input *prompt; + 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; + + prompt = l_queue_pop_head(data.prompts); + if (prompt) + data.saved_prompt = true; + + data.saved_func = NULL; + data.saved_user_data = NULL; + + func(input, user_data); + + if (prompt) { + prompt_input(prompt->str, prompt->func, prompt->user_data); + prompt_free(prompt); + } + + return 0; +} + +static void rl_handler(char *input) +{ + wordexp_t w; + + if (!input) { + rl_insert_text("quit"); + rl_redisplay(); + rl_crlf(); + l_main_quit(); + return; + } + + if (!strlen(input)) + goto done; + + if (!bt_shell_release_prompt(input)) + goto done; + + if (history_search(input, -1)) + add_history(input); + + if (data.monitor) + bt_log_printf(0xffff, data.name, LOG_INFO, "%s", 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, submenu_enabled; + static const struct bt_shell_menu *menu; + char *cmd; + + if (!state) { + index = 0; + menu = NULL; + default_menu_enabled = true; + submenu_enabled = false; + } + + if (default_menu_enabled) { + cmd = find_cmd(text, default_menu, &index); + if (cmd) + return cmd; + + index = 0; + menu = data.menu; + default_menu_enabled = false; + } + + if (!submenu_enabled) { + cmd = find_cmd(text, menu->entries, &index); + if (cmd || menu != data.main) + return cmd; + + cmd = strrchr(text, '.'); + if (!cmd) + return NULL; + + menu = find_menu(text, cmd - text); + if (!menu) + return NULL; + + index = 0; + submenu_enabled = true; + } + + cmd = find_cmd(text + strlen(menu->name) + 1, menu->entries, &index); + if (cmd) { + int err; + char *tmp; + + err = asprintf(&tmp, "%s.%s", menu->name, cmd); + + free(cmd); + + if (err < 0) + return NULL; + + cmd = tmp; + } + + return cmd; +} + +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 end; + + str = strdup(entry->arg); + + if (parse_args(str, &args, "<>[]", WRDE_NOCMD)) + goto done; + + /* Check if argument is valid */ + if ((unsigned int) index > args.we_wordc - 1) + goto done; + + /* Check if there are multiple values */ + if (!strrchr(entry->arg, '/')) + goto done; + + free(str); + + /* Split values separated by / */ + str = strdelimit(args.we_wordv[index], "/", ' '); + + args.we_offs = 0; + wordfree(&args); + + if (wordexp(str, &args, WRDE_NOCMD)) + goto done; + + rl_completion_display_matches_hook = NULL; + matches = rl_completion_matches(text, arg_generator); + +done: + free(str); +end: + if (!matches && text[0] == '\0') + bt_shell_printf("Usage: %s %s\n", entry->cmd, + entry->arg ? entry->arg : ""); + + args.we_offs = 0; + 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 void io_hup(struct l_io *io, void *user_data) +{ + l_main_quit(); +} + +static void signal_callback(unsigned int signum, void *user_data) +{ + static bool terminated; + + switch (signum) { + case SIGINT: + if (data.input && !data.mode) { + rl_replace_line("", 0); + rl_crlf(); + rl_on_new_line(); + rl_redisplay(); + return; + } + + /* + * 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(); + } + l_main_quit(); + } + + terminated = true; + break; + } +} + +static void rl_init_history(void) +{ + const char *name; + char *dir; + + memset(data.history, 0, sizeof(data.history)); + + name = strrchr(data.name, '/'); + if (!name) + name = data.name; + else + name++; + + dir = getenv("XDG_CACHE_HOME"); + if (dir) { + snprintf(data.history, sizeof(data.history), "%s/.%s_history", + dir, name); + goto done; + } + + dir = getenv("HOME"); + if (dir) { + snprintf(data.history, sizeof(data.history), + "%s/.cache/.%s_history", dir, name); + goto done; + } + + dir = getenv("PWD"); + if (dir) { + snprintf(data.history, sizeof(data.history), "%s/.%s_history", + dir, name); + goto done; + } + + return; + +done: + read_history(data.history); + using_history(); + bt_shell_set_env("HISTORY", data.history); +} + +static void rl_init(void) +{ + if (data.mode) + return; + + /* Allow conditional parsing of the ~/.inputrc file. */ + rl_readline_name = data.name; + + setlinebuf(stdout); + rl_attempted_completion_function = shell_completion; + + rl_erase_empty_line = 1; + rl_callback_handler_install(NULL, rl_handler); + + rl_init_history(); +} + +static const struct option main_options[] = { + { "version", no_argument, 0, 'v' }, + { "help", no_argument, 0, 'h' }, + { "timeout", required_argument, 0, 't' }, + { "monitor", no_argument, 0, 'm' }, +}; + +static void usage(int argc, char **argv, const struct bt_shell_opt *opt) +{ + unsigned int i; + + printf("%s ver %s\n", data.name, VERSION); + printf("Usage:\n" + "\t%s [--options] [commands]\n", data.name); + + 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--monitor \tEnable monitor output\n" + "\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), "+mhvt:%s", opt->optstr); + } else + snprintf(optstr, sizeof(optstr), "+mhvt:"); + + data.name = strrchr(argv[0], '/'); + if (!data.name) + data.name = strdup(argv[0]); + else + data.name = strdup(++data.name); + + while ((c = getopt_long(argc, argv, optstr, options, &index)) != -1) { + switch (c) { + case 'v': + printf("%s: %s\n", data.name, VERSION); + exit(EXIT_SUCCESS); + return; + case 'h': + usage(argc, argv, opt); + data.argc = 1; + data.argv = &cmplt; + data.mode = 1; + goto done; + case 't': + data.timeout = atoi(optarg); + break; + case 'm': + data.monitor = true; + if (bt_log_open() < 0) { + data.monitor = false; + printf("Unable to open logging channel\n"); + } + 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; + } + + index = -1; + } + + bt_shell_set_env("SHELL", data.name); + + data.argc = argc - optind; + data.argv = argv + optind; + optind = 0; + data.mode = (data.argc > 0); + +done: + if (data.mode) + bt_shell_set_env("NON_INTERACTIVE", &data.mode); + + l_main_init(); + + rl_init(); + + data.init = true; + data.prompts = l_queue_new(); +} + +static void rl_cleanup(void) +{ + if (data.mode) + return; + + if (data.history[0] != '\0') + write_history(data.history); + + rl_message(""); + rl_callback_handler_remove(); +} + +static void env_destroy(void *data) +{ + struct bt_shell_env *env = data; + + free(env->name); + free(env); +} + +int bt_shell_run(void) +{ + int status; + + status = l_main_run_with_signal(signal_callback, NULL); + + bt_shell_cleanup(); + + l_main_exit(); + + return status; +} + +void bt_shell_cleanup(void) +{ + bt_shell_release_prompt(""); + bt_shell_detach(); + + if (data.envs) { + l_queue_destroy(data.envs, env_destroy); + data.envs = NULL; + } + + if (data.monitor) + bt_log_close(); + + rl_cleanup(); + + l_queue_destroy(data.prompts, prompt_free); + data.prompts = NULL; + + data.init = false; + free(data.name); +} + +void bt_shell_quit(int status) +{ + l_main_quit(); +} + +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 = l_queue_new(); + + l_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); + rl_redisplay(); +} + +static bool input_read(struct l_io *io, void *user_data) +{ + rl_callback_read_char(); + return true; +} + +static void quit_on_timeout(struct l_timeout *timeout, void *user_data) +{ + l_main_quit(); +} + +bool bt_shell_attach(int fd) +{ + struct l_io *io; + + /* TODO: Allow more than one input? */ + if (data.input) + return false; + + io = l_io_new(fd); + + if (!data.mode) + l_io_set_read_handler(io, input_read, NULL, NULL); + + l_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) + l_timeout_create(data.timeout, quit_on_timeout, NULL, + NULL); + } + + return true; +} + +bool bt_shell_detach(void) +{ + if (!data.input) + return false; + + l_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) { + if (!value) + return; + data.envs = l_queue_new(); + goto done; + } + + env = l_queue_remove_if(data.envs, match_env, (void *) name); + if (env) + env_destroy(env); + + /* Don't create an env if value is not set */ + if (!value) + return; + +done: + env = l_new(struct bt_shell_env, 1); + env->name = strdup(name); + env->value = value; + + l_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 = l_queue_find(data.envs, match_env, name); + if (!env) + return NULL; + + return env->value; +} -- 2.21.0
Thsi adds a new API function bt_shell_clear_history(). The function can be used to clear up the command history, when it gets too cluttered. --- src/shared/shell-ell.c | 5 +++++ src/shared/shell.c | 7 ++++++- src/shared/shell.h | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/shared/shell-ell.c b/src/shared/shell-ell.c index 1b481e04e..1e3de521c 100644 --- a/src/shared/shell-ell.c +++ b/src/shared/shell-ell.c @@ -1318,3 +1318,8 @@ void *bt_shell_get_env(const char *name) return env->value; } + +void bt_shell_clear_history(void) +{ + rl_clear_history(); +} diff --git a/src/shared/shell.c b/src/shared/shell.c index eac654f40..86f9b71df 100644 --- a/src/shared/shell.c +++ b/src/shared/shell.c @@ -2,7 +2,7 @@ * * BlueZ - Bluetooth protocol stack for Linux * - * Copyright (C) 2017 Intel Corporation. All rights reserved. + * Copyright (C) 2017-2019 Intel Corporation. All rights reserved. * * * This library is free software; you can redistribute it and/or @@ -1328,3 +1328,8 @@ void *bt_shell_get_env(const char *name) return env->value; } + +void bt_shell_clear_history(void) +{ + rl_clear_history(); +} diff --git a/src/shared/shell.h b/src/shared/shell.h index e14d58381..9ba4a20c8 100644 --- a/src/shared/shell.h +++ b/src/shared/shell.h @@ -94,4 +94,5 @@ bool bt_shell_detach(void); void bt_shell_set_env(const char *name, void *value); void *bt_shell_get_env(const char *name); +void bt_shell_clear_history(void); void bt_shell_cleanup(void); -- 2.21.0
Hi Inga, On Wed, Aug 7, 2019 at 9:47 AM Inga Stotland <inga.stotland@intel.com> wrote: > > This adds the functionality of bt_shell that uses ell > mainloop. > --- > Makefile.am | 13 +- > src/shared/shell-ell.c | 1320 ++++++++++++++++++++++++++++++++++++++++ > 2 files changed, 1330 insertions(+), 3 deletions(-) > create mode 100644 src/shared/shell-ell.c > > diff --git a/Makefile.am b/Makefile.am > index 9d25a815b..0ce3048ad 100644 > --- a/Makefile.am > +++ b/Makefile.am > @@ -187,9 +187,6 @@ shared_sources = src/shared/io.h src/shared/timeout.h \ > src/shared/log.h src/shared/log.c \ > src/shared/tty.h > > -if READLINE > -shared_sources += src/shared/shell.c src/shared/shell.h > -endif > > src_libshared_glib_la_SOURCES = $(shared_sources) \ > src/shared/io-glib.c \ > @@ -205,11 +202,21 @@ src_libshared_mainloop_la_SOURCES = $(shared_sources) \ > src/shared/mainloop-notify.h \ > src/shared/mainloop-notify.c > > +if READLINE > +shared_sources += src/shared/shell.h > +src_libshared_glib_la_SOURCES += src/shared/shell.c > +src_libshared_mainloop_la_SOURCES += src/shared/shell.c > +endif > + > if LIBSHARED_ELL > noinst_LTLIBRARIES += src/libshared-ell.la > > src_libshared_ell_la_SOURCES = $(shared_sources) \ > src/shared/io-ell.c > + > +if READLINE > +src_libshared_ell_la_SOURCES += src/shared/shell-ell.c > +endif > endif > > attrib_sources = attrib/att.h attrib/att-database.h attrib/att.c \ > diff --git a/src/shared/shell-ell.c b/src/shared/shell-ell.c > new file mode 100644 > index 000000000..1b481e04e > --- /dev/null > +++ b/src/shared/shell-ell.c > @@ -0,0 +1,1320 @@ > +/* > + * > + * BlueZ - Bluetooth protocol stack for Linux > + * > + * Copyright (C) 2017-2019 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. > + * > + */ > + > +#ifdef HAVE_CONFIG_H > +#include <config.h> > +#endif > + > +#define _GNU_SOURCE > +#include <stdio.h> > +#include <errno.h> > +#include <syslog.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 "ell/ell.h" > + > +#include "src/shared/util.h" > +#include "src/shared/log.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 char *cmplt = "help"; > + > +struct bt_shell_prompt_input { > + char *str; > + bt_shell_prompt_input_func func; > + void *user_data; > +}; > + > +static struct { > + bool init; > + char *name; > + char history[256]; > + int argc; > + char **argv; > + bool mode; > + bool monitor; > + int timeout; > + struct l_io *input; > + > + bool saved_prompt; > + bt_shell_prompt_input_func saved_func; > + void *saved_user_data; > + > + struct l_queue *prompts; > + > + const struct bt_shell_menu *menu; > + const struct bt_shell_menu *main; > + struct l_queue *submenus; > + const struct bt_shell_menu_entry *exec; > + > + struct l_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[]) > +{ > + l_main_quit(); > +} > + > +static void print_cmds(void) > +{ > + const struct bt_shell_menu_entry *entry; > + const struct l_queue_entry *submenu; > + > + if (!data.menu) > + return; > + > + printf("Commands:\n"); > + > + for (entry = data.menu->entries; entry->cmd; entry++) { > + printf("\t%s%s\t%s\n", entry->cmd, > + strlen(entry->cmd) < 8 ? "\t" : "", entry->desc); > + } > + > + for (submenu = l_queue_get_entries(data.submenus); submenu; > + submenu = submenu->next) { > + struct bt_shell_menu *menu = submenu->data; > + > + printf("\n\t%s.:\n", menu->name); > + > + for (entry = menu->entries; entry->cmd; entry++) { > + printf("\t\t%s%s\t%s\n", entry->cmd, > + strlen(entry->cmd) < 8 ? "\t" : "", > + entry->desc); > + } > + } > +} > + > +static void cmd_help(int argc, char *argv[]) > +{ > + if (argv[0] == cmplt) > + print_cmds(); > + else > + shell_print_menu(); > + > + return bt_shell_noninteractive_quit(EXIT_SUCCESS); > +} > + > +static const struct bt_shell_menu *find_menu(const char *name, size_t len) > +{ > + const struct l_queue_entry *entry; > + > + for (entry = l_queue_get_entries(data.submenus); entry; > + entry = entry->next) { > + struct bt_shell_menu *menu = entry->data; > + > + if (!strncmp(menu->name, name, len)) > + return menu; > + } > + > + return NULL; > +} > + > +static char *menu_generator(const char *text, int state) > +{ > + static unsigned int index, len; > + static struct l_queue_entry *entry; > + > + if (!state) { > + index = 0; > + len = strlen(text); > + entry = (void *) l_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], strlen(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 || l_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 void cmd_export(int argc, char *argv[]) > +{ > + const struct l_queue_entry *entry; > + > + entry = l_queue_get_entries(data.envs); > + > + for (; entry; entry = entry->next) { > + struct bt_shell_env *env = entry->data; > + > + print_text(COLOR_HIGHLIGHT, "%s=%p", env->name, env->value); > + } > +} > + > +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" }, > + { "export", NULL, cmd_export, > + "Print evironment variables" }, > + { } > +}; > + > +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 l_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 = l_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; > + bool optargs = false; > + > + 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); > + optargs = true; > + } > + > + if (parse_args(man, &w, "<>", flags) < 0) { > + print_text(COLOR_HIGHLIGHT, > + "Unable to parse mandatory command arguments: %s", man); > + free(man); > + return -EINVAL; > + } > + > + free(man); > + > + /* Check if there are enough arguments */ > + if ((unsigned int) 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); > + free(opt); > + return -EINVAL; > + } > + > + free(opt); > + > + /* Check if there are too many arguments */ > + if (!optargs && ((unsigned int) argc - 1 > w.we_wordc && !w.we_offs)) { > + print_text(COLOR_HIGHLIGHT, "Too many arguments: %d > %zu", > + argc - 1, w.we_wordc); > + goto fail; > + } > + > + w.we_offs = 0; > + wordfree(&w); > + > +exec: > + data.exec = entry; > + > + if (entry->func) > + entry->func(argc, argv); > + > + data.exec = NULL; > + > + return 0; > + > +fail: > + w.we_offs = 0; > + 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], strlen(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); > + if (!data.saved_prompt) > + rl_save_prompt(); > + rl_replace_line("", 0); > + rl_redisplay(); > + } > + > + va_start(args, fmt); > + vprintf(fmt, args); > + va_end(args); > + > + if (data.monitor) { > + va_start(args, fmt); > + bt_log_vprintf(0xffff, data.name, LOG_INFO, fmt, args); > + va_end(args); > + } > + > + if (save_input) { > + if (!data.saved_prompt) > + 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(void) > +{ > + if (!data.exec) > + return; > + > + bt_shell_printf("Usage: %s %s\n", data.exec->cmd, > + data.exec->arg ? data.exec->arg : ""); > +} > + > +static void prompt_input(const char *str, bt_shell_prompt_input_func func, > + void *user_data) > +{ > + data.saved_prompt = true; > + data.saved_func = func; > + data.saved_user_data = user_data; > + > + rl_save_prompt(); > + bt_shell_set_prompt(str); > +} > + > +void bt_shell_prompt_input(const char *label, const char *msg, > + bt_shell_prompt_input_func func, void *user_data) > +{ > + char *str; > + > + if (!data.init || data.mode) > + return; > + > + if (data.saved_prompt) { > + struct bt_shell_prompt_input *prompt; > + > + prompt = l_new(struct bt_shell_prompt_input, 1); > + > + if (asprintf(&str, COLOR_HIGHLIGHT "[%s] %s " COLOR_OFF, label, > + msg) < 0) { > + free(prompt); > + return; > + } > + > + prompt->func = func; > + prompt->user_data = user_data; > + > + l_queue_push_tail(data.prompts, prompt); > + > + return; > + } > + > + if (asprintf(&str, COLOR_HIGHLIGHT "[%s] %s " COLOR_OFF, label, > + msg) < 0) > + return; > + > + prompt_input(str, func, user_data); > + > + free(str); > +} > + > +static void prompt_free(void *data) > +{ > + struct bt_shell_prompt_input *prompt = data; > + > + free(prompt->str); > + free(prompt); > +} > + > +int bt_shell_release_prompt(const char *input) > +{ > + struct bt_shell_prompt_input *prompt; > + 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; > + > + prompt = l_queue_pop_head(data.prompts); > + if (prompt) > + data.saved_prompt = true; > + > + data.saved_func = NULL; > + data.saved_user_data = NULL; > + > + func(input, user_data); > + > + if (prompt) { > + prompt_input(prompt->str, prompt->func, prompt->user_data); > + prompt_free(prompt); > + } > + > + return 0; > +} > + > +static void rl_handler(char *input) > +{ > + wordexp_t w; > + > + if (!input) { > + rl_insert_text("quit"); > + rl_redisplay(); > + rl_crlf(); > + l_main_quit(); > + return; > + } > + > + if (!strlen(input)) > + goto done; > + > + if (!bt_shell_release_prompt(input)) > + goto done; > + > + if (history_search(input, -1)) > + add_history(input); > + > + if (data.monitor) > + bt_log_printf(0xffff, data.name, LOG_INFO, "%s", 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, submenu_enabled; > + static const struct bt_shell_menu *menu; > + char *cmd; > + > + if (!state) { > + index = 0; > + menu = NULL; > + default_menu_enabled = true; > + submenu_enabled = false; > + } > + > + if (default_menu_enabled) { > + cmd = find_cmd(text, default_menu, &index); > + if (cmd) > + return cmd; > + > + index = 0; > + menu = data.menu; > + default_menu_enabled = false; > + } > + > + if (!submenu_enabled) { > + cmd = find_cmd(text, menu->entries, &index); > + if (cmd || menu != data.main) > + return cmd; > + > + cmd = strrchr(text, '.'); > + if (!cmd) > + return NULL; > + > + menu = find_menu(text, cmd - text); > + if (!menu) > + return NULL; > + > + index = 0; > + submenu_enabled = true; > + } > + > + cmd = find_cmd(text + strlen(menu->name) + 1, menu->entries, &index); > + if (cmd) { > + int err; > + char *tmp; > + > + err = asprintf(&tmp, "%s.%s", menu->name, cmd); > + > + free(cmd); > + > + if (err < 0) > + return NULL; > + > + cmd = tmp; > + } > + > + return cmd; > +} > + > +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 end; > + > + str = strdup(entry->arg); > + > + if (parse_args(str, &args, "<>[]", WRDE_NOCMD)) > + goto done; > + > + /* Check if argument is valid */ > + if ((unsigned int) index > args.we_wordc - 1) > + goto done; > + > + /* Check if there are multiple values */ > + if (!strrchr(entry->arg, '/')) > + goto done; > + > + free(str); > + > + /* Split values separated by / */ > + str = strdelimit(args.we_wordv[index], "/", ' '); > + > + args.we_offs = 0; > + wordfree(&args); > + > + if (wordexp(str, &args, WRDE_NOCMD)) > + goto done; > + > + rl_completion_display_matches_hook = NULL; > + matches = rl_completion_matches(text, arg_generator); > + > +done: > + free(str); > +end: > + if (!matches && text[0] == '\0') > + bt_shell_printf("Usage: %s %s\n", entry->cmd, > + entry->arg ? entry->arg : ""); > + > + args.we_offs = 0; > + 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 void io_hup(struct l_io *io, void *user_data) > +{ > + l_main_quit(); > +} > + > +static void signal_callback(unsigned int signum, void *user_data) > +{ > + static bool terminated; > + > + switch (signum) { > + case SIGINT: > + if (data.input && !data.mode) { > + rl_replace_line("", 0); > + rl_crlf(); > + rl_on_new_line(); > + rl_redisplay(); > + return; > + } > + > + /* > + * 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(); > + } > + l_main_quit(); > + } > + > + terminated = true; > + break; > + } > +} > + > +static void rl_init_history(void) > +{ > + const char *name; > + char *dir; > + > + memset(data.history, 0, sizeof(data.history)); > + > + name = strrchr(data.name, '/'); > + if (!name) > + name = data.name; > + else > + name++; > + > + dir = getenv("XDG_CACHE_HOME"); > + if (dir) { > + snprintf(data.history, sizeof(data.history), "%s/.%s_history", > + dir, name); > + goto done; > + } > + > + dir = getenv("HOME"); > + if (dir) { > + snprintf(data.history, sizeof(data.history), > + "%s/.cache/.%s_history", dir, name); > + goto done; > + } > + > + dir = getenv("PWD"); > + if (dir) { > + snprintf(data.history, sizeof(data.history), "%s/.%s_history", > + dir, name); > + goto done; > + } > + > + return; > + > +done: > + read_history(data.history); > + using_history(); > + bt_shell_set_env("HISTORY", data.history); > +} > + > +static void rl_init(void) > +{ > + if (data.mode) > + return; > + > + /* Allow conditional parsing of the ~/.inputrc file. */ > + rl_readline_name = data.name; > + > + setlinebuf(stdout); > + rl_attempted_completion_function = shell_completion; > + > + rl_erase_empty_line = 1; > + rl_callback_handler_install(NULL, rl_handler); > + > + rl_init_history(); > +} > + > +static const struct option main_options[] = { > + { "version", no_argument, 0, 'v' }, > + { "help", no_argument, 0, 'h' }, > + { "timeout", required_argument, 0, 't' }, > + { "monitor", no_argument, 0, 'm' }, > +}; > + > +static void usage(int argc, char **argv, const struct bt_shell_opt *opt) > +{ > + unsigned int i; > + > + printf("%s ver %s\n", data.name, VERSION); > + printf("Usage:\n" > + "\t%s [--options] [commands]\n", data.name); > + > + 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--monitor \tEnable monitor output\n" > + "\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), "+mhvt:%s", opt->optstr); > + } else > + snprintf(optstr, sizeof(optstr), "+mhvt:"); > + > + data.name = strrchr(argv[0], '/'); > + if (!data.name) > + data.name = strdup(argv[0]); > + else > + data.name = strdup(++data.name); > + > + while ((c = getopt_long(argc, argv, optstr, options, &index)) != -1) { > + switch (c) { > + case 'v': > + printf("%s: %s\n", data.name, VERSION); > + exit(EXIT_SUCCESS); > + return; > + case 'h': > + usage(argc, argv, opt); > + data.argc = 1; > + data.argv = &cmplt; > + data.mode = 1; > + goto done; > + case 't': > + data.timeout = atoi(optarg); > + break; > + case 'm': > + data.monitor = true; > + if (bt_log_open() < 0) { > + data.monitor = false; > + printf("Unable to open logging channel\n"); > + } > + 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; > + } > + > + index = -1; > + } > + > + bt_shell_set_env("SHELL", data.name); > + > + data.argc = argc - optind; > + data.argv = argv + optind; > + optind = 0; > + data.mode = (data.argc > 0); > + > +done: > + if (data.mode) > + bt_shell_set_env("NON_INTERACTIVE", &data.mode); > + > + l_main_init(); > + > + rl_init(); > + > + data.init = true; > + data.prompts = l_queue_new(); > +} > + > +static void rl_cleanup(void) > +{ > + if (data.mode) > + return; > + > + if (data.history[0] != '\0') > + write_history(data.history); > + > + rl_message(""); > + rl_callback_handler_remove(); > +} > + > +static void env_destroy(void *data) > +{ > + struct bt_shell_env *env = data; > + > + free(env->name); > + free(env); > +} > + > +int bt_shell_run(void) > +{ > + int status; > + > + status = l_main_run_with_signal(signal_callback, NULL); > + > + bt_shell_cleanup(); > + > + l_main_exit(); > + > + return status; > +} > + > +void bt_shell_cleanup(void) > +{ > + bt_shell_release_prompt(""); > + bt_shell_detach(); > + > + if (data.envs) { > + l_queue_destroy(data.envs, env_destroy); > + data.envs = NULL; > + } > + > + if (data.monitor) > + bt_log_close(); > + > + rl_cleanup(); > + > + l_queue_destroy(data.prompts, prompt_free); > + data.prompts = NULL; > + > + data.init = false; > + free(data.name); > +} > + > +void bt_shell_quit(int status) > +{ > + l_main_quit(); > +} > + > +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 = l_queue_new(); > + > + l_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); > + rl_redisplay(); > +} > + > +static bool input_read(struct l_io *io, void *user_data) > +{ > + rl_callback_read_char(); > + return true; > +} > + > +static void quit_on_timeout(struct l_timeout *timeout, void *user_data) > +{ > + l_main_quit(); > +} > + > +bool bt_shell_attach(int fd) > +{ > + struct l_io *io; > + > + /* TODO: Allow more than one input? */ > + if (data.input) > + return false; > + > + io = l_io_new(fd); > + > + if (!data.mode) > + l_io_set_read_handler(io, input_read, NULL, NULL); > + > + l_io_set_disconnect_handler(io, io_hup, NULL, NULL); There is an io abstraction to implement this, afaik most of the things here are just copy+paste of shell.c just with a different mainloop and IO while the io.h and mainloop.h are exactly to abstract these so I wonder why you took this alternative. > + 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) > + l_timeout_create(data.timeout, quit_on_timeout, NULL, > + NULL); > + } > + > + return true; > +} > + > +bool bt_shell_detach(void) > +{ > + if (!data.input) > + return false; > + > + l_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) { > + if (!value) > + return; > + data.envs = l_queue_new(); > + goto done; > + } > + > + env = l_queue_remove_if(data.envs, match_env, (void *) name); > + if (env) > + env_destroy(env); > + > + /* Don't create an env if value is not set */ > + if (!value) > + return; > + > +done: > + env = l_new(struct bt_shell_env, 1); > + env->name = strdup(name); > + env->value = value; > + > + l_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 = l_queue_find(data.envs, match_env, name); > + if (!env) > + return NULL; > + > + return env->value; > +} > -- > 2.21.0 > -- Luiz Augusto von Dentz
[-- Attachment #1: Type: text/plain, Size: 1191 bytes --] Hi Luiz, On Wed, 2019-08-07 at 14:18 +0300, Luiz Augusto von Dentz wrote: > Hi Inga, > > On Wed, Aug 7, 2019 at 9:47 AM Inga Stotland <inga.stotland@intel.com > > wrote: > > This adds the functionality of bt_shell that uses ell > > mainloop. > > " > > + > > +static void quit_on_timeout(struct l_timeout *timeout, void > > *user_data) > > +{ > > + l_main_quit(); > > +} > > + > > +bool bt_shell_attach(int fd) > > +{ > > + struct l_io *io; > > + > > + /* TODO: Allow more than one input? */ > > + if (data.input) > > + return false; > > + > > + io = l_io_new(fd); > > + > > + if (!data.mode) > > + l_io_set_read_handler(io, input_read, NULL, NULL); > > + > > + l_io_set_disconnect_handler(io, io_hup, NULL, NULL); > > There is an io abstraction to implement this, afaik most of the > things > here are just copy+paste of shell.c just with a different mainloop > and > IO while the io.h and mainloop.h are exactly to abstract these so I > wonder why you took this alternative. > True. Much easier to add ell-based mainloop.c, same effect and lightweight. Regards, Inga [-- Attachment #2: smime.p7s --] [-- Type: application/x-pkcs7-signature, Size: 3265 bytes --]