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