summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
Diffstat (limited to 'fs')
-rw-r--r--fs/btrfs/ioctl.c57
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;