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 http://www.opensolaris.org/os/licensing.
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 2009 Sun Microsystems, Inc.  All rights reserved.
24# Use is subject to license terms.
25#
26
27#
28# Copyright (c) 2013, 2018 by Delphix. All rights reserved.
29# Copyright (c) 2020 by Datto Inc. All rights reserved.
30#
31
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
36
37#
38# Set up test model which includes various datasets
39#
40#               @final
41#               @snapB
42#               @init
43#               |
44#   ______ pclone
45#  |      /
46#  |@psnap
47#  ||                         @final
48#  ||@final       @final      @snapC
49#  ||@snapC       @snapC      @snapB
50#  ||@snapA       @snapB      @snapA
51#  ||@init        @init       @init
52#  |||            |           |
53# $pool -------- $FS ------- fs1 ------- fs2
54#    \             \\_____     \          |
55#     vol           vol   \____ \         @fsnap
56#      |              |        \ \              \
57#      @init          @vsnap   |  ------------ fclone
58#      @snapA         @init \  |                    |
59#      @final         @snapB \ |                    @init
60#                     @snapC  vclone                @snapA
61#                     @final       |                @final
62#                                 @init
63#                                 @snapC
64#                                 @final
65#
66# $1 pool name
67#
68function setup_test_model
69{
70	typeset pool=$1
71
72	log_must zfs create -p $pool/$FS/fs1/fs2
73
74	log_must zfs snapshot $pool@psnap
75	log_must zfs clone $pool@psnap $pool/pclone
76
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
80		block_device_wait
81
82		log_must zfs snapshot $pool/$FS/vol@vsnap
83		log_must zfs clone $pool/$FS/vol@vsnap $pool/$FS/vclone
84		block_device_wait
85	fi
86
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
90
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
100
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
106	fi
107
108	log_must zfs snapshot -r $pool@final
109
110	return 0
111}
112
113#
114# Cleanup the BACKDIR and given pool content and all the sub datasets
115#
116# $1 pool name
117#
118function cleanup_pool
119{
120	typeset pool=$1
121	log_must rm -rf $BACKDIR/*
122
123	if is_global_zone ; then
124		log_must_busy zfs destroy -Rf $pool
125	else
126		typeset list=$(zfs list -H -r -t all -o name $pool)
127		for ds in $list ; do
128			if [[ $ds != $pool ]] ; then
129				if datasetexists $ds ; then
130					log_must_busy zfs destroy -Rf $ds
131				fi
132			fi
133		done
134	fi
135
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/*
141		fi
142
143		log_must zfs mount $pool
144	fi
145	if [[ -d $mntpnt ]]; then
146		rm -rf $mntpnt/*
147	fi
148
149	return 0
150}
151
152function cleanup_pools
153{
154	cleanup_pool $POOL2
155	destroy_pool $POOL3
156}
157
158function cmp_md5s {
159	typeset file1=$1
160	typeset file2=$2
161
162	typeset sum1=$(md5digest $file1)
163	typeset sum2=$(md5digest $file2)
164	test "$sum1" = "$sum2"
165}
166
167#
168# Detect if the given two filesystems have same sub-datasets
169#
170# $1 source filesystem
171# $2 destination filesystem
172#
173function cmp_ds_subs
174{
175	typeset src_fs=$1
176	typeset dst_fs=$2
177
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
180
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
183
184	diff $BACKDIR/src $BACKDIR/dst
185	typeset -i ret=$?
186
187	rm -f $BACKDIR/src $BACKDIR/dst $BACKDIR/src1 $BACKDIR/dst1
188
189	return $ret
190}
191
192#
193# Compare all the directories and files in two filesystems
194#
195# $1 source filesystem
196# $2 destination filesystem
197#
198function cmp_ds_cont
199{
200	typeset src_fs=$1
201	typeset dst_fs=$2
202
203	typeset srcdir dstdir
204	srcdir=$(get_prop mountpoint $src_fs)
205	dstdir=$(get_prop mountpoint $dst_fs)
206
207	diff -r $srcdir $dstdir > /dev/null 2>&1
208	return $?
209}
210
211#
212# Compare the given two dataset properties
213#
214# $1 dataset 1
215# $2 dataset 2
216#
217function cmp_ds_prop
218{
219	typeset dtst1=$1
220	typeset dtst2=$2
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");
225	if is_freebsd; then
226		props+=("jailed")
227	else
228		props+=("zoned")
229	fi
230
231	for prop in $props;
232	do
233		zfs get -H -o property,value,source $prop $dtst1 >> \
234		    $BACKDIR/dtst1
235		zfs get -H -o property,value,source $prop $dtst2 >> \
236		    $BACKDIR/dtst2
237	done
238
239	eval sed -e 's:$dtst1:PREFIX:g' < $BACKDIR/dtst1 > $BACKDIR/dtst1
240	eval sed -e 's:$dtst2:PREFIX:g' < $BACKDIR/dtst2 > $BACKDIR/dtst2
241
242	diff $BACKDIR/dtst1 $BACKDIR/dtst2
243	typeset -i ret=$?
244
245	rm -f $BACKDIR/dtst1 $BACKDIR/dtst2
246
247	return $ret
248
249}
250
251#
252# Random create directories and files
253#
254# $1 directory
255#
256function random_tree
257{
258	typeset dir=$1
259
260	if [[ -d $dir ]]; then
261		rm -rf $dir
262	fi
263	mkdir -p $dir
264	typeset -i ret=$?
265
266	typeset -i nl nd nf
267	((nl = RANDOM % 6 + 1))
268	((nd = RANDOM % 3 ))
269	((nf = RANDOM % 5 ))
270	mktree -b $dir -l $nl -d $nd -f $nf
271	((ret |= $?))
272
273	return $ret
274}
275
276#
277# Put data in filesystem and take snapshot
278#
279# $1 snapshot name
280#
281function snapshot_tree
282{
283	typeset snap=$1
284	typeset ds=${snap%%@*}
285	typeset type=$(get_prop "type" $ds)
286
287	typeset -i ret=0
288	if [[ $type == "filesystem" ]]; then
289		typeset mntpnt=$(get_prop mountpoint $ds)
290		((ret |= $?))
291
292		if ((ret == 0)) ; then
293			eval random_tree $mntpnt/${snap##$ds}
294			((ret |= $?))
295		fi
296	fi
297
298	if ((ret == 0)) ; then
299		zfs snapshot $snap
300		((ret |= $?))
301	fi
302
303	return $ret
304}
305
306#
307# Destroy the given snapshot and stuff
308#
309# $1 snapshot
310#
311function destroy_tree
312{
313	typeset -i ret=0
314	typeset snap
315	for snap in "$@" ; do
316		log_must_busy zfs destroy $snap
317
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
323				rm -rf $mntpnt/$snap
324			fi
325		fi
326	done
327
328	return 0
329}
330
331#
332# Get all the sub-datasets of give dataset with specific suffix
333#
334# $1 Given dataset
335# $2 Suffix
336#
337function getds_with_suffix
338{
339	typeset ds=$1
340	typeset suffix=$2
341
342	typeset list=$(zfs list -r -H -t all -o name $ds | grep "$suffix$")
343
344	echo $list
345}
346
347#
348# Output inherited properties which is edited for file system
349#
350function fs_inherit_prop
351{
352	typeset fs_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")
358		fi
359	else
360		fs_prop=$(zfs inherit 2>&1 | \
361		    awk '$2=="YES" && $3=="YES" {print $1}'|
362		    egrep -v "devices|mlslabel|sharenfs|sharesmb|zoned")
363	fi
364
365	echo $fs_prop
366}
367
368#
369# Output inherited properties for volume
370#
371function vol_inherit_prop
372{
373	echo "checksum readonly"
374}
375
376#
377# Get the destination dataset to compare
378#
379function get_dst_ds
380{
381	typeset srcfs=$1
382	typeset dstfs=$2
383
384	#
385	# If the srcfs is not pool
386	#
387	if ! zpool list $srcfs > /dev/null 2>&1 ; then
388		eval dstfs="$dstfs/${srcfs#*/}"
389	fi
390
391	echo $dstfs
392}
393
394#
395# Make test files
396#
397# $1 Number of files to create
398# $2 Maximum file size
399# $3 File ID offset
400# $4 File system to create the files on
401#
402function mk_files
403{
404	nfiles=$1
405	maxsize=$2
406	file_id_offset=$3
407	fs=$4
408	bs=512
409
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))
413
414		#
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.
419		#
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"
428		else
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"
437		fi
438	done
439	echo Created $nfiles files of random sizes up to $maxsize bytes
440}
441
442#
443# Remove test files
444#
445# $1 Number of files to remove
446# $2 Maximum file size
447# $3 File ID offset
448# $4 File system to remove the files from
449#
450function rm_files
451{
452	nfiles=$1
453	maxsize=$2
454	file_id_offset=$3
455	fs=$4
456
457	for ((i=0; i<$nfiles; i=i+1)); do
458		rm -f /$fs/file-$maxsize-$((i+$file_id_offset))
459	done
460	echo Removed $nfiles files of random sizes up to $maxsize bytes
461}
462
463#
464# Simulate a random set of operations which could be reasonably expected
465# to occur on an average filesystem.
466#
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)
471#
472function churn_files
473{
474	nfiles=$1
475	maxsize=$2
476	fs=$3
477	xattrs=${4:-1}
478
479	#
480	# Remove roughly half of the files in order to make it more
481	# likely that a dnode will be reallocated.
482	#
483	for ((i=0; i<$nfiles; i=i+1)); do
484		file_name="/$fs/file-$i"
485
486		if [[ -e $file_name ]]; then
487			if [ $((RANDOM % 2)) -eq 0 ]; then
488				rm $file_name || \
489				    log_fail "Failed to remove $file_name"
490			fi
491		fi
492	done
493
494	#
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.
498	#
499	log_must zfs unmount $fs
500	log_must zfs mount $fs
501
502	for i in {0..$nfiles}; do
503		file_name="/$fs/file-$i"
504		file_size=$((($RANDOM * $RANDOM % ($maxsize - 1)) + 1))
505
506		#
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
514		#
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.
519		#
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 \
526				    $attrlen)"
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"
541			else
542				rm $file_name || \
543				    log_fail "Failed to remove $file_name"
544			fi
545		else
546			if [ $((RANDOM % 5)) -eq 0 ]; then
547				file_size=$((($RANDOM % 64) + 1))
548			fi
549
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"
553
554			if [ $xattrs -ne 0 ]; then
555				for j in {0..2}; do
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"
563				done
564			fi
565		fi
566	done
567
568	return 0
569}
570
571#
572# Mess up a send file's contents
573#
574# $1 The send file path
575#
576function mess_send_file
577{
578	file=$1
579
580	filesize=$(stat_size $file)
581
582	offset=$(($RANDOM * $RANDOM % $filesize))
583
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 }')
595	done
596
597	if (($RANDOM % 7 <= 1)); then
598		#
599		# We corrupt 2 bytes to minimize the chance that we
600		# write the same value that's already there.
601		#
602		log_must eval "dd if=/dev/urandom of=$file conv=notrunc " \
603		    "bs=1 count=2 seek=$offset >/dev/null 2>&1"
604	else
605		log_must truncate -s $offset $file
606	fi
607}
608
609#
610# Diff the send/receive filesystems
611#
612# $1 The sent filesystem
613# $2 The received filesystem
614#
615function file_check
616{
617	sendfs=$1
618	recvfs=$2
619
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"
624	fi
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"
629	fi
630}
631
632#
633# Resume test helper
634#
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)
639#
640function resume_test
641{
642	typeset sendcmd=$1
643	typeset streamfs=$2
644	typeset recvfs=$3
645	typeset dryrun=${4:-1}
646
647	stream_num=1
648	log_must eval "$sendcmd >/$streamfs/$stream_num"
649
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))
654
655		token=$(zfs get -Hp -o value receive_resume_token $recvfs)
656
657		# Do a dry-run
658		[ $dryrun -ne 0 ] && \
659			log_must eval "zfs send -nvt $token > /dev/null"
660
661		log_must eval "zfs send -t $token  >/$streamfs/$stream_num"
662		[[ -f /$streamfs/$stream_num ]] || \
663		    log_fail "NO FILE /$streamfs/$stream_num"
664	done
665	log_must zfs recv -suv $recvfs </$streamfs/$stream_num
666}
667
668function get_resume_token
669{
670	sendcmd=$1
671	streamfs=$2
672	recvfs=$3
673
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
679
680	return 0
681}
682
683#
684# Setup filesystems for the resumable send/receive tests
685#
686# $1 The "send" filesystem
687# $2 The "recv" filesystem
688#
689function test_fs_setup
690{
691	typeset sendfs=$1
692	typeset recvfs=$2
693	typeset streamfs=$3
694	typeset sendpool=${sendfs%%/*}
695	typeset recvpool=${recvfs%%/*}
696
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
700
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 &
707		log_must wait
708		log_must zfs snapshot $sendfs@a
709
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 &
714		log_must wait
715
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 &
720		log_must wait
721
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"
726	fi
727
728	log_must zfs create -o compress=lz4 $streamfs
729}
730
731#
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
734#
735# $1 The stream file
736# $2-$n The flags expected in the stream
737#
738function stream_has_features
739{
740	typeset file=$1
741	shift
742
743	[[ -f $file ]] || log_fail "Couldn't find file: $file"
744	typeset flags=$(cat $file | zstreamdump | \
745	    awk '/features =/ {features = $3} END {print features}')
746	typeset -A feature
747	feature[dedup]="1"
748	feature[dedupprops]="2"
749	feature[sa_spill]="4"
750	feature[embed_data]="10000"
751	feature[lz4]="20000"
752	feature[mooch_byteswap]="40000"
753	feature[large_blocks]="80000"
754	feature[resuming]="100000"
755	feature[redacted]="200000"
756	feature[compressed]="400000"
757
758	typeset flag known derived=0
759	for flag in "$@"; do
760		known=${feature[$flag]}
761		[[ -z $known ]] && log_fail "Unknown feature: $flag"
762
763		derived=$(printf "%x" $((0x${flags} & 0x${feature[$flag]})))
764		[[ $derived = $known ]] || return 1
765	done
766
767	return 0
768}
769
770#
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
775# that remove data.
776#
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
782#
783
784function verify_stream_size
785{
786	typeset stream=$1
787	typeset ds=$2
788	typeset percent=${3:-90}
789	typeset inc_src=$4
790
791	[[ -f $stream ]] || log_fail "No such file: $stream"
792	datasetexists $ds || log_fail "No such dataset: $ds"
793
794	typeset stream_size=$(cat $stream | zstreamdump | sed -n \
795	    's/	Total payload size = \(.*\) (0x.*)/\1/p')
796
797	typeset inc_size=0
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)
802		fi
803	fi
804
805	if stream_has_features $stream compressed; then
806		ds_size=$(get_prop refer $ds)
807	else
808		ds_size=$(get_prop lrefer $ds)
809	fi
810	ds_size=$((ds_size - inc_size))
811
812	within_percent $stream_size $ds_size $percent || log_fail \
813	    "$stream_size $ds_size differed by too much"
814}
815
816# Cleanup function for tests involving resumable send
817function resume_cleanup
818{
819	typeset sendfs=$1
820	typeset streamfs=$2
821	typeset sendpool=${sendfs%%/*}
822
823	datasetexists $sendfs && log_must_busy zfs destroy -r $sendfs
824	datasetexists $streamfs && log_must_busy zfs destroy -r $streamfs
825	cleanup_pool $POOL2
826	rm -f /$sendpool/initial.zsend /$sendpool/incremental.zsend
827}
828
829# Randomly set the property to one of the enumerated values.
830function rand_set_prop
831{
832	typeset dtst=$1
833	typeset prop=$2
834	shift 2
835	typeset value=$(random_get $@)
836
837	log_must eval "zfs set $prop='$value' $dtst"
838}
839
840# Generate a recursive checksum of a filesystem which includes the file
841# contents and any associated extended attributes.
842function recursive_cksum
843{
844	case "$(uname)" in
845	FreeBSD)
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
849		;;
850	*)
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 | \
854		    awk '{ print $1 }'
855		;;
856	esac
857}
858