1#!/usr/bin/ksh -p
2#
3# CDDL HEADER START
4#
5# The contents of this file are subject to the terms of the
6# Common Development and Distribution License (the "License").
7# You may not use this file except in compliance with the License.
8#
9# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10# or http://www.opensolaris.org/os/licensing.
11# See the License for the specific language governing permissions
12# and limitations under the License.
13#
14# When distributing Covered Code, include this CDDL HEADER in each
15# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16# If applicable, add the following below this CDDL HEADER, with the
17# fields enclosed by brackets "[]" replaced with your own identifying
18# information: Portions Copyright [yyyy] [name of copyright owner]
19#
20# CDDL HEADER END
21#
22
23#
24# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
25# Use is subject to license terms.
26#
27
28#
29# Copyright (c) 2012 by Delphix. All rights reserved.
30#
31
32
33. $STF_SUITE/tests/functional/acl/acl_common.kshlib
34
35#
36# DESCRIPTION:
37#	Verify chmod have correct behaviour on directories and files when
38#	filesystem has the different aclmode setting
39#
40# STRATEGY:
41#	1. Loop super user and non-super user to run the test case.
42#	2. Create basedir and a set of subdirectores and files within it.
43#	3. Separately chmod basedir with different aclmode options,
44#	   combine with the variable setting of aclmode:
45#	   "discard", "groupmask", or "passthrough".
46#	4. Verify each directories and files have the correct access control
47#	   capability.
48#
49
50verify_runnable "both"
51
52function cleanup
53{
54	# Cleanup tarfile & basedir.
55
56	(( ${#cwd} != 0 )) && cd $cwd
57
58	if [[ -f $TARFILE ]]; then
59		log_must $RM -f $TARFILE
60	fi
61
62	if [[ -d $basedir ]]; then
63		log_must $RM -rf $basedir
64	fi
65}
66
67log_assert "Verify chmod have correct behaviour to directory and file when " \
68	"filesystem has the different aclmode setting."
69log_onexit cleanup
70
71# Define aclmode flag
72set -A aclmode_flag discard groupmask passthrough
73
74set -A ace_prefix "user:$ZFS_ACL_OTHER1" \
75		"user:$ZFS_ACL_OTHER2" \
76		"group:$ZFS_ACL_STAFF_GROUP" \
77		"group:$ZFS_ACL_OTHER_GROUP"
78
79set -A argv  "000" "444" "644" "777" "755" "231" "562" "413"
80
81set -A ace_file_preset "read_data" \
82		"write_data" \
83		"append_data" \
84		"execute" \
85		"read_data/write_data" \
86		"read_data/write_data/append_data" \
87		"write_data/append_data" \
88		"read_data/execute" \
89		"write_data/append_data/execute" \
90		"read_data/write_data/append_data/execute"
91
92# Defile the based directory and file
93basedir=$TESTDIR/basedir;  ofile=$basedir/ofile; odir=$basedir/odir
94nfile=$basedir/nfile; ndir=$basedir/ndir
95
96TARFILE=$TESTDIR/tarfile
97
98# Verify all the node have expected correct access control
99allnodes="$nfile $ndir"
100
101#
102# According to the original bits, the input ACE access and ACE type, return the
103# expect bits after 'chmod A0{+|=}'.
104#
105# $1 isdir indicate if the target is a directory
106# $2 bits which was make up of three bit 'rwx'
107# $3 bits_limit which was make up of three bit 'rwx'
108# $4 ACE access which is read_data, write_data or execute
109# $5 ctrl which is to determine allow or deny according to owner/group bit
110#
111function cal_bits # isdir bits bits_limit acl_access ctrl
112{
113	typeset -i isdir=$1
114	typeset -i bits=$2
115	typeset -i bits_limit=$3
116	typeset acl_access=$4
117	typeset -i ctrl=${5:-0}
118	typeset flagr=0 flagw=0 flagx=0
119	typeset tmpstr
120
121	if (( ctrl == 0 )); then
122		if (( (( bits & 4 )) != 0 )); then
123			flagr=1
124		fi
125		if (( (( bits & 2 )) != 0 )); then
126			flagw=1
127		fi
128		if (( (( bits & 1 )) != 0 )); then
129			flagx=1
130		fi
131	else
132		#Determine ACE as per owner/group bit
133		flagr=1
134		flagw=1
135		flagx=1
136
137		if (( ((bits & 4)) != 0 )) && \
138			(( ((bits_limit & 4)) != 0 )); then
139			flagr=0
140		fi
141		if (( ((bits & 2)) != 0 )) && \
142			(( ((bits_limit & 2)) != 0 )); then
143			flagw=0
144		fi
145		if (( ((bits & 1)) != 0 )) && \
146			(( ((bits_limit & 1)) != 0 )); then
147			flagx=0
148		fi
149	fi
150	if ((flagr != 0)); then
151		if [[ $acl_access == *"read_data"* ]]; then
152			if [[ $acl_access == *"allow"*  && $passthrough == 0 ]]; then
153					tmpstr=${tmpstr}
154			else
155				if ((isdir == 0)); then
156					tmpstr=${tmpstr}/read_data
157				else
158					tmpstr=${tmpstr}/list_directory/read_data
159				fi
160			fi
161		fi
162	fi
163
164	if ((flagw != 0)); then
165		if [[ $acl_access == *"allow"* && $passthrough == 0 ]]; then
166			tmpstr=${tmpstr}
167		else
168			if [[ $acl_access == *"write_data"* ]]; then
169				if ((isdir == 0)); then
170					tmpstr=${tmpstr}/write_data
171				else
172					tmpstr=${tmpstr}/add_file/write_data
173				fi
174			fi
175			if [[ $acl_access == *"append_data"* ]]; then
176				if ((isdir == 0)); then
177					tmpstr=${tmpstr}/append_data
178				else
179					tmpstr=${tmpstr}/add_subdirectory/append_data
180				fi
181			fi
182		fi
183	fi
184	if ((flagx != 0)); then
185		if [[ $acl_access == *"execute"* ]]; then
186			if [[ $acl_access == *"allow"* && $passthrough == 0 ]]; then
187				tmpstr=${tmpstr}
188			else
189				tmpstr=${tmpstr}/execute
190			fi
191		fi
192	fi
193
194	tmpstr=${tmpstr#/}
195
196	$ECHO "$tmpstr"
197}
198
199#
200# To translate an ace if the node is dir
201#
202# $1 isdir indicate if the target is a directory
203# $2 acl to be translated
204#
205function translate_acl # isdir acl
206{
207	typeset -i isdir=$1
208	typeset acl=$2
209	typeset who prefix acltemp action
210
211	if ((isdir != 0)); then
212		who=${acl%%:*}
213		prefix=$who
214		acltemp=${acl#*:}
215		acltemp=${acltemp%%:*}
216		prefix=$prefix:$acltemp
217		action=${acl##*:}
218		acl=$prefix:$(cal_bits $isdir 7 7 $acl 0):$action
219	fi
220	$ECHO "$acl"
221}
222
223#
224# To verify if a new ACL is generated as result of
225# chmod operation.
226#
227# $1 bit indicates whether owner/group bit
228# $2 newmode indicates the mode changed using chmod
229# $3 isdir indicate if the target is a directory
230#
231function check_new_acl # bit newmode isdir
232{
233	typeset bits=$1
234	typeset mode=$2
235	typeset -i isdir=$3
236	typeset new_acl
237	typeset gbit
238	typeset ebit
239	typeset str=":"
240	gbit=${mode:1:1}
241	ebit=${mode:2:1}
242	if (( ((bits & 4)) == 0 )); then
243		if (( ((gbit & 4)) != 0 || \
244		    ((ebit & 4)) != 0 )); then
245			if ((isdir == 0)); then
246				new_acl=${new_acl}${str}read_data
247			else
248				new_acl=${new_acl}${str}list_directory/read_data
249			fi
250			str="/"
251		fi
252	fi
253	if (( ((bits & 2)) == 0 )); then
254		if (( ((gbit & 2)) != 0 || \
255		    ((ebit & 2)) != 0 )); then
256			if ((isdir == 0)); then
257				new_acl=${new_acl}${str}write_data/append_data
258			else
259				new_acl=${new_acl}${str}add_file/write_data/
260				new_acl=${new_acl}add_subdirectory/append_data
261			fi
262			str="/"
263		fi
264	fi
265	if (( ((bits & 1)) == 0 )); then
266		if (( ((gbit & 1)) != 0 || \
267		    ((ebit & 1)) != 0 )); then
268				new_acl=${new_acl}${str}execute
269		fi
270	fi
271	$ECHO "$new_acl"
272}
273
274function build_new_acl # newmode isdir
275{
276	typeset newmode=$1
277	typeset isdir=$2
278	typeset expect
279	if ((flag == 0)); then
280		prefix="owner@"
281		bit=${newmode:0:1}
282		status=$(check_new_acl $bit $newmode $isdir)
283
284	else
285		prefix="group@"
286		bit=${newmode:1:1}
287		status=$(check_new_acl $bit $newmode $isdir)
288	fi
289	expect=$prefix$status:deny
290	$ECHO $expect
291}
292
293#
294# According to inherited flag, verify subdirectories and files within it has
295# correct inherited access control.
296#
297function verify_aclmode # <aclmode> <node> <newmode>
298{
299	# Define the nodes which will be affected by inherit.
300	typeset aclmode=$1
301	typeset node=$2
302	typeset newmode=$3
303
304	# count: the ACE item to fetch
305	# pass: to mark if the current ACE should apply to the target
306	# passcnt: counter, if it achieves to maxnumber,
307	#	then no additional ACE should apply.
308
309	typeset -i count=0 pass=0 passcnt=0
310	typeset -i bits=0 obits=0 bits_owner=0 isdir=0
311	typeset -i total_acl
312	typeset -i acl_count=$(count_ACE $node)
313
314	((total_acl = maxnumber + 3))
315
316	if [[ -d $node ]]; then
317		((isdir = 1))
318	fi
319
320	((i = maxnumber - 1))
321	count=0
322	passcnt=0
323	flag=0
324	while ((i >= 0)); do
325		pass=0
326		expect1=${acls[$i]}
327		passthrough=0
328		#
329		# aclmode=passthrough,
330		# no changes will be made to the ACL other than
331		# generating the necessary ACL entries to represent
332		# the new mode of the file or directory.
333		#
334		# aclmode=discard,
335		# delete all ACL entries that don't represent
336		# the mode of the file.
337		#
338		# aclmode=groupmask,
339		# reduce user or group permissions.  The permissions are
340		# reduced, such that they are no greater than the group
341		# permission bits, unless it is a user entry that has the
342		# same UID as the owner of the file or directory.
343		# Then, the ACL permissions are reduced so that they are
344		# no greater than owner permission bits.
345		#
346
347		case $aclmode in
348			passthrough)
349				if ((acl_count > total_acl)); then
350					expect1=$(build_new_acl $newmode $isdir)
351					flag=1
352					((total_acl = total_acl + 1))
353					((i = i + 1))
354				else
355					passthrough=1
356					expect1=$(translate_acl $isdir $expect1)
357				fi
358				;;
359			groupmask)
360				if ((acl_count > total_acl)); then
361					expect1=$(build_new_acl $newmode $isdir)
362					flag=1
363					((total_acl = total_acl + 1))
364					((i = i + 1))
365
366				elif [[ $expect1 == *":allow"* ]]; then
367					who=${expect1%%:*}
368					aclaction=${expect1##*:}
369					prefix=$who
370					acltemp=""
371					reduce=0
372					#
373					# To determine the mask bits
374					# according to the entry type.
375					#
376					case $who in
377						owner@)
378							pos=0
379							;;
380						group@)
381							pos=1
382							;;
383						everyone@)
384							pos=2
385							;;
386						user)
387							acltemp=${expect1#*:}
388							acltemp=${acltemp%%:*}
389							owner=$(get_owner $node)
390							group=$(get_group $node)
391							if [[ $acltemp == \
392							    $owner ]]; then
393								pos=0
394							else
395								pos=1
396							fi
397							prefix=$prefix:$acltemp
398							;;
399						group)
400							acltemp=${expect1#*:}
401							acltemp=${acltemp%%:*}
402							pos=1
403							prefix=$prefix:$acltemp
404							reduce=1
405							;;
406					esac
407					obits=${newmode:$pos:1}
408					((bits = $obits))
409					#
410					# permission should be no greater than the
411					# group permission bits
412					#
413					if ((reduce != 0)); then
414						((bits &= ${newmode:1:1}))
415					# The ACL permissions are reduced so
416					# that they are no greater than owner
417					# permission bits.
418
419						((bits_owner = ${newmode:0:1}))
420						((bits &= $bits_owner))
421					fi
422
423					if ((bits < obits)) && \
424					    [[ -n $acltemp ]]; then
425						expect2=$prefix:
426						new_bit=$(cal_bits $isdir $obits $bits_owner $expect1 1)
427						expect2=${expect2}${new_bit}:allow
428					else
429						expect2=$prefix:
430						new_bit=$(cal_bits $isdir $obits $obits $expect1 1)
431						expect2=${expect2}${new_bit}:allow
432					fi
433					priv=$(cal_bits $isdir $obits $bits_owner $expect2 0)
434					expect1=$prefix:$priv:$aclaction
435				else
436					expect1=$(translate_acl $isdir $expect1)
437				fi
438				;;
439			discard)
440				passcnt=maxnumber
441				break
442				;;
443		esac
444
445		if ((pass == 0)) ; then
446			# Get the first ACE to do comparison
447
448			aclcur=$(get_ACE $node $count)
449			aclcur=${aclcur#$count:}
450			if [[ -n $expect1 && $expect1 != $aclcur ]]; then
451				$LS -vd $node
452				log_fail "$aclmode $i #$count " \
453					"ACE: $aclcur, expect to be " \
454					"$expect1"
455			fi
456		((count = count + 1))
457		fi
458		((i = i - 1))
459	done
460
461	#
462	# If there's no any ACE be checked, it should be identify as
463	# an normal file/dir, verify it.
464	#
465	if ((passcnt == maxnumber)); then
466		if [[ -d $node ]]; then
467			compare_acls $node $odir
468		elif [[	-f $node ]]; then
469			compare_acls $node $ofile
470		fi
471
472		if [[ $? -ne 0 ]]; then
473			$LS -vd $node
474			log_fail "Unexpect acl: $node, $aclmode ($newmode)"
475		fi
476	fi
477}
478
479
480
481typeset -i maxnumber=0
482typeset acl
483typeset target
484typeset -i passthrough=0
485typeset -i flag=0
486cwd=$PWD
487cd $TESTDIR
488
489for mode in "${aclmode_flag[@]}"; do
490
491	#
492	# Set different value of aclmode
493	#
494
495	log_must $ZFS set aclmode=$mode $TESTPOOL/$TESTFS
496
497	for user in root $ZFS_ACL_STAFF1; do
498		log_must set_cur_usr $user
499
500		log_must usr_exec $MKDIR $basedir
501
502		log_must usr_exec $MKDIR $odir
503		log_must usr_exec $TOUCH $ofile
504		log_must usr_exec $MKDIR $ndir
505		log_must usr_exec $TOUCH $nfile
506
507		for obj in $allnodes; do
508			maxnumber=0
509			for preset in "${ace_file_preset[@]}"; do
510				for prefix in "${ace_prefix[@]}"; do
511					acl=$prefix:$preset
512
513					case $((maxnumber % 2)) in
514						0)
515							acl=$acl:deny
516							;;
517						1)
518							acl=$acl:allow
519							;;
520					esac
521
522				#
523				# Place on the target should succeed.
524				#
525					log_must usr_exec $CHMOD A+$acl $obj
526					acls[$maxnumber]=$acl
527
528					((maxnumber = maxnumber + 1))
529				done
530			done
531			# Archive the file and directory
532			log_must $TAR cpf@ $TARFILE $basedir
533
534			if [[ -d $obj ]]; then
535				target=$odir
536			elif [[ -f $obj ]]; then
537				target=$ofile
538			fi
539			for newmode in "${argv[@]}"; do
540				log_must usr_exec $CHMOD $newmode $obj
541				log_must usr_exec $CHMOD $newmode $target
542				log_must verify_aclmode $mode $obj $newmode
543
544				# Restore the tar archive
545				log_must $TAR xpf@ $TARFILE
546			done
547		done
548
549		log_must usr_exec $RM -rf $basedir $TARFILE
550	done
551done
552
553log_pass "Verify chmod behaviour co-op with aclmode setting passed."
554