summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fs/kernfs/file.c63
-rw-r--r--include/linux/kernfs.h1
2 files changed, 33 insertions, 31 deletions
diff --git a/fs/kernfs/file.c b/fs/kernfs/file.c
index ddcb471b9cc9..8034706a7af8 100644
--- a/fs/kernfs/file.c
+++ b/fs/kernfs/file.c
@@ -253,55 +253,50 @@ static ssize_t kernfs_fop_write(struct file *file, const char __user *user_buf,
{
struct kernfs_open_file *of = kernfs_of(file);
const struct kernfs_ops *ops;
- char *buf = NULL;
- ssize_t len;
-
- /*
- * @of->mutex nests outside active ref and is just to ensure that
- * the ops aren't called concurrently for the same open file.
- */
- mutex_lock(&of->mutex);
- if (!kernfs_get_active(of->kn)) {
- mutex_unlock(&of->mutex);
- return -ENODEV;
- }
-
- ops = kernfs_ops(of->kn);
- if (!ops->write) {
- len = -EINVAL;
- goto out_unlock;
- }
+ size_t len;
+ char *buf;
- if (ops->atomic_write_len) {
+ if (of->atomic_write_len) {
len = count;
- if (len > ops->atomic_write_len) {
- len = -E2BIG;
- goto out_unlock;
- }
+ if (len > of->atomic_write_len)
+ return -E2BIG;
} else {
len = min_t(size_t, count, PAGE_SIZE);
}
buf = kmalloc(len + 1, GFP_KERNEL);
- if (!buf) {
- len = -ENOMEM;
- goto out_unlock;
- }
+ if (!buf)
+ return -ENOMEM;
if (copy_from_user(buf, user_buf, len)) {
len = -EFAULT;
- goto out_unlock;
+ goto out_free;
}
buf[len] = '\0'; /* guarantee string termination */
- len = ops->write(of, buf, len, *ppos);
-out_unlock:
+ /*
+ * @of->mutex nests outside active ref and is just to ensure that
+ * the ops aren't called concurrently for the same open file.
+ */
+ mutex_lock(&of->mutex);
+ if (!kernfs_get_active(of->kn)) {
+ mutex_unlock(&of->mutex);
+ len = -ENODEV;
+ goto out_free;
+ }
+
+ ops = kernfs_ops(of->kn);
+ if (ops->write)
+ len = ops->write(of, buf, len, *ppos);
+ else
+ len = -EINVAL;
+
kernfs_put_active(of->kn);
mutex_unlock(&of->mutex);
if (len > 0)
*ppos += len;
-
+out_free:
kfree(buf);
return len;
}
@@ -666,6 +661,12 @@ static int kernfs_fop_open(struct inode *inode, struct file *file)
of->file = file;
/*
+ * Write path needs to atomic_write_len outside active reference.
+ * Cache it in open_file. See kernfs_fop_write() for details.
+ */
+ of->atomic_write_len = ops->atomic_write_len;
+
+ /*
* Always instantiate seq_file even if read access doesn't use
* seq_file or is not requested. This unifies private data access
* and readable regular files are the vast majority anyway.
diff --git a/include/linux/kernfs.h b/include/linux/kernfs.h
index 09669d092748..b0122dc6f96a 100644
--- a/include/linux/kernfs.h
+++ b/include/linux/kernfs.h
@@ -158,6 +158,7 @@ struct kernfs_open_file {
int event;
struct list_head list;
+ size_t atomic_write_len;
bool mmapped;
const struct vm_operations_struct *vm_ops;
};