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