diff mbox series

[4/5] btrfs: qgroup: preallocate memory before adding a relation

Message ID 950a5c2128b1ea79be6c7c438649b71201db00dc.1718816796.git.dsterba@suse.com (mailing list archive)
State New, archived
Headers show
Series Error handling fixes | expand

Commit Message

David Sterba June 19, 2024, 5:09 p.m. UTC
There's a transaction joined in the qgroup relation add/remove ioctl and
any error will lead to abort/error. We could lift the allocation from
btrfs_add_qgroup_relation() and move it outside of the transaction
context. The relation deletion does not need that.

The ownership of the structure is moved to the add relation handler.

Signed-off-by: David Sterba <dsterba@suse.com>
---
 fs/btrfs/ioctl.c  | 20 ++++++++++++++++----
 fs/btrfs/qgroup.c | 25 ++++++++-----------------
 fs/btrfs/qgroup.h | 11 ++++++++++-
 3 files changed, 34 insertions(+), 22 deletions(-)

Comments

Qu Wenruo June 20, 2024, 1:59 a.m. UTC | #1
在 2024/6/20 02:39, David Sterba 写道:
> There's a transaction joined in the qgroup relation add/remove ioctl and
> any error will lead to abort/error. We could lift the allocation from
> btrfs_add_qgroup_relation() and move it outside of the transaction
> context. The relation deletion does not need that.
>
> The ownership of the structure is moved to the add relation handler.
>
> Signed-off-by: David Sterba <dsterba@suse.com>
> ---
>   fs/btrfs/ioctl.c  | 20 ++++++++++++++++----
>   fs/btrfs/qgroup.c | 25 ++++++++-----------------
>   fs/btrfs/qgroup.h | 11 ++++++++++-
>   3 files changed, 34 insertions(+), 22 deletions(-)
>
> diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c
> index d28ebabe3720..31c4aea8b93a 100644
> --- a/fs/btrfs/ioctl.c
> +++ b/fs/btrfs/ioctl.c
> @@ -3829,6 +3829,7 @@ static long btrfs_ioctl_qgroup_assign(struct file *file, void __user *arg)
>   	struct btrfs_fs_info *fs_info = inode_to_fs_info(inode);
>   	struct btrfs_root *root = BTRFS_I(inode)->root;
>   	struct btrfs_ioctl_qgroup_assign_args *sa;
> +	struct btrfs_qgroup_list *prealloc = NULL;
>   	struct btrfs_trans_handle *trans;
>   	int ret;
>   	int err;
> @@ -3849,17 +3850,27 @@ static long btrfs_ioctl_qgroup_assign(struct file *file, void __user *arg)
>   		goto drop_write;
>   	}
>
> +	prealloc = kzalloc(sizeof(*prealloc), GFP_KERNEL);
> +	if (!prealloc) {
> +		ret = -ENOMEM;
> +		goto drop_write;
> +	}
> +
>   	trans = btrfs_join_transaction(root);
>   	if (IS_ERR(trans)) {
>   		ret = PTR_ERR(trans);
>   		goto out;
>   	}
>
> -	if (sa->assign) {
> -		ret = btrfs_add_qgroup_relation(trans, sa->src, sa->dst);
> -	} else {
> +	/*
> +	 * Prealloc ownership is moved to the relation handler, there it's used
> +	 * or freed on error.
> +	 */
> +	if (sa->assign)
> +		ret = btrfs_add_qgroup_relation(trans, sa->src, sa->dst, prealloc);
> +	else
>   		ret = btrfs_del_qgroup_relation(trans, sa->src, sa->dst);
> -	}
> +	prealloc = NULL;

This leads to memory leak, as if we're doing relation deletion, we just
do the preallocation, then reset prealloc to NULL, no way to release the
preallocated memory.

Thanks,
Qu

