#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#

#
# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#

#
# Copyright (c) 2013, 2015 by Delphix. All rights reserved.
#

. $STF_SUITE/include/libtest.shlib
. $STF_SUITE/tests/functional/rsend/rsend.cfg

#
# Set up test model which includes various datasets
#
#               @final
#               @snapB
#               @init
#               |
#   ______ pclone
#  |      /
#  |@psnap
#  ||                         @final
#  ||@final       @final      @snapC
#  ||@snapC       @snapC      @snapB
#  ||@snapA       @snapB      @snapA
#  ||@init        @init       @init
#  |||            |           |
# $pool -------- $FS ------- fs1 ------- fs2
#    \             \\_____     \          |
#     vol           vol   \____ \         @fsnap
#      |              |        \ \              \
#      @init          @vsnap   |  ------------ fclone
#      @snapA         @init \  |                    |
#      @final         @snapB \ |                    @init
#                     @snapC  vclone                @snapA
#                     @final       |                @final
#                                 @init
#                                 @snapC
#                                 @final
#
# $1 pool name
#
function setup_test_model
{
	typeset pool=$1

	log_must $ZFS create -p $pool/$FS/fs1/fs2

	log_must $ZFS snapshot $pool@psnap
	log_must $ZFS clone $pool@psnap $pool/pclone

	if is_global_zone ; then
		log_must $ZFS create -V 16M $pool/vol
		log_must $ZFS create -V 16M $pool/$FS/vol
		block_device_wait

		log_must $ZFS snapshot $pool/$FS/vol@vsnap
		log_must $ZFS clone $pool/$FS/vol@vsnap $pool/$FS/vclone
		block_device_wait
	fi

	log_must snapshot_tree $pool/$FS/fs1/fs2@fsnap
	log_must $ZFS clone $pool/$FS/fs1/fs2@fsnap $pool/$FS/fs1/fclone
	log_must $ZFS snapshot -r $pool@init

	log_must snapshot_tree $pool@snapA
	log_must snapshot_tree $pool@snapC
	log_must snapshot_tree $pool/pclone@snapB
	log_must snapshot_tree $pool/$FS@snapB
	log_must snapshot_tree $pool/$FS@snapC
	log_must snapshot_tree $pool/$FS/fs1@snapA
	log_must snapshot_tree $pool/$FS/fs1@snapB
	log_must snapshot_tree $pool/$FS/fs1@snapC
	log_must snapshot_tree $pool/$FS/fs1/fclone@snapA

	if is_global_zone ; then
		log_must $ZFS snapshot $pool/vol@snapA
		log_must $ZFS snapshot $pool/$FS/vol@snapB
		log_must $ZFS snapshot $pool/$FS/vol@snapC
		log_must $ZFS snapshot $pool/$FS/vclone@snapC
	fi

	log_must $ZFS snapshot -r $pool@final

	return 0
}

