1#! /bin/ksh -p
2#
3# CDDL HEADER START
4#
5# The contents of this file are subject to the terms of the
6# Common Development and Distribution License (the "License").
7# You may not use this file except in compliance with the License.
8#
9# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10# or https://opensource.org/licenses/CDDL-1.0.
11# See the License for the specific language governing permissions
12# and limitations under the License.
13#
14# When distributing Covered Code, include this CDDL HEADER in each
15# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16# If applicable, add the following below this CDDL HEADER, with the
17# fields enclosed by brackets "[]" replaced with your own identifying
18# information: Portions Copyright [yyyy] [name of copyright owner]
19#
20# CDDL HEADER END
21#
22
23#
24# Copyright (c) 2023 by Pawel Jakub Dawidek
25#
26
27. $STF_SUITE/include/libtest.shlib
28. $STF_SUITE/include/math.shlib
29. $STF_SUITE/tests/functional/bclone/bclone_common.kshlib
30
31function first_half_checksum
32{
33    typeset -r file=$1
34
35    dd if=$file bs=$HALFRECORDSIZE count=1 2>/dev/null | sha256digest
36}
37
38function second_half_checksum
39{
40    typeset -r file=$1
41
42    dd if=$file bs=$HALFRECORDSIZE count=1 skip=1 2>/dev/null | sha256digest
43}
44
45function bclone_corner_cases_init
46{
47    typeset -r srcdir=$1
48    typeset -r dstdir=$2
49
50    export RECORDSIZE=4096
51    export HALFRECORDSIZE=$((RECORDSIZE / 2))
52
53    export CLONE="$dstdir/clone0"
54    export ORIG0="$srcdir/orig0"
55    export ORIG1="$srcdir/orig1"
56    export ORIG2="$srcdir/orig2"
57
58    # Create source files.
59    log_must dd if=/dev/urandom of="$ORIG0" bs=$RECORDSIZE count=1
60    log_must dd if=/dev/urandom of="$ORIG1" bs=$RECORDSIZE count=1
61    log_must dd if=/dev/urandom of="$ORIG2" bs=$RECORDSIZE count=1
62
63    export FIRST_HALF_ORIG0_CHECKSUM=$(first_half_checksum $ORIG0)
64    export FIRST_HALF_ORIG1_CHECKSUM=$(first_half_checksum $ORIG1)
65    export FIRST_HALF_ORIG2_CHECKSUM=$(first_half_checksum $ORIG2)
66    export SECOND_HALF_ORIG0_CHECKSUM=$(second_half_checksum $ORIG0)
67    export SECOND_HALF_ORIG1_CHECKSUM=$(second_half_checksum $ORIG1)
68    export SECOND_HALF_ORIG2_CHECKSUM=$(second_half_checksum $ORIG2)
69    export ZEROS_CHECKSUM=$(dd if=/dev/zero bs=$HALFRECORDSIZE count=1 2>/dev/null | sha256digest)
70    export FIRST_HALF_CHECKSUM=""
71    export SECOND_HALF_CHECKSUM=""
72}
73
74function cache_clone
75{
76    typeset -r cached=$1
77
78    case "$cached" in
79    "cached")
80        dd if=$CLONE of=/dev/null bs=$RECORDSIZE 2>/dev/null
81        ;;
82    "uncached")
83        ;;
84    *)
85        log_fail "invalid cached: $cached"
86        ;;
87    esac
88}
89
90function create_existing
91{
92    typeset -r existing=$1
93
94    case "$existing" in
95    "no")
96        ;;
97    "small empty")
98        log_must truncate_test -s $HALFRECORDSIZE -f $CLONE
99        ;;
100    "full empty")
101        log_must truncate_test -s $RECORDSIZE -f $CLONE
102        ;;
103    "small data")
104        log_must dd if=/dev/urandom of=$CLONE bs=$HALFRECORDSIZE count=1 \
105         2>/dev/null
106        ;;
107    "full data")
108        log_must dd if=/dev/urandom of=$CLONE bs=$RECORDSIZE count=1 2>/dev/null
109        ;;
110    *)
111        log_fail "invalid existing: $existing"
112        ;;
113    esac
114}
115
116function create_clone
117{
118    typeset -r clone=$1
119    typeset -r file=$2
120
121    case "$clone" in
122    "no")
123        ;;
124    "yes")
125        clonefile -f $file $CLONE
126        case "$file" in
127        $ORIG0)
128            FIRST_HALF_CHECKSUM=$FIRST_HALF_ORIG0_CHECKSUM
129            SECOND_HALF_CHECKSUM=$SECOND_HALF_ORIG0_CHECKSUM
130            ;;
131        $ORIG2)
132            FIRST_HALF_CHECKSUM=$FIRST_HALF_ORIG2_CHECKSUM
133            SECOND_HALF_CHECKSUM=$SECOND_HALF_ORIG2_CHECKSUM
134            ;;
135        *)
136            log_fail "invalid file: $file"
137            ;;
138        esac
139        ;;
140    *)
141        log_fail "invalid clone: $clone"
142        ;;
143    esac
144}
145
146function overwrite_clone
147{
148    typeset -r overwrite=$1
149
150    case "$overwrite" in
151    "no")
152        ;;
153    "free")
154        log_must truncate_test -s 0 -f $CLONE
155        log_must truncate_test -s $RECORDSIZE -f $CLONE
156        FIRST_HALF_CHECKSUM=$ZEROS_CHECKSUM
157        SECOND_HALF_CHECKSUM=$ZEROS_CHECKSUM
158        ;;
159    "full")
160        log_must dd if=$ORIG1 of=$CLONE bs=$RECORDSIZE count=1 2>/dev/null
161        FIRST_HALF_CHECKSUM=$FIRST_HALF_ORIG1_CHECKSUM
162        SECOND_HALF_CHECKSUM=$SECOND_HALF_ORIG1_CHECKSUM
163        ;;
164    "first half")
165        log_must dd if=$ORIG1 of=$CLONE bs=$HALFRECORDSIZE skip=0 seek=0 \
166          count=1 conv=notrunc 2>/dev/null
167        FIRST_HALF_CHECKSUM=$FIRST_HALF_ORIG1_CHECKSUM
168        ;;
169    "second half")
170        log_must dd if=$ORIG1 of=$CLONE bs=$HALFRECORDSIZE skip=1 seek=1 \
171          count=1 conv=notrunc 2>/dev/null
172        SECOND_HALF_CHECKSUM=$SECOND_HALF_ORIG1_CHECKSUM
173        ;;
174    *)
175        log_fail "invalid overwrite: $overwrite"
176        ;;
177    esac
178}
179
180function checksum_compare
181{
182    typeset -r compare=$1
183    typeset first_half_calculated_checksum second_half_calculated_checksum
184
185    case "$compare" in
186    "no")
187        ;;
188    "yes")
189        first_half_calculated_checksum=$(first_half_checksum $CLONE)
190        second_half_calculated_checksum=$(second_half_checksum $CLONE)
191
192        if [[ $first_half_calculated_checksum != $FIRST_HALF_CHECKSUM ]] || \
193           [[ $second_half_calculated_checksum != $SECOND_HALF_CHECKSUM ]]; then
194            return 1
195        fi
196        ;;
197    *)
198        log_fail "invalid compare: $compare"
199        ;;
200    esac
201}
202
203function bclone_corner_cases_test
204{
205    typeset cached existing
206    typeset first_clone first_overwrite
207    typeset read_after read_before
208    typeset second_clone second_overwrite
209    typeset -r srcdir=$1
210    typeset -r dstdir=$2
211    typeset limit=$3
212    typeset -i count=0
213    typeset oused
214    typeset osaved
215
216    if [[ $srcdir != "count" ]]; then
217        if [[ -n "$limit" ]]; then
218            typeset -r total_count=$(bclone_corner_cases_test count)
219            limit=$(random_int_between 1 $total_count $((limit*2)) | sort -nu | head -n $limit | xargs)
220        fi
221        bclone_corner_cases_init $srcdir $dstdir
222
223        # Save current block cloning stats for later use.
224        sync_pool $TESTPOOL
225        oused=$(get_pool_prop bcloneused $TESTPOOL)
226        osaved=$(get_pool_prop bclonesaved $TESTPOOL)
227    fi
228
229    #
230    # (create) / (cache) / (clone) / (overwrite) / (read) / (clone) / (overwrite) / (read) / read next txg
231    #
232    for existing in "no" "small empty" "full empty" "small data" "full data"; do
233        for cached in "uncached" "cached"; do
234            for first_clone in "no" "yes"; do
235                for first_overwrite in "no" "free" "full" "first half" "second half"; do
236                    for read_before in "no" "yes"; do
237                        for second_clone in "no" "yes"; do
238                            for second_overwrite in "no" "free" "full" "first half" "second half"; do
239                                for read_after in "no" "yes"; do
240                                    if [[ $first_clone = "no" ]] && \
241                                       [[ $second_clone = "no" ]]; then
242                                        continue
243                                    fi
244                                    if [[ $first_clone = "no" ]] && \
245                                       [[ $read_before = "yes" ]]; then
246                                        continue
247                                    fi
248                                    if [[ $second_clone = "no" ]] && \
249                                       [[ $read_before = "yes" ]] && \
250                                       [[ $read_after = "yes" ]]; then
251                                        continue
252                                    fi
253
254                                    count=$((count+1))
255
256                                    if [[ $srcdir = "count" ]]; then
257                                        # Just counting.
258                                        continue
259                                    fi
260
261                                    if [[ -n "$limit" ]]; then
262                                        if ! echo " $limit " | grep -q " $count "; then
263                                            continue
264                                        fi
265                                    fi
266
267                                    FIRST_HALF_CHECKSUM=""
268                                    SECOND_HALF_CHECKSUM=""
269
270                                    log_must zpool export $TESTPOOL
271                                    log_must zpool import $TESTPOOL
272
273                                    create_existing "$existing"
274
275                                    log_must zpool export $TESTPOOL
276                                    log_must zpool import $TESTPOOL
277
278                                    cache_clone "$cached"
279
280                                    create_clone "$first_clone" "$ORIG0"
281
282                                    overwrite_clone "$first_overwrite"
283
284                                    if checksum_compare $read_before; then
285                                        log_note "existing: $existing / cached: $cached / first_clone: $first_clone / first_overwrite: $first_overwrite / read_before: $read_before"
286                                    else
287                                        log_fail "FAIL: existing: $existing / cached: $cached / first_clone: $first_clone / first_overwrite: $first_overwrite / read_before: $read_before"
288                                    fi
289
290                                    create_clone "$second_clone" "$ORIG2"
291
292                                    overwrite_clone "$second_overwrite"
293
294                                    if checksum_compare $read_after; then
295                                        log_note "existing: $existing / cached: $cached / first_clone: $first_clone / first_overwrite: $first_overwrite / read_before: $read_before / second_clone: $second_clone / second_overwrite: $second_overwrite / read_after: $read_after"
296                                    else
297                                        log_fail "FAIL: existing: $existing / cached: $cached / first_clone: $first_clone / first_overwrite: $first_overwrite / read_before: $read_before / second_clone: $second_clone / second_overwrite: $second_overwrite / read_after: $read_after"
298                                    fi
299
300                                    log_must zpool export $TESTPOOL
301                                    log_must zpool import $TESTPOOL
302
303                                    if checksum_compare "yes"; then
304                                        log_note "existing: $existing / cached: $cached / first_clone: $first_clone / first_overwrite: $first_overwrite / read_before: $read_before / second_clone: $second_clone / second_overwrite: $second_overwrite / read_after: $read_after / read_next_txg"
305                                    else
306                                        log_fail "FAIL: existing: $existing / cached: $cached / first_clone: $first_clone / first_overwrite: $first_overwrite / read_before: $read_before / second_clone: $second_clone / second_overwrite: $second_overwrite / read_after: $read_after / read_next_txg"
307                                    fi
308
309                                    rm -f "$CLONE"
310                                    sync_pool $TESTPOOL
311                                    verify_pool_prop_eq bcloneused $oused
312                                    verify_pool_prop_eq bclonesaved $osaved
313                                done
314                            done
315                        done
316                    done
317                done
318            done
319        done
320    done
321
322    if [[ $srcdir = "count" ]]; then
323        echo $count
324    fi
325}
326