>
>   	/* update qgroup status and info */
>   	mutex_lock(&fs_info->qgroup_ioctl_lock);
> @@ -3873,6 +3884,7 @@ static long btrfs_ioctl_qgroup_assign(struct file *file, void __user *arg)
>   		ret = err;
>
>   out:
> +	kfree(prealloc);
>   	kfree(sa);
>   drop_write:
>   	mnt_drop_write_file(file);
> diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c
> index 3edbe5bb19c6..4ae01c87e418 100644
> --- a/fs/btrfs/qgroup.c
> +++ b/fs/btrfs/qgroup.c
> @@ -155,16 +155,6 @@ static inline u64 btrfs_qgroup_get_new_refcnt(const struct btrfs_qgroup *qg, u64
>   	return qg->new_refcnt - seq;
>   }
>
> -/*
> - * glue structure to represent the relations between qgroups.
> - */
> -struct btrfs_qgroup_list {
> -	struct list_head next_group;
> -	struct list_head next_member;
> -	struct btrfs_qgroup *group;
> -	struct btrfs_qgroup *member;
> -};
> -
>   static int
>   qgroup_rescan_init(struct btrfs_fs_info *fs_info, u64 progress_objectid,
>   		   int init_flags);
> @@ -1568,15 +1558,21 @@ static int quick_update_accounting(struct btrfs_fs_info *fs_info,
>   	return ret;
>   }
>
> -int btrfs_add_qgroup_relation(struct btrfs_trans_handle *trans, u64 src, u64 dst)
> +/*
> + * Add relation between @src and @dst qgroup. The @prealloc is allocated by the
> + * callers and transferred here (either used or freed on error).
> + */
> +int btrfs_add_qgroup_relation(struct btrfs_trans_handle *trans, u64 src, u64 dst,
> +			      struct btrfs_qgroup_list *prealloc)
>   {
>   	struct btrfs_fs_info *fs_info = trans->fs_info;
>   	struct btrfs_qgroup *parent;
>   	struct btrfs_qgroup *member;
>   	struct btrfs_qgroup_list *list;
> -	struct btrfs_qgroup_list *prealloc = NULL;
>   	int ret = 0;
>
> +	ASSERT(prealloc);
> +
>   	/* Check the level of src and dst first */
>   	if (btrfs_qgroup_level(src) >= btrfs_qgroup_level(dst))
>   		return -EINVAL;
> @@ -1601,11 +1597,6 @@ int btrfs_add_qgroup_relation(struct btrfs_trans_handle *trans, u64 src, u64 dst
>   		}
>   	}
>
> -	prealloc = kzalloc(sizeof(*list), GFP_NOFS);
> -	if (!prealloc) {
> -		ret = -ENOMEM;
> -		goto out;
> -	}
>   	ret = add_qgroup_relation_item(trans, src, dst);
>   	if (ret)
>   		goto out;
> diff --git a/fs/btrfs/qgroup.h b/fs/btrfs/qgroup.h
> index 95881dcab684..deb479d176a9 100644
> --- a/fs/btrfs/qgroup.h
> +++ b/fs/btrfs/qgroup.h
> @@ -278,6 +278,14 @@ struct btrfs_qgroup {
>   	struct kobject kobj;
>   };
>
> +/* Glue structure to represent the relations between qgroups. */
> +struct btrfs_qgroup_list {
> +	struct list_head next_group;
> +	struct list_head next_member;
> +	struct btrfs_qgroup *group;
> +	struct btrfs_qgroup *member;
> +};
> +
>   struct btrfs_squota_delta {
>   	/* The fstree root this delta counts against. */
>   	u64 root;
> @@ -321,7 +329,8 @@ int btrfs_qgroup_rescan(struct btrfs_fs_info *fs_info);
>   void btrfs_qgroup_rescan_resume(struct btrfs_fs_info *fs_info);
>   int btrfs_qgroup_wait_for_completion(struct btrfs_fs_info *fs_info,
>   				     bool interruptible);
> -int btrfs_add_qgroup_relation(struct btrfs_trans_handle *trans, u64 src, u64 dst);
> +int btrfs_add_qgroup_relation(struct btrfs_trans_handle *trans, u64 src, u64 dst,
> +			      struct btrfs_qgroup_list *prealloc);
>   int btrfs_del_qgroup_relation(struct btrfs_trans_handle *trans, u64 src,
>   			      u64 dst);
>   int btrfs_create_qgroup(struct btrfs_trans_handle *trans, u64 qgroupid);
David Sterba June 20, 2024, 5:40 p.m. UTC | #2
On Thu, Jun 20, 2024 at 11:29:22AM +0930, Qu Wenruo wrote:
> 
> 
> 在 2024/6/20 02:39, David Sterba 写道:
> > There's a transaction joined in the qgroup relation add/remove ioctl and
> > any error will lead to abort/error. We could lift the allocation from
> > btrfs_add_qgroup_relation() and move it outside of the transaction
> > context. The relation deletion does not need that.
> >
> > The ownership of the structure is moved to the add relation handler.
> >
> > Signed-off-by: David Sterba <dsterba@suse.com>
> > ---
> >   fs/btrfs/ioctl.c  | 20 ++++++++++++++++----
> >   fs/btrfs/qgroup.c | 25 ++++++++-----------------
> >   fs/btrfs/qgroup.h | 11 ++++++++++-
> >   3 files changed, 34 insertions(+), 22 deletions(-)
> >
> > diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c
> > index d28ebabe3720..31c4aea8b93a 100644
> > --- a/fs/btrfs/ioctl.c
> > +++ b/fs/btrfs/ioctl.c
> > @@ -3829,6 +3829,7 @@ static long btrfs_ioctl_qgroup_assign(struct file *file, void __user *arg)
> >   	struct btrfs_fs_info *fs_info = inode_to_fs_info(inode);
> >   	struct btrfs_root *root = BTRFS_I(inode)->root;
> >   	struct btrfs_ioctl_qgroup_assign_args *sa;
> > +	struct btrfs_qgroup_list *prealloc = NULL;
> >   	struct btrfs_trans_handle *trans;
> >   	int ret;
> >   	int err;
> > @@ -3849,17 +3850,27 @@ static long btrfs_ioctl_qgroup_assign(struct file *file, void __user *arg)
> >   		goto drop_write;
> >   	}
> >
> > +	prealloc = kzalloc(sizeof(*prealloc), GFP_KERNEL);
> > +	if (!prealloc) {
> > +		ret = -ENOMEM;
> > +		goto drop_write;
> > +	}
> > +
> >   	trans = btrfs_join_transaction(root);
> >   	if (IS_ERR(trans)) {
> >   		ret = PTR_ERR(trans);
> >   		goto out;
> >   	}
> >
> > -	if (sa->assign) {
> > -		ret = btrfs_add_qgroup_relation(trans, sa->src, sa->dst);
> > -	} else {
> > +	/*
> > +	 * Prealloc ownership is moved to the relation handler, there it's used
> > +	 * or freed on error.
> > +	 */
> > +	if (sa->assign)
> > +		ret = btrfs_add_qgroup_relation(trans, sa->src, sa->dst, prealloc);
> > +	else
> >   		ret = btrfs_del_qgroup_relation(trans, sa->src, sa->dst);
> > -	}
> > +	prealloc = NULL;
> 
> This leads to memory leak, as if we're doing relation deletion, we just
> do the preallocation, then reset prealloc to NULL, no way to release the
> preallocated memory.

