Browse Source

wpa_cli: Add internal line edit implementation

CONFIG_WPA_CLI_EDIT=y can now be used to build wpa_cli with internal
implementation of line editing and history support. This can be used
as a replacement for CONFIG_READLINE=y.
Jouni Malinen 14 years ago
parent
commit
aee680e8b2
3 changed files with 510 additions and 4 deletions
  1. 5 1
      wpa_supplicant/Makefile
  2. 4 0
      wpa_supplicant/defconfig
  3. 501 3
      wpa_supplicant/wpa_cli.c

+ 5 - 1
wpa_supplicant/Makefile

@@ -50,6 +50,7 @@ OBJS_p += ../src/utils/common.o
 OBJS_p += ../src/utils/wpa_debug.o
 OBJS_p += ../src/utils/wpabuf.o
 OBJS_c = wpa_cli.o ../src/common/wpa_ctrl.o
+OBJS_c += ../src/utils/wpa_debug.o
 
 -include .config
 
@@ -74,7 +75,6 @@ CFLAGS += -DWPA_TRACE
 OBJS += ../src/utils/trace.o
 OBJS_p += ../src/utils/trace.o
 OBJS_c += ../src/utils/trace.o
-OBJS_c += ../src/utils/wpa_debug.o
 LDFLAGS += -rdynamic
 CFLAGS += -funwind-tables
 ifdef CONFIG_WPA_TRACE_BFD
@@ -1150,6 +1150,10 @@ CFLAGS += -DCONFIG_READLINE
 LIBS_c += -lncurses -lreadline
 endif
 
+ifdef CONFIG_WPA_CLI_EDIT
+CFLAGS += -DCONFIG_WPA_CLI_EDIT
+endif
+
 ifdef CONFIG_NATIVE_WINDOWS
 CFLAGS += -DCONFIG_NATIVE_WINDOWS
 LIBS += -lws2_32 -lgdi32 -lcrypt32

+ 4 - 0
wpa_supplicant/defconfig

@@ -230,6 +230,10 @@ CONFIG_CTRL_IFACE=y
 # the resulting binary.
 #CONFIG_READLINE=y
 
+# Include internal line edit mode in wpa_cli. This can be used as a replacement
+# for GNU Readline to provide limited command line editing and history support.
+#CONFIG_WPA_CLI_EDIT=y
+
 # Remove debugging code that is printing out debug message to stdout.
 # This can be used to reduce the size of the wpa_supplicant considerably
 # if debugging code is not needed. The size reduction can be around 35%

+ 501 - 3
wpa_supplicant/wpa_cli.c

@@ -13,6 +13,9 @@
  */
 
 #include "includes.h"
+#ifdef CONFIG_WPA_CLI_EDIT
+#include <termios.h>
+#endif /* CONFIG_WPA_CLI_EDIT */
 
 #ifdef CONFIG_CTRL_IFACE
 
@@ -100,9 +103,17 @@ static const char *action_file = NULL;
 static int ping_interval = 5;
 static int interactive = 0;
 #ifndef CONFIG_READLINE
-static char cmdbuf[256];
+#define CMD_BUF_LEN 256
+static char cmdbuf[CMD_BUF_LEN];
 static int cmdbuf_pos = 0;
+static int cmdbuf_len = 0;
 #endif /* CONFIG_READLINE */
+#ifdef CONFIG_WPA_CLI_EDIT
+#define CMD_HISTORY_LEN 20
+static char history_buf[CMD_HISTORY_LEN][CMD_BUF_LEN];
+static int history_pos = 0;
+static int history_current = 0;
+#endif /* CONFIG_WPA_CLI_EDIT */
 
 
 static void print_help(void);
@@ -133,8 +144,15 @@ static void readline_redraw()
 	rl_on_new_line();
 	rl_redisplay();
 #else /* CONFIG_READLINE */
-	cmdbuf[cmdbuf_pos] = '\0';
+	char tmp;
+	cmdbuf[cmdbuf_len] = '\0';
 	printf("\r> %s", cmdbuf);
+	if (cmdbuf_pos != cmdbuf_len) {
+		tmp = cmdbuf[cmdbuf_pos];
+		cmdbuf[cmdbuf_pos] = '\0';
+		printf("\r> %s", cmdbuf);
+		cmdbuf[cmdbuf_pos] = tmp;
+	}
 	fflush(stdout);
 #endif /* CONFIG_READLINE */
 }
@@ -2904,12 +2922,472 @@ static void wpa_cli_interactive(void)
 
 #else /* CONFIG_READLINE */
 
