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 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#
28# Copyright (c) 2013, 2016 by Delphix. All rights reserved.
29# Copyright 2020 Joyent, Inc.
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 propogates 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 propogates
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			if [[ $dset == $src ]]; then
206				new_path=$mnt_val$new_path
207			else
208				mod_prop_val=${mnt_val##*/}
209				new_path="/"$mod_prop_val$new_path
210			fi
211
212			dataset=$dset
213		done
214
215		mntpt=$new_path"/"$obj_name
216	fi
217	echo $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 prop=$1
227	typeset dataset=$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	if [[ $prop_val != $exp_val ]]; then
255		# After putback PSARC/2008/231 Apr,09,2008,
256		# the default value of aclinherit has changed to be
257		# 'restricted' instead of 'secure',
258		# but the old interface of 'secure' still exist
259
260		if [[ $prop != "aclinherit" || \
261		    $exp_val != "secure" || \
262		    $prop_val != "restricted" ]]; then
263
264			log_fail "$prop of $dataset is [$prop_val] rather "\
265			    "than [$exp_val]"
266		fi
267	fi
268}
269
270#
271# Function to read the configX.cfg files and create the specified
272# dataset hierarchy
273#
274function scan_config { #config-file
275	typeset config_file=$1
276
277	DISK=${DISKS%% *}
278
279	list=""
280	typeset -i mount_dir=1
281
282	grep "^[^#]" $config_file | {
283		while read name type init ; do
284			create_dataset $name $type $DISK
285			init_props $name $init $mount_dir
286			((mount_dir = mount_dir + 1))
287		done
288	}
289}
290
291#
292# Function to check an exit flag, calling log_fail if that exit flag
293# is non-zero. Can be used from code that runs in a tight loop, which
294# would otherwise result in a lot of journal output.
295#
296function check_failure { # int status, error message to use
297
298	typeset -i exit_flag=$1
299	error_message=$2
300
301	if [[ $exit_flag -ne 0 ]]; then
302		log_fail "$error_message"
303	fi
304}
305
306
307#
308# Main function. Executes the commands specified in the stateX.cfg
309# files and then verifies that all the properties have the correct
310# values and 'source' fields.
311#
312function scan_state { #state-file
313	typeset state_file=$1
314	typeset -i i=0
315	typeset -i j=0
316
317	log_note "Reading state from $state_file"
318
319	while ((i <  ${#prop[*]})); do
320		grep "^[^#]" $state_file | {
321			while IFS=: read target op; do
322				#
323				# The user can if they wish specify that no
324				# operation be performed (by specifying '-'
325				# rather than a command). This is not as
326				# useless as it sounds as it allows us to
327				# verify that the dataset hierarchy has been
328				# set up correctly as specified in the
329				# configX.cfg file (which includes 'set'ting
330				# properties at a higher level and checking
331				# that they propogate down to the lower levels.
332				#
333				# Note in a few places here, we use
334				# check_failure, rather than log_must - this
335				# substantially reduces journal output.
336				#
337				if [[ $op == "-" ]]; then
338					log_note "No operation specified"
339				else
340					export __ZFS_POOL_RESTRICT="TESTPOOL"
341					log_must zfs unmount -a
342					unset __ZFS_POOL_RESTRICT
343
344					for p in ${prop[i]} ${prop[((i+1))]}; do
345						zfs $op $p $target
346						ret=$?
347						check_failure $ret "zfs $op $p \
348						    $target"
349					done
350				fi
351				for check_obj in $list; do
352					read init_src final_src
353
354					for p in ${prop[i]} ${prop[((i+1))]}; do
355					# check_failure to keep journal small
356						verify_prop_src $check_obj $p \
357						    $final_src
358						ret=$?
359						check_failure $ret "verify" \
360						    "_prop_src $check_obj $p" \
361						    "$final_src"
362
363					# Again, to keep journal size down.
364						verify_prop_val $p $check_obj \
365						    $final_src $j
366						ret=$?
367						check_failure $ret "verify" \
368						    "_prop_val $check_obj $p" \
369						    "$final_src"
370					done
371				done
372			done
373		}
374		((i = i + 2))
375		((j = j + 1))
376	done
377}
378
379#
380# Note that we keep this list relatively short so that this test doesn't
381# time out (after taking more than 10 minutes).
382#
383set -A prop "checksum" "" \
384	"compression" "" \
385	"atime" "" \
386	"sharenfs" "" \
387	"recordsize" "recsize" \
388	"mountpoint" "" \
389	"snapdir" "" \
390	"aclmode" "" \
391	"readonly" ""
392
393#
394# Note except for the mountpoint default value (which is handled in
395# the routine itself), each property specified in the 'prop' array
396# above must have a corresponding entry in the two arrays below.
397#
398
399set -A def_val "on" "off" "on" \
400	"off" "" \
401	"" "hidden" "discard" \
402	"off"
403
404set -A local_val "off" "on" "off" \
405	"on" "" \
406	"$TESTDIR" "visible" "groupmask" \
407	"off"
408
409#
410# Global flag indicating whether the default record size had been
411# read.
412#
413typeset def_recordsize=0
414
415set -A config_files $(ls $STF_SUITE/tests/functional/inheritance/config*[1-9]*.cfg)
416set -A state_files $(ls $STF_SUITE/tests/functional/inheritance/state*.cfg)
417
418#
419# Global list of datasets created.
420#
421list=""
422
423typeset -i k=0
424
425if [[ ${#config_files[*]} != ${#state_files[*]} ]]; then
426	log_fail "Must have the same number of config files " \
427	    " (${#config_files[*]}) and state files ${#state_files[*]}"
428fi
429
430while ((k < ${#config_files[*]})); do
431	default_cleanup_noexit
432	def_recordsize=0
433
434	log_note "Testing configuration ${config_files[k]}"
435
436	scan_config ${config_files[k]}
437	scan_state ${state_files[k]}
438
439	((k = k + 1))
440done
441
442log_pass "Properties correctly inherited as expected"
443