From mboxrd@z Thu Jan 1 00:00:00 1970 From: zhaolei@cn.fujitsu.com (Zhaolei) Date: Sun, 29 Mar 2009 16:44:48 +0800 Subject: [ltt-dev] [PATCH 3/3] In-kernel filter module In-Reply-To: <49CF3437.50407@cn.fujitsu.com> References: <49646F42.5040003@cn.fujitsu.com> <20090107170544.GA10541@Krystal> <49867DA2.1070300@cn.fujitsu.com> <20090203051144.GA23739@Krystal> <49927147.9010909@cn.fujitsu.com> <20090211065021.GA10376@Krystal> <49927D46.6010508@cn.fujitsu.com> <20090211073719.GC10376@Krystal> <499D15A9.10509@cn.fujitsu.com> <49AB8445.7050202@cn.fujitsu.com> <20090303192532.GA4334@Krystal> <49CF3437.50407@cn.fujitsu.com> Message-ID: <49CF3500.7050109@cn.fujitsu.com> LTTng's in-kernel filter is used to filter event by its arguments. User can config every event's filter option by echo to filts in debugfs/ltt/filter Signed-off-by: Zhao Lei --- ltt/Kconfig | 9 + ltt/Makefile | 1 + ltt/ltt-filter.c | 1831 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1841 insertions(+), 0 deletions(-) create mode 100644 ltt/ltt-filter.c diff --git a/ltt/Kconfig b/ltt/Kconfig index 3c520f5..2cfb479 100644 --- a/ltt/Kconfig +++ b/ltt/Kconfig @@ -214,4 +214,13 @@ config LTT_ASCII Output trace data in a text-formatted ascii file, presented in /mnt/debugfs/ltt/ascii/. +config LTT_FILTER + tristate "Linux Trace Toolkit Filter" + depends on LTT_TRACER + depends on LTT_SERIALIZE + default n + help + Filter for LTTng to control which type of event to be traced. + Config filter rule by debugfs/ltt/filter + endif # LTT diff --git a/ltt/Makefile b/ltt/Makefile index 5ea5cca..29ab6e4 100644 --- a/ltt/Makefile +++ b/ltt/Makefile @@ -19,3 +19,4 @@ obj-$(CONFIG_LTT_KPROBES) += ltt-kprobes.o obj-$(CONFIG_LTT_TRACEPROBES) += probes/ obj-$(CONFIG_LTT_FTRACE) += ltt-ftrace.o obj-$(CONFIG_LTT_ASCII) += ltt-ascii.o +obj-$(CONFIG_LTT_FILTER) += ltt-filter.o diff --git a/ltt/ltt-filter.c b/ltt/ltt-filter.c new file mode 100644 index 0000000..bf2760a --- /dev/null +++ b/ltt/ltt-filter.c @@ -0,0 +1,1831 @@ +/* + * LTT filter module. + * + * Copyright 2009 - + * Zhaolei + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define LTT_FILTER_DIR "filter" + +struct dentry *ltt_filter_dir; + +/* + * Marker format operations + * + * Maintenance a marker_format list sync with module add and release + * Each data-type in format are paresed and stored in marker_format struct + * Impl a notifier for respond add/remove marker format event + */ + +enum format_item_type { + SIGNED, + UNSIGNED, + STRING, + CHAR, +}; +const char *format_item_type_string[] = { + "signed int", + "unsigned int", + "string", + "char", +}; + +struct format_item { + struct list_head list; + char name[PATH_MAX]; + enum format_item_type type; + size_t size; +}; + +struct marker_format { + atomic_t refcnt; + struct list_head list; + const char *name; + const char *format; + struct format_item **items; + size_t format_item_cnt; +}; + +static LIST_HEAD(marker_format_head); +static DEFINE_MUTEX(format_lock); + +BLOCKING_NOTIFIER_HEAD(format_notifier_list); +enum FORMAT_EVENT { + ADD_FORMAT, + DEL_FORMAT, +}; + +static struct format_item *new_format_item(const char *name_head, + const char *name_tail, int type_type, int type_size) +{ + struct format_item *format_item; + const char *p; + int i, name_len; + + format_item = kmalloc(sizeof(struct format_item), GFP_KERNEL); + if (!format_item) { + printk(KERN_ERR "%s: alloc format_item failed\n", __func__); + goto err_alloc_format_item; + } + + INIT_LIST_HEAD(&format_item->list); + + /* trim itemname */ + while (name_head < name_tail + && *name_head == ' ') + name_head++; + while (name_tail > name_head + && *(name_tail - 1) == ' ') + name_tail--; + + name_len = name_tail - name_head; + if (name_len) { + for (i = 0, p = name_head; i < PATH_MAX - 1 && p < name_tail; + i++, p++) { + if ((*p == '%' && p + 1 < name_tail && *(p + 1) == '%') + || (*p == '#' && + p + 1 < name_tail && *(p + 1) == '#')) { + p++; + } + format_item->name[i] = isalnum(*p) ? *p : '_'; + } + format_item->name[i] = 0; + } else { + format_item->name[0] = 0; + } + + format_item->type = type_type; + format_item->size = type_size; + + return format_item; + +err_alloc_format_item: + return NULL; +} + +static size_t parse_format(const char *format, struct format_item ***items) +{ + int ret = 0; /* number of types in format string, or -ERRNO on fail */ + int state; /* 0: in name, 1: in type, 2: in # */ + const char *p; /* format string's iter */ + /* name for current type, init with 0 to avoid compile warning */ + const char *name_head, *name_tail = 0; + enum format_item_type type_type; + /* + * size(in bytes) of current type + * init with 0 to avoid compile warning + */ + int type_size = 0; + + LIST_HEAD(format_item_list); + struct format_item *format_item, *format_item_next; + + for (state = 0, name_head = p = format; *p; p++) { + switch (state) { + case 0: + switch (*p) { + case '%': + if (*(p+1) != '%') { + name_tail = p; + type_size = sizeof(int); + state = 1; + } else { + p++; + } + break; + case '#': + if (*(p+1) != '#') { + name_tail = p; + state = 2; + } else { + p++; + } + break; + default: + break; + } + break; + case 1: + switch (*p) { + case 'l': + if (*(p+1) == 'l') { + type_size = sizeof(long long); + p++; + } else { + type_size = sizeof(long); + } + break; + case 'L': + type_size = sizeof(long long); + break; + case 'z': + case 'Z': + type_size = sizeof(size_t); + break; + case 't': + type_size = sizeof(ptrdiff_t); + break; + case 'h': + type_size = sizeof(short); + break; + case 'd': + case 'i': + type_type = SIGNED; + goto type_finish; + break; + case 'u': + case 'o': + case 'x': + case 'X': + type_type = UNSIGNED; + goto type_finish; + break; + case 'p': + type_type = UNSIGNED; + type_size = sizeof(void *); + goto type_finish; + break; + case 's': + type_type = STRING; + type_size = sizeof(void *); + goto type_finish; + break; + case 'c': + type_type = CHAR; + type_size = sizeof(char); + goto type_finish; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + break; + default: + printk(KERN_ERR + "%s: unknown char after %%: %c\n", + __func__, *p); + break; + } + break; + case 2: + switch (*p) { + case 'l': + case 'L': + case 'Z': + case 'z': + case 't': + case 'h': + case 'd': + case 'i': + case 'u': + case 'o': + case 'x': + case 'X': + case 'p': + case 's': + case 'c': + case '1': + case '2': + case '4': + case '8': + break; + case '%': + type_size = sizeof(int); + state = 1; + break; + default: + printk(KERN_ERR "%s: unknown char after #: %c\n", + __func__, *p); + break; + } + break; + default: + /* should not run to here */ + ret = -EINVAL; + goto err_unknown_state; + } + continue; +type_finish: + format_item = new_format_item(name_head, name_tail, type_type, + type_size); + if (!format_item) { + printk(KERN_ERR "%s: add_format_item failed\n", + __func__); + ret = -ENOMEM; + goto err_new_format_item; + } + + list_add_tail(&format_item->list, &format_item_list); + ret++; + name_head = p + 1; + state = 0; + continue; + } + + /* + * We have parsed types in format, and build linked_list to save + * there information. + * Now we will build a point array to save each format_item for + * easy access. + */ + + *items = kmalloc(ret * sizeof(struct format_item *), GFP_KERNEL); + if (!*items) { + printk(KERN_ERR "%s: alloc format_item index failed\n", + __func__); + ret = -ENOMEM; + goto err_alloc_format_item; + } + + /* we reuse state as a iter for (*items)[] */ + state = 0; + list_for_each_entry(format_item, &format_item_list, list) + (*items)[state++] = format_item; + + return ret; + +err_alloc_format_item: +err_unknown_state: +err_new_format_item: + list_for_each_entry_safe(format_item, format_item_next, + &format_item_list, list) + kfree(format_item); + return ret; +} + +/* need have format_lock */ +static struct marker_format *_find_marker_format(const char *name) +{ + struct marker_format *format; + list_for_each_entry(format, &marker_format_head, list) { + if (!strcmp(name, format->name)) + return format; + } + return NULL; +} + +/* + * For add_marker_format internal use + * need have format_lock + */ +static struct marker_format *_new_marker_format(struct marker *marker) +{ + struct marker_format *format; + char *tmp; + + format = kmalloc(sizeof(struct marker_format), GFP_KERNEL); + if (!format) + goto err_alloc_format; + + atomic_set(&format->refcnt, 1); + + INIT_LIST_HEAD(&format->list); + + tmp = kmalloc(strlen(marker->name) + 1, GFP_KERNEL); + if (!tmp) + goto err_alloc_name; + strcpy(tmp, marker->name); + format->name = tmp; + + tmp = kmalloc(strlen(marker->format) + 1, GFP_KERNEL); + if (!tmp) + goto err_alloc_formatstr; + strcpy(tmp, marker->format); + format->format = tmp; + + format->format_item_cnt = parse_format(marker->format, + &format->items); + if (IS_ERR_VALUE(format->format_item_cnt)) { + printk(KERN_ERR "%s: parse format failed: %s\n", __func__, + marker->format); + goto err_parse_file; + } + + list_add(&format->list, &marker_format_head); + + blocking_notifier_call_chain(&format_notifier_list, ADD_FORMAT, format); + + return format; + +err_parse_file: + kfree(format->format); +err_alloc_formatstr: + kfree(format->name); +err_alloc_name: + kfree(format); +err_alloc_format: + return NULL; +} + +/* need have format_lock */ +static void _destroy_marker_format(struct marker_format *format) +{ + int i; + + blocking_notifier_call_chain(&format_notifier_list, DEL_FORMAT, format); + + list_del(&format->list); + + for (i = 0; i < format->format_item_cnt; i++) + kfree(format->items[i]); + kfree(format->items); + kfree(format->format); + kfree(format->name); + kfree(format); + + return; +} + +static struct marker_format *add_marker_format(struct marker *marker) +{ + struct marker_format *format; + + mutex_lock(&format_lock); + + format = _find_marker_format(marker->name); + if (!format) { + format = _new_marker_format(marker); + if (!format) { + printk(KERN_ERR + "%s: new format struct for %s failed\n", + __func__, marker->name); + goto err_alloc_format; + } + } else { + atomic_inc(&format->refcnt); + } + + mutex_unlock(&format_lock); + return format; + +err_alloc_format: + mutex_unlock(&format_lock); + return NULL; +} + +static int put_marker_format(const char *name) +{ + struct marker_format *format; + int err = 0; + + mutex_lock(&format_lock); + + format = _find_marker_format(name); + if (!format) { + printk(KERN_ERR "%s: can't find format item for %s\n", + __func__, name); + err = -ENOENT; + goto err_find_marker_format; + } + + if (atomic_dec_and_test(&format->refcnt)) + _destroy_marker_format(format); + + mutex_unlock(&format_lock); + return 0; + +err_find_marker_format: + mutex_unlock(&format_lock); + return err; +} + +static void destroy_all_format(void) +{ + struct marker_format *format, *next; + + mutex_lock(&format_lock); + + list_for_each_entry_safe(format, next, &marker_format_head, list) { + _destroy_marker_format(format); + } + + mutex_unlock(&format_lock); + + return; +} + +static int bulid_exist_format(void) +{ + struct marker_iter iter; + struct marker_format *format; + int err = 0; + + if (!list_empty(&marker_format_head)) { + err = -EEXIST; + goto err_not_empty; + } + + marker_iter_reset(&iter); + marker_iter_start(&iter); + for (; iter.marker != NULL; marker_iter_next(&iter)) { + format = add_marker_format(iter.marker); + if (!format) { + err = -EIO; + goto err_build_fail; + } + } + marker_iter_stop(&iter); + return 0; + +err_build_fail: + destroy_all_format(); +err_not_empty: + return err; +} + +static int module_notify(struct notifier_block *self, + unsigned long val, void *data) +{ + struct module *mod = data; + struct marker *iter; + + switch (val) { + case MODULE_STATE_COMING: + for (iter = mod->markers; + iter < mod->markers + mod->num_markers; iter++) + add_marker_format(iter); + break; + case MODULE_STATE_GOING: + for (iter = mod->markers; + iter < mod->markers + mod->num_markers; iter++) + put_marker_format(iter->name); + break; + } + return NOTIFY_OK; +} + +static struct notifier_block module_nb = { + .notifier_call = module_notify, +}; + +static int marker_format_init(void) +{ + int err = 0; + + err = bulid_exist_format(); + if (IS_ERR_VALUE(err)) { + printk(KERN_ERR "%s: bulid_exist_format failed\n", __func__); + goto err_add_exist_filter; + } + + /* Todo: + * If a module is insert/delete here(between + * bulid_exist_format() and register_module_notifier()), + * marker_format struct is not uptodate. + */ + + err = register_module_notifier(&module_nb); + if (IS_ERR_VALUE(err)) { + printk(KERN_ERR "%s register module notifier failed\n", + __func__); + goto err_register_module_notifier; + } + + return 0; + +err_register_module_notifier: + destroy_all_format(); +err_add_exist_filter: + return err; +} + +static void marker_format_destroy(void) +{ + unregister_module_notifier(&module_nb); + destroy_all_format(); +} + +static int register_format_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&format_notifier_list, nb); +} + +static int unregister_format_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&format_notifier_list, nb); +} + + +/* + * Control operations + * + * Maintenance a dir struct and datastruct for control filter + * dir struct will dynamical updated when marker_format got changed + * + * Todo: + * Control per-trace filter + * + */ + +enum compare_op { + gt, + ge, + eq, + le, + lt, +}; +const char *compare_op_string[] = { + ">", + ">=", + "=", + "<=", + "<", +}; + +struct marker_filter; +struct filter_item { + int enable; + union { + struct { + enum compare_op op; + long long value; + } signed_type; + struct { + enum compare_op op; + unsigned long long value; + } unsigned_type; + struct { + enum compare_op op; + char value[PATH_MAX]; /* Todo: Make it dynamic */ + } string_type; + struct { + enum compare_op op; + unsigned char value; + } char_type; + } setting; + struct marker_filter *parent; + struct dentry *den[2]; /* [0]: __argX, [1]: symbol link to __argX */ +}; + +struct marker_filter { + struct list_head list; + struct marker_format *format; + struct filter_item *items; + struct dentry *rootden; + struct dentry *formatden; + /* + * is any of items[] are enabled + * for bypass marker which haven't any filter fast + */ + int enable; +}; +static inline unsigned int filter_item_no(struct filter_item *filter_item) +{ + return filter_item - filter_item->parent->items; +} +static inline struct format_item *filter_item_format( + struct filter_item *filter_item) +{ + return filter_item->parent->format->items[filter_item_no(filter_item)]; +} + +static LIST_HEAD(marker_filter_head); + +/* + * Use spinlock to lock filter struct because we can't use mutex in filter + * callback + */ +static DEFINE_SPINLOCK(filter_lock); + +/* + * We need call some kernel function in ltt-filter function, but many kernel + * function have tp/markers which cause cpu jump to ltt, then jump to + * ltt-filter from ltt code. + * Even if we don't call any kernel function in filter code, interrupt will + * do similar thing. + * To make it easy, we avoid this type of nesting by lock/unlock_filter. + */ +DEFINE_PER_CPU(atomic_t, filter_nesting) = ATOMIC_INIT(0); +static inline int lock_filter(void) +{ + preempt_disable(); + if (atomic_xchg(&__get_cpu_var(filter_nesting), 1) > 0) { + /* Already in lock */ + preempt_enable(); + return 1; + } + return 0; +} +static inline void unlock_filter(void) +{ + atomic_set(&__get_cpu_var(filter_nesting), 0); + preempt_enable(); +} + +struct marker_filter *_find_marker_filter(const char *name) +{ + struct marker_filter *filter; + list_for_each_entry(filter, &marker_filter_head, list) { + if (!strcmp(name, filter->format->name)) + return filter; + } + return NULL; +} + +static ssize_t format_read(struct file *filp, char __user *ubuf, size_t cnt, + loff_t *ppos) +{ + size_t ret; + struct marker_filter *filter; + + lock_filter(); + spin_lock(&filter_lock); + filter = filp->f_dentry->d_inode->i_private; + if (filter) { + ret = simple_read_from_buffer(ubuf, cnt, ppos, + filter->format->format, strlen(filter->format->format)); + } else { + ret = -EIO; + } + spin_unlock(&filter_lock); + unlock_filter(); + + return ret; +} + +static const struct file_operations format_fops = { + .read = format_read, +}; + +static int signed_type_read(char *buf, size_t bufsize, + struct filter_item *filter_item) +{ + int ret; + int pos = 0; + + ret = scnprintf(buf + pos, bufsize - pos, "%s", + compare_op_string[filter_item->setting.signed_type.op]); + if (!ret) + return 0; + pos += ret; + + ret = scnprintf(buf + pos, bufsize - pos, " %lld", + filter_item->setting.signed_type.value); + if (!ret) + return 0; + pos += ret; + + return pos; +} + +static int unsigned_type_read(char *buf, size_t bufsize, + struct filter_item *filter_item) +{ + int ret; + int pos = 0; + + ret = scnprintf(buf + pos, bufsize - pos, "%s", + compare_op_string[filter_item->setting.unsigned_type.op]); + if (!ret) + return 0; + pos += ret; + + ret = scnprintf(buf + pos, bufsize - pos, " %llu", + filter_item->setting.unsigned_type.value); + if (!ret) + return 0; + pos += ret; + + return pos; +} + +static int string_type_read(char *buf, size_t bufsize, + struct filter_item *filter_item) +{ + int ret; + int pos = 0; + + ret = scnprintf(buf + pos, bufsize - pos, "%s", + compare_op_string[filter_item->setting.string_type.op]); + if (!ret) + return 0; + pos += ret; + + ret = scnprintf(buf + pos, bufsize - pos, " %s", + filter_item->setting.string_type.value); + if (!ret) + return 0; + pos += ret; + + return pos; +} + +static int char_type_read(char *buf, size_t bufsize, + struct filter_item *filter_item) +{ + int ret; + int pos = 0; + + ret = scnprintf(buf + pos, bufsize - pos, "%s", + compare_op_string[filter_item->setting.char_type.op]); + if (!ret) + return 0; + pos += ret; + + switch (filter_item->setting.char_type.value) { + case '\t': + ret = scnprintf(buf + pos, bufsize - pos, " \\t"); + break; + case ' ': + ret = scnprintf(buf + pos, bufsize - pos, " blank"); + break; + case '\n': + ret = scnprintf(buf + pos, bufsize - pos, " \\n"); + break; + default: + if (isgraph(filter_item->setting.char_type.value)) { + ret = scnprintf(buf + pos, bufsize - pos, " %c", + filter_item->setting.char_type.value); + } else { + ret = scnprintf(buf + pos, bufsize - pos, + " \\%u", (unsigned int) + filter_item->setting.char_type.value); + } + break; + } + if (!ret) + return 0; + pos += ret; + + return pos; +} + +static ssize_t type_read(struct file *filp, char __user *ubuf, size_t cnt, + loff_t *ppos) +{ + size_t ret; + struct filter_item *filter_item; + + lock_filter(); + spin_lock(&filter_lock); + filter_item = filp->f_dentry->d_inode->i_private; + if (filter_item) { + struct format_item *format_item; + char buf[128]; + int pos = 0; + + format_item = filter_item_format(filter_item); + + ret = scnprintf(buf + pos, sizeof(buf) - pos, + "type: %u byte(s) %s\n", + format_item->size, + format_item_type_string[format_item->type]); + if (!ret) { + ret = -EINVAL; + goto err_format_string; + } + pos += ret; + + ret = scnprintf(buf + pos, sizeof(buf) - pos, "setting: "); + if (!ret) { + ret = -EINVAL; + goto err_format_string; + } + pos += ret; + + if (!filter_item->enable) { + ret = scnprintf(buf + pos, sizeof(buf) - pos, "*"); + if (!ret) { + ret = -EINVAL; + goto err_format_string; + } + pos += ret; + goto format_finish; + } + + switch (format_item->type) { + case SIGNED: + ret = signed_type_read(buf + pos, sizeof(buf) - pos, + filter_item); + break; + case UNSIGNED: + ret = unsigned_type_read(buf + pos, sizeof(buf) - pos, + filter_item); + break; + case STRING: + ret = string_type_read(buf + pos, sizeof(buf) - pos, + filter_item); + break; + case CHAR: + ret = char_type_read(buf + pos, sizeof(buf) - pos, + filter_item); + break; + default: + /* should not run to here */ + printk(KERN_ERR "%s: unknown type %d\n", + __func__, format_item->type); + ret = -EIO; + goto err_unknown_type; + } + if (!ret) { + ret = -EINVAL; + goto err_format_string; + } + pos += ret; + +format_finish: + ret = scnprintf(buf + pos, sizeof(buf) - pos, "\n"); + if (!ret) { + ret = -EINVAL; + goto err_format_string; + } + pos += ret; + + ret = simple_read_from_buffer(ubuf, cnt, ppos, buf, + strlen(buf)); + } else { + ret = -EIO; + } + +err_format_string: +err_unknown_type: + spin_unlock(&filter_lock); + unlock_filter(); + + return ret; +} + +/* + * Check op info in a input string + * return length of op part on success, *op is set. + * return 0 on fail + */ +static size_t scan_op(const char *str, enum compare_op *op) +{ + /* Must reverse sort with strlen */ + static struct { + const char *str; + enum compare_op op; + } str_op[] = { + {"gt", gt}, + {"ge", ge}, + {"eq", eq}, + {"le", le}, + {"lt", lt}, + {">=", ge}, + {"<=", le}, + {">", gt}, + {"=", eq}, + {"<", lt}, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(str_op); i++) { + if (!strncmp(str, str_op[i].str, strlen(str_op[i].str))) { + *op = str_op[i].op; + return strlen(str_op[i].str); + } + } + + return 0; +} + +static int signed_type_write(const char *buf, struct format_item *format_item, + struct filter_item *filter_item) +{ + int oplen; + enum compare_op op; + long long value = 0; + + oplen = scan_op(buf, &op); + if (!oplen) + return -EINVAL; + + switch (format_item->size) { + case 1: + { + char tmp; + if (sscanf(buf + oplen, "%hhd", &tmp) != 1) + return -EINVAL; + value = tmp; + } + break; + case 2: + { + short int tmp; + if (sscanf(buf + oplen, "%hd", &tmp) != 1) + return -EINVAL; + value = tmp; + } + break; + case 4: + { + int tmp; + if (sscanf(buf + oplen, "%d", &tmp) != 1) + return -EINVAL; + value = tmp; + } + break; + case 8: + if (sscanf(buf + oplen, "%lld", &value) != 1) + return -EINVAL; + break; + default: + /* should not run to here */ + printk(KERN_ERR "%s: unknown size %zu\n", __func__, + format_item->size); + return -EIO; + } + + filter_item->setting.signed_type.op = op; + filter_item->setting.signed_type.value = value; + + return 0; +} + +static int unsigned_type_write(const char *buf, struct format_item *format_item, + struct filter_item *filter_item) +{ + int oplen; + enum compare_op op; + long long value = 0; + + oplen = scan_op(buf, &op); + if (!oplen) + return -EINVAL; + + switch (format_item->size) { + case 1: + { + unsigned char tmp; + if (sscanf(buf + oplen, "%hhu", &tmp) != 1) + return -EINVAL; + value = tmp; + } + break; + case 2: + { + unsigned short int tmp; + if (sscanf(buf + oplen, "%hu", &tmp) != 1) + return -EINVAL; + value = tmp; + } + break; + case 4: + { + unsigned int tmp; + if (sscanf(buf + oplen, "%u", &tmp) != 1) + return -EINVAL; + value = tmp; + } + break; + case 8: + if (sscanf(buf + oplen, "%llu", &value) != 1) + return -EINVAL; + break; + default: + /* should not run to here */ + printk(KERN_ERR "%s: unknown size %zu\n", __func__, + format_item->size); + return -EIO; + } + + filter_item->setting.unsigned_type.op = op; + filter_item->setting.unsigned_type.value = value; + + return 0; +} + +static int string_type_write(const char *buf, struct format_item *format_item, + struct filter_item *filter_item) +{ + int oplen; + enum compare_op op; + int val_len; + + oplen = scan_op(buf, &op); + if (!oplen) + return -EINVAL; + + /* End string on \n */ + for (val_len = 0; *(buf + oplen + val_len); val_len++) { + if (*(buf + oplen + val_len) == '\n') + break; + } + + if (val_len > ARRAY_SIZE(filter_item->setting.string_type.value) - 1) + return -EINVAL; + + filter_item->setting.string_type.op = op; + memcpy(filter_item->setting.string_type.value, buf + oplen, val_len); + filter_item->setting.string_type.value[val_len] = 0; + + return 0; +} + +static int char_type_write(const char *buf, struct format_item *format_item, + struct filter_item *filter_item) +{ + int oplen; + enum compare_op op; + unsigned char value; + int val_start; + + oplen = scan_op(buf, &op); + if (!oplen) + return -EINVAL; + + /* bypass blanks */ + for (val_start = 0; *(buf + oplen + val_start) == ' ' || + *(buf + oplen + val_start) == '\t'; val_start++) + ; + + /* + * parse escape string, following escape string are supported: + * "\n" + * "\[num]" + * "\[char expect \n and \[num]]", as "\\", "\ ", ... + */ + switch (*(buf + oplen + val_start)) { + case '\0': + case '\n': + return -EINVAL; + case '\\': + switch (*(buf + oplen + val_start + 1)) { + case '\0': + case '\n': + return -EINVAL; + case 'n': + value = '\n'; + break; + default: + if (isdigit(*(buf + oplen + val_start + 1))) { + if (sscanf(buf + oplen + val_start + 1, "%hhu", + &value) != 1) + return -EINVAL; + } else { + value = + (*(buf + oplen + val_start + 1)); + } + break; + } + break; + default: + value = *(buf + oplen + val_start); + break; + } + + filter_item->setting.char_type.op = op; + filter_item->setting.char_type.value = value; + + return 0; +} + +static ssize_t type_write(struct file *filp, const char __user *ubuf, + size_t cnt, loff_t *ppos) +{ + size_t ret; + struct filter_item *filter_item; + + lock_filter(); + spin_lock(&filter_lock); + filter_item = filp->f_dentry->d_inode->i_private; + if (filter_item) { + struct format_item *format_item; + char buf[NAME_MAX]; + size_t buf_size; + int val_start; + + format_item = filter_item_format(filter_item); + + buf_size = min(cnt, sizeof(buf) - 1); + if (copy_from_user(buf, ubuf, buf_size)) { + ret = -EFAULT; + goto err_copy_from_user; + } + buf[buf_size] = 0; + + /* Check is to disable filter */ + for (val_start = 0; *(buf + val_start) == ' ' || + *(buf + val_start) == '\t'; val_start++) + ; + if (!strncmp(buf + val_start, "*", strlen("*")) + || !strncmp(buf + val_start, "off", strlen("off"))) { + struct marker_filter *filter; + int i; + + filter_item->enable = 0; + + /* + * rescan all items in this marker to + * update marker_filter->enable + */ + filter = filter_item->parent; + filter->enable = 0; + for (i = 0; i < filter->format->format_item_cnt; i++) { + if (filter->items[i].enable) { + filter->enable = 1; + break; + } + } + + goto scan_finish; + } + + switch (format_item->type) { + case SIGNED: + ret = signed_type_write(buf, format_item, filter_item); + break; + case UNSIGNED: + ret = unsigned_type_write(buf, format_item, + filter_item); + break; + case STRING: + ret = string_type_write(buf, format_item, filter_item); + break; + case CHAR: + ret = char_type_write(buf, format_item, filter_item); + break; + default: + /* should not run to here */ + printk(KERN_ERR "%s: unknown type %d\n", + __func__, format_item->type); + ret = -EIO; + goto err_unknown_type; + } + if (IS_ERR_VALUE(ret)) + goto err_scan_string; + + filter_item->enable = 1; + filter_item->parent->enable = 1; + +scan_finish: + ret = cnt; + } else { + ret = -EIO; + } + +err_scan_string: +err_unknown_type: +err_copy_from_user: + spin_unlock(&filter_lock); + unlock_filter(); + + return ret; +} + +static const struct file_operations type_fops = { + .read = type_read, + .write = type_write, +}; + +static int init_filter_items(struct marker_filter *filter, + struct marker_format *format) +{ + int err = 0; + int i; + + for (i = 0; i < format->format_item_cnt; i++) { + char fname[16]; + + filter->items[i].parent = filter; + filter->items[i].enable = 0; + + /* Create type file as __arg1, __arg2 */ + sprintf(fname, "__arg%d", i + 1); + filter->items[i].den[0] = debugfs_create_file(fname, S_IRUGO, + filter->rootden, &filter->items[i], &type_fops); + if (IS_ERR(filter->items[i].den[0]) + || !filter->items[i].den[0]) { + printk(KERN_ERR + "%s: create type file of %s for %s failed\n", + __func__, fname, filter->format->name); + err = -EIO; + goto err_build_typefile; + } + + /* Create symbol link to type file */ + if (filter->format->items[i]->name[0]) { + + filter->items[i].den[1] = debugfs_create_symlink( + filter->format->items[i]->name, + filter->rootden, fname); + if (IS_ERR(filter->items[i].den[1]) + || !filter->items[i].den[1]) { + /* + * same-name type is allowed, but it will cause + * create symlink fail. + * we ignore this type of error. + */ + filter->items[i].den[1] = NULL; + } + } + } + + return 0; + +err_build_typefile: + for (i--; i >= 0; i--) { + /* see comments in _destroy_marker_filter() for why set NULL */ + filter->items[i].den[0]->d_inode->i_private = NULL; + debugfs_remove(filter->items[i].den[0]); + debugfs_remove(filter->items[i].den[1]); + } + return err; +} + +static int add_marker_filter(struct marker_format *format) +{ + int err = 0; + struct marker_filter *filter; + + lock_filter(); + spin_lock(&filter_lock); + +#ifdef _DEBUG + if (_find_marker_filter(format->name)) { + printk(KERN_ERR "%s: filter %s already exist\n", __func__, + format->name); + err = -EIO; + goto err_already_exist; + } +#endif /* _DEBUG */ + + filter = kmalloc(sizeof(struct marker_filter), GFP_KERNEL); + if (!filter) { + printk(KERN_ERR "%s: alloc struct marker_filter failed\n", + __func__); + err = -ENOMEM; + goto err_alloc_marker_filter; + } + + INIT_LIST_HEAD(&filter->list); + + filter->format = format; + + filter->rootden = debugfs_create_dir(format->name, ltt_filter_dir); + if (IS_ERR(filter->rootden) || !filter->rootden) { + printk(KERN_ERR "%s: create dir of %s failed\n", __func__, + format->name); + err = -EIO; + goto err_create_dir; + } + + filter->formatden = debugfs_create_file("__arg0", S_IRUGO, + filter->rootden, filter, &format_fops); + if (IS_ERR(filter->formatden) || !filter->formatden) { + printk(KERN_ERR + "%s: create typefile failed\n", __func__); + err = -EIO; + goto err_build_typefile; + } + + if (format->format_item_cnt) { + filter->items = kmalloc(sizeof(struct filter_item) + * format->format_item_cnt, GFP_KERNEL); + if (!filter->items) { + printk(KERN_ERR + "%s: alloc filter's items failed\n", __func__); + err = -ENOMEM; + goto err_alloc_types; + } + err = init_filter_items(filter, format); + if (IS_ERR_VALUE(err)) { + printk(KERN_ERR + "%s: init filter's items failed\n", __func__); + goto err_init_types; + } + } else { + filter->items = NULL; + } + + filter->enable = 0; + + list_add(&filter->list, &marker_filter_head); + + spin_unlock(&filter_lock); + unlock_filter(); + + return 0; + +err_init_types: + kfree(filter->items); +err_alloc_types: + /* see comments in _destroy_marker_filter() for why set NULL */ + filter->formatden->d_inode->i_private = NULL; + debugfs_remove(filter->formatden); +err_build_typefile: + debugfs_remove(filter->rootden); +err_create_dir: + kfree(filter); +err_alloc_marker_filter: +#ifdef _DEBUG +err_already_exist: +#endif /* _DEBUG */ + spin_unlock(&filter_lock); + unlock_filter(); + return err; +} + +static void _destroy_marker_filter(struct marker_filter *filter) +{ + int i; + + list_del(&filter->list); + + for (i = 0; i < filter->format->format_item_cnt; i++) { + filter->items[i].den[0]->d_inode->i_private = NULL; + debugfs_remove(filter->items[i].den[0]); + debugfs_remove(filter->items[i].den[1]); + } + kfree(filter->items); + + /* + * Set i_private to NULL, so that if some fileop in process, + * it can avoid illegal access using i_private in fileop. + * (we must get filter_lock in fileop to ensure no other change + * in filter struct) + */ + filter->formatden->d_inode->i_private = NULL; + debugfs_remove(filter->formatden); + + debugfs_remove(filter->rootden); + + kfree(filter); + + return; +} + +static void destroy_all_filter(void) +{ + struct marker_filter *filter, *next; + + lock_filter(); + spin_lock(&filter_lock); + list_for_each_entry_safe(filter, next, &marker_filter_head, list) { + _destroy_marker_filter(filter); + } + spin_unlock(&filter_lock); + unlock_filter(); +} + +static int bulid_exist_filter(void) +{ + int err; + struct marker_format *format; + + if (!list_empty(&marker_filter_head)) { + err = -EEXIST; + goto err_not_empty; + } + + mutex_lock(&format_lock); + list_for_each_entry(format, &marker_format_head, list) { + err = add_marker_filter(format); + if (IS_ERR_VALUE(err)) { + printk("%s: add_marker_filter failed\n", __func__); + goto err_add_filter; + } + } + mutex_unlock(&format_lock); + + return 0; + +err_add_filter: + destroy_all_filter(); + mutex_unlock(&format_lock); +err_not_empty: + return err; +} + +static int format_notify(struct notifier_block *self, unsigned long val, + void *data) +{ + struct marker_format *format = data; + struct marker_filter *filter, *next; + + switch (val) { + case ADD_FORMAT: + add_marker_filter(format); + break; + case DEL_FORMAT: + lock_filter(); + spin_lock(&filter_lock); + list_for_each_entry_safe(filter, next, &marker_filter_head, + list) { + if (filter->format == format) + _destroy_marker_filter(filter); + } + spin_unlock(&filter_lock); + unlock_filter(); + break; + } + return NOTIFY_OK; +} + +static struct notifier_block format_nb = { + .notifier_call = format_notify, +}; + +static int marker_filter_init(void) +{ + int err = 0; + + err = bulid_exist_filter(); + if (IS_ERR_VALUE(err)) { + printk(KERN_ERR "%s: bulid_exist_filter failed\n", __func__); + goto err_add_exist_filter; + } + + err = register_format_notifier(&format_nb); + if (IS_ERR_VALUE(err)) { + printk(KERN_ERR "%s register format notifier failed\n", + __func__); + goto err_register_format_notifier; + } + + return 0; + +err_register_format_notifier: + destroy_all_filter(); +err_add_exist_filter: + return err; +} + +static void marker_filter_destroy(void) +{ + unregister_format_notifier(&format_nb); + destroy_all_filter(); +} + + +/* + * Filter operations + * + * define filter callback for lttng to determine if to output a event + * by user's setting. + * + */ + +/* + * Ret: + * 0: filted + * 1: passed + * <0: err + */ +static int signed_filter(struct format_item *format_item, + struct filter_item *filter_item, va_list *args) +{ + long long int val; + + switch (format_item->size) { + case 1: + val = (char)va_arg(*args, int); + break; + case 2: + val = (short int)va_arg(*args, int); + break; + case 4: + val = va_arg(*args, int); + break; + case 8: + val = va_arg(*args, long long int); + break; + default: + /* should not run to here */ + printk(KERN_ERR "%s: unknown size %d\n", __func__, + format_item->size); + return -1; + } + + /* + * We judge filter_item->enable here, because even if filter is not + * enabled, we still need to bypass this data in va_list + */ + if (!filter_item->enable) + return 1; + + switch (filter_item->setting.signed_type.op) { + case gt: + return val > filter_item->setting.signed_type.value; + case ge: + return val >= filter_item->setting.signed_type.value; + case eq: + return val == filter_item->setting.signed_type.value; + case le: + return val <= filter_item->setting.signed_type.value; + case lt: + return val < filter_item->setting.signed_type.value; + } + + /* should not run to here */ + return -1; +} + +static int unsigned_filter(struct format_item *format_item, + struct filter_item *filter_item, va_list *args) +{ + unsigned long long int val; + + switch (format_item->size) { + case 1: + val = (unsigned char)va_arg(*args, unsigned int); + break; + case 2: + val = (short int)va_arg(*args, unsigned int); + break; + case 4: + val = va_arg(*args, unsigned int); + break; + case 8: + val = va_arg(*args, unsigned long long int); + break; + default: + /* should not run to here */ + printk(KERN_ERR "%s: unknown size %d\n", __func__, + format_item->size); + return -1; + } + + /* + * We judge filter_item->enable here, because even if filter is not + * enabled, we still need to bypass this data in va_list + */ + if (!filter_item->enable) + return 1; + + switch (filter_item->setting.unsigned_type.op) { + case gt: + return val > filter_item->setting.unsigned_type.value; + case ge: + return val >= filter_item->setting.unsigned_type.value; + case eq: + return val == filter_item->setting.unsigned_type.value; + case le: + return val <= filter_item->setting.unsigned_type.value; + case lt: + return val < filter_item->setting.unsigned_type.value; + } + + /* should not run to here */ + return -1; +} + +static int string_filter(struct format_item *format_item, + struct filter_item *filter_item, va_list *args) +{ + const char *val; + + val = va_arg(*args, const char *); + + /* + * We judge filter_item->enable here, because even if filter is not + * enabled, we still need to bypass this data in va_list + */ + if (!filter_item->enable) + return 1; + + switch (filter_item->setting.string_type.op) { + case gt: + return strcmp(val, filter_item->setting.string_type.value) > 0; + case ge: + return strcmp(val, filter_item->setting.string_type.value) >= 0; + case eq: + return strcmp(val, filter_item->setting.string_type.value) == 0; + case le: + return strcmp(val, filter_item->setting.string_type.value) <= 0; + case lt: + return strcmp(val, filter_item->setting.string_type.value) < 0; + } + + /* should not run to here */ + return -1; +} + +static int char_filter(struct format_item *format_item, + struct filter_item *filter_item, va_list *args) +{ + unsigned char val; + + val = (unsigned char)va_arg(*args, unsigned int); + + /* + * We judge filter_item->enable here, because even if filter is not + * enabled, we still need to bypass this data in va_list + */ + if (!filter_item->enable) + return 1; + + switch (filter_item->setting.char_type.op) { + case gt: + return val > filter_item->setting.char_type.value; + case ge: + return val >= filter_item->setting.char_type.value; + case eq: + return val == filter_item->setting.char_type.value; + case le: + return val <= filter_item->setting.char_type.value; + case lt: + return val < filter_item->setting.char_type.value; + } + + /* should not run to here */ + return -1; +} + +static int ltt_filter(struct ltt_trace_struct *trace, + const struct marker *mdata, va_list *args) +{ + int ret; + struct marker_filter *filter; + struct marker_format *format; + int i; + + if (lock_filter()) { + /* Don't output events caused by ltt-filter */ + return 0; + } + + spin_lock(&filter_lock); + + filter = _find_marker_filter(mdata->name); + if (!filter) { + printk(KERN_ERR "%s: unknown marker %s\n", __func__, + mdata->name); + ret = 1; + goto done; + } + + /* Bypass if no filter setting on this marker */ + if (!filter->enable) { + ret = 1; + goto done; + } + + format = filter->format; + + for (i = 0; i < format->format_item_cnt; i++) { + int result; + + switch (format->items[i]->type) { + case SIGNED: + result = signed_filter(format->items[i], + filter->items + i, args); + break; + case UNSIGNED: + result = unsigned_filter(format->items[i], + filter->items + i, args); + break; + case STRING: + result = string_filter(format->items[i], + filter->items + i, args); + break; + case CHAR: + result = char_filter(format->items[i], + filter->items + i, args); + break; + default: + /* should not run to here */ + printk(KERN_ERR "%s: unknown type %d\n", __func__, + format->items[i]->type); + ret = 1; + goto done; + } + if (result < 0) { + ret = 1; + goto done; + } + if (!result) { + ret = 0; + goto done; + } + } + + ret = 1; + +done: + spin_unlock(&filter_lock); + unlock_filter(); + return ret; +} + + +/* + * Init/exit option of module + * + * Init marker_format and marker_filter operations + * register filter callback in ltt + * + */ + +static int __init ltt_filter_init(void) +{ + int err = 0; + struct dentry *ltt_root_dentry; + + ltt_root_dentry = get_ltt_root(); + if (!ltt_root_dentry) { + err = -ENOENT; + goto err_no_root; + } + + ltt_filter_dir = debugfs_create_dir(LTT_FILTER_DIR, ltt_root_dentry); + if (IS_ERR(ltt_filter_dir) || !ltt_filter_dir) { + printk(KERN_ERR "%s: create dir of %s failed\n", __func__, + LTT_FILTER_DIR); + err = -ENOMEM; + goto err_create_filter_dir; + } + + err = marker_format_init(); + if (IS_ERR_VALUE(err)) { + printk(KERN_ERR "%s: init marker format failed\n", __func__); + goto err_marker_format_init; + } + + err = marker_filter_init(); + if (IS_ERR_VALUE(err)) { + printk(KERN_ERR "%s: init marker filter failed\n", __func__); + goto err_marker_filter_init; + } + + err = ltt_module_register(LTT_FUNCTION_RUN_FILTER, ltt_filter, + THIS_MODULE); + if (IS_ERR_VALUE(err)) { + printk(KERN_ERR "%s: register ltt filter failed\n", __func__); + goto err_filter_register; + } + + return 0; + +err_filter_register: + marker_filter_destroy(); +err_marker_filter_init: + marker_format_destroy(); +err_marker_format_init: + debugfs_remove(ltt_filter_dir); +err_create_filter_dir: +err_no_root: + return err; +} + +static void __exit ltt_filter_exit(void) +{ + ltt_module_unregister(LTT_FUNCTION_RUN_FILTER); + marker_filter_destroy(); + marker_format_destroy(); + + debugfs_remove(ltt_filter_dir); +} + +module_init(ltt_filter_init); +module_exit(ltt_filter_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Zhao Lei "); +MODULE_DESCRIPTION("Linux Trace Toolkit Filter"); -- 1.5.5.3