linux-btrfs.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/3] Fix transaction abort when rmdir'ing a subvol
@ 2019-12-18 22:20 Josef Bacik
  2019-12-18 22:20 ` [PATCH 1/3] btrfs: rework arguments for btrfs_unlink_subvol Josef Bacik
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Josef Bacik @ 2019-12-18 22:20 UTC (permalink / raw)
  To: linux-btrfs, kernel-team

btrfs/004 with my new fsstress was sometimes failing with a transaction abort
while trying to remove a root ref.  This turned out to be because of how we stub
out subvol links in snapshot'ed subvolumes.  The specific steps are detailed in
"btrfs: fix invalid removal of root ref", but they are relatively
straightforward.  A xfstest is forthcoming, I want to get an overnight run of
fsstress with these patches in place to make sure there are no other
rename+removal shenanigans left.  With these patches we pass my basic test and
no longer abort the transaction.  Thanks,

Josef


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 1/3] btrfs: rework arguments for btrfs_unlink_subvol
  2019-12-18 22:20 [PATCH 0/3] Fix transaction abort when rmdir'ing a subvol Josef Bacik
@ 2019-12-18 22:20 ` Josef Bacik
  2019-12-18 22:20 ` [PATCH 2/3] btrfs: fix invalid removal of root ref Josef Bacik
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Josef Bacik @ 2019-12-18 22:20 UTC (permalink / raw)
  To: linux-btrfs, kernel-team

btrfs_unlink_subvol takes the name of the dentry and the root objectid
based on what kind of inode this is, either a real subvolume link or a
empty one that we inherited as a snapshot.  We need to fix how we unlink
in the case for BTRFS_EMPTY_SUBVOL_DIR_OBJECTID in the future, so rework
btrfs_unlink_subvol to just take the dentry and handle getting the right
objectid given the type of inode this is.  There is no functional change
here, simply pushing the work into btrfs_unlink_subvol() proper.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
 fs/btrfs/inode.c | 44 ++++++++++++++++++++------------------------
 1 file changed, 20 insertions(+), 24 deletions(-)

diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index c1c6e6afa3bc..3cbdca2749bd 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -4234,17 +4234,29 @@ static int btrfs_unlink(struct inode *dir, struct dentry *dentry)
 }
 
 static int btrfs_unlink_subvol(struct btrfs_trans_handle *trans,
-			       struct inode *dir, u64 objectid,
-			       const char *name, int name_len)
+			       struct inode *dir, struct dentry *dentry)
 {
 	struct btrfs_root *root = BTRFS_I(dir)->root;
 	struct btrfs_path *path;
 	struct extent_buffer *leaf;
 	struct btrfs_dir_item *di;
+	const char *name = dentry->d_name.name;
+	struct btrfs_inode *inode = BTRFS_I(d_inode(dentry));
 	struct btrfs_key key;
+	int name_len = dentry->d_name.len;
 	u64 index;
 	int ret;
 	u64 dir_ino = btrfs_ino(BTRFS_I(dir));
+	u64 objectid;
+
+	if (btrfs_ino(inode) == BTRFS_FIRST_FREE_OBJECTID) {
+		objectid = inode->root->root_key.objectid;
+	} else if (btrfs_ino(inode) == BTRFS_EMPTY_SUBVOL_DIR_OBJECTID) {
+		objectid = inode->location.objectid;
+	} else {
+		WARN_ON(1);
+		return -EINVAL;
+	}
 
 	path = btrfs_alloc_path();
 	if (!path)
@@ -4483,8 +4495,7 @@ int btrfs_delete_subvolume(struct inode *dir, struct dentry *dentry)
 
 	btrfs_record_snapshot_destroy(trans, BTRFS_I(dir));
 
-	ret = btrfs_unlink_subvol(trans, dir, dest->root_key.objectid,
-				  dentry->d_name.name, dentry->d_name.len);
+	ret = btrfs_unlink_subvol(trans, dir, dentry);
 	if (ret) {
 		err = ret;
 		btrfs_abort_transaction(trans, ret);
@@ -4579,10 +4590,7 @@ static int btrfs_rmdir(struct inode *dir, struct dentry *dentry)
 		return PTR_ERR(trans);
 
 	if (unlikely(btrfs_ino(BTRFS_I(inode)) == BTRFS_EMPTY_SUBVOL_DIR_OBJECTID)) {
-		err = btrfs_unlink_subvol(trans, dir,
-					  BTRFS_I(inode)->location.objectid,
-					  dentry->d_name.name,
-					  dentry->d_name.len);
+		err = btrfs_unlink_subvol(trans, dir, dentry);
 		goto out;
 	}
 
@@ -9540,7 +9548,6 @@ static int btrfs_rename_exchange(struct inode *old_dir,
 	u64 new_ino = btrfs_ino(BTRFS_I(new_inode));
 	u64 old_idx = 0;
 	u64 new_idx = 0;
-	u64 root_objectid;
 	int ret;
 	bool root_log_pinned = false;
 	bool dest_log_pinned = false;
@@ -9646,10 +9653,7 @@ static int btrfs_rename_exchange(struct inode *old_dir,
 
 	/* src is a subvolume */
 	if (old_ino == BTRFS_FIRST_FREE_OBJECTID) {
-		root_objectid = BTRFS_I(old_inode)->root->root_key.objectid;
-		ret = btrfs_unlink_subvol(trans, old_dir, root_objectid,
-					  old_dentry->d_name.name,
-					  old_dentry->d_name.len);
+		ret = btrfs_unlink_subvol(trans, old_dir, old_dentry);
 	} else { /* src is an inode */
 		ret = __btrfs_unlink_inode(trans, root, BTRFS_I(old_dir),
 					   BTRFS_I(old_dentry->d_inode),
@@ -9665,10 +9669,7 @@ static int btrfs_rename_exchange(struct inode *old_dir,
 
 	/* dest is a subvolume */
 	if (new_ino == BTRFS_FIRST_FREE_OBJECTID) {
-		root_objectid = BTRFS_I(new_inode)->root->root_key.objectid;
-		ret = btrfs_unlink_subvol(trans, new_dir, root_objectid,
-					  new_dentry->d_name.name,
-					  new_dentry->d_name.len);
+		ret = btrfs_unlink_subvol(trans, new_dir, new_dentry);
 	} else { /* dest is an inode */
 		ret = __btrfs_unlink_inode(trans, dest, BTRFS_I(new_dir),
 					   BTRFS_I(new_dentry->d_inode),
@@ -9974,10 +9975,7 @@ static int btrfs_rename(struct inode *old_dir, struct dentry *old_dentry,
 				BTRFS_I(old_inode), 1);
 
 	if (unlikely(old_ino == BTRFS_FIRST_FREE_OBJECTID)) {
-		root_objectid = BTRFS_I(old_inode)->root->root_key.objectid;
-		ret = btrfs_unlink_subvol(trans, old_dir, root_objectid,
-					old_dentry->d_name.name,
-					old_dentry->d_name.len);
+		ret = btrfs_unlink_subvol(trans, old_dir, old_dentry);
 	} else {
 		ret = __btrfs_unlink_inode(trans, root, BTRFS_I(old_dir),
 					BTRFS_I(d_inode(old_dentry)),
@@ -9997,9 +9995,7 @@ static int btrfs_rename(struct inode *old_dir, struct dentry *old_dentry,
 		if (unlikely(btrfs_ino(BTRFS_I(new_inode)) ==
 			     BTRFS_EMPTY_SUBVOL_DIR_OBJECTID)) {
 			root_objectid = BTRFS_I(new_inode)->location.objectid;
-			ret = btrfs_unlink_subvol(trans, new_dir, root_objectid,
-						new_dentry->d_name.name,
-						new_dentry->d_name.len);
+			ret = btrfs_unlink_subvol(trans, new_dir, new_dentry);
 			BUG_ON(new_inode->i_nlink == 0);
 		} else {
 			ret = btrfs_unlink_inode(trans, dest, BTRFS_I(new_dir),
-- 
2.23.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 2/3] btrfs: fix invalid removal of root ref
  2019-12-18 22:20 [PATCH 0/3] Fix transaction abort when rmdir'ing a subvol Josef Bacik
  2019-12-18 22:20 ` [PATCH 1/3] btrfs: rework arguments for btrfs_unlink_subvol Josef Bacik
