Linux-Bluetooth Archive on lore.kernel.org
 help / color / Atom feed
* [PATCH BlueZ 0/2] ELL-based bt_shell implementation
@ 2019-08-07  6:47 Inga Stotland
  2019-08-07  6:47 ` [PATCH BlueZ 1/2] shared/shell: Add ell based shell implementation Inga Stotland
  2019-08-07  6:47 ` [PATCH BlueZ 2/2] shared/shell: Add "clear-history" API Inga Stotland
  0 siblings, 2 replies; 5+ messages in thread
From: Inga Stotland @ 2019-08-07  6:47 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: brian.gix, luiz.dentz, Inga Stotland

This patch set addresses the need of using bt-shell functionality
when running with ell mainloop.
Specifically, need this for implementation of mesh profisioning tool:
mesh daemon is implemented using ell library and is driven by
ell main loop.

Inga Stotland (2):
  shared/shell: Add ell based shell implementation
  shared/shell: Add "clear-history" API

 Makefile.am            |   13 +-
 src/shared/shell-ell.c | 1325 ++++++++++++++++++++++++++++++++++++++++
 src/shared/shell.c     |    7 +-
 src/shared/shell.h     |    1 +
 4 files changed, 1342 insertions(+), 4 deletions(-)
 create mode 100644 src/shared/shell-ell.c

-- 
2.21.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH BlueZ 1/2] shared/shell: Add ell based shell implementation
  2019-08-07  6:47 [PATCH BlueZ 0/2] ELL-based bt_shell implementation Inga Stotland
