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.
8 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 # or http://www.opensolaris.org/os/licensing.
10 # See the License for the specific language governing permissions
11 # and limitations under the License.
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]
23 # Copyright 2009 Sun Microsystems, Inc. All rights reserved.
24 # Use is subject to license terms.
28 # Copyright (c) 2013, 2018 by Delphix. All rights reserved.
29 # Copyright (c) 2020 by Datto Inc. All rights reserved.
32 . $STF_SUITE/include/libtest.shlib
33 . $STF_SUITE/include/math.shlib
34 . $STF_SUITE/tests/functional/cli_root/zfs_set/zfs_set_common.kshlib
35 . $STF_SUITE/tests/functional/rsend/rsend.cfg
38 # Set up test model which includes various datasets
48 # ||@final @final @snapC
49 # ||@snapC @snapC @snapB
50 # ||@snapA @snapB @snapA
53 # $pool -------- $FS ------- fs1 ------- fs2
55 # vol vol \____ \ @fsnap
57 # @init @vsnap | ------------ fclone
59 # @final @snapB \ | @init
60 # @snapC vclone @snapA
68 function setup_test_model
72 log_must zfs create -p $pool/$FS/fs1/fs2
74 log_must zfs snapshot $pool@psnap
75 log_must zfs clone $pool@psnap $pool/pclone
77 if is_global_zone ; then
78 log_must zfs create -V 16M $pool/vol
79 log_must zfs create -V 16M $pool/$FS/vol
82 log_must zfs snapshot $pool/$FS/vol@vsnap
83 log_must zfs clone $pool/$FS/vol@vsnap $pool/$FS/vclone
87 log_must snapshot_tree $pool/$FS/fs1/fs2@fsnap
88 log_must zfs clone $pool/$FS/fs1/fs2@fsnap $pool/$FS/fs1/fclone
89 log_must zfs snapshot -r $pool@init
91 log_must snapshot_tree $pool@snapA
92 log_must snapshot_tree $pool@snapC
93 log_must snapshot_tree $pool/pclone@snapB
94 log_must snapshot_tree $pool/$FS@snapB
95 log_must snapshot_tree $pool/$FS@snapC
96 log_must snapshot_tree $pool/$FS/fs1@snapA
97 log_must snapshot_tree $pool/$FS/fs1@snapB
98 log_must snapshot_tree $pool/$FS/fs1@snapC
99 log_must snapshot_tree $pool/$FS/fs1/fclone@snapA
101 if is_global_zone ; then
102 log_must zfs snapshot $pool/vol@snapA
103 log_must zfs snapshot $pool/$FS/vol@snapB
104 log_must zfs snapshot $pool/$FS/vol@snapC
105 log_must zfs snapshot $pool/$FS/vclone@snapC
108 log_must zfs snapshot -r $pool@final
114 # Cleanup the BACKDIR and given pool content and all the sub datasets
118 function cleanup_pool
121 log_must rm -rf $BACKDIR/*
123 if is_global_zone ; then
124 log_must_busy zfs destroy -Rf $pool
126 typeset list=$(zfs list -H -r -t all -o name $pool)
128 if [[ $ds != $pool ]] ; then
129 if datasetexists $ds ; then
130 log_must_busy zfs destroy -Rf $ds
136 typeset mntpnt=$(get_prop mountpoint $pool)
137 if ! ismounted $pool ; then
138 # Make sure mountpoint directory is empty
139 if [[ -d $mntpnt ]]; then
140 log_must rm -rf $mntpnt/*
143 log_must zfs mount $pool
145 if [[ -d $mntpnt ]]; then
152 function cleanup_pools
162 typeset sum1=$(md5digest $file1)
163 typeset sum2=$(md5digest $file2)
164 test "$sum1" = "$sum2"
168 # Detect if the given two filesystems have same sub-datasets
170 # $1 source filesystem
171 # $2 destination filesystem
178 zfs list -r -H -t all -o name $src_fs > $BACKDIR/src1
179 zfs list -r -H -t all -o name $dst_fs > $BACKDIR/dst1
181 eval sed -e 's:^$src_fs:PREFIX:g' < $BACKDIR/src1 > $BACKDIR/src
182 eval sed -e 's:^$dst_fs:PREFIX:g' < $BACKDIR/dst1 > $BACKDIR/dst
184 diff $BACKDIR/src $BACKDIR/dst
187 rm -f $BACKDIR/src $BACKDIR/dst $BACKDIR/src1 $BACKDIR/dst1
193 # Compare all the directories and files in two filesystems
195 # $1 source filesystem
196 # $2 destination filesystem
203 typeset srcdir dstdir
204 srcdir=$(get_prop mountpoint $src_fs)
205 dstdir=$(get_prop mountpoint $dst_fs)
207 diff -r $srcdir $dstdir > /dev/null 2>&1
212 # Compare the given two dataset properties
221 typeset -a props=("type" "origin" "volblocksize" "acltype" "dnodesize" \
222 "atime" "canmount" "checksum" "compression" "copies" "devices" \
223 "exec" "quota" "readonly" "recordsize" "reservation" "setuid" \
224 "snapdir" "version" "volsize" "xattr" "mountpoint");
233 zfs get -H -o property,value,source $prop $dtst1 >> \
235 zfs get -H -o property,value,source $prop $dtst2 >> \
239 eval sed -e 's:$dtst1:PREFIX:g' < $BACKDIR/dtst1 > $BACKDIR/dtst1
240 eval sed -e 's:$dtst2:PREFIX:g' < $BACKDIR/dtst2 > $BACKDIR/dtst2
242 diff $BACKDIR/dtst1 $BACKDIR/dtst2
245 rm -f $BACKDIR/dtst1 $BACKDIR/dtst2
252 # Random create directories and files
260 if [[ -d $dir ]]; then
267 ((nl = RANDOM % 6 + 1))
270 mktree -b $dir -l $nl -d $nd -f $nf
277 # Put data in filesystem and take snapshot
281 function snapshot_tree
284 typeset ds=${snap%%@*}
285 typeset type=$(get_prop "type" $ds)
288 if [[ $type == "filesystem" ]]; then
289 typeset mntpnt=$(get_prop mountpoint $ds)
292 if ((ret == 0)) ; then
293 eval random_tree $mntpnt/${snap##$ds}
298 if ((ret == 0)) ; then
307 # Destroy the given snapshot and stuff
311 function destroy_tree
315 for snap in "$@" ; do
316 log_must_busy zfs destroy $snap
318 typeset ds=${snap%%@*}
319 typeset type=$(get_prop "type" $ds)
320 if [[ $type == "filesystem" ]]; then
321 typeset mntpnt=$(get_prop mountpoint $ds)
322 if [[ -n $mntpnt ]]; then
332 # Get all the sub-datasets of give dataset with specific suffix
337 function getds_with_suffix
342 typeset list=$(zfs list -r -H -t all -o name $ds | grep "$suffix$")
348 # Output inherited properties which is edited for file system
350 function fs_inherit_prop
353 if is_global_zone ; then
354 fs_prop=$(zfs inherit 2>&1 | \
355 awk '$2=="YES" && $3=="YES" {print $1}')
356 if ! is_te_enabled ; then
357 fs_prop=$(echo $fs_prop | grep -v "mlslabel")
360 fs_prop=$(zfs inherit 2>&1 | \
361 awk '$2=="YES" && $3=="YES" {print $1}'|
362 egrep -v "devices|mlslabel|sharenfs|sharesmb|zoned")
369 # Output inherited properties for volume
371 function vol_inherit_prop
373 echo "checksum readonly"
377 # Get the destination dataset to compare
385 # If the srcfs is not pool
387 if ! zpool list $srcfs > /dev/null 2>&1 ; then
388 eval dstfs="$dstfs/${srcfs#*/}"
397 # $1 Number of files to create
398 # $2 Maximum file size
400 # $4 File system to create the files on
410 for ((i=0; i<$nfiles; i=i+1)); do
411 file_name="/$fs/file-$maxsize-$((i+$file_id_offset))"
412 file_size=$((($RANDOM * $RANDOM % ($maxsize - 1)) + 1))
415 # Create an interesting mix of files which contain both
416 # data blocks and holes for more realistic test coverage.
417 # Half the files are created as sparse then partially filled,
418 # the other half is dense then a hole is punched in the file.
420 if [ $((RANDOM % 2)) -eq 0 ]; then
421 truncate -s $file_size $file_name || \
422 log_fail "Failed to create $file_name"
423 dd if=/dev/urandom of=$file_name \
424 bs=$bs count=$(($file_size / 2 / $bs)) \
425 seek=$(($RANDOM % (($file_size / 2 / $bs) + 1))) \
426 conv=notrunc >/dev/null 2>&1 || \
427 log_fail "Failed to create $file_name"
429 dd if=/dev/urandom of=$file_name \
430 bs=$file_size count=1 >/dev/null 2>&1 || \
431 log_fail "Failed to create $file_name"
432 dd if=/dev/zero of=$file_name \
433 bs=$bs count=$(($file_size / 2 / $bs)) \
434 seek=$(($RANDOM % (($file_size / 2 / $bs) + 1))) \
435 conv=notrunc >/dev/null 2>&1 || \
436 log_fail "Failed to create $file_name"
439 echo Created $nfiles files of random sizes up to $maxsize bytes
445 # $1 Number of files to remove
446 # $2 Maximum file size
448 # $4 File system to remove the files from
457 for ((i=0; i<$nfiles; i=i+1)); do
458 rm -f /$fs/file-$maxsize-$((i+$file_id_offset))
460 echo Removed $nfiles files of random sizes up to $maxsize bytes
464 # Simulate a random set of operations which could be reasonably expected
465 # to occur on an average filesystem.
467 # $1 Number of files to modify
468 # $2 Maximum file size
469 # $3 File system to modify the file on
470 # $4 Enabled xattrs (optional)
480 # Remove roughly half of the files in order to make it more
481 # likely that a dnode will be reallocated.
483 for ((i=0; i<$nfiles; i=i+1)); do
484 file_name="/$fs/file-$i"
486 if [[ -e $file_name ]]; then
487 if [ $((RANDOM % 2)) -eq 0 ]; then
489 log_fail "Failed to remove $file_name"
495 # Remount the filesystem to simulate normal usage. This resets
496 # the last allocated object id allowing for new objects to be
497 # reallocated in the locations of previously freed objects.
499 log_must zfs unmount $fs
500 log_must zfs mount $fs
502 for i in {0..$nfiles}; do
503 file_name="/$fs/file-$i"
504 file_size=$((($RANDOM * $RANDOM % ($maxsize - 1)) + 1))
507 # When the file exists modify it in one of five ways to
508 # simulate normal usage:
509 # - (20%) Remove and set and extended attribute on the file
510 # - (20%) Overwrite the existing file
511 # - (20%) Truncate the existing file to a random length
512 # - (20%) Truncate the existing file to zero length
513 # - (20%) Remove the file
515 # Otherwise create the missing file. 20% of the created
516 # files will be small and use embedded block pointers, the
517 # remainder with have random sizes up to the maximum size.
518 # Three extended attributes are attached to all of the files.
520 if [[ -e $file_name ]]; then
521 value=$((RANDOM % 5))
522 if [ $value -eq 0 -a $xattrs -ne 0 ]; then
523 attrname="testattr$((RANDOM % 3))"
524 attrlen="$(((RANDOM % 1000) + 1))"
525 attrvalue="$(random_string VALID_NAME_CHAR \
527 rm_xattr $attrname $file_name || \
528 log_fail "Failed to remove $attrname"
529 set_xattr $attrname "$attrvalue" $file_name || \
530 log_fail "Failed to set $attrname"
531 elif [ $value -eq 1 ]; then
532 dd if=/dev/urandom of=$file_name \
533 bs=$file_size count=1 >/dev/null 2>&1 || \
534 log_fail "Failed to overwrite $file_name"
535 elif [ $value -eq 2 ]; then
536 truncate -s $file_size $file_name || \
537 log_fail "Failed to truncate $file_name"
538 elif [ $value -eq 3 ]; then
539 truncate -s 0 $file_name || \
540 log_fail "Failed to truncate $file_name"
543 log_fail "Failed to remove $file_name"
546 if [ $((RANDOM % 5)) -eq 0 ]; then
547 file_size=$((($RANDOM % 64) + 1))
550 dd if=/dev/urandom of=$file_name \
551 bs=$file_size count=1 >/dev/null 2>&1 || \
552 log_fail "Failed to create $file_name"
554 if [ $xattrs -ne 0 ]; then
556 attrname="testattr$j"
557 attrlen="$(((RANDOM % 1000) + 1))"
558 attrvalue="$(random_string \
559 VALID_NAME_CHAR $attrlen)"
560 set_xattr $attrname \
561 "$attrvalue" $file_name || \
562 log_fail "Failed to set $attrname"
572 # Mess up a send file's contents
574 # $1 The send file path
576 function mess_send_file
580 filesize=$(stat_size $file)
582 offset=$(($RANDOM * $RANDOM % $filesize))
584 # The random offset might truncate the send stream to be
585 # smaller than the DRR_BEGIN record. If this happens, then
586 # the receiving system won't have enough info to create the
587 # partial dataset at all. We use zstreamdump to check for
588 # this and retry in this case.
589 nr_begins=$(head -c $offset $file | zstreamdump | \
590 grep DRR_BEGIN | awk '{ print $5 }')
591 while [ "$nr_begins" -eq 0 ]; do
592 offset=$(($RANDOM * $RANDOM % $filesize))
593 nr_begins=$(head -c $offset $file | zstreamdump | \
594 grep DRR_BEGIN | awk '{ print $5 }')
597 if (($RANDOM % 7 <= 1)); then
599 # We corrupt 2 bytes to minimize the chance that we
600 # write the same value that's already there.
602 log_must eval "dd if=/dev/urandom of=$file conv=notrunc " \
603 "bs=1 count=2 seek=$offset >/dev/null 2>&1"
605 log_must truncate -s $offset $file
610 # Diff the send/receive filesystems
612 # $1 The sent filesystem
613 # $2 The received filesystem
620 if [[ -d /$recvfs/.zfs/snapshot/a && -d \
621 /$sendfs/.zfs/snapshot/a ]]; then
622 diff -r /$recvfs/.zfs/snapshot/a /$sendfs/.zfs/snapshot/a
623 [[ $? -eq 0 ]] || log_fail "Differences found in snap a"
625 if [[ -d /$recvfs/.zfs/snapshot/b && -d \
626 /$sendfs/.zfs/snapshot/b ]]; then
627 diff -r /$recvfs/.zfs/snapshot/b /$sendfs/.zfs/snapshot/b
628 [[ $? -eq 0 ]] || log_fail "Differences found in snap b"
635 # $1 The ZFS send command
636 # $2 The filesystem where the streams are sent
637 # $3 The receive filesystem
638 # $4 Test dry-run (optional)
645 typeset dryrun=${4:-1}
648 log_must eval "$sendcmd >/$streamfs/$stream_num"
650 for ((i=0; i<2; i=i+1)); do
651 mess_send_file /$streamfs/$stream_num
652 log_mustnot zfs recv -suv $recvfs </$streamfs/$stream_num
653 stream_num=$((stream_num+1))
655 token=$(zfs get -Hp -o value receive_resume_token $recvfs)
658 [ $dryrun -ne 0 ] && \
659 log_must eval "zfs send -nvt $token > /dev/null"
661 log_must eval "zfs send -t $token >/$streamfs/$stream_num"
662 [[ -f /$streamfs/$stream_num ]] || \
663 log_fail "NO FILE /$streamfs/$stream_num"
665 log_must zfs recv -suv $recvfs </$streamfs/$stream_num
668 function get_resume_token
674 log_must eval "$sendcmd > /$streamfs/1"
675 mess_send_file /$streamfs/1
676 log_mustnot zfs recv -suv $recvfs < /$streamfs/1 2>&1
677 token=$(zfs get -Hp -o value receive_resume_token $recvfs)
678 echo "$token" > /$streamfs/resume_token
684 # Setup filesystems for the resumable send/receive tests
686 # $1 The "send" filesystem
687 # $2 The "recv" filesystem
689 function test_fs_setup
694 typeset sendpool=${sendfs%%/*}
695 typeset recvpool=${recvfs%%/*}
697 datasetexists $sendfs && log_must_busy zfs destroy -r $sendpool
698 datasetexists $recvfs && log_must_busy zfs destroy -r $recvpool
699 datasetexists $streamfs && log_must_busy zfs destroy -r $streamfs
701 if datasetexists $sendfs || zfs create -o compress=lz4 $sendfs; then
702 mk_files 1000 256 0 $sendfs &
703 mk_files 1000 131072 0 $sendfs &
704 mk_files 100 1048576 0 $sendfs &
705 mk_files 10 10485760 0 $sendfs &
706 mk_files 1 104857600 0 $sendfs &
708 log_must zfs snapshot $sendfs@a
710 rm_files 200 256 0 $sendfs &
711 rm_files 200 131072 0 $sendfs &
712 rm_files 20 1048576 0 $sendfs &
713 rm_files 2 10485760 0 $sendfs &
716 mk_files 400 256 0 $sendfs &
717 mk_files 400 131072 0 $sendfs &
718 mk_files 40 1048576 0 $sendfs &
719 mk_files 4 10485760 0 $sendfs &
722 log_must zfs snapshot $sendfs@b
723 log_must eval "zfs send -v $sendfs@a >/$sendpool/initial.zsend"
724 log_must eval "zfs send -v -i @a $sendfs@b " \
725 ">/$sendpool/incremental.zsend"
728 log_must zfs create -o compress=lz4 $streamfs
732 # Check to see if the specified features are set in a send stream.
733 # The values for these features are found in include/sys/zfs_ioctl.h
736 # $2-$n The flags expected in the stream
738 function stream_has_features
743 [[ -f $file ]] || log_fail "Couldn't find file: $file"
744 typeset flags=$(cat $file | zstreamdump | \
745 awk '/features =/ {features = $3} END {print features}')
748 feature[dedupprops]="2"
749 feature[sa_spill]="4"
750 feature[embed_data]="10000"
752 feature[mooch_byteswap]="40000"
753 feature[large_blocks]="80000"
754 feature[resuming]="100000"
755 feature[redacted]="200000"
756 feature[compressed]="400000"
758 typeset flag known derived=0
760 known=${feature[$flag]}
761 [[ -z $known ]] && log_fail "Unknown feature: $flag"
763 derived=$(printf "%x" $((0x${flags} & 0x${feature[$flag]})))
764 [[ $derived = $known ]] || return 1
771 # Given a send stream, verify that the size of the stream matches what's
772 # expected based on the source or target dataset. If the stream is an
773 # incremental stream, subtract the size of the source snapshot before
774 # comparing. This function does not currently handle incremental streams
777 # $1 The zstreamdump output file
778 # $2 The dataset to compare against
779 # This can be a source of a send or recv target (fs, not snapshot)
780 # $3 The percentage below which verification is deemed a failure
781 # $4 The source snapshot of an incremental send
784 function verify_stream_size
788 typeset percent=${3:-90}
791 [[ -f $stream ]] || log_fail "No such file: $stream"
792 datasetexists $ds || log_fail "No such dataset: $ds"
794 typeset stream_size=$(cat $stream | zstreamdump | sed -n \
795 's/ Total payload size = \(.*\) (0x.*)/\1/p')
798 if [[ -n $inc_src ]]; then
799 inc_size=$(get_prop lrefer $inc_src)
800 if stream_has_features $stream compressed; then
801 inc_size=$(get_prop refer $inc_src)
805 if stream_has_features $stream compressed; then
806 ds_size=$(get_prop refer $ds)
808 ds_size=$(get_prop lrefer $ds)
810 ds_size=$((ds_size - inc_size))
812 within_percent $stream_size $ds_size $percent || log_fail \
813 "$stream_size $ds_size differed by too much"
816 # Cleanup function for tests involving resumable send
817 function resume_cleanup
821 typeset sendpool=${sendfs%%/*}
823 datasetexists $sendfs && log_must_busy zfs destroy -r $sendfs
824 datasetexists $streamfs && log_must_busy zfs destroy -r $streamfs
826 rm -f /$sendpool/initial.zsend /$sendpool/incremental.zsend
829 # Randomly set the property to one of the enumerated values.
830 function rand_set_prop
835 typeset value=$(random_get $@)
837 log_must eval "zfs set $prop='$value' $dtst"
840 # Generate a recursive checksum of a filesystem which includes the file
841 # contents and any associated extended attributes.
842 function recursive_cksum
846 find $1 -type f -exec sh -c 'sha256 -q {}; lsextattr -q \
847 system {} | sha256 -q; lsextattr -q user {} | sha256 -q' \
848 \; | sort | sha256 -q
851 find $1 -type f -exec sh -c 'sha256sum {}; getfattr \
852 --absolute-names --only-values -d {} | sha256sum' \; | \
853 sort -k 2 | awk '{ print $1 }' | sha256sum | \