Hi Marcel, On Thu, Nov 9, 2017 at 5:37 PM, Marcel Holtmann <marcel@xxxxxxxxxxxx> wrote: > 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 <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/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. I endup forget about converting it since in the end I moved more and more stuff internally, anyway it should be no problem to convert to our internal mainloop and io. As for the concept I guess you are fine and I can convert all other readline tools as well? > Regards > > Marcel > -- 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