diff options
Diffstat (limited to 'fs/sysfs')
-rw-r--r-- | fs/sysfs/file.c | 114 |
1 files changed, 52 insertions, 62 deletions
diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index af6e9092a67..53cc096e6a1 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c @@ -162,92 +162,82 @@ out: } /** - * fill_write_buffer - copy buffer from userspace. - * @of: open file struct. - * @buf: data from user. - * @count: number of bytes in @userbuf. + * flush_write_buffer - push buffer to kobject + * @of: open file + * @buf: data buffer for file + * @count: number of bytes * - * Allocate @of->page if it hasn't been already, then copy the - * user-supplied buffer into it. + * Get the correct pointers for the kobject and the attribute we're dealing + * with, then call the store() method for it with @buf. */ -static int fill_write_buffer(struct sysfs_open_file *of, - const char __user *buf, size_t count) -{ - int error; - - if (!of->page) - of->page = (char *)get_zeroed_page(GFP_KERNEL); - if (!of->page) - return -ENOMEM; - - if (count >= PAGE_SIZE) - count = PAGE_SIZE - 1; - error = copy_from_user(of->page, buf, count); - - /* - * If buf is assumed to contain a string, terminate it by \0, so - * e.g. sscanf() can scan the string easily. - */ - of->page[count] = 0; - return error ? -EFAULT : count; -} - -/** - * flush_write_buffer - push buffer to kobject. - * @of: open file - * @count: number of bytes - * - * Get the correct pointers for the kobject and the attribute we're - * dealing with, then call the store() method for the attribute, - * passing the buffer that we acquired in fill_write_buffer(). - */ -static int flush_write_buffer(struct sysfs_open_file *of, size_t count) +static int flush_write_buffer(struct sysfs_open_file *of, char *buf, + size_t count) { struct kobject *kobj = of->sd->s_parent->s_dir.kobj; const struct sysfs_ops *ops; - int rc; + int rc = 0; - /* need @of->sd for attr and ops, its parent for kobj */ - if (!sysfs_get_active(of->sd)) + /* + * Need @of->sd for attr and ops, its parent for kobj. @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 (!sysfs_get_active(of->sd)) { + mutex_unlock(&of->mutex); return -ENODEV; + } ops = sysfs_file_ops(of->sd); - rc = ops->store(kobj, of->sd->s_attr.attr, of->page, count); + rc = ops->store(kobj, of->sd->s_attr.attr, buf, count); sysfs_put_active(of->sd); + mutex_unlock(&of->mutex); return rc; } /** - * sysfs_write_file - write an attribute. - * @file: file pointer - * @buf: data to write - * @count: number of bytes - * @ppos: starting offset + * sysfs_write_file - write an attribute + * @file: file pointer + * @user_buf: data to write + * @count: number of bytes + * @ppos: starting offset * - * Similar to sysfs_read_file(), though working in the opposite direction. - * We allocate and fill the data from the user in fill_write_buffer(), - * then push it to the kobject in flush_write_buffer(). - * There is no easy way for us to know if userspace is only doing a partial - * write, so we don't support them. We expect the entire buffer to come - * on the first write. - * Hint: if you're writing a value, first read the file, modify only the - * the value you're changing, then write entire buffer back. + * Copy data in from userland and pass it to the matching + * sysfs_ops->store() by invoking flush_write_buffer(). + * + * There is no easy way for us to know if userspace is only doing a partial + * write, so we don't support them. We expect the entire buffer to come on + * the first write. Hint: if you're writing a value, first read the file, + * modify only the the value you're changing, then write entire buffer + * back. */ -static ssize_t sysfs_write_file(struct file *file, const char __user *buf, +static ssize_t sysfs_write_file(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { struct sysfs_open_file *of = file->private_data; - ssize_t len; + ssize_t len = min_t(size_t, count, PAGE_SIZE - 1); + char *buf; - mutex_lock(&of->mutex); - len = fill_write_buffer(of, buf, count); - if (len > 0) - len = flush_write_buffer(of, len); + if (!len) + return 0; + + buf = kmalloc(len + 1, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (copy_from_user(buf, user_buf, len)) { + len = -EFAULT; + goto out_free; + } + buf[len] = '\0'; /* guarantee string termination */ + + len = flush_write_buffer(of, buf, len); if (len > 0) *ppos += len; - mutex_unlock(&of->mutex); +out_free: + kfree(buf); return len; } |