Home Home > GIT Browse
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFilipe Manana <fdmanana@suse.com>2018-10-15 17:51:23 +0100
committerFilipe Manana <fdmanana@suse.com>2018-10-15 17:51:23 +0100
commit9fad86c061245d75d4f163826aa0fd2e0a9506ea (patch)
tree2cd0621b4d36512a3dbe1c7f20636887240f74ac
parent944429a5cae08d82cedd5e6df2e445054316839a (diff)
Btrfs: fix mount failure after fsync due to hard link recreation
(bsc#1103543).
-rw-r--r--patches.suse/btrfs-fix-mount-failure-after-fsync-due-to-hard-link.patch143
-rw-r--r--series.conf1
2 files changed, 144 insertions, 0 deletions
diff --git a/patches.suse/btrfs-fix-mount-failure-after-fsync-due-to-hard-link.patch b/patches.suse/btrfs-fix-mount-failure-after-fsync-due-to-hard-link.patch
new file mode 100644
index 0000000000..23923766e7
--- /dev/null
+++ b/patches.suse/btrfs-fix-mount-failure-after-fsync-due-to-hard-link.patch
@@ -0,0 +1,143 @@
+From: Filipe Manana <fdmanana@suse.com>
+Date: Fri, 20 Jul 2018 10:59:06 +0100
+Patch-mainline: 4.19-rc1
+Git-commit: 0d836392cadd5535f4184d46d901a82eb276ed62
+References: bsc#1103543
+Subject: [PATCH] Btrfs: fix mount failure after fsync due to hard link
+ recreation
+
+If we end up with logging an inode reference item which has the same name
+but different index from the one we have persisted, we end up failing when
+replaying the log with an errno value of -EEXIST. The error comes from
+btrfs_add_link(), which is called from add_inode_ref(), when we are
+replaying an inode reference item.
+
+Example scenario where this happens:
+
+ $ mkfs.btrfs -f /dev/sdb
+ $ mount /dev/sdb /mnt
+
+ $ touch /mnt/foo
+ $ ln /mnt/foo /mnt/bar
+
+ $ sync
+
+ # Rename the first hard link (foo) to a new name and rename the second
+ # hard link (bar) to the old name of the first hard link (foo).
+ $ mv /mnt/foo /mnt/qwerty
+ $ mv /mnt/bar /mnt/foo
+
+ # Create a new file, in the same parent directory, with the old name of
+ # the second hard link (bar) and fsync this new file.
+ # We do this instead of calling fsync on foo/qwerty because if we did
+ # that the fsync resulted in a full transaction commit, not triggering
+ # the problem.
+ $ touch /mnt/bar
+ $ xfs_io -c "fsync" /mnt/bar
+
+ <power fail>
+
+ $ mount /dev/sdb /mnt
+ mount: mount /dev/sdb on /mnt failed: File exists
+
+So fix this by checking if a conflicting inode reference exists (same
+name, same parent but different index), removing it (and the associated
+dir index entries from the parent inode) if it exists, before attempting
+to add the new reference.
+
+A test case for fstests follows soon.
+
+CC: stable@vger.kernel.org # 4.4+
+Signed-off-by: Filipe Manana <fdmanana@suse.com>
+Signed-off-by: David Sterba <dsterba@suse.com>
+---
+ fs/btrfs/tree-log.c | 66 +++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 66 insertions(+)
+
+diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c
+index 686d566a607c..0dabcb8bab71 100644
+--- a/fs/btrfs/tree-log.c
++++ b/fs/btrfs/tree-log.c
+@@ -1312,6 +1312,46 @@ static int unlink_old_inode_refs(struct btrfs_trans_handle *trans,
+ return ret;
+ }
+
++static int btrfs_inode_ref_exists(struct inode *inode, struct inode *dir,
++ const u8 ref_type, const char *name,
++ const int namelen)
++{
++ struct btrfs_key key;
++ struct btrfs_path *path;
++ const u64 parent_id = btrfs_ino(BTRFS_I(dir));
++ int ret;
++
++ path = btrfs_alloc_path();
++ if (!path)
++ return -ENOMEM;
++
++ key.objectid = btrfs_ino(BTRFS_I(inode));
++ key.type = ref_type;
++ if (key.type == BTRFS_INODE_REF_KEY)
++ key.offset = parent_id;
++ else
++ key.offset = btrfs_extref_hash(parent_id, name, namelen);
++
++ ret = btrfs_search_slot(NULL, BTRFS_I(inode)->root, &key, path, 0, 0);
++ if (ret < 0)
++ goto out;
++ if (ret > 0) {
++ ret = 0;
++ goto out;
++ }
++ if (key.type == BTRFS_INODE_EXTREF_KEY)
++ ret = btrfs_find_name_in_ext_backref(path->nodes[0],
++ path->slots[0], parent_id,
++ name, namelen, NULL);
++ else
++ ret = btrfs_find_name_in_backref(path->nodes[0], path->slots[0],
++ name, namelen, NULL);
++
++out:
++ btrfs_free_path(path);
++ return ret;
++}
++
+ /*
+ * replay one inode back reference item found in the log tree.
+ * eb, slot and key refer to the buffer and key found in the log tree.
+@@ -1421,6 +1461,32 @@ static noinline int add_inode_ref(struct btrfs_trans_handle *trans,
+ }
+ }
+
++ /*
++ * If a reference item already exists for this inode
++ * with the same parent and name, but different index,
++ * drop it and the corresponding directory index entries
++ * from the parent before adding the new reference item
++ * and dir index entries, otherwise we would fail with
++ * -EEXIST returned from btrfs_add_link() below.
++ */
++ ret = btrfs_inode_ref_exists(inode, dir, key->type,
++ name, namelen);
++ if (ret > 0) {
++ ret = btrfs_unlink_inode(trans, root,
++ BTRFS_I(dir),
++ BTRFS_I(inode),
++ name, namelen);
++ /*
++ * If we dropped the link count to 0, bump it so
++ * that later the iput() on the inode will not
++ * free it. We will fixup the link count later.
++ */
++ if (!ret && inode->i_nlink == 0)
++ inc_nlink(inode);
++ }
++ if (ret < 0)
++ goto out;
++
+ /* insert our name */
+ ret = btrfs_add_link(trans, BTRFS_I(dir),
+ BTRFS_I(inode),
+--
+2.19.0
+
diff --git a/series.conf b/series.conf
index cacf16d607..c4e8da21cb 100644
--- a/series.conf
+++ b/series.conf
@@ -17077,6 +17077,7 @@
patches.arch/s390-fix-br_r1_trampoline-for-machines-without-exrl.patch
patches.fixes/binfmt_elf-Respect-error-return-from-regset-active.patch
patches.suse/0001-btrfs-Don-t-remove-block-group-still-has-pinned-down.patch
+ patches.suse/btrfs-fix-mount-failure-after-fsync-due-to-hard-link.patch
patches.suse/btrfs-fix-send-failure-when-root-has-deleted-files-s.patch
patches.fixes/ext4-sysfs-print-ext4_super_block-fields-as-little-e.patch
patches.suse/xfs-fix-a-null-pointer-dereference-in-xfs_bmap_exten.patch