Skip to content

Commit a3baaf0

Browse files
fdmananakdave
authored andcommitted
Btrfs: fix fsync after succession of renames and unlink/rmdir
After a succession of renames operations of different files and unlinking one of them, if we fsync one of the renamed files we can end up with a log that will either fail to replay at mount time or result in a filesystem that is in an inconsistent state. One example scenario: $ mkfs.btrfs -f /dev/sdb $ mount /dev/sdb /mnt $ mkdir /mnt/testdir $ touch /mnt/testdir/fname1 $ touch /mnt/testdir/fname2 $ sync $ mv /mnt/testdir/fname1 /mnt/testdir/fname3 $ rm -f /mnt/testdir/fname2 $ ln /mnt/testdir/fname3 /mnt/testdir/fname2 $ touch /mnt/testdir/fname1 $ xfs_io -c "fsync" /mnt/testdir/fname1 <power failure> $ mount /dev/sdb /mnt $ umount /mnt $ btrfs check /dev/sdb [1/7] checking root items [2/7] checking extents [3/7] checking free space cache [4/7] checking fs roots root 5 inode 259 errors 2, no orphan item ERROR: errors found in fs roots Opening filesystem to check... Checking filesystem on /dev/sdc UUID: 20e4abb8-5a19-4492-8bb4-6084125c2d0d found 393216 bytes used, error(s) found total csum bytes: 0 total tree bytes: 131072 total fs tree bytes: 32768 total extent tree bytes: 16384 btree space waste bytes: 122986 file data blocks allocated: 262144 referenced 262144 On a kernel without the first patch in this series, titled "[PATCH] Btrfs: fix fsync after succession of renames of different files", we get instead an error when mounting the filesystem due to failure of replaying the log: $ mount /dev/sdb /mnt mount: mount /dev/sdb on /mnt failed: File exists Fix this by logging the parent directory of an inode whenever we find an inode that no longer exists (was unlinked in the current transaction), during the procedure which finds inodes that have old names that collide with new names of other inodes. A test case for fstests follows soon. Reviewed-by: Nikolay Borisov <nborisov@suse.com> Signed-off-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
1 parent 6b5fc43 commit a3baaf0

File tree

1 file changed

+37
-12
lines changed

1 file changed

+37
-12
lines changed

fs/btrfs/tree-log.c

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#define LOG_INODE_ALL 0
2828
#define LOG_INODE_EXISTS 1
2929
#define LOG_OTHER_INODE 2
30+
#define LOG_OTHER_INODE_ALL 3
3031