#
# Cleanup the BACKDIR and given pool content and all the sub datasets
#
# $1 pool name
#
function cleanup_pool
{
	typeset pool=$1
	log_must $RM -rf $BACKDIR/*

	if is_global_zone ; then
		log_must $ZFS destroy -Rf $pool
	else
		typeset list=$($ZFS list -H -r -t filesystem,snapshot,volume -o name $pool)
		for ds in $list ; do
			if [[ $ds != $pool ]] ; then
				if datasetexists $ds ; then
					log_must $ZFS destroy -Rf $ds
				fi
			fi
		done
	fi

	typeset mntpnt=$(get_prop mountpoint $pool)
	if ! ismounted $pool ; then
		# Make sure mountpoint directory is empty
		if [[ -d $mntpnt ]]; then
			log_must $RM -rf $mntpnt/*
		fi

		log_must $ZFS mount $pool
	fi
	if [[ -d $mntpnt ]]; then
		log_must $RM -rf $mntpnt/*
	fi

	return 0
}

#
# Detect if the given two filesystems have same sub-datasets
#
# $1 source filesystem
# $2 destination filesystem
#
function cmp_ds_subs
{
	typeset src_fs=$1
	typeset dst_fs=$2

	$ZFS list -r -H -t filesystem,snapshot,volume -o name $src_fs > $BACKDIR/src1
	$ZFS list -r -H -t filesystem,snapshot,volume -o name $dst_fs > $BACKDIR/dst1

	eval $SED -e 's:^$src_fs:PREFIX:g' < $BACKDIR/src1 > $BACKDIR/src
	eval $SED -e 's:^$dst_fs:PREFIX:g' < $BACKDIR/dst1 > $BACKDIR/dst

	$DIFF $BACKDIR/src $BACKDIR/dst
	typeset -i ret=$?

	$RM -f $BACKDIR/src $BACKDIR/dst $BACKDIR/src1 $BACKDIR/dst1

	return $ret
}

#
# Compare all the directories and files in two filesystems
#
# $1 source filesystem
# $2 destination filesystem
#
function cmp_ds_cont
{
	typeset src_fs=$1
	typeset dst_fs=$2

	typeset srcdir dstdir
	srcdir=$(get_prop mountpoint $src_fs)
	dstdir=$(get_prop mountpoint $dst_fs)

	$DIFF -r $srcdir $dstdir > /dev/null 2>&1
	return $?
}

#
# Compare the given two dataset properties
#
# $1 dataset 1
# $2 dataset 2
#
function cmp_ds_prop
{
	typeset dtst1=$1
	typeset dtst2=$2

	for item in "type" "origin" "volblocksize" "aclinherit" "acltype" \
	    "atime" "canmount" "checksum" "compression" "copies" "devices" \
	    "dnodesize" "exec" "quota" "readonly" "recordsize" "reservation" \
	    "setuid" "snapdir" "version" "volsize" "xattr" "zoned" \
	    "mountpoint";
	do
		$ZFS get -H -o property,value,source $item $dtst1 >> \
		    $BACKDIR/dtst1
		$ZFS get -H -o property,value,source $item $dtst2 >> \
		    $BACKDIR/dtst2
	done

	eval $SED -e 's:$dtst1:PREFIX:g' < $BACKDIR/dtst1 > $BACKDIR/dtst1
	eval $SED -e 's:$dtst2:PREFIX:g' < $BACKDIR/dtst2 > $BACKDIR/dtst2

	$DIFF $BACKDIR/dtst1 $BACKDIR/dtst2
	typeset -i ret=$?

	$RM -f $BACKDIR/dtst1 $BACKDIR/dtst2

	return $ret

}

#
# Random create directories and files
#
# $1 directory
#
function random_tree
{
	typeset dir=$1

	if [[ -d $dir ]]; then
		$RM -rf $dir
	fi
	$MKDIR -p $dir
	typeset -i ret=$?

	typeset -i nl nd nf
	((nl = RANDOM % 6 + 1))
	((nd = RANDOM % 3 ))
	((nf = RANDOM % 5 ))
	$MKTREE -b $dir -l $nl -d $nd -f $nf
	((ret |= $?))

	return $ret
}

#
# Put data in filesystem and take snapshot
#
# $1 snapshot name
#
function snapshot_tree
{
	typeset snap=$1
	typeset ds=${snap%%@*}
	typeset type=$(get_prop "type" $ds)

	typeset -i ret=0
	if [[ $type == "filesystem" ]]; then
		typeset mntpnt=$(get_prop mountpoint $ds)
		((ret |= $?))

		if ((ret == 0)) ; then
			eval random_tree $mntpnt/${snap##$ds}
			((ret |= $?))
		fi
	fi

	if ((ret == 0)) ; then
		$ZFS snapshot $snap
		((ret |= $?))
	fi

	return $ret
}

#
# Destroy the given snapshot and stuff
#
# $1 snapshot
#
function destroy_tree
{
	typeset -i ret=0
	typeset snap
	for snap in "$@" ; do
		$ZFS destroy $snap
		ret=$?

		typeset ds=${snap%%@*}
		typeset type=$(get_prop "type" $ds)
		if [[ $type == "filesystem" ]]; then
			typeset mntpnt=$(get_prop mountpoint $ds)
			((ret |= $?))

			if ((ret != 0)); then
				$RM -r $mntpnt/$snap
				((ret |= $?))
			fi
		fi

		if ((ret != 0)); then
			return $ret
		fi
	done

	return 0
}

#
# Get all the sub-datasets of give dataset with specific suffix
#
# $1 Given dataset
# $2 Suffix
#
function getds_with_suffix
{
	typeset ds=$1
	typeset suffix=$2

	typeset list=$($ZFS list -r -H -t filesystem,snapshot,volume -o name $ds \
	    | $GREP "$suffix$")

	$ECHO $list
}

#
# Output inherited properties whitch is edited for file system
#
function fs_inherit_prop
{
	typeset fs_prop
	if is_global_zone ; then
		fs_prop=$($ZFS inherit 2>&1 | \
		    $AWK '$2=="YES" && $3=="YES" {print $1}')
		if ! is_te_enabled ; then
		        fs_prop=$(echo $fs_prop | $GREP -v "mlslabel")
		fi
	else
		fs_prop=$($ZFS inherit 2>&1 | \
		    $AWK '$2=="YES" && $3=="YES" {print $1}'|
		    $EGREP -v "devices|mlslabel|sharenfs|sharesmb|zoned")
	fi

	$ECHO $fs_prop
}

#
# Output inherited properties for volume
#
function vol_inherit_prop
{
	$ECHO "checksum readonly"
}

#
# Get the destination dataset to compare
#
function get_dst_ds
{
	typeset srcfs=$1
	typeset dstfs=$2

	#
	# If the srcfs is not pool
	#
	if ! $ZPOOL list $srcfs > /dev/null 2>&1 ; then
		eval dstfs="$dstfs/${srcfs#*/}"
	fi

	$ECHO $dstfs
}

