xfs: flush removing page cache in xfs_reflink_remap_prep
authorDave Chinner <dchinner@redhat.com>
Mon, 19 Nov 2018 21:31:10 +0000 (13:31 -0800)
committerDarrick J. Wong <darrick.wong@oracle.com>
Wed, 21 Nov 2018 18:10:53 +0000 (10:10 -0800)
On a sub-page block size filesystem, fsx is failing with a data
corruption after a series of operations involving copying a file
with the destination offset beyond EOF of the destination of the file:

8093(157 mod 256): TRUNCATE DOWN        from 0x7a120 to 0x50000 ******WWWW
8094(158 mod 256): INSERT 0x25000 thru 0x25fff  (0x1000 bytes)
8095(159 mod 256): COPY 0x18000 thru 0x1afff    (0x3000 bytes) to 0x2f400
8096(160 mod 256): WRITE    0x5da00 thru 0x651ff        (0x7800 bytes) HOLE
8097(161 mod 256): COPY 0x2000 thru 0x5fff      (0x4000 bytes) to 0x6fc00

The second copy here is beyond EOF, and it is to sub-page (4k) but
block aligned (1k) offset. The clone runs the EOF zeroing, landing
in a pre-existing post-eof delalloc extent. This zeroes the post-eof
extents in the page cache just fine, dirtying the pages correctly.

The problem is that xfs_reflink_remap_prep() now truncates the page
cache over the range that it is copying it to, and rounds that down
to cover the entire start page. This removes the dirty page over the
delalloc extent from the page cache without having written it back.
Hence later, when the page cache is flushed, the page at offset
0x6f000 has not been written back and hence exposes stale data,
which fsx trips over less than 10 operations later.

Fix this by changing xfs_reflink_remap_prep() to use
xfs_flush_unmap_range().

Signed-off-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Darrick J. Wong <darrick.wong@oracle.com>
Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
fs/xfs/xfs_bmap_util.c
fs/xfs/xfs_bmap_util.h
fs/xfs/xfs_reflink.c

index 167ff4297e5c753ff7bc70fe3a2bf48bb55a9d05..404e581f1ea1e879e4a32099d303e53a89fbb91a 100644 (file)
@@ -1042,7 +1042,7 @@ out_trans_cancel:
        goto out_unlock;
 }
 
-static int
+int
 xfs_flush_unmap_range(
        struct xfs_inode        *ip,
        xfs_off_t               offset,
index 87363d136bb618145c396223da5b4755bdd572c8..7a78229cf1a79807c7794e5f468eff6e4bf8ff7f 100644 (file)
@@ -80,4 +80,7 @@ int xfs_bmap_count_blocks(struct xfs_trans *tp, struct xfs_inode *ip,
                          int whichfork, xfs_extnum_t *nextents,
                          xfs_filblks_t *count);
 
+int    xfs_flush_unmap_range(struct xfs_inode *ip, xfs_off_t offset,
+                             xfs_off_t len);
+
 #endif /* __XFS_BMAP_UTIL_H__ */
index c56bdbfcf7ae58e904d4c971c03c4d84cad43aa3..322a852ce284a017382ceb9e75bb74762dfbad78 100644 (file)
@@ -1352,10 +1352,19 @@ xfs_reflink_remap_prep(
        if (ret)
                goto out_unlock;
 
-       /* Zap any page cache for the destination file's range. */
-       truncate_inode_pages_range(&inode_out->i_data,
-                       round_down(pos_out, PAGE_SIZE),
-                       round_up(pos_out + *len, PAGE_SIZE) - 1);
+       /*
+        * If pos_out > EOF, we may have dirtied blocks between EOF and
+        * pos_out. In that case, we need to extend the flush and unmap to cover
+        * from EOF to the end of the copy length.
+        */
+       if (pos_out > XFS_ISIZE(dest)) {
+               loff_t  flen = *len + (pos_out - XFS_ISIZE(dest));
+               ret = xfs_flush_unmap_range(dest, XFS_ISIZE(dest), flen);
+       } else {
+               ret = xfs_flush_unmap_range(dest, pos_out, *len);
+       }
+       if (ret)
+               goto out_unlock;
 
        return 1;
 out_unlock: