diff mbox series

[v2,1/2] shared/shell: Add support for -i/--init-script

Message ID 20230322011349.2763404-1-luiz.dentz@gmail.com
State New
Headers show
Series [v2,1/2] shared/shell: Add support for -i/--init-script | expand

Commit Message

Luiz Augusto von Dentz March 22, 2023, 1:13 a.m. UTC
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

This adds support for -i/--init-script which can be used to provide a
file with commands to be initialized, the commands are then run in
sequence after completing:

client/bluetoothctl -i client/power-on-off.bt
Agent registered
Changing power on succeeded
[CHG] Controller A8:7E:EA:56:87:D5 Pairable: yes
[CHG] Controller 98:8D:46:EE:6D:16 Pairable: yes
[CHG] Controller 98:8D:46:EE:6D:16 PowerState: on-disabling
AdvertisementMonitor path registered
---
 src/shared/shell.c | 159 ++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 142 insertions(+), 17 deletions(-)

Comments

patchwork-bot+bluetooth@kernel.org March 25, 2023, midnight UTC | #1
Hello:

This series was applied to bluetooth/bluez.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:

On Tue, 21 Mar 2023 18:13:48 -0700 you wrote:
> From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
> 
> This adds support for -i/--init-script which can be used to provide a
> file with commands to be initialized, the commands are then run in
> sequence after completing:
> 
> client/bluetoothctl -i client/power-on-off.bt
> Agent registered
> Changing power on succeeded
> [CHG] Controller A8:7E:EA:56:87:D5 Pairable: yes
> [CHG] Controller 98:8D:46:EE:6D:16 Pairable: yes
> [CHG] Controller 98:8D:46:EE:6D:16 PowerState: on-disabling
> AdvertisementMonitor path registered
> 
> [...]

Here is the summary with links:
  - [v2,1/2] shared/shell: Add support for -i/--init-script
    (no matching commit)
  - [v2,2/2] client: Add samples init scripts
    https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=fb1c694100b2

You are awesome, thank you!
diff mbox series

Patch

diff --git a/src/shared/shell.c b/src/shared/shell.c
index 3358b383e9e4..8ea4cb355f63 100644
--- a/src/shared/shell.c
+++ b/src/shared/shell.c
@@ -24,6 +24,7 @@ 
 #include <sys/signalfd.h>
 #include <wordexp.h>
 #include <getopt.h>
+#include <fcntl.h>
 
 #include <readline/readline.h>
 #include <readline/history.h>
@@ -69,7 +70,12 @@  static struct {
 	bool zsh;
 	bool monitor;
 	int timeout;
-	struct io *input;
+	int init_fd;
+	FILE *f;
+	struct queue *inputs;
+
+	char *line;
+	struct queue *queue;
 
 	bool saved_prompt;
 	bt_shell_prompt_input_func saved_func;
@@ -535,7 +541,7 @@  void bt_shell_printf(const char *fmt, ...)
 	char *saved_line;
 	int saved_point;
 
-	if (!data.input)
+	if (queue_isempty(data.inputs))
 		return;
 
 	if (data.mode) {
@@ -615,6 +621,32 @@  void bt_shell_usage()
 					data.exec->arg ? data.exec->arg : "");
 }
 
