aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFilipe Manana <fdmanana@suse.com>2016-04-06 12:11:56 -0400
committerFilipe Manana <fdmanana@suse.com>2016-05-12 20:59:11 -0400
commit657ed1aa4898c8304500e0d13f240d5a67e8be5f (patch)
treeb24f088d140a46bb908624b25a75e309207ac1f6
parent44549e8f5eea4e0a41b487b63e616cb089922b99 (diff)
Btrfs: fix for incorrect directory entries after fsync log replay
If we move a directory to a new parent and later log that parent and don't explicitly log the old parent, when we replay the log we can end up with entries for the moved directory in both the old and new parent directories. Besides being ilegal to have directories with multiple hard links in linux, it also resulted in the leaving the inode item with a link count of 1. A similar issue also happens if we move a regular file - after the log tree is replayed the file has a link in both the old and new parent directories, when it should be only at the new directory. Sample reproducer: $ mkfs.btrfs -f /dev/sdc $ mount /dev/sdc /mnt $ mkdir /mnt/x $ mkdir /mnt/y $ touch /mnt/x/foo $ mkdir /mnt/y/z $ sync $ ln /mnt/x/foo /mnt/x/bar $ mv /mnt/y/z /mnt/x/z < power fail > $ mount /dev/sdc /mnt $ ls -1Ri /mnt /mnt: 257 x 258 y /mnt/x: 259 bar 259 foo 260 z /mnt/x/z: /mnt/y: 260 z /mnt/y/z: $ umount /dev/sdc $ btrfs check /dev/sdc Checking filesystem on /dev/sdc UUID: a67e2c4a-a4b4-4fdc-b015-9d9af1e344be checking extents checking free space cache checking fs roots root 5 inode 260 errors 2000, link count wrong unresolved ref dir 257 index 4 namelen 1 name z filetype 2 errors 0 unresolved ref dir 258 index 2 namelen 1 name z filetype 2 errors 0 (...) Attempting to remove the directory becomes impossible: $ mount /dev/sdc /mnt $ rmdir /mnt/y/z $ ls -lh /mnt/y ls: cannot access /mnt/y/z: No such file or directory total 0 d????????? ? ? ? ? ? z $ rmdir /mnt/x/z rmdir: failed to remove ā€˜/mnt/x/z’: Stale file handle $ ls -lh /mnt/x ls: cannot access /mnt/x/z: Stale file handle total 0 -rw-r--r-- 2 root root 0 Apr 6 18:06 bar -rw-r--r-- 2 root root 0 Apr 6 18:06 foo d????????? ? ? ? ? ? z So make sure that on rename we set the last_unlink_trans value for our inode, even if it's a directory, to the value of the current transaction's ID and that if the new parent directory is logged that we fallback to a transaction commit. A test case for fstests is being submitted as well. Signed-off-by: Filipe Manana <fdmanana@suse.com>
-rw-r--r--fs/btrfs/tree-log.c13
1 files changed, 8 insertions, 5 deletions
diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c
index 517d0ccb351e..4709932c62fb 100644
--- a/fs/btrfs/tree-log.c
+++ b/fs/btrfs/tree-log.c
@@ -5278,11 +5278,16 @@ static int btrfs_log_all_parents(struct btrfs_trans_handle *trans,
5278 if (IS_ERR(dir_inode)) 5278 if (IS_ERR(dir_inode))
5279 continue; 5279 continue;
5280 5280
5281 if (ctx)
5282 ctx->log_new_dentries = false;
5281 ret = btrfs_log_inode(trans, root, dir_inode, 5283 ret = btrfs_log_inode(trans, root, dir_inode,
5282 LOG_INODE_ALL, 0, LLONG_MAX, ctx); 5284 LOG_INODE_ALL, 0, LLONG_MAX, ctx);
5283 if (!ret && 5285 if (!ret &&
5284 btrfs_must_commit_transaction(trans, dir_inode)) 5286 btrfs_must_commit_transaction(trans, dir_inode))
5285 ret = 1; 5287 ret = 1;
5288 if (!ret && ctx && ctx->log_new_dentries)
5289 ret = log_new_dir_dentries(trans, root,
5290 dir_inode, ctx);
5286 iput(dir_inode); 5291 iput(dir_inode);
5287 if (ret) 5292 if (ret)
5288 goto out; 5293 goto out;
@@ -5652,11 +5657,9 @@ void btrfs_record_unlink_dir(struct btrfs_trans_handle *trans,
5652 * into the file. When the file is logged we check it and 5657 * into the file. When the file is logged we check it and
5653 * don't log the parents if the file is fully on disk. 5658 * don't log the parents if the file is fully on disk.
5654 */ 5659 */
5655 if (S_ISREG(inode->i_mode)) { 5660 mutex_lock(&BTRFS_I(inode)->log_mutex);
5656 mutex_lock(&BTRFS_I(inode)->log_mutex); 5661 BTRFS_I(inode)->last_unlink_trans = trans->transid;
5657 BTRFS_I(inode)->last_unlink_trans = trans->transid; 5662 mutex_unlock(&BTRFS_I(inode)->log_mutex);
5658 mutex_unlock(&BTRFS_I(inode)->log_mutex);
5659 }
5660 5663
5661 /* 5664 /*
5662 * if this directory was already logged any new 5665 * if this directory was already logged any new