[PATCH v2 2/2] debugfs: prevent access to removed files' private data

From: Nicolai Stange
Date: Mon Feb 08 2016 - 10:03:36 EST


Upon return of debugfs_remove()/debugfs_remove_recursive(), it might
still be attempted to access associated private file data through
previously opened struct file objects. If that data has been freed by
the caller of debugfs_remove*() in the meanwhile, the reading/writing
process would either encounter a fault or, if the memory address in
question has been reassigned again, unrelated data structures could get
overwritten.

However, since debugfs files are seldomly removed, usually from module
exit handlers only, the impact is very low.

Since debugfs_remove() and debugfs_remove_recursive() are already
waiting for a SRCU grace period before returning to their callers,
enclosing the access to private file data from ->read() and ->write()
within a SRCU read-side critical section does the trick:
- Introduce the debugfs_file_use_data_start() and
debugfs_file_use_data_finish() helpers which just enter and leave
a SRCU read-side critical section. The former also reports whether the
file is still alive, that is if d_delete() has _not_ been called on
the corresponding dentry.
- Introduce the DEFINE_DEBUGFS_ATTRIBUTE() macro which is completely
equivalent to the DEFINE_SIMPLE_ATTRIBUTE() macro except that
->read() and ->write are set to SRCU protecting wrappers around the
original simple_read() and simple_write() helpers.
- Use that DEFINE_DEBUGFS_ATTRIBUTE() macro for all debugfs_create_*()
attribute creation variants where appropriate.
- Manually introduce SRCU protection to the debugfs-predefined readers
and writers not covered by the above DEFINE_SIMPLE_ATTRIBUTE()->
DEFINE_DEBUGFS_ATTRIBUTE() replacement.

Finally, it should be worth to note that in the vast majority of cases
where debugfs users are handing in a "custom" struct file_operations
object to debugfs_create_file(), an attribute's associated data's
lifetime is bound to the one of the containing module and thus,
taking a reference on ->owner during file opening acts as a proxy here.
There is no need to do a mass replace of DEFINE_SIMPLE_ATTRIBUTE() to
DEFINE_DEBUGFS_ATTRIBUTE() outside of debugfs.

OTOH, new users of debugfs are encouraged to prefer the
DEFINE_DEBUGFS_ATTRIBUTE() macro over DEFINE_SIMPLE_ATTRIBUTE() and it,
as well as the needed read/write wrappers are made available globally.
For new users implementing their own readers and writers, the lifetime
management helpers debugfs_file_use_data_start() and
debugfs_file_use_data_finish() are exported.

Signed-off-by: Nicolai Stange <nicstange@xxxxxxxxx>
---
fs/debugfs/file.c | 221 ++++++++++++++++++++++++++++++++++++++----------
fs/debugfs/internal.h | 3 -
include/linux/debugfs.h | 28 ++++++
3 files changed, 205 insertions(+), 47 deletions(-)

diff --git a/fs/debugfs/file.c b/fs/debugfs/file.c
index 35ceae7..e6591a8 100644
--- a/fs/debugfs/file.c
+++ b/fs/debugfs/file.c
@@ -72,6 +72,81 @@ const struct file_operations debugfs_proxy_file_operations = {
.open = proxy_open,
};