+#ifdef CONFIG_WPA_CLI_EDIT
+
+static void wpa_cli_clear_line(void)
+{
+	int i;
+	putchar('\r');
+	for (i = 0; i < cmdbuf_len + 2; i++)
+		putchar(' ');
+}
+
+
+static void move_start(void)
+{
+	cmdbuf_pos = 0;
+	readline_redraw();
+}
+
+
+static void move_end(void)
+{
+	cmdbuf_pos = cmdbuf_len;
+	readline_redraw();
+}
+
+
+static void move_left(void)
+{
+	if (cmdbuf_pos > 0) {
+		cmdbuf_pos--;
+		readline_redraw();
+	}
+}
+
+
+static void move_right(void)
+{
+	if (cmdbuf_pos < cmdbuf_len) {
+		cmdbuf_pos++;
+		readline_redraw();
+	}
+}
+
+
+static void move_word_left(void)
+{
+	while (cmdbuf_pos > 0 && cmdbuf[cmdbuf_pos - 1] == ' ')
+		cmdbuf_pos--;
+	while (cmdbuf_pos > 0 && cmdbuf[cmdbuf_pos - 1] != ' ')
+		cmdbuf_pos--;
+	readline_redraw();
+}
+
+
+static void move_word_right(void)
+{
+	while (cmdbuf_pos < cmdbuf_len && cmdbuf[cmdbuf_pos] == ' ')
+		cmdbuf_pos++;
+	while (cmdbuf_pos < cmdbuf_len && cmdbuf[cmdbuf_pos] != ' ')
+		cmdbuf_pos++;
+	readline_redraw();
+}
+
+
+static void delete_left(void)
+{
+	if (cmdbuf_pos == 0)
+		return;
+
+	wpa_cli_clear_line();
+	os_memmove(cmdbuf + cmdbuf_pos - 1, cmdbuf + cmdbuf_pos,
+		   cmdbuf_len - cmdbuf_pos);
+	cmdbuf_pos--;
+	cmdbuf_len--;
+	readline_redraw();
+}
+
+
+static void delete_current(void)
+{
+	if (cmdbuf_pos == cmdbuf_len)
+		return;
+
+	wpa_cli_clear_line();
+	os_memmove(cmdbuf + cmdbuf_pos, cmdbuf + cmdbuf_pos + 1,
+		   cmdbuf_len - cmdbuf_pos);
+	cmdbuf_len--;
+	readline_redraw();
+}
+
+
+static void delete_word(void)
+{
+	wpa_cli_clear_line();
+	while (cmdbuf_len > 0 && cmdbuf[cmdbuf_len - 1] == ' ')
+		cmdbuf_len--;
+	while (cmdbuf_len > 0 && cmdbuf[cmdbuf_len - 1] != ' ')
+		cmdbuf_len--;
+	readline_redraw();
+}
+
+
+static void clear_left(void)
+{
+	if (cmdbuf_pos == 0)
+		return;
+
+	wpa_cli_clear_line();
+	os_memmove(cmdbuf, cmdbuf + cmdbuf_pos, cmdbuf_len - cmdbuf_pos);
+	cmdbuf_len -= cmdbuf_pos;
+	cmdbuf_pos = 0;
+	readline_redraw();
+}
+
+
+static void clear_right(void)
+{
+	if (cmdbuf_pos == cmdbuf_len)
+		return;
+
+	wpa_cli_clear_line();
+	cmdbuf_len = cmdbuf_pos;
+	readline_redraw();
+}
+
+
+static void history_add(const char *str)
+{
+	int prev;
+
+	if (str[0] == '\0')
+		return;
+
+	if (history_pos == 0)
+		prev = CMD_HISTORY_LEN - 1;
+	else
+		prev = history_pos - 1;
+	if (os_strcmp(history_buf[prev], str) == 0)
+		return;
+
+	os_strlcpy(history_buf[history_pos], str, CMD_BUF_LEN);
+	history_pos++;
+	if (history_pos == CMD_HISTORY_LEN)
+		history_pos = 0;
+	history_current = history_pos;
+}
+
+
+static void history_prev(void)
+{
+	int pos;
+
+	if (history_current == (history_pos + 1) % CMD_HISTORY_LEN)
+		return;
+
+	pos = history_current;
+
+	if (history_current == history_pos && cmdbuf_len) {
+		cmdbuf[cmdbuf_len] = '\0';
+		history_add(cmdbuf);
+	}
+
+	if (pos > 0)
+		pos--;
+	else
+		pos = CMD_HISTORY_LEN - 1;
+	if (history_buf[pos][0] == '\0')
+		return;
+	history_current = pos;
+
+	wpa_cli_clear_line();
+	cmdbuf_len = cmdbuf_pos = os_strlen(history_buf[history_current]);
+	os_memcpy(cmdbuf, history_buf[history_current], cmdbuf_len);
+	readline_redraw();
+}
+
+
+static void history_next(void)
+{
+	if (history_current == history_pos)
+		return;
+
+	history_current++;
+	if (history_current == CMD_HISTORY_LEN)
+	    history_current = 0;
+
+	wpa_cli_clear_line();
+	cmdbuf_len = cmdbuf_pos = os_strlen(history_buf[history_current]);
+	os_memcpy(cmdbuf, history_buf[history_current], cmdbuf_len);
+	readline_redraw();
+}
+
+
+static void history_debug_dump(void)
+{
+	int p;
+	wpa_cli_clear_line();
+	printf("\r");
+	p = (history_pos + 1) % CMD_HISTORY_LEN;
+	for (;;) {
+		printf("[%d%s%s] %s\n",
+		       p, p == history_current ? "C" : "",
+		       p == history_pos ? "P" : "", history_buf[p]);
+		if (p == history_pos)
+			break;
+		p++;
+		if (p == CMD_HISTORY_LEN)
+			p = 0;
+	}
+	readline_redraw();
+}
+
+
+static void insert_char(int c)
+{
+	if (c < 32 && c > 255) {
+		printf("[%d]\n", c);
+		readline_redraw();
+	}
+
+	if (cmdbuf_len >= (int) sizeof(cmdbuf) - 1)
+		return;
+	if (cmdbuf_len == cmdbuf_pos) {
+		cmdbuf[cmdbuf_pos++] = c;
+		cmdbuf_len++;
+		putchar(c);
+		fflush(stdout);
+	} else {
+		os_memmove(cmdbuf + cmdbuf_pos + 1, cmdbuf + cmdbuf_pos,
+			   cmdbuf_len - cmdbuf_pos);
+		cmdbuf[cmdbuf_pos++] = c;
+		cmdbuf_len++;
+		readline_redraw();
+	}
+}
+
+
+static void process_cmd(void)
+{
+	char *argv[max_args];
+	int argc;
+
+	if (cmdbuf_len == 0) {
+		printf("\n> ");
+		fflush(stdout);
+		return;
+	}
+	printf("\n");
+	cmdbuf[cmdbuf_len] = '\0';
+	history_add(cmdbuf);
+	cmdbuf_pos = 0;
+	cmdbuf_len = 0;
+	trunc_nl(cmdbuf);
+	argc = tokenize_cmd(cmdbuf, argv);
+	if (argc)
+		wpa_request(ctrl_conn, argc, argv);
+	printf("> ");
+	fflush(stdout);
+}
+
+
+static void wpa_cli_read_char(int sock, void *eloop_ctx, void *sock_ctx)
+{
+	int c;
+	unsigned char buf[1];
+	int res;
+	static int esc = -1;
+	static char esc_buf[6];
+
+	res = read(sock, buf, 1);
+	if (res < 0)
+		perror("read");
+	if (res <= 0) {
+		eloop_terminate();
+		return;
+	}
+	c = buf[0];
+
+	if (esc >= 0) {
+		if (esc == 5) {
+			printf("{ESC%s}[0]\n", esc_buf);
+			readline_redraw();
+			esc = -1;
+		} else {
+			esc_buf[esc++] = c;
+			esc_buf[esc] = '\0';
+			if (esc == 1)
+				return;
+		}
+	}
+
+	if (esc == 2 && esc_buf[0] == '[' && c >= 'A' && c <= 'Z') {
+		switch (c) {
+		case 'A': /* up */
+			history_prev();
+			break;
+		case 'B': /* down */
+			history_next();
+			break;
+		case 'C': /* right */
+			move_right();
+			break;
+		case 'D': /* left */
+			move_left();
+			break;
+		case 'F': /* end */
+			move_end();
+			break;
+		case 'H': /* home */
+			move_start();
+			break;
+		default:
+			printf("{ESC%s}[1]\n", esc_buf);
+			readline_redraw();
+			break;
+		}
+		esc = -1;
+		return;
+	}
+
+	if (esc > 1 && esc_buf[0] == '[') {
+		if ((c >= '0' && c <= '9') || c == ';')
+			return;
+
+		if (esc_buf[1] == '1' && esc_buf[2] == ';' &&
+		    esc_buf[3] == '5') {
+			switch (esc_buf[4]) {
+			case 'A': /* Ctrl-Up */
+			case 'B': /* Ctrl-Down */
+				break;
+			case 'C': /* Ctrl-Right */
+				move_word_right();
+				break;
+			case 'D': /* Ctrl-Left */
+				move_word_left();
+				break;
+			default:
+				printf("{ESC%s}[2]\n", esc_buf);
+				readline_redraw();
+				break;
+			}
+			esc = -1;
+			return;
+		}
+
+		switch (c) {
+		case '~':
+			switch (atoi(&esc_buf[1])) {
+			case 2: /* Insert */
+				break;
+			case 3: /* Delete */
+				delete_current();
+				break;
+			case 5: /* Page Up */
+			case 6: /* Page Down */
+			case 15: /* F5 */
+			case 17: /* F6 */
+			case 18: /* F7 */
+			case 19: /* F8 */
+			case 20: /* F9 */
+			case 21: /* F10 */
+			case 23: /* F11 */
+			case 24: /* F12 */
+				break;
+			default:
+				printf("{ESC%s}[3]\n", esc_buf);
+				readline_redraw();
+				break;
+			}
+			break;
+		default:
+			printf("{ESC%s}[4]\n", esc_buf);
+			readline_redraw();
+			break;
+		}
+
+		esc = -1;
+		return;
+	}
+
+	if (esc > 1 && esc_buf[0] == 'O') {
+		switch (esc_buf[1]) {
+		case 'P': /* F1 */
+			history_debug_dump();
+			break;
+		case 'Q': /* F2 */
+		case 'R': /* F3 */
+		case 'S': /* F4 */
+			break;
+		default:
+			printf("{ESC%s}[5]\n", esc_buf);
+			readline_redraw();
+			break;
+		}
+		esc = -1;
+		return;
+	}
+
+	if (esc > 1) {
+		printf("{ESC%s}[6]\n", esc_buf);
+		readline_redraw();
+		esc = -1;
+		return;
+	}
+
+	switch (c) {
+	case 1: /* ^A */
+		move_start();
+		break;
+	case 4: /* ^D */
+		if (cmdbuf_len > 0) {
+			delete_current();
+			return;
+		}
+		printf("\n");
+		eloop_terminate();
+		break;
+	case 5: /* ^E */
+		move_end();
+		break;
+	case 8: /* ^H = BS */
+		delete_left();
+		break;
+	case 9: /* ^I = TAB */
+		break;
+	case 10: /* NL */
+	case 13: /* CR */
+		process_cmd();
+		break;
+	case 11: /* ^K */
+		clear_right();
+		break;
+	case 14: /* ^N */
+		history_next();
+		break;
+	case 16: /* ^P */
+		history_prev();
+		break;
+	case 18: /* ^R */
+		/* TODO: search history */
+		break;
+	case 21: /* ^U */
+		clear_left();
+		break;
+	case 23: /* ^W */
+		delete_word();
+		break;
+	case 27: /* ESC */
+		esc = 0;
+		break;
+	case 127: /* DEL */
+		delete_left();
+		break;
+	default:
+		insert_char(c);
+		break;
+	}
+}
+
+#else /* CONFIG_WPA_CLI_EDIT */
+
 static void wpa_cli_read_char(int sock, void *eloop_ctx, void *sock_ctx)
 {
 	char *argv[max_args];
 	int argc;
 	int c;
-	char buf[1];
+	unsigned char buf[1];
 	int res;
 
 	res = read(sock, buf, 1);
@@ -2940,16 +3418,32 @@ static void wpa_cli_read_char(int sock, void *eloop_ctx, void *sock_ctx)
 	}
 }
 
