[PATCH v8 05/12] Implement new filtering language parsing

Nikolay Marchuk marchuk.nikolay.a at gmail.com
Tue Aug 22 06:01:46 UTC 2017


* basic_filters.c (lookup_class, parse_syscall_class, parse_syscall,
parse_syscall_set, parse_syscall_filter, parse_set, parse_fd_filter,
parse_path_filter): Add qualify_mode argument.
(parse_set, parse_syscall_set): Use set inversion only in
qualify mode.
(lookup_class): Use deprecated class names only in qualify mode.
* filter.h (parse_filter_action, parse_qualify_action,
parse_filter_expression): Add new declarations.
(parse_filter, parse_set): Add qualify_mode argument.
* filter_action.c (parse_filter_action): Add new parsing function.
(filtering_parse_finish, inject_path_tracing): Use filtering_parse
instead of parse_qualify_filter.
* filter_expression.c (parse_filter_expression): Implement parsing of filter
expression.
(parse_operator, push_operator, is_higher_priority): Add helper functions.
(is_space_ascii, is_allowed_in_name): Add new definition.
* filter_parse.c: New file.
* filter_qualify.c (parse_read, parse_write, qualify_signals, parse_trace,
parse_abbrev, parse_verbose, parse_raw, parse_inject_common, parse_fault,
parse_inject): Use new parsing API.
* strace.c (init): Use filtering_parse instead of parse_qualify_filter.
* Makefile.am (strace_SOURCES): Add filter_parse.c.
* defs.h (filtering_parse): Add new definition.
---
 Makefile.am         |   1 +
 basic_filters.c     |  64 +++++++------
 defs.h              |   2 +-
 filter.c            |   8 +-
 filter.h            |   9 +-
 filter_action.c     |  14 ++-
 filter_expression.c | 188 +++++++++++++++++++++++++++++++++++++++
 filter_parse.c      | 252 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 filter_qualify.c    | 100 ++++++++++++---------
 strace.c            |   6 +-
 10 files changed, 563 insertions(+), 81 deletions(-)
 create mode 100644 filter_parse.c

diff --git a/Makefile.am b/Makefile.am
index d418a98d..1b08d9c3 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -136,6 +136,7 @@ strace_SOURCES =	\
 	file_ioctl.c	\
 	filter_action.c	\
 	filter_expression.c \
+	filter_parse.c	\
 	filter_qualify.c \
 	filter.c	\
 	filter.h	\
diff --git a/basic_filters.c b/basic_filters.c
index 5b0d3e58..5b544473 100644
--- a/basic_filters.c
+++ b/basic_filters.c
@@ -142,7 +142,7 @@ parse_syscall_regex(const char *s, struct number_set *set)
 }

 static unsigned int
