[PATCH] Introduce new filtering architecture

Eugene Syromiatnikov esyr at redhat.com
Mon Jun 19 04:08:48 UTC 2017


On Thu, Jun 15, 2017 at 11:22:57AM +0700, Nikolay Marchuk wrote:
> This change introduces new filtering architecture primitives: filter,
> filter_action and bool_expression. It also splits qualify.c
> into basic_filters.c and basic_actions.c and changes processing of -e
> strace option. Now, filtering is done after decoding of syscall and
> tcp->qual_flg stores filtering results.
> 
> * basic_actions.c: New file.
> * basic_filters.c: New file, part of qualify.c.
> * defs.h (struct tcb): Change inject options variable.
> (QUAL_READ, QUAL_WRITE): Change description.
> (read_set, write_set): Remove global set variables.
> (is_number_in_set): Change declaration.
> (qualify, qual_flags): Remove old declarations ...
> (filter_syscall, parse_qualify_filter): ... and add new declarations.
> * filter.c: New file.
> * filter.h: Likewise.
> * filter_action.c: Likewise.
> * filter_expression.c: Likewise.
> * filter_qualify.c: New file, part of qualify.c.
> * Makefile.am (strace_SOURCES): Add new files, remove qualify.c.
> * qualify.c: Split into new files.
> * strace.c (droptcb): Remove inject_vec freeing.
> (init): Change qualify to parse_qualify_filter.
> (trace_syscall): Add filtering after syscall decoding.
> * syscall.c (decode_socket_subcall): Remove qual_flags from decoder.
> (decode_ipc_subcall): Likewise.
> (decode_mips_subcall): Likewise.
> (get_scno): Likewise.
> (inject_vec, tcb_inject_opts, tamper_with_syscall_entering,
>  tamper_with_syscall_exiting): Remove inject_vec support code.
> (dumpio): Check tcp->qual_flg instead of global set.
> ---
>  Makefile.am         |   8 +-
>  basic_actions.c     |  98 ++++++++
>  basic_filters.c     | 425 ++++++++++++++++++++++++++++++++
>  defs.h              |  14 +-
>  filter.c            | 133 ++++++++++
>  filter.h            |  68 +++++
>  filter_action.c     | 184 ++++++++++++++
>  filter_expression.c | 132 ++++++++++
>  filter_qualify.c    | 346 ++++++++++++++++++++++++++
>  qualify.c           | 697 ----------------------------------------------------
>  strace.c            |  18 +-
>  syscall.c           |  31 +--
>  12 files changed, 1412 insertions(+), 742 deletions(-)
>  create mode 100644 basic_actions.c
>  create mode 100644 basic_filters.c
>  create mode 100644 filter.c
>  create mode 100644 filter.h
>  create mode 100644 filter_action.c
>  create mode 100644 filter_expression.c
>  create mode 100644 filter_qualify.c
>  delete mode 100644 qualify.c
> 
> diff --git a/Makefile.am b/Makefile.am
> index 80f8a34..2533b80 100644
> --- a/Makefile.am
> +++ b/Makefile.am
> @@ -86,6 +86,8 @@ strace_SOURCES =	\
>  	affinity.c	\
>  	aio.c		\
>  	alpha.c		\
> +	basic_actions.c	\
> +	basic_filters.c	\
>  	bjm.c		\
>  	block.c		\
>  	bpf.c		\
> @@ -123,6 +125,11 @@ strace_SOURCES =	\
>  	fetch_struct_statfs.c \
>  	file_handle.c	\
>  	file_ioctl.c	\
> +	filter_action.c	\
> +	filter_expression.c \
> +	filter_qualify.c \
> +	filter.c	\
> +	filter.h	\
>  	fs_x_ioctl.c	\
>  	flock.c		\
>  	flock.h		\
> @@ -205,7 +212,6 @@ strace_SOURCES =	\
>  	process_vm.c	\
>  	ptp.c		\
>  	ptrace.h	\
> -	qualify.c	\
>  	quota.c		\
>  	readahead.c	\
>  	readlink.c	\
> diff --git a/basic_actions.c b/basic_actions.c
> new file mode 100644
> index 0000000..de87cde
> --- /dev/null
> +++ b/basic_actions.c
> @@ -0,0 +1,98 @@
> +/*
> + * 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"
> +
> +bool
> +is_traced(struct tcb *tcp)
> +{
> +	return (tcp->qual_flg & QUAL_TRACE);
> +}
> +
> +void *
> +parse_null(const char *str)
> +{
> +	return NULL;
> +}
> +
> +void
> +free_null(void *_priv_data)
> +{
> +	return;
> +}
> +
> +void
> +apply_trace(struct tcb *tcp, void *_priv_data)
> +{
> +	tcp->qual_flg |= QUAL_TRACE;
> +}
> +void
> +apply_inject(struct tcb *tcp, void *_priv_data)
> +{
> +	struct inject_opts **inject_vec = (struct inject_opts **)_priv_data;
> +	tcp->qual_flg |= QUAL_INJECT;
> +	tcp->inject_opts = (scno_in_range(tcp->scno) && inject_vec[current_personality])
> +	       ? &inject_vec[current_personality][tcp->scno] : NULL;
> +}
> +void*
> +parse_inject(const char *str)
> +{
> +	return NULL;
> +}
> +void free_inject(void *_priv_data)
> +{
> +	struct inject_opts **vec = (struct inject_opts **)_priv_data;
> +	unsigned int p;
> +	for (p = 0; p < SUPPORTED_PERSONALITIES; ++p)
> +		if (vec[p])
> +			free(vec[p]);
> +	free(vec);
> +}
> +void
> +apply_read(struct tcb *tcp, void *_priv_data)
> +{
> +	tcp->qual_flg |= QUAL_READ;
> +}
> +void
> +apply_write(struct tcb *tcp, void *_priv_data)
> +{
> +	tcp->qual_flg |= QUAL_WRITE;
> +}
> +void
> +apply_raw(struct tcb *tcp, void *_priv_data)
> +{
> +	tcp->qual_flg |= QUAL_RAW;
> +}
> +void
> +apply_abbrev(struct tcb *tcp, void *_priv_data)
> +{
> +	tcp->qual_flg |= QUAL_ABBREV;
> +}
> +void
> +apply_verbose(struct tcb *tcp, void *_priv_data)
> +{
> +	tcp->qual_flg |= QUAL_VERBOSE;
> +}
> diff --git a/basic_filters.c b/basic_filters.c
> new file mode 100644
> index 0000000..75543ee
> --- /dev/null
> +++ b/basic_filters.c
> @@ -0,0 +1,425 @@
> +/*
> + * Copyright (c) 2016 Dmitry V. Levin <ldv at altlinux.org>
> + * Copyright (c) 2016-2017 The strace developers.
> + * 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 <regex.h>
> +
> +typedef unsigned int number_slot_t;
> +#define BITS_PER_SLOT (sizeof(number_slot_t) * 8)
> +
> +struct number_set {
> +	number_slot_t *vec;
> +	unsigned int nslots;
> +	bool not;
> +};
> +
> +static void
> +number_setbit(const unsigned int i, number_slot_t *const vec)
> +{
> +	vec[i / BITS_PER_SLOT] |= (number_slot_t) 1 << (i % BITS_PER_SLOT);
> +}
> +
> +static bool
> +number_isset(const unsigned int i, const number_slot_t *const vec)
> +{
> +	return vec[i / BITS_PER_SLOT] & ((number_slot_t) 1 << (i % BITS_PER_SLOT));
bool type is assumed to be 0 or 1, it's probably a good idea to pass the
returned value to the "!!" operators.

> +}
> +
> +
> +static void
> +reallocate_number_set(struct number_set *const set, const unsigned int new_nslots)
> +{
> +	if (new_nslots <= set->nslots)
> +		return;
> +	set->vec = xreallocarray(set->vec, new_nslots, sizeof(*set->vec));
> +	memset(set->vec + set->nslots, 0,
> +	       sizeof(*set->vec) * (new_nslots - set->nslots));
> +	set->nslots = new_nslots;
> +}
> +
> +void
> +add_number_to_set(const unsigned int number, struct number_set *const set)
> +{
> +	reallocate_number_set(set, number / BITS_PER_SLOT + 1);
> +	number_setbit(number, set->vec);
> +}
> +
> +bool
> +is_number_in_set(const unsigned int number, const struct number_set *const set)
> +{
> +	return ((number / BITS_PER_SLOT < set->nslots)
> +		&& number_isset(number, set->vec)) ^ set->not;
> +}
> +
> +static bool
> +parse_syscall_number(const char *s, struct number_set *set)
> +{
> +	int n = string_to_uint(s);
> +	if (n < 0)
> +		return false;
> +
> +	unsigned int p;
> +	bool done = false;
> +
> +	for (p = 0; p < SUPPORTED_PERSONALITIES; ++p) {
> +		if ((unsigned) n >= nsyscall_vec[p]) {
> +			continue;
> +		}
> +		add_number_to_set(n, &set[p]);
> +		done = true;
> +	}
> +
> +	return done;
> +}
> +
> +static void
> +regerror_msg_and_die(int errcode, const regex_t *preg,
> +		     const char *str, const char *pattern)
> +{
> +	char buf[512];
> +
> +	regerror(errcode, preg, buf, sizeof(buf));
> +	error_msg_and_die("%s: %s: %s", str, pattern, buf);
> +}
> +
> +static bool
> +parse_syscall_regex(const char *s, struct number_set *set)
> +{
> +	regex_t preg;
> +	int rc;
> +
> +	if ((rc = regcomp(&preg, s, REG_EXTENDED | REG_NOSUB)) != 0)
> +		regerror_msg_and_die(rc, &preg, "regcomp", s);
> +
> +	unsigned int p;
> +	bool found = false;
> +	for (p = 0; p < SUPPORTED_PERSONALITIES; ++p) {
> +		unsigned int i;
> +
> +		for (i = 0; i < nsyscall_vec[p]; ++i) {
> +			if (!sysent_vec[p][i].sys_name)
> +				continue;
> +			rc = regexec(&preg, sysent_vec[p][i].sys_name,
> +				     0, NULL, 0);
> +			if (rc == REG_NOMATCH)
> +				continue;
> +			else if (rc)
> +				regerror_msg_and_die(rc, &preg, "regexec", s);
> +			add_number_to_set(i, &set[p]);
> +			found = true;
> +		}
> +	}
> +
> +	regfree(&preg);
> +	return found;
> +}
> +
> +static unsigned int
> +lookup_class(const char *s)
> +{
> +	static const struct {
> +		const char *name;
> +		unsigned int value;
> +	} syscall_class[] = {
> +		{ "desc",	TRACE_DESC	},
> +		{ "file",	TRACE_FILE	},
> +		{ "memory",	TRACE_MEMORY	},
> +		{ "process",	TRACE_PROCESS	},
> +		{ "signal",	TRACE_SIGNAL	},
> +		{ "ipc",	TRACE_IPC	},
> +		{ "network",	TRACE_NETWORK	},
> +		{ "%desc",	TRACE_DESC	},
> +		{ "%file",	TRACE_FILE	},
> +		{ "%memory",	TRACE_MEMORY	},
> +		{ "%process",	TRACE_PROCESS	},
> +		{ "%signal",	TRACE_SIGNAL	},
> +		{ "%ipc",	TRACE_IPC	},
> +		{ "%network",	TRACE_NETWORK	},
> +		{ "%stat",	TRACE_STAT	},
> +		{ "%lstat",	TRACE_LSTAT	},
> +		{ "%fstat",	TRACE_FSTAT	},
> +		{ "%%stat",	TRACE_STAT_LIKE	},
> +		{ "%statfs",	TRACE_STATFS	},
> +		{ "%fstatfs",	TRACE_FSTATFS	},
> +		{ "%%statfs",	TRACE_STATFS_LIKE	},
> +	};
> +
> +	unsigned int i;
> +	for (i = 0; i < ARRAY_SIZE(syscall_class); ++i) {
> +		if (strcmp(s, syscall_class[i].name) == 0) {
> +			return syscall_class[i].value;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static bool
> +parse_syscall_class(const char *s, struct number_set *set)
> +{
> +	const unsigned int n = lookup_class(s);
> +	if (!n)
> +		return false;
> +
> +	unsigned int p;
> +	for (p = 0; p < SUPPORTED_PERSONALITIES; ++p) {
> +		unsigned int i;
> +
> +		for (i = 0; i < nsyscall_vec[p]; ++i) {
> +			if (!sysent_vec[p][i].sys_name
> +			    || (sysent_vec[p][i].sys_flags & n) != n) {
> +				continue;
> +			}
> +			add_number_to_set(i, &set[p]);
> +		}
> +	}
> +
> +	return true;
> +}
> +
> +static bool
> +parse_syscall_name(const char *s, struct number_set *set)
> +{
> +	unsigned int p;
> +	bool found = false;
> +
> +	for (p = 0; p < SUPPORTED_PERSONALITIES; ++p) {
> +		unsigned int i;
> +
> +		for (i = 0; i < nsyscall_vec[p]; ++i) {
> +			if (!sysent_vec[p][i].sys_name
> +			    || strcmp(s, sysent_vec[p][i].sys_name)) {
> +				continue;
> +			}
> +			add_number_to_set(i, &set[p]);
> +			found = true;
> +		}
> +	}
> +
> +	return found;
> +}
> +
> +
> +static bool
> +parse_syscall(const char *token, struct number_set *set)
> +{
> +	bool ignore_fail = false;
> +
> +	while (*token == '?') {
> +		token++;
> +		ignore_fail = true;
> +	}
> +	if (*token >= '0' && *token <= '9')
> +		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)
> +	       || parse_syscall_name(token, set)
> +	       || ignore_fail;
> +}
> +
> +static void
> +parse_syscall_set(const char *const str, struct number_set *const set,
> +                  const char *const name)
> +{
> +	/* Clear all sets. */
> +	unsigned int p;
> +	for (p = 0; p < SUPPORTED_PERSONALITIES; ++p) {
> +		if (set[p].nslots)
> +			memset(set[p].vec, 0,
> +			       sizeof(*set[p].vec) * set[p].nslots);
> +		set[p].not = false;
> +	}
> +
> +	/*
> +	 * Each leading ! character means inversion
> +	 * of the remaining specification.
> +	 */
> +	const char *s = str;
> +handle_inversion:
> +	while (*s == '!') {
> +		for (p = 0; p < SUPPORTED_PERSONALITIES; ++p) {
> +			set[p].not = !set[p].not;
> +		}
> +		++s;
> +	}
> +
> +	if (strcmp(s, "none") == 0) {
> +		/*
> +		 * No syscall numbers are added to sets.
> +		 * Subsequent is_number_in_set invocations
> +		 * will return set[p]->not.
> +		 */
> +		return;
> +	} else if (strcmp(s, "all") == 0) {
> +		s = "!none";
> +		goto handle_inversion;
> +	}
> +
> +	/*
> +	 * Split the string into comma separated tokens.
> +	 * For each token, call qualify_syscall that will take care
> +	 * if adding appropriate syscall numbers to sets.
> +	 * The absence of tokens or a negative return code
> +	 * from qualify_syscall is a fatal error.
> +	 */
> +	char *copy = xstrdup(s);
> +	char *saveptr = NULL;
> +	const char *token;
> +	bool done = false;
> +
> +	for (token = strtok_r(copy, ",", &saveptr); token;
> +	     token = strtok_r(NULL, ",", &saveptr)) {
> +		done = parse_syscall(token, set);
> +		if (!done) {
> +			error_msg_and_die("invalid %s '%s'", name, token);
> +		}
> +	}
> +
> +	free(copy);
> +
> +	if (!done) {
> +		error_msg_and_die("invalid %s '%s'", name, str);
> +	}
> +}
> +
> +void*
> +parse_syscall_filter(const char *str, const char *const name)
> +{
> +	struct number_set *set = xcalloc(SUPPORTED_PERSONALITIES,
> +	                                 sizeof(struct number_set));
> +	parse_syscall_set(str, set, name);
> +	return set;
> +}
> +
> +bool
> +run_syscall_filter(struct tcb *tcp, void *_priv_data)
> +{
> +	struct number_set *set = (struct number_set *)_priv_data;
> +	return is_number_in_set(tcp->scno, &set[current_personality]);
> +}
> +
> +void
> +free_syscall_filter(void *_priv_data)
> +{
> +	struct number_set *set = (struct number_set *)_priv_data;
> +	unsigned int p;
> +	for (p = 0; p < SUPPORTED_PERSONALITIES; ++p) {
> +		free(set[p].vec);
> +	}
> +	free(set);
> +}
> +
> +typedef int (*string_to_uint_func)(const char *);
> +
> +void
> +parse_set(const char *const str, struct number_set *const set,
> +	       string_to_uint_func func, const char *const name)
> +{
> +	/* Clear the set. */
> +	if (set->nslots)
> +		memset(set->vec, 0, sizeof(*set->vec) * set->nslots);
> +	set->not = false;
> +
> +	/*
> +	 * Each leading ! character means inversion
> +	 * of the remaining specification.
> +	 */
> +	const char *s = str;
> +handle_inversion:
> +	while (*s == '!') {
> +		set->not = !set->not;
> +		++s;
> +	}
> +
> +	if (strcmp(s, "none") == 0) {
> +		/*
> +		 * No numbers are added to the set.
> +		 * Subsequent is_number_in_set invocations will return set->not.
> +		 */
> +		return;
> +	} else if (strcmp(s, "all") == 0) {
> +		s = "!none";
> +		goto handle_inversion;
> +	}
> +
> +	/*
> +	 * Split the string into comma separated tokens.
> +	 * For each token, find out the corresponding number
> +	 * by calling FUNC, and add that number to the set.
> +	 * The absence of tokens or a negative answer
> +	 * from FUNC is a fatal error.
> +	 */
> +	char *copy = xstrdup(s);
> +	char *saveptr = NULL;
> +	const char *token;
> +	int number = -1;
> +
> +	for (token = strtok_r(copy, ",", &saveptr); token;
> +	     token = strtok_r(NULL, ",", &saveptr)) {
> +		number = func(token);
> +		if (number < 0) {
> +			error_msg_and_die("invalid %s '%s'", name, token);
> +		}
> +
> +		add_number_to_set(number, set);
> +	}
> +
> +	free(copy);
> +
> +	if (number < 0) {
> +		error_msg_and_die("invalid %s '%s'", name, str);
> +	}
> +}
> +
> +void*
> +parse_fd_filter(const char *str, const char *const name)
> +{
> +	struct number_set *set = xmalloc(sizeof(struct number_set));
> +	memset(set, 0, sizeof(struct number_set));
> +	parse_set(str, set, string_to_uint, name);
> +	return set;
> +}
> +
> +bool
> +run_fd_filter(struct tcb *tcp, void *_priv_data)
> +{
> +	int fd = tcp->u_arg[0];
> +	if (fd < 0)
> +		return false;
> +	struct number_set *set = (struct number_set *)_priv_data;
> +	return is_number_in_set(fd, set);
> +}
> +
> +void
> +free_fd_filter(void *_priv_data)
> +{
> +	struct number_set *set = (struct number_set *)_priv_data;
> +	free(set->vec);
> +	free(set);
> +	return;
> +}
> diff --git a/defs.h b/defs.h
> index 6449bce..f758b14 100644
> --- a/defs.h
> +++ b/defs.h
> @@ -228,7 +228,7 @@ struct tcb {
>  	void (*_free_priv_data)(void *); /* Callback for freeing priv_data */
>  	const struct_sysent *s_ent; /* sysent[scno] or dummy struct for bad scno */
>  	const struct_sysent *s_prev_ent; /* for "resuming interrupted SYSCALL" msg */
> -	struct inject_opts *inject_vec[SUPPORTED_PERSONALITIES];
> +	struct inject_opts *inject_opts;
>  	struct timeval stime;	/* System time usage as of last process wait */
>  	struct timeval dtime;	/* Delta for system time usage */
>  	struct timeval etime;	/* Syscall entry time */
> @@ -272,8 +272,8 @@ struct tcb {
>  #define QUAL_RAW	0x008	/* print all args in hex for this syscall */
>  #define QUAL_INJECT	0x010	/* tamper with this system call on purpose */
>  #define QUAL_SIGNAL	0x100	/* report events with this signal */
> -#define QUAL_READ	0x200	/* dump data read from this file descriptor */
> -#define QUAL_WRITE	0x400	/* dump data written to this file descriptor */
> +#define QUAL_READ	0x200	/* dump data read in this syscall */
> +#define QUAL_WRITE	0x400	/* dump data written in this syscall */
>  
>  #define DEFAULT_QUAL_FLAGS (QUAL_TRACE | QUAL_ABBREV | QUAL_VERBOSE)
>  
> @@ -651,13 +651,11 @@ print_struct_statfs64(struct tcb *, kernel_ulong_t addr, kernel_ulong_t size);
>  extern void print_ifindex(unsigned int);
>  
>  struct number_set;
> -extern struct number_set read_set;
> -extern struct number_set write_set;
>  extern struct number_set signal_set;
>  
> -extern bool is_number_in_set(unsigned int number, const struct number_set *);
> -extern void qualify(const char *);
> -extern unsigned int qual_flags(const unsigned int);
> +extern bool is_number_in_set(const unsigned int, const struct number_set *);
> +extern void filter_syscall(struct tcb *);
> +extern void parse_qualify_filter(const char *);
>  
>  #define DECL_IOCTL(name)						\
>  extern int								\
> diff --git a/filter.c b/filter.c
> new file mode 100644
> index 0000000..46d3217
> --- /dev/null
> +++ b/filter.c
> @@ -0,0 +1,133 @@
> +/*
> + * 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"
> +
> +#define DECL_FILTER(name)					\
> +extern void*							\
> +parse_ ## name ## _filter(const char *, const char *const);	\
> +extern bool							\
> +run_ ## name ## _filter(struct tcb *, void *);			\
> +extern void							\
> +free_ ## name ## _filter(void *)
> +DECL_FILTER(syscall);
> +DECL_FILTER(fd);
> +DECL_FILTER(path);
> +#undef DECL_FILTER
It's pretty confusing that the functions defined in basic_filters.c are
declared here.

> +
> +#define FILTER_TYPE(name) 					\
Tabs after spaces.

> +{#name, parse_ ## name ## _filter, run_ ## name ## _filter,	\
> +	free_ ## name ## _filter}
> +
> +static const struct filter_type{
Missing space before the opening curly bracket.

> +	const char *name;
> +	void* (*parse_filter)(const char *, const char *const);
void *(*parse_filter)(const char *, const char *const);

> +	bool (*run_filter)(struct tcb *, void *);
> +	void (*_free_priv_data)(void *);
Not sure why you decided to prepend underscore here.

> +} filter_types[] = {
> +	FILTER_TYPE(syscall),
> +	FILTER_TYPE(fd)
> +};
> +
> +#undef FILTER_TYPE
> +
> +struct filter{
Missing space before the opening curly bracket.

> +	const struct filter_type *type;
> +	void *_priv_data;
> +};
> +
> +static const struct filter_type*
Missing space before the asterisk.

> +lookup_filter_type(const char *str)
> +{
> +	unsigned int i;
> +	for (i = 0; i < ARRAY_SIZE(filter_types); i++) {
> +		const char *name = filter_types[i].name;
> +		const size_t len = strlen(name);
This can be optimized (yay, premature optimisation) with storing
sizeof(name) - 1 in the struct filter_type.

> +		if (!strncmp(name, str, len)) {
Not sure whether strncmp is better than strcmp here, since this function
is not called with user-supplied str at all.

Or, do you expect that these will work with user-supplied strings in the
future?

BTW, personally, I don't think that this needs to be string-keyed.
Something as simple as

    enum filter_type_name {
            FILTER_TYPE_syscall,
            FILTER_TYPE_fd,
    };

    #define FILTER_TYPE(name) \
            [FILTER_TYPE_ ## name] = { \
                    .parse_filter = parse_ ## name ## _filter, \
                    .run_filter = run_ ## name ## _filter, \
                    ._free_priv_data = free_ ## name ## _filter, \
            }

    static const struct filter_type {
            void* (*parse_filter)(const char *, const char *const);
            bool (*run_filter)(struct tcb *, void *);
            void (*_free_priv_data)(void *);
    } filter_types[] = {
            FILTER_TYPE(syscall),
            FILTER_TYPE(fd),
    };

    struct filter *
    add_filter_to_array(struct filter **filters, unsigned int *nfilters,
                        enum filter_type_name name)
    {
            *filters = xreallocarray(*filters, ++(*nfilters),
                                     sizeof(struct filter));
            struct filter *filter = &((*filters)[*nfilters - 1]);
            filter->type = filters[name];
            return filter;
    }

    [...]

    #define create_filter(a, t) actual_create_filter(a, FILTER_TYPE_ ## t)

    [...]

    create_filter(action, fd);

should also suffice.

> +			return &filter_types[i];
> +		}
> +	}
> +	return NULL;
> +}
> +
> +struct filter *
> +add_filter_to_array(struct filter **filters, unsigned int *nfilters,
> +                    const char *name)
> +{
> +	const struct filter_type *type = lookup_filter_type(name);
> +	if (!type)
> +		return NULL;
> +	*filters = xreallocarray(*filters, ++(*nfilters),
> +	                         sizeof(struct filter));
> +	struct filter *filter = &((*filters)[*nfilters - 1]);
> +	filter->type = type;
> +	return filter;
> +}
> +
> +void
> +parse_filter(struct filter *filter, const char *str, const char *const name)
> +{
> +	filter->_priv_data = filter->type->parse_filter(str, name);
> +}
> +
> +static bool
> +run_filter(struct tcb *tcp, struct filter *filt)
> +{
> +	return filt->type->run_filter(tcp, filt->_priv_data);
> +}
Why filt there and filter in the previous function?

> +
> +bool*
Missing space before the asterisk.

> +run_filters(struct tcb *tcp, struct filter *filters, unsigned int nfilters)
> +{
> +	bool *variables = xcalloc(nfilters, sizeof(bool));
> +	unsigned int i;
> +	for (i = 0; i < nfilters; ++i) {
> +		variables[i] = run_filter(tcp, &filters[i]);
> +	}
> +	return variables;
> +}
> +
> +void
> +free_filter(struct filter *filt)
> +{
> +	if (!filt)
> +		return;
> +	filt->type->_free_priv_data(filt->_priv_data);
> +}
> +
> +void *
> +get_filter_priv_data(struct filter *filt)
> +{
> +	return filt ? filt->_priv_data : NULL;
> +}
> +
> +void
> +set_filter_priv_data(struct filter *filt, void *_priv_data)
> +{
> +	if (filt)
> +		filt->_priv_data = _priv_data;
> +}
> diff --git a/filter.h b/filter.h
> new file mode 100644
> index 0000000..f88cfa4
> --- /dev/null
> +++ b/filter.h
> @@ -0,0 +1,68 @@
> +/*
> + * 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.
> + */
> +#ifndef STRACE_FILTER_H
> +# define STRACE_FILTER_H
> +
> +struct filter;
> +
> +struct filter_action;
> +
> +struct bool_expression;
> +
> +typedef unsigned int number_slot_t;
> +struct number_set {
> +	number_slot_t *vec;
> +	unsigned int nslots;
> +	bool not;
> +};
> +
> +extern bool is_traced(struct tcb *);
> +
> +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);
> +
> +struct filter* add_filter_to_array(struct filter **, unsigned int *,
> +                                   const char *);
> +void parse_filter(struct filter *, const char *, const char *const);
> +bool* run_filters(struct tcb *, struct filter *, unsigned int);
> +void free_filter(struct filter *);
> +void* get_filter_priv_data(struct filter *);
> +void set_filter_priv_data(struct filter *, void *);
> +
> +struct filter* create_filter(struct filter_action *, const char *);
struct filter *create_filter(struct filter_action *, const char *);

> +void set_qualify_mode(struct filter_action *);
> +struct filter_action* add_action(const char *);
struct filter_action *add_action(const char *);

> +struct filter_action* find_or_add_action(const char *);
struct filter_action *find_or_add_action(const char *);

> +void* get_filter_action_priv_data(struct filter_action *);
> +void set_filter_action_priv_data(struct filter_action *, void *);
> +
> +struct bool_expression* create_expression();
struct bool_expression *create_expression(void);

> +void set_expression_qualify_mode(struct bool_expression *, unsigned int);
> +bool run_expression(struct bool_expression *, unsigned int, bool *);
> +
> +#endif
> diff --git a/filter_action.c b/filter_action.c
> new file mode 100644
> index 0000000..3a0bc00
> --- /dev/null
> +++ b/filter_action.c
> @@ -0,0 +1,184 @@
> +/*
> + * 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"
> +
> +#define DECL_FILTER_ACTION(name) 		\
Tabs after spaces.

> +extern void apply_ ## name(struct tcb *, void *)
Missing indentation. An there's probably no sense to move it to a
separate line.

> +
> +DECL_FILTER_ACTION(trace);
> +DECL_FILTER_ACTION(inject);
> +DECL_FILTER_ACTION(read);
> +DECL_FILTER_ACTION(write);
> +DECL_FILTER_ACTION(raw);
> +DECL_FILTER_ACTION(abbrev);
> +DECL_FILTER_ACTION(verbose);
> +
> +#undef DECL_FILTER_ACTION
And the same confusion about declarations and definitions.

> +
> +#define DECL_FILTER_ACTION_PARSER(name) 	\
Tabs after spaces.

> +extern void * parse_ ## name(const char *);	\
> +extern void free_ ## name(void *)
Missing indentation.

> +DECL_FILTER_ACTION_PARSER(null);
> +DECL_FILTER_ACTION_PARSER(inject);
> +
> +#undef DECL_FILTER_ACTION_PARSER
> +
> +#define FILTER_ACTION_TYPE(NAME, PRIORITY, PARSER, PREFILTER)		\
> +{.name = #NAME, .priority = PRIORITY, .parse_args = parse_ ## PARSER,	\
> +	._free_priv_data = free_ ## PARSER, .prefilter = PREFILTER,	\
> +	.apply = apply_ ## NAME}
> +
> +static const struct filter_action_type{
Missing space before the curly bracket.

> +	const char *name;
> +	unsigned int priority;
> +	void* (*parse_args)(const char *);
void *(*parse_args)(const char *);

> +	void (*_free_priv_data)(void *);
> +	bool (*prefilter)(struct tcb *);
> +	void (*apply)(struct tcb *, void *);
> +} action_types[] = {
> +	FILTER_ACTION_TYPE(trace,	2,	null,	NULL),
> +	FILTER_ACTION_TYPE(inject,	2,	inject,	NULL),
> +	FILTER_ACTION_TYPE(read,	1,	null,	is_traced),
> +	FILTER_ACTION_TYPE(write,	1,	null,	is_traced),
This is actually an interesting question: do we want I/O being dumped for
the calls which we do not trace? Current architecture doesn't allow that,
but is there a possibility that there are circumstances where it may make
sense?

> +	FILTER_ACTION_TYPE(raw,		1,	null,	is_traced),
> +	FILTER_ACTION_TYPE(abbrev,	1,	null,	is_traced),
> +	FILTER_ACTION_TYPE(verbose,	1,	null,	is_traced)
> +};
> +
> +#undef FILTER_ACTION_TYPE
> +
> +struct filter_action{
Missing space before the curly bracket.

> +	const struct filter_action_type* type;
const struct filter_action_type *type;

> +	struct bool_expression *expr;
> +	unsigned int nfilters;
> +	struct filter *filters;
> +	void* _priv_data;
> +};
> +
> +static struct filter_action *filter_actions;
> +static unsigned int nfilter_actions;
> +
> +static const struct filter_action_type*
Missing space before the asterisk.

> +lookup_filter_action_type(const char *str)
> +{
> +	unsigned int i;
> +	for (i = 0; i < ARRAY_SIZE(action_types); ++i) {
> +		const char *name = action_types[i].name;
> +		const size_t len = strlen(name);
> +		if (!strncmp(name, str, len)) {
> +			return &action_types[i];
> +		}
> +	}
> +	return NULL;
> +}
The same here, I don't see much benefit in action types being
string-keyed.

> +
> +struct filter_action*
Missing space before the asterisk.

> +add_action(const char *name)
> +{
> +	const struct filter_action_type *type = lookup_filter_action_type(name);
> +	if (!type)
> +		error_msg_and_die("invalid filter action '%s'", name);
> +	filter_actions = xreallocarray(filter_actions, ++nfilter_actions,
> +	                               sizeof(struct filter_action));
> +	struct filter_action *action = filter_actions + (nfilter_actions - 1);
> +	memset(action, 0, sizeof(*action));
> +	action->type = type;
> +	action->expr = create_expression();
> +	return action;
> +}
> +
> +struct filter_action*
Missing space before the asterisk.

> +find_or_add_action(const char *name)
> +{
> +	const struct filter_action_type *type = lookup_filter_action_type(name);
> +	if (!type)
> +		error_msg_and_die("invalid filter action '%s'", name);
> +	unsigned int i;
> +	for (i = 0; i < nfilter_actions; i++) {
> +		if (filter_actions[i].type == type)
> +			return (filter_actions + i);
> +	}
> +	return add_action(name);
> +}
> +
> +static void
> +run_filter_action(struct tcb *tcp, struct filter_action *filt_action)
> +{
> +	if (filt_action->type->prefilter
> +	    && !filt_action->type->prefilter(tcp))
I'm not quite sure how it is guaranteed that the flag would be set (like,
where the "trace filter runs first" order is implied).

> +		return;
> +	bool *variables = run_filters(tcp, filt_action->filters,
> +	                              filt_action->nfilters);
> +	bool res = run_expression(filt_action->expr,
> +	                          filt_action->nfilters, variables);
> +	free(variables);
> +	if (res) {
> +		filt_action->type->apply(tcp, filt_action->_priv_data);
> +	}
> +}
> +
> +struct filter*
Missing space before the asterisk.

> +create_filter(struct filter_action *filt_action, const char *name)
> +{
> +	if (!filt_action)
> +		error_msg_and_die("invalid filter action");
> +	return add_filter_to_array(&filt_action->filters,
> +	                           &filt_action->nfilters, name);
> +}
> +
> +void
> +set_qualify_mode(struct filter_action *filt_action)
> +{
> +	if ((!filt_action) || filt_action->nfilters == 0)
> +		error_msg_and_die("invalid filter action");
> +	set_expression_qualify_mode(filt_action->expr,
> +	                            filt_action->nfilters - 1);
> +}
> +
> +void
> +filter_syscall(struct tcb *tcp)
> +{
> +	unsigned int i;
> +	for (i = 0; i < nfilter_actions; i++) {
> +		run_filter_action(tcp, &filter_actions[i]);
> +	}
> +}
> +
> +void *
> +get_filter_action_priv_data(struct filter_action *action)
> +{
> +	return action ? action->_priv_data : NULL;
> +}
> +
> +void
> +set_filter_action_priv_data(struct filter_action *action, void *_priv_data)
> +{
> +	if (action)
> +		action->_priv_data = _priv_data;
> +}
> diff --git a/filter_expression.c b/filter_expression.c
> new file mode 100644
> index 0000000..550f93f
> --- /dev/null
> +++ b/filter_expression.c
> @@ -0,0 +1,132 @@
> +/*
> + * 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"
> +
> +struct expression_token{
> +	enum token_type{
> +		TOK_VARIABLE,
> +		TOK_OPERATOR
> +	} type;
> +	union token_data{
> +		unsigned int variable_id;
> +		enum operator_type{
> +			OP_NOT,
> +			OP_AND,
> +			OP_OR
> +		} operator_id;
> +	} data;
> +};
> +
> +struct bool_expression{
> +	unsigned int ntokens;
> +	struct expression_token *tokens;
> +};
> +
> +struct bool_expression *
> +create_expression(void)
> +{
> +	struct bool_expression *expr = xmalloc(sizeof(struct bool_expression));
> +	memset(expr, 0, sizeof(struct bool_expression));
> +	return expr;
> +}
> +
> +static void
> +reallocate_expression(struct bool_expression *const expr, const unsigned int new_ntokens)
> +{
> +	if (new_ntokens <= expr->ntokens)
> +		return;
> +	expr->tokens = xreallocarray(expr->tokens, new_ntokens,
> +	                             sizeof(*expr->tokens));
> +	memset(expr->tokens + expr->ntokens, 0,
> +	       sizeof(*expr->tokens) * (new_ntokens - expr->ntokens));
> +	expr->ntokens = new_ntokens;
> +}
> +
> +#define STACK_SIZE 32
> +static bool stack[STACK_SIZE];
> +
> +void
> +set_expression_qualify_mode(struct bool_expression *expr,
> +                            unsigned int variable_id)
> +{
> +	if (!expr)
> +		error_msg_and_die("invalid expression");
> +	reallocate_expression(expr, 1);
> +	expr->tokens[0].type = TOK_VARIABLE;
> +	expr->tokens[0].data.variable_id = variable_id;
> +}
> +
> +bool
> +run_expression(struct bool_expression *expr,
> +               unsigned int variables_num, bool *variables)
> +{
> +	/* Current stack index */
> +	unsigned int index = 0;
> +	struct expression_token *tok = expr->tokens;
> +	struct expression_token *const tok_end = expr->tokens + expr->ntokens;
> +	for (; tok != tok_end; ++tok) {
> +		switch (tok->type) {
> +		case TOK_VARIABLE:
> +			if (index == STACK_SIZE)
> +				error_msg_and_die("stack overflow");
> +			if (tok->data.variable_id >= variables_num)
> +				error_msg_and_die("corrupted filter "
> +				                  "expression");
> +			stack[index] = variables[tok->data.variable_id];
> +			index++;
> +			break;
> +		case TOK_OPERATOR:
> +			switch (tok->data.operator_id) {
> +			case OP_NOT:
> +				if (index == 0)
> +					error_msg_and_die("corrupted filter "
> +					                  "expression");
> +				stack[index - 1] = !stack[index - 1];
> +				break;
> +			case OP_AND:
> +				if (index < 2)
> +					error_msg_and_die("corrupted filter "
> +					                  "expression");
> +				stack[index - 2] = stack[index - 2]
> +				                   && stack[index - 1];
> +				--index;
> +				break;
> +			case OP_OR:
> +				if (index < 2)
> +					error_msg_and_die("corrupted filter "
> +					                  "expression");
> +				stack[index - 2] = stack[index - 2]
> +				                   || stack[index - 1];
> +				--index;
> +				break;
> +			}
> +		}
> +	}
> +	if (index != 1)
> +		error_msg_and_die("corrupted filter expression");
> +	return stack[0];
> +}
> diff --git a/filter_qualify.c b/filter_qualify.c
> new file mode 100644
> index 0000000..83e22e8
> --- /dev/null
> +++ b/filter_qualify.c
> @@ -0,0 +1,346 @@
> +/*
> + * 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 "nsig.h"
> +#include "filter.h"
> +
> +struct number_set signal_set;
> +
> +static int
> +find_errno_by_name(const char *name)
> +{
> +	unsigned int i;
> +
> +	for (i = 1; i < nerrnos; ++i) {
> +		if (errnoent[i] && (strcasecmp(name, errnoent[i]) == 0))
> +			return i;
> +	}
> +
> +	return -1;
> +}
> +
> +static int
> +sigstr_to_uint(const char *s)
> +{
> +	int i;
> +
> +	if (*s >= '0' && *s <= '9')
> +		return string_to_uint_upto(s, 255);
> +
> +	if (strncasecmp(s, "SIG", 3) == 0)
> +		s += 3;
> +
> +	for (i = 0; i <= 255; ++i) {
> +		const char *name = signame(i);
> +
> +		if (strncasecmp(name, "SIG", 3) != 0)
> +			continue;
> +
> +		name += 3;
> +
> +		if (strcasecmp(name, s) != 0)
> +			continue;
> +
> +		return i;
> +	}
> +
> +	return -1;
> +}
> +
> +static bool
> +parse_inject_token(const char *const token, struct inject_opts *const fopts,
> +		   const bool fault_tokens_only)
> +{
> +	const char *val;
> +	int intval;
> +
> +	if ((val = STR_STRIP_PREFIX(token, "when=")) != token) {
> +		/*
> +		 * 	== 1+1
> +		 * F	== F+0
> +		 * F+	== F+1
> +		 * F+S
> +		 */
> +		char *end;
> +		intval = string_to_uint_ex(val, &end, 0xffff, "+");
> +		if (intval < 1)
> +			return false;
> +
> +		fopts->first = intval;
> +
> +		if (*end) {
> +			val = end + 1;
> +			if (*val) {
> +				/* F+S */
> +				intval = string_to_uint_upto(val, 0xffff);
> +				if (intval < 1)
> +					return false;
> +				fopts->step = intval;
> +			} else {
> +				/* F+ == F+1 */
> +				fopts->step = 1;
> +			}
> +		} else {
> +			/* F == F+0 */
> +			fopts->step = 0;
> +		}
> +	} else if ((val = STR_STRIP_PREFIX(token, "error=")) != token) {
> +		if (fopts->rval != INJECT_OPTS_RVAL_DEFAULT)
> +			return false;
> +		intval = string_to_uint_upto(val, MAX_ERRNO_VALUE);
> +		if (intval < 0)
> +			intval = find_errno_by_name(val);
> +		if (intval < 1)
> +			return false;
> +		fopts->rval = -intval;
> +	} else if (!fault_tokens_only
> +		   && (val = STR_STRIP_PREFIX(token, "retval=")) != token) {
> +		if (fopts->rval != INJECT_OPTS_RVAL_DEFAULT)
> +			return false;
> +		intval = string_to_uint(val);
> +		if (intval < 0)
> +			return false;
> +		fopts->rval = intval;
> +	} else if (!fault_tokens_only
> +		   && (val = STR_STRIP_PREFIX(token, "signal=")) != token) {
> +		intval = sigstr_to_uint(val);
> +		if (intval < 1 || intval > NSIG_BYTES * 8)
> +			return false;
> +		fopts->signo = intval;
> +	} else {
> +		return false;
> +	}
> +
> +	return true;
> +}
> +
> +static char *
> +parse_inject_expression(const char *const s, char **buf,
> +			struct inject_opts *const fopts,
> +			const bool fault_tokens_only)
> +{
> +	char *saveptr = NULL;
> +	char *name = NULL;
> +	char *token;
> +
> +	*buf = xstrdup(s);
> +	for (token = strtok_r(*buf, ":", &saveptr); token;
> +	     token = strtok_r(NULL, ":", &saveptr)) {
> +		if (!name)
> +			name = token;
> +		else if (!parse_inject_token(token, fopts, fault_tokens_only))
> +			goto parse_error;
> +	}
> +
> +	if (name)
> +		return name;
> +
> +parse_error:
> +	free(*buf);
> +	return *buf = NULL;
> +}
> +
> +static void
> +parse_read(const char *const str)
> +{
> +	struct filter_action *action = find_or_add_action("read");
> +	struct filter *filter = create_filter(action, "fd");
> +	parse_filter(filter, str, "descriptor");
> +	set_qualify_mode(action);
> +}
> +
> +static void
> +parse_write(const char *const str)
> +{
> +	struct filter_action *action = find_or_add_action("write");
> +	struct filter *filter = create_filter(action, "fd");
> +	parse_filter(filter, str, "descriptor");
> +	set_qualify_mode(action);
> +}
> +
> +static void
> +qualify_signals(const char *const str)
> +{
> +	parse_set(str, &signal_set, sigstr_to_uint, "signal");
> +}
> +
> +static void
> +parse_trace(const char *const str)
> +{
> +	struct filter_action *action = find_or_add_action("trace");
> +	struct filter *filter = create_filter(action, "syscall");
> +	parse_filter(filter, str, "system call");
> +	set_qualify_mode(action);
> +}
> +
> +static void
> +parse_abbrev(const char *const str)
> +{
> +	struct filter_action *action = find_or_add_action("abbrev");
> +	struct filter *filter = create_filter(action, "syscall");
> +	parse_filter(filter, str, "system call");
> +	set_qualify_mode(action);
> +}
> +
> +static void
> +parse_verbose(const char *const str)
> +{
> +	struct filter_action *action = find_or_add_action("verbose");
> +	struct filter *filter = create_filter(action, "syscall");
> +	parse_filter(filter, str, "system call");
> +	set_qualify_mode(action);
> +}
> +
> +static void
> +parse_raw(const char *const str)
> +{
> +	struct filter_action *action = find_or_add_action("raw");
> +	struct filter *filter = create_filter(action, "syscall");
> +	parse_filter(filter, str, "system call");
> +	set_qualify_mode(action);
> +}
> +
> +static void
> +parse_inject_common(const char *const str,
> +		      const bool fault_tokens_only,
> +		      const char *const description)
> +{
> +	struct inject_opts opts = {
> +		.first = 1,
> +		.step = 1,
> +		.rval = INJECT_OPTS_RVAL_DEFAULT,
> +		.signo = 0
> +	};
> +	char *buf = NULL;
> +	char *name = parse_inject_expression(str, &buf, &opts, fault_tokens_only);
> +	if (!name) {
> +		error_msg_and_die("invalid %s '%s'", description, str);
> +	}
> +
> +	/* If neither of retval, error, or signal is specified, then ... */
> +	if (opts.rval == INJECT_OPTS_RVAL_DEFAULT && !opts.signo) {
> +		if (fault_tokens_only) {
> +			/* in fault= syntax the default error code is ENOSYS. */
> +			opts.rval = -ENOSYS;
> +		} else {
> +			/* in inject= syntax this is not allowed. */
> +			error_msg_and_die("invalid %s '%s'", description, str);
> +		}
> +	}
> +
> +	struct filter_action *action = add_action("inject");
> +	struct filter *filter = create_filter(action, "syscall");
> +	parse_filter(filter, name, description);
> +	set_qualify_mode(action);
> +
> +	free(buf);
> +	const struct number_set *tmp_set = (const struct number_set *)
> +	                             get_filter_priv_data(filter);
> +	struct inject_opts **inject_vec = xcalloc(SUPPORTED_PERSONALITIES,
> +	                                          sizeof(struct inject_opts *));
> +	/*
> +	 * Initialize inject_vec accourding to tmp_set.
> +	 */
> +
> +	unsigned int p;
> +	for (p = 0; p < SUPPORTED_PERSONALITIES; ++p) {
> +		if (!tmp_set[p].nslots && !tmp_set[p].not) {
> +			continue;
> +		}
> +
> +		if (!inject_vec[p]) {
> +			inject_vec[p] = xcalloc(nsyscall_vec[p],
> +					       sizeof(*inject_vec[p]));
> +		}
> +
> +		unsigned int i;
> +		for (i = 0; i < nsyscall_vec[p]; ++i) {
> +			if (is_number_in_set(i, &tmp_set[p])) {
> +				inject_vec[p][i] = opts;
> +			}
> +		}
> +	}
> +	set_filter_action_priv_data(action, inject_vec);
> +}
> +
> +static void
> +parse_fault(const char *const str)
> +{
> +	parse_inject_common(str, true, "fault argument");
> +}
> +
> +static void
> +parse_inject(const char *const str)
> +{
> +	parse_inject_common(str, false, "inject argument");
> +}
> +
> +static const struct qual_options {
> +	const char *name;
> +	void (*qualify)(const char *);
> +} qual_options[] = {
> +	{ "trace",	parse_trace	},
> +	{ "t",		parse_trace	},
> +	{ "abbrev",	parse_abbrev	},
> +	{ "a",		parse_abbrev	},
> +	{ "verbose",	parse_verbose	},
> +	{ "v",		parse_verbose	},
> +	{ "raw",	parse_raw	},
> +	{ "x",		parse_raw	},
> +	{ "signal",	qualify_signals	},
> +	{ "signals",	qualify_signals	},
> +	{ "s",		qualify_signals	},
> +	{ "read",	parse_read	},
> +	{ "reads",	parse_read	},
> +	{ "r",		parse_read	},
> +	{ "write",	parse_write	},
> +	{ "writes",	parse_write	},
> +	{ "w",		parse_write	},
> +	{ "fault",	parse_fault	},
> +	{ "inject",	parse_inject	},
> +};
> +
> +
> +void
> +parse_qualify_filter(const char *str)
> +{
> +	const struct qual_options *opt = qual_options;
> +	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;
> +	}
> +	opt->qualify(str);
> +}
> diff --git a/qualify.c b/qualify.c
> deleted file mode 100644
> index de1141d..0000000
> --- a/qualify.c
> +++ /dev/null
> @@ -1,697 +0,0 @@
> -/*
> - * Copyright (c) 2016 Dmitry V. Levin <ldv at altlinux.org>
> - * Copyright (c) 2016-2017 The strace developers.
> - * 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 "nsig.h"
> -#include <regex.h>
> -
> -typedef unsigned int number_slot_t;
> -#define BITS_PER_SLOT (sizeof(number_slot_t) * 8)
> -
> -struct number_set {
> -	number_slot_t *vec;
> -	unsigned int nslots;
> -	bool not;
> -};
> -
> -struct number_set read_set;
> -struct number_set write_set;
> -struct number_set signal_set;
> -
> -static struct number_set abbrev_set[SUPPORTED_PERSONALITIES];
> -static struct number_set inject_set[SUPPORTED_PERSONALITIES];
> -static struct number_set raw_set[SUPPORTED_PERSONALITIES];
> -static struct number_set trace_set[SUPPORTED_PERSONALITIES];
> -static struct number_set verbose_set[SUPPORTED_PERSONALITIES];
> -
> -static void
> -number_setbit(const unsigned int i, number_slot_t *const vec)
> -{
> -	vec[i / BITS_PER_SLOT] |= (number_slot_t) 1 << (i % BITS_PER_SLOT);
> -}
> -
> -static bool
> -number_isset(const unsigned int i, const number_slot_t *const vec)
> -{
> -	return vec[i / BITS_PER_SLOT] & ((number_slot_t) 1 << (i % BITS_PER_SLOT));
> -}
> -
> -static void
> -reallocate_number_set(struct number_set *const set, const unsigned int new_nslots)
> -{
> -	if (new_nslots <= set->nslots)
> -		return;
> -	set->vec = xreallocarray(set->vec, new_nslots, sizeof(*set->vec));
> -	memset(set->vec + set->nslots, 0,
> -	       sizeof(*set->vec) * (new_nslots - set->nslots));
> -	set->nslots = new_nslots;
> -}
> -
> -static void
> -add_number_to_set(const unsigned int number, struct number_set *const set)
> -{
> -	reallocate_number_set(set, number / BITS_PER_SLOT + 1);
> -	number_setbit(number, set->vec);
> -}
> -
> -bool
> -is_number_in_set(const unsigned int number, const struct number_set *const set)
> -{
> -	return ((number / BITS_PER_SLOT < set->nslots)
> -		&& number_isset(number, set->vec)) ^ set->not;
> -}
> -
> -typedef int (*string_to_uint_func)(const char *);
> -
> -/*
> - * Add numbers to SET according to STR specification.
> - */
> -static void
> -qualify_tokens(const char *const str, struct number_set *const set,
> -	       string_to_uint_func func, const char *const name)
> -{
> -	/* Clear the set. */
> -	if (set->nslots)
> -		memset(set->vec, 0, sizeof(*set->vec) * set->nslots);
> -	set->not = false;
> -
> -	/*
> -	 * Each leading ! character means inversion
> -	 * of the remaining specification.
> -	 */
> -	const char *s = str;
> -handle_inversion:
> -	while (*s == '!') {
> -		set->not = !set->not;
> -		++s;
> -	}
> -
> -	if (strcmp(s, "none") == 0) {
> -		/*
> -		 * No numbers are added to the set.
> -		 * Subsequent is_number_in_set invocations will return set->not.
> -		 */
> -		return;
> -	} else if (strcmp(s, "all") == 0) {
> -		s = "!none";
> -		goto handle_inversion;
> -	}
> -
> -	/*
> -	 * Split the string into comma separated tokens.
> -	 * For each token, find out the corresponding number
> -	 * by calling FUNC, and add that number to the set.
> -	 * The absence of tokens or a negative answer
> -	 * from FUNC is a fatal error.
> -	 */
> -	char *copy = xstrdup(s);
> -	char *saveptr = NULL;
> -	const char *token;
> -	int number = -1;
> -
> -	for (token = strtok_r(copy, ",", &saveptr); token;
> -	     token = strtok_r(NULL, ",", &saveptr)) {
> -		number = func(token);
> -		if (number < 0) {
> -			error_msg_and_die("invalid %s '%s'", name, token);
> -		}
> -
> -		add_number_to_set(number, set);
> -	}
> -
> -	free(copy);
> -
> -	if (number < 0) {
> -		error_msg_and_die("invalid %s '%s'", name, str);
> -	}
> -}
> -
> -static int
> -sigstr_to_uint(const char *s)
> -{
> -	int i;
> -
> -	if (*s >= '0' && *s <= '9')
> -		return string_to_uint_upto(s, 255);
> -
> -	if (strncasecmp(s, "SIG", 3) == 0)
> -		s += 3;
> -
> -	for (i = 0; i <= 255; ++i) {
> -		const char *name = signame(i);
> -
> -		if (strncasecmp(name, "SIG", 3) != 0)
> -			continue;
> -
> -		name += 3;
> -
> -		if (strcasecmp(name, s) != 0)
> -			continue;
> -
> -		return i;
> -	}
> -
> -	return -1;
> -}
> -
> -static bool
> -qualify_syscall_number(const char *s, struct number_set *set)
> -{
> -	int n = string_to_uint(s);
> -	if (n < 0)
> -		return false;
> -
> -	unsigned int p;
> -	bool done = false;
> -
> -	for (p = 0; p < SUPPORTED_PERSONALITIES; ++p) {
> -		if ((unsigned) n >= nsyscall_vec[p]) {
> -			continue;
> -		}
> -		add_number_to_set(n, &set[p]);
> -		done = true;
> -	}
> -
> -	return done;
> -}
> -
> -static void
> -regerror_msg_and_die(int errcode, const regex_t *preg,
> -		     const char *str, const char *pattern)
> -{
> -	char buf[512];
> -
> -	regerror(errcode, preg, buf, sizeof(buf));
> -	error_msg_and_die("%s: %s: %s", str, pattern, buf);
> -}
> -
> -static bool
> -qualify_syscall_regex(const char *s, struct number_set *set)
> -{
> -	regex_t preg;
> -	int rc;
> -
> -	if ((rc = regcomp(&preg, s, REG_EXTENDED | REG_NOSUB)) != 0)
> -		regerror_msg_and_die(rc, &preg, "regcomp", s);
> -
> -	unsigned int p;
> -	bool found = false;
> -	for (p = 0; p < SUPPORTED_PERSONALITIES; ++p) {
> -		unsigned int i;
> -
> -		for (i = 0; i < nsyscall_vec[p]; ++i) {
> -			if (!sysent_vec[p][i].sys_name)
> -				continue;
> -			rc = regexec(&preg, sysent_vec[p][i].sys_name,
> -				     0, NULL, 0);
> -			if (rc == REG_NOMATCH)
> -				continue;
> -			else if (rc)
> -				regerror_msg_and_die(rc, &preg, "regexec", s);
> -			add_number_to_set(i, &set[p]);
> -			found = true;
> -		}
> -	}
> -
> -	regfree(&preg);
> -	return found;
> -}
> -
> -static unsigned int
> -lookup_class(const char *s)
> -{
> -	static const struct {
> -		const char *name;
> -		unsigned int value;
> -	} syscall_class[] = {
> -		{ "desc",	TRACE_DESC	},
> -		{ "file",	TRACE_FILE	},
> -		{ "memory",	TRACE_MEMORY	},
> -		{ "process",	TRACE_PROCESS	},
> -		{ "signal",	TRACE_SIGNAL	},
> -		{ "ipc",	TRACE_IPC	},
> -		{ "network",	TRACE_NETWORK	},
> -		{ "%desc",	TRACE_DESC	},
> -		{ "%file",	TRACE_FILE	},
> -		{ "%memory",	TRACE_MEMORY	},
> -		{ "%process",	TRACE_PROCESS	},
> -		{ "%signal",	TRACE_SIGNAL	},
> -		{ "%ipc",	TRACE_IPC	},
> -		{ "%network",	TRACE_NETWORK	},
> -		{ "%stat",	TRACE_STAT	},
> -		{ "%lstat",	TRACE_LSTAT	},
> -		{ "%fstat",	TRACE_FSTAT	},
> -		{ "%%stat",	TRACE_STAT_LIKE	},
> -		{ "%statfs",	TRACE_STATFS	},
> -		{ "%fstatfs",	TRACE_FSTATFS	},
> -		{ "%%statfs",	TRACE_STATFS_LIKE	},
> -	};
> -
> -	unsigned int i;
> -	for (i = 0; i < ARRAY_SIZE(syscall_class); ++i) {
> -		if (strcmp(s, syscall_class[i].name) == 0) {
> -			return syscall_class[i].value;
> -		}
> -	}
> -
> -	return 0;
> -}
> -
> -static bool
> -qualify_syscall_class(const char *s, struct number_set *set)
> -{
> -	const unsigned int n = lookup_class(s);
> -	if (!n)
> -		return false;
> -
> -	unsigned int p;
> -	for (p = 0; p < SUPPORTED_PERSONALITIES; ++p) {
> -		unsigned int i;
> -
> -		for (i = 0; i < nsyscall_vec[p]; ++i) {
> -			if (!sysent_vec[p][i].sys_name
> -			    || (sysent_vec[p][i].sys_flags & n) != n) {
> -				continue;
> -			}
> -			add_number_to_set(i, &set[p]);
> -		}
> -	}
> -
> -	return true;
> -}
> -
> -static bool
> -qualify_syscall_name(const char *s, struct number_set *set)
> -{
> -	unsigned int p;
> -	bool found = false;
> -
> -	for (p = 0; p < SUPPORTED_PERSONALITIES; ++p) {
> -		unsigned int i;
> -
> -		for (i = 0; i < nsyscall_vec[p]; ++i) {
> -			if (!sysent_vec[p][i].sys_name
> -			    || strcmp(s, sysent_vec[p][i].sys_name)) {
> -				continue;
> -			}
> -			add_number_to_set(i, &set[p]);
> -			found = true;
> -		}
> -	}
> -
> -	return found;
> -}
> -
> -static bool
> -qualify_syscall(const char *token, struct number_set *set)
> -{
> -	bool ignore_fail = false;
> -
> -	while (*token == '?') {
> -		token++;
> -		ignore_fail = true;
> -	}
> -	if (*token >= '0' && *token <= '9')
> -		return qualify_syscall_number(token, set) || ignore_fail;
> -	if (*token == '/')
> -		return qualify_syscall_regex(token + 1, set) || ignore_fail;
> -	return qualify_syscall_class(token, set)
> -	       || qualify_syscall_name(token, set)
> -	       || ignore_fail;
> -}
> -
> -/*
> - * Add syscall numbers to SETs for each supported personality
> - * according to STR specification.
> - */
> -static void
> -qualify_syscall_tokens(const char *const str, struct number_set *const set,
> -		       const char *const name)
> -{
> -	/* Clear all sets. */
> -	unsigned int p;
> -	for (p = 0; p < SUPPORTED_PERSONALITIES; ++p) {
> -		if (set[p].nslots)
> -			memset(set[p].vec, 0,
> -			       sizeof(*set[p].vec) * set[p].nslots);
> -		set[p].not = false;
> -	}
> -
> -	/*
> -	 * Each leading ! character means inversion
> -	 * of the remaining specification.
> -	 */
> -	const char *s = str;
> -handle_inversion:
> -	while (*s == '!') {
> -		for (p = 0; p < SUPPORTED_PERSONALITIES; ++p) {
> -			set[p].not = !set[p].not;
> -		}
> -		++s;
> -	}
> -
> -	if (strcmp(s, "none") == 0) {
> -		/*
> -		 * No syscall numbers are added to sets.
> -		 * Subsequent is_number_in_set invocations
> -		 * will return set[p]->not.
> -		 */
> -		return;
> -	} else if (strcmp(s, "all") == 0) {
> -		s = "!none";
> -		goto handle_inversion;
> -	}
> -
> -	/*
> -	 * Split the string into comma separated tokens.
> -	 * For each token, call qualify_syscall that will take care
> -	 * if adding appropriate syscall numbers to sets.
> -	 * The absence of tokens or a negative return code
> -	 * from qualify_syscall is a fatal error.
> -	 */
> -	char *copy = xstrdup(s);
> -	char *saveptr = NULL;
> -	const char *token;
> -	bool done = false;
> -
> -	for (token = strtok_r(copy, ",", &saveptr); token;
> -	     token = strtok_r(NULL, ",", &saveptr)) {
> -		done = qualify_syscall(token, set);
> -		if (!done) {
> -			error_msg_and_die("invalid %s '%s'", name, token);
> -		}
> -	}
> -
> -	free(copy);
> -
> -	if (!done) {
> -		error_msg_and_die("invalid %s '%s'", name, str);
> -	}
> -}
> -
> -static int
> -find_errno_by_name(const char *name)
> -{
> -	unsigned int i;
> -
> -	for (i = 1; i < nerrnos; ++i) {
> -		if (errnoent[i] && (strcasecmp(name, errnoent[i]) == 0))
> -			return i;
> -	}
> -
> -	return -1;
> -}
> -
> -static bool
> -parse_inject_token(const char *const token, struct inject_opts *const fopts,
> -		   const bool fault_tokens_only)
> -{
> -	const char *val;
> -	int intval;
> -
> -	if ((val = STR_STRIP_PREFIX(token, "when=")) != token) {
> -		/*
> -		 * 	== 1+1
> -		 * F	== F+0
> -		 * F+	== F+1
> -		 * F+S
> -		 */
> -		char *end;
> -		intval = string_to_uint_ex(val, &end, 0xffff, "+");
> -		if (intval < 1)
> -			return false;
> -
> -		fopts->first = intval;
> -
> -		if (*end) {
> -			val = end + 1;
> -			if (*val) {
> -				/* F+S */
> -				intval = string_to_uint_upto(val, 0xffff);
> -				if (intval < 1)
> -					return false;
> -				fopts->step = intval;
> -			} else {
> -				/* F+ == F+1 */
> -				fopts->step = 1;
> -			}
> -		} else {
> -			/* F == F+0 */
> -			fopts->step = 0;
> -		}
> -	} else if ((val = STR_STRIP_PREFIX(token, "error=")) != token) {
> -		if (fopts->rval != INJECT_OPTS_RVAL_DEFAULT)
> -			return false;
> -		intval = string_to_uint_upto(val, MAX_ERRNO_VALUE);
> -		if (intval < 0)
> -			intval = find_errno_by_name(val);
> -		if (intval < 1)
> -			return false;
> -		fopts->rval = -intval;
> -	} else if (!fault_tokens_only
> -		   && (val = STR_STRIP_PREFIX(token, "retval=")) != token) {
> -		if (fopts->rval != INJECT_OPTS_RVAL_DEFAULT)
> -			return false;
> -		intval = string_to_uint(val);
> -		if (intval < 0)
> -			return false;
> -		fopts->rval = intval;
> -	} else if (!fault_tokens_only
> -		   && (val = STR_STRIP_PREFIX(token, "signal=")) != token) {
> -		intval = sigstr_to_uint(val);
> -		if (intval < 1 || intval > NSIG_BYTES * 8)
> -			return false;
> -		fopts->signo = intval;
> -	} else {
> -		return false;
> -	}
> -
> -	return true;
> -}
> -
> -static char *
> -parse_inject_expression(const char *const s, char **buf,
> -			struct inject_opts *const fopts,
> -			const bool fault_tokens_only)
> -{
> -	char *saveptr = NULL;
> -	char *name = NULL;
> -	char *token;
> -
> -	*buf = xstrdup(s);
> -	for (token = strtok_r(*buf, ":", &saveptr); token;
> -	     token = strtok_r(NULL, ":", &saveptr)) {
> -		if (!name)
> -			name = token;
> -		else if (!parse_inject_token(token, fopts, fault_tokens_only))
> -			goto parse_error;
> -	}
> -
> -	if (name)
> -		return name;
> -
> -parse_error:
> -	free(*buf);
> -	return *buf = NULL;
> -}
> -
> -static void
> -qualify_read(const char *const str)
> -{
> -	qualify_tokens(str, &read_set, string_to_uint, "descriptor");
> -}
> -
> -static void
> -qualify_write(const char *const str)
> -{
> -	qualify_tokens(str, &write_set, string_to_uint, "descriptor");
> -}
> -
> -static void
> -qualify_signals(const char *const str)
> -{
> -	qualify_tokens(str, &signal_set, sigstr_to_uint, "signal");
> -}
> -
> -static void
> -qualify_trace(const char *const str)
> -{
> -	qualify_syscall_tokens(str, trace_set, "system call");
> -}
> -
> -static void
> -qualify_abbrev(const char *const str)
> -{
> -	qualify_syscall_tokens(str, abbrev_set, "system call");
> -}
> -
> -static void
> -qualify_verbose(const char *const str)
> -{
> -	qualify_syscall_tokens(str, verbose_set, "system call");
> -}
> -
> -static void
> -qualify_raw(const char *const str)
> -{
> -	qualify_syscall_tokens(str, raw_set, "system call");
> -}
> -
> -static void
> -qualify_inject_common(const char *const str,
> -		      const bool fault_tokens_only,
> -		      const char *const description)
> -{
> -	struct inject_opts opts = {
> -		.first = 1,
> -		.step = 1,
> -		.rval = INJECT_OPTS_RVAL_DEFAULT,
> -		.signo = 0
> -	};
> -	char *buf = NULL;
> -	char *name = parse_inject_expression(str, &buf, &opts, fault_tokens_only);
> -	if (!name) {
> -		error_msg_and_die("invalid %s '%s'", description, str);
> -	}
> -
> -	/* If neither of retval, error, or signal is specified, then ... */
> -	if (opts.rval == INJECT_OPTS_RVAL_DEFAULT && !opts.signo) {
> -		if (fault_tokens_only) {
> -			/* in fault= syntax the default error code is ENOSYS. */
> -			opts.rval = -ENOSYS;
> -		} else {
> -			/* in inject= syntax this is not allowed. */
> -			error_msg_and_die("invalid %s '%s'", description, str);
> -		}
> -	}
> -
> -	struct number_set tmp_set[SUPPORTED_PERSONALITIES];
> -	memset(tmp_set, 0, sizeof(tmp_set));
> -	qualify_syscall_tokens(name, tmp_set, description);
> -
> -	free(buf);
> -
> -	/*
> -	 * Initialize inject_vec accourding to tmp_set.
> -	 * Merge tmp_set into inject_set.
> -	 */
> -	unsigned int p;
> -	for (p = 0; p < SUPPORTED_PERSONALITIES; ++p) {
> -		if (!tmp_set[p].nslots && !tmp_set[p].not) {
> -			continue;
> -		}
> -
> -		if (!inject_vec[p]) {
> -			inject_vec[p] = xcalloc(nsyscall_vec[p],
> -					       sizeof(*inject_vec[p]));
> -		}
> -
> -		unsigned int i;
> -		for (i = 0; i < nsyscall_vec[p]; ++i) {
> -			if (is_number_in_set(i, &tmp_set[p])) {
> -				add_number_to_set(i, &inject_set[p]);
> -				inject_vec[p][i] = opts;
> -			}
> -		}
> -
> -		free(tmp_set[p].vec);
> -	}
> -}
> -
> -static void
> -qualify_fault(const char *const str)
> -{
> -	qualify_inject_common(str, true, "fault argument");
> -}
> -
> -static void
> -qualify_inject(const char *const str)
> -{
> -	qualify_inject_common(str, false, "inject argument");
> -}
> -
> -static const struct qual_options {
> -	const char *name;
> -	void (*qualify)(const char *);
> -} qual_options[] = {
> -	{ "trace",	qualify_trace	},
> -	{ "t",		qualify_trace	},
> -	{ "abbrev",	qualify_abbrev	},
> -	{ "a",		qualify_abbrev	},
> -	{ "verbose",	qualify_verbose	},
> -	{ "v",		qualify_verbose	},
> -	{ "raw",	qualify_raw	},
> -	{ "x",		qualify_raw	},
> -	{ "signal",	qualify_signals	},
> -	{ "signals",	qualify_signals	},
> -	{ "s",		qualify_signals	},
> -	{ "read",	qualify_read	},
> -	{ "reads",	qualify_read	},
> -	{ "r",		qualify_read	},
> -	{ "write",	qualify_write	},
> -	{ "writes",	qualify_write	},
> -	{ "w",		qualify_write	},
> -	{ "fault",	qualify_fault	},
> -	{ "inject",	qualify_inject	},
> -};
> -
> -void
> -qualify(const char *str)
> -{
> -	const struct qual_options *opt = qual_options;
> -	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;
> -	}
> -
> -	opt->qualify(str);
> -}
> -
> -unsigned int
> -qual_flags(const unsigned int scno)
> -{
> -	return	(is_number_in_set(scno, &trace_set[current_personality])
> -		   ? QUAL_TRACE : 0)
> -		| (is_number_in_set(scno, &abbrev_set[current_personality])
> -		   ? QUAL_ABBREV : 0)
> -		| (is_number_in_set(scno, &verbose_set[current_personality])
> -		   ? QUAL_VERBOSE : 0)
> -		| (is_number_in_set(scno, &raw_set[current_personality])
> -		   ? QUAL_RAW : 0)
> -		| (is_number_in_set(scno, &inject_set[current_personality])
> -		   ? QUAL_INJECT : 0);
> -}
> diff --git a/strace.c b/strace.c
> index 5a1bcff..0e62262 100644
> --- a/strace.c
> +++ b/strace.c
> @@ -824,10 +824,6 @@ droptcb(struct tcb *tcp)
>  	if (tcp->pid == 0)
>  		return;
>  
> -	int p;
> -	for (p = 0; p < SUPPORTED_PERSONALITIES; ++p)
> -		free(tcp->inject_vec[p]);
> -
>  	free_tcb_priv_data(tcp);
>  
>  #ifdef USE_LIBUNWIND
> @@ -1633,13 +1629,13 @@ init(int argc, char *argv[])
>  	shared_log = stderr;
>  	set_sortby(DEFAULT_SORTBY);
>  	set_personality(DEFAULT_PERSONALITY);
> -	qualify("trace=all");
> -	qualify("abbrev=all");
> -	qualify("verbose=all");
> +	parse_qualify_filter("trace=all");
> +	parse_qualify_filter("abbrev=all");
> +	parse_qualify_filter("verbose=all");
>  #if DEFAULT_QUAL_FLAGS != (QUAL_TRACE | QUAL_ABBREV | QUAL_VERBOSE)
>  # error Bug in DEFAULT_QUAL_FLAGS
>  #endif
> -	qualify("signal=all");
> +	parse_qualify_filter("signal=all");
>  	while ((c = getopt(argc, argv,
>  		"+b:cCdfFhiqrtTvVwxyz"
>  #ifdef USE_LIBUNWIND
> @@ -1706,7 +1702,7 @@ init(int argc, char *argv[])
>  			show_fd_path++;
>  			break;
>  		case 'v':
> -			qualify("abbrev=none");
> +			parse_qualify_filter("abbrev=none");
>  			break;
>  		case 'V':
>  			print_version();
> @@ -1721,7 +1717,7 @@ init(int argc, char *argv[])
>  				error_opt_arg(c, optarg);
>  			break;
>  		case 'e':
> -			qualify(optarg);
> +			parse_qualify_filter(optarg);
>  			break;
>  		case 'o':
>  			outfname = xstrdup(optarg);
> @@ -2468,6 +2464,8 @@ trace_syscall(struct tcb *tcp, unsigned int *sig)
>  		case 0:
>  			return 0;
>  		case 1:
> +			if (!tcp->qual_flg)
> +				filter_syscall(tcp);
>  			res = syscall_entering_trace(tcp, sig);
>  		}
>  		syscall_entering_finish(tcp, res);
> diff --git a/syscall.c b/syscall.c
> index 0250540..49908d1 100644
> --- a/syscall.c
> +++ b/syscall.c
> @@ -382,7 +382,6 @@ decode_socket_subcall(struct tcb *tcp)
>  		return;
>  
>  	tcp->scno = scno;
> -	tcp->qual_flg = qual_flags(scno);
>  	tcp->s_ent = &sysent[scno];
>  
>  	unsigned int i;
> @@ -422,7 +421,6 @@ decode_ipc_subcall(struct tcb *tcp)
>  	}
>  
>  	tcp->scno = SYS_ipc_subcall + call;
> -	tcp->qual_flg = qual_flags(tcp->scno);
>  	tcp->s_ent = &sysent[tcp->scno];
>  
>  	const unsigned int n = tcp->s_ent->nargs;
> @@ -439,7 +437,6 @@ decode_mips_subcall(struct tcb *tcp)
>  	if (!scno_is_valid(tcp->u_arg[0]))
>  		return;
>  	tcp->scno = tcp->u_arg[0];
> -	tcp->qual_flg = qual_flags(tcp->scno);
>  	tcp->s_ent = &sysent[tcp->scno];
>  	memmove(&tcp->u_arg[0], &tcp->u_arg[1],
>  		sizeof(tcp->u_arg) - sizeof(tcp->u_arg[0]));
> @@ -468,7 +465,7 @@ dumpio(struct tcb *tcp)
>  	if (fd < 0)
>  		return;
>  
> -	if (is_number_in_set(fd, &read_set)) {
> +	if (tcp->qual_flg & QUAL_READ) {
Some macro can be added for this check, similar to verbose(tcp) and alike.

>  		switch (tcp->s_ent->sen) {
>  		case SEN_read:
>  		case SEN_pread:
> @@ -491,7 +488,7 @@ dumpio(struct tcb *tcp)
>  			return;
>  		}
>  	}
> -	if (is_number_in_set(fd, &write_set)) {
> +	if (tcp->qual_flg & QUAL_WRITE) {
>  		switch (tcp->s_ent->sen) {
>  		case SEN_write:
>  		case SEN_pwrite:
> @@ -577,28 +574,10 @@ static void get_error(struct tcb *, const bool);
>  static int arch_set_error(struct tcb *);
>  static int arch_set_success(struct tcb *);
>  
> -struct inject_opts *inject_vec[SUPPORTED_PERSONALITIES];
> -
> -static struct inject_opts *
> -tcb_inject_opts(struct tcb *tcp)
> -{
> -	return (scno_in_range(tcp->scno) && tcp->inject_vec[current_personality])
> -	       ? &tcp->inject_vec[current_personality][tcp->scno] : NULL;
> -}
> -
> -
>  static long
>  tamper_with_syscall_entering(struct tcb *tcp, unsigned int *signo)
>  {
> -	if (!tcp->inject_vec[current_personality]) {
> -		tcp->inject_vec[current_personality] =
> -			xcalloc(nsyscalls, sizeof(**inject_vec));
> -		memcpy(tcp->inject_vec[current_personality],
> -		       inject_vec[current_personality],
> -		       nsyscalls * sizeof(**inject_vec));
> -	}
> -
> -	struct inject_opts *opts = tcb_inject_opts(tcp);
> +	struct inject_opts *opts = tcp->inject_opts;
>  
>  	if (!opts || opts->first == 0)
>  		return 0;
> @@ -621,7 +600,7 @@ tamper_with_syscall_entering(struct tcb *tcp, unsigned int *signo)
>  static long
>  tamper_with_syscall_exiting(struct tcb *tcp)
>  {
> -	struct inject_opts *opts = tcb_inject_opts(tcp);
> +	struct inject_opts *opts = tcp->inject_opts;
>  
>  	if (!opts)
>  		return 0;
> @@ -1261,7 +1240,7 @@ get_scno(struct tcb *tcp)
>  
>  	if (scno_is_valid(tcp->scno)) {
>  		tcp->s_ent = &sysent[tcp->scno];
> -		tcp->qual_flg = qual_flags(tcp->scno);
> +		tcp->qual_flg = 0;
>  	} else {
>  		struct sysent_buf *s = xcalloc(1, sizeof(*s));
>  
> -- 
> 2.1.4
> 

Overall the most significant issue I see with the approach is the need
to run all the filters even they are not needed for the evaluation of
the boolean expression. Since I do not see the current action-to-filter
relation as many-to-one, I see little advantage in running all
the filters in advance and not during the expression evaluation (current
implementation already can benefit of it, as it adds only the last
filter to expression, effectively replacing previous one, so only the
last filter has to be executed).

Another minor note is that some cases (like abbrev=all and similar)
allow skipping bitmask creation altogether via custom constant filter
implementation, but this is probably one of the future possible
optimisations.




More information about the Strace-devel mailing list