1#! /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 https://opensource.org/licenses/CDDL-1.0.
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#
28# Copyright (c) 2013, 2016 by Delphix. All rights reserved.
29# Copyright (c) 2022 Hewlett Packard Enterprise Development LP.
30#
31
32. $STF_SUITE/include/libtest.shlib
33. $STF_SUITE/tests/functional/inheritance/inherit.kshlib
34
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
47verify_runnable "global"
48
49log_assert "Test properties are inherited correctly"
50
51#
52# Simple function to create specified datasets.
53#
54function create_dataset { #name type disks
55	typeset dataset=$1
56	typeset type=$2
57	typeset disks=$3
58
59	if [[ $type == "POOL" ]]; then
60		create_pool "$dataset" "$disks"
61	elif [[ $type == "CTR" ]]; then
62		log_must zfs create $dataset
63		log_must zfs set canmount=off $dataset
64	elif [[ $type == "FS" ]]; then
65		log_must zfs create $dataset
66	else
67		log_fail "Unrecognised type $type"
68	fi
69
70	list="$list $dataset"
71}
72
73#
74# Function to walk through all the properties in a
75# dataset, setting them to a 'local' value if required.
76#
77function init_props { #dataset init_code
78	typeset dataset=$1
79	typeset init_code=$2
80	typeset dir=$3
81
82	typeset -i i=0
83
84	#
85	# Though the effect of '-' and 'default' is the same we
86	# call them out via a log_note to aid in debugging the
87	# config files
88	#
89	if [[ $init_code == "-" ]]; then
90		log_note "Leaving properties for $dataset unchanged."
91		[[ $def_recordsize == 0 ]] && \
92		    update_recordsize $dataset $init_code
93		return;
94	elif [[ $init_code == "default" ]]; then
95		log_note "Leaving properties for $dataset at default values."
96		[[ $def_recordsize == 0 ]] && \
97		    update_recordsize $dataset $init_code
98		return;
99	elif [[ $init_code == "local" ]]; then
100		log_note "Setting properties for $dataset to local values."
101		while (( i <  ${#prop[*]} )); do
102			if [[ ${prop[i]} == "recordsize" ]]; then
103				update_recordsize $dataset $init_code
104			else
105				if [[ ${prop[i]} == "mountpoint" ]]; then
106					set_n_verify_prop ${prop[i]} \
107					    ${local_val[((i/2))]}.$dir $dataset
108				else
109					set_n_verify_prop ${prop[i]} \
110					    ${local_val[((i/2))]} $dataset
111				fi
112			fi
113
114			((i = i + 2))
115		done
116	else
117		log_fail "Unrecognised init code $init_code"
118	fi
119}
120
121#
122# We enter this function either to update the recordsize value
123# in the default array, or to update the local value array.
124#
125function update_recordsize { #dataset init_code
126	typeset dataset=$1
127	typeset init_code=$2
128	typeset idx=0
129	typeset record_val
130
131	#
132	# First need to find where the recordsize property is
133	# located in the arrays
134	#
135	while (( idx <  ${#prop[*]} )); do
136		[[ ${prop[idx]} == "recordsize" ]] && break
137
138		((idx = idx + 2))
139	done
140
141	((idx = idx / 2))
142	record_val=`get_prop recordsize $dataset`
143	if [[ $init_code == "-" || $init_code == "default" ]]; then
144		def_val[idx]=$record_val
145		def_recordsize=1
146	elif [[ $init_code == "local" ]]; then
147		log_must zfs set recordsize=$record_val $dataset
148		local_val[idx]=$record_val
149	fi
150}
151
152#
153# The mountpoint property is slightly different from other properties and
154# so is handled here. For all other properties if they are set to a specific
155# value at a higher level in the data hierarchy (i.e. checksum=on) then that
156# value propagates down the hierarchy unchanged, with the source field being
157# set to 'inherited from <higher dataset>'.
158#
159# The mountpoint property is different in that while the value propagates
160# down the hierarchy, the value at each level is determined by a combination
161# of the top-level value and the current level in the hierarchy.
162#
163# For example consider the case where we have a pool (called pool1), containing
164# a dataset (ctr) which in turn contains a filesystem (fs). If we set the
165# mountpoint of the pool to '/mnt2' then the mountpoints for the dataset and
166# filesystem are '/mnt2/ctr' and /mnt2/ctr/fs' respectively, with the 'source'
167# field being set to 'inherited from pool1'.
168#
169# So at the filesystem level to calculate what our mountpoint property should
170# be set to we walk back up the hierarchy sampling the mountpoint property at
171# each level and forming up the expected mountpoint value piece by piece until
172# we reach the level specified in the 'source' field, which in this example is
173# the top-level pool.
174#
175function get_mntpt_val #dataset src index
176{
177	typeset dataset=$1
178	typeset src=$2
179	typeset idx=$3
180	typeset new_path=""
181	typeset dset
182	typeset mntpt=""
183
184	if [[ $src == "local" ]]; then
185		# Extract mount points specific to datasets
186		if [[ $dataset == "TESTPOOL" ]]; then
187			mntpt=${local_val[idx]}.1
188		elif [[ $dataset == "TESTPOOL/TESTCTR" ]]; then
189			mntpt=${local_val[idx]}.2
190		else
191			mntpt=${local_val[idx]}.3
192		fi
193	elif [[ $src == "default" ]]; then
194		mntpt="/$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	echo $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 prop=$1
222	typeset dataset=$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	if [[ $prop_val != $exp_val ]]; then
250		# After putback PSARC/2008/231 Apr,09,2008,
251		# the default value of aclinherit has changed to be
252		# 'restricted' instead of 'secure',
253		# but the old interface of 'secure' still exist
254
255		if [[ $prop != "aclinherit" || \
256		    $exp_val != "secure" || \
257		    $prop_val != "restricted" ]]; then
258
259			log_fail "$prop of $dataset is [$prop_val] rather "\
260			    "than [$exp_val]"
261		fi
262	fi
263}
264
265#
266# Function to read the configX.cfg files and create the specified
267# dataset hierarchy
268#
269function scan_config { #config-file
270	typeset config_file=$1
271
272	DISK=${DISKS%% *}
273
274	list=""
275	typeset -i mount_dir=1
276
277	grep "^[^#]" $config_file | {
278		while read name type init ; do
279			create_dataset $name $type $DISK
280			init_props $name $init $mount_dir
281			((mount_dir = mount_dir + 1))
282		done
283	}
284}
285
286#
287# Function to check an exit flag, calling log_fail if that exit flag
288# is non-zero. Can be used from code that runs in a tight loop, which
289# would otherwise result in a lot of journal output.
290#
291function check_failure { # int status, error message to use
292
293	typeset -i exit_flag=$1
294	error_message=$2
295
296	if [[ $exit_flag -ne 0 ]]; then
297		log_fail "$error_message"
298	fi
299}
300
301
302#
303# Main function. Executes the commands specified in the stateX.cfg
304# files and then verifies that all the properties have the correct
305# values and 'source' fields.
306#
307function scan_state { #state-file
308	typeset state_file=$1
309	typeset -i i=0
310	typeset -i j=0
311
312	log_note "Reading state from $state_file"
313
314	while ((i <  ${#prop[*]})); do
315		grep "^[^#]" $state_file | {
316			while IFS=: read target op; do
317				#
318				# The user can if they wish specify that no
319				# operation be performed (by specifying '-'
320				# rather than a command). This is not as
321				# useless as it sounds as it allows us to
322				# verify that the dataset hierarchy has been
323				# set up correctly as specified in the
324				# configX.cfg file (which includes 'set'ting
325				# properties at a higher level and checking
326				# that they propagate down to the lower levels.
327				#
328				# Note in a few places here, we use
329				# check_failure, rather than log_must - this
330				# substantially reduces journal output.
331				#
332				if [[ $op == "-" ]]; then
333					log_note "No operation specified"
334				else
335					export __ZFS_POOL_RESTRICT="TESTPOOL"
336					log_must_busy zfs unmount -a
337					unset __ZFS_POOL_RESTRICT
338
339					for p in ${prop[i]} ${prop[((i+1))]}; do
340						zfs $op $p $target
341						check_failure $? "zfs $op $p $target"
342					done
343				fi
344				for check_obj in $list; do
345					read init_src final_src
346
347					for p in ${prop[i]} ${prop[((i+1))]}; do
348					# check_failure to keep journal small
349						verify_prop_src $check_obj $p \
350						    $final_src
351						check_failure $? "verify" \
352						    "_prop_src $check_obj $p" \
353						    "$final_src"
354
355					# Again, to keep journal size down.
356						verify_prop_val $p $check_obj \
357						    $final_src $j
358						check_failure $? "verify" \
359						    "_prop_val $check_obj $p" \
360						    "$final_src"
361					done
362				done
363			done
364		}
365		((i = i + 2))
366		((j = j + 1))
367	done
368}
369
370#
371# Note that we keep this list relatively short so that this test doesn't
372# time out (after taking more than 10 minutes).
373#
374set -A prop "checksum" "" \
375	"compression" "" \
376	"atime" "" \
377	"sharenfs" "" \
378	"recordsize" "recsize" \
379	"snapdir" "" \
380	"readonly" "" \
381	"redundant_metadata" ""
382
383#
384# Note except for the mountpoint default value (which is handled in
385# the routine itself), each property specified in the 'prop' array
386# above must have a corresponding entry in the two arrays below.
387#
388
389set -A def_val "on" "on" "on" \
390	"off" "" \
391	"hidden" \
392	"off" \
393	"all"
394
395set -A local_val "off" "off" "off" \
396	"on" "" \
397	"visible" \
398	"off" \
399	"none"
400
401#
402# Add system specific values
403#
404if is_linux; then
405	prop+=("acltype" "")
406	def_val+=("off")
407	local_val+=("off")
408else
409	prop+=("aclmode" "")
410	def_val+=("discard")
411	local_val+=("groupmask")
412fi
413if is_illumos; then
414	prop+=("mountpoint" "")
415	def_val+=("")
416	local_val+=("$TESTDIR")
417fi
418
419#
420# Global flag indicating whether the default record size had been
421# read.
422#
423typeset def_recordsize=0
424
425set -A config_files $(ls $STF_SUITE/tests/functional/inheritance/config*[1-9]*.cfg)
426set -A state_files $(ls $STF_SUITE/tests/functional/inheritance/state*.cfg)
427
428#
429# Global list of datasets created.
430#
431list=""
432
433typeset -i k=0
434
435if [[ ${#config_files[*]} != ${#state_files[*]} ]]; then
436	log_fail "Must have the same number of config files " \
437	    " (${#config_files[*]}) and state files ${#state_files[*]}"
438fi
439
440while ((k < ${#config_files[*]})); do
441	default_cleanup_noexit
442	def_recordsize=0
443
444	log_note "Testing configuration ${config_files[k]}"
445
446	scan_config ${config_files[k]}
447	scan_state ${state_files[k]}
448
449	((k = k + 1))
450done
451
452log_pass "Properties correctly inherited as expected"
453