# SPDX-License-Identifier: CDDL-1.0
#
# 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 https://opensource.org/licenses/CDDL-1.0.
# 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 2007 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#

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

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

function run_and_verify
{
	typeset user pool
	while getopts "p:u:" opt; do
		case $opt in
		p)
			pool=$OPTARG
			;;
		u)
			user=$OPTARG
			;;
		esac
	done
	shift $(($OPTIND - 1))

	pool=${pool:-$TESTPOOL}
	user=${user:-"root"}
	fullcmd="$1"
	flags="$2"

	if is_illumos; then
		histcmd=$(echo $fullcmd | sed 's=/usr/sbin/==g')
	else
		histcmd=$(echo $fullcmd | sed -E 's=^.*/(zpool|zfs)$=\1=')
	fi

	read -r cmd subcmd _ <<<"$histcmd"

	# If we aren't running zpool or zfs, something is wrong
	[[ $cmd == "zpool" || $cmd == "zfs" ]] || \
	    log_fail "run_and_verify called with \"$cmd ($fullcmd)\""

	# If this is a 'zfs receive' truncate the stdin redirect
	[[ $subcmd == "receive" || $subcmd == "recv" ]] && \
	    histcmd=${histcmd%% <*}

	# Run the command as the specified user, and find the new history.
	zpool history $flags $pool > $OLD_HISTORY 2>/dev/null
	if [[ $user == "root" ]]; then
		log_must_busy eval "$fullcmd"
	else
		log_must_busy user_run $user "$fullcmd"
	fi
	zpool history $flags $pool > $TMP_HISTORY 2>/dev/null
	diff $OLD_HISTORY $TMP_HISTORY | sed -n 's/^> //gp' > $NEW_HISTORY

	# Verify what's common to every case, regardless of zpool history flags.
	grep -q "$histcmd" $NEW_HISTORY || \
	    log_fail "Didn't find \"$histcmd\" in pool history"

	# If 'zpool history' was called without any flags, then we're done.
	[[ -z $flags ]] && return

	# Verify the new history in cases that are more interesting because
	# additional information is logged with -i or -l.

	[[ $flags =~ "i" ]] && log_must verify_$subcmd "$histcmd" "$subcmd" \
	    "$flags"
	[[ $flags =~ "l" ]] && log_must verify_long "$histcmd" "$user" "$flags"
}

function verify_long
{
	typeset cmd=$1
	typeset user=$2
	typeset flags=$3

	[[ $flags =~ "l" ]] || return 1

	typeset uid=$(id -u $user)
	typeset hname=$(hostname)
	if ! is_global_zone; then
		hname=$hname:$(zonename)
	fi

	typeset suffix=""
	if is_linux; then
		suffix=":linux"
	elif is_freebsd; then
		suffix=":freebsd"
	fi

	if ! grep -q "$cmd \[user $uid ($user) on $hname$suffix\]" $NEW_HISTORY; then
		log_note "Couldn't find long information for \"$cmd\""
		return 1
	fi

	return 0
}

