Re: [PATCH v3 1/6] shared/shell: Add initial implementation

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi,

On Thu, Nov 16, 2017 at 12:59 PM, Luiz Augusto von Dentz
<luiz.dentz@xxxxxxxxx> wrote:
> From: Luiz Augusto von Dentz <luiz.von.dentz@xxxxxxxxx>
>
> This add initial bt_shell helper which can be used to create shell-like
> command line tools.
> ---
> v3: Add submenu changes
>
>  Makefile.tools     |   3 +-
>  src/shared/shell.c | 570 +++++++++++++++++++++++++++++++++++++++++++++++++++++
>  src/shared/shell.h |  67 +++++++
>  3 files changed, 639 insertions(+), 1 deletion(-)
>  create mode 100644 src/shared/shell.c
>  create mode 100644 src/shared/shell.h
>
> diff --git a/Makefile.tools b/Makefile.tools
> index 561302fa1..dc2902cb7 100644
> --- a/Makefile.tools
> +++ b/Makefile.tools
> @@ -8,7 +8,8 @@ client_bluetoothctl_SOURCES = client/main.c \
>                                         client/advertising.h \
>                                         client/advertising.c \
>                                         client/gatt.h client/gatt.c \
> -                                       monitor/uuid.h monitor/uuid.c
> +                                       monitor/uuid.h monitor/uuid.c \
> +                                       src/shared/shell.h src/shared/shell.c
>  client_bluetoothctl_LDADD = gdbus/libgdbus-internal.la src/libshared-glib.la \
>                                 @GLIB_LIBS@ @DBUS_LIBS@ -lreadline
>  endif
> diff --git a/src/shared/shell.c b/src/shared/shell.c
> new file mode 100644
> index 000000000..7db629bf1
> --- /dev/null
> +++ b/src/shared/shell.c
> @@ -0,0 +1,570 @@
> +/*
> + *
> + *  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 <stdbool.h>
> +#include <signal.h>
> +#include <sys/signalfd.h>
> +
> +#include <readline/readline.h>
> +#include <readline/history.h>
> +#include <glib.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)
> +
> +static GMainLoop *main_loop;
> +static gboolean option_version = FALSE;
> +
> +static struct {
> +       struct io *input;
> +
> +       bool saved_prompt;
> +       bt_shell_prompt_input_func saved_func;
> +       void *saved_user_data;
> +
> +       const struct bt_shell_menu_entry *menu;
> +       /* TODO: Add submenus support */
> +} data;
> +
> +static void shell_print_menu(void);
> +
> +static void cmd_version(const char *arg)
> +{
> +       bt_shell_printf("Version %s\n", VERSION);
> +}
> +
> +static void cmd_quit(const char *arg)
> +{
> +       g_main_loop_quit(main_loop);
> +}
> +
> +static void cmd_help(const char *arg)
> +{
> +       shell_print_menu();
> +}
> +
> +static const struct bt_shell_menu_entry default_menu[] = {
> +       { "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_menu(void)
> +{
> +       const struct bt_shell_menu_entry *entry;
> +
> +       if (!data.menu)
> +               return;
> +
> +       print_text(COLOR_HIGHLIGHT, "Available commands:");
> +       print_text(COLOR_HIGHLIGHT, "-------------------");
> +       for (entry = data.menu; entry->cmd; entry++) {
> +               print_menu(entry->cmd, entry->arg ? : "", entry->desc ? : "");
> +       }
> +
> +       for (entry = default_menu; entry->cmd; entry++) {
> +               print_menu(entry->cmd, entry->arg ? : "", entry->desc ? : "");
> +       }
> +}
> +
> +static void shell_exec(const char *cmd, const char *arg)
> +{
> +       const struct bt_shell_menu_entry *entry;
> +
> +       if (!data.menu || !cmd)
> +               return;
> +
> +       for (entry = data.menu; entry->cmd; entry++) {
> +               if (strcmp(cmd, entry->cmd))
> +                       continue;
> +
> +               if (entry->func) {
> +                       entry->func(arg);
> +                       return;
> +               }
> +       }
> +
> +       for (entry = default_menu; entry->cmd; entry++) {
> +               if (strcmp(cmd, entry->cmd))
> +                       continue;
> +
> +               if (entry->func) {
> +                       entry->func(arg);
> +                       return;
> +               }
> +       }
> +
> +       print_text(COLOR_HIGHLIGHT, "Invalid command");
> +}
> +
> +void bt_shell_printf(const char *fmt, ...)
> +{
> +       va_list args;
> +       bool save_input;
> +       char *saved_line;
> +       int saved_point;
> +
> +       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 (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);
> +       }
> +}
> +
> +void bt_shell_hexdump(const unsigned char *buf, size_t len)
> +{
> +       static const char hexdigits[] = "0123456789abcdef";
> +       char str[68];
> +       size_t i;
> +
> +       if (!len)
> +               return;
> +
> +       str[0] = ' ';
> +
> +       for (i = 0; i < len; i++) {
> +               str[((i % 16) * 3) + 1] = ' ';
> +               str[((i % 16) * 3) + 2] = hexdigits[buf[i] >> 4];
> +               str[((i % 16) * 3) + 3] = hexdigits[buf[i] & 0xf];
> +               str[(i % 16) + 51] = isprint(buf[i]) ? buf[i] : '.';
> +
> +               if ((i + 1) % 16 == 0) {
> +                       str[49] = ' ';
> +                       str[50] = ' ';
> +                       str[67] = '\0';
> +                       bt_shell_printf("%s\n", str);
> +                       str[0] = ' ';
> +               }
> +       }
> +
> +       if (i % 16 > 0) {
> +               size_t j;
> +               for (j = (i % 16); j < 16; j++) {
> +                       str[(j * 3) + 1] = ' ';
> +                       str[(j * 3) + 2] = ' ';
> +                       str[(j * 3) + 3] = ' ';
> +                       str[j + 51] = ' ';
> +               }
> +               str[49] = ' ';
> +               str[50] = ' ';
> +               str[67] = '\0';
> +               bt_shell_printf("%s\n", str);
> +       }
> +}
> +
> +void bt_shell_prompt_input(const char *label, const char *msg,
> +                       bt_shell_prompt_input_func func, void *user_data)
> +{
> +       /* 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)
> +{
> +       char *cmd, *arg;
> +
> +       if (!input) {
> +               rl_insert_text("quit");
> +               rl_redisplay();
> +               rl_crlf();
> +               g_main_loop_quit(main_loop);
> +               return;
> +       }
> +
> +       if (!strlen(input))
> +               goto done;
> +
> +       if (!bt_shell_release_prompt(input))
> +               goto done;
> +
> +       if (history_search(input, -1))
> +               add_history(input);
> +
> +       cmd = strtok_r(input, " ", &arg);
> +       if (!cmd)
> +               goto done;
> +
> +       if (arg) {
> +               int len = strlen(arg);
> +               if (len > 0 && arg[len - 1] == ' ')
> +                       arg[len - 1] = '\0';
> +       }
> +
> +       shell_exec(cmd, arg);
> +done:
> +       free(input);
> +}
> +
> +static char *cmd_generator(const char *text, int state)
> +{
> +       static const struct bt_shell_menu_entry *entry;
> +       static int index, len;
> +       const char *cmd;
> +
> +       if (!state) {
> +               entry = default_menu;
> +               index = 0;
> +               len = strlen(text);
> +       }
> +
> +       while ((cmd = entry[index].cmd)) {
> +               index++;
> +
> +               if (!strncmp(cmd, text, len))
> +                       return strdup(cmd);
> +       }
> +
> +       if (state)
> +               return NULL;
> +
> +       entry = data.menu;
> +       index = 0;
> +
> +       return cmd_generator(text, 1);
> +}
> +
> +static char **menu_completion(const struct bt_shell_menu_entry *entry,
> +                               const char *text, char *input_cmd)
> +{
> +       char **matches = NULL;
> +
> +       for (entry = data.menu; entry->cmd; entry++) {
> +               if (strcmp(entry->cmd, input_cmd))
> +                       continue;
> +
> +               if (!entry->gen)
> +                       continue;
> +
> +               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) {
> +               char *input_cmd;
> +
> +               input_cmd = strndup(rl_line_buffer, start - 1);
> +               matches = menu_completion(default_menu, text, input_cmd);
> +               if (!matches)
> +                       matches = menu_completion(data.menu, text,
> +                                                       input_cmd);
> +
> +               free(input_cmd);
> +       } 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)
> +{
> +       g_main_loop_quit(main_loop);
> +
> +       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) {
> +                       rl_replace_line("", 0);
> +                       rl_crlf();
> +                       rl_on_new_line();
> +                       rl_redisplay();
> +                       break;
> +               }
> +
> +               /*
> +                * 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) {
> +                       rl_replace_line("", 0);
> +                       rl_crlf();
> +                       g_main_loop_quit(main_loop);
> +               }
> +
> +               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 GOptionEntry options[] = {
> +       { "version", 'v', 0, G_OPTION_ARG_NONE, &option_version,
> +                               "Show version information and exit" },
> +       { NULL },
> +};
> +
> +static void rl_init(void)
> +{
> +       setlinebuf(stdout);
> +       rl_attempted_completion_function = shell_completion;
> +
> +       rl_erase_empty_line = 1;
> +       rl_callback_handler_install(NULL, rl_handler);
> +}
> +
> +void bt_shell_init(int *argc, char ***argv)
> +{
> +       GOptionContext *context;
> +       GError *error = NULL;
> +
> +       context = g_option_context_new(NULL);
> +       g_option_context_add_main_entries(context, options, NULL);
> +
> +       if (g_option_context_parse(context, argc, argv, &error) == FALSE) {
> +               if (error != NULL) {
> +                       g_printerr("%s\n", error->message);
> +                       g_error_free(error);
> +               } else
> +                       g_printerr("An unknown error occurred\n");
> +               exit(1);
> +       }
> +
> +       g_option_context_free(context);
> +
> +       if (option_version == TRUE) {
> +               g_print("%s\n", VERSION);
> +               exit(EXIT_SUCCESS);
> +       }
> +
> +       main_loop = g_main_loop_new(NULL, FALSE);
> +
> +       rl_init();
> +}
> +
> +static void rl_cleanup(void)
> +{
> +       rl_message("");
> +       rl_callback_handler_remove();
> +}
> +
> +void bt_shell_run(void)
> +{
> +       struct io *signal;
> +
> +       signal = setup_signalfd();
> +
> +       g_main_loop_run(main_loop);
> +
> +       bt_shell_release_prompt("");
> +       bt_shell_detach();
> +
> +       io_destroy(signal);
> +
> +       g_main_loop_unref(main_loop);
> +       main_loop = NULL;
> +
> +       rl_cleanup();
> +}
> +
> +bool bt_shell_set_menu(const struct bt_shell_menu_entry *menu)
> +{
> +       if (data.menu || !menu)
> +               return false;
> +
> +       data.menu = menu;
> +
> +       return true;
> +}
> +
> +void bt_shell_set_prompt(const char *string)
> +{
> +       if (!main_loop)
> +               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;
> +}
> +
> +bool bt_shell_attach(int fd)
> +{
> +       struct io *io;
> +
> +       /* TODO: Allow more than one input? */
> +       if (data.input)
> +               return false;
> +
> +       io = io_new(fd);
> +
> +       io_set_read_handler(io, input_read, NULL, NULL);
> +       io_set_disconnect_handler(io, io_hup, NULL, NULL);
> +
> +       data.input = io;
> +
> +       return true;
> +}
> +
> +bool bt_shell_detach(void)
> +{
> +       if (!data.input)
> +               return false;
> +
> +       io_destroy(data.input);
> +       data.input = NULL;
> +
> +       return true;
> +}
> diff --git a/src/shared/shell.h b/src/shared/shell.h
> new file mode 100644
> index 000000000..843335784
> --- /dev/null
> +++ b/src/shared/shell.h
> @@ -0,0 +1,67 @@
> +/*
> + *
> + *  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
> + *
> + */
> +
> +#define COLOR_OFF      "\x1B[0m"
> +#define COLOR_RED      "\x1B[0;91m"
> +#define COLOR_GREEN    "\x1B[0;92m"
> +#define COLOR_YELLOW   "\x1B[0;93m"
> +#define COLOR_BLUE     "\x1B[0;94m"
> +#define COLOR_BOLDGRAY "\x1B[1;30m"
> +#define COLOR_BOLDWHITE        "\x1B[1;37m"
> +#define COLOR_HIGHLIGHT        "\x1B[1;39m"
> +
> +typedef void (*bt_shell_menu_cb_t)(const char *arg);
> +typedef char * (*bt_shell_menu_gen_t)(const char *text, int state);
> +typedef void (*bt_shell_menu_disp_t) (char **matches, int num_matches,
> +                                                       int max_length);
> +typedef void (*bt_shell_prompt_input_func) (const char *input, void *user_data);
> +
> +struct bt_shell_menu_entry {
> +       const char *cmd;
> +       const char *arg;
> +       bt_shell_menu_cb_t func;
> +       const char *desc;
> +       bt_shell_menu_gen_t gen;
> +       bt_shell_menu_disp_t disp;
> +};
> +
> +void bt_shell_init(int *argc, char ***argv);
> +
> +void bt_shell_run(void);
> +
> +bool bt_shell_set_menu(const struct bt_shell_menu_entry *menu);
> +
> +void bt_shell_set_prompt(const char *string);
> +
> +void bt_shell_printf(const char *fmt,
> +                               ...) __attribute__((format(printf, 1, 2)));
> +void bt_shell_hexdump(const unsigned char *buf, size_t len);
> +
> +void bt_shell_prompt_input(const char *label, const char *msg,
> +                       bt_shell_prompt_input_func func, void *user_data);
> +int bt_shell_release_prompt(const char *input);
> +
> +bool bt_shell_attach(int fd);
> +bool bt_shell_detach(void);
> +
> +void bt_shell_cleanup(void);
> --
> 2.13.6

Applied.


-- 
Luiz Augusto von Dentz
--
To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Bluez Devel]     [Linux Wireless Networking]     [Linux Wireless Personal Area Networking]     [Linux ATH6KL]     [Linux USB Devel]     [Linux Media Drivers]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Big List of Linux Books]

  Powered by Linux