+#endif /* CONFIG_WPA_CLI_EDIT */
+
 
 static void wpa_cli_interactive(void)
 {
+#ifdef CONFIG_WPA_CLI_EDIT
+	struct termios prevt, newt;
+#endif /* CONFIG_WPA_CLI_EDIT */
+
 	printf("\nInteractive mode\n\n");
 	cmdbuf_pos = 0;
+	cmdbuf_len = 0;
 
 	eloop_register_signal_terminate(wpa_cli_eloop_terminate, NULL);
 	eloop_register_read_sock(STDIN_FILENO, wpa_cli_read_char, NULL, NULL);
 	eloop_register_timeout(ping_interval, 0, wpa_cli_ping, NULL, NULL);
 
+#ifdef CONFIG_WPA_CLI_EDIT
+	os_memset(history_buf, 0, sizeof(history_buf));
+
+	tcgetattr(STDIN_FILENO, &prevt);
+	newt = prevt;
+	newt.c_lflag &= ~(ICANON | ECHO);
+	tcsetattr(STDIN_FILENO, TCSANOW, &newt);
+#endif /* CONFIG_WPA_CLI_EDIT */
+
 	printf("> ");
 	fflush(stdout);
 
@@ -2958,6 +3452,10 @@ static void wpa_cli_interactive(void)
 	eloop_unregister_read_sock(STDIN_FILENO);
 	eloop_cancel_timeout(wpa_cli_ping, NULL, NULL);
 	wpa_cli_close_connection();
+
+#ifdef CONFIG_WPA_CLI_EDIT
+	tcsetattr(STDIN_FILENO, TCSANOW, &prevt);
+#endif /* CONFIG_WPA_CLI_EDIT */
 }
 
 #endif /* CONFIG_READLINE */