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