[RFC PATCH v2] nft: autocomplete for libreadline

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

 



The lack for autocomplete in interactive mode is a bit annoying, and
there doesn't seem to be an easy way to support it using bash
completion. Here is a proposal to use bison to guess the next tokens.

Add support for autocomplete for libreadline CLI.
This patch uses the bison push parser to step through each token and
list all possible next tokens.

- First word autocomplete doesn't work, since the last token flex passes
  is TOKEN_EOF, and the parser is default state
- Number of special tokens are specifically skipped, and only symbols
  that are escaped by \" or \' are considered valid keywords

Changes since v1:
- Fix libtool version as suggested by Jan 
- Add YYSYMBOL_NUM to special token list

Signed-off-by: Sriram Yagnaraman <sriram.yagnaraman@xxxxxxxx>
---
 Make_global.am                 |   2 +-
 include/nftables/libnftables.h |   2 +
 include/parser.h               |   3 +
 src/cli.c                      |   5 +-
 src/libnftables.c              |  13 ++++
 src/libnftables.map            |   4 ++
 src/parser_bison.y             | 108 +++++++++++++++++++++++++++++++++
 7 files changed, 135 insertions(+), 2 deletions(-)

diff --git a/Make_global.am b/Make_global.am
index 5bb541f6..67216a2c 100644
--- a/Make_global.am
+++ b/Make_global.am
@@ -18,4 +18,4 @@
 # set age to 0.
 # </snippet>
 #
-libnftables_LIBVERSION=2:0:1
+libnftables_LIBVERSION=3:0:2
diff --git a/include/nftables/libnftables.h b/include/nftables/libnftables.h
index 85e08c9b..57f8be64 100644
--- a/include/nftables/libnftables.h
+++ b/include/nftables/libnftables.h
@@ -92,6 +92,8 @@ void nft_ctx_clear_vars(struct nft_ctx *ctx);
 int nft_run_cmd_from_buffer(struct nft_ctx *nft, const char *buf);
 int nft_run_cmd_from_filename(struct nft_ctx *nft, const char *filename);
 
+char **nft_get_expected_tokens(struct nft_ctx *nft, const char *line, const char *text);
+
 #ifdef __cplusplus
 } /* extern "C" */
 #endif
diff --git a/include/parser.h b/include/parser.h
index f79a22f3..cec3a8f8 100644
--- a/include/parser.h
+++ b/include/parser.h
@@ -98,6 +98,9 @@ extern void parser_init(struct nft_ctx *nft, struct parser_state *state,
 			struct scope *top_scope);
 extern int nft_parse(struct nft_ctx *ctx, void *, struct parser_state *state);
 
+extern char **expected_matches (struct nft_ctx *nft, struct parser_state *state,
+				const char *text);
+
 extern void *scanner_init(struct parser_state *state);
 extern void scanner_destroy(struct nft_ctx *nft);
 
diff --git a/src/cli.c b/src/cli.c
index 11fc85ab..ffd7bf61 100644
--- a/src/cli.c
+++ b/src/cli.c
@@ -158,7 +158,10 @@ static void cli_complete(char *line)
 
 static char **cli_completion(const char *text, int start, int end)
 {
-	return NULL;
+	char *line = strndup(rl_line_buffer, (size_t)start);
+
+	rl_attempted_completion_over = 1;
+	return nft_get_expected_tokens(cli_nft, line, text);
 }
 
 int cli_init(struct nft_ctx *nft)
diff --git a/src/libnftables.c b/src/libnftables.c
index 4f538c44..a85cb155 100644
--- a/src/libnftables.c
+++ b/src/libnftables.c
@@ -555,6 +555,19 @@ static int nft_evaluate(struct nft_ctx *nft, struct list_head *msgs,
 	return 0;
 }
 