-lookup_class(const char *s)
+lookup_class(const char *s, bool qualify_mode)
 {
 	static const struct {
 		const char *name;
@@ -173,6 +173,8 @@ lookup_class(const char *s)

 	unsigned int i;
 	for (i = 0; i < ARRAY_SIZE(syscall_class); ++i) {
+		if (!qualify_mode && *s != '%')
+			continue;
 		if (strcmp(s, syscall_class[i].name) == 0) {
 			return syscall_class[i].value;
 		}
@@ -182,9 +184,9 @@ lookup_class(const char *s)
 }

 static bool
-parse_syscall_class(const char *s, struct number_set *set)
+parse_syscall_class(const char *s, struct number_set *set, bool qualify_mode)
 {
-	const unsigned int n = lookup_class(s);
+	const unsigned int n = lookup_class(s, qualify_mode);
 	if (!n)
 		return false;

@@ -227,7 +229,7 @@ parse_syscall_name(const char *s, struct number_set *set)
 }

 static bool
-parse_syscall(const char *token, struct number_set *set)
+parse_syscall(const char *token, struct number_set *set, bool qualify_mode)
 {
 	bool ignore_fail = false;

@@ -239,7 +241,7 @@ parse_syscall(const char *token, struct number_set *set)
 		return parse_syscall_number(token, set) || ignore_fail;
 	if (*token == '/')
 		return parse_syscall_regex(token + 1, set) || ignore_fail;
-	return parse_syscall_class(token, set)
+	return parse_syscall_class(token, set, qualify_mode)
 	       || parse_syscall_name(token, set)
 	       || ignore_fail;
 }
@@ -249,19 +251,22 @@ parse_syscall(const char *token, struct number_set *set)
  * according to STR specification.
  */
 void
-parse_syscall_set(const char *const str, struct number_set *const set)
+parse_syscall_set(const char *const str, struct number_set *const set,
+		  bool qualify_mode)
 {
 	unsigned int p;
 	const char *s = str;

-	/*
-	 * Each leading ! character means inversion
-	 * of the remaining specification.
-	 */
-	while (*s == '!') {
-		for (p = 0; p < SUPPORTED_PERSONALITIES; ++p)
-			set[p].not = !set[p].not;
-		++s;
+	if (qualify_mode) {
+		/*
+		 * Each leading ! character means inversion
+		 * of the remaining specification.
+		 */
+		while (*s == '!') {
+			for (p = 0; p < SUPPORTED_PERSONALITIES; ++p)
+				set[p].not = !set[p].not;
+			++s;
+		}
 	}

 	if (strcmp(s, "none") == 0) {
@@ -291,7 +296,7 @@ parse_syscall_set(const char *const str, struct number_set *const set)

 	for (token = strtok_r(copy, ",", &saveptr); token;
 	     token = strtok_r(NULL, ",", &saveptr)) {
-		done = parse_syscall(token, set);
+		done = parse_syscall(token, set, qualify_mode);
 		if (!done)
 			error_msg_and_die("invalid system call '%s'", token);
 	}
@@ -304,12 +309,12 @@ parse_syscall_set(const char *const str, struct number_set *const set)
 }

 void *
-parse_syscall_filter(const char *str)
+parse_syscall_filter(const char *str, bool qualify_mode)
 {
 	struct number_set *set = xcalloc(SUPPORTED_PERSONALITIES,
 					 sizeof(struct number_set));

-	parse_syscall_set(str, set);
+	parse_syscall_set(str, set, qualify_mode);
 	return set;
 }

@@ -337,17 +342,20 @@ free_syscall_filter(void *_priv_data)
  */
 void
 parse_set(const char *const str, struct number_set *const set,
-	       string_to_uint_func func, const char *const name)
+	       string_to_uint_func func, const char *const name,
+	       bool qualify_mode)
 {
 	const char *s = str;

-	/*
-	 * Each leading ! character means inversion
-	 * of the remaining specification.
-	 */
-	while (*s == '!') {
-		set->not = !set->not;
-		++s;
+	if (qualify_mode) {
+		/*
+		 * Each leading ! character means inversion
+		 * of the remaining specification.
+		 */
+		while (*s == '!') {
+			set->not = !set->not;
+			++s;
+		}
 	}

 	if (strcmp(s, "none") == 0) {
@@ -389,11 +397,11 @@ parse_set(const char *const str, struct number_set *const set,
 }

 void *
-parse_fd_filter(const char *str)
+parse_fd_filter(const char *str, bool qualify_mode)
 {
 	struct number_set *set = xcalloc(1, sizeof(struct number_set));

-	parse_set(str, set, string_to_uint, "descriptor");
+	parse_set(str, set, string_to_uint, "descriptor", qualify_mode);
 	return set;
 }

@@ -435,7 +443,7 @@ free_fd_filter(void *_priv_data)
 }

 void *
-parse_path_filter(const char *path, const char *const name)
+parse_path_filter(const char *path, const char *const name, bool qualify_mode)
 {
 	struct path_set *set = xcalloc(1, sizeof(struct path_set));

diff --git a/defs.h b/defs.h
index 8c5777c9..d7406d81 100644
--- a/defs.h
+++ b/defs.h
@@ -674,7 +674,7 @@ extern struct number_set signal_set;
 extern bool is_number_in_set(unsigned int number, const struct number_set *);
 extern void filtering_parsing_finish(void);
 extern void filter_syscall(struct tcb *);
-extern void parse_qualify_filter(const char *);
+extern void filtering_parse(const char *);

 #define DECL_IOCTL(name)						\
 extern int								\
diff --git a/filter.c b/filter.c
index 9c9b7e0d..7aab4a59 100644
--- a/filter.c
+++ b/filter.c
@@ -30,7 +30,7 @@

 #define DECL_FILTER(name)						\
 extern void *								\
-parse_ ## name ## _filter(const char *);				\
+parse_ ## name ## _filter(const char *, bool);				\
 extern bool								\
 run_ ## name ## _filter(struct tcb *, void *);				\
 extern void								\
@@ -49,7 +49,7 @@ DECL_FILTER(path);

 static const struct filter_type {
 	const char *name;
-	void *(*parse_filter)(const char *);
+	void *(*parse_filter)(const char *, bool);
 	bool (*run_filter)(struct tcb *, void *);
 	void (*free_priv_data)(void *);
 } filter_types[] = {
@@ -93,9 +93,9 @@ add_filter_to_array(struct filter **filters, unsigned int *nfilters,
 }

 void
-parse_filter(struct filter *filter, const char *str)
+parse_filter(struct filter *filter, const char *str, bool qualify_mode)
 {
-	filter->_priv_data = filter->type->parse_filter(str);
+	filter->_priv_data = filter->type->parse_filter(str, qualify_mode);
 }

 static bool
diff --git a/filter.h b/filter.h
index ae2f47ff..658eefa0 100644
--- a/filter.h
+++ b/filter.h
@@ -37,7 +37,7 @@ struct bool_expression;

 typedef int (*string_to_uint_func)(const char *);
 void parse_set(const char *const, struct number_set *const,
-	       string_to_uint_func, const char *const);
+	       string_to_uint_func, const char *const, bool qualify_mode);
 void parse_inject_common_args(char *, struct inject_opts *,
 			      const bool fault_tokens_only, bool qualify_mode);
 typedef bool (*match_fd_func)(struct tcb *, int, void *);
@@ -46,7 +46,7 @@ int match_fd_common(struct tcb *, match_fd_func, void *);
 /* filter api */
 struct filter* add_filter_to_array(struct filter **, unsigned int *nfilters,
 				   const char *name);
-void parse_filter(struct filter *, const char *str);
+void parse_filter(struct filter *, const char *str, bool qualify_mode);
 void run_filters(struct tcb *, struct filter *, unsigned int, bool *);
 void free_filter(struct filter *);
 void set_filter_priv_data(struct filter *, void *);
@@ -56,6 +56,7 @@ void set_filters_qualify_mode(struct filter **, unsigned int *nfilters,
 /* filter action api */
 struct filter *create_filter(struct filter_action *, const char *name);
 struct filter_action *find_or_add_action(const char *);
+void parse_filter_action(const char *, const char *, const char *);
 void set_filter_action_priv_data(struct filter_action *, void *);
 void set_qualify_mode(struct filter_action *, unsigned int);

@@ -64,5 +65,9 @@ struct bool_expression *create_expression();
 bool run_expression(struct bool_expression *, bool *, unsigned int);
 void set_expression_qualify_mode(struct bool_expression *, unsigned int);
 void expression_add_filter_and(struct bool_expression *, unsigned int);
+void parse_filter_expression(struct bool_expression *, const char *,
+			     struct filter_action *, unsigned int);
+
+void parse_qualify_action(const char *, const char *, const char *);

 #endif /* !STRACE_FILTER_H */
diff --git a/filter_action.c b/filter_action.c
index eae501ba..697a71ce 100644
--- a/filter_action.c
+++ b/filter_action.c
@@ -122,7 +122,7 @@ inject_path_tracing(void)
 	struct filter *path_filter;

 	if (!action->nfilters)
-		parse_qualify_filter("trace=all");
+		filtering_parse("trace=all");
 	path_filter = add_filter_to_array(&action->filters, &action->nfilters,
 					  "path");
 	set_filter_priv_data(path_filter, &global_path_set);
@@ -203,6 +203,18 @@ find_or_add_action(const char *name)
 	return add_action(type);
 }

+void
+parse_filter_action(const char *action_name, const char *expr, const char *args)
+{
+	struct filter_action *action = find_or_add_action(action_name);
+
+	parse_filter_expression(action->expr, expr, action, action->nfilters);
+	if (args && action->type->parse_args == &parse_null)
+		error_msg("%s action takes no arguments, ignored arguments "
+			  "'%s'", action->type->name, args);
+	action->_priv_data = action->type->parse_args(args);
+}
+
 static void
 run_filter_action(struct tcb *tcp, struct filter_action *action)
 {
diff --git a/filter_expression.c b/filter_expression.c
index 73aaae3a..cdf757d3 100644
--- a/filter_expression.c
+++ b/filter_expression.c
@@ -29,6 +29,10 @@
 #include <stdarg.h>
 #include "filter.h"

+extern bool is_space_ascii(char);
+
+extern bool is_allowed_in_name(char);
+
 struct expression_token {
 	enum token_type {
 		TOK_VARIABLE,
@@ -43,6 +47,8 @@ struct expression_token {
 		} operator_id;
 	} data;
 };
+/* Pseudo-operator for parsing */
+#define OP_PARENTHESIS 3

 struct bool_expression {
 	unsigned int ntokens;
@@ -279,3 +285,185 @@ run_expression(struct bool_expression *expr, bool *variables,
 					    variables, variables_num);
 	return stack[0];
 }
+
+/*
+ * Parse operator and add operator length to str and pos.
+ * Return -1 if no operator found.
+ */
+static int
+parse_operator(char **str, unsigned int *pos)
+{
+#define _OP(s, op) { s, sizeof(s) - 1, op }
+	struct {
+		const char *str;
+		int len;
+		enum operator_type op;
+	} ops[] = {
+		_OP("!",	OP_NOT),
+		_OP("not",	OP_NOT),
+		_OP("&&",	OP_AND),
+		_OP("and",	OP_AND),
+		_OP("||",	OP_OR),
+		_OP("or",	OP_OR),
+	};
+#undef _OP
+	char *p = *str;
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(ops); i++) {
+		if (!strncmp(p, ops[i].str, ops[i].len) &&
+		    (!is_allowed_in_name(ops[i].str[0]) ||
+		    !is_allowed_in_name(p[ops[i].len]))) {
+			*str += ops[i].len - 1;
+			*pos += ops[i].len - 1;
+			return ops[i].op;
+		}
+	}
+	return -1;
+}
+
+static char *
+unescape_argument(char **str)
+{
+	char *p;
+	char *p_new;
+	bool escaped = false;
+	unsigned int size = 1;
+	char *new_str = xcalloc(strlen(*str) + 1, 1);
+
+	for (p = *str, p_new = new_str; *p; ++p) {
+		if (!escaped) {
+			if (*p == '\\') {
+				escaped = true;
+				continue;
+			} else if (is_space_ascii(*p) || *p == ')' || *p == '|'
+			           || *p == '&') {
+				break;
+			}
+		}
+		escaped = false;
+		*(p_new++) = *p;
+		size++;
+	}
+	*str = p - 1;
+	return xreallocarray(new_str, size, 1);
+}
+
+static void
+push_operator(int *stack, unsigned int *stack_size, int op)
+{
+	if (*stack_size == MAX_STACK_SIZE)
+		error_msg_and_die("stack overflow (expression is too complex)");
+	stack[*stack_size] = op;
+	(*stack_size)++;
+}
+
+static bool
+is_higher_priority(int op_a, int op_b)
+{
+	bool op_priority[] = {
+		[OP_NOT] = 2,
+		[OP_AND] = 1,
+		[OP_OR]  = 0,
+	};
+	return op_priority[op_a] > op_priority[op_b];
+}
+
+void
+parse_filter_expression(struct bool_expression *expr, const char *str,
+			struct filter_action *action, unsigned int start_id)
+{
+	enum {
+		WAIT_FILTER,
+		FILTER_NAME,
+		FILTER_ARG,
+		WAIT_OPERATOR,
+	} state = WAIT_FILTER;
+	unsigned int variable_id = start_id;
+	/* Current stack stack_size */
+	unsigned int st_size = 0;
+	int stack[MAX_STACK_SIZE];
+	char *buf = xstrdup(str);
+	struct filter *cur_filter = NULL;
+	char *filter_name = NULL;
+	char *filter_arg = NULL;
+	int op;
+	char *p;
+	unsigned int pos = 0;
+
+	for (p = buf; *p; ++p, ++pos) {
+		switch (state) {
+		case WAIT_FILTER:
+			if (*p == '(') {
+				push_operator(stack, &st_size, OP_PARENTHESIS);
+			} else if ((op = parse_operator(&p, &pos)) >= 0) {
+				if (op == OP_NOT) {
+					push_operator(stack, &st_size, op);
+				} else {
+					error_msg_and_die("invalid operator "
+							  "at '%s':%u",
+							  str, pos);
+				}
+			} else if (!is_space_ascii(*p)) {
+				filter_name = p;
+				state = FILTER_NAME;
+			}
+			break;
+
+		case FILTER_NAME:
+			if (is_space_ascii(*p)) {
+				*p = '\0';
+				cur_filter = create_filter(action, filter_name);
+				filter_arg = NULL;
+				state = FILTER_ARG;
+			}
+			break;
+
+		case FILTER_ARG:
+			if (!filter_arg && is_space_ascii(*p))
+				break;
+			filter_arg = unescape_argument(&p);
+			parse_filter(cur_filter, filter_arg, false);
+			free(filter_arg);
+			add_variable_token(expr, variable_id++);
+			state = WAIT_OPERATOR;
+			break;
+
+		case WAIT_OPERATOR:
+			if (is_space_ascii(*p))
+				break;
+			if (*p == ')') {
+				while ((st_size > 0) &&
+				       (stack[st_size - 1] != OP_PARENTHESIS)) {
+						op = stack[--st_size];
+						add_operator_token(expr, op);
+					}
+				--st_size;
+				break;
+			}
+			op = parse_operator(&p, &pos);
+			if (op < 0 || op == OP_NOT)
+				error_msg_and_die("invalid operator at '%s':%u",
+						  str, pos);
+
+			/* Pop operators with higher priority. */
+			while ((st_size > 0) &&
+			       (stack[st_size - 1] != OP_PARENTHESIS) &&
+			       is_higher_priority(stack[st_size - 1], op))
+				add_operator_token(expr, stack[--st_size]);
+
+			push_operator(stack, &st_size, op);
+			state = WAIT_FILTER;
+			break;
+		}
+	}
+
+	free(buf);
+	if (state != WAIT_OPERATOR)
+		error_msg_and_die("unfinished filter expression '%s'", str);
+
+	while (st_size > 0)
+		add_operator_token(expr, stack[--st_size]);
+	if (start_id > 0)
+		add_operator_token(expr, OP_OR);
+}
diff --git a/filter_parse.c b/filter_parse.c
new file mode 100644
index 00000000..e0fd8e58
--- /dev/null
+++ b/filter_parse.c
@@ -0,0 +1,252 @@
+/*
+ * Copyright (c) 2017 Nikolay Marchuk <marchuk.nikolay.a at gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "defs.h"
+#include "filter.h"
+
+bool
+is_space_ascii(char c)
+{
+	return (c == ' ') || (c == '\t') || (c == '\n') || (c == '\r') ||
+	       (c == '\v') || (c == '\f');
+}
+
+bool
+is_allowed_in_name(char c)
+{
+	return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
+	       || (c >= '0' && c <= '9') || (c == '_');
+}
+
+/*
+ * Split expression into action name, filter expression or qualify set
+ * and action arguments.
+ */
+void
+filtering_parse(const char *str)
+{
+	enum parsing_states {
+		F_EMPTY,
+		F_BEGIN,
+		F_QUAL_SET,
+		F_FILT_EXPR,
+		F_QUAL_ARGS,
+		F_FILT_ARGS,
+		F_END,
+	} state = F_EMPTY;
+	const char *begin = NULL;
+	const char *action_name = "trace";
+	const char *main_part = NULL;
+	const char *args = NULL;
+	bool action_specified = false;
+	bool escaped = false;
+	int parentheses_count = 0;
+	/* Used to store position of last terminating parenthesis. */
+	char *expression_end = NULL;
+	/* Used to provide diagnostics. */
+	unsigned int pos = 0;
+	char *buf = xstrdup(str);
+	char *p;
+
+	for (p = buf; *p; ++p, ++pos) {
+		switch (state) {
+		case F_EMPTY:
+			switch (*p) {
+			/* trace(), action name omitted */
+			case '(':
+				parentheses_count++;
+				main_part = p;
+				state = F_FILT_EXPR;
+				break;
+			/* missing action name */
+			case '=':
+				*p = '\0';
+				error_msg_and_die("invalid filter action '%s'",
+						  buf);
+			default:
+				if (is_space_ascii(*p)) {
+					break;
+				} else if (!strncmp(p, "not", 3) && *(p + 3) &&
+					   (is_space_ascii(*(p + 3)) ||
+					   *(p + 3) == '(')) {
+					main_part = p;
+					state = F_FILT_EXPR;
+					break;
+				} else {
+					begin = p;
+					state = F_BEGIN;
+				}
+			}
+			if (state != F_BEGIN)
+				break;
+			/* else fall through to check for qualify set */
+
+		case F_BEGIN:
+			switch (*p) {
+			/* action(...) */
+			case '(':
+				if (*begin == '!') {
+					main_part = begin;
+				} else {
+					action_name = begin;
+					action_specified = true;
+					*p = '\0';
+					main_part = p + 1;
+				}
+				state = F_FILT_EXPR;
+				parentheses_count++;
+				break;
+			/* action=... */
+			case '=':
+				action_name = begin;
+				action_specified = true;
+				*p = '\0';
+				main_part = p + 1;
+				state = F_QUAL_SET;
+				break;
+			case ':':
+				main_part = begin;
+				*p = '\0';
+				args = p + 1;
+				state = F_QUAL_ARGS;
+				break;
+			case ';':
+				error_msg_and_die("invalid arguments position "
+						  "'%s':%u",
+						  str, pos);
+			/* qualify set without action. */
+			case ',':
+			case '?':
+			case '/':
+			case '%':
+			case '-':
+				main_part = begin;
+				state = F_QUAL_SET;
+				break;
+			default:
+				/* new expression without action. */
+				if (is_space_ascii(*p)) {
+					main_part = begin;
+					state = F_FILT_EXPR;
+				}
+			}
+			break;
+
+		case F_QUAL_SET:
+			if (*p == ':') {
+				*p = '\0';
+				args = p + 1;
+				state = F_QUAL_ARGS;
+			}
+			break;
+
+		case F_FILT_EXPR:
+			if (!escaped) {
+				switch (*p) {
+				case ';':
+					if (parentheses_count != 1 ||
+					    !action_specified)
+						error_msg_and_die("invalid "
+								  "arguments "
+								  "position "
+								  "'%s':%u",
+								  str, pos);
+					*p = '\0';
+					args = p + 1;
+					state = F_FILT_ARGS;
+					break;
+				case '(':
+					parentheses_count++;
+					break;
+				case ')':
+					if (parentheses_count <= 0)
+						error_msg_and_die("unexpected "
+								  "')' at "
+								  "'%s':%u",
+								  str, pos);
+					parentheses_count--;
+					expression_end = p;
+					if (action_specified &&
+					    parentheses_count == 0)
+						state = F_END;
+					break;
+				case '\\':
+					escaped = true;
+					break;
+				}
+			} else
+				escaped = false;
+			break;
+
+		case F_QUAL_ARGS:
+			break;
+		case F_FILT_ARGS:
+			if (!escaped) {
+				switch (*p) {
+				case ')':
+					parentheses_count--;
+					expression_end = p;
+					state = F_END;
+					break;
+				case '\\':
+					escaped = true;
+					break;
+				}
+			} else
+				escaped = false;
+			break;
+		case F_END:
+			if (!is_space_ascii(*p))
+				error_msg_and_die("unexpected '%c' at "
+						  "'%s':%u", *p, str, pos);
+		}
+	}
+
+	switch (state) {
+	case F_EMPTY:
+		main_part = buf;
+		parse_qualify_action(action_name, main_part, args);
+		break;
+	case F_BEGIN:
+		main_part = begin;
+		/* fall through */
+	case F_QUAL_SET:
+	case F_QUAL_ARGS:
+		parse_qualify_action(action_name, main_part, args);
+		break;
+	case F_FILT_EXPR:
+	case F_FILT_ARGS:
+	case F_END:
+		if (parentheses_count != 0)
+			error_msg_and_die("missing ')' in '%s'", str);
+		if (action_specified && expression_end)
+			*expression_end = '\0';
+		parse_filter_action(action_name, main_part, args);
+		break;
+	}
+	free(buf);
+}
diff --git a/filter_qualify.c b/filter_qualify.c
index ff54720a..06c322a0 100644
--- a/filter_qualify.c
+++ b/filter_qualify.c
@@ -182,27 +182,33 @@ parse_inject_common_args(char *str, struct inject_opts *const opts,
 }

 static void
-parse_read(const char *const str)
+parse_read(const char *const main_part, const char *const args)
 {
 	struct filter_action *action = find_or_add_action("read");
 	struct filter *filter = create_filter(action, "fd");

-	parse_filter(filter, str);
+	parse_filter(filter, main_part, true);
+	if (args)
+		error_msg("read action takes no arguments, ignored arguments "
+			  "'%s'", args);
 	set_qualify_mode(action, 1);
 }

 static void
-parse_write(const char *const str)
+parse_write(const char *const main_part, const char *const args)
 {
 	struct filter_action *action = find_or_add_action("write");
 	struct filter *filter = create_filter(action, "fd");

-	parse_filter(filter, str);
+	parse_filter(filter, main_part, true);
+	if (args)
+		error_msg("write action takes no arguments, ignored arguments "
+			  "'%s'", args);
 	set_qualify_mode(action, 1);
 }

 static void
-qualify_signals(const char *const str)
+qualify_signals(const char *const main_part, const char *const args)
 {
 	/* Clear the set. */
 	if (signal_set.nslots)
@@ -210,89 +216,101 @@ qualify_signals(const char *const str)
 		       sizeof(*signal_set.vec) * signal_set.nslots);
 	signal_set.not = false;

-	parse_set(str, &signal_set, sigstr_to_uint, "signal");
+	parse_set(main_part, &signal_set, sigstr_to_uint, "signal", true);
+	if (args)
+		error_msg("signal action takes no arguments, ignored arguments "
+			  "'%s'", args);
 }

 static void
-parse_trace(const char *const str)
+parse_trace(const char *const main_part, const char *const args)
 {
 	struct filter_action *action = find_or_add_action("trace");
 	struct filter *filter = create_filter(action, "syscall");

-	parse_filter(filter, str);
+	parse_filter(filter, main_part, true);
+	if (args)
+		error_msg("trace action takes no arguments, ignored arguments "
+			  "'%s'", args);
 	set_qualify_mode(action, 1);
 }

 static void
-parse_abbrev(const char *const str)
+parse_abbrev(const char *const main_part, const char *const args)
 {
 	struct filter_action *action = find_or_add_action("abbrev");
 	struct filter *filter = create_filter(action, "syscall");

-	parse_filter(filter, str);
+	parse_filter(filter, main_part, true);
+	if (args)
+		error_msg("abbrev action takes no arguments, ignored arguments "
+			  "'%s'", args);
 	set_qualify_mode(action, 1);
 }

 static void
-parse_verbose(const char *const str)
+parse_verbose(const char *const main_part, const char *const args)
 {
 	struct filter_action *action = find_or_add_action("verbose");
 	struct filter *filter = create_filter(action, "syscall");

-	parse_filter(filter, str);
+	parse_filter(filter, main_part, true);
+	if (args)
+		error_msg("verbose action takes no arguments, ignored arguments"
+			  " '%s'", args);
 	set_qualify_mode(action, 1);
 }

 static void
-parse_raw(const char *const str)
+parse_raw(const char *const main_part, const char *const args)
 {
 	struct filter_action *action = find_or_add_action("raw");
 	struct filter *filter = create_filter(action, "syscall");

-	parse_filter(filter, str);
+	parse_filter(filter, main_part, true);
+	if (args)
+		error_msg("raw action takes no arguments, ignored arguments "
+			  "'%s'", args);
 	set_qualify_mode(action, 1);
 }

 static void
-parse_inject_common_qualify(const char *const str, const bool fault_tokens_only,
-			    const char *const description)
+parse_inject_common_qualify(const char *const main_part, const char *const args,
+		    const bool fault_tokens_only, const char *const description)
 {
 	struct inject_opts *opts = xmalloc(sizeof(struct inject_opts));
-	char *buf = xstrdup(str);
+	char *buf = args ? xstrdup(args) : NULL;
 	struct filter_action *action;
 	struct filter *filter;
-	char *args = strchr(buf, ':');
-
-	if (args)
-		*(args++) = '\0';

 	action = find_or_add_action(fault_tokens_only ? "fault" : "inject");
 	filter = create_filter(action, "syscall");
-	parse_filter(filter, buf);
+	parse_filter(filter, main_part, true);
 	set_qualify_mode(action, 1);
-	parse_inject_common_args(args, opts, fault_tokens_only, true);
+	parse_inject_common_args(buf, opts, fault_tokens_only, true);
 	if (!opts->init)
-		error_msg_and_die("invalid %s '%s'", description,
+		error_msg_and_die("invalid %s argument '%s'", description,
 				  args ? args : "");
-	free(buf);
+	if (buf)
+		free(buf);
 	set_filter_action_priv_data(action, opts);
 }

 static void
-parse_fault(const char *const str)
+parse_fault(const char *const main_part, const char *const args)
 {
-	parse_inject_common_qualify(str, true, "fault argument");
+	parse_inject_common_qualify(main_part, args, true, "fault");
 }

 static void
-parse_inject(const char *const str)
+parse_inject(const char *const main_part, const char *const args)
 {
-	parse_inject_common_qualify(str, false, "inject argument");
+	parse_inject_common_qualify(main_part, args, false, "inject");
 }

 static const struct qual_options {
 	const char *name;
-	void (*qualify)(const char *);
+	void (*qualify)(const char *, const char *);
 } qual_options[] = {
 	{ "trace",	parse_trace	},
 	{ "t",		parse_trace	},
@@ -316,22 +334,20 @@ static const struct qual_options {
 };

 void
-parse_qualify_filter(const char *str)
+parse_qualify_action(const char *action_name, const char *main_part,
+		     const char *args)
 {
-	const struct qual_options *opt = qual_options;
+	const struct qual_options *opt = NULL;
 	unsigned int i;

 	for (i = 0; i < ARRAY_SIZE(qual_options); ++i) {
-		const char *name = qual_options[i].name;
-		const size_t len = strlen(name);
-		const char *val = str_strip_prefix_len(str, name, len);
-
-		if (val == str || *val != '=')
-			continue;
-		str = val + 1;
-		opt = &qual_options[i];
-		break;
+		if (!strcmp(action_name, qual_options[i].name)) {
+			opt = &qual_options[i];
+			break;
+		}
 	}

-	opt->qualify(str);
+	if (!opt)
+		error_msg_and_die("invalid filter action '%s'", action_name);
+	opt->qualify(main_part ? main_part : "", args);
 }
diff --git a/strace.c b/strace.c
index 6162d3b3..79c5397e 100644
--- a/strace.c
+++ b/strace.c
@@ -1580,7 +1580,7 @@ init(int argc, char *argv[])
 #if DEFAULT_QUAL_FLAGS != (QUAL_TRACE | QUAL_ABBREV | QUAL_VERBOSE)
 # error Bug in DEFAULT_QUAL_FLAGS
 #endif
-	parse_qualify_filter("signal=all");
+	filtering_parse("signal=all");
 	while ((c = getopt(argc, argv,
 		"+b:cCdfFhiqrtTvVwxyz"
 #ifdef USE_LIBUNWIND
@@ -1647,7 +1647,7 @@ init(int argc, char *argv[])
 			show_fd_path++;
 			break;
 		case 'v':
-			parse_qualify_filter("abbrev=none");
+			filtering_parse("abbrev=none");
 			break;
 		case 'V':
 			print_version();
@@ -1662,7 +1662,7 @@ init(int argc, char *argv[])
 				error_opt_arg(c, optarg);
 			break;
 		case 'e':
-			parse_qualify_filter(optarg);
+			filtering_parse(optarg);
 			break;
 		case 'o':
 			outfname = optarg;
--
2.11.0




More information about the Strace-devel mailing list