1#! /usr/local/bin/ksh93 -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 2009 Sun Microsystems, Inc.  All rights reserved.
25# Use is subject to license terms.
26
27. $STF_SUITE/include/libtest.kshlib
28. $STF_SUITE/tests/inheritance/inherit.kshlib
29
30###############################################################################
31#
32# __stc_assertion_start
33#
34# ID: inherit_001_pos
35#
36# DESCRIPTION:
37# Test that properties are correctly inherited using 'zfs set',
38# 'zfs inherit' and 'zfs inherit -r'.
39#
40# STRATEGY:
41# 1) Read a configX.cfg file and create the specified datasets
42# 2) Read a stateX.cfg file and execute the commands within it
43# and verify that the properties have the correct values
44# 3) Repeat steps 1-2 for each configX and stateX files found.
45#
46# TESTABILITY: explicit
47#
48# TEST_AUTOMATION_LEVEL: automated
49#
50# CODING_STATUS: COMPLETED (2005-07-04)
51#
52# __stc_assertion_end
53#
54################################################################################
55
56verify_runnable "global"
57
58log_assert "Test properties are inherited correctly"
59
60#
61# Simple function to create specified datasets.
62#
63function create_dataset #name type disks
64{
65        typeset dataset=$1
66        typeset type=$2
67        typeset disks=$3
68
69        if [[ $type == "POOL" ]]; then
70                create_pool "$dataset" "$disks"
71        elif [[ $type == "CTR" ]]; then
72                log_must $ZFS create $dataset
73		log_must $ZFS set canmount=off $dataset
74        elif [[ $type == "FS" ]]; then
75                log_must $ZFS create $dataset
76        else
77                log_fail "ERROR: Unrecognised type $type"
78        fi
79
80        list="$list $dataset"
81}
82
83#
84# Function to walk through all the properties in a
85# dataset, setting them to a 'local' value if required.
86#
87function init_props #dataset init_code
88{
89        typeset dataset=$1
90        typeset init_code=$2
91	typeset new_val
92	typeset -i i=0
93
94	#
95	# Though the effect of '-' and 'default' is the same we
96	# call them out via a log_note to aid in debugging the
97	# config files 
98	#
99        if [[ $init_code == "-" ]]; then
100                log_note "Leaving properties for $dataset unchanged."
101		[[ $def_recordsize == 0 ]] && \
102			update_recordsize $dataset $init_code
103                return;
104        elif [[ $init_code == "default" ]]; then
105                log_note "Leaving properties for $dataset at default values."
106		[[ $def_recordsize == 0 ]] && \
107			update_recordsize $dataset $init_code
108                return;
109        elif [[ $init_code == "local" ]]; then
110                log_note "Setting properties for $dataset to local values."
111		for (( ; i < ${#props[*]}; i += 2 )); do
112			if [[ ${props[i]} == "recordsize" ]]; then
113				update_recordsize $dataset $init_code
114                	else
115				set_n_verify_prop ${props[i]} \
116					${local_val[((i/2))]} $dataset
117                	fi
118                done
119        else
120		log_fail "ERROR: Unrecognised init code $init_code"
121	fi
122}
123
124#
125# We enter this function either to update the recordsize value
126# in the default array, or to update the local value array.
127#
128function update_recordsize { #dataset init_code 
129	typeset dataset=$1
130	typeset init_code=$2
131	typeset idx=0
132	typeset record_val
133	
134	#
135	# First need to find where the recordsize property is
136	# located in the arrays
137	#
138	for (( ; idx < ${#props[*]}; idx += 2 )); do
139		[[ ${props[idx]} == "recordsize" ]] && \
140			break
141	done
142
143	(( idx = idx / 2 ))
144	record_val=`get_prop recordsize $dataset`
145	if [[ $init_code == "-" || \
146		$init_code == "default" ]]; then
147
148		def_val[idx]=$record_val
149		def_recordsize=1
150
151	elif [[ $init_code == "local" ]]; then
152
153		log_must $ZFS set recordsize=$record_val $dataset
154
155		local_val[idx]=$record_val
156	fi
157}
158
159#
160# The mountpoint property is slightly different from other properties and 
161# so is handled here. For all other properties if they are set to a specific 
162# value at a higher level in the data hierarchy (i.e. checksum=on) then that 
163# value propogates down the hierarchy unchanged, with the source field being 
164# set to 'inherited from <higher dataset>'.
165#
166# The mountpoint property is different in that while the value propogates
167# down the hierarchy, the value at each level is determined by a combination 
168# of the top-level value and the current level in the hierarchy. 
169#
170# For example consider the case where we have a pool (called pool1), containing 
171# a dataset (ctr) which in turn contains a filesystem (fs). If we set the 
172# mountpoint of the pool to '/mnt2' then the mountpoints for the dataset and 
173# filesystem are '/mnt2/ctr' and /mnt2/ctr/fs' respectively, with the 'source' 
174# field being set to 'inherited from pool1'. 
175#
176# So at the filesystem level to calculate what our mountpoint property should 
177# be set to we walk back up the hierarchy sampling the mountpoint property at 
178# each level and forming up the expected mountpoint value piece by piece until 
179# we reach the level specified in the 'source' field, which in this example is 
180# the top-level pool.
181#
182function get_mntpt_val #dataset src index
183{
184        typeset dataset=$1
185        typeset src=$2
186        typeset idx=$3
187        typeset new_path=""
188        typeset dset
189	typeset mntpt=""
190
191	if [[ $src == "local" ]]; then
192		mntpt=${local_val[idx]}
193	elif [[ $src == "default" ]]; then
194		mntpt="$ZFSROOT/"$dataset
195	else
196		# Walk back up the hierarchy building up the 
197		# expected mountpoint property value.
198		obj_name=${dataset##*/}
199
200		while [[ $src != $dataset ]]; do
201			dset=${dataset%/*}
202
203			mnt_val=`get_prop mountpoint $dset`
204
205			mod_prop_val=${mnt_val##*/}
206			new_path="/"$mod_prop_val$new_path
207			dataset=$dset
208		done
209
210		mntpt=$new_path"/"$obj_name
211	fi
212	print $mntpt
213}
214
215#
216# Simple function to verify that a property has the
217# expected value.
218#
219function verify_prop_val #property dataset src index
220{
221	typeset dataset=$1
222	typeset prop=$2
223	typeset src=$3
224	typeset idx=$4
225	typeset new_path=""
226	typeset dset
227	typeset exp_val
228	typeset prop_val
229
230	prop_val=`get_prop $prop $dataset`
231
232	# mountpoint property is handled as a special case
233	if [[ $prop == "mountpoint" ]]; then
234		exp_val=`get_mntpt_val $dataset $src $idx`
235	else
236		if [[ $src == "local" ]]; then
237			exp_val=${local_val[idx]}
238		elif [[ $src == "default" ]]; then
239			exp_val=${def_val[idx]}
240		else
241			#
242			# We are inheriting the value from somewhere
243			# up the hierarchy.
244			#
245			exp_val=`get_prop $prop $src`
246		fi
247	fi
248
249	[ "$prop_val" = "$exp_val" ] && return
250
251	# After putback PSARC/2008/231 Apr,09,2008, the default value of
252	# aclinherit has changed to be 'restricted' instead of 'secure',
253	# but the old interface of 'secure' still exist
254	[ "$prop" = "aclinherit" ] && return
255	[ "$exp_val" = "secure" ] && return
256	[ "$prop_val" = "restricted" ] && return
257
258	log_fail "ERROR: Property $prop (source $src index $idx) for $dataset" \
259	    "was [$prop_val]; expected [$exp_val]"
260}
261
262#
263# Function to read the configX.cfg files and create the specified 
264# dataset hierarchy
265#
266function scan_config #config-file
267{
268	typeset config_file=$1
269
270	DISK=${DISKS%% *}
271
272	list=""
273
274        grep "^[^#]" $config_file | {
275                while read name type init ; do
276                        create_dataset $name $type $DISK
277                        init_props $name $init
278                done
279        }
280}
281
282function check_state
283{
284	typeset i=$1
285	typeset j=$2
286	typeset op=$3
287	typeset target=$4
288
289	#
290	# The user can if they wish specify that no operation be performed
291	# (by specifying '-' rather than a command). This is not as 
292	# useless as it sounds as it allows us to verify that the dataset
293	# hierarchy has been set up correctly as specified in the 
294	# configX.cfg file (which includes 'set'ting properties at a higher
295	# level and checking that they propogate down to the lower levels.
296	#
297	# Note in a few places here, we use log_onfail, rather than
298	# log_must - this substantially reduces journal output.
299	#
300	if [[ $op != "-" ]]; then
301		# Unmount the test datasets if they are still mounted.
302		# Most often, they won't be, so discard the output
303		unmount_all_safe > /dev/null 2>&1
304
305		for p in ${props[i]} ${props[((i+1))]}; do
306			log_onfail $ZFS $op $p $target
307		done
308	fi
309	for check_obj in $list; do
310		read init_src final_src
311
312		for p in ${props[i]} ${props[((i+1))]}; do
313			verify_args="$check_obj $p $final_src"
314
315			log_onfail verify_prop_src $check_obj $p $final_src
316			log_onfail verify_prop_val $check_obj $p $final_src $j
317		done
318	done
319}
320
321#
322# Main function. Executes the commands specified in the stateX.cfg
323# files and then verifies that all the properties have the correct
324# values and 'source' fields.
325#
326function scan_state #state-file
327{
328	typeset state_file=$1
329	typeset -i i=0
330	typeset -i j=0
331
332	log_note "Reading state from $state_file"
333	for (( ; i < ${#props[*]}; i += 2, j += 1 )); do
334		grep "^[^#]" $state_file | {
335			while IFS=: read target op; do
336				check_state $i $j "$op" "$target"
337			done
338                }
339        done
340}
341
342
343set -A props "checksum" "" \
344	"compression" "compress" \
345	"atime" "" \
346	"exec" "" \
347        "setuid" "" \
348	"sharenfs" "" \
349	"recordsize" "recsize" \
350	"mountpoint" "" \
351	"snapdir" "" \
352	"aclmode" "" \
353	"aclinherit" "" \
354	"readonly" "rdonly"
355
356# 
357# Note except for the mountpoint default value (which is handled in
358# the routine itself), each property specified in the 'props' array
359# above must have a corresponding entry in the two arrays below.
360# 
361set -A def_val "on" \
362	"off" \
363	"on" \
364	"on" \
365	"on" \
366	"off" \
367	"" \
368	"" \
369	"hidden" \
370	"discard" \
371	"secure" \
372	"off"
373
374set -A local_val "off" "on" "off"  "off" \
375	"off" "on" "" \
376	"$TESTDIR" "visible" "groupmask" "discard" \
377	"off"
378
379log_must $ZPOOL create $TESTPOOL ${DISKS%% *}
380
381# Append the "shareiscsi" property if it is supported
382$ZFS get shareiscsi $TESTPOOL > /dev/null 2>&1
383if [[ $? -eq 0 ]]; then
384	typeset -i i=${#props[*]}
385	props[i]="shareiscsi"
386	props[((i+1))]=""
387	def_val[((i/2))]="off"
388	local_val[((i/2))]="on"
389fi
390
391# Append the "devices" property if it is settable
392$ZFS set devices=off $TESTPOOL
393if [[ $? -eq 0 ]]; then
394	typeset -i i=${#props[*]}
395	props[i]="devices"
396	props[((i+1))]=""
397	def_val[((i/2))]="on"
398	local_val[((i/2))]="off"
399else
400	log_note "Setting devices=off is not supported on this system"
401fi
402
403log_must $ZPOOL destroy $TESTPOOL
404
405#
406# Global flag indicating whether the default record size had been
407# read.
408#
409typeset def_recordsize=0
410
411TDIR=$STF_SUITE/tests/inheritance
412set -A config_files $(ls $TDIR/config*[1-9]*.cfg)
413set -A state_files $(ls $TDIR/state*.cfg)
414
415#
416# Global list of datasets created.
417#
418list=""
419
420if [[ ${#config_files[*]} != ${#state_files[*]} ]]; then
421	log_fail "ERROR: Must have the same number of config files"\
422		"(${#config_files[*]}) and state files ${#state_files[*]}"
423fi
424
425typeset -i fnum=0
426for (( ; fnum < ${#config_files[*]}; fnum += 1 )); do
427	default_cleanup_noexit
428	def_recordsize=0
429
430	log_note "*** Testing configuration ${config_files[fnum]}"
431	scan_config ${config_files[fnum]}
432	scan_state ${state_files[fnum]}
433done
434
435log_pass "Properties correctly inherited as expected"
436