@ 2019-08-07  6:47 ` Inga Stotland
  2019-08-07 11:18   ` Luiz Augusto von Dentz
  2019-08-07  6:47 ` [PATCH BlueZ 2/2] shared/shell: Add "clear-history" API Inga Stotland
  1 sibling, 1 reply; 5+ messages in thread
From: Inga Stotland @ 2019-08-07  6:47 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: brian.gix, luiz.dentz, Inga Stotland

This adds the functionality of bt_shell that uses ell
mainloop.
---
 Makefile.am            |   13 +-
 src/shared/shell-ell.c | 1320 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 1330 insertions(+), 3 deletions(-)
 create mode 100644 src/shared/shell-ell.c

diff --git a/Makefile.am b/Makefile.am
index 9d25a815b..0ce3048ad 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -187,9 +187,6 @@ shared_sources = src/shared/io.h src/shared/timeout.h \
 			src/shared/log.h src/shared/log.c \
 			src/shared/tty.h
 
-if READLINE
-shared_sources += src/shared/shell.c src/shared/shell.h
-endif
 
 src_libshared_glib_la_SOURCES = $(shared_sources) \
 				src/shared/io-glib.c \
@@ -205,11 +202,21 @@ src_libshared_mainloop_la_SOURCES = $(shared_sources) \
 				src/shared/mainloop-notify.h \
 				src/shared/mainloop-notify.c
 
+if READLINE
+shared_sources += src/shared/shell.h
+src_libshared_glib_la_SOURCES += src/shared/shell.c
+src_libshared_mainloop_la_SOURCES += src/shared/shell.c
+endif
+
 if LIBSHARED_ELL
 noinst_LTLIBRARIES += src/libshared-ell.la
 
 src_libshared_ell_la_SOURCES = $(shared_sources) \
 				src/shared/io-ell.c
+
+if READLINE
+src_libshared_ell_la_SOURCES += src/shared/shell-ell.c
+endif
 endif
 
 attrib_sources = attrib/att.h attrib/att-database.h attrib/att.c \
diff --git a/src/shared/shell-ell.c b/src/shared/shell-ell.c
new file mode 100644
index 000000000..1b481e04e
--- /dev/null
+++ b/src/shared/shell-ell.c
@@ -0,0 +1,1320 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2017-2019  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.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <errno.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <signal.h>
+#include <sys/signalfd.h>
+#include <wordexp.h>
+#include <getopt.h>
+
+#include <readline/readline.h>
+#include <readline/history.h>
+
+#include "ell/ell.h"
+
+#include "src/shared/util.h"
+#include "src/shared/log.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)
+#define print_submenu(cmd, desc) \
+		printf(COLOR_BLUE "%s %-*s " COLOR_OFF "%s\n", \
+			cmd, (int)(CMD_LENGTH - strlen(cmd)), "", desc)
+
+struct bt_shell_env {
+	char *name;
+	void *value;
+};
+
+static char *cmplt = "help";
+
+struct bt_shell_prompt_input {
+	char *str;
+	bt_shell_prompt_input_func func;
+	void *user_data;
+};
+
+static struct {
+	bool init;
+	char *name;
+	char history[256];
+	int argc;
+	char **argv;
+	bool mode;
+	bool monitor;
+	int timeout;
+	struct l_io *input;
+
+	bool saved_prompt;
+	bt_shell_prompt_input_func saved_func;
+	void *saved_user_data;
+
+	struct l_queue *prompts;
+
+	const struct bt_shell_menu *menu;
+	const struct bt_shell_menu *main;
+	struct l_queue *submenus;
+	const struct bt_shell_menu_entry *exec;
+
+	struct l_queue *envs;
+} data;
+
+static void shell_print_menu(void);
+
+static void cmd_version(int argc, char *argv[])
+{
+	bt_shell_printf("Version %s\n", VERSION);
+
+	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static void cmd_quit(int argc, char *argv[])
+{
+	l_main_quit();
+}
+
+static void print_cmds(void)
+{
+	const struct bt_shell_menu_entry *entry;
+	const struct l_queue_entry *submenu;
+
+	if (!data.menu)
+		return;
+
+	printf("Commands:\n");
+
+	for (entry = data.menu->entries; entry->cmd; entry++) {
+		printf("\t%s%s\t%s\n", entry->cmd,
+			strlen(entry->cmd) < 8 ? "\t" : "", entry->desc);
+	}
+
+	for (submenu = l_queue_get_entries(data.submenus); submenu;
+					submenu = submenu->next) {
+		struct bt_shell_menu *menu = submenu->data;
+
+		printf("\n\t%s.:\n", menu->name);
+
+		for (entry = menu->entries; entry->cmd; entry++) {
+			printf("\t\t%s%s\t%s\n", entry->cmd,
+				strlen(entry->cmd) < 8 ? "\t" : "",
+				entry->desc);
+		}
+	}
+}
+
+static void cmd_help(int argc, char *argv[])
+{
+	if (argv[0] == cmplt)
+		print_cmds();
+	else
+		shell_print_menu();
+
+	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static const struct bt_shell_menu *find_menu(const char *name, size_t len)
+{
+	const struct l_queue_entry *entry;
+
+	for (entry = l_queue_get_entries(data.submenus); entry;
+						entry = entry->next) {
+		struct bt_shell_menu *menu = entry->data;
+
+		if (!strncmp(menu->name, name, len))
+			return menu;
+	}
+
+	return NULL;
+}
+
+static char *menu_generator(const char *text, int state)
+{
+	static unsigned int index, len;
+	static struct l_queue_entry *entry;
+
+	if (!state) {
+		index = 0;
+		len = strlen(text);
+		entry = (void *) l_queue_get_entries(data.submenus);
+	}
+
+	for (; entry; entry = entry->next) {
+		struct bt_shell_menu *menu = entry->data;
+
+		index++;
+
+		if (!strncmp(menu->name, text, len)) {
+			entry = entry->next;
+			return strdup(menu->name);
+		}
+	}
+
+	return NULL;
+}
+
+static void cmd_menu(int argc, char *argv[])
+{
+	const struct bt_shell_menu *menu;
+
+	if (argc < 2 || !strlen(argv[1])) {
+		bt_shell_printf("Missing name argument\n");
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	menu = find_menu(argv[1], strlen(argv[1]));
+	if (!menu) {
+		bt_shell_printf("Unable find menu with name: %s\n", argv[1]);
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+	}
+
+	bt_shell_set_menu(menu);
+
+	shell_print_menu();
+
+	return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static bool cmd_menu_exists(const struct bt_shell_menu *menu)
+{
+	/* Skip menu command if not on main menu or if there are no
+	 * submenus.
+	 */
+	if (menu != data.main || l_queue_isempty(data.submenus))
+		return false;
+
+	return true;
+}
+
+static void cmd_back(int argc, char *argv[])
+{
+	if (data.menu == data.main) {
+		bt_shell_printf("Already on main menu\n");
+		return;
+	}
+
+	bt_shell_set_menu(data.main);
+
+	shell_print_menu();
+}
+
+static bool cmd_back_exists(const struct bt_shell_menu *menu)
+{
+	/* Skip back command if on main menu */
+	if (menu == data.main)
+		return false;
+
+	return true;
+}
+
+static void cmd_export(int argc, char *argv[])
+{
+	const struct l_queue_entry *entry;
+
+	entry = l_queue_get_entries(data.envs);
+
+	for (; entry; entry = entry->next) {
+		struct bt_shell_env *env = entry->data;
+
+		print_text(COLOR_HIGHLIGHT, "%s=%p", env->name, env->value);
+	}
+}
+
+static const struct bt_shell_menu_entry default_menu[] = {
+	{ "back",         NULL,       cmd_back, "Return to main menu", NULL,
+							NULL, cmd_back_exists },
+	{ "menu",         "<name>",   cmd_menu, "Select submenu",
+							menu_generator, NULL,
+							cmd_menu_exists},
+	{ "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" },
+	{ "export",       NULL,       cmd_export,
+						"Print evironment variables" },
+	{ }
+};
+
+static void shell_print_help(void)
+{
+	print_text(COLOR_HIGHLIGHT,
+		"\n"
+		"Use \"help\" for a list of available commands in a menu.\n"
+		"Use \"menu <submenu>\" if you want to enter any submenu.\n"
+		"Use \"back\" if you want to return to menu main.");
+}
+
+static void shell_print_menu(void)
+{
+	const struct bt_shell_menu_entry *entry;
+	const struct l_queue_entry *submenu;
+
+	if (!data.menu)
+		return;
+
+	print_text(COLOR_HIGHLIGHT, "Menu %s:", data.menu->name);
+	print_text(COLOR_HIGHLIGHT, "Available commands:");
+	print_text(COLOR_HIGHLIGHT, "-------------------");
+
+	if (data.menu == data.main) {
+		for (submenu = l_queue_get_entries(data.submenus); submenu;
+						submenu = submenu->next) {
+			struct bt_shell_menu *menu = submenu->data;
+
+			print_submenu(menu->name, menu->desc ? menu->desc :
+								"Submenu");
+		}
+	}
+
+	for (entry = data.menu->entries; entry->cmd; entry++)
+		print_menu(entry->cmd, entry->arg ? : "", entry->desc ? : "");
+
+	for (entry = default_menu; entry->cmd; entry++) {
+		if (entry->exists && !entry->exists(data.menu))
+			continue;
+
+		print_menu(entry->cmd, entry->arg ? : "", entry->desc ? : "");
+	}
+}
+
+static int parse_args(char *arg, wordexp_t *w, char *del, int flags)
+{
+	char *str;
+
+	str = strdelimit(arg, del, '"');
+
+	if (wordexp(str, w, flags)) {
+		free(str);
+		return -EINVAL;
+	}
+
+	/* If argument ends with ... set we_offs bypass strict checks */
+	if (w->we_wordc && !strsuffix(w->we_wordv[w->we_wordc - 1], "..."))
+		w->we_offs = 1;
+
+	free(str);
+
+	return 0;
+}
+
+static int cmd_exec(const struct bt_shell_menu_entry *entry,
+					int argc, char *argv[])
+{
+	wordexp_t w;
+	size_t len;
+	char *man, *opt;
+	int flags = WRDE_NOCMD;
+	bool optargs = false;
+
+	if (!entry->arg || entry->arg[0] == '\0') {
+		if (argc > 1) {
+			print_text(COLOR_HIGHLIGHT, "Too many arguments");
+			return -EINVAL;
+		}
+		goto exec;
+	}
+
+	/* Find last mandatory arguments */
+	man = strrchr(entry->arg, '>');
+	if (!man) {
+		opt = strdup(entry->arg);
+		goto optional;
+	}
+
+	len = man - entry->arg;
+	if (entry->arg[0] == '<')
+		man = strndup(entry->arg, len + 1);
+	else {
+		/* Find where mandatory arguments start */
+		opt = strrchr(entry->arg, '<');
+		/* Skip if mandatory arguments are not in the right format */
+		if (!opt || opt > man) {
+			opt = strdup(entry->arg);
+			goto optional;
+		}
+		man = strndup(opt, man - opt + 1);
+		optargs = true;
+	}
+
+	if (parse_args(man, &w, "<>", flags) < 0) {
+		print_text(COLOR_HIGHLIGHT,
+			"Unable to parse mandatory command arguments: %s", man);
+		free(man);
+		return -EINVAL;
+	}
+
+	free(man);
+
+	/* Check if there are enough arguments */
+	if ((unsigned int) argc - 1 < w.we_wordc) {
+		print_text(COLOR_HIGHLIGHT, "Missing %s argument",
+						w.we_wordv[argc - 1]);
+		goto fail;
+	}
+
+	flags |= WRDE_APPEND;
+	opt = strdup(entry->arg + len + 1);
+
+optional:
+	if (parse_args(opt, &w, "[]", flags) < 0) {
+		print_text(COLOR_HIGHLIGHT,
+			"Unable to parse optional command arguments: %s", opt);
+		free(opt);
+		return -EINVAL;
+	}
+
+	free(opt);
+
+	/* Check if there are too many arguments */
+	if (!optargs && ((unsigned int) argc - 1 > w.we_wordc && !w.we_offs)) {
+		print_text(COLOR_HIGHLIGHT, "Too many arguments: %d > %zu",
+					argc - 1, w.we_wordc);
+		goto fail;
+	}
+
+	w.we_offs = 0;
+	wordfree(&w);
+
+exec:
+	data.exec = entry;
+
+	if (entry->func)
+		entry->func(argc, argv);
+
+	data.exec = NULL;
+
+	return 0;
+
+fail:
+	w.we_offs = 0;
+	wordfree(&w);
+	return -EINVAL;
+}
+
+static int menu_exec(const struct bt_shell_menu_entry *entry,
+					int argc, char *argv[])
+{
+	for (; entry->cmd; entry++) {
+		if (strcmp(argv[0], entry->cmd))
+			continue;
+
+		/* Skip menu command if not on main menu */
+		if (data.menu != data.main && !strcmp(entry->cmd, "menu"))
+			continue;
+
+		/* Skip back command if on main menu */
+		if (data.menu == data.main && !strcmp(entry->cmd, "back"))
+			continue;
+
+		return cmd_exec(entry, argc, argv);
+	}
+
+	return -ENOENT;
+}
+
+static int submenu_exec(int argc, char *argv[])
+{
+	char *name;
+	int len, tlen;
+	const struct bt_shell_menu *submenu;
+
+	if (data.menu != data.main)
+		return -ENOENT;
+
+	name = strchr(argv[0], '.');
+	if (!name)
+		return -ENOENT;
+
+	tlen = strlen(argv[0]);
+	len = name - argv[0];
+	name[0] = '\0';
+
+	submenu = find_menu(argv[0], strlen(argv[0]));
+	if (!submenu)
+		return -ENOENT;
+
+	/* Replace submenu.command with command */
+	memmove(argv[0], argv[0] + len + 1, tlen - len - 1);
+	memset(argv[0] + tlen - len - 1, 0, len + 1);
+
+	return menu_exec(submenu->entries, argc, argv);
+}
+
+static int shell_exec(int argc, char *argv[])
+{
+	int err;
+
+	if (!data.menu || !argv[0])
+		return -EINVAL;
+
+	err  = menu_exec(default_menu, argc, argv);
+	if (err == -ENOENT) {
+		err  = menu_exec(data.menu->entries, argc, argv);
+		if (err == -ENOENT) {
+			err = submenu_exec(argc, argv);
+			if (err == -ENOENT) {
+				print_text(COLOR_HIGHLIGHT,
+					"Invalid command in menu %s: %s",
+					data.menu->name, argv[0]);
+				shell_print_help();
+			}
+		}
+	}
+
+	return err;
+}
+
+void bt_shell_printf(const char *fmt, ...)
+{
+	va_list args;
+	bool save_input;
+	char *saved_line;
+	int saved_point;
+
+	if (!data.input)
+		return;
+
+	if (data.mode) {
+		va_start(args, fmt);
+		vprintf(fmt, args);
+		va_end(args);
+		return;
+	}
+
+	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 (data.monitor) {
+		va_start(args, fmt);
+		bt_log_vprintf(0xffff, data.name, LOG_INFO, 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);
+	}
+}
+
+static void print_string(const char *str, void *user_data)
+{
+	bt_shell_printf("%s\n", str);
+}
+
+void bt_shell_hexdump(const unsigned char *buf, size_t len)
+{
+	util_hexdump(' ', buf, len, print_string, NULL);
+}
+
+void bt_shell_usage(void)
+{
+	if (!data.exec)
+		return;
+
+	bt_shell_printf("Usage: %s %s\n", data.exec->cmd,
+					data.exec->arg ? data.exec->arg : "");
+}
+
+static void prompt_input(const char *str, bt_shell_prompt_input_func func,
+							void *user_data)
+{
+	data.saved_prompt = true;
+	data.saved_func = func;
+	data.saved_user_data = user_data;
+
+	rl_save_prompt();
+	bt_shell_set_prompt(str);
+}
+
+void bt_shell_prompt_input(const char *label, const char *msg,
+			bt_shell_prompt_input_func func, void *user_data)
+{
+	char *str;
+
+	if (!data.init || data.mode)
+		return;
+
+	if (data.saved_prompt) {
+		struct bt_shell_prompt_input *prompt;
+
+		prompt = l_new(struct bt_shell_prompt_input, 1);
+
+		if (asprintf(&str, COLOR_HIGHLIGHT "[%s] %s " COLOR_OFF, label,
+								msg) < 0) {
+			free(prompt);
+			return;
+		}
+
+		prompt->func = func;
+		prompt->user_data = user_data;
+
+		l_queue_push_tail(data.prompts, prompt);
+
+		return;
+	}
+
+	if (asprintf(&str, COLOR_HIGHLIGHT "[%s] %s " COLOR_OFF, label,
+								msg) < 0)
+		return;
+
+	prompt_input(str, func, user_data);
+
+	free(str);
+}
+
+static void prompt_free(void *data)
+{
+	struct bt_shell_prompt_input *prompt = data;
+
+	free(prompt->str);
+	free(prompt);
+}
+
+int bt_shell_release_prompt(const char *input)
+{
+	struct bt_shell_prompt_input *prompt;
+	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;
+
+	prompt = l_queue_pop_head(data.prompts);
+	if (prompt)
+		data.saved_prompt = true;
+
+	data.saved_func = NULL;
+	data.saved_user_data = NULL;
+
+	func(input, user_data);
+
+	if (prompt) {
+		prompt_input(prompt->str, prompt->func, prompt->user_data);
+		prompt_free(prompt);
+	}
+
+	return 0;
+}
+
+static void rl_handler(char *input)
+{
+	wordexp_t w;
+
+	if (!input) {
+		rl_insert_text("quit");
+		rl_redisplay();
+		rl_crlf();
+		l_main_quit();
+		return;
+	}
+
+	if (!strlen(input))
+		goto done;
+
+	if (!bt_shell_release_prompt(input))
+		goto done;
+
+	if (history_search(input, -1))
+		add_history(input);
+
+	if (data.monitor)
+		bt_log_printf(0xffff, data.name, LOG_INFO, "%s", input);
+
+	if (wordexp(input, &w, WRDE_NOCMD))
+		goto done;
+
+	if (w.we_wordc == 0) {
+		wordfree(&w);
+		goto done;
+	}
+
+	shell_exec(w.we_wordc, w.we_wordv);
+	wordfree(&w);
+done:
+	free(input);
+}
+
+static char *find_cmd(const char *text,
+			const struct bt_shell_menu_entry *entry, int *index)
+{
+	const struct bt_shell_menu_entry *tmp;
+	int len;
+
+	len = strlen(text);
+
+	while ((tmp = &entry[*index])) {
+		(*index)++;
+
+		if (!tmp->cmd)
+			break;
+
+		if (tmp->exists && !tmp->exists(data.menu))
+			continue;
+
+		if (!strncmp(tmp->cmd, text, len))
+			return strdup(tmp->cmd);
+	}
+
+	return NULL;
+}
+
+static char *cmd_generator(const char *text, int state)
+{
+	static int index;
+	static bool default_menu_enabled, submenu_enabled;
+	static const struct bt_shell_menu *menu;
+	char *cmd;
+
+	if (!state) {
+		index = 0;
+		menu = NULL;
+		default_menu_enabled = true;
+		submenu_enabled = false;
+	}
+
+	if (default_menu_enabled) {
+		cmd = find_cmd(text, default_menu, &index);
+		if (cmd)
+			return cmd;
+
+		index = 0;
+		menu = data.menu;
+		default_menu_enabled = false;
+	}
+
+	if (!submenu_enabled) {
+		cmd = find_cmd(text, menu->entries, &index);
+		if (cmd || menu != data.main)
+			return cmd;
+
+		cmd = strrchr(text, '.');
+		if (!cmd)
+			return NULL;
+
+		menu = find_menu(text, cmd - text);
+		if (!menu)
+			return NULL;
+
+		index = 0;
+		submenu_enabled = true;
+	}
+
+	cmd = find_cmd(text + strlen(menu->name) + 1, menu->entries, &index);
+	if (cmd) {
+		int err;
+		char *tmp;
+
+		err = asprintf(&tmp, "%s.%s", menu->name, cmd);
+
+		free(cmd);
+
+		if (err < 0)
+			return NULL;
+
+		cmd = tmp;
+	}
+
+	return cmd;
+}
+
+static wordexp_t args;
+
+static char *arg_generator(const char *text, int state)
+{
+	static unsigned int index, len;
+	const char *arg;
+
+	if (!state) {
+		index = 0;
+		len = strlen(text);
+	}
+
+	while (index < args.we_wordc) {
+		arg = args.we_wordv[index];
+		index++;
+
+		if (!strncmp(arg, text, len))
+			return strdup(arg);
+	}
+
+	return NULL;
+}
+
+static char **args_completion(const struct bt_shell_menu_entry *entry, int argc,
+							const char *text)
+{
+	char **matches = NULL;
+	char *str;
+	int index;
+
+	index = text[0] == '\0' ? argc - 1 : argc - 2;
+	if (index < 0)
+		return NULL;
+
+	if (!entry->arg)
+		goto end;
+
+	str = strdup(entry->arg);
+
+	if (parse_args(str, &args, "<>[]", WRDE_NOCMD))
+		goto done;
+
+	/* Check if argument is valid */
+	if ((unsigned int) index > args.we_wordc - 1)
+		goto done;
+
+	/* Check if there are multiple values */
+	if (!strrchr(entry->arg, '/'))
+		goto done;
+
+	free(str);
+
+	/* Split values separated by / */
+	str = strdelimit(args.we_wordv[index], "/", ' ');
+
+	args.we_offs = 0;
+	wordfree(&args);
+
+	if (wordexp(str, &args, WRDE_NOCMD))
+		goto done;
+
+	rl_completion_display_matches_hook = NULL;
+	matches = rl_completion_matches(text, arg_generator);
+
+done:
+	free(str);
+end:
+	if (!matches && text[0] == '\0')
+		bt_shell_printf("Usage: %s %s\n", entry->cmd,
+					entry->arg ? entry->arg : "");
+
+	args.we_offs = 0;
+	wordfree(&args);
+	return matches;
+}
+
+static char **menu_completion(const struct bt_shell_menu_entry *entry,
+				const char *text, int argc, char *input_cmd)
+{
+	char **matches = NULL;
+
+	for (; entry->cmd; entry++) {
+		if (strcmp(entry->cmd, input_cmd))
+			continue;
+
+		if (!entry->gen) {
+			matches = args_completion(entry, argc, text);
+			break;
+		}
+
+		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) {
+		wordexp_t w;
+
+		if (wordexp(rl_line_buffer, &w, WRDE_NOCMD))
+			return NULL;
+
+		matches = menu_completion(default_menu, text, w.we_wordc,
+							w.we_wordv[0]);
+		if (!matches)
+			matches = menu_completion(data.menu->entries, text,
+							w.we_wordc,
+							w.we_wordv[0]);
+
+		wordfree(&w);
+	} else {
+		rl_completion_display_matches_hook = NULL;
+		matches = rl_completion_matches(text, cmd_generator);
+	}
+
+	if (!matches)
+		rl_attempted_completion_over = 1;
+
+	return matches;
+}
+
+static void io_hup(struct l_io *io, void *user_data)
+{
+	l_main_quit();
+}
+
+static void signal_callback(unsigned int signum, void *user_data)
+{
+	static bool terminated;
+
+	switch (signum) {
+	case SIGINT:
+		if (data.input && !data.mode) {
+			rl_replace_line("", 0);
+			rl_crlf();
+			rl_on_new_line();
+			rl_redisplay();
+			return;
+		}
+
+		/*
+		 * 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) {
+			if (!data.mode) {
+				rl_replace_line("", 0);
+				rl_crlf();
+			}
+			l_main_quit();
+		}
+
+		terminated = true;
+		break;
+	}
+}
+
+static void rl_init_history(void)
+{
+	const char *name;
+	char *dir;
+
+	memset(data.history, 0, sizeof(data.history));
+
+	name = strrchr(data.name, '/');
+	if (!name)
+		name = data.name;
+	else
+		name++;
+
+	dir = getenv("XDG_CACHE_HOME");
+	if (dir) {
+		snprintf(data.history, sizeof(data.history), "%s/.%s_history",
+							dir, name);
+		goto done;
+	}
+
+	dir = getenv("HOME");
+	if (dir) {
+		snprintf(data.history, sizeof(data.history),
+				"%s/.cache/.%s_history", dir, name);
+		goto done;
+	}
+
+	dir = getenv("PWD");
+	if (dir) {
+		snprintf(data.history, sizeof(data.history), "%s/.%s_history",
+							dir, name);
+		goto done;
+	}
+
+	return;
+
+done:
+	read_history(data.history);
+	using_history();
+	bt_shell_set_env("HISTORY", data.history);
+}
+
+static void rl_init(void)
+{
+	if (data.mode)
+		return;
+
+	/* Allow conditional parsing of the ~/.inputrc file. */
+	rl_readline_name = data.name;
+
+	setlinebuf(stdout);
+	rl_attempted_completion_function = shell_completion;
+
+	rl_erase_empty_line = 1;
+	rl_callback_handler_install(NULL, rl_handler);
+
+	rl_init_history();
+}
+
+static const struct option main_options[] = {
+	{ "version",	no_argument, 0, 'v' },
+	{ "help",	no_argument, 0, 'h' },
+	{ "timeout",	required_argument, 0, 't' },
+	{ "monitor",	no_argument, 0, 'm' },
+};
+
+static void usage(int argc, char **argv, const struct bt_shell_opt *opt)
+{
+	unsigned int i;
+
+	printf("%s ver %s\n", data.name, VERSION);
+	printf("Usage:\n"
+		"\t%s [--options] [commands]\n", data.name);
+
+	printf("Options:\n");
+
+	for (i = 0; opt && opt->options[i].name; i++)
+		printf("\t--%s \t%s\n", opt->options[i].name, opt->help[i]);
+
+	printf("\t--monitor \tEnable monitor output\n"
+		"\t--timeout \tTimeout in seconds for non-interactive mode\n"
+		"\t--version \tDisplay version\n"
+		"\t--help \t\tDisplay help\n");
+}
+
+void bt_shell_init(int argc, char **argv, const struct bt_shell_opt *opt)
+{
+	int c, index = -1;
+	struct option options[256];
+	char optstr[256];
+	size_t offset;
+
+	offset = sizeof(main_options) / sizeof(struct option);
+
+	memcpy(options, main_options, sizeof(struct option) * offset);
+
+	if (opt) {
+		memcpy(options + offset, opt->options,
+				sizeof(struct option) * opt->optno);
+		snprintf(optstr, sizeof(optstr), "+mhvt:%s", opt->optstr);
+	} else
+		snprintf(optstr, sizeof(optstr), "+mhvt:");
+
+	data.name = strrchr(argv[0], '/');
+	if (!data.name)
+		data.name = strdup(argv[0]);
+	else
+		data.name = strdup(++data.name);
+
+	while ((c = getopt_long(argc, argv, optstr, options, &index)) != -1) {
+		switch (c) {
+		case 'v':
+			printf("%s: %s\n", data.name, VERSION);
+			exit(EXIT_SUCCESS);
+			return;
+		case 'h':
+			usage(argc, argv, opt);
+			data.argc = 1;
+			data.argv = &cmplt;
+			data.mode = 1;
+			goto done;
+		case 't':
+			data.timeout = atoi(optarg);
+			break;
+		case 'm':
+			data.monitor = true;
+			if (bt_log_open() < 0) {
+				data.monitor = false;
+				printf("Unable to open logging channel\n");
+			}
+			break;
+		default:
+			if (index < 0) {
+				for (index = 0; options[index].val; index++) {
+					if (c == options[index].val)
+						break;
+				}
+			}
+
+			if (c != opt->options[index - offset].val) {
+				usage(argc, argv, opt);
+				exit(EXIT_SUCCESS);
+				return;
+			}
+
+			*opt->optarg[index - offset] = optarg;
+		}
+
+		index = -1;
+	}
+
+	bt_shell_set_env("SHELL", data.name);
+
+	data.argc = argc - optind;
+	data.argv = argv + optind;
+	optind = 0;
+	data.mode = (data.argc > 0);
+
+done:
+	if (data.mode)
+		bt_shell_set_env("NON_INTERACTIVE", &data.mode);
+
+	l_main_init();
+
+	rl_init();
+
+	data.init = true;
+	data.prompts = l_queue_new();
+}
+
+static void rl_cleanup(void)
+{
+	if (data.mode)
+		return;
+
+	if (data.history[0] != '\0')
+		write_history(data.history);
+
+	rl_message("");
+	rl_callback_handler_remove();
+}
+
+static void env_destroy(void *data)
+{
+	struct bt_shell_env *env = data;
+
+	free(env->name);
+	free(env);
+}
+
+int bt_shell_run(void)
+{
+	int status;
+
+	status = l_main_run_with_signal(signal_callback, NULL);
+
+	bt_shell_cleanup();
+
+	l_main_exit();
+
+	return status;
+}
+
+void bt_shell_cleanup(void)
+{
+	bt_shell_release_prompt("");
+	bt_shell_detach();
+
+	if (data.envs) {
+		l_queue_destroy(data.envs, env_destroy);
+		data.envs = NULL;
+	}
+
+	if (data.monitor)
+		bt_log_close();
+
+	rl_cleanup();
+
+	l_queue_destroy(data.prompts, prompt_free);
+	data.prompts = NULL;
+
+	data.init = false;
+	free(data.name);
+}
+
+void bt_shell_quit(int status)
+{
+	l_main_quit();
+}
+
+void bt_shell_noninteractive_quit(int status)
+{
+	if (!data.mode || data.timeout)
+		return;
+
+	bt_shell_quit(status);
+}
+
+bool bt_shell_set_menu(const struct bt_shell_menu *menu)
+{
+	if (!menu)
+		return false;
+
+	data.menu = menu;
+
+	if (!data.main)
+		data.main = menu;
+
+	return true;
+}
+
+bool bt_shell_add_submenu(const struct bt_shell_menu *menu)
+{
+	if (!menu)
+		return false;
+
+	if (!data.submenus)
+		data.submenus = l_queue_new();
+
+	l_queue_push_tail(data.submenus, (void *) menu);
+
+	return true;
+}
+
+void bt_shell_set_prompt(const char *string)
+{
+	if (!data.init || data.mode)
+		return;
+
+	rl_set_prompt(string);
+	rl_redisplay();
+}
+
+static bool input_read(struct l_io *io, void *user_data)
+{
+	rl_callback_read_char();
+	return true;
+}
+
+static void quit_on_timeout(struct l_timeout *timeout, void *user_data)
+{
+	l_main_quit();
+}
+
+bool bt_shell_attach(int fd)
+{
+	struct l_io *io;
+
+	/* TODO: Allow more than one input? */
+	if (data.input)
+		return false;
+
+	io = l_io_new(fd);
+
+	if (!data.mode)
+		l_io_set_read_handler(io, input_read, NULL, NULL);
+
+	l_io_set_disconnect_handler(io, io_hup, NULL, NULL);
+
+	data.input = io;
+
+	if (data.mode) {
+		if (shell_exec(data.argc, data.argv) < 0) {
+			bt_shell_noninteractive_quit(EXIT_FAILURE);
+			return true;
+		}
+
+
+		if (data.timeout)
+			l_timeout_create(data.timeout, quit_on_timeout, NULL,
+								NULL);
+	}
+
+	return true;
+}
+
+bool bt_shell_detach(void)
+{
+	if (!data.input)
+		return false;
+
+	l_io_destroy(data.input);
+	data.input = NULL;
+
+	return true;
+}
+
+static bool match_env(const void *data, const void *user_data)
+{
+	const struct bt_shell_env *env = data;
+	const char *name = user_data;
+
+	return !strcmp(env->name, name);
+}
+
+void bt_shell_set_env(const char *name, void *value)
+{
+	struct bt_shell_env *env;
+
+	if (!data.envs) {
+		if (!value)
+			return;
+		data.envs = l_queue_new();
+		goto done;
+	}
+
+	env = l_queue_remove_if(data.envs, match_env, (void *) name);
+	if (env)
+		env_destroy(env);
+
+	/* Don't create an env if value is not set */
+	if (!value)
+		return;
+
+done:
+	env = l_new(struct bt_shell_env, 1);
+	env->name = strdup(name);
+	env->value = value;
+
+	l_queue_push_tail(data.envs, env);
+}
+
+void *bt_shell_get_env(const char *name)
+{
+	const struct bt_shell_env *env;
+
+	if (!data.envs)
+		return NULL;
+
+	env = l_queue_find(data.envs, match_env, name);
+	if (!env)
+		return NULL;
+
+	return env->value;
+}
-- 
2.21.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH BlueZ 2/2] shared/shell: Add "clear-history" API
  2019-08-07  6:47 [PATCH BlueZ 0/2] ELL-based bt_shell implementation Inga Stotland
  2019-08-07  6:47 ` [PATCH BlueZ 1/2] shared/shell: Add ell based shell implementation Inga Stotland
