From: Luiz Augusto von Dentz <luiz.dentz@gmail.com>
To: "linux-bluetooth@vger.kernel.org" <linux-bluetooth@vger.kernel.org>
Subject: Re: [PATCH v3 1/6] shared/shell: Add initial implementation
Date: Fri, 17 Nov 2017 16:12:08 +0200 [thread overview]
Message-ID: <CABBYNZ+8jrvmsG5AKe6Gw0ovPicCH7xxrDYc07ULCyJgNQcfBQ@mail.gmail.com> (raw)
In-Reply-To: <20171116105925.2117-1-luiz.dentz@gmail.com>
Hi,
On Thu, Nov 16, 2017 at 12:59 PM, Luiz Augusto von Dentz
<luiz.dentz@gmail.com> wrote:
> From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
>
> 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
prev parent reply other threads:[~2017-11-17 14:12 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2017-11-16 10:59 [PATCH v3 1/6] shared/shell: Add initial implementation Luiz Augusto von Dentz
2017-11-16 10:59 ` [PATCH v3 2/6] client: Make use of bt_shell Luiz Augusto von Dentz
2017-11-16 10:59 ` [PATCH v3 3/6] shared/shell: Add submenu support Luiz Augusto von Dentz
2017-11-16 10:59 ` [PATCH v3 4/6] client: Move advertise related commands to a submenu Luiz Augusto von Dentz
2017-11-16 10:59 ` [PATCH v3 5/6] client: Move scan " Luiz Augusto von Dentz
2017-11-16 10:59 ` [PATCH v3 6/6] client: Move gatt " Luiz Augusto von Dentz
2017-11-17 14:12 ` Luiz Augusto von Dentz [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=CABBYNZ+8jrvmsG5AKe6Gw0ovPicCH7xxrDYc07ULCyJgNQcfBQ@mail.gmail.com \
--to=luiz.dentz@gmail.com \
--cc=linux-bluetooth@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).