Skip to content

Commit 0d83639

Browse files
fdmananakdave
authored andcommitted
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: [email protected] # 4.4+ Signed-off-by: Filipe Manana <[email protected]> Signed-off-by: David Sterba <[email protected]>
1 parent 4559b0a commit 0d83639

File tree

1 file changed

+66
-0
lines changed

1 file changed

+66
-0
lines changed

fs/btrfs/tree-log.c

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1290,6 +1290,46 @@ static int unlink_old_inode_refs(struct btrfs_trans_handle *trans,
12901290
return ret;
12911291
}
12921292

1293+
static 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+
1328+
out:
1329+
btrfs_free_path(path);
1330+
return ret;
1331+
}
1332+
12931333
/*
12941334
* replay one inode back reference item found in the log tree.
12951335
* 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,
13991439
}
14001440
}
14011441

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+
14021468
/* insert our name */
14031469
ret = btrfs_add_link(trans, BTRFS_I(dir),
14041470
BTRFS_I(inode),

0 commit comments

Comments
 (0)