@ 2019-08-07  6:47 ` Inga Stotland
  1 sibling, 0 replies; 5+ messages in thread
From: Inga Stotland @ 2019-08-07  6:47 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: brian.gix, luiz.dentz, Inga Stotland

Thsi adds a new API function bt_shell_clear_history(). The
function can be used to clear up the command history, when
it gets too cluttered.
---
 src/shared/shell-ell.c | 5 +++++
 src/shared/shell.c     | 7 ++++++-
 src/shared/shell.h     | 1 +
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/shared/shell-ell.c b/src/shared/shell-ell.c
index 1b481e04e..1e3de521c 100644
--- a/src/shared/shell-ell.c
+++ b/src/shared/shell-ell.c
@@ -1318,3 +1318,8 @@ void *bt_shell_get_env(const char *name)
 
 	return env->value;
 }
+
+void bt_shell_clear_history(void)
+{
+	rl_clear_history();
+}
diff --git a/src/shared/shell.c b/src/shared/shell.c
index eac654f40..86f9b71df 100644
--- a/src/shared/shell.c
+++ b/src/shared/shell.c
@@ -2,7 +2,7 @@
  *
  *  BlueZ - Bluetooth protocol stack for Linux
  *
- *  Copyright (C) 2017  Intel Corporation. All rights reserved.
+ *  Copyright (C) 2017-2019  Intel Corporation. All rights reserved.
  *
  *
  *  This library is free software; you can redistribute it and/or
@@ -1328,3 +1328,8 @@ void *bt_shell_get_env(const char *name)
 
 	return env->value;
 }
+
+void bt_shell_clear_history(void)
+{
+	rl_clear_history();
+}
diff --git a/src/shared/shell.h b/src/shared/shell.h
index e14d58381..9ba4a20c8 100644
--- a/src/shared/shell.h
+++ b/src/shared/shell.h
@@ -94,4 +94,5 @@ bool bt_shell_detach(void);
 void bt_shell_set_env(const char *name, void *value);
 void *bt_shell_get_env(const char *name);
 
+void bt_shell_clear_history(void);
 void bt_shell_cleanup(void);
-- 
2.21.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH BlueZ 1/2] shared/shell: Add ell based shell implementation
  2019-08-07  6:47 ` [PATCH BlueZ 1/2] shared/shell: Add ell based shell implementation Inga Stotland
