summaryrefslogtreecommitdiffstats
path: root/fs/btrfs/tree-log.c
diff options
context:
space:
mode:
authorFilipe Manana <fdmanana@suse.com>2019-05-16 10:48:55 -0400
committerDavid Sterba <dsterba@suse.com>2019-05-28 12:56:50 -0400
commit60d9f50308e5df19bc18c2fefab0eba4a843900a (patch)
tree5637f1146e8aa994b2941224aed2f2addc400f9b /fs/btrfs/tree-log.c
parent57949d033a09c57d77be218b5bec07af6878ab32 (diff)
Btrfs: fix fsync not persisting changed attributes of a directory
While logging an inode we follow its ancestors and for each one we mark it as logged in the current transaction, even if we have not logged it. As a consequence if we change an attribute of an ancestor, such as the UID or GID for example, and then explicitly fsync it, we end up not logging the inode at all despite returning success to user space, which results in the attribute being lost if a power failure happens after the fsync. Sample reproducer: $ mkfs.btrfs -f /dev/sdb $ mount /dev/sdb /mnt $ mkdir /mnt/dir $ chown 6007:6007 /mnt/dir $ sync $ chown 9003:9003 /mnt/dir $ touch /mnt/dir/file $ xfs_io -c fsync /mnt/dir/file # fsync our directory after fsync'ing the new file, should persist the # new values for the uid and gid. $ xfs_io -c fsync /mnt/dir <power failure> $ mount /dev/sdb /mnt $ stat -c %u:%g /mnt/dir 6007:6007 --> should be 9003:9003, the uid and gid were not persisted, despite the explicit fsync on the directory prior to the power failure Fix this by not updating the logged_trans field of ancestor inodes when logging an inode, since we have not logged them. Let only future calls to btrfs_log_inode() to mark inodes as logged. This could be triggered by my recent fsync fuzz tester for fstests, for which an fstests patch exists titled "fstests: generic, fsync fuzz tester with fsstress". Fixes: 12fcfd22fe5b ("Btrfs: tree logging unlink/rename fixes") CC: stable@vger.kernel.org # 4.4+ Signed-off-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
Diffstat (limited to 'fs/btrfs/tree-log.c')
-rw-r--r--fs/btrfs/tree-log.c12
1 files changed, 0 insertions, 12 deletions
diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c
index 6c47f6ed3e94..de729acee738 100644
--- a/fs/btrfs/tree-log.c
+++ b/fs/btrfs/tree-log.c
@@ -5478,7 +5478,6 @@ static noinline int check_parent_dirs_for_sync(struct btrfs_trans_handle *trans,
5478{ 5478{
5479 int ret = 0; 5479 int ret = 0;
5480 struct dentry *old_parent = NULL; 5480 struct dentry *old_parent = NULL;
5481 struct btrfs_inode *orig_inode = inode;
5482 5481
5483 /* 5482 /*
5484 * for regular files, if its inode is already on disk, we don't 5483 * for regular files, if its inode is already on disk, we don't
@@ -5498,16 +5497,6 @@ static noinline int check_parent_dirs_for_sync(struct btrfs_trans_handle *trans,
5498 } 5497 }
5499 5498
5500 while (1) { 5499 while (1) {
5501 /*
5502 * If we are logging a directory then we start with our inode,
5503 * not our parent's inode, so we need to skip setting the
5504 * logged_trans so that further down in the log code we don't
5505 * think this inode has already been logged.
5506 */
5507 if (inode != orig_inode)
5508 inode->logged_trans = trans->transid;
5509 smp_mb();
5510
5511 if (btrfs_must_commit_transaction(trans, inode)) { 5500 if (btrfs_must_commit_transaction(trans, inode)) {
5512 ret = 1; 5501 ret = 1;
5513 break; 5502 break;
@@ -6384,7 +6373,6 @@ void btrfs_record_unlink_dir(struct btrfs_trans_handle *trans,
6384 * if this directory was already logged any new 6373 * if this directory was already logged any new
6385 * names for this file/dir will get recorded 6374 * names for this file/dir will get recorded
6386 */ 6375 */
6387 smp_mb();
6388 if (dir->logged_trans == trans->transid) 6376 if (dir->logged_trans == trans->transid)
6389 return; 6377 return;
6390 6378