@ 2019-12-18 22:20 ` Josef Bacik
  2019-12-18 22:20 ` [PATCH 3/3] btrfs: do not delete mismatched root ref's Josef Bacik
  2020-01-03 17:08 ` [PATCH 0/3] Fix transaction abort when rmdir'ing a subvol David Sterba
  3 siblings, 0 replies; 5+ messages in thread
From: Josef Bacik @ 2019-12-18 22:20 UTC (permalink / raw)
  To: linux-btrfs, kernel-team

If we have the following sequence of events

btrfs sub create /a
btrfs sub create /a/b
btrfs sub snap /a /c
mkdir /c/foo
mv /a/b /mnt/test/c/foo
rm -rf /mnt/test/*

We will end up with a transaction abort.

The reason for this is because we create a root ref for b pointing to a.
When we create a snapshot of c we still have b in our tree, but because
the root ref points to a and not c we will make it appear to be empty.
The problem happens when we move b into c.  This removes the root ref
for b pointing to a and adds a ref of b pointing to c.  When we rmdir c
we'll see that we have a ref to our root and remove the root ref,
despite not actually matching our reference name.

Now btrfs_del_root_ref() allowing this to work is a bug as well, however
we know that this inode does not actually point to a root ref in the
first place, so we shouldn't be calling btrfs_del_root_ref() in the
first place and instead simply look up our dir index for this item and
do the rest of the removal.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
 fs/btrfs/inode.c | 27 +++++++++++++++++++--------
 1 file changed, 19 insertions(+), 8 deletions(-)

diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 3cbdca2749bd..db67e1984c91 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -4279,13 +4279,16 @@ static int btrfs_unlink_subvol(struct btrfs_trans_handle *trans,
 	}
 	btrfs_release_path(path);
 
-	ret = btrfs_del_root_ref(trans, objectid, root->root_key.objectid,
-				 dir_ino, &index, name, name_len);
-	if (ret < 0) {
-		if (ret != -ENOENT) {
-			btrfs_abort_transaction(trans, ret);
-			goto out;
-		}
+	/*
+	 * This is a placeholder inode for a subvolume we didn't have a
+	 * reference to at the time of the snapshot creation.  In the meantime
+	 * we could have renamed the real subvol link into our snapshot, so
+	 * depending on btrfs_del_root_ref to return -ENOENT here is incorret.
+	 * Instead simply lookup the dir_index_item for this entry so we can
+	 * remove it.  Otherwise we know we have a ref to the root and we can
+	 * call btrfs_del_root_ref, and it _shouldn't_ fail.
+	 */
+	if (btrfs_ino(inode) == BTRFS_EMPTY_SUBVOL_DIR_OBJECTID) {
 		di = btrfs_search_dir_index_item(root, path, dir_ino,
 						 name, name_len);
 		if (IS_ERR_OR_NULL(di)) {
@@ -4300,8 +4303,16 @@ static int btrfs_unlink_subvol(struct btrfs_trans_handle *trans,
 		leaf = path->nodes[0];
 		btrfs_item_key_to_cpu(leaf, &key, path->slots[0]);
 		index = key.offset;
+		btrfs_release_path(path);
+	} else {
+		ret = btrfs_del_root_ref(trans, objectid,
+					 root->root_key.objectid, dir_ino,
+					 &index, name, name_len);
+		if (ret) {
+			btrfs_abort_transaction(trans, ret);
+			goto out;
+		}
 	}
-	btrfs_release_path(path);
 
 	ret = btrfs_delete_delayed_dir_index(trans, BTRFS_I(dir), index);
 	if (ret) {
-- 
2.23.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 3/3] btrfs: do not delete mismatched root ref's
  2019-12-18 22:20 [PATCH 0/3] Fix transaction abort when rmdir'ing a subvol Josef Bacik
  2019-12-18 22:20 ` [PATCH 1/3] btrfs: rework arguments for btrfs_unlink_subvol Josef Bacik
  2019-12-18 22:20 ` [PATCH 2/3] btrfs: fix invalid removal of root ref Josef Bacik
@ 2019-12-18 22:20 ` Josef Bacik
  2020-01-03 17:08 ` [PATCH 0/3] Fix transaction abort when rmdir'ing a subvol David Sterba
  3 siblings, 0 replies; 5+ messages in thread
From: Josef Bacik @ 2019-12-18 22:20 UTC (permalink / raw)
  To: linux-btrfs, kernel-team

btrfs_del_root_ref() will simply WARN_ON() if the ref doesn't match in
any way, and then continue to delete the reference.  This shouldn't
happen, we have these values because there's more to the reference than
the original root and the sub root.  If any of these checks fail, return
-ENOENT.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
 fs/btrfs/root-tree.c | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/fs/btrfs/root-tree.c b/fs/btrfs/root-tree.c
index 0a455f116666..f2a59ec6c1ce 100644
--- a/fs/btrfs/root-tree.c
+++ b/fs/btrfs/root-tree.c
@@ -346,11 +346,13 @@ int btrfs_del_root_ref(struct btrfs_trans_handle *trans, u64 root_id,
 		leaf = path->nodes[0];
 		ref = btrfs_item_ptr(leaf, path->slots[0],
 				     struct btrfs_root_ref);
-
-		WARN_ON(btrfs_root_ref_dirid(leaf, ref) != dirid);
-		WARN_ON(btrfs_root_ref_name_len(leaf, ref) != name_len);
 		ptr = (unsigned long)(ref + 1);
-		WARN_ON(memcmp_extent_buffer(leaf, name, ptr, name_len));
+		if ((btrfs_root_ref_dirid(leaf, ref) != dirid) ||
+		    (btrfs_root_ref_name_len(leaf, ref) != name_len) ||
+		    memcmp_extent_buffer(leaf, name, ptr, name_len)) {
+			err = -ENOENT;
+			goto out;
+		}
 		*sequence = btrfs_root_ref_sequence(leaf, ref);
 
 		ret = btrfs_del_item(trans, tree_root, path);
-- 
2.23.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH 0/3] Fix transaction abort when rmdir'ing a subvol
  2019-12-18 22:20 [PATCH 0/3] Fix transaction abort when rmdir'ing a subvol Josef Bacik
                   ` (2 preceding siblings ...)
  2019-12-18 22:20 ` [PATCH 3/3] btrfs: do not delete mismatched root ref's Josef Bacik
@ 2020-01-03 17:08 ` David Sterba
  3 siblings, 0 replies; 5+ messages in thread