@ 2019-08-07 11:18   ` Luiz Augusto von Dentz
  2019-08-07 19:27     ` Stotland, Inga
  0 siblings, 1 reply; 5+ messages in thread
From: Luiz Augusto von Dentz @ 2019-08-07 11:18 UTC (permalink / raw)
  To: Inga Stotland; +Cc: linux-bluetooth, Gix, Brian

Hi Inga,

On Wed, Aug 7, 2019 at 9:47 AM Inga Stotland <inga.stotland@intel.com> wrote:
>
> This adds the functionality of bt_shell that uses ell
> mainloop.
> ---
>  Makefile.am            |   13 +-
>  src/shared/shell-ell.c | 1320 ++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 1330 insertions(+), 3 deletions(-)
>  create mode 100644 src/shared/shell-ell.c
>
> diff --git a/Makefile.am b/Makefile.am
> index 9d25a815b..0ce3048ad 100644
> --- a/Makefile.am
> +++ b/Makefile.am
> @@ -187,9 +187,6 @@ shared_sources = src/shared/io.h src/shared/timeout.h \
>                         src/shared/log.h src/shared/log.c \
>                         src/shared/tty.h
>
> -if READLINE
> -shared_sources += src/shared/shell.c src/shared/shell.h
> -endif
>
>  src_libshared_glib_la_SOURCES = $(shared_sources) \
>                                 src/shared/io-glib.c \
> @@ -205,11 +202,21 @@ src_libshared_mainloop_la_SOURCES = $(shared_sources) \
>                                 src/shared/mainloop-notify.h \
>                                 src/shared/mainloop-notify.c
>
> +if READLINE
> +shared_sources += src/shared/shell.h
> +src_libshared_glib_la_SOURCES += src/shared/shell.c
> +src_libshared_mainloop_la_SOURCES += src/shared/shell.c
> +endif
> +
>  if LIBSHARED_ELL
>  noinst_LTLIBRARIES += src/libshared-ell.la
>
>  src_libshared_ell_la_SOURCES = $(shared_sources) \
>                                 src/shared/io-ell.c
> +
> +if READLINE
> +src_libshared_ell_la_SOURCES += src/shared/shell-ell.c
> +endif
>  endif
>
>  attrib_sources = attrib/att.h attrib/att-database.h attrib/att.c \
> diff --git a/src/shared/shell-ell.c b/src/shared/shell-ell.c
> new file mode 100644
> index 000000000..1b481e04e
> --- /dev/null
> +++ b/src/shared/shell-ell.c
> @@ -0,0 +1,1320 @@
> +/*
> + *
> + *  BlueZ - Bluetooth protocol stack for Linux
> + *
> + *  Copyright (C) 2017-2019  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.
> + *
> + */
> +
> +#ifdef HAVE_CONFIG_H
> +#include <config.h>
> +#endif
> +
> +#define _GNU_SOURCE
> +#include <stdio.h>
> +#include <errno.h>
> +#include <syslog.h>
> +#include <unistd.h>
> +#include <stdlib.h>
> +#include <stdarg.h>
> +#include <stdbool.h>
> +#include <signal.h>
> +#include <sys/signalfd.h>
> +#include <wordexp.h>
> +#include <getopt.h>
> +
> +#include <readline/readline.h>
> +#include <readline/history.h>
> +
> +#include "ell/ell.h"
> +
> +#include "src/shared/util.h"
> +#include "src/shared/log.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)
> +#define print_submenu(cmd, desc) \
> +               printf(COLOR_BLUE "%s %-*s " COLOR_OFF "%s\n", \
> +                       cmd, (int)(CMD_LENGTH - strlen(cmd)), "", desc)
> +
> +struct bt_shell_env {
> +       char *name;
> +       void *value;
> +};
> +
> +static char *cmplt = "help";
> +
> +struct bt_shell_prompt_input {
> +       char *str;
> +       bt_shell_prompt_input_func func;
> +       void *user_data;
> +};
> +
> +static struct {
> +       bool init;
> +       char *name;
> +       char history[256];
> +       int argc;
> +       char **argv;
> +       bool mode;
> +       bool monitor;
> +       int timeout;
> +       struct l_io *input;
> +
> +       bool saved_prompt;
> +       bt_shell_prompt_input_func saved_func;
> +       void *saved_user_data;
> +
> +       struct l_queue *prompts;
> +
> +       const struct bt_shell_menu *menu;
> +       const struct bt_shell_menu *main;
> +       struct l_queue *submenus;
> +       const struct bt_shell_menu_entry *exec;
> +
> +       struct l_queue *envs;
> +} data;
> +
> +static void shell_print_menu(void);
> +
> +static void cmd_version(int argc, char *argv[])
> +{
> +       bt_shell_printf("Version %s\n", VERSION);
> +
> +       return bt_shell_noninteractive_quit(EXIT_SUCCESS);
> +}
> +
> +static void cmd_quit(int argc, char *argv[])
> +{
> +       l_main_quit();
> +}
> +
> +static void print_cmds(void)
> +{
> +       const struct bt_shell_menu_entry *entry;
> +       const struct l_queue_entry *submenu;
> +
> +       if (!data.menu)
> +               return;
> +
> +       printf("Commands:\n");
> +
> +       for (entry = data.menu->entries; entry->cmd; entry++) {
> +               printf("\t%s%s\t%s\n", entry->cmd,
> +                       strlen(entry->cmd) < 8 ? "\t" : "", entry->desc);
> +       }
> +
> +       for (submenu = l_queue_get_entries(data.submenus); submenu;
> +                                       submenu = submenu->next) {
> +               struct bt_shell_menu *menu = submenu->data;
> +
> +               printf("\n\t%s.:\n", menu->name);
> +
> +               for (entry = menu->entries; entry->cmd; entry++) {
> +                       printf("\t\t%s%s\t%s\n", entry->cmd,
> +                               strlen(entry->cmd) < 8 ? "\t" : "",
> +                               entry->desc);
> +               }
> +       }
> +}
> +
> +static void cmd_help(int argc, char *argv[])
> +{
> +       if (argv[0] == cmplt)
> +               print_cmds();
> +       else
> +               shell_print_menu();
> +
> +       return bt_shell_noninteractive_quit(EXIT_SUCCESS);
> +}
> +
> +static const struct bt_shell_menu *find_menu(const char *name, size_t len)
> +{
> +       const struct l_queue_entry *entry;
> +
> +       for (entry = l_queue_get_entries(data.submenus); entry;
> +                                               entry = entry->next) {
> +               struct bt_shell_menu *menu = entry->data;
> +
> +               if (!strncmp(menu->name, name, len))
> +                       return menu;
> +       }
> +
> +       return NULL;
> +}
> +
> +static char *menu_generator(const char *text, int state)
> +{
> +       static unsigned int index, len;
> +       static struct l_queue_entry *entry;
> +
> +       if (!state) {
> +               index = 0;
> +               len = strlen(text);
> +               entry = (void *) l_queue_get_entries(data.submenus);
> +       }
> +
> +       for (; entry; entry = entry->next) {
> +               struct bt_shell_menu *menu = entry->data;
> +
> +               index++;
> +
> +               if (!strncmp(menu->name, text, len)) {
> +                       entry = entry->next;
> +                       return strdup(menu->name);
> +               }
> +       }
> +
> +       return NULL;
> +}
> +
> +static void cmd_menu(int argc, char *argv[])
> +{
> +       const struct bt_shell_menu *menu;
> +
> +       if (argc < 2 || !strlen(argv[1])) {
> +               bt_shell_printf("Missing name argument\n");
> +               return bt_shell_noninteractive_quit(EXIT_FAILURE);
> +       }
> +
> +       menu = find_menu(argv[1], strlen(argv[1]));
> +       if (!menu) {
> +               bt_shell_printf("Unable find menu with name: %s\n", argv[1]);
> +               return bt_shell_noninteractive_quit(EXIT_FAILURE);
> +       }
> +
> +       bt_shell_set_menu(menu);
> +
> +       shell_print_menu();
> +
> +       return bt_shell_noninteractive_quit(EXIT_SUCCESS);
> +}
> +
> +static bool cmd_menu_exists(const struct bt_shell_menu *menu)
> +{
> +       /* Skip menu command if not on main menu or if there are no
> +        * submenus.
> +        */
> +       if (menu != data.main || l_queue_isempty(data.submenus))
> +               return false;
> +
> +       return true;
> +}
> +
> +static void cmd_back(int argc, char *argv[])
> +{
> +       if (data.menu == data.main) {
> +               bt_shell_printf("Already on main menu\n");
> +               return;
> +       }
> +
> +       bt_shell_set_menu(data.main);
> +
> +       shell_print_menu();
> +}
> +
> +static bool cmd_back_exists(const struct bt_shell_menu *menu)
> +{
> +       /* Skip back command if on main menu */
> +       if (menu == data.main)
> +               return false;
> +
> +       return true;
> +}
> +
> +static void cmd_export(int argc, char *argv[])
> +{
> +       const struct l_queue_entry *entry;
> +
> +       entry = l_queue_get_entries(data.envs);
> +
> +       for (; entry; entry = entry->next) {
> +               struct bt_shell_env *env = entry->data;
> +
> +               print_text(COLOR_HIGHLIGHT, "%s=%p", env->name, env->value);
> +       }
> +}
> +
> +static const struct bt_shell_menu_entry default_menu[] = {
> +       { "back",         NULL,       cmd_back, "Return to main menu", NULL,
> +                                                       NULL, cmd_back_exists },
> +       { "menu",         "<name>",   cmd_menu, "Select submenu",
> +                                                       menu_generator, NULL,
> +                                                       cmd_menu_exists},
> +       { "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" },
> +       { "export",       NULL,       cmd_export,
> +                                               "Print evironment variables" },
> +       { }
> +};
> +
> +static void shell_print_help(void)
> +{
> +       print_text(COLOR_HIGHLIGHT,
> +               "\n"
> +               "Use \"help\" for a list of available commands in a menu.\n"
> +               "Use \"menu <submenu>\" if you want to enter any submenu.\n"
> +               "Use \"back\" if you want to return to menu main.");
> +}
> +
> +static void shell_print_menu(void)
> +{
> +       const struct bt_shell_menu_entry *entry;
> +       const struct l_queue_entry *submenu;
> +
> +       if (!data.menu)
> +               return;
> +
> +       print_text(COLOR_HIGHLIGHT, "Menu %s:", data.menu->name);
> +       print_text(COLOR_HIGHLIGHT, "Available commands:");
> +       print_text(COLOR_HIGHLIGHT, "-------------------");
> +
> +       if (data.menu == data.main) {
> +               for (submenu = l_queue_get_entries(data.submenus); submenu;
> +                                               submenu = submenu->next) {
> +                       struct bt_shell_menu *menu = submenu->data;
> +
> +                       print_submenu(menu->name, menu->desc ? menu->desc :
> +                                                               "Submenu");
> +               }
> +       }
> +
> +       for (entry = data.menu->entries; entry->cmd; entry++)
> +               print_menu(entry->cmd, entry->arg ? : "", entry->desc ? : "");
> +
> +       for (entry = default_menu; entry->cmd; entry++) {
> +               if (entry->exists && !entry->exists(data.menu))
> +                       continue;
> +
> +               print_menu(entry->cmd, entry->arg ? : "", entry->desc ? : "");
> +       }
> +}
> +
> +static int parse_args(char *arg, wordexp_t *w, char *del, int flags)
> +{
> +       char *str;
> +
> +       str = strdelimit(arg, del, '"');
> +
> +       if (wordexp(str, w, flags)) {
> +               free(str);
> +               return -EINVAL;
> +       }
> +
> +       /* If argument ends with ... set we_offs bypass strict checks */
> +       if (w->we_wordc && !strsuffix(w->we_wordv[w->we_wordc - 1], "..."))
> +               w->we_offs = 1;
> +
> +       free(str);
> +
> +       return 0;
> +}
> +
> +static int cmd_exec(const struct bt_shell_menu_entry *entry,
> +                                       int argc, char *argv[])
> +{
> +       wordexp_t w;
> +       size_t len;
> +       char *man, *opt;
> +       int flags = WRDE_NOCMD;
> +       bool optargs = false;
> +
> +       if (!entry->arg || entry->arg[0] == '\0') {
> +               if (argc > 1) {
> +                       print_text(COLOR_HIGHLIGHT, "Too many arguments");
> +                       return -EINVAL;
> +               }
> +               goto exec;
> +       }
> +
> +       /* Find last mandatory arguments */
> +       man = strrchr(entry->arg, '>');
> +       if (!man) {
> +               opt = strdup(entry->arg);
> +               goto optional;
> +       }
> +
> +       len = man - entry->arg;
> +       if (entry->arg[0] == '<')
> +               man = strndup(entry->arg, len + 1);
> +       else {
> +               /* Find where mandatory arguments start */
> +               opt = strrchr(entry->arg, '<');
> +               /* Skip if mandatory arguments are not in the right format */
> +               if (!opt || opt > man) {
> +                       opt = strdup(entry->arg);
> +                       goto optional;
> +               }
> +               man = strndup(opt, man - opt + 1);
> +               optargs = true;
> +       }
> +
> +       if (parse_args(man, &w, "<>", flags) < 0) {
> +               print_text(COLOR_HIGHLIGHT,
> +                       "Unable to parse mandatory command arguments: %s", man);
> +               free(man);
> +               return -EINVAL;
> +       }
> +
> +       free(man);
> +
> +       /* Check if there are enough arguments */
> +       if ((unsigned int) argc - 1 < w.we_wordc) {
> +               print_text(COLOR_HIGHLIGHT, "Missing %s argument",
> +                                               w.we_wordv[argc - 1]);
> +               goto fail;
> +       }
> +
> +       flags |= WRDE_APPEND;
> +       opt = strdup(entry->arg + len + 1);
> +
> +optional:
> +       if (parse_args(opt, &w, "[]", flags) < 0) {
> +               print_text(COLOR_HIGHLIGHT,
> +                       "Unable to parse optional command arguments: %s", opt);
> +               free(opt);
> +               return -EINVAL;
> +       }
> +
> +       free(opt);
> +
> +       /* Check if there are too many arguments */
> +       if (!optargs && ((unsigned int) argc - 1 > w.we_wordc && !w.we_offs)) {
> +               print_text(COLOR_HIGHLIGHT, "Too many arguments: %d > %zu",
> +                                       argc - 1, w.we_wordc);
> +               goto fail;
> +       }
> +
> +       w.we_offs = 0;
> +       wordfree(&w);
> +
> +exec:
> +       data.exec = entry;
> +
> +       if (entry->func)
> +               entry->func(argc, argv);
> +
> +       data.exec = NULL;
> +
> +       return 0;
> +
> +fail:
> +       w.we_offs = 0;
> +       wordfree(&w);
> +       return -EINVAL;
> +}
> +
> +static int menu_exec(const struct bt_shell_menu_entry *entry,
> +                                       int argc, char *argv[])
> +{
> +       for (; entry->cmd; entry++) {
> +               if (strcmp(argv[0], entry->cmd))
> +                       continue;
> +
> +               /* Skip menu command if not on main menu */
> +               if (data.menu != data.main && !strcmp(entry->cmd, "menu"))
> +                       continue;
> +
> +               /* Skip back command if on main menu */
> +               if (data.menu == data.main && !strcmp(entry->cmd, "back"))
> +                       continue;
> +
> +               return cmd_exec(entry, argc, argv);
> +       }
> +
> +       return -ENOENT;
> +}
> +
> +static int submenu_exec(int argc, char *argv[])
> +{
> +       char *name;
> +       int len, tlen;
> +       const struct bt_shell_menu *submenu;
> +
> +       if (data.menu != data.main)
> +               return -ENOENT;
> +
> +       name = strchr(argv[0], '.');
> +       if (!name)
> +               return -ENOENT;
> +
> +       tlen = strlen(argv[0]);
> +       len = name - argv[0];
> +       name[0] = '\0';
> +
> +       submenu = find_menu(argv[0], strlen(argv[0]));
> +       if (!submenu)
> +               return -ENOENT;
> +
> +       /* Replace submenu.command with command */
> +       memmove(argv[0], argv[0] + len + 1, tlen - len - 1);
> +       memset(argv[0] + tlen - len - 1, 0, len + 1);
> +
> +       return menu_exec(submenu->entries, argc, argv);
> +}
> +
> +static int shell_exec(int argc, char *argv[])
> +{
> +       int err;
> +
> +       if (!data.menu || !argv[0])
> +               return -EINVAL;
> +
> +       err  = menu_exec(default_menu, argc, argv);
> +       if (err == -ENOENT) {
> +               err  = menu_exec(data.menu->entries, argc, argv);
> +               if (err == -ENOENT) {
> +                       err = submenu_exec(argc, argv);
> +                       if (err == -ENOENT) {
> +                               print_text(COLOR_HIGHLIGHT,
> +                                       "Invalid command in menu %s: %s",
> +                                       data.menu->name, argv[0]);
> +                               shell_print_help();
> +                       }
> +               }
> +       }
> +
> +       return err;
> +}
> +
> +void bt_shell_printf(const char *fmt, ...)
> +{
> +       va_list args;
> +       bool save_input;
> +       char *saved_line;
> +       int saved_point;
> +
> +       if (!data.input)
> +               return;
> +
> +       if (data.mode) {
> +               va_start(args, fmt);
> +               vprintf(fmt, args);
> +               va_end(args);
> +               return;
> +       }
> +
> +       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 (data.monitor) {
> +               va_start(args, fmt);
> +               bt_log_vprintf(0xffff, data.name, LOG_INFO, 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);
> +       }
> +}
> +
> +static void print_string(const char *str, void *user_data)
> +{
> +       bt_shell_printf("%s\n", str);
> +}
> +
> +void bt_shell_hexdump(const unsigned char *buf, size_t len)
> +{
> +       util_hexdump(' ', buf, len, print_string, NULL);
> +}
> +
> +void bt_shell_usage(void)
> +{
> +       if (!data.exec)
> +               return;
> +
> +       bt_shell_printf("Usage: %s %s\n", data.exec->cmd,
> +                                       data.exec->arg ? data.exec->arg : "");
> +}
> +
> +static void prompt_input(const char *str, bt_shell_prompt_input_func func,
> +                                                       void *user_data)
> +{
> +       data.saved_prompt = true;
> +       data.saved_func = func;
> +       data.saved_user_data = user_data;
> +
> +       rl_save_prompt();
> +       bt_shell_set_prompt(str);
> +}
> +
> +void bt_shell_prompt_input(const char *label, const char *msg,
> +                       bt_shell_prompt_input_func func, void *user_data)
> +{
> +       char *str;
> +
> +       if (!data.init || data.mode)
> +               return;
> +
> +       if (data.saved_prompt) {
> +               struct bt_shell_prompt_input *prompt;
> +
> +               prompt = l_new(struct bt_shell_prompt_input, 1);
> +
> +               if (asprintf(&str, COLOR_HIGHLIGHT "[%s] %s " COLOR_OFF, label,
> +                                                               msg) < 0) {
> +                       free(prompt);
> +                       return;
> +               }
> +
> +               prompt->func = func;
> +               prompt->user_data = user_data;
> +
> +               l_queue_push_tail(data.prompts, prompt);
> +
> +               return;
> +       }
> +
> +       if (asprintf(&str, COLOR_HIGHLIGHT "[%s] %s " COLOR_OFF, label,
> +                                                               msg) < 0)
> +               return;
> +
> +       prompt_input(str, func, user_data);
> +
> +       free(str);
> +}
> +
> +static void prompt_free(void *data)
> +{
> +       struct bt_shell_prompt_input *prompt = data;
> +
> +       free(prompt->str);
> +       free(prompt);
> +}
> +
> +int bt_shell_release_prompt(const char *input)
> +{
> +       struct bt_shell_prompt_input *prompt;
> +       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;
> +
> +       prompt = l_queue_pop_head(data.prompts);
> +       if (prompt)
> +               data.saved_prompt = true;
> +
> +       data.saved_func = NULL;
> +       data.saved_user_data = NULL;
> +
> +       func(input, user_data);
> +
> +       if (prompt) {
> +               prompt_input(prompt->str, prompt->func, prompt->user_data);
> +               prompt_free(prompt);
> +       }
> +
> +       return 0;
> +}
> +
> +static void rl_handler(char *input)
> +{
> +       wordexp_t w;
> +
> +       if (!input) {
> +               rl_insert_text("quit");
> +               rl_redisplay();
> +               rl_crlf();
> +               l_main_quit();
> +               return;
> +       }
> +
> +       if (!strlen(input))
> +               goto done;
> +
> +       if (!bt_shell_release_prompt(input))
> +               goto done;
> +
> +       if (history_search(input, -1))
> +               add_history(input);
> +
> +       if (data.monitor)
> +               bt_log_printf(0xffff, data.name, LOG_INFO, "%s", input);
> +
> +       if (wordexp(input, &w, WRDE_NOCMD))
> +               goto done;
> +
> +       if (w.we_wordc == 0) {
> +               wordfree(&w);
> +               goto done;
> +       }
> +
> +       shell_exec(w.we_wordc, w.we_wordv);
> +       wordfree(&w);
> +done:
> +       free(input);
> +}
> +
> +static char *find_cmd(const char *text,
> +                       const struct bt_shell_menu_entry *entry, int *index)
> +{
> +       const struct bt_shell_menu_entry *tmp;
> +       int len;
> +
> +       len = strlen(text);
> +
> +       while ((tmp = &entry[*index])) {
> +               (*index)++;
> +
> +               if (!tmp->cmd)
> +                       break;
> +
> +               if (tmp->exists && !tmp->exists(data.menu))
> +                       continue;
> +
> +               if (!strncmp(tmp->cmd, text, len))
> +                       return strdup(tmp->cmd);
> +       }
> +
> +       return NULL;
> +}
> +
> +static char *cmd_generator(const char *text, int state)
> +{
> +       static int index;
> +       static bool default_menu_enabled, submenu_enabled;
> +       static const struct bt_shell_menu *menu;
> +       char *cmd;
> +
> +       if (!state) {
> +               index = 0;
> +               menu = NULL;
> +               default_menu_enabled = true;
> +               submenu_enabled = false;
> +       }
> +
> +       if (default_menu_enabled) {
> +               cmd = find_cmd(text, default_menu, &index);
> +               if (cmd)
> +                       return cmd;
> +
> +               index = 0;
> +               menu = data.menu;
> +               default_menu_enabled = false;
> +       }
> +
> +       if (!submenu_enabled) {
> +               cmd = find_cmd(text, menu->entries, &index);
> +               if (cmd || menu != data.main)
> +                       return cmd;
> +
> +               cmd = strrchr(text, '.');
> +               if (!cmd)
> +                       return NULL;
> +
> +               menu = find_menu(text, cmd - text);
> +               if (!menu)
> +                       return NULL;
> +
> +               index = 0;
> +               submenu_enabled = true;
> +       }
> +
> +       cmd = find_cmd(text + strlen(menu->name) + 1, menu->entries, &index);
> +       if (cmd) {
> +               int err;
> +               char *tmp;
> +
> +               err = asprintf(&tmp, "%s.%s", menu->name, cmd);
> +
> +               free(cmd);
> +
> +               if (err < 0)
> +                       return NULL;
> +
> +               cmd = tmp;
> +       }
> +
> +       return cmd;
> +}
> +
> +static wordexp_t args;
> +
> +static char *arg_generator(const char *text, int state)
> +{
> +       static unsigned int index, len;
> +       const char *arg;
> +
> +       if (!state) {
> +               index = 0;
> +               len = strlen(text);
> +       }
> +
> +       while (index < args.we_wordc) {
> +               arg = args.we_wordv[index];
> +               index++;
> +
> +               if (!strncmp(arg, text, len))
> +                       return strdup(arg);
> +       }
> +
> +       return NULL;
> +}
> +
> +static char **args_completion(const struct bt_shell_menu_entry *entry, int argc,
> +                                                       const char *text)
> +{
> +       char **matches = NULL;
> +       char *str;
> +       int index;
> +
> +       index = text[0] == '\0' ? argc - 1 : argc - 2;
> +       if (index < 0)
> +               return NULL;
> +
> +       if (!entry->arg)
> +               goto end;
> +
> +       str = strdup(entry->arg);
> +
> +       if (parse_args(str, &args, "<>[]", WRDE_NOCMD))
> +               goto done;
> +
> +       /* Check if argument is valid */
> +       if ((unsigned int) index > args.we_wordc - 1)
> +               goto done;
> +
> +       /* Check if there are multiple values */
> +       if (!strrchr(entry->arg, '/'))
> +               goto done;
> +
> +       free(str);
> +
> +       /* Split values separated by / */
> +       str = strdelimit(args.we_wordv[index], "/", ' ');
> +
> +       args.we_offs = 0;
> +       wordfree(&args);
> +
> +       if (wordexp(str, &args, WRDE_NOCMD))
> +               goto done;
> +
> +       rl_completion_display_matches_hook = NULL;
> +       matches = rl_completion_matches(text, arg_generator);
> +
> +done:
> +       free(str);
> +end:
> +       if (!matches && text[0] == '\0')
> +               bt_shell_printf("Usage: %s %s\n", entry->cmd,
> +                                       entry->arg ? entry->arg : "");
> +
> +       args.we_offs = 0;
> +       wordfree(&args);
> +       return matches;
> +}
> +
> +static char **menu_completion(const struct bt_shell_menu_entry *entry,
> +                               const char *text, int argc, char *input_cmd)
> +{
> +       char **matches = NULL;
> +
> +       for (; entry->cmd; entry++) {
> +               if (strcmp(entry->cmd, input_cmd))
> +                       continue;
> +
> +               if (!entry->gen) {
> +                       matches = args_completion(entry, argc, text);
> +                       break;
> +               }
> +
> +               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) {
> +               wordexp_t w;
> +
> +               if (wordexp(rl_line_buffer, &w, WRDE_NOCMD))
> +                       return NULL;
> +
> +               matches = menu_completion(default_menu, text, w.we_wordc,
> +                                                       w.we_wordv[0]);
> +               if (!matches)
> +                       matches = menu_completion(data.menu->entries, text,
> +                                                       w.we_wordc,
> +                                                       w.we_wordv[0]);
> +
> +               wordfree(&w);
> +       } else {
> +               rl_completion_display_matches_hook = NULL;
> +               matches = rl_completion_matches(text, cmd_generator);
> +       }
> +
> +       if (!matches)
> +               rl_attempted_completion_over = 1;
> +
> +       return matches;
> +}
> +
> +static void io_hup(struct l_io *io, void *user_data)
> +{
> +       l_main_quit();
> +}
> +
> +static void signal_callback(unsigned int signum, void *user_data)
> +{
> +       static bool terminated;
> +
> +       switch (signum) {
> +       case SIGINT:
> +               if (data.input && !data.mode) {
> +                       rl_replace_line("", 0);
> +                       rl_crlf();
> +                       rl_on_new_line();
> +                       rl_redisplay();
> +                       return;
> +               }
> +
> +               /*
> +                * 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) {
> +                       if (!data.mode) {
> +                               rl_replace_line("", 0);
> +                               rl_crlf();
> +                       }
> +                       l_main_quit();
> +               }
> +
> +               terminated = true;
> +               break;
> +       }
> +}
> +
> +static void rl_init_history(void)
> +{
> +       const char *name;
> +       char *dir;
> +
> +       memset(data.history, 0, sizeof(data.history));
> +
> +       name = strrchr(data.name, '/');
> +       if (!name)
> +               name = data.name;
> +       else
> +               name++;
> +
> +       dir = getenv("XDG_CACHE_HOME");
> +       if (dir) {
> +               snprintf(data.history, sizeof(data.history), "%s/.%s_history",
> +                                                       dir, name);
> +               goto done;
> +       }
> +
> +       dir = getenv("HOME");
> +       if (dir) {
> +               snprintf(data.history, sizeof(data.history),
> +                               "%s/.cache/.%s_history", dir, name);
> +               goto done;
> +       }
> +
> +       dir = getenv("PWD");
> +       if (dir) {
> +               snprintf(data.history, sizeof(data.history), "%s/.%s_history",
> +                                                       dir, name);
> +               goto done;
> +       }
> +
> +       return;
> +
> +done:
> +       read_history(data.history);
> +       using_history();
> +       bt_shell_set_env("HISTORY", data.history);
> +}
> +
> +static void rl_init(void)
> +{
> +       if (data.mode)
> +               return;
> +
> +       /* Allow conditional parsing of the ~/.inputrc file. */
> +       rl_readline_name = data.name;
> +
> +       setlinebuf(stdout);
> +       rl_attempted_completion_function = shell_completion;
> +
> +       rl_erase_empty_line = 1;
> +       rl_callback_handler_install(NULL, rl_handler);
> +
> +       rl_init_history();
> +}
> +
> +static const struct option main_options[] = {
> +       { "version",    no_argument, 0, 'v' },
> +       { "help",       no_argument, 0, 'h' },
> +       { "timeout",    required_argument, 0, 't' },
> +       { "monitor",    no_argument, 0, 'm' },
> +};
> +
> +static void usage(int argc, char **argv, const struct bt_shell_opt *opt)
> +{
> +       unsigned int i;
> +
> +       printf("%s ver %s\n", data.name, VERSION);
> +       printf("Usage:\n"
> +               "\t%s [--options] [commands]\n", data.name);
> +
> +       printf("Options:\n");
> +
> +       for (i = 0; opt && opt->options[i].name; i++)
> +               printf("\t--%s \t%s\n", opt->options[i].name, opt->help[i]);
> +
> +       printf("\t--monitor \tEnable monitor output\n"
> +               "\t--timeout \tTimeout in seconds for non-interactive mode\n"
> +               "\t--version \tDisplay version\n"
> +               "\t--help \t\tDisplay help\n");
> +}
> +
> +void bt_shell_init(int argc, char **argv, const struct bt_shell_opt *opt)
> +{
> +       int c, index = -1;
> +       struct option options[256];
> +       char optstr[256];
> +       size_t offset;
> +
> +       offset = sizeof(main_options) / sizeof(struct option);
> +
> +       memcpy(options, main_options, sizeof(struct option) * offset);
> +
> +       if (opt) {
> +               memcpy(options + offset, opt->options,
> +                               sizeof(struct option) * opt->optno);
> +               snprintf(optstr, sizeof(optstr), "+mhvt:%s", opt->optstr);
> +       } else
> +               snprintf(optstr, sizeof(optstr), "+mhvt:");
> +
> +       data.name = strrchr(argv[0], '/');
> +       if (!data.name)
> +               data.name = strdup(argv[0]);
> +       else
> +               data.name = strdup(++data.name);
> +
> +       while ((c = getopt_long(argc, argv, optstr, options, &index)) != -1) {
> +               switch (c) {
> +               case 'v':
> +                       printf("%s: %s\n", data.name, VERSION);
> +                       exit(EXIT_SUCCESS);
> +                       return;
> +               case 'h':
> +                       usage(argc, argv, opt);
> +                       data.argc = 1;
> +                       data.argv = &cmplt;
> +                       data.mode = 1;
> +                       goto done;
> +               case 't':
> +                       data.timeout = atoi(optarg);
> +                       break;
> +               case 'm':
> +                       data.monitor = true;
> +                       if (bt_log_open() < 0) {
> +                               data.monitor = false;
> +                               printf("Unable to open logging channel\n");
> +                       }
> +                       break;
> +               default:
> +                       if (index < 0) {
> +                               for (index = 0; options[index].val; index++) {
> +                                       if (c == options[index].val)
> +                                               break;
> +                               }
> +                       }
> +
> +                       if (c != opt->options[index - offset].val) {
> +                               usage(argc, argv, opt);
> +                               exit(EXIT_SUCCESS);
> +                               return;
> +                       }
> +
> +                       *opt->optarg[index - offset] = optarg;
> +               }
> +
> +               index = -1;
> +       }
> +
> +       bt_shell_set_env("SHELL", data.name);
> +
> +       data.argc = argc - optind;
> +       data.argv = argv + optind;
> +       optind = 0;
> +       data.mode = (data.argc > 0);
> +
> +done:
> +       if (data.mode)
> +               bt_shell_set_env("NON_INTERACTIVE", &data.mode);
> +
> +       l_main_init();
> +
> +       rl_init();
> +
> +       data.init = true;
> +       data.prompts = l_queue_new();
> +}
> +
> +static void rl_cleanup(void)
> +{
> +       if (data.mode)
> +               return;
> +
> +       if (data.history[0] != '\0')
> +               write_history(data.history);
> +
> +       rl_message("");
> +       rl_callback_handler_remove();
> +}
> +
> +static void env_destroy(void *data)
> +{
> +       struct bt_shell_env *env = data;
> +
> +       free(env->name);
> +       free(env);
> +}
> +
> +int bt_shell_run(void)
> +{
> +       int status;
> +
> +       status = l_main_run_with_signal(signal_callback, NULL);
> +
> +       bt_shell_cleanup();
> +
> +       l_main_exit();
> +
> +       return status;
> +}
> +
> +void bt_shell_cleanup(void)
> +{
> +       bt_shell_release_prompt("");
> +       bt_shell_detach();
> +
> +       if (data.envs) {
> +               l_queue_destroy(data.envs, env_destroy);
> +               data.envs = NULL;
> +       }
> +
> +       if (data.monitor)
> +               bt_log_close();
> +
> +       rl_cleanup();
> +
> +       l_queue_destroy(data.prompts, prompt_free);
> +       data.prompts = NULL;
> +
> +       data.init = false;
> +       free(data.name);
> +}
> +
> +void bt_shell_quit(int status)
> +{
> +       l_main_quit();
> +}
> +
> +void bt_shell_noninteractive_quit(int status)
> +{
> +       if (!data.mode || data.timeout)
> +               return;
> +
> +       bt_shell_quit(status);
> +}
> +
> +bool bt_shell_set_menu(const struct bt_shell_menu *menu)
> +{
> +       if (!menu)
> +               return false;
> +
> +       data.menu = menu;
> +
> +       if (!data.main)
> +               data.main = menu;
> +
> +       return true;
> +}
> +
> +bool bt_shell_add_submenu(const struct bt_shell_menu *menu)
> +{
> +       if (!menu)
> +               return false;
> +
> +       if (!data.submenus)
> +               data.submenus = l_queue_new();
> +
> +       l_queue_push_tail(data.submenus, (void *) menu);
> +
> +       return true;
> +}
> +
> +void bt_shell_set_prompt(const char *string)
> +{
> +       if (!data.init || data.mode)
> +               return;
> +
> +       rl_set_prompt(string);
> +       rl_redisplay();
> +}
> +
> +static bool input_read(struct l_io *io, void *user_data)
> +{
> +       rl_callback_read_char();
> +       return true;
> +}
> +
> +static void quit_on_timeout(struct l_timeout *timeout, void *user_data)
> +{
> +       l_main_quit();
> +}
> +
> +bool bt_shell_attach(int fd)
> +{
> +       struct l_io *io;
> +
> +       /* TODO: Allow more than one input? */
> +       if (data.input)
> +               return false;
> +
> +       io = l_io_new(fd);
> +
> +       if (!data.mode)
> +               l_io_set_read_handler(io, input_read, NULL, NULL);
> +
> +       l_io_set_disconnect_handler(io, io_hup, NULL, NULL);

There is an io abstraction to implement this, afaik most of the things
here are just copy+paste of shell.c just with a different mainloop and
IO while the io.h and mainloop.h are exactly to abstract these so I
wonder why you took this alternative.

> +       data.input = io;
> +
> +       if (data.mode) {
> +               if (shell_exec(data.argc, data.argv) < 0) {
> +                       bt_shell_noninteractive_quit(EXIT_FAILURE);
> +                       return true;
> +               }
> +
> +
> +               if (data.timeout)
> +                       l_timeout_create(data.timeout, quit_on_timeout, NULL,
> +                                                               NULL);
> +       }
> +
> +       return true;
> +}
> +
> +bool bt_shell_detach(void)
> +{
> +       if (!data.input)
> +               return false;
> +
> +       l_io_destroy(data.input);
> +       data.input = NULL;
> +
> +       return true;
> +}
> +
> +static bool match_env(const void *data, const void *user_data)
> +{
> +       const struct bt_shell_env *env = data;
> +       const char *name = user_data;
> +
> +       return !strcmp(env->name, name);
> +}
> +
> +void bt_shell_set_env(const char *name, void *value)
> +{
> +       struct bt_shell_env *env;
> +
> +       if (!data.envs) {
> +               if (!value)
> +                       return;
> +               data.envs = l_queue_new();
> +               goto done;
> +       }
> +
> +       env = l_queue_remove_if(data.envs, match_env, (void *) name);
> +       if (env)
> +               env_destroy(env);
> +
> +       /* Don't create an env if value is not set */
> +       if (!value)
> +               return;
> +
> +done:
> +       env = l_new(struct bt_shell_env, 1);
> +       env->name = strdup(name);
> +       env->value = value;
> +
> +       l_queue_push_tail(data.envs, env);
> +}
> +
> +void *bt_shell_get_env(const char *name)
> +{
> +       const struct bt_shell_env *env;
> +
> +       if (!data.envs)
> +               return NULL;
> +
> +       env = l_queue_find(data.envs, match_env, name);
> +       if (!env)
> +               return NULL;
> +
> +       return env->value;
> +}
> --
> 2.21.0
>


-- 
Luiz Augusto von Dentz

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH BlueZ 1/2] shared/shell: Add ell based shell implementation
  2019-08-07 11:18   ` Luiz Augusto von Dentz
@ 2019-08-07 19:27     ` Stotland, Inga
  0 siblings, 0 replies; 5+ messages in thread
From: Stotland, Inga @ 2019-08-07 19:27 UTC (permalink / raw)
  To: luiz.dentz; +Cc: linux-bluetooth, Gix, Brian

[-- Attachment #1: Type: text/plain, Size: 1191 bytes --]

Hi Luiz,

On Wed, 2019-08-07 at 14:18 +0300, Luiz Augusto von Dentz wrote:
> Hi Inga,
> 
> On Wed, Aug 7, 2019 at 9:47 AM Inga Stotland <inga.stotland@intel.com
> > wrote:
> > This adds the functionality of bt_shell that uses ell
> > mainloop.
> > "
> > +
> > +static void quit_on_timeout(struct l_timeout *timeout, void
> > *user_data)
> > +{
> > +       l_main_quit();
> > +}
> > +
> > +bool bt_shell_attach(int fd)
> > +{
> > +       struct l_io *io;
> > +
> > +       /* TODO: Allow more than one input? */
> > +       if (data.input)
> > +               return false;
> > +
> > +       io = l_io_new(fd);
> > +
> > +       if (!data.mode)
> > +               l_io_set_read_handler(io, input_read, NULL, NULL);
> > +
> > +       l_io_set_disconnect_handler(io, io_hup, NULL, NULL);
> 
> There is an io abstraction to implement this, afaik most of the
> things
> here are just copy+paste of shell.c just with a different mainloop
> and
> IO while the io.h and mainloop.h are exactly to abstract these so I
> wonder why you took this alternative.
> 

True. Much easier to add ell-based mainloop.c, same effect and
lightweight.

Regards,
Inga


[-- Attachment #2: smime.p7s --]
[-- Type: application/x-pkcs7-signature, Size: 3265 bytes --]

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, back to index

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-08-07  6:47 [PATCH BlueZ 0/2] ELL-based bt_shell implementation Inga Stotland
2019-08-07  6:47 ` [PATCH BlueZ 1/2] shared/shell: Add ell based shell implementation Inga Stotland
2019-08-07 11:18   ` Luiz Augusto von Dentz
2019-08-07 19:27     ` Stotland, Inga
2019-08-07  6:47 ` [PATCH BlueZ 2/2] shared/shell: Add "clear-history" API Inga Stotland

Linux-Bluetooth Archive on lore.kernel.org

Archives are clonable:
	git clone --mirror https://lore.kernel.org/linux-bluetooth/0 linux-bluetooth/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 linux-bluetooth linux-bluetooth/ https://lore.kernel.org/linux-bluetooth \
		linux-bluetooth@vger.kernel.org linux-bluetooth@archiver.kernel.org
	public-inbox-index linux-bluetooth


Newsgroup available over NNTP:
	nntp://nntp.lore.kernel.org/org.kernel.vger.linux-bluetooth


AGPL code for this site: git clone https://public-inbox.org/ public-inbox