btrfs: add test for multiple fsync with adjacent preallocated extents
[xfstests-dev.git] / tests / btrfs / 240
1 #! /bin/bash
2 # SPDX-License-Identifier: GPL-2.0
3 # Copyright (C) 2021 SUSE Linux Products GmbH. All Rights Reserved.
4 #
5 # FSQA Test No. 240
6 #
7 # Test a scenario where we do several partial writes into multiple preallocated
8 # extents across two transactions and with several fsyncs in between. The goal
9 # is to check that the fsyncs succeed. This scenario used to trigger an -EIO
10 # failure on the last fsync and turn the filesystem to RO mode because of a
11 # transaction abort.
12 #
13 seq=`basename $0`
14 seqres=$RESULT_DIR/$seq
15 echo "QA output created by $seq"
16 tmp=/tmp/$$
17 status=1        # failure is the default!
18 trap "_cleanup; exit \$status" 0 1 2 3 15
19
20 _cleanup()
21 {
22         _cleanup_flakey
23         cd /
24         rm -f $tmp.*
25 }
26
27 # get standard environment, filters and checks
28 . ./common/rc
29 . ./common/filter
30 . ./common/dmflakey
31
32 # real QA test starts here
33 _supported_fs btrfs
34 _require_scratch
35 _require_dm_target flakey
36 _require_xfs_io_command "falloc"
37
38 rm -f $seqres.full
39
40 _scratch_mkfs >>$seqres.full 2>&1
41 _require_metadata_journaling $SCRATCH_DEV
42 _init_flakey
43 _mount_flakey
44
45
46 # Create our test file with 2 preallocated extents. Leave a 1M hole between them
47 # to ensure that we get two file extent items that will never be merged into a
48 # single one. The extents are contiguous on disk, which will later result in the
49 # checksums for their data to be merged into a single checksum item in the csums
50 # btree.
51 #
52 $XFS_IO_PROG -f \
53              -c "falloc 0 1M" \
54              -c "falloc 3M 3M" \
55              $SCRATCH_MNT/foobar
56
57 # Now write to the second extent and leave only 1M of it as unwritten, which
58 # corresponds to the file range [4M, 5M[.
59 #
60 # Then fsync the file to flush delalloc and to clear full sync flag from the
61 # inode, so that a future fsync will use the fast code path.
62 #
63 # After the writeback triggered by the fsync we have 3 file extent items that
64 # point to the second extent we previously allocated with fallocate():
65 #
66 # 1) One file extent item of type BTRFS_FILE_EXTENT_REG that covers the file
67 #    range [3M, 4M[
68 #
69 # 2) One file extent item of type BTRFS_FILE_EXTENT_PREALLOC that covers the
70 #    file range [4M, 5M[
71 #
72 # 3) One file extent item of type BTRFS_FILE_EXTENT_REG that covers the file
73 #    range [5M, 6M[
74 #
75 # All these file extent items have a generation of 6, which is the ID of the
76 # transaction where they were created. The split of the original file extent
77 # item is done at btrfs_mark_extent_written() when ordered extents complete for
78 # the file ranges [3M, 4M[ and [5M, 6M[.
79 #
80 $XFS_IO_PROG -c "pwrite -S 0xab 3M 1M" \
81              -c "pwrite -S 0xef 5M 1M" \
82              -c "fsync" \
83              $SCRATCH_MNT/foobar | _filter_xfs_io
84
85 # Commit the current transaction. This wipes out the log tree created by the
86 # previous fsync.
87 sync
88
89 # Now write to the unwritten range of the second extent we allocated,
90 # corresponding to the file range [4M, 5M[, and fsync the file, which triggers
91 # the fast fsync code path.
92 #
93 # The fast fsync code path sees that there is a new extent map covering the file
94 # range [4M, 5M[ and therefore it will log a checksum item covering the range
95 # [1M, 2M[ of the second extent we allocated.
96 #
97 # Also, after the fsync finishes we no longer have the 3 file extent items that
98 # pointed to 3 sections of the second extent we allocated. Instead we end up
99 # with a single file extent item pointing to the whole extent, with a type of
100 # BTRFS_FILE_EXTENT_REG and a generation of 7 (the current transaction ID). This
101 # is due to the file extent item merging we do when completing ordered extents
102 # into ranges that point to unwritten (preallocated) extents. This merging is
103 # done at btrfs_mark_extent_written().
104 #
105 $XFS_IO_PROG -c "pwrite -S 0xcd 4M 1M" \
106              -c "fsync" \
107              $SCRATCH_MNT/foobar | _filter_xfs_io
108
109 # Now do some write to our file outside the range of the second extent that we
110 # allocated with fallocate() and truncate the file size from 6M down to 5M.
111 #
112 # The truncate operation sets the full sync runtime flag on the inode, forcing
113 # the next fsync to use the slow code path. It also changes the length of the
114 # second file extent item so that it represents the file range [3M, 5M[ and not
115 # the range [3M, 6M[ anymore.
116 #
117 # Finally fsync the file. Since this is a fsync that triggers the slow code path,
118 # it will remove all items associated to the inode from the log tree and then it
119 # will scan for file extent items in the fs/subvolume tree that have a generation
120 # matching the current transaction ID, which is 7. This means it will log 2 file
121 # extent items:
122 #
123 # 1) One for the first extent we allocated, covering the file range [0, 1M[
124 #
125 # 2) Another for the first 2M of the second extent we allocated, covering the
126 #    file range [3M, 5M[
127 #
128 # When logging the first file extent item we log a single checksum item that has
129 # all the checksums for the entire extent.
130 #
131 # When logging the second file extent item, we also lookup for the checksums that
132 # are associated with the range [0, 2M[ of the second extent we allocated (file
133 # range [3M, 5M[), and then we log them with btrfs_csum_file_blocks(). However
134 # that results in ending up with a log that has two checksum items with ranges
135 # that overlap:
136 #
137 # 1) One for the range [1M, 2M[ of the second extent we allocated, corresponding
138 #    to the file range [4M, 5M[, which we logged in the previous fsync that used
139 #    the fast code path;
140 #
141 # 2) One for the ranges [0, 1M[ and [0, 2M[ of the first and second extents,
142 #    respectively, corresponding to the files ranges [0, 1M[ and [3M, 5M[.
143 #    This one was added during this last fsync that uses the slow code path
144 #    and overlaps with the previous one logged by the previous fast fsync.
145 #
146 # This happens because when logging the checksums for the second extent, we
147 # notice they start at an offset that matches the end of the checksums item that
148 # we logged for the first extent, and because both extents are contiguous on
149 # disk, btrfs_csum_file_blocks() decides to extend that existing checksums item
150 # and append the checksums for the second extent to this item. The end result is
151 # we end up with two checksum items in the log tree that have overlapping ranges,
152 # as listed before, resulting in the fsync to fail with -EIO and aborting the
153 # transaction, turning the filesystem into RO mode.
154 #
155 $XFS_IO_PROG -c "pwrite -S 0xff 0 1M" \
156              -c "truncate 5M" \
157              -c "fsync" \
158              $SCRATCH_MNT/foobar | _filter_xfs_io
159
160 echo "File content before power failure:"
161 od -A d -t x1 $SCRATCH_MNT/foobar
162
163 # Simulate a power failure and mount again the filesystem. The file content
164 # must be the same that we had before.
165 _flakey_drop_and_remount
166
167 echo "File content before after failure:"
168 od -A d -t x1 $SCRATCH_MNT/foobar
169
170 _unmount_flakey
171
172 status=0
173 exit