Re: [PATCH BlueZ 1/2] shared/shell: Add initial implementation

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

 



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



[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