xfs: log local to remote symlink conversions correctly on v5 supers
authorBrian Foster <bfoster@redhat.com>
Mon, 12 Oct 2015 04:40:24 +0000 (15:40 +1100)
committerDave Chinner <david@fromorbit.com>
Mon, 12 Oct 2015 04:40:24 +0000 (15:40 +1100)
A local format symlink inode is converted to extent format when an
extended attribute is set on an inode as part of the attribute fork
creation. This means a block is allocated, the local symlink target name
is copied to the block and the block is logged. Currently,
xfs_bmap_local_to_extents() handles logging the remote block data based
on the size of the data fork prior to the conversion. This is not
correct on v5 superblock filesystems, which add an additional header to
remote symlink blocks that is nonexistent in local format inodes.

As a result, the full length of the remote symlink block content is not
logged. This can lead to corruption should a crash occur and log
recovery replay this transaction.

Since a callout is already used to initialize the new remote symlink
block, update the local-to-extents conversion mechanism to make the
callout also responsible for logging the block. It is already required
to set the log buffer type and format the block appropriately based on
the superblock version. This ensures the remote symlink is always logged
correctly. Note that xfs_bmap_local_to_extents() is only called for
symlinks so there are no other callouts that require modification.

Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
fs/xfs/libxfs/xfs_bmap.c
fs/xfs/libxfs/xfs_symlink_remote.c

index 8e2010d53b079556de461d6a1be1f9cbbc7e2924..b5cb61ecd5bb5d6ecc5558cad21c04566a383555 100644 (file)
@@ -948,14 +948,16 @@ xfs_bmap_local_to_extents(
        bp = xfs_btree_get_bufl(args.mp, tp, args.fsbno, 0);
 
        /*
-        * Initialise the block and copy the data
+        * Initialize the block, copy the data and log the remote buffer.
         *
-        * Note: init_fn must set the buffer log item type correctly!
+        * The callout is responsible for logging because the remote format
+        * might differ from the local format and thus we don't know how much to
+        * log here. Note that init_fn must also set the buffer log item type
+        * correctly.
         */
        init_fn(tp, bp, ip, ifp);
 
-       /* account for the change in fork size and log everything */
-       xfs_trans_log_buf(tp, bp, 0, ifp->if_bytes - 1);
+       /* account for the change in fork size */
        xfs_idata_realloc(ip, -ifp->if_bytes, whichfork);
        xfs_bmap_local_to_extents_empty(ip, whichfork);
        flags |= XFS_ILOG_CORE;
index 8f8af05b3f13da7acadc9ebfbc64f0e7debaf655..b9884aab46d32eb2e51c790357ea4a6c4bf6c193 100644 (file)
@@ -183,6 +183,7 @@ xfs_symlink_local_to_remote(
        if (!xfs_sb_version_hascrc(&mp->m_sb)) {
                bp->b_ops = NULL;
                memcpy(bp->b_addr, ifp->if_u1.if_data, ifp->if_bytes);
+               xfs_trans_log_buf(tp, bp, 0, ifp->if_bytes - 1);
                return;
        }
 
@@ -198,4 +199,6 @@ xfs_symlink_local_to_remote(
        buf = bp->b_addr;
        buf += xfs_symlink_hdr_set(mp, ip->i_ino, 0, ifp->if_bytes, bp);
        memcpy(buf, ifp->if_u1.if_data, ifp->if_bytes);
+       xfs_trans_log_buf(tp, bp, 0, sizeof(struct xfs_dsymlink_hdr) +
+                                       ifp->if_bytes - 1);
 }