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, 2016 by Delphix. All rights reserved.
29#
30
31. $STF_SUITE/include/libtest.shlib
32. $STF_SUITE/include/math.shlib
33. $STF_SUITE/tests/functional/rsend/rsend.cfg
34
35#
36# Set up test model which includes various datasets
37#
38#               @final
39#               @snapB
40#               @init
41#               |
42#   ______ pclone
43#  |      /
44#  |@psnap
45#  ||                         @final
46#  ||@final       @final      @snapC
47#  ||@snapC       @snapC      @snapB
48#  ||@snapA       @snapB      @snapA
49#  ||@init        @init       @init
50#  |||            |           |
51# $pool -------- $FS ------- fs1 ------- fs2
52#    \             \\_____     \          |
53#     vol           vol   \____ \         @fsnap
54#      |              |        \ \              \
55#      @init          @vsnap   |  ------------ fclone
56#      @snapA         @init \  |                    |
57#      @final         @snapB \ |                    @init
58#                     @snapC  vclone                @snapA
59#                     @final       |                @final
60#                                 @init
61#                                 @snapC
62#                                 @final
63#
64# $1 pool name
65#
66function setup_test_model
67{
68	typeset pool=$1
69
70	log_must zfs create -p $pool/$FS/fs1/fs2
71
72	log_must zfs snapshot $pool@psnap
73	log_must zfs clone $pool@psnap $pool/pclone
74
75	if is_global_zone ; then
76		log_must zfs create -V 16M $pool/vol
77		log_must zfs create -V 16M $pool/$FS/vol
78
79		log_must zfs snapshot $pool/$FS/vol@vsnap
80		log_must zfs clone $pool/$FS/vol@vsnap $pool/$FS/vclone
81	fi
82
83	log_must snapshot_tree $pool/$FS/fs1/fs2@fsnap
84	log_must zfs clone $pool/$FS/fs1/fs2@fsnap $pool/$FS/fs1/fclone
85	log_must zfs snapshot -r $pool@init
86
87	log_must snapshot_tree $pool@snapA
88	log_must snapshot_tree $pool@snapC
89	log_must snapshot_tree $pool/pclone@snapB
90	log_must snapshot_tree $pool/$FS@snapB
91	log_must snapshot_tree $pool/$FS@snapC
92	log_must snapshot_tree $pool/$FS/fs1@snapA
93	log_must snapshot_tree $pool/$FS/fs1@snapB
94	log_must snapshot_tree $pool/$FS/fs1@snapC
95	log_must snapshot_tree $pool/$FS/fs1/fclone@snapA
96
97	if is_global_zone ; then
98		log_must zfs snapshot $pool/vol@snapA
99		log_must zfs snapshot $pool/$FS/vol@snapB
100		log_must zfs snapshot $pool/$FS/vol@snapC
101		log_must zfs snapshot $pool/$FS/vclone@snapC
102	fi
103
104	log_must zfs snapshot -r $pool@final
105
106	return 0
107}
108
109#
110# Cleanup the BACKDIR and given pool content and all the sub datasets
111#
112# $1 pool name
113#
114function cleanup_pool
115{
116	typeset pool=$1
117	log_must rm -rf $BACKDIR/*
118
119	if is_global_zone ; then
120		log_must zfs destroy -Rf $pool
121	else
122		typeset list=$(zfs list -H -r -t all -o name $pool)
123		for ds in $list ; do
124			if [[ $ds != $pool ]] ; then
125				if datasetexists $ds ; then
126					log_must zfs destroy -Rf $ds
127				fi
128			fi
129		done
130	fi
131
132	typeset mntpnt=$(get_prop mountpoint $pool)
133	if ! ismounted $pool ; then
134		# Make sure mountpoint directory is empty
135		if [[ -d $mntpnt ]]; then
136			log_must rm -rf $mntpnt/*
137		fi
138
139		log_must zfs mount $pool
140	fi
141	if [[ -d $mntpnt ]]; then
142		rm -rf $mntpnt/*
143	fi
144
145	return 0
146}
147
148function cleanup_pools
149{
150	cleanup_pool $POOL2
151	destroy_pool $POOL3
152}
153
154#
155# Detect if the given two filesystems have same sub-datasets
156#
157# $1 source filesystem
158# $2 destination filesystem
159#
160function cmp_ds_subs
161{
162	typeset src_fs=$1
163	typeset dst_fs=$2
164
165	zfs list -r -H -t all -o name $src_fs > $BACKDIR/src1
166	zfs list -r -H -t all -o name $dst_fs > $BACKDIR/dst1
167
168	eval sed -e 's:^$src_fs:PREFIX:g' < $BACKDIR/src1 > $BACKDIR/src
169	eval sed -e 's:^$dst_fs:PREFIX:g' < $BACKDIR/dst1 > $BACKDIR/dst
170
171	diff $BACKDIR/src $BACKDIR/dst
172	typeset -i ret=$?
173
174	rm -f $BACKDIR/src $BACKDIR/dst $BACKDIR/src1 $BACKDIR/dst1
175
176	return $ret
177}
178
179#
180# Compare all the directores and files in two filesystems
181#
182# $1 source filesystem
183# $2 destination filesystem
184#
185function cmp_ds_cont
186{
187	typeset src_fs=$1
188	typeset dst_fs=$2
189
190	typeset srcdir dstdir
191	srcdir=$(get_prop mountpoint $src_fs)
192	dstdir=$(get_prop mountpoint $dst_fs)
193
194	diff -r $srcdir $dstdir > /dev/null 2>&1
195	return $?
196}
197
198#
199# Compare the given two dataset properties
200#
201# $1 dataset 1
202# $2 dataset 2
203#
204function cmp_ds_prop
205{
206	typeset dtst1=$1
207	typeset dtst2=$2
208
209	for item in "type" "origin" "volblocksize" "aclinherit" "aclmode" \
210	    "atime" "canmount" "checksum" "compression" "copies" "devices" \
211	    "dnodesize" "exec" "quota" "readonly" "recordsize" "reservation" \
212	    "setuid" "sharenfs" "snapdir" "version" "volsize" "xattr" "zoned" \
213	    "mountpoint";
214	do
215		zfs get -H -o property,value,source $item $dtst1 >> \
216		    $BACKDIR/dtst1
217		zfs get -H -o property,value,source $item $dtst2 >> \
218		    $BACKDIR/dtst2
219	done
220
221	eval sed -e 's:$dtst1:PREFIX:g' < $BACKDIR/dtst1 > $BACKDIR/dtst1
222	eval sed -e 's:$dtst2:PREFIX:g' < $BACKDIR/dtst2 > $BACKDIR/dtst2
223
224	diff $BACKDIR/dtst1 $BACKDIR/dtst2
225	typeset -i ret=$?
226
227	rm -f $BACKDIR/dtst1 $BACKDIR/dtst2
228
229	return $ret
230
231}
232
233#
234# Random create directories and files
235#
236# $1 directory
237#
238function random_tree
239{
240	typeset dir=$1
241
242	if [[ -d $dir ]]; then
243		rm -rf $dir
244	fi
245	mkdir -p $dir
246	typeset -i ret=$?
247
248	typeset -i nl nd nf
249	((nl = RANDOM % 6 + 1))
250	((nd = RANDOM % 3 ))
251	((nf = RANDOM % 5 ))
252	mktree -b $dir -l $nl -d $nd -f $nf
253	((ret |= $?))
254
255	return $ret
256}
257
258#
259# Put data in filesystem and take snapshot
260#
261# $1 snapshot name
262#
263function snapshot_tree
264{
265	typeset snap=$1
266	typeset ds=${snap%%@*}
267	typeset type=$(get_prop "type" $ds)
268
269	typeset -i ret=0
270	if [[ $type == "filesystem" ]]; then
271		typeset mntpnt=$(get_prop mountpoint $ds)
272		((ret |= $?))
273
274		if ((ret == 0)) ; then
275			eval random_tree $mntpnt/${snap##$ds}
276			((ret |= $?))
277		fi
278	fi
279
280	if ((ret == 0)) ; then
281		zfs snapshot $snap
282		((ret |= $?))
283	fi
284
285	return $ret
286}
287
288#
289# Destroy the given snapshot and stuff
290#
291# $1 snapshot
292#
293function destroy_tree
294{
295	typeset -i ret=0
296	typeset snap
297	for snap in "$@" ; do
298		zfs destroy $snap
299		ret=$?
300
301		typeset ds=${snap%%@*}
302		typeset type=$(get_prop "type" $ds)
303		if [[ $type == "filesystem" ]]; then
304			typeset mntpnt=$(get_prop mountpoint $ds)
305			((ret |= $?))
306
307			if ((ret != 0)); then
308				rm -r $mntpnt/$snap
309				((ret |= $?))
310			fi
311		fi
312
313		if ((ret != 0)); then
314			return $ret
315		fi
316	done
317
318	return 0
319}
320
321#
322# Get all the sub-datasets of give dataset with specific suffix
323#
324# $1 Given dataset
325# $2 Suffix
326#
327function getds_with_suffix
328{
329	typeset ds=$1
330	typeset suffix=$2
331
332	typeset list=$(zfs list -r -H -t all -o name $ds | grep "$suffix$")
333
334	echo $list
335}
336
337#
338# Output inherited properties whitch is edited for file system
339#
340function fs_inherit_prop
341{
342	typeset fs_prop
343	if is_global_zone ; then
344		fs_prop=$(zfs inherit 2>&1 | \
345		    awk '$2=="YES" && $3=="YES" {print $1}')
346		if ! is_te_enabled ; then
347		        fs_prop=$(echo $fs_prop | grep -v "mlslabel")
348		fi
349	else
350		fs_prop=$(zfs inherit 2>&1 | \
351		    awk '$2=="YES" && $3=="YES" {print $1}'|
352		    egrep -v "devices|mlslabel|sharenfs|sharesmb|zoned")
353	fi
354
355	echo $fs_prop
356}
357
358#
359# Output inherited properties for volume
360#
361function vol_inherit_prop
362{
363	echo "checksum readonly"
364}
365
366#
367# Get the destination dataset to compare
368#
369function get_dst_ds
370{
371	typeset srcfs=$1
372	typeset dstfs=$2
373
374	#
375	# If the srcfs is not pool
376	#
377	if ! zpool list $srcfs > /dev/null 2>&1 ; then
378		eval dstfs="$dstfs/${srcfs#*/}"
379	fi
380
381	echo $dstfs
382}
383
384#
385# Make test files
386#
387# $1 Number of files to create
388# $2 Maximum file size
389# $3 File ID offset
390# $4 File system to create the files on
391#
392function mk_files
393{
394	nfiles=$1
395	maxsize=$2
396	file_id_offset=$3
397	fs=$4
398
399	for ((i=0; i<$nfiles; i=i+1)); do
400		dd if=/dev/urandom \
401		    of=/$fs/file-$maxsize-$((i+$file_id_offset)) \
402		    bs=$(($RANDOM * $RANDOM % $maxsize)) \
403		    count=1 >/dev/null 2>&1 || log_fail \
404		    "Failed to create /$fs/file-$maxsize-$((i+$file_id_offset))"
405	done
406	echo Created $nfiles files of random sizes up to $maxsize bytes
407}
408
409#
410# Remove test files
411#
412# $1 Number of files to remove
413# $2 Maximum file size
414# $3 File ID offset
415# $4 File system to remove the files from
416#
417function rm_files
418{
419	nfiles=$1
420	maxsize=$2
421	file_id_offset=$3
422	fs=$4
423
424	for ((i=0; i<$nfiles; i=i+1)); do
425		rm -f /$fs/file-$maxsize-$((i+$file_id_offset))
426	done
427	echo Removed $nfiles files of random sizes up to $maxsize bytes
428}
429
430#
431# Simulate a random set of operations which could be reasonably expected
432# to occur on an average filesystem.
433#
434# $1 Number of files to modify
435# $2 Maximum file size
436# $3 File system to modify the file on
437# $4 Enabled xattrs (optional)
438#
439function churn_files
440{
441        nfiles=$1
442        maxsize=$2
443        fs=$3
444        xattrs=${4:-1}
445
446        #
447        # Remove roughly half of the files in order to make it more
448        # likely that a dnode will be reallocated.
449        #
450        for ((i=0; i<$nfiles; i=i+1)); do
451                file_name="/$fs/file-$i"
452
453                if [[ -e $file_name ]]; then
454                        if [ $((RANDOM % 2)) -eq 0 ]; then
455                                rm $file_name || \
456                                    log_fail "Failed to remove $file_name"
457                        fi
458                fi
459        done
460
461        #
462        # Remount the filesystem to simulate normal usage.  This resets
463        # the last allocated object id allowing for new objects to be
464        # reallocated in the locations of previously freed objects.
465        #
466        log_must zfs unmount $fs
467        log_must zfs mount $fs
468
469        for i in {0..$nfiles}; do
470                file_name="/$fs/file-$i"
471                file_size=$((($RANDOM * $RANDOM % ($maxsize - 1)) + 1))
472
473                #
474                # When the file exists modify it in one of five ways to
475                # simulate normal usage:
476                # - (20%) Remove and set and extended attribute on the file
477                # - (20%) Overwrite the existing file
478                # - (20%) Truncate the existing file to a random length
479                # - (20%) Truncate the existing file to zero length
480                # - (20%) Remove the file
481                #
482                # Otherwise create the missing file.  20% of the created
483                # files will be small and use embedded block pointers, the
484                # remainder with have random sizes up to the maximum size.
485                # Three extended attributes are attached to all of the files.
486                #
487                if [[ -e $file_name ]]; then
488                        value=$((RANDOM % 5))
489                        if [ $value -eq 0 -a $xattrs -ne 0 ]; then
490                                attrname="testattr$((RANDOM % 3))"
491                                attr -qr $attrname $file_name || \
492                                    log_fail "Failed to remove $attrname"
493                                attr -qs $attrname -V TestValue $file_name || \
494                                    log_fail "Failed to set $attrname"
495                        elif [ $value -eq 1 ]; then
496                                dd if=/dev/urandom of=$file_name \
497                                    bs=$file_size count=1 >/dev/null 2>&1 || \
498                                    log_fail "Failed to overwrite $file_name"
499                        elif [ $value -eq 2 ]; then
500                                truncate -s $file_size $file_name || \
501                                    log_fail "Failed to truncate $file_name"
502                        elif [ $value -eq 3 ]; then
503                                truncate -s 0 $file_name || \
504                                    log_fail "Failed to truncate $file_name"
505                        else
506                                rm $file_name || \
507                                    log_fail "Failed to remove $file_name"
508                        fi
509                else
510                        if [ $((RANDOM % 5)) -eq 0 ]; then
511                                file_size=$((($RANDOM % 64) + 1))
512                        fi
513
514                        dd if=/dev/urandom of=$file_name \
515                            bs=$file_size count=1 >/dev/null 2>&1 || \
516                            log_fail "Failed to create $file_name"
517
518                        if [ $xattrs -ne 0 ]; then
519                                for j in {0..2}; do
520                                        attrname="testattr$j"
521                                        attr -qs $attrname -V TestValue \
522                                            $file_name || log_fail \
523                                            "Failed to set $attrname"
524                                done
525                        fi
526                fi
527        done
528
529        return 0
530}
531
532#
533# Mess up file contents
534#
535# $1 The file path
536#
537function mess_file
538{
539	file=$1
540
541	filesize=$(stat -c '%s' $file)
542	offset=$(($RANDOM * $RANDOM % $filesize))
543	if (($RANDOM % 7 <= 1)); then
544		#
545		# We corrupt 2 bytes to minimize the chance that we
546		# write the same value that's already there.
547		#
548		log_must eval "dd if=/dev/random of=$file conv=notrunc " \
549		    "bs=1 count=2 oseek=$offset >/dev/null 2>&1"
550	else
551		log_must truncate -s $offset $file
552	fi
553}
554
555#
556# Diff the send/receive filesystems
557#
558# $1 The sent filesystem
559# $2 The received filesystem
560#
561function file_check
562{
563	sendfs=$1
564	recvfs=$2
565
566	if [[ -d /$recvfs/.zfs/snapshot/a && -d \
567	    /$sendfs/.zfs/snapshot/a ]]; then
568		diff -r /$recvfs/.zfs/snapshot/a /$sendfs/.zfs/snapshot/a
569		[[ $? -eq 0 ]] || log_fail "Differences found in snap a"
570	fi
571	if [[ -d /$recvfs/.zfs/snapshot/b && -d \
572	    /$sendfs/.zfs/snapshot/b ]]; then
573		diff -r /$recvfs/.zfs/snapshot/b /$sendfs/.zfs/snapshot/b
574		[[ $? -eq 0 ]] || log_fail "Differences found in snap b"
575	fi
576}
577
578#
579# Resume test helper
580#
581# $1 The ZFS send command
582# $2 The filesystem where the streams are sent
583# $3 The receive filesystem
584#
585function resume_test
586{
587	sendcmd=$1
588	streamfs=$2
589	recvfs=$3
590
591	stream_num=1
592	log_must eval "$sendcmd >/$streamfs/$stream_num"
593
594	for ((i=0; i<2; i=i+1)); do
595		mess_file /$streamfs/$stream_num
596		log_mustnot zfs recv -suv $recvfs </$streamfs/$stream_num
597		stream_num=$((stream_num+1))
598
599		token=$(zfs get -Hp -o value receive_resume_token $recvfs)
600		log_must eval "zfs send -v -t $token >/$streamfs/$stream_num"
601		[[ -f /$streamfs/$stream_num ]] || \
602		    log_fail "NO FILE /$streamfs/$stream_num"
603	done
604	log_must zfs recv -suv $recvfs </$streamfs/$stream_num
605}
606
607#
608# Setup filesystems for the resumable send/receive tests
609#
610# $1 The "send" filesystem
611# $2 The "recv" filesystem
612#
613function test_fs_setup
614{
615	typeset sendfs=$1
616	typeset recvfs=$2
617	typeset streamfs=$3
618	typeset sendpool=${sendfs%%/*}
619	typeset recvpool=${recvfs%%/*}
620
621	datasetexists $sendfs && log_must zfs destroy -r $sendpool
622	datasetexists $recvfs && log_must zfs destroy -r $recvpool
623	datasetexists $streamfs && log_must zfs destroy -r $streamfs
624
625	if $(datasetexists $sendfs || zfs create -o compress=lz4 $sendfs); then
626		mk_files 1000 256 0 $sendfs &
627		mk_files 1000 131072 0 $sendfs &
628		mk_files 100 1048576 0 $sendfs &
629		mk_files 10 10485760 0 $sendfs &
630		mk_files 1 104857600 0 $sendfs &
631		log_must wait
632		log_must zfs snapshot $sendfs@a
633
634		rm_files 200 256 0 $sendfs &
635		rm_files 200 131072 0 $sendfs &
636		rm_files 20 1048576 0 $sendfs &
637		rm_files 2 10485760 0 $sendfs &
638		log_must wait
639
640		mk_files 400 256 0 $sendfs &
641		mk_files 400 131072 0 $sendfs &
642		mk_files 40 1048576 0 $sendfs &
643		mk_files 4 10485760 0 $sendfs &
644		log_must wait
645
646		log_must zfs snapshot $sendfs@b
647		log_must eval "zfs send -v $sendfs@a >/$sendpool/initial.zsend"
648		log_must eval "zfs send -v -i @a $sendfs@b " \
649		    ">/$sendpool/incremental.zsend"
650	fi
651
652	log_must zfs create -o compress=lz4 $streamfs
653}
654
655#
656# Check to see if the specified features are set in a send stream.
657# The values for these features are found in uts/common/fs/zfs/sys/zfs_ioctl.h
658#
659# $1 The stream file
660# $2-$n The flags expected in the stream
661#
662function stream_has_features
663{
664	typeset file=$1
665	shift
666
667	[[ -f $file ]] || log_fail "Couldn't find file: $file"
668	typeset flags=$(cat $file | zstreamdump | awk '/features =/ {print $3}')
669	typeset -A feature
670	feature[dedup]="1"
671	feature[dedupprops]="2"
672	feature[sa_spill]="4"
673	feature[embed_data]="10000"
674	feature[lz4]="20000"
675	feature[mooch_byteswap]="40000"
676	feature[large_blocks]="80000"
677	feature[resuming]="100000"
678	feature[redacted]="200000"
679	feature[compressed]="400000"
680
681	typeset flag known derived=0
682	for flag in "$@"; do
683		known=${feature[$flag]}
684		[[ -z $known ]] && log_fail "Unknown feature: $flag"
685
686		derived=$(echo "$flags & ${feature[$flag]} = X" | mdb | sed 's/ //g')
687		[[ $derived = $known ]] || return 1
688	done
689
690	return 0
691}
692
693#
694# Parse zstreamdump -v output.  The output varies for each kind of record:
695# BEGIN records are simply output as "BEGIN"
696# END records are output as "END"
697# OBJECT records become "OBJECT <object num>"
698# FREEOBJECTS records become "FREEOBJECTS <startobj> <numobjs>"
699# FREE records become "<record type> <start> <length>"
700# WRITE records become:
701# "<record type> <compression type> <start> <logical size> <compressed size>
702#  <data size>"
703#
704function parse_dump
705{
706	sed '/^WRITE/{N;s/\n/ /;}' | grep "^[A-Z]" | awk '{
707	    if ($1 == "BEGIN" || $1 == "END") print $1
708	    if ($1 == "OBJECT") print $1" "$4
709	    if ($1 == "FREEOBJECTS") print $1" "$4" "$7
710	    if ($1 == "FREE") print $1" "$7" "$10
711	    if ($1 == "WRITE") print $1" "$15" "$21" "$24" "$27" "$30}'
712}
713
714#
715# Given a send stream, verify that the size of the stream matches what's
716# expected based on the source or target dataset. If the stream is an
717# incremental stream, subtract the size of the source snapshot before
718# comparing. This function does not currently handle incremental streams
719# that remove data.
720#
721# $1 The zstreamdump output file
722# $2 The dataset to compare against
723#    This can be a source of a send or recv target (fs, not snapshot)
724# $3 The percentage below which verification is deemed a failure
725# $4 The source snapshot of an incremental send
726#
727
728function verify_stream_size
729{
730	typeset stream=$1
731	typeset ds=$2
732	typeset percent=${3:-90}
733	typeset inc_src=$4
734
735	[[ -f $stream ]] || log_fail "No such file: $stream"
736	datasetexists $ds || log_fail "No such dataset: $ds"
737
738	typeset stream_size=$(cat $stream | zstreamdump | sed -n \
739	    's/	Total write size = \(.*\) (0x.*)/\1/p')
740
741	typeset inc_size=0
742	if [[ -n $inc_src ]]; then
743		inc_size=$(get_prop lrefer $inc_src)
744		if stream_has_features $stream compressed; then
745			inc_size=$(get_prop refer $inc_src)
746		fi
747	fi
748
749	if stream_has_features $stream compressed; then
750		ds_size=$(get_prop refer $ds)
751	else
752		ds_size=$(get_prop lrefer $ds)
753	fi
754	ds_size=$((ds_size - inc_size))
755
756	within_percent $stream_size $ds_size $percent || log_fail \
757	    "$stream_size $ds_size differed by too much"
758}
759
760# Cleanup function for tests involving resumable send
761function resume_cleanup
762{
763	typeset sendfs=$1
764	typeset streamfs=$2
765	typeset sendpool=${sendfs%%/*}
766
767	datasetexists $sendfs && log_must zfs destroy -r $sendfs
768	datasetexists $streamfs && log_must zfs destroy -r $streamfs
769	cleanup_pool $POOL2
770	rm -f /$sendpool/initial.zsend /$sendpool/incremental.zsend
771}
772