+static void bt_shell_dequeue_exec(void)
+{
+	int err;
+
+	if (!data.line)
+		return;
+
+	free(data.line);
+	data.line = NULL;
+
+	data.line = queue_pop_head(data.queue);
+	if (!data.line)
+		return;
+
+	bt_shell_printf("%s\n", data.line);
+
+	if (!bt_shell_release_prompt(data.line)) {
+		bt_shell_dequeue_exec();
+		return;
+	}
+
+	err = bt_shell_exec(data.line);
+	if (err)
+		bt_shell_dequeue_exec();
+}
+
 static void prompt_input(const char *str, bt_shell_prompt_input_func func,
 							void *user_data)
 {
@@ -988,6 +1020,11 @@  static char **shell_completion(const char *text, int start, int end)
 
 static bool io_hup(struct io *io, void *user_data)
 {
+	if (queue_remove(data.inputs, io)) {
+		if (!queue_isempty(data.inputs))
+			return false;
+	}
+
 	mainloop_quit();
 
 	return false;
@@ -999,7 +1036,7 @@  static void signal_callback(int signum, void *user_data)
 
 	switch (signum) {
 	case SIGINT:
-		if (data.input && !data.mode) {
+		if (!queue_isempty(data.inputs) && !data.mode) {
 			rl_replace_line("", 0);
 			rl_crlf();
 			rl_on_new_line();
@@ -1091,6 +1128,7 @@  static void rl_init(void)
 static const struct option main_options[] = {
 	{ "version",	no_argument, 0, 'v' },
 	{ "help",	no_argument, 0, 'h' },
+	{ "init-script",required_argument, 0, 'i' },
 	{ "timeout",	required_argument, 0, 't' },
 	{ "monitor",	no_argument, 0, 'm' },
 	{ "zsh-complete",	no_argument, 0, 'z' },
@@ -1112,6 +1150,7 @@  static void usage(int argc, char **argv, const struct bt_shell_opt *opt)
 	printf("\t--monitor \tEnable monitor output\n"
 		"\t--timeout \tTimeout in seconds for non-interactive mode\n"
 		"\t--version \tDisplay version\n"
+		"\t--init-script \tInit script file\n"
 		"\t--help \t\tDisplay help\n");
 }
 
@@ -1130,9 +1169,9 @@  void bt_shell_init(int argc, char **argv, const struct bt_shell_opt *opt)
 	if (opt) {
 		memcpy(options + offset, opt->options,
 				sizeof(struct option) * opt->optno);
-		snprintf(optstr, sizeof(optstr), "+mhvt:%s", opt->optstr);
+		snprintf(optstr, sizeof(optstr), "+mhvi:t:%s", opt->optstr);
 	} else
-		snprintf(optstr, sizeof(optstr), "+mhvt:");
+		snprintf(optstr, sizeof(optstr), "+mhvi:t:");
 
 	data.name = strrchr(argv[0], '/');
 	if (!data.name)
@@ -1140,6 +1179,8 @@  void bt_shell_init(int argc, char **argv, const struct bt_shell_opt *opt)
 	else
 		data.name = strdup(++data.name);
 
+	data.init_fd = -1;
+
 	while ((c = getopt_long(argc, argv, optstr, options, &index)) != -1) {
 		switch (c) {
 		case 'v':
@@ -1152,6 +1193,13 @@  void bt_shell_init(int argc, char **argv, const struct bt_shell_opt *opt)
 			data.argv = &cmplt;
 			data.mode = 1;
 			goto done;
+		case 'i':
+			if (optarg)
+				data.init_fd = open(optarg, O_RDONLY);
+			if (data.init_fd < 0)
+				printf("Unable to open %s: %s (%d)\n", optarg,
+						strerror(errno), errno);
+			break;
 		case 't':
 			if (optarg)
 				data.timeout = strtol(optarg, &endptr, 0);
@@ -1205,6 +1253,8 @@  done:
 	rl_init();
 
 	data.init = true;
+	data.inputs = queue_new();
+	data.queue = queue_new();
 	data.prompts = queue_new();
 }
 
@@ -1239,6 +1289,25 @@  int bt_shell_run(void)
 	return status;
 }
 
+static int bt_shell_queue_exec(char *line)
+{
+	int err;
+
+	/* Queue if already executing */
+	if (data.line) {
+		queue_push_tail(data.queue, strdup(line));
+		return 0;
+	}
+
+	bt_shell_printf("%s\n", line);
+
+	err = bt_shell_exec(line);
+	if (!err)
+		data.line = strdup(line);
+
+	return err;
+}
+
 int bt_shell_exec(const char *input)
 {
 	wordexp_t w;
@@ -1247,8 +1316,20 @@  int bt_shell_exec(const char *input)
 	if (!input)
 		return 0;
 
-	if (wordexp(input, &w, WRDE_NOCMD))
-		return -ENOEXEC;
+	err = wordexp(input, &w, WRDE_NOCMD);
+	switch (err) {
+	case WRDE_BADCHAR:
+		return -EBADMSG;
+	case WRDE_BADVAL:
+	case WRDE_SYNTAX:
+		return -EINVAL;
+	case WRDE_NOSPACE:
+		return -ENOMEM;
+	case WRDE_CMDSUB:
+		if (wordexp(input, &w, 0))
+			return -ENOEXEC;
+		break;
+	};
 
 	if (w.we_wordc == 0) {
 		wordfree(&w);
@@ -1277,6 +1358,8 @@  void bt_shell_cleanup(void)
 
 	rl_cleanup();
 
+	queue_destroy(data.inputs, NULL);
+	queue_destroy(data.queue, free);
 	queue_destroy(data.prompts, prompt_free);
 	data.prompts = NULL;
 
@@ -1294,8 +1377,10 @@  void bt_shell_quit(int status)
 
 void bt_shell_noninteractive_quit(int status)
 {
-	if (!data.mode || data.timeout)
+	if (!data.mode || data.timeout) {
+		bt_shell_dequeue_exec();
 		return;
+	}
 
 	bt_shell_quit(status);
 }
@@ -1340,7 +1425,43 @@  void bt_shell_set_prompt(const char *string)
 
 static bool input_read(struct io *io, void *user_data)
 {
-	rl_callback_read_char();
+	int fd;
+	char *line = NULL;
+	size_t len = 0;
+	ssize_t nread;
+
+	fd = io_get_fd(io);
+
+	if (fd == STDIN_FILENO) {
+		rl_callback_read_char();
+		return true;
+	}
+
+	if (!data.f) {
+		data.f = fdopen(fd, "r");
+		if (!data.f) {
+			printf("fdopen: %s (%d)\n", strerror(errno), errno);
+			return false;
+		}
+	}
+
+	nread = getline(&line, &len, data.f);
+	if (nread > 0) {
+		int err;
+
+		if (line[nread - 1] == '\n')
+			line[nread - 1] = '\0';
+
+		err = bt_shell_queue_exec(line);
+		if (err < 0)
+			printf("%s: %s (%d)\n", line, strerror(-err), -err);
+	} else {
+		fclose(data.f);
+		data.f = NULL;
+	}
+
+	free(line);
+
 	return true;
 }
 
@@ -1355,18 +1476,16 @@  bool bt_shell_attach(int fd)
 {
 	struct io *io;
 
-	/* TODO: Allow more than one input? */
-	if (data.input)
-		return false;
-
 	io = io_new(fd);
+	if (!io)
+		return false;
 
 	if (!data.mode) {
 		io_set_read_handler(io, input_read, NULL, NULL);
 		io_set_disconnect_handler(io, io_hup, NULL, NULL);
 	}
 
-	data.input = io;
+	queue_push_tail(data.inputs, io);
 
 	if (data.mode) {
 		if (shell_exec(data.argc, data.argv) < 0) {
@@ -1377,6 +1496,12 @@  bool bt_shell_attach(int fd)
 		if (data.timeout)
 			timeout_add(data.timeout * 1000, shell_quit, NULL,
 								NULL);
+	} else if (data.init_fd >= 0) {
+		int fd = data.init_fd;
+
+		data.init_fd = -1;
+		if (!bt_shell_attach(fd))
+			return false;
 	}
 
 	return true;
@@ -1384,11 +1509,11 @@  bool bt_shell_attach(int fd)
 
 bool bt_shell_detach(void)
 {
-	if (!data.input)
+	if (queue_isempty(data.inputs))
 		return false;
 
-	io_destroy(data.input);
-	data.input = NULL;
+	queue_remove_all(data.inputs, NULL, NULL,
+					(queue_destroy_func_t) io_destroy);
 
 	return true;
 }