Right, I should maybe also add the preallocation only when it's adding
the relation so it does not fail for deletion where it's not needed.
diff mbox series

Patch

diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c
index d28ebabe3720..31c4aea8b93a 100644
--- a/fs/btrfs/ioctl.c
+++ b/fs/btrfs/ioctl.c
@@ -3829,6 +3829,7 @@  static long btrfs_ioctl_qgroup_assign(struct file *file, void __user *arg)
 	struct btrfs_fs_info *fs_info = inode_to_fs_info(inode);
 	struct btrfs_root *root = BTRFS_I(inode)->root;
 	struct btrfs_ioctl_qgroup_assign_args *sa;
+	struct btrfs_qgroup_list *prealloc = NULL;
 	struct btrfs_trans_handle *trans;
 	int ret;
 	int err;
@@ -3849,17 +3850,27 @@  static long btrfs_ioctl_qgroup_assign(struct file *file, void __user *arg)
 		goto drop_write;
 	}
 
+	prealloc = kzalloc(sizeof(*prealloc), GFP_KERNEL);
+	if (!prealloc) {
+		ret = -ENOMEM;
+		goto drop_write;
+	}
+
 	trans = btrfs_join_transaction(root);
 	if (IS_ERR(trans)) {
 		ret = PTR_ERR(trans);
 		goto out;
 	}
 
-	if (sa->assign) {
-		ret = btrfs_add_qgroup_relation(trans, sa->src, sa->dst);
-	} else {
+	/*
+	 * Prealloc ownership is moved to the relation handler, there it's used
+	 * or freed on error.
+	 */
+	if (sa->assign)
+		ret = btrfs_add_qgroup_relation(trans, sa->src, sa->dst, prealloc);
+	else
 		ret = btrfs_del_qgroup_relation(trans, sa->src, sa->dst);
-	}
+	prealloc = NULL;
 
 	/* update qgroup status and info */
 	mutex_lock(&fs_info->qgroup_ioctl_lock);
