aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFilipe Manana <fdmanana@suse.com>2018-07-20 05:59:06 -0400
committerDavid Sterba <dsterba@suse.com>2018-08-06 07:12:59 -0400
commit0d836392cadd5535f4184d46d901a82eb276ed62 (patch)
treefcb877b9f410107371ed2e1e1e169ee5c8deb933
parent4559b0a71749c442d34f7cfb9e72c9e58db83948 (diff)
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>
-rw-r--r--fs/btrfs/tree-log.c66
1 files changed, 66 insertions, 0 deletions
diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c
index 10f6a4223897..033aeebbe9de 100644
--- a/fs/btrfs/tree-log.c
+++ b/fs/btrfs/tree-log.c
@@ -1290,6 +1290,46 @@ again:
1290 return ret; 1290 return ret;
1291} 1291}
1292 1292
1293static int btrfs_inode_ref_exists(struct inode *inode, struct inode *dir,
1294 const u8 ref_type, const char *name,
1295 const int namelen)
1296{
1297 struct btrfs_key key;
1298 struct btrfs_path *path;
1299 const u64 parent_id = btrfs_ino(BTRFS_I(dir));
1300 int ret;
1301
1302 path = btrfs_alloc_path();
1303 if (!path)
1304 return -ENOMEM;
1305
1306 key.objectid = btrfs_ino(BTRFS_I(inode));
1307 key.type = ref_type;
1308 if (key.type == BTRFS_INODE_REF_KEY)
1309 key.offset = parent_id;
1310 else
1311 key.offset = btrfs_extref_hash(parent_id, name, namelen);
1312
1313 ret = btrfs_search_slot(NULL, BTRFS_I(inode)->root, &key, path, 0, 0);
1314 if (ret < 0)
1315 goto out;
1316 if (ret > 0) {
1317 ret = 0;
1318 goto out;
1319 }
1320 if (key.type == BTRFS_INODE_EXTREF_KEY)
1321 ret = btrfs_find_name_in_ext_backref(path->nodes[0],
1322 path->slots[0], parent_id,
1323 name, namelen, NULL);
1324 else
1325 ret = btrfs_find_name_in_backref(path->nodes[0], path->slots[0],
1326 name, namelen, NULL);
1327
1328out:
1329 btrfs_free_path(path);
1330 return ret;
1331}
1332
1293/* 1333/*
1294 * replay one inode back reference item found in the log tree. 1334 * replay one inode back reference item found in the log tree.
1295 * eb, slot and key refer to the buffer and key found in the log tree. 1335 * eb, slot and key refer to the buffer and key found in the log tree.
@@ -1399,6 +1439,32 @@ static noinline int add_inode_ref(struct btrfs_trans_handle *trans,
1399 } 1439 }
1400 } 1440 }
1401 1441
1442 /*
1443 * If a reference item already exists for this inode
1444 * with the same parent and name, but different index,
1445 * drop it and the corresponding directory index entries
1446 * from the parent before adding the new reference item
1447 * and dir index entries, otherwise we would fail with
1448 * -EEXIST returned from btrfs_add_link() below.
1449 */
1450 ret = btrfs_inode_ref_exists(inode, dir, key->type,
1451 name, namelen);
1452 if (ret > 0) {
1453 ret = btrfs_unlink_inode(trans, root,
1454 BTRFS_I(dir),
1455 BTRFS_I(inode),
1456 name, namelen);
1457 /*
1458 * If we dropped the link count to 0, bump it so
1459 * that later the iput() on the inode will not
1460 * free it. We will fixup the link count later.
1461 */
1462 if (!ret && inode->i_nlink == 0)
1463 inc_nlink(inode);
1464 }
1465 if (ret < 0)
1466 goto out;
1467
1402 /* insert our name */ 1468 /* insert our name */
1403 ret = btrfs_add_link(trans, BTRFS_I(dir), 1469 ret = btrfs_add_link(trans, BTRFS_I(dir),
1404 BTRFS_I(inode), 1470 BTRFS_I(inode),