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