@@ -3873,6 +3884,7 @@  static long btrfs_ioctl_qgroup_assign(struct file *file, void __user *arg)
 		ret = err;
 
 out:
+	kfree(prealloc);
 	kfree(sa);
 drop_write:
 	mnt_drop_write_file(file);
diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c
index 3edbe5bb19c6..4ae01c87e418 100644
--- a/fs/btrfs/qgroup.c
+++ b/fs/btrfs/qgroup.c
@@ -155,16 +155,6 @@  static inline u64 btrfs_qgroup_get_new_refcnt(const struct btrfs_qgroup *qg, u64
 	return qg->new_refcnt - seq;
 }
 
-/*
- * glue structure to represent the relations between qgroups.
- */
-struct btrfs_qgroup_list {
-	struct list_head next_group;
-	struct list_head next_member;
-	struct btrfs_qgroup *group;
-	struct btrfs_qgroup *member;
-};
-
 static int
 qgroup_rescan_init(struct btrfs_fs_info *fs_info, u64 progress_objectid,
 		   int init_flags);
@@ -1568,15 +1558,21 @@  static int quick_update_accounting(struct btrfs_fs_info *fs_info,
 	return ret;
 }
 
-int btrfs_add_qgroup_relation(struct btrfs_trans_handle *trans, u64 src, u64 dst)
+/*
+ * Add relation between @src and @dst qgroup. The @prealloc is allocated by the
+ * callers and transferred here (either used or freed on error).
+ */
+int btrfs_add_qgroup_relation(struct btrfs_trans_handle *trans, u64 src, u64 dst,
+			      struct btrfs_qgroup_list *prealloc)
 {
 	struct btrfs_fs_info *fs_info = trans->fs_info;
 	struct btrfs_qgroup *parent;
 	struct btrfs_qgroup *member;
 	struct btrfs_qgroup_list *list;
-	struct btrfs_qgroup_list *prealloc = NULL;
 	int ret = 0;
 
+	ASSERT(prealloc);
+
 	/* Check the level of src and dst first */
 	if (btrfs_qgroup_level(src) >= btrfs_qgroup_level(dst))
 		return -EINVAL;
@@ -1601,11 +1597,6 @@  int btrfs_add_qgroup_relation(struct btrfs_trans_handle *trans, u64 src, u64 dst
 		}
 	}
 
-	prealloc = kzalloc(sizeof(*list), GFP_NOFS);
-	if (!prealloc) {
-		ret = -ENOMEM;
-		goto out;
-	}
 	ret = add_qgroup_relation_item(trans, src, dst);
 	if (ret)
 		goto out;
diff --git a/fs/btrfs/qgroup.h b/fs/btrfs/qgroup.h
index 95881dcab684..deb479d176a9 100644
--- a/fs/btrfs/qgroup.h
+++ b/fs/btrfs/qgroup.h
@@ -278,6 +278,14 @@  struct btrfs_qgroup {
 	struct kobject kobj;
 };
 
+/* Glue structure to represent the relations between qgroups. */
+struct btrfs_qgroup_list {
+	struct list_head next_group;
+	struct list_head next_member;
+	struct btrfs_qgroup *group;
+	struct btrfs_qgroup *member;
+};
+
 struct btrfs_squota_delta {
 	/* The fstree root this delta counts against. */
 	u64 root;
@@ -321,7 +329,8 @@  int btrfs_qgroup_rescan(struct btrfs_fs_info *fs_info);
 void btrfs_qgroup_rescan_resume(struct btrfs_fs_info *fs_info);
 int btrfs_qgroup_wait_for_completion(struct btrfs_fs_info *fs_info,
 				     bool interruptible);
-int btrfs_add_qgroup_relation(struct btrfs_trans_handle *trans, u64 src, u64 dst);
+int btrfs_add_qgroup_relation(struct btrfs_trans_handle *trans, u64 src, u64 dst,
+			      struct btrfs_qgroup_list *prealloc);
 int btrfs_del_qgroup_relation(struct btrfs_trans_handle *trans, u64 src,
 			      u64 dst);
 int btrfs_create_qgroup(struct btrfs_trans_handle *trans, u64 qgroupid);