diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 2e8ee12e06de..fce0d52b9cfe 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -7748,8 +7748,13 @@ Kernel parameters trace_event=:mod: The value before :mod: will only enable specific events - that are part of the module. See the above mentioned - document for more information. + that are part of the module. + + To apply a filter, use the :filter: keyword: + + trace_event=...:filter: + + See the above mentioned document for more information. trace_instance=[instance-info] [FTRACE] Create a ring buffer instance early in boot up. diff --git a/Documentation/trace/events.rst b/Documentation/trace/events.rst index 581f2260614b..88a9099f1688 100644 --- a/Documentation/trace/events.rst +++ b/Documentation/trace/events.rst @@ -118,6 +118,11 @@ In order to facilitate early boot debugging, use boot option:: event-list is a comma separated list of events. See section 2.1 for event format. +The boot option also supports the ``:filter:`` keyword to apply a filter. +The ``:filter:`` keyword needs to be at the end of the event specification:: + + ::mod::filter: + 3. Defining an event-enabled tracepoint ======================================= @@ -243,6 +248,18 @@ the function "security_prepare_creds" and less than the end of that function. The ".function" postfix can only be attached to values of size long, and can only be compared with "==" or "!=". +The special field "within" can be used to filter events based on whether +a specific function appears in the current call stack:: + + within == "function_name" + within != "function_name" + +For example, to only trace events where "vfs_read" is in the call stack:: + + # echo 'within == "vfs_read"' > events/sched/sched_switch/filter + +The within field supports only the "==" and "!=" operators. + Cpumask fields or scalar fields that encode a CPU number can be filtered using a user-provided cpumask in cpulist format. The format is as follows:: diff --git a/Documentation/trace/histogram.rst b/Documentation/trace/histogram.rst index 340bcb5099e7..97b67f05f5ea 100644 --- a/Documentation/trace/histogram.rst +++ b/Documentation/trace/histogram.rst @@ -1943,6 +1943,9 @@ the ".buckets" modifier and specify a size (in this case groups of 10):: Entries: 16 Dropped: 0 +Keys are 64-bit, so the group holding the largest values is truncated where +that range ends and can be smaller than the size asked for. + To save stacktraces, create a synthetic event with a field of type "unsigned long[]" or even just "long[]". For example, to see how long a task is blocked in an uninterruptible state:: diff --git a/include/linux/ring_buffer.h b/include/linux/ring_buffer.h index e4370064273e..649b2276b557 100644 --- a/include/linux/ring_buffer.h +++ b/include/linux/ring_buffer.h @@ -173,7 +173,7 @@ void ring_buffer_iter_reset(struct ring_buffer_iter *iter); int ring_buffer_iter_empty(struct ring_buffer_iter *iter); bool ring_buffer_iter_dropped(struct ring_buffer_iter *iter); -unsigned long ring_buffer_size(struct trace_buffer *buffer, int cpu); +unsigned long ring_buffer_capacity(struct trace_buffer *buffer, int cpu); unsigned long ring_buffer_max_event_size(struct trace_buffer *buffer); void ring_buffer_reset_cpu(struct trace_buffer *buffer, int cpu); diff --git a/include/linux/trace_events.h b/include/linux/trace_events.h index 5cbd09c8be8d..321f2f3a6c8e 100644 --- a/include/linux/trace_events.h +++ b/include/linux/trace_events.h @@ -872,6 +872,7 @@ enum { FILTER_COMM, FILTER_CPU, FILTER_STACKTRACE, + FILTER_WITHIN, }; extern int trace_event_raw_init(struct trace_event_call *call); @@ -882,7 +883,7 @@ extern int trace_add_event_call(struct trace_event_call *call); extern int trace_remove_event_call(struct trace_event_call *call); extern int trace_event_get_offsets(struct trace_event_call *call); -int ftrace_set_clr_event(struct trace_array *tr, char *buf, int set); +int ftrace_set_clr_event(struct trace_array *tr, const char *buf, int set, bool handle_filter); int trace_set_clr_event(const char *system, const char *event, int set); int trace_array_set_clr_event(struct trace_array *tr, const char *system, const char *event, bool enable); diff --git a/include/trace/events/power.h b/include/trace/events/power.h index 370f8df2fdb4..4d17eb307b6a 100644 --- a/include/trace/events/power.h +++ b/include/trace/events/power.h @@ -497,6 +497,7 @@ DEFINE_EVENT(dev_pm_qos_request, dev_pm_qos_remove_request, TP_ARGS(name, type, new_value) ); +#ifdef CONFIG_CPU_IDLE_GOV_HALTPOLL TRACE_EVENT(guest_halt_poll_ns, TP_PROTO(bool grow, unsigned int new, unsigned int old), @@ -525,6 +526,8 @@ TRACE_EVENT(guest_halt_poll_ns, trace_guest_halt_poll_ns(true, new, old) #define trace_guest_halt_poll_ns_shrink(new, old) \ trace_guest_halt_poll_ns(false, new, old) +#endif /* CONFIG_CPU_IDLE_GOV_HALTPOLL */ + #endif /* _TRACE_POWER_H */ /* This part must be outside protection */ diff --git a/kernel/trace/Kconfig b/kernel/trace/Kconfig index d8faeb659cfc..c41397eccee5 100644 --- a/kernel/trace/Kconfig +++ b/kernel/trace/Kconfig @@ -151,6 +151,14 @@ config EVENT_TRACING select GLOB bool +config EVENT_TRACING_TEST + tristate "Test for event tracing" if !KUNIT_ALL_TESTS + depends on EVENT_TRACING + depends on KUNIT + default KUNIT_ALL_TESTS + help + KUnit test for the event tracing implementation. + config CONTEXT_SWITCH_TRACER bool diff --git a/kernel/trace/Makefile b/kernel/trace/Makefile index 1f925cd1b9f8..b0c44f438999 100644 --- a/kernel/trace/Makefile +++ b/kernel/trace/Makefile @@ -96,6 +96,7 @@ obj-$(CONFIG_EVENT_TRACING) += blktrace.o endif obj-$(CONFIG_EVENT_TRACING) += trace_events.o obj-$(CONFIG_EVENT_TRACING) += trace_export.o +obj-$(CONFIG_EVENT_TRACING_TEST) += trace_events_test.o obj-$(CONFIG_FTRACE_SYSCALLS) += trace_syscalls.o ifeq ($(CONFIG_PERF_EVENTS),y) obj-$(CONFIG_EVENT_TRACING) += trace_event_perf.o diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index b52f51db9045..1fd28a72e6ee 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -6548,18 +6548,23 @@ void ring_buffer_iter_advance(struct ring_buffer_iter *iter) EXPORT_SYMBOL_GPL(ring_buffer_iter_advance); /** - * ring_buffer_size - return the size of the ring buffer (in bytes) + * ring_buffer_capacity - return the capacity of the ring buffer (in bytes) * @buffer: The ring buffer. - * @cpu: The CPU to get ring buffer size from. + * @cpu: The CPU to get ring buffer capacity from. + * + * Capacity is the total memory available for recording event data in the + * ring buffer. It excludes subbuffer headers. + * + * Returns the capacity in bytes, or 0 if the CPU is not set in the buffer mask. */ -unsigned long ring_buffer_size(struct trace_buffer *buffer, int cpu) +unsigned long ring_buffer_capacity(struct trace_buffer *buffer, int cpu) { if (!cpumask_test_cpu(cpu, buffer->cpumask)) return 0; return rb_subbuf_capacity(buffer) * buffer->buffers[cpu]->nr_pages; } -EXPORT_SYMBOL_GPL(ring_buffer_size); +EXPORT_SYMBOL_GPL(ring_buffer_capacity); /** * ring_buffer_max_event_size - return the max data size of an event diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 97a058623f94..e8c4efa8107b 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -3614,6 +3614,7 @@ static int tracing_single_release_tr(struct inode *inode, struct file *file) return single_release(inode, file); } +static bool __update_last_data_if_empty(struct trace_array *tr); static bool update_last_data_if_empty(struct trace_array *tr); static int tracing_open(struct inode *inode, struct file *file) @@ -4708,24 +4709,24 @@ tracing_set_trace_read(struct file *filp, char __user *ubuf, int tracer_init(struct tracer *t, struct trace_array *tr) { tracing_reset_online_cpus(&tr->array_buffer); - update_last_data_if_empty(tr); + __update_last_data_if_empty(tr); return t->init(tr); } -void trace_set_buffer_entries(struct array_buffer *buf, unsigned long val) +void trace_set_buffer_capacity(struct array_buffer *buf, unsigned long val) { int cpu; for_each_tracing_cpu(cpu) - per_cpu_ptr(buf->data, cpu)->entries = val; + per_cpu_ptr(buf->data, cpu)->capacity = val; } -static void update_buffer_entries(struct array_buffer *buf, int cpu) +static void update_buffer_capacity(struct array_buffer *buf, int cpu) { if (cpu == RING_BUFFER_ALL_CPUS) { - trace_set_buffer_entries(buf, ring_buffer_size(buf->buffer, 0)); + trace_set_buffer_capacity(buf, ring_buffer_capacity(buf->buffer, 0)); } else { - per_cpu_ptr(buf->data, cpu)->entries = ring_buffer_size(buf->buffer, cpu); + per_cpu_ptr(buf->data, cpu)->capacity = ring_buffer_capacity(buf->buffer, cpu); } } @@ -4781,12 +4782,12 @@ static int __tracing_resize_ring_buffer(struct trace_array *tr, goto out_start; } - update_buffer_entries(&tr->snapshot_buffer, cpu); + update_buffer_capacity(&tr->snapshot_buffer, cpu); out: #endif /* CONFIG_TRACER_SNAPSHOT */ - update_buffer_entries(&tr->array_buffer, cpu); + update_buffer_capacity(&tr->array_buffer, cpu); out_start: tracing_start_tr(tr); return ret; @@ -5387,8 +5388,10 @@ static int tracing_wait_pipe(struct file *filp) return 1; } -static bool update_last_data_if_empty(struct trace_array *tr) +static bool __update_last_data_if_empty(struct trace_array *tr) { + lockdep_assert_held(&trace_types_lock); + if (!(tr->flags & TRACE_ARRAY_FL_LAST_BOOT)) return false; @@ -5403,6 +5406,16 @@ static bool update_last_data_if_empty(struct trace_array *tr) return true; } +static bool update_last_data_if_empty(struct trace_array *tr) +{ + /* Avoid the lock on the read path unless this is a last boot buffer */ + if (!(tr->flags & TRACE_ARRAY_FL_LAST_BOOT)) + return false; + + guard(mutex)(&trace_types_lock); + return __update_last_data_if_empty(tr); +} + /* * Consumer reader. */ @@ -5688,8 +5701,7 @@ tracing_syscall_buf_write(struct file *filp, const char __user *ubuf, } static ssize_t -tracing_entries_read(struct file *filp, char __user *ubuf, - size_t cnt, loff_t *ppos) +tracing_capacity_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos) { struct inode *inode = file_inode(filp); struct trace_array *tr = inode->i_private; @@ -5710,8 +5722,8 @@ tracing_entries_read(struct file *filp, char __user *ubuf, for_each_tracing_cpu(cpu) { /* fill in the size from first enabled cpu */ if (size == 0) - size = per_cpu_ptr(tr->array_buffer.data, cpu)->entries; - if (size != per_cpu_ptr(tr->array_buffer.data, cpu)->entries) { + size = per_cpu_ptr(tr->array_buffer.data, cpu)->capacity; + if (size != per_cpu_ptr(tr->array_buffer.data, cpu)->capacity) { buf_size_same = 0; break; } @@ -5727,7 +5739,7 @@ tracing_entries_read(struct file *filp, char __user *ubuf, } else r = sprintf(buf, "X\n"); } else - r = sprintf(buf, "%lu\n", per_cpu_ptr(tr->array_buffer.data, cpu)->entries >> 10); + r = sprintf(buf, "%lu\n", per_cpu_ptr(tr->array_buffer.data, cpu)->capacity >> 10); mutex_unlock(&trace_types_lock); @@ -5736,8 +5748,7 @@ tracing_entries_read(struct file *filp, char __user *ubuf, } static ssize_t -tracing_entries_write(struct file *filp, const char __user *ubuf, - size_t cnt, loff_t *ppos) +tracing_capacity_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos) { struct inode *inode = file_inode(filp); struct trace_array *tr = inode->i_private; @@ -5764,8 +5775,7 @@ tracing_entries_write(struct file *filp, const char __user *ubuf, } static ssize_t -tracing_total_entries_read(struct file *filp, char __user *ubuf, - size_t cnt, loff_t *ppos) +tracing_total_capacity_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos) { struct trace_array *tr = filp->private_data; char buf[64]; @@ -5774,7 +5784,7 @@ tracing_total_entries_read(struct file *filp, char __user *ubuf, mutex_lock(&trace_types_lock); for_each_tracing_cpu(cpu) { - size += per_cpu_ptr(tr->array_buffer.data, cpu)->entries >> 10; + size += per_cpu_ptr(tr->array_buffer.data, cpu)->capacity >> 10; if (!tr->ring_buffer_expanded) expanded_size += trace_buf_size >> 10; } @@ -6495,7 +6505,7 @@ int tracing_set_clock(struct trace_array *tr, const char *clockstr) ring_buffer_set_clock(tr->snapshot_buffer.buffer, trace_clocks[i].func); tracing_reset_online_cpus(&tr->snapshot_buffer); #endif - update_last_data_if_empty(tr); + __update_last_data_if_empty(tr); if (tr->scratch && !(tr->flags & TRACE_ARRAY_FL_LAST_BOOT)) { struct trace_scratch *tscratch = tr->scratch; @@ -6616,10 +6626,10 @@ static const struct file_operations tracing_pipe_fops = { .release = tracing_release_pipe, }; -static const struct file_operations tracing_entries_fops = { +static const struct file_operations tracing_capacity_fops = { .open = tracing_open_generic_tr, - .read = tracing_entries_read, - .write = tracing_entries_write, + .read = tracing_capacity_read, + .write = tracing_capacity_write, .llseek = generic_file_llseek, .release = tracing_release_generic_tr, }; @@ -6639,9 +6649,9 @@ static const struct file_operations tracing_buffer_meta_fops = { .release = tracing_seq_release, }; -static const struct file_operations tracing_total_entries_fops = { +static const struct file_operations tracing_total_capacity_fops = { .open = tracing_open_generic_tr, - .read = tracing_total_entries_read, + .read = tracing_total_capacity_read, .llseek = generic_file_llseek, .release = tracing_release_generic_tr, }; @@ -7669,7 +7679,7 @@ tracing_init_tracefs_percpu(struct trace_array *tr, long cpu) tr, cpu, &tracing_stats_fops); trace_create_cpu_file("buffer_size_kb", TRACE_MODE_WRITE, d_cpu, - tr, cpu, &tracing_entries_fops); + tr, cpu, &tracing_capacity_fops); if (tr->range_addr_start) trace_create_cpu_file("buffer_meta", TRACE_MODE_READ, d_cpu, @@ -8535,8 +8545,8 @@ int allocate_trace_buffer(struct trace_array *tr, struct array_buffer *buf, int } /* Allocate the first page for all buffers */ - trace_set_buffer_entries(&tr->array_buffer, - ring_buffer_size(tr->array_buffer.buffer, 0)); + trace_set_buffer_capacity(&tr->array_buffer, + ring_buffer_capacity(tr->array_buffer.buffer, 0)); return 0; } @@ -9014,10 +9024,10 @@ init_tracer_tracefs(struct trace_array *tr, struct dentry *d_tracer) tr, &tracing_pipe_fops); trace_create_file("buffer_size_kb", writable_mode, d_tracer, - tr, &tracing_entries_fops); + tr, &tracing_capacity_fops); trace_create_file("buffer_total_size_kb", TRACE_MODE_READ, d_tracer, - tr, &tracing_total_entries_fops); + tr, &tracing_total_capacity_fops); trace_create_file("trace_clock", writable_mode, d_tracer, tr, &trace_clock_fops); @@ -9263,6 +9273,8 @@ static void trace_module_record(struct module *mod, bool add) struct trace_array *tr; unsigned long flags; + guard(mutex)(&trace_types_lock); + list_for_each_entry(tr, &ftrace_trace_arrays, list) { flags = tr->flags & (TRACE_ARRAY_FL_BOOT | TRACE_ARRAY_FL_LAST_BOOT); /* Update any persistent trace array that has already been started */ diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h index 9044f03fe2d9..0635503a31dd 100644 --- a/kernel/trace/trace.h +++ b/kernel/trace/trace.h @@ -191,7 +191,7 @@ struct trace_array; struct trace_array_cpu { local_t disabled; - unsigned long entries; + unsigned long capacity; unsigned long saved_latency; unsigned long critical_start; unsigned long critical_end; @@ -731,7 +731,7 @@ ssize_t tracing_nsecs_read(unsigned long *ptr, char __user *ubuf, ssize_t tracing_nsecs_write(unsigned long *ptr, const char __user *ubuf, size_t cnt, loff_t *ppos); -void trace_set_buffer_entries(struct array_buffer *buf, unsigned long val); +void trace_set_buffer_capacity(struct array_buffer *buf, unsigned long val); /* * Should be used after trace_array_get(), trace_types_lock @@ -1823,7 +1823,8 @@ static inline bool is_string_field(struct ftrace_event_field *field) field->filter_type == FILTER_RDYN_STRING || field->filter_type == FILTER_STATIC_STRING || field->filter_type == FILTER_PTR_STRING || - field->filter_type == FILTER_COMM; + field->filter_type == FILTER_COMM || + field->filter_type == FILTER_WITHIN; } static inline bool is_function_field(struct ftrace_event_field *field) @@ -2286,7 +2287,6 @@ static inline const char *get_syscall_name(int syscall) void trace_event_init(void); void trace_event_update_all(struct trace_eval_map **map, int len, struct module *mod); /* Used from boot time tracer */ -extern int ftrace_set_clr_event(struct trace_array *tr, char *buf, int set); extern int trigger_process_regex(struct trace_event_file *file, char *buff); #else static inline void __init trace_event_init(void) { } @@ -2506,3 +2506,8 @@ static inline int rv_init_interface(void) }) #endif /* _LINUX_KERNEL_TRACE_H */ + +#if IS_ENABLED(CONFIG_KUNIT) +void ftrace_parse_event_string(char *buf, char **match, char **sub, char **event, char **mod, + bool handle_filter, char **filter); +#endif diff --git a/kernel/trace/trace_boot.c b/kernel/trace/trace_boot.c index 5da0a8bbb110..6ba70dac1f06 100644 --- a/kernel/trace/trace_boot.c +++ b/kernel/trace/trace_boot.c @@ -94,7 +94,7 @@ trace_boot_enable_events(struct trace_array *tr, struct xbc_node *node) continue; } - if (ftrace_set_clr_event(tr, buf, 1) < 0) + if (ftrace_set_clr_event(tr, buf, 1, false) < 0) pr_err("Failed to enable event: %s\n", p); } } diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c index 6428861102a1..49d135bc2fbf 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c @@ -25,6 +25,8 @@ #include #include +#include + #include #include @@ -201,6 +203,9 @@ static int trace_define_generic_fields(void) __generic_field(char *, comm, FILTER_COMM); __generic_field(char *, stacktrace, FILTER_STACKTRACE); __generic_field(char *, STACKTRACE, FILTER_STACKTRACE); +#ifdef CONFIG_STACKTRACE + __generic_field(char *, within, FILTER_WITHIN); +#endif return ret; } @@ -241,6 +246,10 @@ int trace_event_get_offsets(struct trace_event_call *call) struct list_head *head; head = trace_get_fields(call); + + if (list_empty(head)) + return sizeof(struct trace_entry); + /* * head->next points to the last field with the largest offset, * since it was added last by trace_define_field() @@ -941,6 +950,7 @@ struct event_mod_load { char *match; char *system; char *event; + char *filter; }; static void free_event_mod(struct event_mod_load *event_mod) @@ -950,6 +960,7 @@ static void free_event_mod(struct event_mod_load *event_mod) kfree(event_mod->match); kfree(event_mod->system); kfree(event_mod->event); + kfree(event_mod->filter); kfree(event_mod); } @@ -991,7 +1002,7 @@ static int remove_cache_mod(struct trace_array *tr, const char *mod, } static int cache_mod(struct trace_array *tr, const char *mod, int set, - const char *match, const char *system, const char *event) + const char *match, const char *system, const char *event, const char *filter) { struct event_mod_load *event_mod; @@ -1030,6 +1041,12 @@ static int cache_mod(struct trace_array *tr, const char *mod, int set, goto out_free; } + if (filter) { + event_mod->filter = kstrdup(filter, GFP_KERNEL); + if (!event_mod->filter) + goto out_free; + } + list_add(&event_mod->list, &tr->mod_events); return 0; @@ -1042,7 +1059,7 @@ static int cache_mod(struct trace_array *tr, const char *mod, int set, #else /* CONFIG_MODULES */ static inline void clear_mod_events(struct trace_array *tr) { } static int cache_mod(struct trace_array *tr, const char *mod, int set, - const char *match, const char *system, const char *event) + const char *match, const char *system, const char *event, const char *filter) { return -EINVAL; } @@ -1351,7 +1368,7 @@ static void remove_event_file_dir(struct trace_event_file *file) static int __ftrace_set_clr_event_nolock(struct trace_array *tr, const char *match, const char *sub, const char *event, int set, - const char *mod) + const char *mod, char *filter) { struct trace_event_file *file; struct trace_event_call *call; @@ -1401,7 +1418,11 @@ __ftrace_set_clr_event_nolock(struct trace_array *tr, const char *match, if (event && strcmp(event, name) != 0) continue; - ret = ftrace_event_enable_disable(file, set); + if (filter) + ret = apply_event_filter(file, filter); + + if (!filter || !ret) + ret = ftrace_event_enable_disable(file, set); /* * Save the first error and return that. Some events @@ -1419,14 +1440,14 @@ __ftrace_set_clr_event_nolock(struct trace_array *tr, const char *match, * check if the module was loaded. If it wasn't cache it. */ if (module && ret == -EINVAL && !eret) - ret = cache_mod(tr, module, set, match, sub, event); + ret = cache_mod(tr, module, set, match, sub, event, filter); return ret; } static int __ftrace_set_clr_event(struct trace_array *tr, const char *match, const char *sub, const char *event, int set, - const char *mod) + const char *mod, char *filter) { int ret; @@ -1434,26 +1455,35 @@ static int __ftrace_set_clr_event(struct trace_array *tr, const char *match, return -EACCES; mutex_lock(&event_mutex); - ret = __ftrace_set_clr_event_nolock(tr, match, sub, event, set, mod); + ret = __ftrace_set_clr_event_nolock(tr, match, sub, event, set, mod, filter); mutex_unlock(&event_mutex); return ret; } -int ftrace_set_clr_event(struct trace_array *tr, char *buf, int set) +VISIBLE_IF_KUNIT void +ftrace_parse_event_string(char *buf, char **match, char **sub, char **event, char **mod, + bool handle_filter, char **filter) { - char *event = NULL, *sub = NULL, *match, *mod; - int ret; + *event = NULL; + *sub = NULL; + *filter = NULL; - if (!tr) - return -ENOENT; + /* event filters can be appended with :filter: */ + if (handle_filter) { + *filter = strstr(buf, ":filter:"); + if (*filter) { + **filter = '\0'; + *filter += 8; + } + } /* Modules events can be appended with :mod: */ - mod = strstr(buf, ":mod:"); - if (mod) { - *mod = '\0'; + *mod = strstr(buf, ":mod:"); + if (*mod) { + **mod = '\0'; /* move to the module name */ - mod += 5; + *mod += 5; } /* @@ -1468,31 +1498,38 @@ int ftrace_set_clr_event(struct trace_array *tr, char *buf, int set) * the name or any event that matches */ - match = strsep(&buf, ":"); + *match = strsep(&buf, ":"); if (buf) { - sub = match; - event = buf; - match = NULL; + *sub = *match; + *event = buf; + *match = NULL; - if (!strlen(sub) || strcmp(sub, "*") == 0) - sub = NULL; - if (!strlen(event) || strcmp(event, "*") == 0) - event = NULL; - } else if (mod) { + if (!strlen(*sub) || strcmp(*sub, "*") == 0) + *sub = NULL; + if (!strlen(*event) || strcmp(*event, "*") == 0) + *event = NULL; + } else if (*mod) { /* Allow wildcard for no length or star */ - if (!strlen(match) || strcmp(match, "*") == 0) - match = NULL; + if (!strlen(*match) || strcmp(*match, "*") == 0) + *match = NULL; } +} +EXPORT_SYMBOL_IF_KUNIT(ftrace_parse_event_string); - ret = __ftrace_set_clr_event(tr, match, sub, event, set, mod); +int ftrace_set_clr_event(struct trace_array *tr, const char *arg_buf, int set, bool handle_filter) +{ + char *event, *sub, *match, *mod, *filter; - /* Put back the colon to allow this to be called again */ - if (buf) - *(buf - 1) = ':'; - if (mod) - *(mod - 5) = ':'; + if (!tr) + return -ENOENT; - return ret; + char *dupped_buf __free(kfree) = kstrdup(arg_buf, GFP_KERNEL); + if (!dupped_buf) + return -ENOMEM; + + ftrace_parse_event_string(dupped_buf, &match, &sub, &event, &mod, handle_filter, &filter); + + return __ftrace_set_clr_event(tr, match, sub, event, set, mod, filter); } /** @@ -1514,7 +1551,7 @@ int trace_set_clr_event(const char *system, const char *event, int set) if (!tr) return -ENODEV; - return __ftrace_set_clr_event(tr, NULL, system, event, set, NULL); + return __ftrace_set_clr_event(tr, NULL, system, event, set, NULL, NULL); } EXPORT_SYMBOL_GPL(trace_set_clr_event); @@ -1540,7 +1577,7 @@ int trace_array_set_clr_event(struct trace_array *tr, const char *system, return -ENOENT; set = (enable == true) ? 1 : 0; - return __ftrace_set_clr_event(tr, NULL, system, event, set, NULL); + return __ftrace_set_clr_event(tr, NULL, system, event, set, NULL, NULL); } EXPORT_SYMBOL_GPL(trace_array_set_clr_event); @@ -1574,7 +1611,7 @@ ftrace_event_write(struct file *file, const char __user *ubuf, if (*parser.buffer == '!') set = 0; - ret = ftrace_set_clr_event(tr, parser.buffer + !set, set); + ret = ftrace_set_clr_event(tr, parser.buffer + !set, set, false); if (ret) goto out_put; } @@ -2060,7 +2097,7 @@ system_enable_write(struct file *filp, const char __user *ubuf, size_t cnt, if (system) name = system->name; - ret = __ftrace_set_clr_event(dir->tr, NULL, name, NULL, val, NULL); + ret = __ftrace_set_clr_event(dir->tr, NULL, name, NULL, val, NULL, NULL); if (ret) goto out; @@ -4035,7 +4072,8 @@ static void update_mod_cache(struct trace_array *tr, struct module *mod) __ftrace_set_clr_event_nolock(tr, event_mod->match, event_mod->system, - event_mod->event, 1, mod->name); + event_mod->event, 1, mod->name, + event_mod->filter); free_event_mod(event_mod); } } @@ -4809,7 +4847,7 @@ int event_trace_del_tracer(struct trace_array *tr) __ftrace_clear_event_pids(tr, TRACE_PIDS | TRACE_NO_PIDS); /* Disable any running events */ - __ftrace_set_clr_event_nolock(tr, NULL, NULL, NULL, 0, NULL); + __ftrace_set_clr_event_nolock(tr, NULL, NULL, NULL, 0, NULL, NULL); /* Make sure no more events are being executed */ tracepoint_synchronize_unregister(); @@ -4842,10 +4880,10 @@ static __init void __early_set_events(struct trace_array *tr, char *buf, bool en while ((token = strsep(&buf, ","))) { if (*token) { if (enable) { - if (ftrace_set_clr_event(tr, token, 1)) + if (ftrace_set_clr_event(tr, token, 1, true)) pr_warn("Failed to enable trace event: %s\n", token); } else { - ftrace_set_clr_event(tr, token, 0); + ftrace_set_clr_event(tr, token, 0, true); } } @@ -5119,7 +5157,7 @@ static __init void event_trace_self_tests(void) pr_info("Testing event system %s: ", system->name); - ret = __ftrace_set_clr_event(tr, NULL, system->name, NULL, 1, NULL); + ret = __ftrace_set_clr_event(tr, NULL, system->name, NULL, 1, NULL, NULL); if (WARN_ON_ONCE(ret)) { pr_warn("error enabling system %s\n", system->name); @@ -5128,7 +5166,7 @@ static __init void event_trace_self_tests(void) event_test_stuff(); - ret = __ftrace_set_clr_event(tr, NULL, system->name, NULL, 0, NULL); + ret = __ftrace_set_clr_event(tr, NULL, system->name, NULL, 0, NULL, NULL); if (WARN_ON_ONCE(ret)) { pr_warn("error disabling system %s\n", system->name); @@ -5143,7 +5181,7 @@ static __init void event_trace_self_tests(void) pr_info("Running tests on all trace events:\n"); pr_info("Testing all events: "); - ret = __ftrace_set_clr_event(tr, NULL, NULL, NULL, 1, NULL); + ret = __ftrace_set_clr_event(tr, NULL, NULL, NULL, 1, NULL, NULL); if (WARN_ON_ONCE(ret)) { pr_warn("error enabling all events\n"); return; @@ -5152,7 +5190,7 @@ static __init void event_trace_self_tests(void) event_test_stuff(); /* reset sysname */ - ret = __ftrace_set_clr_event(tr, NULL, NULL, NULL, 0, NULL); + ret = __ftrace_set_clr_event(tr, NULL, NULL, NULL, 0, NULL, NULL); if (WARN_ON_ONCE(ret)) { pr_warn("error disabling all events\n"); return; diff --git a/kernel/trace/trace_events_filter.c b/kernel/trace/trace_events_filter.c index 127880ebe83f..c10951bcfc5f 100644 --- a/kernel/trace/trace_events_filter.c +++ b/kernel/trace/trace_events_filter.c @@ -72,6 +72,7 @@ enum filter_pred_fn { FILTER_PRED_FN_CPUMASK, FILTER_PRED_FN_CPUMASK_CPU, FILTER_PRED_FN_FUNCTION, + FILTER_PRED_FN_WITHIN, FILTER_PRED_FN_, FILTER_PRED_TEST_VISITED, }; @@ -117,6 +118,7 @@ struct filter_pred { C(IP_FIELD_ONLY, "Only 'ip' field is supported for function trace"), \ C(INVALID_VALUE, "Invalid value (did you forget quotes)?"), \ C(NO_FUNCTION, "Function not found"), \ + C(GLOB_WITHIN, "'within' filter cannot take glob expressions"), \ C(ERRNO, "Error"), \ C(NO_FILTER, "No filter found") @@ -595,6 +597,12 @@ predicate_parse(const char *str, int nr_parens, int nr_preds, top--; /* #12 */ } } + /* Only the inner loop can complete a non-empty expression. */ + if (N) { + ret = -EINVAL; + parse_error(pe, FILT_ERR_INVALID_FILTER, ptr - str); + goto out_free; + } out: if (top != op_stack) { /* Too many '(' */ @@ -1008,6 +1016,22 @@ static int filter_pred_function(struct filter_pred *pred, void *event) return pred->op == OP_EQ ? ret : !ret; } +/* Filter predicate for within. */ +static int filter_pred_within(struct filter_pred *pred, void *event) +{ +#ifdef CONFIG_STACKTRACE + unsigned long entries[16]; + unsigned int nr_entries; + int i; + + nr_entries = stack_trace_save(entries, ARRAY_SIZE(entries), 0); + for (i = 0; i < nr_entries; i++) + if (pred->val <= entries[i] && entries[i] < pred->val2) + return !pred->not; +#endif + return pred->not; +} + /* * regex_match_foo - Basic regex callbacks * @@ -1617,6 +1641,8 @@ static int filter_pred_fn_call(struct filter_pred *pred, void *event) return filter_pred_cpumask_cpu(pred, event); case FILTER_PRED_FN_FUNCTION: return filter_pred_function(pred, event); + case FILTER_PRED_FN_WITHIN: + return filter_pred_within(pred, event); case FILTER_PRED_TEST_VISITED: return test_pred_visited_fn(pred, event); default: @@ -2002,10 +2028,30 @@ static int parse_pred(const char *str, void *data, } else if (field->filter_type == FILTER_DYN_STRING) { pred->fn_num = FILTER_PRED_FN_STRLOC; - } else if (field->filter_type == FILTER_RDYN_STRING) + } else if (field->filter_type == FILTER_RDYN_STRING) { pred->fn_num = FILTER_PRED_FN_STRRELLOC; - else { + } else if (field->filter_type == FILTER_WITHIN) { + unsigned long func; + if (op == OP_GLOB) { + parse_error(pe, FILT_ERR_GLOB_WITHIN, pos + i); + goto err_free; + } + + pred->fn_num = FILTER_PRED_FN_WITHIN; + func = kallsyms_lookup_name(pred->regex->pattern); + if (!func) { + parse_error(pe, FILT_ERR_NO_FUNCTION, pos + i); + goto err_free; + } + /* Now find the function start and end address */ + if (!kallsyms_lookup_size_offset(func, &size, &offset)) { + parse_error(pe, FILT_ERR_NO_FUNCTION, pos + i); + goto err_free; + } + pred->val = func - offset; + pred->val2 = pred->val + size; + } else { if (!ustring_per_cpu) { /* Once allocated, keep it around for good */ ustring_per_cpu = alloc_percpu(struct ustring_buffer); diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 5c07fee3a328..1aae7d7c6dc5 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -279,6 +279,14 @@ static u64 hist_field_pstring(struct hist_field *hist_field, return (u64)(unsigned long)*addr; } +static bool hist_field_is_signed(struct hist_field *hist_field) +{ + if (hist_field->field) + return hist_field->field->is_signed; + + return hist_field->is_signed; +} + static u64 hist_field_log2(struct hist_field *hist_field, struct tracing_map_elt *elt, struct trace_buffer *buffer, @@ -466,7 +474,7 @@ static u64 hist_field_unary_minus(struct hist_field *hist_field, { \ type *addr = (type *)(event + hist_field->field->offset); \ \ - return (u64)(unsigned long)*addr; \ + return (u64)*addr; \ } DEFINE_HIST_FIELD_FN(s64); @@ -549,7 +557,7 @@ struct field_var { }; struct field_var_hist { - struct hist_trigger_data *hist_data; + struct trace_event_file *file; char *cmd; }; @@ -1725,7 +1733,7 @@ static const char *get_hist_field_flags(struct hist_field *hist_field) flags_str = "percent"; else if (hist_field->flags & HIST_FIELD_FL_GRAPH) flags_str = "graph"; - else if (hist_field->flags & HIST_FIELD_FL_STACKTRACE) + else if (hist_field->flags & HIST_FIELD_FL_STACKTRACE && hist_field->field) flags_str = "stacktrace"; return flags_str; @@ -1754,6 +1762,9 @@ static bool expr_field_str(struct hist_field *field, struct seq_buf *s) seq_buf_printf(s, ".%s", flags_str); } + if (field->buckets) + seq_buf_printf(s, "=%lu", field->buckets); + return !seq_buf_has_overflowed(s); } @@ -3117,8 +3128,8 @@ create_field_var_hist(struct hist_trigger_data *target_hist_data, return ERR_PTR(-ENOMEM); } - /* Save the compatible histogram information */ - var_hist->hist_data = hist_data; + /* Needed to remove the histogram when the target goes away */ + var_hist->file = file; /* Create the new histogram with our variable */ ret = event_hist_trigger_parse(&trigger_hist_cmd, file, @@ -3133,20 +3144,18 @@ create_field_var_hist(struct hist_trigger_data *target_hist_data, kfree(cmd); + n = target_hist_data->n_field_var_hists; + target_hist_data->field_var_hists[n] = var_hist; + target_hist_data->n_field_var_hists++; + /* If we can't find the variable, something went wrong */ event_var = find_synthetic_field_var(target_hist_data, subsys_name, event_name, field_name); if (IS_ERR_OR_NULL(event_var)) { - kfree(var_hist->cmd); - kfree(var_hist); hist_err(tr, HIST_ERR_SYNTH_VAR_NOT_FOUND, errpos(field_name)); return ERR_PTR(-EINVAL); } - n = target_hist_data->n_field_var_hists; - target_hist_data->field_var_hists[n] = var_hist; - target_hist_data->n_field_var_hists++; - return event_var; } @@ -5565,15 +5574,24 @@ static void hist_trigger_print_key(struct seq_file *m, *(u64 *)(key + key_field->offset)); } else if (key_field->flags & HIST_FIELD_FL_BUCKET) { unsigned long buckets = key_field->buckets; + u64 end; + uval = *(u64 *)(key + key_field->offset); + end = uval + buckets - 1; + if (end < uval) + end = U64_MAX; seq_printf(m, "%s: ~ %llu-%llu", field_name, - uval, uval + buckets -1); + uval, end); } else if (key_field->flags & HIST_FIELD_FL_STRING) { seq_printf(m, "%s: %-50s", field_name, (char *)(key + key_field->offset)); } else { uval = *(u64 *)(key + key_field->offset); - seq_printf(m, "%s: %10llu", field_name, uval); + if (hist_field_is_signed(key_field)) + seq_printf(m, "%s: %10lld", field_name, + (s64)uval); + else + seq_printf(m, "%s: %10llu", field_name, uval); } } @@ -6205,7 +6223,7 @@ static void hist_field_print(struct seq_file *m, struct hist_field *hist_field) } } if (hist_field->buckets) - seq_printf(m, "=%ld", hist_field->buckets); + seq_printf(m, "=%lu", hist_field->buckets); } static int event_hist_trigger_print(struct seq_file *m, @@ -6349,7 +6367,7 @@ static void unregister_field_var_hists(struct hist_trigger_data *hist_data) int ret; for (i = 0; i < hist_data->n_field_var_hists; i++) { - file = hist_data->field_var_hists[i]->hist_data->event_file; + file = hist_data->field_var_hists[i]->file; cmd = hist_data->field_var_hists[i]->cmd; ret = event_hist_trigger_parse(&trigger_hist_cmd, file, "!hist", "hist", cmd); @@ -6799,7 +6817,6 @@ static void hist_unreg_all(struct trace_event_file *file) struct event_trigger_data *test, *n; struct hist_trigger_data *hist_data; struct synth_event *se; - const char *se_name; lockdep_assert_held(&event_mutex); @@ -6812,8 +6829,7 @@ static void hist_unreg_all(struct trace_event_file *file) list_del_rcu(&test->list); trace_event_trigger_enable_disable(file, 0); - se_name = trace_event_name(file->event_call); - se = find_synth_event(se_name); + se = find_synth_event_call(file->event_call); if (se) se->ref--; @@ -6837,7 +6853,6 @@ static int event_hist_trigger_parse(struct event_command *cmd_ops, struct hist_trigger_data *hist_data; char *param, *filter, *p, *start; struct synth_event *se; - const char *se_name; bool remove; int ret = 0; @@ -6931,8 +6946,7 @@ static int event_hist_trigger_parse(struct event_command *cmd_ops, } event_trigger_unregister(cmd_ops, file, glob+1, trigger_data); - se_name = trace_event_name(file->event_call); - se = find_synth_event(se_name); + se = find_synth_event_call(file->event_call); if (se) se->ref--; ret = 0; @@ -6967,8 +6981,7 @@ static int event_hist_trigger_parse(struct event_command *cmd_ops, if (ret) goto out_unreg; - se_name = trace_event_name(file->event_call); - se = find_synth_event(se_name); + se = find_synth_event_call(file->event_call); if (se) se->ref++; out: @@ -6985,6 +6998,8 @@ static int event_hist_trigger_parse(struct event_command *cmd_ops, trigger_data_free(trigger_data); + unregister_field_var_hists(hist_data); + destroy_hist_data(hist_data); goto out; } diff --git a/kernel/trace/trace_events_synth.c b/kernel/trace/trace_events_synth.c index dc15658a887c..e2870212e58d 100644 --- a/kernel/trace/trace_events_synth.c +++ b/kernel/trace/trace_events_synth.c @@ -914,6 +914,16 @@ struct synth_event *find_synth_event(const char *name) return NULL; } +struct synth_event *find_synth_event_call(struct trace_event_call *call) +{ + struct synth_event *se; + const char *name; + + name = trace_event_name(call); + se = find_synth_event(name); + return se && &se->call == call ? se : NULL; +} + static struct trace_event_fields synth_event_fields_array[] = { { .type = TRACE_FUNCTION_TYPE, .define_fields = synth_event_define_fields }, diff --git a/kernel/trace/trace_events_test.c b/kernel/trace/trace_events_test.c new file mode 100644 index 000000000000..4a2d77fa0f58 --- /dev/null +++ b/kernel/trace/trace_events_test.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0 +#include + +#include + +#include "trace.h" + +struct parse_event_string_test_case { + const char *input; + + const char *match, *sub, *event, *mod, *filter; +}; + +static const struct parse_event_string_test_case parse_event_string_test_cases[] = { + { + "", + .match = "", + }, + { + "*:event", + .event = "event", + }, + { + ":event", + .event = "event", + }, + { + "sub:*", + .sub = "sub", + }, + { + "sub:", + .sub = "sub", + }, + { + "sub:event", + .sub = "sub", + .event = "event", + }, + { + "sub:event:filter:filter", + .sub = "sub", + .event = "event", + .filter = "filter", + }, + { + "match", + .match = "match", + }, + { + ":mod:module", + .mod = "module", + }, + { + "sub:event:mod:module", + .sub = "sub", + .event = "event", + .mod = "module", + }, + { + "sub:event:mod:module:filter:filter", + .sub = "sub", + .event = "event", + .mod = "module", + .filter = "filter", + }, + { + "sub:event:mod:module:filter:unexpected-filter", + .sub = "sub", + .event = "event", + .mod = "module:filter:unexpected-filter", + }, +}; + +static void +parse_event_string_test_desc(const struct parse_event_string_test_case *params, char *desc) +{ + snprintf(desc, KUNIT_PARAM_DESC_SIZE, "'%s'", params->input); +} + +KUNIT_ARRAY_PARAM(parse_event_string, parse_event_string_test_cases, parse_event_string_test_desc); + +#define EXPECT_NULL_OR_STR_EQ(test, left, right) \ +({ \ + if ((left) != NULL) \ + KUNIT_EXPECT_STREQ(test, (left), (right)); \ + else \ + KUNIT_EXPECT_NULL(test, (right)); \ +}) + +static void parse_event_string(struct kunit *test) +{ + const struct parse_event_string_test_case *params = test->param_value; + char *input, *match, *sub, *event, *mod, *filter; + + input = kunit_kstrdup(test, params->input, GFP_KERNEL); + if (!input) + kunit_skip(test, "ENOMEM"); + + ftrace_parse_event_string(input, &match, &sub, &event, &mod, params->filter, &filter); + + EXPECT_NULL_OR_STR_EQ(test, match, params->match); + EXPECT_NULL_OR_STR_EQ(test, sub, params->sub); + EXPECT_NULL_OR_STR_EQ(test, event, params->event); + EXPECT_NULL_OR_STR_EQ(test, mod, params->mod); + EXPECT_NULL_OR_STR_EQ(test, filter, params->filter); +} + +static struct kunit_case trace_events_test_cases[] = { + KUNIT_CASE_PARAM(parse_event_string, parse_event_string_gen_params), + {} +}; + +static struct kunit_suite trace_events_test_suite = { + .name = "trace-events", + .test_cases = trace_events_test_cases, +}; +kunit_test_suite(trace_events_test_suite); + +MODULE_DESCRIPTION("KUnit test for the event tracing implementation"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c index 0e1265acd1cc..995da9e0b548 100644 --- a/kernel/trace/trace_osnoise.c +++ b/kernel/trace/trace_osnoise.c @@ -83,6 +83,7 @@ struct osnoise_instance { static struct list_head osnoise_instances; +__printf(1, 2) static void osnoise_print(const char *fmt, ...) { struct osnoise_instance *inst; diff --git a/kernel/trace/trace_snapshot.c b/kernel/trace/trace_snapshot.c index 4731ef7325a5..72b8e6803144 100644 --- a/kernel/trace/trace_snapshot.c +++ b/kernel/trace/trace_snapshot.c @@ -142,18 +142,18 @@ int resize_buffer_duplicate_size(struct array_buffer *trace_buf, if (cpu_id == RING_BUFFER_ALL_CPUS) { for_each_tracing_cpu(cpu) { ret = ring_buffer_resize(trace_buf->buffer, - per_cpu_ptr(size_buf->data, cpu)->entries, cpu); + per_cpu_ptr(size_buf->data, cpu)->capacity, cpu); if (ret < 0) break; - per_cpu_ptr(trace_buf->data, cpu)->entries = - per_cpu_ptr(size_buf->data, cpu)->entries; + per_cpu_ptr(trace_buf->data, cpu)->capacity = + per_cpu_ptr(size_buf->data, cpu)->capacity; } } else { ret = ring_buffer_resize(trace_buf->buffer, - per_cpu_ptr(size_buf->data, cpu_id)->entries, cpu_id); + per_cpu_ptr(size_buf->data, cpu_id)->capacity, cpu_id); if (ret == 0) - per_cpu_ptr(trace_buf->data, cpu_id)->entries = - per_cpu_ptr(size_buf->data, cpu_id)->entries; + per_cpu_ptr(trace_buf->data, cpu_id)->capacity = + per_cpu_ptr(size_buf->data, cpu_id)->capacity; } return ret; @@ -193,7 +193,7 @@ void free_snapshot(struct trace_array *tr) */ ring_buffer_subbuf_order_set(tr->snapshot_buffer.buffer, 0); ring_buffer_resize(tr->snapshot_buffer.buffer, 1, RING_BUFFER_ALL_CPUS); - trace_set_buffer_entries(&tr->snapshot_buffer, 1); + trace_set_buffer_capacity(&tr->snapshot_buffer, 1); tracing_reset_online_cpus(&tr->snapshot_buffer); tr->allocated_snapshot = false; } diff --git a/kernel/trace/trace_synth.h b/kernel/trace/trace_synth.h index 43f6fb6078db..d4ebf01b7049 100644 --- a/kernel/trace/trace_synth.h +++ b/kernel/trace/trace_synth.h @@ -37,5 +37,6 @@ struct synth_event { }; extern struct synth_event *find_synth_event(const char *name); +extern struct synth_event *find_synth_event_call(struct trace_event_call *call); #endif /* __TRACE_SYNTH_H */ diff --git a/kernel/trace/trace_syscalls.c b/kernel/trace/trace_syscalls.c index e35744049e3f..d9190fdc030d 100644 --- a/kernel/trace/trace_syscalls.c +++ b/kernel/trace/trace_syscalls.c @@ -683,7 +683,7 @@ static int syscall_copy_user_array(char *buf, const char __user *ptr, for (int i = 0; i < args->uargs; i++, buf += SYSCALL_FAULT_ARG_SZ) { ptr = (char __user *)args->ptr_array[i]; - ret = __copy_from_user(buf, ptr, size); + ret = copy_from_user(buf, ptr, size); args->read[i] = ret ? -1 : size; } return 0; diff --git a/tools/testing/selftests/ftrace/test.d/filter/event-filter-incomplete.tc b/tools/testing/selftests/ftrace/test.d/filter/event-filter-incomplete.tc new file mode 100644 index 000000000000..cc6df762845f --- /dev/null +++ b/tools/testing/selftests/ftrace/test.d/filter/event-filter-incomplete.tc @@ -0,0 +1,24 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# description: event filter - reject incomplete logical expressions +# requires: events/sched/sched_switch/filter +# flags: instance + +filter=events/sched/sched_switch/filter + +for base in \ + 'common_pid == 1' \ + 'common_pid == 1 || common_pid == 2' \ + 'common_pid == 1 && common_pid == 2' \ + '!(common_pid == 1 || common_pid == 2)' +do + printf '%s' "$base" > "$filter" + for tail in '&&' '||' '&& !' '|| !' '&& !! ' '|| !! '; do + if printf '%s' "$base $tail" > "$filter" 2>/dev/null; then + echo "Accepted incomplete filter: $base $tail" + exit_fail + fi + done +done + +echo 0 > "$filter"