#!/usr/bin/ksh -p
#
# 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 2008 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#

#
# Copyright (c) 2012, 2016 by Delphix. All rights reserved.
# Copyright 2016 Nexenta Systems, Inc.
#


. $STF_SUITE/tests/functional/acl/acl_common.kshlib

# DESCRIPTION:
# Verify chmod have correct behaviour on directories and files when
# filesystem has the different aclmode setting
#
# STRATEGY:
# 1. Loop super user and non-super user to run the test case.
# 2. Create basedir and a set of subdirectores and files within it.
# 3. Separately chmod basedir with different aclmode options,
#    combine with the variable setting of aclmode:
#    "discard", "groupmask", or "passthrough".
# 4. Verify each directories and files have the correct access control
#    capability.

verify_runnable "both"

function cleanup
{
	(( ${#cwd} != 0 )) && cd $cwd

	[[ -f $TARFILE ]] && log_must rm -f $TARFILE
	[[ -d $basedir ]] && log_must rm -rf $basedir
}

log_assert "Verify chmod have correct behaviour to directory and file when" \
    "filesystem has the different aclmode setting"
log_onexit cleanup

set -A aclmode_flag "discard" "groupmask" "passthrough"

set -A ace_prefix \
    "user:$ZFS_ACL_OTHER1" \
    "user:$ZFS_ACL_OTHER2" \
    "group:$ZFS_ACL_STAFF_GROUP" \
    "group:$ZFS_ACL_OTHER_GROUP"

set -A argv "000" "444" "644" "777" "755" "231" "562" "413"

set -A ace_file_preset \
    "read_data" \
    "write_data" \
    "append_data" \
    "execute" \
    "read_data/write_data" \
    "read_data/write_data/append_data" \
    "write_data/append_data" \
    "read_data/execute" \
    "write_data/append_data/execute" \
    "read_data/write_data/append_data/execute"

# Define the base directory and file
basedir=$TESTDIR/basedir;  ofile=$basedir/ofile; odir=$basedir/odir
nfile=$basedir/nfile; ndir=$basedir/ndir

TARFILE=$TESTDIR/tarfile

# Verify all the node have expected correct access control
allnodes="$nfile $ndir"

# According to the original bits, the input ACE access and ACE type, return the
# expect bits after 'chmod A0{+|=}'.
#
# $1 isdir indicate if the target is a directory
# $2 bits which was make up of three bit 'rwx'
# $3 bits_limit which was make up of three bit 'rwx'
# $4 ACE access which is read_data, write_data or execute
# $5 ctrl which is to determine allow or deny according to owner/group bit
function cal_bits # isdir bits bits_limit acl_access ctrl
{
	typeset -i isdir=$1
	typeset -i bits=$2
	typeset -i bits_limit=$3
	typeset acl_access=$4
	typeset -i ctrl=${5:-0}
	typeset flagr=0 flagw=0 flagx=0
	typeset tmpstr

	if (( ctrl == 0 )); then
		if (( (( bits & 4 )) != 0 )); then
			flagr=1
		fi
		if (( (( bits & 2 )) != 0 )); then
			flagw=1
		fi
		if (( (( bits & 1 )) != 0 )); then
			flagx=1
		fi
	else
		# Determine ACE as per owner/group bit
		flagr=1
		flagw=1
		flagx=1

		if (( ((bits & 4)) != 0 )) && \
			(( ((bits_limit & 4)) != 0 )); then
			flagr=0
		fi
		if (( ((bits & 2)) != 0 )) && \
			(( ((bits_limit & 2)) != 0 )); then
			flagw=0
		fi
		if (( ((bits & 1)) != 0 )) && \
			(( ((bits_limit & 1)) != 0 )); then
			flagx=0
		fi
	fi

	if ((flagr != 0)); then
		if [[ $acl_access == *"read_data"* ]]; then
			if ((isdir != 0)); then
				tmpstr=${tmpstr}/list_directory
			fi
			tmpstr=${tmpstr}/read_data
		fi
	fi

	if ((flagw != 0)); then
		if [[ $acl_access == *"write_data"* ]]; then
			if ((isdir != 0)); then
				tmpstr=${tmpstr}/add_file
			fi
			tmpstr=${tmpstr}/write_data
		fi
		if [[ $acl_access == *"append_data"* ]]; then
			if ((isdir != 0)); then
				tmpstr=${tmpstr}/add_subdirectory
			fi
			tmpstr=${tmpstr}/append_data
		fi
	fi

	if ((flagx != 0)); then
		if [[ $acl_access == *"execute"* ]]; then
			tmpstr=${tmpstr}/execute
		fi
	fi

	tmpstr=${tmpstr#/}

	echo "$tmpstr"
}

#
# To translate an ace if the node is dir
#
# $1 isdir indicate if the target is a directory
# $2 acl to be translated
#
function translate_acl # isdir acl
{
	typeset -i isdir=$1
	typeset acl=$2
	typeset who prefix acltemp action

	if ((isdir != 0)); then
		who=${acl%%:*}
		prefix=$who
		acltemp=${acl#*:}
		acltemp=${acltemp%%:*}
		prefix=$prefix:$acltemp
		action=${acl##*:}
		acl=$prefix:$(cal_bits $isdir 7 7 $acl 0):$action
	fi
	echo "$acl"
}

#
# To verify if a new ACL is generated as result of
# chmod operation.
#
# $1 bit indicates whether owner/group bit
# $2 newmode indicates the mode changed using chmod
# $3 isdir indicate if the target is a directory
#
function check_new_acl # bit newmode isdir
{
	typeset bits=$1
	typeset mode=$2
	typeset -i isdir=$3
	typeset new_acl
	typeset gbit
	typeset ebit
	typeset str=":"
	typeset dc=""

	gbit=${mode:1:1}
	ebit=${mode:2:1}
	if (( ((bits & 4)) == 0 )); then
		if (( ((gbit & 4)) != 0 || \
		    ((ebit & 4)) != 0 )); then
			if ((isdir == 0)); then
				new_acl=${new_acl}${str}read_data
			else
				new_acl=${new_acl}${str}list_directory/read_data
			fi
			str="/"
		fi
	fi
	if (( ((bits & 2)) == 0 )); then
		if (( ((gbit & 2)) != 0 || \
		    ((ebit & 2)) != 0 )); then
			if ((isdir == 0)); then
				new_acl=${new_acl}${str}write_data/append_data
			else
				new_acl=${new_acl}${str}add_file/write_data/
				new_acl=${new_acl}add_subdirectory/append_data
				dc="/delete_child"
			fi
			str="/"
		fi
	fi
	if (( ((bits & 1)) == 0 )); then
		if (( ((gbit & 1)) != 0 || \
		    ((ebit & 1)) != 0 )); then
				new_acl=${new_acl}${str}execute
		fi
	fi
	new_acl=${new_acl}${dc}
	echo "$new_acl"
}

function build_new_acl # newmode isdir
{
	typeset newmode=$1
	typeset isdir=$2
	typeset expect
	if ((flag == 0)); then
		prefix="owner@"
		bit=${newmode:0:1}
		status=$(check_new_acl $bit $newmode $isdir)

	else
		prefix="group@"
		bit=${newmode:1:1}
		status=$(check_new_acl $bit $newmode $isdir)
	fi
	expect=$prefix$status:deny
	echo $expect
}

# According to inherited flag, verify subdirectories and files within it has
# correct inherited access control.
function verify_aclmode # <aclmode> <node> <newmode>
{
	# Define the nodes which will be affected by inherit.
	typeset aclmode=$1
	typeset node=$2
	typeset newmode=$3

	# count: the ACE item to fetch
	# passcnt: counter, if it achieves to maxnumber,
	#	then no additional ACE should apply.

	typeset -i count=0 passcnt=0
	typeset -i bits=0 obits=0 bits_owner=0 isdir=0
	typeset -i total_acl
	typeset -i acl_count=$(count_ACE $node)

	((total_acl = maxnumber + 3))

	if [[ -d $node ]]; then
		((isdir = 1))
	fi

	((i = maxnumber - 1))
	count=0
	passcnt=0
	flag=0
	while ((i >= 0)); do
		expect1=${acls[$i]}
		passthrough=0
		#
		# aclmode=passthrough,
		# no changes will be made to the ACL other than
		# generating the necessary ACL entries to represent
		# the new mode of the file or directory.
		#
		# aclmode=discard,
		# delete all ACL entries that don't represent
		# the mode of the file.
		#
		# aclmode=groupmask,
		# reduce user or group permissions.  The permissions are
		# reduced, such that they are no greater than the group
		# permission bits, unless it is a user entry that has the
		# same UID as the owner of the file or directory.
		# Then, the ACL permissions are reduced so that they are
		# no greater than owner permission bits.
		#

		case $aclmode in
		passthrough)
			if ((acl_count > total_acl)); then
				expect1=$(build_new_acl $newmode $isdir)
				flag=1
				((total_acl = total_acl + 1))
				((i = i + 1))
			else
				passthrough=1
				expect1=$(translate_acl $isdir $expect1)
			fi
			;;
		groupmask)
			if ((acl_count > total_acl)); then
				expect1=$(build_new_acl $newmode $isdir)
				flag=1
				((total_acl = total_acl + 1))
				((i = i + 1))
			elif [[ $expect1 == *":allow"* ]]; then
				who=${expect1%%:*}
				aclaction=${expect1##*:}
				prefix=$who
				acltemp=""
				reduce=0
				# To determine the mask bits
				# according to the entry type.
				#
				case $who in
				owner@)
					pos=0
					;;
				group@)
					pos=1
					;;
				everyone@)
					pos=2
					;;
				user)
					acltemp=${expect1#*:}
					acltemp=${acltemp%%:*}
					owner=$(get_owner $node)
					group=$(get_group $node)
					if [[ $acltemp == $owner ]]; then
						pos=0
					else
						pos=1
					fi
					prefix=$prefix:$acltemp
					;;
				group)
					acltemp=${expect1#*:}
					acltemp=${acltemp%%:*}
					pos=1
					prefix=$prefix:$acltemp
					reduce=1
					;;
				esac

				obits=${newmode:$pos:1}
				((bits = $obits))
				# permission should be no greater than the
				# group permission bits
				if ((reduce != 0)); then
					((bits &= ${newmode:1:1}))
					# The ACL permissions are reduced so
					# that they are no greater than owner
					# permission bits.
					((bits_owner = ${newmode:0:1}))
					((bits &= $bits_owner))
				fi

				if ((bits < obits)) && [[ -n $acltemp ]]; then
					expect2=$prefix:
					new_bit=$(cal_bits $isdir $obits \
					    $bits_owner $expect1 0)
					expect2=${expect2}${new_bit}:allow
				else
					expect2=$prefix:
					new_bit=$(cal_bits $isdir $obits \
					    $obits $expect1 0)
					expect2=${expect2}${new_bit}:allow
				fi

				priv=$(cal_bits $isdir $obits $bits_owner \
				    $expect2 0)
				expect1=$prefix:$priv:$aclaction
			else
				expect1=$(translate_acl $isdir $expect1)
			fi
			;;
		discard)
			passcnt=maxnumber
			break
			;;
		esac

		# Get the first ACE to do comparison
		aclcur=$(get_ACE $node $count)
		aclcur=${aclcur#$count:}
		if [[ -n $expect1 && $expect1 != $aclcur ]]; then
			ls -vd $node
			log_fail "$aclmode $i #$count " \
				"ACE: $aclcur, expect to be " \
				"$expect1"
		fi
		((count = count + 1))
		((i = i - 1))
	done

	#
	# If there's no any ACE be checked, it should be identify as
	# an normal file/dir, verify it.
	#
	if ((passcnt == maxnumber)); then
		if [[ -d $node ]]; then
			compare_acls $node $odir
		elif [[	-f $node ]]; then
			compare_acls $node $ofile
		fi

		if [[ $? -ne 0 ]]; then
			ls -vd $node
			log_fail "Unexpect acl: $node, $aclmode ($newmode)"
		fi
	fi
}



typeset -i maxnumber=0
typeset acl
typeset target
typeset -i passthrough=0
typeset -i flag=0

cd $TESTDIR
for mode in "${aclmode_flag[@]}"; do
	log_must zfs set aclmode=$mode $TESTPOOL/$TESTFS

	for user in root $ZFS_ACL_STAFF1; do
		log_must set_cur_usr $user

		log_must usr_exec mkdir $basedir

		log_must usr_exec mkdir $odir
		log_must usr_exec touch $ofile
		log_must usr_exec mkdir $ndir
		log_must usr_exec touch $nfile

		for obj in $allnodes; do
			maxnumber=0
			for preset in "${ace_file_preset[@]}"; do
				for prefix in "${ace_prefix[@]}"; do
					acl=$prefix:$preset

					case $((maxnumber % 2)) in
					0)
						acl=$acl:deny
						;;
					1)
						acl=$acl:allow
						;;
					esac

					log_must usr_exec chmod A+$acl $obj
					acls[$maxnumber]=$acl

					((maxnumber = maxnumber + 1))
				done
			done
			# Archive the file and directory
			log_must tar cpf@ $TARFILE ${basedir#$TESTDIR/}

			if [[ -d $obj ]]; then
				target=$odir
			elif [[ -f $obj ]]; then
				target=$ofile
			fi
			for newmode in "${argv[@]}"; do
				log_must usr_exec chmod $newmode $obj
				log_must usr_exec chmod $newmode $target
				log_must verify_aclmode $mode $obj $newmode
				log_must tar xpf@ $TARFILE
			done
		done

		log_must usr_exec rm -rf $basedir $TARFILE
	done
done

log_pass "Verify chmod behaviour co-op with aclmode setting passed"
