| From d0e64a981fd841cb0f28fcd6afcac55e6f1e6994 Mon Sep 17 00:00:00 2001 |
| From: Filipe Manana <fdmanana@suse.com> |
| Date: Thu, 21 Apr 2022 10:56:39 +0100 |
| Subject: btrfs: always log symlinks in full mode |
| |
| From: Filipe Manana <fdmanana@suse.com> |
| |
| commit d0e64a981fd841cb0f28fcd6afcac55e6f1e6994 upstream. |
| |
| On Linux, empty symlinks are invalid, and attempting to create one with |
| the system call symlink(2) results in an -ENOENT error and this is |
| explicitly documented in the man page. |
| |
| If we rename a symlink that was created in the current transaction and its |
| parent directory was logged before, we actually end up logging the symlink |
| without logging its content, which is stored in an inline extent. That |
| means that after a power failure we can end up with an empty symlink, |
| having no content and an i_size of 0 bytes. |
| |
| It can be easily reproduced like this: |
| |
| $ mkfs.btrfs -f /dev/sdc |
| $ mount /dev/sdc /mnt |
| |
| $ mkdir /mnt/testdir |
| $ sync |
| |
| # Create a file inside the directory and fsync the directory. |
| $ touch /mnt/testdir/foo |
| $ xfs_io -c "fsync" /mnt/testdir |
| |
| # Create a symlink inside the directory and then rename the symlink. |
| $ ln -s /mnt/testdir/foo /mnt/testdir/bar |
| $ mv /mnt/testdir/bar /mnt/testdir/baz |
| |
| # Now fsync again the directory, this persist the log tree. |
| $ xfs_io -c "fsync" /mnt/testdir |
| |
| <power failure> |
| |
| $ mount /dev/sdc /mnt |
| $ stat -c %s /mnt/testdir/baz |
| 0 |
| $ readlink /mnt/testdir/baz |
| $ |
| |
| Fix this by always logging symlinks in full mode (LOG_INODE_ALL), so that |
| their content is also logged. |
| |
| A test case for fstests will follow. |
| |
| CC: stable@vger.kernel.org # 4.9+ |
| Signed-off-by: Filipe Manana <fdmanana@suse.com> |
| Signed-off-by: David Sterba <dsterba@suse.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| fs/btrfs/tree-log.c | 14 +++++++++++++- |
| 1 file changed, 13 insertions(+), 1 deletion(-) |
| |
| --- a/fs/btrfs/tree-log.c |
| +++ b/fs/btrfs/tree-log.c |
| @@ -5656,6 +5656,18 @@ static int btrfs_log_inode(struct btrfs_ |
| } |
| |
| /* |
| + * For symlinks, we must always log their content, which is stored in an |
| + * inline extent, otherwise we could end up with an empty symlink after |
| + * log replay, which is invalid on linux (symlink(2) returns -ENOENT if |
| + * one attempts to create an empty symlink). |
| + * We don't need to worry about flushing delalloc, because when we create |
| + * the inline extent when the symlink is created (we never have delalloc |
| + * for symlinks). |
| + */ |
| + if (S_ISLNK(inode->vfs_inode.i_mode)) |
| + inode_only = LOG_INODE_ALL; |
| + |
| + /* |
| * This is for cases where logging a directory could result in losing a |
| * a file after replaying the log. For example, if we move a file from a |
| * directory A to a directory B, then fsync directory A, we have no way |
| @@ -6015,7 +6027,7 @@ process_leaf: |
| } |
| |
| ctx->log_new_dentries = false; |
| - if (type == BTRFS_FT_DIR || type == BTRFS_FT_SYMLINK) |
| + if (type == BTRFS_FT_DIR) |
| log_mode = LOG_INODE_ALL; |
| ret = btrfs_log_inode(trans, BTRFS_I(di_inode), |
| log_mode, ctx); |