| ##/bin/bash | |
| # SPDX-License-Identifier: GPL-2.0 | |
| # Copyright (c) 2007 Silicon Graphics, Inc. All Rights Reserved. | |
| # | |
| # common functions for excersizing hole punches with extent size hints etc. | |
| _spawn_test_file() { | |
| echo "# spawning test file with $*" | |
| local blksize=$1 | |
| local file_size=`expr $2 \* $blksize` | |
| local extent_size_hint=`expr $3 \* $blksize` | |
| local test_file=$4 | |
| local reserve_space=$5 | |
| if [ $extent_size_hint -ne 0 ]; then | |
| echo "+ setting extent size hint to $extent_size_hint" | |
| $XFS_IO_PROG -f \ | |
| -c "extsize $extent_size_hint" \ | |
| $test_file | |
| fi | |
| # print extent size hint for $test_file | |
| $XFS_IO_PROG -f \ | |
| -c "extsize" \ | |
| $test_file | |
| if [ "$reserve_space" == "noresv" ]; then | |
| echo "+ not using resvsp at file creation" | |
| $XFS_IO_PROG -f \ | |
| -c "truncate $file_size" \ | |
| $test_file | |
| else | |
| $XFS_IO_PROG -f \ | |
| -c "truncate $file_size" \ | |
| -c "resvsp 0 $file_size" \ | |
| $test_file | |
| fi | |
| } | |
| _do_write() { | |
| echo "# writing with $*" | |
| local blksize=$1 | |
| local write_offset=`expr $2 \* $blksize` | |
| local write_size=`expr $3 \* $blksize` | |
| local test_file=$4 | |
| $XFS_IO_PROG -f \ | |
| -c "pwrite $write_offset $write_size" \ | |
| $test_file >/dev/null | |
| } | |
| _do_bmap() { | |
| echo "# showing file state $*" | |
| local test_file=$1 | |
| $XFS_IO_PROG -f \ | |
| -c "bmap -vvp" \ | |
| $test_file | |
| } | |
| _coalesce_extents() | |
| { | |
| block_size=$1 | |
| [[ -z $block_size ]] && block_size=512 | |
| awk -v block_size="$block_size" -F: ' | |
| { | |
| range = $2; | |
| type = $3; | |
| split(range, bounds, "[\\[ \\.\\]]"); | |
| low = bounds[3]; | |
| high = bounds[5]; | |
| if (type != prev_type) { | |
| if (prev_type != "") | |
| printf("%u]:%s\n", (low * 512 / block_size) - 1, | |
| prev_type); | |
| printf("%u: [%u..", out_count++, | |
| (low * 512) / block_size); | |
| prev_type = type; | |
| } | |
| } | |
| END { | |
| if (prev_type != "") | |
| printf("%u]:%s\n", ((high + 1) * 512 / block_size) - 1, | |
| prev_type); | |
| }' | |
| } | |
| _filter_fiemap() | |
| { | |
| block_size=$1 | |
| $AWK_PROG ' | |
| $3 ~ /hole/ { | |
| print $1, $2, $3; | |
| next; | |
| } | |
| $5 ~ /0x[[:xdigit:]]*8[[:xdigit:]][[:xdigit:]]/ { | |
| print $1, $2, "unwritten"; | |
| next; | |
| } | |
| $5 ~ /0x[[:xdigit:]]+/ { | |
| print $1, $2, "data"; | |
| }' | | |
| _coalesce_extents $block_size | |
| } | |
| _filter_fiemap_flags() | |
| { | |
| local include_encoded_flag=0 | |
| # Unless a first argument is passed and with a value of 1, the fiemap | |
| # encoded flag is filtered out. | |
| # This is because tests that use this filter's output in their golden | |
| # output may get the encoded flag set or not depending on the filesystem | |
| # and its configuration. For example, running btrfs with "-o compress" | |
| # (or compress-force) set in MOUNT_OPTIONS, then extents that get | |
| # compressed are reported with the encoded flag, otherwise that flag is | |
| # not reported. Like this the fs configuration does not cause a mismatch | |
| # with the golden output, and tests that exercise specific configurations | |
| # can explicitly ask for the encoded flag to be printed. | |
| if [ ! -z "$1" ] && [ $1 -eq 1 ]; then | |
| include_encoded_flag=1 | |
| fi | |
| local awk_script=' | |
| $3 ~ /hole/ { | |
| print $1, $2, $3; | |
| next; | |
| } | |
| $5 ~ /0x[[:xdigit:]]*8[[:xdigit:]][[:xdigit:]]/ { | |
| print $1, $2, "unwritten"; | |
| next; | |
| } | |
| $5 ~ /0x[[:xdigit:]]+/ { | |
| flags = strtonum($5); | |
| flag_str = "none"; | |
| set = 0; | |
| if (and(flags, 0x2000)) { | |
| flag_str = "shared"; | |
| set = 1; | |
| }' | |
| if [ $include_encoded_flag -eq 1 ]; then | |
| awk_script=$awk_script' | |
| if (and(flags, 0x8)) { | |
| if (set) { | |
| flag_str = flag_str"|"; | |
| } else { | |
| flag_str = ""; | |
| } | |
| flag_str = flag_str"encoded"; | |
| set = 1; | |
| }' | |
| fi | |
| awk_script=$awk_script' | |
| if (and(flags, 0x1)) { | |
| if (set) { | |
| flag_str = flag_str"|"; | |
| } else { | |
| flag_str = ""; | |
| } | |
| flag_str = flag_str"last"; | |
| } | |
| print $1, $2, flag_str | |
| }' | |
| $AWK_PROG -e "$awk_script" | _coalesce_extents | |
| } | |
| # Filters fiemap output to only print the | |
| # file offset column and whether or not | |
| # it is an extent or a hole | |
| _filter_hole_fiemap() | |
| { | |
| $AWK_PROG ' | |
| $3 ~ /hole/ { | |
| print $1, $2, $3; | |
| next; | |
| } | |
| $5 ~ /0x[[:xdigit:]]+/ { | |
| print $1, $2, "extent"; | |
| }' | | |
| _coalesce_extents | |
| } | |
| # Column 7 for datadev files and column 5 for rtdev files, To prevent the | |
| # 5th column in datadev files from being potentially matched incorrectly, | |
| # we need to match Column 7 for datadev files first, because the rtdev | |
| # file only has 5 columns in the `bmap -vp` output. | |
| # 10000 Unwritten preallocated extent | |
| # 01000 Doesn't begin on stripe unit | |
| # 00100 Doesn't end on stripe unit | |
| # 00010 Doesn't begin on stripe width | |
| # 00001 Doesn't end on stripe width | |
| _filter_bmap() | |
| { | |
| awk ' | |
| $3 ~ /hole/ { | |
| print $1, $2, $3; | |
| next; | |
| } | |
| $7 ~ /1[01][01][01][01]/ { | |
| print $1, $2, "unwritten"; | |
| next; | |
| } | |
| $7 ~ /0[01][01][01][01]/ { | |
| print $1, $2, "data" | |
| next; | |
| } | |
| $5 ~ /1[01][01][01][01]/ { | |
| print $1, $2, "unwritten"; | |
| next; | |
| } | |
| $5 ~ /0[01][01][01][01]/ { | |
| print $1, $2, "data" | |
| }' | | |
| _coalesce_extents | |
| } | |
| # test the different corner cases for zeroing a range: | |
| # | |
| # 1. into a hole | |
| # 2. into allocated space | |
| # 3. into unwritten space | |
| # 4. hole -> data | |
| # 5. hole -> unwritten | |
| # 6. data -> hole | |
| # 7. data -> unwritten | |
| # 8. unwritten -> hole | |
| # 9. unwritten -> data | |
| # 10. hole -> data -> hole | |
| # 11. data -> hole -> data | |
| # 12. unwritten -> data -> unwritten | |
| # 13. data -> unwritten -> data | |
| # 14. data -> hole @ EOF | |
| # 15. data -> hole @ 0 | |
| # 16. data -> cache cold ->hole | |
| # 17. data -> hole in single block file | |
| # | |
| # Test file is removed, created and sync'd between tests. | |
| # | |
| # Use -k flag to keep the file between tests. This will | |
| # test the handling of pre-existing holes. | |
| # | |
| # Use the -d flag to not sync the file between tests. | |
| # This will test the handling of delayed extents | |
| # | |
| # Use the -u flag to not run unwritten tests. | |
| # This will eliminate some unnecessary information. | |
| # | |
| _test_generic_punch() | |
| { | |
| remove_testfile=1 | |
| sync_cmd="-c fsync" | |
| unwritten_tests=1 | |
| OPTIND=1 | |
| while getopts 'dku' OPTION | |
| do | |
| case $OPTION in | |
| k) remove_testfile= | |
| ;; | |
| d) sync_cmd= | |
| ;; | |
| u) unwritten_tests= | |
| ;; | |
| ?) echo Invalid flag | |
| _exit 1 | |
| ;; | |
| esac | |
| done | |
| shift $(($OPTIND - 1)) | |
| alloc_cmd=$1 | |
| punch_cmd=$2 | |
| zero_cmd=$3 #if not testing zero just set to punch | |
| map_cmd=$4 | |
| filter_cmd=$5 | |
| testfile=$6 | |
| # The punch hole tests needs multiple of the largest extent size being | |
| # tested, with multiple=16 it can test extent size upto 64k. | |
| multiple=16 | |
| _4k="$((multiple * 4))k" | |
| _8k="$((multiple * 8))k" | |
| _12k="$((multiple * 12))k" | |
| _20k="$((multiple * 20))k" | |
| _require_congruent_file_oplen "$(dirname "$testfile")" $((multiple * 4096)) | |
| # initial test state must be defined, otherwise the first test can fail | |
| # due ot stale file state left from previous tests. | |
| rm -f $testfile | |
| echo " 1. into a hole" | |
| $XFS_IO_PROG -f -c "truncate $_20k" \ | |
| -c "$zero_cmd $_4k $_8k" \ | |
| -c "$map_cmd -v" $testfile | $filter_cmd | |
| [ $? -ne 0 ] && _fatal | |
| _md5_checksum $testfile | |
| echo " 2. into allocated space" | |
| if [ "$remove_testfile" ]; then | |
| rm -f $testfile | |
| fi | |
| $XFS_IO_PROG -f -c "truncate $_20k" \ | |
| -c "pwrite 0 $_20k" $sync_cmd \ | |
| -c "$zero_cmd $_4k $_8k" \ | |
| -c "$map_cmd -v" $testfile | $filter_cmd | |
| [ $? -ne 0 ] && _fatal | |
| _md5_checksum $testfile | |
| if [ "$unwritten_tests" ]; then | |
| echo " 3. into unwritten space" | |
| if [ "$remove_testfile" ]; then | |
| rm -f $testfile | |
| fi | |
| $XFS_IO_PROG -f -c "truncate $_20k" \ | |
| -c "$alloc_cmd 0 $_20k" \ | |
| -c "$zero_cmd $_4k $_8k" \ | |
| -c "$map_cmd -v" $testfile | $filter_cmd | |
| [ $? -ne 0 ] && _fatal | |
| _md5_checksum $testfile | |
| fi | |
| echo " 4. hole -> data" | |
| if [ "$remove_testfile" ]; then | |
| rm -f $testfile | |
| fi | |
| $XFS_IO_PROG -f -c "truncate $_20k" \ | |
| -c "pwrite $_8k $_8k" $sync_cmd \ | |
| -c "$zero_cmd $_4k $_8k" \ | |
| -c "$map_cmd -v" $testfile | $filter_cmd | |
| [ $? -ne 0 ] && _fatal | |
| _md5_checksum $testfile | |
| if [ "$unwritten_tests" ]; then | |
| echo " 5. hole -> unwritten" | |
| if [ "$remove_testfile" ]; then | |
| rm -f $testfile | |
| fi | |
| $XFS_IO_PROG -f -c "truncate $_20k" \ | |
| -c "$alloc_cmd $_8k $_8k" \ | |
| -c "$zero_cmd $_4k $_8k" \ | |
| -c "$map_cmd -v" $testfile | $filter_cmd | |
| [ $? -ne 0 ] && _fatal | |
| _md5_checksum $testfile | |
| fi | |
| echo " 6. data -> hole" | |
| if [ "$remove_testfile" ]; then | |
| rm -f $testfile | |
| fi | |
| $XFS_IO_PROG -f -c "truncate $_20k" \ | |
| -c "pwrite 0 $_8k" $sync_cmd \ | |
| -c "$zero_cmd $_4k $_8k" \ | |
| -c "$map_cmd -v" $testfile | $filter_cmd | |
| [ $? -ne 0 ] && _fatal | |
| _md5_checksum $testfile | |
| if [ "$unwritten_tests" ]; then | |
| echo " 7. data -> unwritten" | |
| if [ "$remove_testfile" ]; then | |
| rm -f $testfile | |
| fi | |
| $XFS_IO_PROG -f -c "truncate $_20k" \ | |
| -c "pwrite 0 $_8k" $sync_cmd \ | |
| -c "$alloc_cmd $_8k $_8k" \ | |
| -c "$zero_cmd $_4k $_8k" \ | |
| -c "$map_cmd -v" $testfile | $filter_cmd | |
| [ $? -ne 0 ] && _fatal | |
| _md5_checksum $testfile | |
| echo " 8. unwritten -> hole" | |
| if [ "$remove_testfile" ]; then | |
| rm -f $testfile | |
| fi | |
| $XFS_IO_PROG -f -c "truncate $_20k" \ | |
| -c "$alloc_cmd 0 $_8k" \ | |
| -c "$zero_cmd $_4k $_8k" \ | |
| -c "$map_cmd -v" $testfile | $filter_cmd | |
| [ $? -ne 0 ] && _fatal | |
| _md5_checksum $testfile | |
| echo " 9. unwritten -> data" | |
| if [ "$remove_testfile" ]; then | |
| rm -f $testfile | |
| fi | |
| $XFS_IO_PROG -f -c "truncate $_20k" \ | |
| -c "$alloc_cmd 0 $_8k" \ | |
| -c "pwrite $_8k $_8k" $sync_cmd \ | |
| -c "$zero_cmd $_4k $_8k" \ | |
| -c "$map_cmd -v" $testfile | $filter_cmd | |
| [ $? -ne 0 ] && _fatal | |
| _md5_checksum $testfile | |
| fi | |
| echo " 10. hole -> data -> hole" | |
| if [ "$remove_testfile" ]; then | |
| rm -f $testfile | |
| fi | |
| $XFS_IO_PROG -f -c "truncate $_20k" \ | |
| -c "pwrite $_8k $_4k" $sync_cmd \ | |
| -c "$zero_cmd $_4k $_12k" \ | |
| -c "$map_cmd -v" $testfile | $filter_cmd | |
| [ $? -ne 0 ] && _fatal | |
| _md5_checksum $testfile | |
| echo " 11. data -> hole -> data" | |
| if [ "$remove_testfile" ]; then | |
| rm -f $testfile | |
| fi | |
| $XFS_IO_PROG -f -c "truncate $_20k" \ | |
| -c "$alloc_cmd 0 $_20k" \ | |
| -c "pwrite 0 $_8k" \ | |
| -c "pwrite $_12k $_8k" $sync_cmd \ | |
| -c "$punch_cmd $_8k $_4k" \ | |
| -c "$zero_cmd $_4k $_12k" \ | |
| -c "$map_cmd -v" $testfile | $filter_cmd | |
| [ $? -ne 0 ] && _fatal | |
| _md5_checksum $testfile | |
| if [ "$unwritten_tests" ]; then | |
| echo " 12. unwritten -> data -> unwritten" | |
| if [ "$remove_testfile" ]; then | |
| rm -f $testfile | |
| fi | |
| $XFS_IO_PROG -f -c "truncate $_20k" \ | |
| -c "$alloc_cmd 0 $_20k" \ | |
| -c "pwrite $_8k $_4k" $sync_cmd \ | |
| -c "$zero_cmd $_4k $_12k" \ | |
| -c "$map_cmd -v" $testfile | $filter_cmd | |
| [ $? -ne 0 ] && _fatal | |
| _md5_checksum $testfile | |
| echo " 13. data -> unwritten -> data" | |
| if [ "$remove_testfile" ]; then | |
| rm -f $testfile | |
| fi | |
| $XFS_IO_PROG -f -c "truncate $_20k" \ | |
| -c "$alloc_cmd 0 $_20k" \ | |
| -c "pwrite 0k $_4k" $sync_cmd \ | |
| -c "pwrite $_12k $_8k" -c "fsync" \ | |
| -c "$zero_cmd $_4k $_12k" \ | |
| -c "$map_cmd -v" $testfile | $filter_cmd | |
| [ $? -ne 0 ] && _fatal | |
| _md5_checksum $testfile | |
| fi | |
| # Don't need to check EOF case for collapse range. | |
| # VFS layer return invalid error in this case, | |
| # So it is not a proper case for collapse range test of each local fs. | |
| if [ "$zero_cmd" != "fcollapse" ]; then | |
| echo " 14. data -> hole @ EOF" | |
| rm -f $testfile | |
| $XFS_IO_PROG -f -c "truncate $_20k" \ | |
| -c "pwrite 0 $_20k" $sync_cmd \ | |
| -c "$zero_cmd $_12k $_8k" \ | |
| -c "$map_cmd -v" $testfile | $filter_cmd | |
| [ $? -ne 0 ] && _fatal | |
| _md5_checksum $testfile | |
| fi | |
| if [ "$zero_cmd" == "fcollapse" ]; then | |
| echo " 14. data -> hole @ 0" | |
| else | |
| echo " 15. data -> hole @ 0" | |
| fi | |
| if [ "$remove_testfile" ]; then | |
| rm -f $testfile | |
| fi | |
| $XFS_IO_PROG -f -c "truncate $_20k" \ | |
| -c "pwrite 0 $_20k" $sync_cmd \ | |
| -c "$zero_cmd 0 $_8k" \ | |
| -c "$map_cmd -v" $testfile | $filter_cmd | |
| [ $? -ne 0 ] && _fatal | |
| _md5_checksum $testfile | |
| # If zero_cmd is fcollpase, don't check unaligned offsets | |
| if [ "$zero_cmd" == "fcollapse" ]; then | |
| return | |
| fi | |
| # If zero_cmd is finsert, don't check unaligned offsets | |
| if [ "$zero_cmd" == "finsert" ]; then | |
| return | |
| fi | |
| echo " 16. data -> cache cold ->hole" | |
| if [ "$remove_testfile" ]; then | |
| rm -f $testfile | |
| rm -f $testfile.2 | |
| else | |
| cp $testfile $testfile.2 | |
| fi | |
| $XFS_IO_PROG -f -c "truncate $_20k" \ | |
| -c "pwrite $_8k $_12k" -c "fsync" $testfile.2 \ | |
| > /dev/null | |
| $XFS_IO_PROG -f -c "truncate $_20k" \ | |
| -c "pwrite 0 $_20k" $sync_cmd \ | |
| -c "$zero_cmd 0k $_8k" \ | |
| -c "fadvise -d" \ | |
| -c "$map_cmd -v" $testfile | $filter_cmd | |
| diff $testfile $testfile.2 | |
| [ $? -ne 0 ] && _fatal | |
| rm -f $testfile.2 | |
| _md5_checksum $testfile | |
| # different file sizes mean we can't use md5sum to check the hole is | |
| # valid. Hence use hexdump to dump the contents and chop off the last | |
| # line of output that indicates the file size. We also have to fudge | |
| # the extent size as that will change with file size, too - that's what | |
| # the sed line noise does - it will always result in an output of [0..7] | |
| # so it matches 4k block size... | |
| echo " 17. data -> hole in single block file" | |
| if [ "$remove_testfile" ]; then | |
| rm -f $testfile | |
| fi | |
| block_size=`_get_file_block_size $TEST_DIR` | |
| $XFS_IO_PROG -f -c "truncate $block_size" \ | |
| -c "pwrite 0 $block_size" $sync_cmd \ | |
| -c "$zero_cmd 128 128" \ | |
| -c "$map_cmd -v" $testfile | $filter_cmd | \ | |
| sed -e "s/\.\.[0-9]*\]/..7\]/" | |
| [ $? -ne 0 ] && _fatal | |
| od -x $testfile | head -n -1 | |
| } | |
| _test_block_boundaries() | |
| { | |
| remove_testfile=1 | |
| sync_cmd="-c fsync" | |
| unwritten_tests=1 | |
| OPTIND=1 | |
| while getopts 'dk' OPTION | |
| do | |
| case $OPTION in | |
| k) remove_testfile= | |
| ;; | |
| d) sync_cmd= | |
| ;; | |
| ?) echo Invalid flag | |
| _exit 1 | |
| ;; | |
| esac | |
| done | |
| shift $(($OPTIND - 1)) | |
| bs=$1 | |
| zero_cmd=$2 | |
| filter_cmd=$3 | |
| testfile=$4 | |
| # Block size plus 1 | |
| bs_p1=$(($bs + 1)) | |
| # Block size plus 2 | |
| bs_p2=$(($bs + 2)) | |
| # Block size minus 1 | |
| bs_m1=$(($bs - 1)) | |
| # Block size multiplied by 2 | |
| bs_t2=$(($bs * 2)) | |
| # Block size divided by 2 | |
| bs_d2=$(($bs / 2)) | |
| echo "zero 0, 1" | |
| $XFS_IO_PROG -f -t -c "pwrite -S 0x41 0 $bs" \ | |
| -c "pwrite -S 0x42 $bs $bs" \ | |
| -c "$zero_cmd 0 1" \ | |
| -c "pread -v 0 $bs_t2" \ | |
| $testfile | $filter_cmd | |
| echo "zero 0, $bs_m1" | |
| $XFS_IO_PROG -f -t -c "pwrite -S 0x41 0 $bs" \ | |
| -c "pwrite -S 0x42 $bs $bs" \ | |
| -c "$zero_cmd 0 $bs_m1" \ | |
| -c "pread -v 0 $bs_t2" \ | |
| $testfile | $filter_cmd | |
| echo "zero 0, $bs" | |
| $XFS_IO_PROG -f -t -c "pwrite -S 0x41 0 $bs" \ | |
| -c "pwrite -S 0x42 $bs $bs" \ | |
| -c "$zero_cmd 0 $bs" \ | |
| -c "pread -v 0 $bs_t2" \ | |
| $testfile | $filter_cmd | |
| echo "zero 0, $bs_p1" | |
| $XFS_IO_PROG -f -t -c "pwrite -S 0x41 0 $bs" \ | |
| -c "pwrite -S 0x42 $bs $bs" \ | |
| -c "$zero_cmd 0 $bs_p1" \ | |
| -c "pread -v 0 $bs_t2" \ | |
| $testfile | $filter_cmd | |
| echo "zero $bs_m1, $bs" | |
| $XFS_IO_PROG -f -t -c "pwrite -S 0x41 0 $bs" \ | |
| -c "pwrite -S 0x42 $bs $bs" \ | |
| -c "$zero_cmd $bs_m1 $bs" \ | |
| -c "pread -v 0 $bs_t2" \ | |
| $testfile | $filter_cmd | |
| echo "zero $bs_m1, $bs_p1" | |
| $XFS_IO_PROG -f -t -c "pwrite -S 0x41 0 $bs" \ | |
| -c "pwrite -S 0x42 $bs $bs" \ | |
| -c "$zero_cmd $bs_m1 $bs_p1" \ | |
| -c "pread -v 0 $bs_t2" \ | |
| $testfile | $filter_cmd | |
| echo "zero $bs_m1, $bs_p2" | |
| $XFS_IO_PROG -f -t -c "pwrite -S 0x41 0 $bs" \ | |
| -c "pwrite -S 0x42 $bs $bs" \ | |
| -c "$zero_cmd $bs_m1 $bs_p2" \ | |
| -c "pread -v 0 $bs_t2" \ | |
| $testfile | $filter_cmd | |
| echo "zero $bs, $bs" | |
| $XFS_IO_PROG -f -t -c "pwrite -S 0x41 0 $bs" \ | |
| -c "pwrite -S 0x42 $bs $bs" \ | |
| -c "$zero_cmd $bs $bs" \ | |
| -c "pread -v 0 $bs_t2" \ | |
| $testfile | $filter_cmd | |
| echo "zero $bs_d2 , $bs" | |
| $XFS_IO_PROG -f -t -c "pwrite -S 0x41 0 $bs" \ | |
| -c "pwrite -S 0x42 $bs $bs" \ | |
| -c "$zero_cmd $bs_d2 $bs" \ | |
| -c "pread -v 0 $bs_t2" \ | |
| $testfile | $filter_cmd | |
| } |