1#
2# CDDL HEADER START
3#
4# The contents of this file are subject to the terms of the
5# Common Development and Distribution License (the "License").
6# You may not use this file except in compliance with the License.
7#
8# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9# or https://opensource.org/licenses/CDDL-1.0.
10# See the License for the specific language governing permissions
11# and limitations under the License.
12#
13# When distributing Covered Code, include this CDDL HEADER in each
14# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15# If applicable, add the following below this CDDL HEADER, with the
16# fields enclosed by brackets "[]" replaced with your own identifying
17# information: Portions Copyright [yyyy] [name of copyright owner]
18#
19# CDDL HEADER END
20#
21
22#
23# Copyright (c) 2023 by Pawel Jakub Dawidek
24#
25
26. $STF_SUITE/tests/functional/bclone/bclone.cfg
27
28export RECORDSIZE=$(zfs get -Hp -o value recordsize $TESTPOOL/$TESTFS)
29
30MINBLKSIZE1=512
31MINBLKSIZE2=1024
32
33function verify_block_cloning
34{
35	if is_linux && [[ $(linux_version) -lt $(linux_version "4.5") ]]; then
36		log_unsupported "copy_file_range not available before Linux 4.5"
37	fi
38}
39
40function verify_crossfs_block_cloning
41{
42	if is_linux && [[ $(linux_version) -lt $(linux_version "5.3") ]]; then
43		log_unsupported "copy_file_range can't copy cross-filesystem before Linux 5.3"
44	fi
45
46	# Cross dataset block cloning only supported on FreeBSD 14+
47	# https://github.com/freebsd/freebsd-src/commit/969071be938c
48        if is_freebsd && [ $(freebsd_version) -lt $(freebsd_version 14.0) ] ; then
49               log_unsupported "Cloning across datasets not supported in $(uname -r)"
50        fi
51}
52
53# Unused.
54function size_to_dsize
55{
56    typeset -r size=$1
57    typeset -r dir=$2
58
59    typeset -r dataset=$(df $dir | tail -1 | awk '{print $1}')
60    typeset -r recordsize=$(get_prop recordsize $dataset)
61    typeset -r copies=$(get_prop copies $dataset)
62    typeset dsize
63
64    if [[ $size -le $recordsize ]]; then
65        dsize=$(( ((size - 1) / MINBLOCKSIZE + 1) * MINBLOCKSIZE ))
66    else
67        dsize=$(( ((size - 1) / recordsize + 1) * recordsize ))
68    fi
69    dsize=$((dsize*copies))
70
71    echo $dsize
72}
73
74function test_file_integrity
75{
76    typeset -r original_checksum=$1
77    typeset -r clone=$2
78    typeset -r filesize=$3
79
80    typeset -r clone_checksum=$(sha256digest $clone)
81
82    if [[ $original_checksum != $clone_checksum ]]; then
83        log_fail "Clone $clone is corrupted with file size $filesize"
84    fi
85}
86
87function verify_pool_prop_eq
88{
89    typeset -r prop=$1
90    typeset -r expected=$2
91
92    typeset -r value=$(get_pool_prop $prop $TESTPOOL)
93    if [[ $value != $expected ]]; then
94        log_fail "Pool property $prop is incorrect: expected $expected, got $value"
95    fi
96}
97
98function verify_pool_props
99{
100    typeset -r oused=$1
101    typeset -r osaved=$2
102    typeset dsize=$3
103    typeset ratio=$4
104
105    if [[ $dsize -eq 0 ]]; then
106        ratio=1
107    elif [[ $ratio -eq 1 ]]; then
108        dsize=0
109    fi
110    verify_pool_prop_eq bcloneused $(($oused+$dsize))
111    verify_pool_prop_eq bclonesaved $(($osaved+dsize*(ratio-1)))
112    if [[ $oused -eq 0 ]]; then
113        verify_pool_prop_eq bcloneratio "${ratio}.00"
114    fi
115}
116
117# Function to test file copying and integrity check.
118function bclone_test
119{
120    typeset -r datatype=$1
121    typeset filesize=$2
122    typeset -r embedded=$3
123    typeset -r srcdir=$4
124    typeset -r dstdir=$5
125    typeset dsize
126    typeset oused
127    typeset osaved
128
129    typeset -r original="${srcdir}/original"
130    typeset -r clone="${dstdir}/clone"
131
132    log_note "Testing file copy with datatype $datatype, file size $filesize, embedded $embedded"
133
134    # Save current block cloning stats for later use.
135    sync_pool $TESTPOOL
136    oused=$(get_pool_prop bcloneused $TESTPOOL)
137    osaved=$(get_pool_prop bclonesaved $TESTPOOL)
138
139    # Create a test file with known content.
140    case $datatype in
141        random|text)
142            if [[ $datatype = "random" ]]; then
143                dd if=/dev/urandom of=$original bs=$filesize count=1 2>/dev/null
144            else
145                filesize=$(((filesize/4)*4))
146                dd if=/dev/urandom bs=$(((filesize/4)*3)) count=1 | \
147                  openssl base64 -A > $original
148            fi
149            sync_pool $TESTPOOL
150            clonefile -f $original "${clone}-tmp"
151            sync_pool $TESTPOOL
152            # It is hard to predict block sizes that will be used,
153            # so just do one clone and take it from bcloneused.
154            dsize=$(get_pool_prop bcloneused $TESTPOOL)
155            dsize=$(($dsize-$oused))
156            if [[ $embedded = "false" ]]; then
157                log_must test $dsize -gt 0
158            fi
159            rm -f "${clone}-tmp"
160            sync_pool $TESTPOOL
161            ;;
162        hole)
163            log_must truncate_test -s $filesize -f $original
164            dsize=0
165            ;;
166        *)
167            log_fail "Unknown datatype $datatype"
168            ;;
169    esac
170    if [[ $embedded = "true" ]]; then
171        dsize=0
172    fi
173
174    typeset -r original_checksum=$(sha256digest $original)
175
176    sync_pool $TESTPOOL
177
178    # Create a first clone of the entire file.
179    clonefile -f $original "${clone}0"
180    # Try to clone the clone in the same transaction group.
181    clonefile -f "${clone}0" "${clone}2"
182
183    # Clone the original again...
184    clonefile -f $original "${clone}1"
185    # ...and overwrite it in the same transaction group.
186    clonefile -f $original "${clone}1"
187
188    # Clone the clone...
189    clonefile -f "${clone}1" "${clone}3"
190    sync_pool $TESTPOOL
191    # ...and overwrite in the new transaction group.
192    clonefile -f "${clone}1" "${clone}3"
193
194    sync_pool $TESTPOOL
195
196    # Test removal of the pending clones (before they are committed to disk).
197    clonefile -f $original "${clone}4"
198    clonefile -f "${clone}4" "${clone}5"
199    rm -f "${clone}4" "${clone}5"
200
201    # Clone into one file, but remove another file, but with the same data in
202    # the same transaction group.
203    clonefile -f $original "${clone}5"
204    sync_pool $TESTPOOL
205    clonefile -f $original "${clone}4"
206    rm -f "${clone}5"
207    test_file_integrity $original_checksum "${clone}4" $filesize
208    sync_pool $TESTPOOL
209    test_file_integrity $original_checksum "${clone}4" $filesize
210
211    clonefile -f "${clone}4" "${clone}5"
212    # Verify integrity of the cloned file before it is committed to disk.
213    test_file_integrity $original_checksum "${clone}5" $filesize
214
215    sync_pool $TESTPOOL
216
217    # Verify integrity in the new transaction group.
218    test_file_integrity $original_checksum "${clone}0" $filesize
219    test_file_integrity $original_checksum "${clone}1" $filesize
220    test_file_integrity $original_checksum "${clone}2" $filesize
221    test_file_integrity $original_checksum "${clone}3" $filesize
222    test_file_integrity $original_checksum "${clone}4" $filesize
223    test_file_integrity $original_checksum "${clone}5" $filesize
224
225    verify_pool_props $oused $osaved $dsize 7
226
227    # Clear cache and test after fresh import.
228    log_must zpool export $TESTPOOL
229    log_must zpool import $TESTPOOL
230
231    # Cloned uncached file.
232    clonefile -f $original "${clone}6"
233    # Cloned uncached clone.
234    clonefile -f "${clone}6" "${clone}7"
235
236    # Cache the file.
237    cat $original >/dev/null
238    clonefile -f $original "${clone}8"
239    clonefile -f "${clone}8" "${clone}9"
240
241    test_file_integrity $original_checksum "${clone}6" $filesize
242    test_file_integrity $original_checksum "${clone}7" $filesize
243    test_file_integrity $original_checksum "${clone}8" $filesize
244    test_file_integrity $original_checksum "${clone}9" $filesize
245
246    sync_pool $TESTPOOL
247
248    verify_pool_props $oused $osaved $dsize 11
249
250    log_must zpool export $TESTPOOL
251    log_must zpool import $TESTPOOL
252
253    test_file_integrity $original_checksum "${clone}0" $filesize
254    test_file_integrity $original_checksum "${clone}1" $filesize
255    test_file_integrity $original_checksum "${clone}2" $filesize
256    test_file_integrity $original_checksum "${clone}3" $filesize
257    test_file_integrity $original_checksum "${clone}4" $filesize
258    test_file_integrity $original_checksum "${clone}5" $filesize
259    test_file_integrity $original_checksum "${clone}6" $filesize
260    test_file_integrity $original_checksum "${clone}7" $filesize
261    test_file_integrity $original_checksum "${clone}8" $filesize
262    test_file_integrity $original_checksum "${clone}9" $filesize
263
264    rm -f $original
265    rm -f "${clone}1" "${clone}3" "${clone}5" "${clone}7"
266
267    sync_pool $TESTPOOL
268
269    test_file_integrity $original_checksum "${clone}0" $filesize
270    test_file_integrity $original_checksum "${clone}2" $filesize
271    test_file_integrity $original_checksum "${clone}4" $filesize
272    test_file_integrity $original_checksum "${clone}6" $filesize
273    test_file_integrity $original_checksum "${clone}8" $filesize
274    test_file_integrity $original_checksum "${clone}9" $filesize
275
276    verify_pool_props $oused $osaved $dsize 6
277
278    rm -f "${clone}0" "${clone}2" "${clone}4" "${clone}8" "${clone}9"
279
280    sync_pool $TESTPOOL
281
282    test_file_integrity $original_checksum "${clone}6" $filesize
283
284    verify_pool_props $oused $osaved $dsize 1
285
286    rm -f "${clone}6"
287
288    sync_pool $TESTPOOL
289
290    verify_pool_props $oused $osaved $dsize 1
291}
292