+EXPORT_SYMBOL(nft_get_expected_tokens);
+char **nft_get_expected_tokens(struct nft_ctx *nft, const char *line, const char *text)
+{
+	LIST_HEAD(msgs);
+	LIST_HEAD(cmds);
+
+	parser_init(nft, nft->state, &msgs, &cmds, nft->top_scope);
+	nft->scanner = scanner_init(nft->state);
+	scanner_push_buffer(nft->scanner, &indesc_cmdline, line);
+
+	return expected_matches(nft, nft->state, text);
+}
+
 EXPORT_SYMBOL(nft_run_cmd_from_buffer);
 int nft_run_cmd_from_buffer(struct nft_ctx *nft, const char *buf)
 {
diff --git a/src/libnftables.map b/src/libnftables.map
index a46a3ad5..9e4d8fcd 100644
--- a/src/libnftables.map
+++ b/src/libnftables.map
@@ -33,3 +33,7 @@ LIBNFTABLES_3 {
   nft_ctx_set_optimize;
   nft_ctx_get_optimize;
 } LIBNFTABLES_2;
+
+LIBNFTABLES_4 {
+  nft_get_expected_tokens;
+} LIBNFTABLES_3;
\ No newline at end of file
diff --git a/src/parser_bison.y b/src/parser_bison.y
index e4f21ca1..6d1c2374 100644
--- a/src/parser_bison.y
+++ b/src/parser_bison.y
@@ -13,6 +13,7 @@
 #include <ctype.h>
 #include <stddef.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <inttypes.h>
 #include <syslog.h>
 #include <netinet/ip.h>
@@ -160,6 +161,7 @@ int nft_lex(void *, void *, void *);
 %name-prefix "nft_"
 %debug
 %define api.pure
+%define api.push-pull both
 %parse-param		{ struct nft_ctx *nft }
 %parse-param		{ void *scanner }
 %parse-param		{ struct parser_state *state }
@@ -6215,3 +6217,109 @@ exthdr_key		:	HBH	close_scope_hbh	{ $$ = IPPROTO_HOPOPTS; }
 			;
 
 %%
+
+static int expected_tokens(struct nft_ctx *nft, struct parser_state *state,
+		    int *tokens, int ntokens)
+{
+	int res = 0;
+	yypstate *pstate = yypstate_new();
+	YYLTYPE yylloc;
+	int yystatus;
+
+	location_init(nft->scanner, state, &yylloc);
+	do {
+		YYSTYPE yylval;
+		int token = nft_lex(&yylval, &yylloc, nft->scanner);
+		// Don't let the parse know when we reach the end of input.
+		if (token == TOKEN_EOF)
+			break;
+		yystatus = nft_push_parse(pstate, token, &yylval, &yylloc, nft, nft->scanner, state);
+	} while (yystatus == YYPUSH_MORE);
+
+	if (!pstate->yynerrs)
+	{
+		res = yypstate_expected_tokens(pstate, tokens, ntokens);
+	}
+
+	yypstate_delete(pstate);
+	return res;
+}
+
+char **expected_matches (struct nft_ctx *nft, struct parser_state *state,
+			 const char *text)
+{
+	const size_t len = strlen(text);
+	int match = 1;
+	char **matches = NULL;
+	bool special_token = false;
+	const int special_tokens[] = {
+		YYSYMBOL_YYEOF,
+		YYSYMBOL_YYUNDEF,
+		YYSYMBOL_JUNK,
+		YYSYMBOL_NEWLINE,
+		YYSYMBOL_COLON,
+		YYSYMBOL_SEMICOLON,
+		YYSYMBOL_COMMA,
+		YYSYMBOL_NUM,
+		YYSYMBOL_STRING,
+		YYSYMBOL_QUOTED_STRING,
+		YYSYMBOL_ASTERISK_STRING,
+		YYSYMBOL_LAST
+	};
+	int tokens[YYNTOKENS];
+	int ntokens = expected_tokens(nft, state, tokens, YYNTOKENS);
+
+	if (ntokens == 0)
+		goto out;
+
+	// Need initial prefix and final NULL.
+	matches = calloc((size_t)ntokens + 2, sizeof(*matches));
+	if (!matches)
+		goto out;
+
+	for (int i = 0; i < ntokens; ++i) {
+		const char* token = yysymbol_name(tokens[i]);
+		const char* unescaped_token = NULL;
+
+		for (size_t j = 0; j < sizeof(special_tokens)/sizeof(special_tokens[0]); ++j) {
+			if (tokens[i] == special_tokens[j]) {
+				special_token = true;
+				break;
+			}
+		}
+
+		if (special_token) {
+			special_token = false;
+			continue;
+		}
+
+		/* Only match tokens that are escaped by \" or \' */
+		if (token[0] == '"' || token[0] == '\'') {
+			unescaped_token = strndup(token + 1, strlen(token) - 2);
+		}
+
+
+		if (unescaped_token && (!len ||
+			strncmp(text, unescaped_token, len) == 0)) {
+			matches[match++] = strdup(unescaped_token);
+		}
+	}
+
+	// Find the longest common prefix, and install it in matches[0], as
+	// required by readline.
+	if (match == 1) {
+		free(matches);
+		return NULL;
+	} else {
+		size_t lcplen = strlen(matches[1]);
+		for (int i = 2; i < match && lcplen; ++i)
+			for (size_t j = 0; j < lcplen; ++j)
+				if (matches[1][j] != matches[i][j])
+					lcplen = j;
+		matches[0] = strdup(matches[1]);
+		matches[0][lcplen] = '\0';
+	}
+
+out:
+	return matches;
+}
-- 
2.34.1




[Index of Archives]     [Netfitler Users]     [Berkeley Packet Filter]     [LARTC]     [Bugtraq]     [Yosemite Forum]

  Powered by Linux