function verify_hold
{
	typeset cmd=$1
	typeset subcmd=$2
	typeset flags=$3

	[[ $flags =~ "i" ]] || return 1

	typeset tag _
	read -r _ _ _ tag _ <<<"$cmd"
	typeset fullname=${cmd##* }
	typeset dsname=${fullname%%@*}
	typeset snapname=${fullname##*@}

	# This works whether or not the hold was recursive
	for ds in $(zfs list -r -Ho name -t snapshot $dsname | \
	    grep "@$snapname"); do
		if ! grep -q "$subcmd $ds ([0-9]*) tag=$tag" $NEW_HISTORY; then
			log_note "Didn't find hold on $ds with $tag"
			return 1
		fi
	done

	return 0
}

function verify_release
{
	# hold and release formats only differ by the subcommand name, so
	# simply reuse the hold function.
	verify_hold "$1" "release" "$3"
}

function verify_rollback
{
	typeset cmd=$1
	typeset flags=$3

	[[ $flags =~ "i" ]] || return 1

	typeset fullname=${cmd##* }
	typeset dsname=${fullname%%@*}
	typeset parent_fs=${dsname##*/}
	typeset rb_fs=${dsname}/%rollback
	typeset snapname=${fullname##*@}

	if ! grep -q "clone swap $rb_fs ([0-9]*) parent=$parent_fs" $NEW_HISTORY ; then
		log_note "Didn't find rollback clone swap in pool history"
		return 1
	fi

	if ! grep -q "destroy $rb_fs" $NEW_HISTORY; then
		log_note "Didn't find rollback destroy in pool history"
		return 1
	fi

	return 0
}

function verify_inherit
{
	typeset cmd=$1
	typeset flags=$3

	[[ $flags =~ "i" ]] || return 1

	typeset dsname=${cmd##* }
	typeset prop=${cmd% *}
	prop=${prop##* }

	# This works whether or not the inherit was recursive
	for ds in $(zfs list -r -Ho name -t filesystem $dsname); do
		if ! grep -q "$subcmd $ds ([0-9]*) ${prop}=" $NEW_HISTORY; then
			log_note "Didn't find inherit history for $ds"
			return 1
		fi
	done

	return 0
}

function verify_allow
{
	typeset cmd=$1
	typeset subcmd=$2
	typeset flags=$3

	[[ $flags =~ "i" ]] || return 1
	[[ $subcmd == "allow" ]] && subcmd="update"
	[[ $subcmd == "unallow" ]] && subcmd="remove"
	typeset is_set lflag dflag dsname gname gid uname uid opt str code tmp

	#
	# Here, we determine three things:
	# - Whether we're operating on a set or an individual permission (which
	#   dictates the case of the first character in the code)
	# - The name of the dataset we're operating on.
	# - Whether the operation applies locally or to descendent datasets (or
	#   both)
	#
	echo $cmd | awk '$(NF - 1) ~ /@/ {exit 1}' || is_set=1
	dsname=${cmd##* }
	[[ $cmd =~ "-l " ]] && lflag=1
	[[ $cmd =~ "-d " ]] && dflag=1
	if [[ -z $lflag && -z $dflag ]]; then
		lflag=1
		dflag=1
	fi

	#
	# For each of the five cases below, the operation is essentially the
	# same. First, use the command passed in to determine what the code at
	# the end of the pool history will be. The specifics of the code are
	# described in a block comment at the top of dsl_deleg.c. Once that's
	# been assembled, check for its presence in the history, and return
	# success or failure accordingly.
	#
	if [[ $cmd =~ "-s " ]]; then
		str="s-\$@"
		[[ -n $is_set ]] && str="S-\$@"
		tmp=${cmd#*@}
		code="$str${tmp% *}"
		if ! grep -q "permission $subcmd $dsname ([0-9]*) $code" $NEW_HISTORY; then
			 log_note "Couldn't find $code in $NEW_HISTORY"
			 return 1
		 fi
	elif [[ $cmd =~ "-c " ]]; then
		str="c-\$"
		[[ -n $is_set ]] && str="C-\$"
		tmp=${cmd#*-c}
		code="$str${tmp% *}"
		if ! grep "permission $subcmd $dsname ([0-9]*) $code" $NEW_HISTORY; then
			 log_note "Couldn't find $code in $NEW_HISTORY"
			 return 1
		fi
	elif [[ $cmd =~ "-u " ]]; then
		str="u"
		[[ -n $is_set ]] && str="U"
		tmp=${cmd##*-u }
		read -r _ opt _ <<<"$opt"
		uid=$(id -u ${tmp%% *})
		if [[ -n $lflag ]]; then
			code="${str}l\$$uid $opt"
			if grep -q "permission $subcmd $dsname ([0-9]*) $code" $NEW_HISTORY]; then
				 log_note "Couldn't find $code in $NEW_HISTORY"
				 return 1
			fi
		fi
		if [[ -n $dflag ]]; then
			code="${str}d\$$uid $opt"
			if grep -q "permission $subcmd $dsname ([0-9]*) $code" $NEW_HISTORY]; then
				 log_note "Couldn't find $code in $NEW_HISTORY"
				 return 1
			fi
		fi
	elif [[ $cmd =~ "-g " ]]; then
		str="g"
		[[ -n $is_set ]] && str="G"
		tmp=${cmd##*-g }
		read -r _ opt _ <<<"$opt"
		gid=$(awk -F: "/^${tmp%% *}:/ {print \$3}" /etc/group)
		if [[ -n $lflag ]]; then
			code="${str}l\$$gid $opt"
			if ! grep -q "permission $subcmd $dsname ([0-9]*) $code" $NEW_HISTORY; then
				 log_note "Couldn't find $code in $NEW_HISTORY"
				 return 1
			fi
		fi
		if [[ -n $dflag ]]; then
			code="${str}d\$$gid $opt"
			if ! grep -q "permission $subcmd $dsname ([0-9]*) $code" $NEW_HISTORY; then
				 log_note "Couldn't find $code in $NEW_HISTORY"
				 return 1
			fi
		fi
	elif [[ $cmd =~ "-e " ]]; then
		str="e"
		[[ -n $is_set ]] && str="E"
		opt=${cmd##*-e }
		opt=${opt%% *}
		if [[ -n $lflag ]]; then
			code="${str}l\$ $opt"
			if ! grep -q "permission $subcmd $dsname ([0-9]*) $code" $NEW_HISTORY; then
				 log_note "Couldn't find $code in $NEW_HISTORY"
				 return 1
			fi
		fi
		if [[ -n $dflag ]]; then
			code="${str}d\$ $opt"
			if ! grep -q "permission $subcmd $dsname ([0-9]*) $code" $NEW_HISTORY; then
				 log_note "Couldn't find $code in $NEW_HISTORY"
				 return 1
			fi
		fi
	else
		log_note "Can't parse command \"$cmd\""
		return 1
	fi

	return 0
}

function verify_unallow
{
	#
	# The unallow and allow history have the same format, except the former
	# logs "permission removed" and the latter "permission updated" so
	# simply reuse the allow function.
	#
	verify_allow "$1" "unallow" "$3"
}

function verify_destroy
{
	typeset cmd=$1
	typeset flags=$3

	# This function doesn't currently verify the zpool command.
	[[ ${cmd%% *} == "zfs" ]] || return 1
	[[ $flags =~ "i" ]] || return 1

	typeset dsname=${cmd##* }
	[[ $dsname =~ "@" ]] && typeset is_snap=1

	if [[ -n $is_snap ]]; then
		if ! grep -q "ioctl destroy_snaps" $NEW_HISTORY; then
			log_note "Didn't find ioctl while destroying $dsname"
			return 1
		fi
	fi

	# This should be present for datasets and snapshots alike
	if ! grep -q "destroy $dsname" $NEW_HISTORY; then
		log_note "Didn't find \"destroy\" for $dsname"
		return 1
	fi

	return 0
}

function verify_snapshot
{
	typeset cmd=$1
	typeset flags=$3

	[[ $flags =~ "i" ]] || return 1

	typeset fullname=${cmd##* }
	typeset dsname=${fullname%%@*}
	typeset snapname=${fullname##*@}

	if ! grep -q "\[txg:[0-9]*\] $subcmd $fullname ([0-9]*)" $NEW_HISTORY; then
		log_note "Didn't find snapshot command for $fullname"
		return 1
	fi

	# This works whether or not the snapshot was recursive
	for ds in $(zfs list -r -Ho name -t snapshot $dsname | \
	    grep "@$snapname"); do
		if ! grep -q "^[ ]* $ds$" $NEW_HISTORY; then
			log_note "Didn't find \"ioctl snapshot\" for $ds"
			return 1
		fi
	done

	return 0
}
