From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Content-Type: text/plain; charset=us-ascii Mime-Version: 1.0 (Mac OS X Mail 11.1 \(3445.4.7\)) Subject: Re: [PATCH BlueZ 1/2] shared/shell: Add initial implementation From: Marcel Holtmann In-Reply-To: <20171109152940.14546-1-luiz.dentz@gmail.com> Date: Thu, 9 Nov 2017 16:37:49 +0100 Cc: linux-bluetooth@vger.kernel.org Message-Id: <86C44C21-CFF8-4D5D-B46A-053EBFB4B913@holtmann.org> References: <20171109152940.14546-1-luiz.dentz@gmail.com> To: Luiz Augusto von Dentz Sender: linux-bluetooth-owner@vger.kernel.org List-ID: Hi Luiz, > This add initial bt_shell helper which can be used to create shell-like > command line tools. > --- > Makefile.tools | 3 +- > src/shared/shell.c | 567 +++++++++++++++++++++++++++++++++++++++++++++++++++++ > src/shared/shell.h | 67 +++++++ > 3 files changed, 636 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..2cd1ea3fb > --- /dev/null > +++ b/src/shared/shell.c > @@ -0,0 +1,567 @@ > +/* > + * > + * 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 > +#endif > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include > +#include > +#include > + > +#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 { > + unsigned int input; > + > + bool saved_prompt; > + bt_shell_prompt_input_func saved_func; > + void *saved_user_data; > + > + const struct bt_shell_menu_entry *current; > + /* 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 cmd_table[] = { > + { "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.current) > + return; > + > + print_text(COLOR_HIGHLIGHT, "Available commands:"); > + print_text(COLOR_HIGHLIGHT, "-------------------"); > + for (entry = data.current; entry->cmd; entry++) { > + print_menu(entry->cmd, entry->arg ? : "", entry->desc ? : ""); > + } > + > + for (entry = cmd_table; 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.current || !cmd) > + return; > + > + for (entry = data.current; entry->cmd; entry++) { > + if (strcmp(cmd, entry->cmd)) > + continue; > + > + if (entry->func) { > + entry->func(arg); > + return; > + } > + } > + > + for (entry = cmd_table; 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) && !data.saved_prompt; > + > + if (save_input) { > + saved_point = rl_point; > + saved_line = rl_copy_text(0, rl_end); > + rl_save_prompt(); > + rl_replace_line("", 0); > + rl_redisplay(); > + } > + > + va_start(args, fmt); > + vprintf(fmt, args); > + va_end(args); > + > + if (save_input) { > + rl_restore_prompt(); > + rl_replace_line(saved_line, 0); > + rl_point = saved_point; > + rl_forced_update_display(); > + free(saved_line); > + } > +} > + > +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_clear_message(); > + 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) > +{ > + const struct bt_shell_menu_entry *entry; > + static int index, len; > + const char *cmd; > + > + entry = data.current; > + > + if (!state) { > + index = 0; > + len = strlen(text); > + } > + > + while ((cmd = entry[index].cmd)) { > + index++; > + > + if (!strncmp(cmd, text, len)) > + return strdup(cmd); > + } > + > + return NULL; > +} > + > +static char **shell_completion(const char *text, int start, int end) > +{ > + char **matches = NULL; > + > + if (!data.current) > + return NULL; > + > + if (start > 0) { > + const struct bt_shell_menu_entry *entry; > + char *input_cmd; > + > + input_cmd = strndup(rl_line_buffer, start - 1); > + for (entry = data.current; 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; > + } > + > + 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 gboolean signal_handler(GIOChannel *channel, GIOCondition condition, > + gpointer user_data) > +{ > + static bool terminated = false; > + struct signalfd_siginfo si; > + ssize_t result; > + int fd; > + > + if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { > + g_main_loop_quit(main_loop); > + return FALSE; > + } > + > + fd = g_io_channel_unix_get_fd(channel); > + > + 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 TRUE; > +} > + > +static guint setup_signalfd(void) > +{ > + GIOChannel *channel; > + guint source; > + 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; > + } > + > + channel = g_io_channel_unix_new(fd); > + > + g_io_channel_set_close_on_unref(channel, TRUE); > + g_io_channel_set_encoding(channel, NULL, NULL); > + g_io_channel_set_buffered(channel, FALSE); any reason you are not using struct io here? You can still link it with Glib, but it would be easier to convert to our internal mainloop. Regards Marcel