+/**
+ * debugfs_file_use_data_start - mark the beginning of file data access
+ * @file: the file object whose data is being accessed.
+ * @srcu_idx: a pointer to some memory to store a SRCU index in.
+ *
+ * Up to a matching call to debugfs_file_use_data_finish(), any
+ * successive call into the file removing functions debugfs_remove()
+ * and debugfs_remove_recursive() will block. Since associated private
+ * file data may only get freed after a successful return of any of
+ * the removal functions, you may safely access it after a successful
+ * call to debugfs_file_use_data_start() without worrying about
+ * lifetime issues.
+ *
+ * If -%EIO is returned, the file has already been removed and thus,
+ * it is not safe to access any of its data. If, on the other hand,
+ * it is allowed to access the file data, zero is returned.
+ *
+ * Regardless of the return code, any call to
+ * debugfs_file_use_data_start() must be followed by a matching call
+ * to debugfs_file_use_data_finish().
+ */
+int debugfs_file_use_data_start(struct file *file, int *srcu_idx)
+ __acquires(&debugfs_srcu)
+{
+ *srcu_idx = srcu_read_lock(&debugfs_srcu);
+ if (d_unlinked(file->f_path.dentry))
+ return -EIO;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(debugfs_file_use_data_start);
+
+/**
+ * debugfs_file_use_data_finish - mark the end of file data access
+ * @srcu_idx: the SRCU index "created" by a former call to
+ * debugfs_file_use_data_start().
+ *
+ * Allow any ongoing concurrent call into debugfs_remove() or
+ * debugfs_remove_recursive() blocked by a former call to
+ * debugfs_file_use_data_start() to proceed and return to its caller.
+ */
+void debugfs_file_use_data_finish(int srcu_idx) __releases(&debugfs_srcu)
+{
+ srcu_read_unlock(&debugfs_srcu, srcu_idx);
+}
+EXPORT_SYMBOL_GPL(debugfs_file_use_data_finish);
+
+ssize_t debugfs_attr_read(struct file *file, char __user *buf,
+ size_t len, loff_t *ppos)
+{
+ ssize_t ret;
+ int srcu_idx;
+
+ ret = debugfs_file_use_data_start(file, &srcu_idx);
+ if (!ret)
+ ret = simple_attr_read(file, buf, len, ppos);
+ debugfs_file_use_data_finish(srcu_idx);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(debugfs_attr_read);
+
+ssize_t debugfs_attr_write(struct file *file, const char __user *buf,
+ size_t len, loff_t *ppos)
+{
+ ssize_t ret;
+ int srcu_idx;
+
+ ret = debugfs_file_use_data_start(file, &srcu_idx);
+ if (!ret)
+ ret = simple_attr_write(file, buf, len, ppos);
+ debugfs_file_use_data_finish(srcu_idx);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(debugfs_attr_write);
+
static struct dentry *debugfs_create_mode(const char *name, umode_t mode,
struct dentry *parent, void *value,
const struct file_operations *fops,
@@ -98,9 +173,9 @@ static int debugfs_u8_get(void *data, u64 *val)
*val = *(u8 *)data;
return 0;
}
-DEFINE_SIMPLE_ATTRIBUTE(fops_u8, debugfs_u8_get, debugfs_u8_set, "%llu\n");
-DEFINE_SIMPLE_ATTRIBUTE(fops_u8_ro, debugfs_u8_get, NULL, "%llu\n");
-DEFINE_SIMPLE_ATTRIBUTE(fops_u8_wo, NULL, debugfs_u8_set, "%llu\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_u8, debugfs_u8_get, debugfs_u8_set, "%llu\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_u8_ro, debugfs_u8_get, NULL, "%llu\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_u8_wo, NULL, debugfs_u8_set, "%llu\n");

/**
* debugfs_create_u8 - create a debugfs file that is used to read and write an unsigned 8-bit value
@@ -144,9 +219,9 @@ static int debugfs_u16_get(void *data, u64 *val)
*val = *(u16 *)data;
return 0;
}
-DEFINE_SIMPLE_ATTRIBUTE(fops_u16, debugfs_u16_get, debugfs_u16_set, "%llu\n");
-DEFINE_SIMPLE_ATTRIBUTE(fops_u16_ro, debugfs_u16_get, NULL, "%llu\n");
-DEFINE_SIMPLE_ATTRIBUTE(fops_u16_wo, NULL, debugfs_u16_set, "%llu\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_u16, debugfs_u16_get, debugfs_u16_set, "%llu\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_u16_ro, debugfs_u16_get, NULL, "%llu\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_u16_wo, NULL, debugfs_u16_set, "%llu\n");

/**
* debugfs_create_u16 - create a debugfs file that is used to read and write an unsigned 16-bit value
@@ -190,9 +265,9 @@ static int debugfs_u32_get(void *data, u64 *val)
*val = *(u32 *)data;
return 0;
}
-DEFINE_SIMPLE_ATTRIBUTE(fops_u32, debugfs_u32_get, debugfs_u32_set, "%llu\n");
-DEFINE_SIMPLE_ATTRIBUTE(fops_u32_ro, debugfs_u32_get, NULL, "%llu\n");
-DEFINE_SIMPLE_ATTRIBUTE(fops_u32_wo, NULL, debugfs_u32_set, "%llu\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_u32, debugfs_u32_get, debugfs_u32_set, "%llu\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_u32_ro, debugfs_u32_get, NULL, "%llu\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_u32_wo, NULL, debugfs_u32_set, "%llu\n");

/**
* debugfs_create_u32 - create a debugfs file that is used to read and write an unsigned 32-bit value
@@ -237,9 +312,9 @@ static int debugfs_u64_get(void *data, u64 *val)
*val = *(u64 *)data;
return 0;
}
-DEFINE_SIMPLE_ATTRIBUTE(fops_u64, debugfs_u64_get, debugfs_u64_set, "%llu\n");
-DEFINE_SIMPLE_ATTRIBUTE(fops_u64_ro, debugfs_u64_get, NULL, "%llu\n");
-DEFINE_SIMPLE_ATTRIBUTE(fops_u64_wo, NULL, debugfs_u64_set, "%llu\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_u64, debugfs_u64_get, debugfs_u64_set, "%llu\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_u64_ro, debugfs_u64_get, NULL, "%llu\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_u64_wo, NULL, debugfs_u64_set, "%llu\n");

/**
* debugfs_create_u64 - create a debugfs file that is used to read and write an unsigned 64-bit value
@@ -284,9 +359,10 @@ static int debugfs_ulong_get(void *data, u64 *val)
*val = *(unsigned long *)data;
return 0;
}
-DEFINE_SIMPLE_ATTRIBUTE(fops_ulong, debugfs_ulong_get, debugfs_ulong_set, "%llu\n");
-DEFINE_SIMPLE_ATTRIBUTE(fops_ulong_ro, debugfs_ulong_get, NULL, "%llu\n");
-DEFINE_SIMPLE_ATTRIBUTE(fops_ulong_wo, NULL, debugfs_ulong_set, "%llu\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_ulong, debugfs_ulong_get, debugfs_ulong_set,
+ "%llu\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_ulong_ro, debugfs_ulong_get, NULL, "%llu\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_ulong_wo, NULL, debugfs_ulong_set, "%llu\n");

/**
* debugfs_create_ulong - create a debugfs file that is used to read and write
@@ -321,21 +397,24 @@ struct dentry *debugfs_create_ulong(const char *name, umode_t mode,
}
EXPORT_SYMBOL_GPL(debugfs_create_ulong);

-DEFINE_SIMPLE_ATTRIBUTE(fops_x8, debugfs_u8_get, debugfs_u8_set, "0x%02llx\n");
-DEFINE_SIMPLE_ATTRIBUTE(fops_x8_ro, debugfs_u8_get, NULL, "0x%02llx\n");
-DEFINE_SIMPLE_ATTRIBUTE(fops_x8_wo, NULL, debugfs_u8_set, "0x%02llx\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_x8, debugfs_u8_get, debugfs_u8_set, "0x%02llx\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_x8_ro, debugfs_u8_get, NULL, "0x%02llx\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_x8_wo, NULL, debugfs_u8_set, "0x%02llx\n");

-DEFINE_SIMPLE_ATTRIBUTE(fops_x16, debugfs_u16_get, debugfs_u16_set, "0x%04llx\n");
-DEFINE_SIMPLE_ATTRIBUTE(fops_x16_ro, debugfs_u16_get, NULL, "0x%04llx\n");
-DEFINE_SIMPLE_ATTRIBUTE(fops_x16_wo, NULL, debugfs_u16_set, "0x%04llx\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_x16, debugfs_u16_get, debugfs_u16_set,
+ "0x%04llx\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_x16_ro, debugfs_u16_get, NULL, "0x%04llx\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_x16_wo, NULL, debugfs_u16_set, "0x%04llx\n");

-DEFINE_SIMPLE_ATTRIBUTE(fops_x32, debugfs_u32_get, debugfs_u32_set, "0x%08llx\n");
-DEFINE_SIMPLE_ATTRIBUTE(fops_x32_ro, debugfs_u32_get, NULL, "0x%08llx\n");
-DEFINE_SIMPLE_ATTRIBUTE(fops_x32_wo, NULL, debugfs_u32_set, "0x%08llx\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_x32, debugfs_u32_get, debugfs_u32_set,
+ "0x%08llx\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_x32_ro, debugfs_u32_get, NULL, "0x%08llx\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_x32_wo, NULL, debugfs_u32_set, "0x%08llx\n");

-DEFINE_SIMPLE_ATTRIBUTE(fops_x64, debugfs_u64_get, debugfs_u64_set, "0x%016llx\n");
-DEFINE_SIMPLE_ATTRIBUTE(fops_x64_ro, debugfs_u64_get, NULL, "0x%016llx\n");
-DEFINE_SIMPLE_ATTRIBUTE(fops_x64_wo, NULL, debugfs_u64_set, "0x%016llx\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_x64, debugfs_u64_get, debugfs_u64_set,
+ "0x%016llx\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_x64_ro, debugfs_u64_get, NULL, "0x%016llx\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_x64_wo, NULL, debugfs_u64_set, "0x%016llx\n");

/*
* debugfs_create_x{8,16,32,64} - create a debugfs file that is used to read and write an unsigned {8,16,32,64}-bit value
@@ -428,10 +507,10 @@ static int debugfs_size_t_get(void *data, u64 *val)
*val = *(size_t *)data;
return 0;
}
-DEFINE_SIMPLE_ATTRIBUTE(fops_size_t, debugfs_size_t_get, debugfs_size_t_set,
+DEFINE_DEBUGFS_ATTRIBUTE(fops_size_t, debugfs_size_t_get, debugfs_size_t_set,
"%llu\n"); /* %llu and %zu are more or less the same */
-DEFINE_SIMPLE_ATTRIBUTE(fops_size_t_ro, debugfs_size_t_get, NULL, "%llu\n");
-DEFINE_SIMPLE_ATTRIBUTE(fops_size_t_wo, NULL, debugfs_size_t_set, "%llu\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_size_t_ro, debugfs_size_t_get, NULL, "%llu\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_size_t_wo, NULL, debugfs_size_t_set, "%llu\n");

/**
* debugfs_create_size_t - create a debugfs file that is used to read and write an size_t value
@@ -461,10 +540,12 @@ static int debugfs_atomic_t_get(void *data, u64 *val)
*val = atomic_read((atomic_t *)data);
return 0;
}
-DEFINE_SIMPLE_ATTRIBUTE(fops_atomic_t, debugfs_atomic_t_get,
+DEFINE_DEBUGFS_ATTRIBUTE(fops_atomic_t, debugfs_atomic_t_get,
debugfs_atomic_t_set, "%lld\n");
-DEFINE_SIMPLE_ATTRIBUTE(fops_atomic_t_ro, debugfs_atomic_t_get, NULL, "%lld\n");
-DEFINE_SIMPLE_ATTRIBUTE(fops_atomic_t_wo, NULL, debugfs_atomic_t_set, "%lld\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_atomic_t_ro, debugfs_atomic_t_get, NULL,
+ "%lld\n");
+DEFINE_DEBUGFS_ATTRIBUTE(fops_atomic_t_wo, NULL, debugfs_atomic_t_set,
+ "%lld\n");

/**
* debugfs_create_atomic_t - create a debugfs file that is used to read and
@@ -490,11 +571,18 @@ ssize_t debugfs_read_file_bool(struct file *file, char __user *user_buf,
{
char buf[3];
bool *val = file->private_data;
+ int ret, srcu_idx;

+ ret = debugfs_file_use_data_start(file, &srcu_idx);
+ if (ret) {
+ debugfs_file_use_data_finish(srcu_idx);
+ return ret;
+ }
if (*val)
buf[0] = 'Y';
else
buf[0] = 'N';
+ debugfs_file_use_data_finish(srcu_idx);
buf[1] = '\n';
buf[2] = 0x00;
return simple_read_from_buffer(user_buf, count, ppos, buf, 2);
@@ -508,16 +596,26 @@ ssize_t debugfs_write_file_bool(struct file *file, const char __user *user_buf,
size_t buf_size;
bool bv;
bool *val = file->private_data;
+ ssize_t ret;
+ int srcu_idx;

buf_size = min(count, (sizeof(buf)-1));
if (copy_from_user(buf, user_buf, buf_size))
return -EFAULT;

buf[buf_size] = '\0';
- if (strtobool(buf, &bv) == 0)
- *val = bv;
+ if (strtobool(buf, &bv) == 0) {
+ ret = debugfs_file_use_data_start(file, &srcu_idx);
+ if (!ret) {
+ *val = bv;
+ ret = count;
+ }
+ debugfs_file_use_data_finish(srcu_idx);
+ } else {
+ return -EINVAL;
+ }

- return count;
+ return ret;
}
EXPORT_SYMBOL_GPL(debugfs_write_file_bool);

@@ -575,9 +673,16 @@ EXPORT_SYMBOL_GPL(debugfs_create_bool);
static ssize_t read_file_blob(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
+ ssize_t ret;
+ int srcu_idx;
struct debugfs_blob_wrapper *blob = file->private_data;
- return simple_read_from_buffer(user_buf, count, ppos, blob->data,
- blob->size);
+
+ ret = debugfs_file_use_data_start(file, &srcu_idx);
+ if (!ret)
+ ret = simple_read_from_buffer(user_buf, count, ppos, blob->data,
+ blob->size);
+ debugfs_file_use_data_finish(srcu_idx);
+ return ret;
}

static const struct file_operations fops_blob = {
@@ -646,6 +751,7 @@ static int u32_array_open(struct inode *inode, struct file *file)
struct array_data *data = inode->i_private;
int size, elements = data->elements;
char *buf;
+ int ret, srcu_idx;

/*
* Max size:
@@ -659,9 +765,17 @@ static int u32_array_open(struct inode *inode, struct file *file)
buf[size] = 0;

file->private_data = buf;
+ ret = debugfs_file_use_data_start(file, &srcu_idx);
+ if (ret) {
+ kfree(file->private_data);
+ goto out;
+ }
u32_format_array(buf, size, data->array, data->elements);
+ nonseekable_open(inode, file);

- return nonseekable_open(inode, file);
+out:
+ debugfs_file_use_data_finish(srcu_idx);
+ return ret;
}

static ssize_t u32_array_read(struct file *file, char __user *buf, size_t len,
@@ -764,15 +878,21 @@ EXPORT_SYMBOL_GPL(debugfs_print_regs32);

static int debugfs_show_regset32(struct seq_file *s, void *data)
{
- struct debugfs_regset32 *regset = s->private;
+ struct file *f = s->private;
+ struct debugfs_regset32 *regset = file_inode(f)->i_private;
+ int ret, srcu_idx;

- debugfs_print_regs32(s, regset->regs, regset->nregs, regset->base, "");
- return 0;
+ ret = debugfs_file_use_data_start(f, &srcu_idx);
+ if (!ret)
+ debugfs_print_regs32(s, regset->regs, regset->nregs,
+ regset->base, "");
+ debugfs_file_use_data_finish(srcu_idx);
+ return ret;
}

static int debugfs_open_regset32(struct inode *inode, struct file *file)
{
- return single_open(file, debugfs_show_regset32, inode->i_private);
+ return single_open(file, debugfs_show_regset32, file);
}

static const struct file_operations fops_regset32 = {
@@ -829,11 +949,24 @@ static int debugfs_devm_entry_open(struct inode *inode, struct file *f)
return single_open(f, entry->read, entry->dev);
}

+static ssize_t debugfs_devm_entry_read(struct file *file, char __user *buf,
+ size_t size, loff_t *ppos)
+{
+ ssize_t ret;
+ int srcu_idx;
+
+ ret = debugfs_file_use_data_start(file, &srcu_idx);
+ if (!ret)
+ ret = seq_read(file, buf, size, ppos);
+ debugfs_file_use_data_finish(srcu_idx);
+ return ret;
+}
+
static const struct file_operations debugfs_devm_entry_ops = {
.owner = THIS_MODULE,
.open = debugfs_devm_entry_open,
.release = single_release,
- .read = seq_read,
+ .read = debugfs_devm_entry_read,
.llseek = seq_lseek
};

diff --git a/fs/debugfs/internal.h b/fs/debugfs/internal.h
index 1f87f3c..51798ed 100644
--- a/fs/debugfs/internal.h
+++ b/fs/debugfs/internal.h
@@ -13,12 +13,9 @@
#define _DEBUGFS_INTERNAL_H_

struct file_operations;
-struct srcu_struct;

/* declared over in file.c */
extern const struct file_operations debugfs_noop_file_operations;
extern const struct file_operations debugfs_proxy_file_operations;

-extern struct srcu_struct debugfs_srcu;
-
#endif /* _DEBUGFS_INTERNAL_H_ */
diff --git a/include/linux/debugfs.h b/include/linux/debugfs.h
index 8b44093..8acd369 100644
--- a/include/linux/debugfs.h
+++ b/include/linux/debugfs.h
@@ -22,6 +22,7 @@

struct device;
struct file_operations;
+struct srcu_struct;

struct debugfs_blob_wrapper {
void *data;
@@ -43,6 +44,8 @@ extern struct dentry *arch_debugfs_dir;

#if defined(CONFIG_DEBUG_FS)

+extern struct srcu_struct debugfs_srcu;
+
struct dentry *debugfs_create_file(const char *name, umode_t mode,
struct dentry *parent, void *data,
const struct file_operations *fops);
@@ -121,6 +124,31 @@ ssize_t debugfs_read_file_bool(struct file *file, char __user *user_buf,
ssize_t debugfs_write_file_bool(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos);

+int debugfs_file_use_data_start(struct file *file, int *srcu_idx)
+ __acquires(&debugfs_srcu);
+
+void debugfs_file_use_data_finish(int srcu_idx) __releases(&debugfs_srcu);
+
+ssize_t debugfs_attr_read(struct file *file, char __user *buf,
+ size_t len, loff_t *ppos);
+ssize_t debugfs_attr_write(struct file *file, const char __user *buf,
+ size_t len, loff_t *ppos);
+
+#define DEFINE_DEBUGFS_ATTRIBUTE(__fops, __get, __set, __fmt) \
+static int __fops ## _open(struct inode *inode, struct file *file) \
+{ \
+ __simple_attr_check_format(__fmt, 0ull); \
+ return simple_attr_open(inode, file, __get, __set, __fmt); \
+} \
+static const struct file_operations __fops = { \
+ .owner = THIS_MODULE, \
+ .open = __fops ## _open, \
+ .release = simple_attr_release, \
+ .read = debugfs_attr_read, \
+ .write = debugfs_attr_write, \
+ .llseek = generic_file_llseek, \
+}
+
#else

#include <linux/err.h>
--
2.7.0