Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8ef445f0 authored by Tejun Heo's avatar Tejun Heo Committed by Greg Kroah-Hartman
Browse files

sysfs: use transient write buffer



There isn't much to be gained by keeping around kernel buffer while a
file is open especially as the read path planned to be converted to
use seq_file and won't use the buffer.  This patch makes
sysfs_write_file() use per-write transient buffer instead of
sysfs_open_file->page.

This simplifies the write path, enables removing sysfs_open_file->page
once read path is updated and will help merging bin file write path
which already requires the use of a transient buffer due to a locking
order issue.

As the function comments of flush_write_buffer() and
sysfs_write_buffer() are being updated anyway, reformat them so that
they're more conventional.

v2: Use min_t() instead of min() in sysfs_write_file() to avoid build
    warning on arm.  Reported by build test robot.

Signed-off-by: default avatarTejun Heo <tj@kernel.org>
Cc: kbuild test robot <fengguang.wu@intel.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent bcafe4ee
Loading
Loading
Loading
Loading
+52 −62
Original line number Original line Diff line number Diff line
@@ -162,92 +162,82 @@ sysfs_read_file(struct file *file, char __user *buf, size_t count, loff_t *ppos)
}
}


/**
/**
 *	fill_write_buffer - copy buffer from userspace.
 * flush_write_buffer - push buffer to kobject
 *	@of:		open file struct.
 *	@buf:		data from user.
 *	@count:		number of bytes in @userbuf.
 *
 *	Allocate @of->page if it hasn't been already, then copy the
 *	user-supplied buffer into it.
 */
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
 * @of: open file
 * @buf: data buffer for file
 * @count: number of bytes
 * @count: number of bytes
 *
 *
 *	Get the correct pointers for the kobject and the attribute we're
 * Get the correct pointers for the kobject and the attribute we're dealing
 *	dealing with, then call the store() method for the attribute,
 * with, then call the store() method for it with @buf.
 *	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;
	struct kobject *kobj = of->sd->s_parent->s_dir.kobj;
	const struct sysfs_ops *ops;
	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;
		return -ENODEV;
	}


	ops = sysfs_file_ops(of->sd);
	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);
	sysfs_put_active(of->sd);
	mutex_unlock(&of->mutex);


	return rc;
	return rc;
}
}


/**
/**
 *	sysfs_write_file - write an attribute.
 * sysfs_write_file - write an attribute
 * @file: file pointer
 * @file: file pointer
 *	@buf:	data to write
 * @user_buf: data to write
 * @count: number of bytes
 * @count: number of bytes
 * @ppos: starting offset
 * @ppos: starting offset
 *
 *
 *	Similar to sysfs_read_file(), though working in the opposite direction.
 * Copy data in from userland and pass it to the matching
 *	We allocate and fill the data from the user in fill_write_buffer(),
 * sysfs_ops->store() by invoking flush_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
 * 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
 * write, so we don't support them. We expect the entire buffer to come on
 *	on the first write.
 * the first write.  Hint: if you're writing a value, first read the file,
 *	Hint: if you're writing a value, first read the file, modify only the
 * modify only the the value you're changing, then write entire buffer
 *	the value you're changing, then write entire buffer back.
 * 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)
				size_t count, loff_t *ppos)
{
{
	struct sysfs_open_file *of = file->private_data;
	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);
	if (!len)
	len = fill_write_buffer(of, buf, count);
		return 0;
	if (len > 0)

		len = flush_write_buffer(of, len);
	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)
	if (len > 0)
		*ppos += len;
		*ppos += len;
	mutex_unlock(&of->mutex);
out_free:
	kfree(buf);
	return len;
	return len;
}
}