3132
/*
3233
* directory trouble cases
@@ -4777,7 +4778,7 @@ static int btrfs_check_ref_name_override(struct extent_buffer *eb,
47774778
const int slot,
47784779
const struct btrfs_key *key,
47794780
struct btrfs_inode *inode,
4780-
u64 *other_ino)
4781+
u64 *other_ino, u64 *other_parent)
47814782
{
47824783
int ret;
47834784
struct btrfs_path *search_path;
@@ -4843,6 +4844,7 @@ static int btrfs_check_ref_name_override(struct extent_buffer *eb,
48434844
if (di_key.objectid != key->objectid) {
48444845
ret = 1;
48454846
*other_ino = di_key.objectid;
4847+
*other_parent = parent;
48464848
} else {
48474849
ret = 0;
48484850
}
@@ -4867,14 +4869,15 @@ static int btrfs_check_ref_name_override(struct extent_buffer *eb,
48674869

48684870
struct btrfs_ino_list {
48694871
u64 ino;
4872+
u64 parent;
48704873
struct list_head list;
48714874
};
48724875

48734876
static int log_conflicting_inodes(struct btrfs_trans_handle *trans,
48744877
struct btrfs_root *root,
48754878
struct btrfs_path *path,
48764879
struct btrfs_log_ctx *ctx,
4877-
u64 ino)
4880+
u64 ino, u64 parent)
48784881
{
48794882
struct btrfs_ino_list *ino_elem;
48804883
LIST_HEAD(inode_list);
@@ -4884,6 +4887,7 @@ static int log_conflicting_inodes(struct btrfs_trans_handle *trans,
48844887
if (!ino_elem)
48854888
return -ENOMEM;
48864889
ino_elem->ino = ino;
4890+
ino_elem->parent = parent;
48874891
list_add_tail(&ino_elem->list, &inode_list);
48884892

48894893
while (!list_empty(&inode_list)) {
@@ -4894,6 +4898,7 @@ static int log_conflicting_inodes(struct btrfs_trans_handle *trans,
48944898
ino_elem = list_first_entry(&inode_list, struct btrfs_ino_list,
48954899
list);
48964900
ino = ino_elem->ino;
4901+
parent = ino_elem->parent;
48974902
list_del(&ino_elem->list);
48984903
kfree(ino_elem);
48994904
if (ret)
@@ -4907,13 +4912,25 @@ static int log_conflicting_inodes(struct btrfs_trans_handle *trans,
49074912
inode = btrfs_iget(fs_info->sb, &key, root, NULL);
49084913
/*
49094914
* If the other inode that had a conflicting dir entry was
4910-
* deleted in the current transaction, we don't need to do more
4911-
* work nor fallback to a transaction commit.
4915+
* deleted in the current transaction, we need to log its parent
4916+
* directory.
49124917
*/
49134918
if (IS_ERR(inode)) {
49144919
ret = PTR_ERR(inode);
4915-
if (ret == -ENOENT)
4916-
ret = 0;
4920+
if (ret == -ENOENT) {
4921+
key.objectid = parent;
4922+
inode = btrfs_iget(fs_info->sb, &key, root,
4923+
NULL);
4924+
if (IS_ERR(inode)) {
4925+
ret = PTR_ERR(inode);
4926+
} else {
4927+
ret = btrfs_log_inode(trans, root,
4928+
BTRFS_I(inode),
4929+
LOG_OTHER_INODE_ALL,
4930+
0, LLONG_MAX, ctx);
4931+
iput(inode);
4932+
}
4933+
}
49174934
continue;
49184935
}
49194936
/*
@@ -4943,6 +4960,7 @@ static int log_conflicting_inodes(struct btrfs_trans_handle *trans,
49434960
struct extent_buffer *leaf = path->nodes[0];
49444961
int slot = path->slots[0];
49454962
u64 other_ino = 0;
4963+
u64 other_parent = 0;
49464964

49474965
if (slot >= btrfs_header_nritems(leaf)) {
49484966
ret = btrfs_next_leaf(root, path);
@@ -4964,7 +4982,8 @@ static int log_conflicting_inodes(struct btrfs_trans_handle *trans,
49644982
}
49654983

49664984
ret = btrfs_check_ref_name_override(leaf, slot, &key,
4967-
BTRFS_I(inode), &other_ino);
4985+
BTRFS_I(inode), &other_ino,
4986+
&other_parent);
49684987
if (ret < 0)
49694988
break;
49704989
if (ret > 0) {
@@ -4974,6 +4993,7 @@ static int log_conflicting_inodes(struct btrfs_trans_handle *trans,
49744993
break;
49754994
}
49764995
ino_elem->ino = other_ino;
4996+
ino_elem->parent = other_parent;
49774997
list_add_tail(&ino_elem->list, &inode_list);
49784998
ret = 0;
49794999
}
@@ -5024,7 +5044,7 @@ static int btrfs_log_inode(struct btrfs_trans_handle *trans,
50245044
u64 logged_isize = 0;
50255045
bool need_log_inode_item = true;
50265046
bool xattrs_logged = false;
5027-
bool recursive_logging = (inode_only == LOG_OTHER_INODE);
5047+
bool recursive_logging = false;
50285048

50295049
path = btrfs_alloc_path();
50305050
if (!path)
@@ -5070,8 +5090,12 @@ static int btrfs_log_inode(struct btrfs_trans_handle *trans,
50705090
return ret;
50715091
}
50725092

5073-
if (inode_only == LOG_OTHER_INODE) {
5074-
inode_only = LOG_INODE_EXISTS;
5093+
if (inode_only == LOG_OTHER_INODE || inode_only == LOG_OTHER_INODE_ALL) {
5094+
recursive_logging = true;
5095+
if (inode_only == LOG_OTHER_INODE)
5096+
inode_only = LOG_INODE_EXISTS;
5097+
else
5098+
inode_only = LOG_INODE_ALL;
50755099
mutex_lock_nested(&inode->log_mutex, SINGLE_DEPTH_NESTING);
50765100
} else {
50775101
mutex_lock(&inode->log_mutex);
@@ -5169,10 +5193,11 @@ static int btrfs_log_inode(struct btrfs_trans_handle *trans,
51695193
inode->generation == trans->transid &&
51705194
!recursive_logging) {
51715195
u64 other_ino = 0;
5196+
u64 other_parent = 0;
51725197

51735198
ret = btrfs_check_ref_name_override(path->nodes[0],
51745199
path->slots[0], &min_key, inode,
5175-
&other_ino);
5200+
&other_ino, &other_parent);
51765201
if (ret < 0) {
51775202
err = ret;
51785203
goto out_unlock;
@@ -5195,7 +5220,7 @@ static int btrfs_log_inode(struct btrfs_trans_handle *trans,
51955220
ins_nr = 0;
51965221

51975222
err = log_conflicting_inodes(trans, root, path,
5198-
ctx, other_ino);
5223+
ctx, other_ino, other_parent);
51995224
if (err)
52005225
goto out_unlock;
52015226
btrfs_release_path(path);

0 commit comments

Comments
 (0)