[ltt-dev] [PATCH 3/3] In-kernel filter module
Zhaolei
zhaolei at cn.fujitsu.com
Sun Mar 29 04:44:48 EDT 2009
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 <zhaolei at cn.fujitsu.com>
---
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/<trace name>.
+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 <zhaolei at cn.fujitsu.com>
+ */
+
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/ltt-core.h>
+#include <linux/ctype.h>
+#include <linux/uaccess.h>
+#include <linux/marker.h>
+#include <linux/ltt-tracer.h>
+#include <asm/atomic.h>
+
+#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 <zhaolei at cn.fujitsu.com>");
+MODULE_DESCRIPTION("Linux Trace Toolkit Filter");
--
1.5.5.3
More information about the lttng-dev
mailing list