--- /dev/null
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2022 SUSE Linux Products GmbH. All Rights Reserved.
+#
+# FS QA Test 260
+#
+# Make sure "btrfs filesystem defragment" can still convert the compression
+# algorithm of all regular extents.
+#
+. ./common/preamble
+_begin_fstest auto quick defrag compress prealloc
+
+# Override the default cleanup function.
+# _cleanup()
+# {
+# cd /
+# rm -r -f $tmp.*
+# }
+
+# Import common functions.
+. ./common/filter
+
+# real QA test starts here
+
+# Modify as appropriate.
+_supported_fs btrfs
+_require_scratch
+_require_xfs_io_command "fiemap" "ranged"
+_require_xfs_io_command "falloc"
+_require_btrfs_command inspect-internal dump-tree
+
+get_inode_number()
+{
+ local file="$1"
+
+ stat -c "%i" "$file"
+}
+
+get_file_extent()
+{
+ local file="$1"
+ local offset="$2"
+ local ino=$(get_inode_number "$file")
+ local file_extent_key="($ino EXTENT_DATA $offset)"
+
+ $BTRFS_UTIL_PROG inspect-internal dump-tree -t 5 $SCRATCH_DEV |\
+ grep -A4 "$file_extent_key"
+}
+
+check_file_extent()
+{
+ local file="$1"
+ local offset="$2"
+ local expected="$3"
+
+ echo "=== file extent at file '$file' offset $offset ===" >> $seqres.full
+ get_file_extent "$file" "$offset" > $tmp.output
+ cat $tmp.output >> $seqres.full
+ grep -q "$expected" $tmp.output ||\
+ echo "file \"$file\" offset $offset doesn't have expected string \"$expected\""
+}
+
+# Unlike file extents whose btrfs specific attributes need to be grabbed from
+# dump-tree, we can check holes by fiemap. And mkfs enables no-holes feature by
+# default in recent versions of btrfs-progs, preventing us from grabbing holes
+# from dump-tree.
+check_hole()
+{
+ local file="$1"
+ local offset="$2"
+ local len="$3"
+
+ output=$($XFS_IO_PROG -c "fiemap $offset $len" "$file" |\
+ _filter_xfs_io_fiemap | head -n1)
+ if [ -z $output ]; then
+ echo "=== file extent at file '$file' offset $offset is a hole ===" \
+ >> $seqres.full
+ else
+ echo "=== file extent at file '$file' offset $offset is not a hole ==="
+ fi
+}
+
+# Needs 4K sectorsize as the test is crafted using that sectorsize
+_require_btrfs_support_sectorsize 4096
+
+_scratch_mkfs -s 4k >> $seqres.full 2>&1
+
+# Initial data is compressed using lzo
+_scratch_mount -o compress=lzo
+
+# file 'large' has all of its compressed extents at their maximum size
+$XFS_IO_PROG -f -c "pwrite 0 1m" "$SCRATCH_MNT/large" >> $seqres.full
+
+# file 'fragment' has all of its compressed extents adjacent to
+# preallocated/hole ranges, which should not be defragged with regular
+# defrag ioctl, but should still be defragged by
+# "btrfs filesystem defragment -c"
+$XFS_IO_PROG -f -c "pwrite 0 16k" \
+ -c "pwrite 32k 16k" -c "pwrite 64k 16k" \
+ "$SCRATCH_MNT/fragment" >> $seqres.full
+sync
+# We only do the falloc after the compressed data reached disk.
+# Or the inode could have PREALLOC flag, and prevent the
+# data from being compressed.
+$XFS_IO_PROG -c "falloc 16k 16k" "$SCRATCH_MNT/fragment"
+sync
+
+echo "====== Before the defrag ======" >> $seqres.full
+
+# Should be lzo compressed
+check_file_extent "$SCRATCH_MNT/large" 0 "compression 2"
+
+# Should be lzo compressed
+check_file_extent "$SCRATCH_MNT/fragment" 0 "compression 2"
+
+# Should be preallocated
+check_file_extent "$SCRATCH_MNT/fragment" 16384 "type 2"
+
+# Should be lzo compressed
+check_file_extent "$SCRATCH_MNT/fragment" 32768 "compression 2"
+
+# Should be hole
+check_hole "$SCRATCH_MNT/fragment" 49152 16384
+
+# Should be lzo compressed
+check_file_extent "$SCRATCH_MNT/fragment" 65536 "compression 2"
+
+$BTRFS_UTIL_PROG filesystem defragment "$SCRATCH_MNT/large" -czstd \
+ "$SCRATCH_MNT/fragment" >> $seqres.full
+# Need to commit the transaction or dump-tree won't grab the new
+# metadata on-disk.
+sync
+
+echo "====== After the defrag ======" >> $seqres.full
+
+# Should be zstd compressed
+check_file_extent "$SCRATCH_MNT/large" 0 "compression 3"
+
+# Should be zstd compressed
+check_file_extent "$SCRATCH_MNT/fragment" 0 "compression 3"
+
+# Should be preallocated
+check_file_extent "$SCRATCH_MNT/fragment" 16384 "type 2"
+
+# Should be zstd compressed
+check_file_extent "$SCRATCH_MNT/fragment" 32768 "compression 3"
+
+# Should be hole
+check_hole "$SCRATCH_MNT/fragment" 49152 16384
+
+# Should be zstd compressed
+check_file_extent "$SCRATCH_MNT/fragment" 65536 "compression 3"
+
+echo "Silence is golden"
+
+# success, all done
+status=0
+exit