#
# Make test files
#
# $1 Number of files to create
# $2 Maximum file size
# $3 File ID offset
# $4 File system to create the files on
#
function mk_files
{
	nfiles=$1
	maxsize=$2
	file_id_offset=$3
	fs=$4

	for ((i=0; i<$nfiles; i=i+1)); do
		$DD if=/dev/urandom \
		    of=/$fs/file-$maxsize-$((i+$file_id_offset)) \
		    bs=$((($RANDOM * $RANDOM % ($maxsize - 1)) + 1)) \
		    count=1 >/dev/null 2>&1 || log_fail \
		    "Failed to create /$fs/file-$maxsize-$((i+$file_id_offset))"
	done
	$ECHO Created $nfiles files of random sizes up to $maxsize bytes
}

#
# Remove test files
#
# $1 Number of files to remove
# $2 Maximum file size
# $3 File ID offset
# $4 File system to remove the files from
#
function rm_files
{
	nfiles=$1
	maxsize=$2
	file_id_offset=$3
	fs=$4

	for ((i=0; i<$nfiles; i=i+1)); do
		$RM -f /$fs/file-$maxsize-$((i+$file_id_offset))
	done
	$ECHO Removed $nfiles files of random sizes up to $maxsize bytes
}

#
# Mess up file contents
#
# $1 The file path
#
function mess_file
{
	file=$1

	filesize=$($STAT -c '%s' $file)
	offset=$(($RANDOM * $RANDOM % $filesize))
	if (($RANDOM % 7 <= 1)); then
		#
		# We corrupt 2 bytes to minimize the chance that we
		# write the same value that's already there.
		#
		log_must eval "$DD if=/dev/urandom of=$file conv=notrunc " \
		    "bs=1 count=2 seek=$offset >/dev/null 2>&1"
	else
		log_must $TRUNCATE -s $offset $file
	fi
}

#
# Diff the send/receive filesystems
#
# $1 The sent filesystem
# $2 The received filesystem
#
function file_check
{
	sendfs=$1
	recvfs=$2

	if [[ -d /$recvfs/.zfs/snapshot/a && -d \
	    /$sendfs/.zfs/snapshot/a ]]; then
		$DIFF -r /$recvfs/.zfs/snapshot/a /$sendfs/.zfs/snapshot/a
		[[ $? -eq 0 ]] || log_fail "Differences found in snap a"
	fi
	if [[ -d /$recvfs/.zfs/snapshot/b && -d \
	    /$sendfs/.zfs/snapshot/b ]]; then
		$DIFF -r /$recvfs/.zfs/snapshot/b /$sendfs/.zfs/snapshot/b
		[[ $? -eq 0 ]] || log_fail "Differences found in snap b"
	fi
}

#
# Resume test helper
#
# $1 The ZFS send command
# $2 The filesystem where the streams are sent
# $3 The receive filesystem
#
function resume_test
{
	sendcmd=$1
	streamfs=$2
	recvfs=$3

	stream_num=1
	log_must eval "$sendcmd >/$streamfs/$stream_num"

	for ((i=0; i<2; i=i+1)); do
		mess_file /$streamfs/$stream_num
		log_mustnot $ZFS recv -sv $recvfs </$streamfs/$stream_num
		stream_num=$((stream_num+1))

		token=$($ZFS get -Hp -o value receive_resume_token $recvfs)
		log_must eval "$ZFS send -v -t $token >/$streamfs/$stream_num"
		[[ -f /$streamfs/$stream_num ]] || \
		    log_fail "NO FILE /$streamfs/$stream_num"
	done
	log_must $ZFS recv -sv $recvfs </$streamfs/$stream_num
}

#
# Setup filesystems for the resumable send/receive tests
#
# $1 The pool to set up with the "send" filesystems
# $2 The pool for receive
#
function test_fs_setup
{
	sendpool=$1
	recvpool=$2

	sendfs=$sendpool/sendfs
	recvfs=$recvpool/recvfs
	streamfs=$sendpool/stream

	if datasetexists $recvfs; then
		log_must $ZFS destroy -r $recvfs
	fi
	if datasetexists $sendfs; then
		log_must $ZFS destroy -r $sendfs
	fi
	if $($ZFS create -o compress=lz4 $sendfs); then
		mk_files 1000 256 0 $sendfs &
		mk_files 1000 131072 0 $sendfs &
		mk_files 100 1048576 0 $sendfs &
		mk_files 10 10485760 0 $sendfs &
		mk_files 1 104857600 0 $sendfs &
		wait
		log_must $ZFS snapshot $sendfs@a

		rm_files 200 256 0 $sendfs &
		rm_files 200 131072 0 $sendfs &
		rm_files 20 1048576 0 $sendfs &
		rm_files 2 10485760 0 $sendfs &
		wait

		mk_files 400 256 0 $sendfs &
		mk_files 400 131072 0 $sendfs &
		mk_files 40 1048576 0 $sendfs &
		mk_files 4 10485760 0 $sendfs &
		wait

		log_must $ZFS snapshot $sendfs@b
		log_must eval "$ZFS send -v $sendfs@a >/$sendpool/initial.zsend"
		log_must eval "$ZFS send -v -i @a $sendfs@b " \
		    ">/$sendpool/incremental.zsend"
	fi

	if datasetexists $streamfs; then
		log_must $ZFS destroy -r $streamfs
	fi
	log_must $ZFS create -o compress=lz4 $sendpool/stream
}
