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

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

 



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.

Regards

Marcel

--
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