1#!/bin/sh
2# Test cp --sparse=always through SEEK_DATA copy
3
4# Copyright (C) 2010-2023 Free Software Foundation, Inc.
5
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15
16# You should have received a copy of the GNU General Public License
17# along with this program.  If not, see <https://www.gnu.org/licenses/>.
18
19. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
20print_ver_ cp
21require_perl_
22
23# The test was seen to fail on ext3 so exclude that type
24# (or any file system where the type can't be determined)
25touch sparse_chk
26if seek_data_capable_ sparse_chk && ! df -t ext3 . >/dev/null; then
27  : # Current partition has working extents.  Good!
28else
29  skip_ "insufficient SEEK_DATA support"
30
31  # It's not;  we need to create one, hence we need root access.
32  require_root_
33
34  cwd=$PWD
35  cleanup_() { cd /; umount "$cwd/mnt"; }
36
37  skip=0
38  # Create an ext4 loopback file system
39  dd if=/dev/zero of=blob bs=32k count=1000 || skip=1
40  mkdir mnt
41  mkfs -t ext4 -F blob ||
42    skip_ "failed to create ext4 file system"
43  mount -oloop blob mnt   || skip=1
44  cd mnt                  || skip=1
45  echo test > f           || skip=1
46  test -s f               || skip=1
47
48  test $skip = 1 &&
49    skip_ "insufficient mount/ext4 support"
50fi
51
52# =================================================
53# The data below was set up to ensure that the original FIEMAP-copying code
54# was exercised enough to provoke at least two iterations of the do...while loop
55# in which it calls ioctl (fd, FS_IOC_FIEMAP,...
56# This also verifies that non-trivial extents are preserved.
57
58# Extract logical block number and length pairs from filefrag -v output.
59# The initial sed is to remove the "eof" from the normally-empty "flags" field.
60# Similarly, remove flags values like "unknown,delalloc,eof".
61# That is required when that final extent has no number in the "expected" field.
62f()
63{
64  sed 's/ [a-z,][a-z,]*$//' $@ \
65    | $AWK '/^ *[0-9]/ {printf "%d %d ", $2, (NF>=6 ? $6 : (NF<5 ? $NF : $5)) }
66            END {print ""}'
67}
68
69for i in $(seq 1 2 21); do
70  for j in 1 2 31 100; do
71    $PERL -e '$n = '$i' * 1024; *F = *STDOUT;' \
72          -e 'for (1..'$j') { sysseek (*F, $n, 1)' \
73          -e '&& syswrite (*F, chr($_)x$n) or die "$!"}' > j1 || fail=1
74
75    # Note there is an implicit sync performed by cp on Linux kernels
76    # before 2.6.39 to work around bugs in EXT4 and BTRFS.
77    # (this was removed in the release after coreutils-8.32).
78    # Note also the -s parameter to the filefrag commands below
79    # for the same reasons.
80    cp --reflink=never --sparse=always j1 j2 || fail=1
81
82    cmp j1 j2 || fail_ "data loss i=$i j=$j"
83    if ! filefrag -vs j1 | grep -F extent >/dev/null; then
84      test $skip != 1 && warn_ 'skipping part; you lack filefrag'
85      skip=1
86    else
87      # Here is sample filefrag output:
88      #   $ perl -e 'BEGIN{$n=16*1024; *F=*STDOUT}' \
89      #          -e 'for (1..5) { sysseek(*F,$n,1)' \
90      #          -e '&& syswrite *F,"."x$n or die "$!"}' > j
91      #   $ filefrag -v j
92      #   File system type is: ef53
93      #   File size of j is 163840 (40 blocks, blocksize 4096)
94      #    ext logical physical expected length flags
95      #      0       4  6258884               4
96      #      1      12  6258892  6258887      4
97      #      2      20  6258900  6258895      4
98      #      3      28  6258908  6258903      4
99      #      4      36  6258916  6258911      4 eof
100      #   j: 6 extents found
101
102      # exclude the physical block numbers; they always differ
103      filefrag -v j1 > ff1 || framework_failure_
104      filefrag -vs j2 > ff2 || framework_failure_
105      { f ff1; f ff2; } | $PERL $abs_srcdir/tests/filefrag-extent-compare \
106        || {
107             warn_ ignoring filefrag-reported extent map differences
108             # Show the differing extent maps.
109             head -n99 ff1 ff2
110           }
111    fi
112    test $fail = 1 && break 2
113  done
114done
115
116Exit $fail
117