From: David Sterba @ 2020-01-03 17:08 UTC (permalink / raw)
  To: Josef Bacik; +Cc: linux-btrfs, kernel-team

On Wed, Dec 18, 2019 at 05:20:26PM -0500, Josef Bacik wrote:
> btrfs/004 with my new fsstress was sometimes failing with a transaction abort
> while trying to remove a root ref.  This turned out to be because of how we stub
> out subvol links in snapshot'ed subvolumes.  The specific steps are detailed in
> "btrfs: fix invalid removal of root ref", but they are relatively
> straightforward.  A xfstest is forthcoming, I want to get an overnight run of
> fsstress with these patches in place to make sure there are no other
> rename+removal shenanigans left.  With these patches we pass my basic test and
> no longer abort the transaction.  Thanks,

Reviewed-by: David Sterba <dsterba@suse.com>

As 2 and 3 are the real fixes, independent of 1, I'll queue them for
next week's pull request.

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2020-01-03 17:08 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-12-18 22:20 [PATCH 0/3] Fix transaction abort when rmdir'ing a subvol Josef Bacik
2019-12-18 22:20 ` [PATCH 1/3] btrfs: rework arguments for btrfs_unlink_subvol Josef Bacik
2019-12-18 22:20 ` [PATCH 2/3] btrfs: fix invalid removal of root ref Josef Bacik
2019-12-18 22:20 ` [PATCH 3/3] btrfs: do not delete mismatched root ref's Josef Bacik
2020-01-03 17:08 ` [PATCH 0/3] Fix transaction abort when rmdir'ing a subvol David Sterba

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).