diff options
| author | Chris Mason <chris.mason@oracle.com> | 2010-03-16 15:40:10 -0400 | 
|---|---|---|
| committer | Chris Mason <chris.mason@oracle.com> | 2010-03-16 15:40:10 -0400 | 
| commit | 7fde62bffb576d384ea49a3aed3403d5609ee5bc (patch) | |
| tree | 0fc0be615e657ed385835b56741c3a62c2c76fff /fs/btrfs | |
| parent | ce769a2904bf5a9110ef534a7702397e38e2b3e9 (diff) | |
Btrfs: buffer results in the space_info ioctl
The space_info ioctl was using copy_to_user inside rcu_read_lock.  This
commit changes things to copy into a buffer first and then dump the
result down to userland.
Signed-off-by: Chris Mason <chris.mason@oracle.com>
Diffstat (limited to 'fs/btrfs')
| -rw-r--r-- | fs/btrfs/ioctl.c | 57 | 
1 files changed, 46 insertions, 11 deletions
| diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 38a68863390a..4329610b141b 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -1848,39 +1848,74 @@ long btrfs_ioctl_space_info(struct btrfs_root *root, void __user *arg)  	struct btrfs_ioctl_space_args space_args;  	struct btrfs_ioctl_space_info space;  	struct btrfs_ioctl_space_info *dest; +	struct btrfs_ioctl_space_info *dest_orig; +	struct btrfs_ioctl_space_info *user_dest;  	struct btrfs_space_info *info; +	int alloc_size;  	int ret = 0; +	int slot_count = 0;  	if (copy_from_user(&space_args,  			   (struct btrfs_ioctl_space_args __user *)arg,  			   sizeof(space_args)))  		return -EFAULT; +	/* first we count slots */ +	rcu_read_lock(); +	list_for_each_entry_rcu(info, &root->fs_info->space_info, list) +		slot_count++; +	rcu_read_unlock(); + +	/* space_slots == 0 means they are asking for a count */ +	if (space_args.space_slots == 0) { +		space_args.total_spaces = slot_count; +		goto out; +	} +	alloc_size = sizeof(*dest) * slot_count; +	/* we generally have at most 6 or so space infos, one for each raid +	 * level.  So, a whole page should be more than enough for everyone +	 */ +	if (alloc_size > PAGE_CACHE_SIZE) +		return -ENOMEM; +  	space_args.total_spaces = 0; -	dest = (struct btrfs_ioctl_space_info *) -		(arg + sizeof(struct btrfs_ioctl_space_args)); +	dest = kmalloc(alloc_size, GFP_NOFS); +	if (!dest) +		return -ENOMEM; +	dest_orig = dest; +	/* now we have a buffer to copy into */  	rcu_read_lock();  	list_for_each_entry_rcu(info, &root->fs_info->space_info, list) { -		if (!space_args.space_slots) { -			space_args.total_spaces++; -			continue; -		} +		/* make sure we don't copy more than we allocated +		 * in our buffer +		 */ +		if (slot_count == 0) +			break; +		slot_count--; + +		/* make sure userland has enough room in their buffer */  		if (space_args.total_spaces >= space_args.space_slots)  			break; +  		space.flags = info->flags;  		space.total_bytes = info->total_bytes;  		space.used_bytes = info->bytes_used; -		if (copy_to_user(dest, &space, sizeof(space))) { -			ret = -EFAULT; -			break; -		} +		memcpy(dest, &space, sizeof(space));  		dest++;  		space_args.total_spaces++;  	}  	rcu_read_unlock(); -	if (copy_to_user(arg, &space_args, sizeof(space_args))) +	user_dest = (struct btrfs_ioctl_space_info *) +		(arg + sizeof(struct btrfs_ioctl_space_args)); + +	if (copy_to_user(user_dest, dest_orig, alloc_size)) +		ret = -EFAULT; + +	kfree(dest_orig); +out: +	if (ret == 0 && copy_to_user(arg, &space_args, sizeof(space_args)))  		ret = -EFAULT;  	return ret; | 
