1# 2# CDDL HEADER START 3# 4# The contents of this file are subject to the terms of the 5# Common Development and Distribution License (the "License"). 6# You may not use this file except in compliance with the License. 7# 8# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9# or https://opensource.org/licenses/CDDL-1.0. 10# See the License for the specific language governing permissions 11# and limitations under the License. 12# 13# When distributing Covered Code, include this CDDL HEADER in each 14# file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15# If applicable, add the following below this CDDL HEADER, with the 16# fields enclosed by brackets "[]" replaced with your own identifying 17# information: Portions Copyright [yyyy] [name of copyright owner] 18# 19# CDDL HEADER END 20# 21 22# 23# Copyright (c) 2009, Sun Microsystems Inc. All rights reserved. 24# Copyright (c) 2012, 2020, Delphix. All rights reserved. 25# Copyright (c) 2017, Tim Chase. All rights reserved. 26# Copyright (c) 2017, Nexenta Systems Inc. All rights reserved. 27# Copyright (c) 2017, Lawrence Livermore National Security LLC. 28# Copyright (c) 2017, Datto Inc. All rights reserved. 29# Copyright (c) 2017, Open-E Inc. All rights reserved. 30# Copyright (c) 2021, The FreeBSD Foundation. 31# Use is subject to license terms. 32# 33 34. ${STF_SUITE}/include/tunables.cfg 35 36. ${STF_TOOLS}/include/logapi.shlib 37. ${STF_SUITE}/include/math.shlib 38. ${STF_SUITE}/include/blkdev.shlib 39 40# On AlmaLinux 9 we will see $PWD = '.' instead of the full path. This causes 41# some tests to fail. Fix it up here. 42if [ "$PWD" = "." ] ; then 43 PWD="$(readlink -f $PWD)" 44fi 45 46# 47# Apply constrained path when available. This is required since the 48# PATH may have been modified by sudo's secure_path behavior. 49# 50if [ -n "$STF_PATH" ]; then 51 export PATH="$STF_PATH" 52fi 53 54# 55# Generic dot version comparison function 56# 57# Returns success when version $1 is greater than or equal to $2. 58# 59function compare_version_gte 60{ 61 [ "$(printf "$1\n$2" | sort -V | tail -n1)" = "$1" ] 62} 63 64# Linux kernel version comparison function 65# 66# $1 Linux version ("4.10", "2.6.32") or blank for installed Linux version 67# 68# Used for comparison: if [ $(linux_version) -ge $(linux_version "2.6.32") ] 69# 70function linux_version 71{ 72 typeset ver="$1" 73 74 [ -z "$ver" ] && ver=$(uname -r | grep -Eo "^[0-9]+\.[0-9]+\.[0-9]+") 75 76 typeset version major minor _ 77 IFS='.' read -r version major minor _ <<<"$ver" 78 79 [ -z "$version" ] && version=0 80 [ -z "$major" ] && major=0 81 [ -z "$minor" ] && minor=0 82 83 echo $((version * 100000 + major * 1000 + minor)) 84} 85 86# Determine if this is a Linux test system 87# 88# Return 0 if platform Linux, 1 if otherwise 89 90function is_linux 91{ 92 [ "$UNAME" = "Linux" ] 93} 94 95# Determine if this is an illumos test system 96# 97# Return 0 if platform illumos, 1 if otherwise 98function is_illumos 99{ 100 [ "$UNAME" = "illumos" ] 101} 102 103# Determine if this is a FreeBSD test system 104# 105# Return 0 if platform FreeBSD, 1 if otherwise 106 107function is_freebsd 108{ 109 [ "$UNAME" = "FreeBSD" ] 110} 111 112# Determine if this is a 32-bit system 113# 114# Return 0 if platform is 32-bit, 1 if otherwise 115 116function is_32bit 117{ 118 [ $(getconf LONG_BIT) = "32" ] 119} 120 121# Determine if kmemleak is enabled 122# 123# Return 0 if kmemleak is enabled, 1 if otherwise 124 125function is_kmemleak 126{ 127 is_linux && [ -e /sys/kernel/debug/kmemleak ] 128} 129 130# Determine whether a dataset is mounted 131# 132# $1 dataset name 133# $2 filesystem type; optional - defaulted to zfs 134# 135# Return 0 if dataset is mounted; 1 if unmounted; 2 on error 136 137function ismounted 138{ 139 typeset fstype=$2 140 [[ -z $fstype ]] && fstype=zfs 141 typeset out dir name 142 143 case $fstype in 144 zfs) 145 if [[ "$1" == "/"* ]] ; then 146 ! zfs mount | awk -v fs="$1" '$2 == fs {exit 1}' 147 else 148 ! zfs mount | awk -v ds="$1" '$1 == ds {exit 1}' 149 fi 150 ;; 151 ufs|nfs) 152 if is_freebsd; then 153 mount -pt $fstype | while read dev dir _t _flags; do 154 [[ "$1" == "$dev" || "$1" == "$dir" ]] && return 0 155 done 156 else 157 out=$(df -F $fstype $1 2>/dev/null) || return 158 159 dir=${out%%\(*} 160 dir=${dir%% *} 161 name=${out##*\(} 162 name=${name%%\)*} 163 name=${name%% *} 164 165 [[ "$1" == "$dir" || "$1" == "$name" ]] && return 0 166 fi 167 ;; 168 ext*) 169 df -t $fstype $1 > /dev/null 2>&1 170 ;; 171 zvol) 172 if [[ -L "$ZVOL_DEVDIR/$1" ]]; then 173 link=$(readlink -f $ZVOL_DEVDIR/$1) 174 [[ -n "$link" ]] && \ 175 mount | grep -q "^$link" && \ 176 return 0 177 fi 178 ;; 179 *) 180 false 181 ;; 182 esac 183} 184 185# Return 0 if a dataset is mounted; 1 otherwise 186# 187# $1 dataset name 188# $2 filesystem type; optional - defaulted to zfs 189 190function mounted 191{ 192 ismounted $1 $2 193} 194 195# Return 0 if a dataset is unmounted; 1 otherwise 196# 197# $1 dataset name 198# $2 filesystem type; optional - defaulted to zfs 199 200function unmounted 201{ 202 ! ismounted $1 $2 203} 204 205function default_setup 206{ 207 default_setup_noexit "$@" 208 209 log_pass 210} 211 212function default_setup_no_mountpoint 213{ 214 default_setup_noexit "$1" "$2" "$3" "yes" 215 216 log_pass 217} 218 219# 220# Given a list of disks, setup storage pools and datasets. 221# 222function default_setup_noexit 223{ 224 typeset disklist=$1 225 typeset container=$2 226 typeset volume=$3 227 typeset no_mountpoint=$4 228 log_note begin default_setup_noexit 229 230 if is_global_zone; then 231 if poolexists $TESTPOOL ; then 232 destroy_pool $TESTPOOL 233 fi 234 [[ -d /$TESTPOOL ]] && rm -rf /$TESTPOOL 235 log_must zpool create -f $TESTPOOL $disklist 236 else 237 reexport_pool 238 fi 239 240 rm -rf $TESTDIR || log_unresolved Could not remove $TESTDIR 241 mkdir -p $TESTDIR || log_unresolved Could not create $TESTDIR 242 243 log_must zfs create $TESTPOOL/$TESTFS 244 if [[ -z $no_mountpoint ]]; then 245 log_must zfs set mountpoint=$TESTDIR $TESTPOOL/$TESTFS 246 fi 247 248 if [[ -n $container ]]; then 249 rm -rf $TESTDIR1 || \ 250 log_unresolved Could not remove $TESTDIR1 251 mkdir -p $TESTDIR1 || \ 252 log_unresolved Could not create $TESTDIR1 253 254 log_must zfs create $TESTPOOL/$TESTCTR 255 log_must zfs set canmount=off $TESTPOOL/$TESTCTR 256 log_must zfs create $TESTPOOL/$TESTCTR/$TESTFS1 257 if [[ -z $no_mountpoint ]]; then 258 log_must zfs set mountpoint=$TESTDIR1 \ 259 $TESTPOOL/$TESTCTR/$TESTFS1 260 fi 261 fi 262 263 if [[ -n $volume ]]; then 264 if is_global_zone ; then 265 log_must zfs create -V $VOLSIZE $TESTPOOL/$TESTVOL 266 block_device_wait 267 else 268 log_must zfs create $TESTPOOL/$TESTVOL 269 fi 270 fi 271} 272 273# 274# Given a list of disks, setup a storage pool, file system and 275# a container. 276# 277function default_container_setup 278{ 279 typeset disklist=$1 280 281 default_setup "$disklist" "true" 282} 283 284# 285# Given a list of disks, setup a storage pool,file system 286# and a volume. 287# 288function default_volume_setup 289{ 290 typeset disklist=$1 291 292 default_setup "$disklist" "" "true" 293} 294 295# 296# Given a list of disks, setup a storage pool,file system, 297# a container and a volume. 298# 299function default_container_volume_setup 300{ 301 typeset disklist=$1 302 303 default_setup "$disklist" "true" "true" 304} 305 306# 307# Create a snapshot on a filesystem or volume. Defaultly create a snapshot on 308# filesystem 309# 310# $1 Existing filesystem or volume name. Default, $TESTPOOL/$TESTFS 311# $2 snapshot name. Default, $TESTSNAP 312# 313function create_snapshot 314{ 315 typeset fs_vol=${1:-$TESTPOOL/$TESTFS} 316 typeset snap=${2:-$TESTSNAP} 317 318 [[ -z $fs_vol ]] && log_fail "Filesystem or volume's name is undefined." 319 [[ -z $snap ]] && log_fail "Snapshot's name is undefined." 320 321 if snapexists $fs_vol@$snap; then 322 log_fail "$fs_vol@$snap already exists." 323 fi 324 datasetexists $fs_vol || \ 325 log_fail "$fs_vol must exist." 326 327 log_must zfs snapshot $fs_vol@$snap 328} 329 330# 331# Create a clone from a snapshot, default clone name is $TESTCLONE. 332# 333# $1 Existing snapshot, $TESTPOOL/$TESTFS@$TESTSNAP is default. 334# $2 Clone name, $TESTPOOL/$TESTCLONE is default. 335# 336function create_clone # snapshot clone 337{ 338 typeset snap=${1:-$TESTPOOL/$TESTFS@$TESTSNAP} 339 typeset clone=${2:-$TESTPOOL/$TESTCLONE} 340 341 [[ -z $snap ]] && \ 342 log_fail "Snapshot name is undefined." 343 [[ -z $clone ]] && \ 344 log_fail "Clone name is undefined." 345 346 log_must zfs clone $snap $clone 347} 348 349# 350# Create a bookmark of the given snapshot. Defaultly create a bookmark on 351# filesystem. 352# 353# $1 Existing filesystem or volume name. Default, $TESTFS 354# $2 Existing snapshot name. Default, $TESTSNAP 355# $3 bookmark name. Default, $TESTBKMARK 356# 357function create_bookmark 358{ 359 typeset fs_vol=${1:-$TESTFS} 360 typeset snap=${2:-$TESTSNAP} 361 typeset bkmark=${3:-$TESTBKMARK} 362 363 [[ -z $fs_vol ]] && log_fail "Filesystem or volume's name is undefined." 364 [[ -z $snap ]] && log_fail "Snapshot's name is undefined." 365 [[ -z $bkmark ]] && log_fail "Bookmark's name is undefined." 366 367 if bkmarkexists $fs_vol#$bkmark; then 368 log_fail "$fs_vol#$bkmark already exists." 369 fi 370 datasetexists $fs_vol || \ 371 log_fail "$fs_vol must exist." 372 snapexists $fs_vol@$snap || \ 373 log_fail "$fs_vol@$snap must exist." 374 375 log_must zfs bookmark $fs_vol@$snap $fs_vol#$bkmark 376} 377 378# 379# Create a temporary clone result of an interrupted resumable 'zfs receive' 380# $1 Destination filesystem name. Must not exist, will be created as the result 381# of this function along with its %recv temporary clone 382# $2 Source filesystem name. Must not exist, will be created and destroyed 383# 384function create_recv_clone 385{ 386 typeset recvfs="$1" 387 typeset sendfs="${2:-$TESTPOOL/create_recv_clone}" 388 typeset snap="$sendfs@snap1" 389 typeset incr="$sendfs@snap2" 390 typeset mountpoint="$TESTDIR/create_recv_clone" 391 typeset sendfile="$TESTDIR/create_recv_clone.zsnap" 392 393 [[ -z $recvfs ]] && log_fail "Recv filesystem's name is undefined." 394 395 datasetexists $recvfs && log_fail "Recv filesystem must not exist." 396 datasetexists $sendfs && log_fail "Send filesystem must not exist." 397 398 log_must zfs create -o compression=off -o mountpoint="$mountpoint" $sendfs 399 log_must zfs snapshot $snap 400 log_must eval "zfs send $snap | zfs recv -u $recvfs" 401 log_must mkfile 1m "$mountpoint/data" 402 log_must zfs snapshot $incr 403 log_must eval "zfs send -i $snap $incr | dd bs=10K count=1 \ 404 iflag=fullblock > $sendfile" 405 log_mustnot eval "zfs recv -su $recvfs < $sendfile" 406 destroy_dataset "$sendfs" "-r" 407 log_must rm -f "$sendfile" 408 409 if [[ $(get_prop 'inconsistent' "$recvfs/%recv") -ne 1 ]]; then 410 log_fail "Error creating temporary $recvfs/%recv clone" 411 fi 412} 413 414function default_mirror_setup 415{ 416 default_mirror_setup_noexit $1 $2 $3 417 418 log_pass 419} 420 421# 422# Given a pair of disks, set up a storage pool and dataset for the mirror 423# @parameters: $1 the primary side of the mirror 424# $2 the secondary side of the mirror 425# @uses: ZPOOL ZFS TESTPOOL TESTFS 426function default_mirror_setup_noexit 427{ 428 readonly func="default_mirror_setup_noexit" 429 typeset primary=$1 430 typeset secondary=$2 431 432 [[ -z $primary ]] && \ 433 log_fail "$func: No parameters passed" 434 [[ -z $secondary ]] && \ 435 log_fail "$func: No secondary partition passed" 436 [[ -d /$TESTPOOL ]] && rm -rf /$TESTPOOL 437 log_must zpool create -f $TESTPOOL mirror $@ 438 log_must zfs create $TESTPOOL/$TESTFS 439 log_must zfs set mountpoint=$TESTDIR $TESTPOOL/$TESTFS 440} 441 442# 443# Destroy the configured testpool mirrors. 444# the mirrors are of the form ${TESTPOOL}{number} 445# @uses: ZPOOL ZFS TESTPOOL 446function destroy_mirrors 447{ 448 default_cleanup_noexit 449 450 log_pass 451} 452 453function default_raidz_setup 454{ 455 default_raidz_setup_noexit "$*" 456 457 log_pass 458} 459 460# 461# Given a minimum of two disks, set up a storage pool and dataset for the raid-z 462# $1 the list of disks 463# 464function default_raidz_setup_noexit 465{ 466 typeset disklist="$*" 467 disks=(${disklist[*]}) 468 469 if [[ ${#disks[*]} -lt 2 ]]; then 470 log_fail "A raid-z requires a minimum of two disks." 471 fi 472 473 [[ -d /$TESTPOOL ]] && rm -rf /$TESTPOOL 474 log_must zpool create -f $TESTPOOL raidz $disklist 475 log_must zfs create $TESTPOOL/$TESTFS 476 log_must zfs set mountpoint=$TESTDIR $TESTPOOL/$TESTFS 477} 478 479# 480# Common function used to cleanup storage pools and datasets. 481# 482# Invoked at the start of the test suite to ensure the system 483# is in a known state, and also at the end of each set of 484# sub-tests to ensure errors from one set of tests doesn't 485# impact the execution of the next set. 486 487function default_cleanup 488{ 489 default_cleanup_noexit 490 491 log_pass 492} 493 494# 495# Utility function used to list all available pool names. 496# 497# NOTE: $KEEP is a variable containing pool names, separated by a newline 498# character, that must be excluded from the returned list. 499# 500function get_all_pools 501{ 502 zpool list -H -o name | grep -Fvx "$KEEP" | grep -v "$NO_POOLS" 503} 504 505function default_cleanup_noexit 506{ 507 typeset pool="" 508 # 509 # Destroying the pool will also destroy any 510 # filesystems it contains. 511 # 512 if is_global_zone; then 513 zfs unmount -a > /dev/null 2>&1 514 ALL_POOLS=$(get_all_pools) 515 # Here, we loop through the pools we're allowed to 516 # destroy, only destroying them if it's safe to do 517 # so. 518 while [ ! -z ${ALL_POOLS} ] 519 do 520 for pool in ${ALL_POOLS} 521 do 522 if safe_to_destroy_pool $pool ; 523 then 524 destroy_pool $pool 525 fi 526 done 527 ALL_POOLS=$(get_all_pools) 528 done 529 530 zfs mount -a 531 else 532 typeset fs="" 533 for fs in $(zfs list -H -o name \ 534 | grep "^$ZONE_POOL/$ZONE_CTR[01234]/"); do 535 destroy_dataset "$fs" "-Rf" 536 done 537 538 # Need cleanup here to avoid garbage dir left. 539 for fs in $(zfs list -H -o name); do 540 [[ $fs == /$ZONE_POOL ]] && continue 541 [[ -d $fs ]] && log_must rm -rf $fs/* 542 done 543 544 # 545 # Reset the $ZONE_POOL/$ZONE_CTR[01234] file systems property to 546 # the default value 547 # 548 for fs in $(zfs list -H -o name); do 549 if [[ $fs == $ZONE_POOL/$ZONE_CTR[01234] ]]; then 550 log_must zfs set reservation=none $fs 551 log_must zfs set recordsize=128K $fs 552 log_must zfs set mountpoint=/$fs $fs 553 typeset enc=$(get_prop encryption $fs) 554 if [ -z "$enc" ] || [ "$enc" = "off" ]; then 555 log_must zfs set checksum=on $fs 556 fi 557 log_must zfs set compression=off $fs 558 log_must zfs set atime=on $fs 559 log_must zfs set devices=off $fs 560 log_must zfs set exec=on $fs 561 log_must zfs set setuid=on $fs 562 log_must zfs set readonly=off $fs 563 log_must zfs set snapdir=hidden $fs 564 log_must zfs set aclmode=groupmask $fs 565 log_must zfs set aclinherit=secure $fs 566 fi 567 done 568 fi 569 570 [[ -d $TESTDIR ]] && \ 571 log_must rm -rf $TESTDIR 572 573 disk1=${DISKS%% *} 574 if is_mpath_device $disk1; then 575 delete_partitions 576 fi 577 578 rm -f $TEST_BASE_DIR/{err,out} 579} 580 581 582# 583# Common function used to cleanup storage pools, file systems 584# and containers. 585# 586function default_container_cleanup 587{ 588 if ! is_global_zone; then 589 reexport_pool 590 fi 591 592 ismounted $TESTPOOL/$TESTCTR/$TESTFS1 && 593 log_must zfs unmount $TESTPOOL/$TESTCTR/$TESTFS1 594 595 destroy_dataset "$TESTPOOL/$TESTCTR/$TESTFS1" "-R" 596 destroy_dataset "$TESTPOOL/$TESTCTR" "-Rf" 597 598 [[ -e $TESTDIR1 ]] && \ 599 log_must rm -rf $TESTDIR1 600 601 default_cleanup 602} 603 604# 605# Common function used to cleanup snapshot of file system or volume. Default to 606# delete the file system's snapshot 607# 608# $1 snapshot name 609# 610function destroy_snapshot 611{ 612 typeset snap=${1:-$TESTPOOL/$TESTFS@$TESTSNAP} 613 614 if ! snapexists $snap; then 615 log_fail "'$snap' does not exist." 616 fi 617 618 # 619 # For the sake of the value which come from 'get_prop' is not equal 620 # to the really mountpoint when the snapshot is unmounted. So, firstly 621 # check and make sure this snapshot's been mounted in current system. 622 # 623 typeset mtpt="" 624 if ismounted $snap; then 625 mtpt=$(get_prop mountpoint $snap) 626 fi 627 628 destroy_dataset "$snap" 629 [[ $mtpt != "" && -d $mtpt ]] && \ 630 log_must rm -rf $mtpt 631} 632 633# 634# Common function used to cleanup clone. 635# 636# $1 clone name 637# 638function destroy_clone 639{ 640 typeset clone=${1:-$TESTPOOL/$TESTCLONE} 641 642 if ! datasetexists $clone; then 643 log_fail "'$clone' does not existed." 644 fi 645 646 # With the same reason in destroy_snapshot 647 typeset mtpt="" 648 if ismounted $clone; then 649 mtpt=$(get_prop mountpoint $clone) 650 fi 651 652 destroy_dataset "$clone" 653 [[ $mtpt != "" && -d $mtpt ]] && \ 654 log_must rm -rf $mtpt 655} 656 657# 658# Common function used to cleanup bookmark of file system or volume. Default 659# to delete the file system's bookmark. 660# 661# $1 bookmark name 662# 663function destroy_bookmark 664{ 665 typeset bkmark=${1:-$TESTPOOL/$TESTFS#$TESTBKMARK} 666 667 if ! bkmarkexists $bkmark; then 668 log_fail "'$bkmarkp' does not existed." 669 fi 670 671 destroy_dataset "$bkmark" 672} 673 674# Return 0 if a snapshot exists; $? otherwise 675# 676# $1 - snapshot name 677 678function snapexists 679{ 680 zfs list -H -t snapshot "$1" > /dev/null 2>&1 681} 682 683# 684# Return 0 if a bookmark exists; $? otherwise 685# 686# $1 - bookmark name 687# 688function bkmarkexists 689{ 690 zfs list -H -t bookmark "$1" > /dev/null 2>&1 691} 692 693# 694# Return 0 if a hold exists; $? otherwise 695# 696# $1 - hold tag 697# $2 - snapshot name 698# 699function holdexists 700{ 701 ! zfs holds "$2" | awk -v t="$1" '$2 ~ t { exit 1 }' 702} 703 704# 705# Set a property to a certain value on a dataset. 706# Sets a property of the dataset to the value as passed in. 707# @param: 708# $1 dataset who's property is being set 709# $2 property to set 710# $3 value to set property to 711# @return: 712# 0 if the property could be set. 713# non-zero otherwise. 714# @use: ZFS 715# 716function dataset_setprop 717{ 718 typeset fn=dataset_setprop 719 720 if (($# < 3)); then 721 log_note "$fn: Insufficient parameters (need 3, had $#)" 722 return 1 723 fi 724 typeset output= 725 output=$(zfs set $2=$3 $1 2>&1) 726 typeset rv=$? 727 if ((rv != 0)); then 728 log_note "Setting property on $1 failed." 729 log_note "property $2=$3" 730 log_note "Return Code: $rv" 731 log_note "Output: $output" 732 return $rv 733 fi 734 return 0 735} 736 737# 738# Check a numeric assertion 739# @parameter: $@ the assertion to check 740# @output: big loud notice if assertion failed 741# @use: log_fail 742# 743function assert 744{ 745 (($@)) || log_fail "$@" 746} 747 748# 749# Function to format partition size of a disk 750# Given a disk cxtxdx reduces all partitions 751# to 0 size 752# 753function zero_partitions #<whole_disk_name> 754{ 755 typeset diskname=$1 756 typeset i 757 758 if is_freebsd; then 759 gpart destroy -F $diskname 760 elif is_linux; then 761 DSK=$DEV_DSKDIR/$diskname 762 DSK=$(echo $DSK | sed -e "s|//|/|g") 763 log_must parted $DSK -s -- mklabel gpt 764 blockdev --rereadpt $DSK 2>/dev/null 765 block_device_wait 766 else 767 for i in 0 1 3 4 5 6 7 768 do 769 log_must set_partition $i "" 0mb $diskname 770 done 771 fi 772 773 return 0 774} 775 776# 777# Given a slice, size and disk, this function 778# formats the slice to the specified size. 779# Size should be specified with units as per 780# the `format` command requirements eg. 100mb 3gb 781# 782# NOTE: This entire interface is problematic for the Linux parted utility 783# which requires the end of the partition to be specified. It would be 784# best to retire this interface and replace it with something more flexible. 785# At the moment a best effort is made. 786# 787# arguments: <slice_num> <slice_start> <size_plus_units> <whole_disk_name> 788function set_partition 789{ 790 typeset -i slicenum=$1 791 typeset start=$2 792 typeset size=$3 793 typeset disk=${4#$DEV_DSKDIR/} 794 disk=${disk#$DEV_RDSKDIR/} 795 796 case "$UNAME" in 797 Linux) 798 if [[ -z $size || -z $disk ]]; then 799 log_fail "The size or disk name is unspecified." 800 fi 801 disk=$DEV_DSKDIR/$disk 802 typeset size_mb=${size%%[mMgG]} 803 804 size_mb=${size_mb%%[mMgG][bB]} 805 if [[ ${size:1:1} == 'g' ]]; then 806 ((size_mb = size_mb * 1024)) 807 fi 808 809 # Create GPT partition table when setting slice 0 or 810 # when the device doesn't already contain a GPT label. 811 parted $disk -s -- print 1 >/dev/null 812 typeset ret_val=$? 813 if [[ $slicenum -eq 0 || $ret_val -ne 0 ]]; then 814 if ! parted $disk -s -- mklabel gpt; then 815 log_note "Failed to create GPT partition table on $disk" 816 return 1 817 fi 818 fi 819 820 # When no start is given align on the first cylinder. 821 if [[ -z "$start" ]]; then 822 start=1 823 fi 824 825 # Determine the cylinder size for the device and using 826 # that calculate the end offset in cylinders. 827 typeset -i cly_size_kb=0 828 cly_size_kb=$(parted -m $disk -s -- unit cyl print | 829 awk -F '[:k.]' 'NR == 3 {print $4}') 830 ((end = (size_mb * 1024 / cly_size_kb) + start)) 831 832 parted $disk -s -- \ 833 mkpart part$slicenum ${start}cyl ${end}cyl 834 typeset ret_val=$? 835 if [[ $ret_val -ne 0 ]]; then 836 log_note "Failed to create partition $slicenum on $disk" 837 return 1 838 fi 839 840 blockdev --rereadpt $disk 2>/dev/null 841 block_device_wait $disk 842 ;; 843 FreeBSD) 844 if [[ -z $size || -z $disk ]]; then 845 log_fail "The size or disk name is unspecified." 846 fi 847 disk=$DEV_DSKDIR/$disk 848 849 if [[ $slicenum -eq 0 ]] || ! gpart show $disk >/dev/null 2>&1; then 850 gpart destroy -F $disk >/dev/null 2>&1 851 if ! gpart create -s GPT $disk; then 852 log_note "Failed to create GPT partition table on $disk" 853 return 1 854 fi 855 fi 856 857 typeset index=$((slicenum + 1)) 858 859 if [[ -n $start ]]; then 860 start="-b $start" 861 fi 862 gpart add -t freebsd-zfs $start -s $size -i $index $disk 863 if [[ $ret_val -ne 0 ]]; then 864 log_note "Failed to create partition $slicenum on $disk" 865 return 1 866 fi 867 868 block_device_wait $disk 869 ;; 870 *) 871 if [[ -z $slicenum || -z $size || -z $disk ]]; then 872 log_fail "The slice, size or disk name is unspecified." 873 fi 874 875 typeset format_file=/var/tmp/format_in.$$ 876 877 echo "partition" >$format_file 878 echo "$slicenum" >> $format_file 879 echo "" >> $format_file 880 echo "" >> $format_file 881 echo "$start" >> $format_file 882 echo "$size" >> $format_file 883 echo "label" >> $format_file 884 echo "" >> $format_file 885 echo "q" >> $format_file 886 echo "q" >> $format_file 887 888 format -e -s -d $disk -f $format_file 889 typeset ret_val=$? 890 rm -f $format_file 891 ;; 892 esac 893 894 if [[ $ret_val -ne 0 ]]; then 895 log_note "Unable to format $disk slice $slicenum to $size" 896 return 1 897 fi 898 return 0 899} 900 901# 902# Delete all partitions on all disks - this is specifically for the use of multipath 903# devices which currently can only be used in the test suite as raw/un-partitioned 904# devices (ie a zpool cannot be created on a whole mpath device that has partitions) 905# 906function delete_partitions 907{ 908 typeset disk 909 910 if [[ -z $DISKSARRAY ]]; then 911 DISKSARRAY=$DISKS 912 fi 913 914 if is_linux; then 915 typeset -i part 916 for disk in $DISKSARRAY; do 917 for (( part = 1; part < MAX_PARTITIONS; part++ )); do 918 typeset partition=${disk}${SLICE_PREFIX}${part} 919 parted $DEV_DSKDIR/$disk -s rm $part > /dev/null 2>&1 920 if lsblk | grep -qF ${partition}; then 921 log_fail "Partition ${partition} not deleted" 922 else 923 log_note "Partition ${partition} deleted" 924 fi 925 done 926 done 927 elif is_freebsd; then 928 for disk in $DISKSARRAY; do 929 if gpart destroy -F $disk; then 930 log_note "Partitions for ${disk} deleted" 931 else 932 log_fail "Partitions for ${disk} not deleted" 933 fi 934 done 935 fi 936} 937 938# 939# Get the end cyl of the given slice 940# 941function get_endslice #<disk> <slice> 942{ 943 typeset disk=$1 944 typeset slice=$2 945 if [[ -z $disk || -z $slice ]] ; then 946 log_fail "The disk name or slice number is unspecified." 947 fi 948 949 case "$UNAME" in 950 Linux) 951 endcyl=$(parted -s $DEV_DSKDIR/$disk -- unit cyl print | \ 952 awk "/part${slice}/"' {sub(/cyl/, "", $3); print $3}') 953 ((endcyl = (endcyl + 1))) 954 ;; 955 FreeBSD) 956 disk=${disk#/dev/zvol/} 957 disk=${disk%p*} 958 slice=$((slice + 1)) 959 endcyl=$(gpart show $disk | \ 960 awk -v slice=$slice '$3 == slice { print $1 + $2 }') 961 ;; 962 *) 963 disk=${disk#/dev/dsk/} 964 disk=${disk#/dev/rdsk/} 965 disk=${disk%s*} 966 967 typeset -i ratio=0 968 ratio=$(prtvtoc /dev/rdsk/${disk}s2 | \ 969 awk '/sectors\/cylinder/ {print $2}') 970 971 if ((ratio == 0)); then 972 return 973 fi 974 975 typeset -i endcyl=$(prtvtoc -h /dev/rdsk/${disk}s2 | 976 awk -v token="$slice" '$1 == token {print $6}') 977 978 ((endcyl = (endcyl + 1) / ratio)) 979 ;; 980 esac 981 982 echo $endcyl 983} 984 985 986# 987# Given a size,disk and total slice number, this function formats the 988# disk slices from 0 to the total slice number with the same specified 989# size. 990# 991function partition_disk #<slice_size> <whole_disk_name> <total_slices> 992{ 993 typeset -i i=0 994 typeset slice_size=$1 995 typeset disk_name=$2 996 typeset total_slices=$3 997 typeset cyl 998 999 zero_partitions $disk_name 1000 while ((i < $total_slices)); do 1001 if ! is_linux; then 1002 if ((i == 2)); then 1003 ((i = i + 1)) 1004 continue 1005 fi 1006 fi 1007 log_must set_partition $i "$cyl" $slice_size $disk_name 1008 cyl=$(get_endslice $disk_name $i) 1009 ((i = i+1)) 1010 done 1011} 1012 1013# 1014# This function continues to write to a filenum number of files into dirnum 1015# number of directories until either file_write returns an error or the 1016# maximum number of files per directory have been written. 1017# 1018# Usage: 1019# fill_fs [destdir] [dirnum] [filenum] [bytes] [num_writes] [data] 1020# 1021# Return value: 0 on success 1022# non 0 on error 1023# 1024# Where : 1025# destdir: is the directory where everything is to be created under 1026# dirnum: the maximum number of subdirectories to use, -1 no limit 1027# filenum: the maximum number of files per subdirectory 1028# bytes: number of bytes to write 1029# num_writes: number of types to write out bytes 1030# data: the data that will be written 1031# 1032# E.g. 1033# fill_fs /testdir 20 25 1024 256 0 1034# 1035# Note: bytes * num_writes equals the size of the testfile 1036# 1037function fill_fs # destdir dirnum filenum bytes num_writes data 1038{ 1039 typeset destdir=${1:-$TESTDIR} 1040 typeset -i dirnum=${2:-50} 1041 typeset -i filenum=${3:-50} 1042 typeset -i bytes=${4:-8192} 1043 typeset -i num_writes=${5:-10240} 1044 typeset data=${6:-0} 1045 1046 mkdir -p $destdir/{1..$dirnum} 1047 for f in $destdir/{1..$dirnum}/$TESTFILE{1..$filenum}; do 1048 file_write -o create -f $f -b $bytes -c $num_writes -d $data \ 1049 || return 1050 done 1051} 1052 1053# Get the specified dataset property in parsable format or fail 1054function get_prop # property dataset 1055{ 1056 typeset prop=$1 1057 typeset dataset=$2 1058 1059 zfs get -Hpo value "$prop" "$dataset" || log_fail "zfs get $prop $dataset" 1060} 1061 1062# Get the specified pool property in parsable format or fail 1063function get_pool_prop # property pool 1064{ 1065 typeset prop=$1 1066 typeset pool=$2 1067 1068 zpool get -Hpo value "$prop" "$pool" || log_fail "zpool get $prop $pool" 1069} 1070 1071# Return 0 if a pool exists; $? otherwise 1072# 1073# $1 - pool name 1074 1075function poolexists 1076{ 1077 typeset pool=$1 1078 1079 if [[ -z $pool ]]; then 1080 log_note "No pool name given." 1081 return 1 1082 fi 1083 1084 zpool get name "$pool" > /dev/null 2>&1 1085} 1086 1087# Return 0 if all the specified datasets exist; $? otherwise 1088# 1089# $1-n dataset name 1090function datasetexists 1091{ 1092 if (($# == 0)); then 1093 log_note "No dataset name given." 1094 return 1 1095 fi 1096 1097 zfs get name "$@" > /dev/null 2>&1 1098} 1099 1100# return 0 if none of the specified datasets exists, otherwise return 1. 1101# 1102# $1-n dataset name 1103function datasetnonexists 1104{ 1105 if (($# == 0)); then 1106 log_note "No dataset name given." 1107 return 1 1108 fi 1109 1110 while (($# > 0)); do 1111 zfs list -H -t filesystem,snapshot,volume $1 > /dev/null 2>&1 \ 1112 && return 1 1113 shift 1114 done 1115 1116 return 0 1117} 1118 1119# FreeBSD breaks exports(5) at whitespace and doesn't process escapes 1120# Solaris just breaks 1121# 1122# cf. https://github.com/openzfs/zfs/pull/13165#issuecomment-1059845807 1123# 1124# Linux can have spaces (which are \OOO-escaped), 1125# but can't have backslashes because they're parsed recursively 1126function shares_can_have_whitespace 1127{ 1128 is_linux 1129} 1130 1131function is_shared_freebsd 1132{ 1133 typeset fs=$1 1134 1135 pgrep -q mountd && showmount -E | grep -qx "$fs" 1136} 1137 1138function is_shared_illumos 1139{ 1140 typeset fs=$1 1141 typeset mtpt 1142 1143 for mtpt in `share | awk '{print $2}'` ; do 1144 if [[ $mtpt == $fs ]] ; then 1145 return 0 1146 fi 1147 done 1148 1149 typeset stat=$(svcs -H -o STA nfs/server:default) 1150 if [[ $stat != "ON" ]]; then 1151 log_note "Current nfs/server status: $stat" 1152 fi 1153 1154 return 1 1155} 1156 1157function is_shared_linux 1158{ 1159 typeset fs=$1 1160 ! exportfs -s | awk -v fs="${fs//\\/\\\\}" '/^\// && $1 == fs {exit 1}' 1161} 1162 1163# 1164# Given a mountpoint, or a dataset name, determine if it is shared via NFS. 1165# 1166# Returns 0 if shared, 1 otherwise. 1167# 1168function is_shared 1169{ 1170 typeset fs=$1 1171 typeset mtpt 1172 1173 if [[ $fs != "/"* ]] ; then 1174 if datasetnonexists "$fs" ; then 1175 return 1 1176 else 1177 mtpt=$(get_prop mountpoint "$fs") 1178 case "$mtpt" in 1179 none|legacy|-) return 1 1180 ;; 1181 *) fs=$mtpt 1182 ;; 1183 esac 1184 fi 1185 fi 1186 1187 case "$UNAME" in 1188 FreeBSD) is_shared_freebsd "$fs" ;; 1189 Linux) is_shared_linux "$fs" ;; 1190 *) is_shared_illumos "$fs" ;; 1191 esac 1192} 1193 1194function is_exported_illumos 1195{ 1196 typeset fs=$1 1197 typeset mtpt _ 1198 1199 while read -r mtpt _; do 1200 [ "$mtpt" = "$fs" ] && return 1201 done < /etc/dfs/sharetab 1202 1203 return 1 1204} 1205 1206function is_exported_freebsd 1207{ 1208 typeset fs=$1 1209 typeset mtpt _ 1210 1211 while read -r mtpt _; do 1212 [ "$mtpt" = "$fs" ] && return 1213 done < /etc/zfs/exports 1214 1215 return 1 1216} 1217 1218function is_exported_linux 1219{ 1220 typeset fs=$1 1221 typeset mtpt _ 1222 1223 while read -r mtpt _; do 1224 [ "$(printf "$mtpt")" = "$fs" ] && return 1225 done < /etc/exports.d/zfs.exports 1226 1227 return 1 1228} 1229 1230# 1231# Given a mountpoint, or a dataset name, determine if it is exported via 1232# the os-specific NFS exports file. 1233# 1234# Returns 0 if exported, 1 otherwise. 1235# 1236function is_exported 1237{ 1238 typeset fs=$1 1239 typeset mtpt 1240 1241 if [[ $fs != "/"* ]] ; then 1242 if datasetnonexists "$fs" ; then 1243 return 1 1244 else 1245 mtpt=$(get_prop mountpoint "$fs") 1246 case $mtpt in 1247 none|legacy|-) return 1 1248 ;; 1249 *) fs=$mtpt 1250 ;; 1251 esac 1252 fi 1253 fi 1254 1255 case "$UNAME" in 1256 FreeBSD) is_exported_freebsd "$fs" ;; 1257 Linux) is_exported_linux "$fs" ;; 1258 *) is_exported_illumos "$fs" ;; 1259 esac 1260} 1261 1262# 1263# Given a dataset name determine if it is shared via SMB. 1264# 1265# Returns 0 if shared, 1 otherwise. 1266# 1267function is_shared_smb 1268{ 1269 typeset fs=$1 1270 1271 datasetexists "$fs" || return 1272 1273 if is_linux; then 1274 net usershare list | grep -xFq "${fs//[-\/]/_}" 1275 else 1276 log_note "SMB on $UNAME currently unsupported by the test framework" 1277 return 1 1278 fi 1279} 1280 1281# 1282# Given a mountpoint, determine if it is not shared via NFS. 1283# 1284# Returns 0 if not shared, 1 otherwise. 1285# 1286function not_shared 1287{ 1288 ! is_shared $1 1289} 1290 1291# 1292# Given a dataset determine if it is not shared via SMB. 1293# 1294# Returns 0 if not shared, 1 otherwise. 1295# 1296function not_shared_smb 1297{ 1298 ! is_shared_smb $1 1299} 1300 1301# 1302# Helper function to unshare a mountpoint. 1303# 1304function unshare_fs #fs 1305{ 1306 typeset fs=$1 1307 1308 if is_shared $fs || is_shared_smb $fs; then 1309 log_must zfs unshare $fs 1310 fi 1311} 1312 1313# 1314# Helper function to share a NFS mountpoint. 1315# 1316function share_nfs #fs 1317{ 1318 typeset fs=$1 1319 1320 is_shared "$fs" && return 1321 1322 case "$UNAME" in 1323 Linux) 1324 log_must exportfs "*:$fs" 1325 ;; 1326 FreeBSD) 1327 typeset mountd 1328 read -r mountd < /var/run/mountd.pid 1329 log_must eval "printf '%s\t\n' \"$fs\" >> /etc/zfs/exports" 1330 log_must kill -s HUP "$mountd" 1331 ;; 1332 *) 1333 log_must share -F nfs "$fs" 1334 ;; 1335 esac 1336 1337 return 0 1338} 1339 1340# 1341# Helper function to unshare a NFS mountpoint. 1342# 1343function unshare_nfs #fs 1344{ 1345 typeset fs=$1 1346 1347 ! is_shared "$fs" && return 1348 1349 case "$UNAME" in 1350 Linux) 1351 log_must exportfs -u "*:$fs" 1352 ;; 1353 FreeBSD) 1354 typeset mountd 1355 read -r mountd < /var/run/mountd.pid 1356 awk -v fs="${fs//\\/\\\\}" '$1 != fs' /etc/zfs/exports > /etc/zfs/exports.$$ 1357 log_must mv /etc/zfs/exports.$$ /etc/zfs/exports 1358 log_must kill -s HUP "$mountd" 1359 ;; 1360 *) 1361 log_must unshare -F nfs $fs 1362 ;; 1363 esac 1364 1365 return 0 1366} 1367 1368# 1369# Helper function to show NFS shares. 1370# 1371function showshares_nfs 1372{ 1373 case "$UNAME" in 1374 Linux) 1375 exportfs -v 1376 ;; 1377 FreeBSD) 1378 showmount 1379 ;; 1380 *) 1381 share -F nfs 1382 ;; 1383 esac 1384} 1385 1386function check_nfs 1387{ 1388 case "$UNAME" in 1389 Linux) 1390 exportfs -s 1391 ;; 1392 FreeBSD) 1393 showmount -e 1394 ;; 1395 *) 1396 log_unsupported "Unknown platform" 1397 ;; 1398 esac || log_unsupported "The NFS utilities are not installed" 1399} 1400 1401# 1402# Check NFS server status and trigger it online. 1403# 1404function setup_nfs_server 1405{ 1406 # Cannot share directory in non-global zone. 1407 # 1408 if ! is_global_zone; then 1409 log_note "Cannot trigger NFS server by sharing in LZ." 1410 return 1411 fi 1412 1413 if is_linux; then 1414 # 1415 # Re-synchronize /var/lib/nfs/etab with /etc/exports and 1416 # /etc/exports.d./* to provide a clean test environment. 1417 # 1418 log_must exportfs -r 1419 1420 log_note "NFS server must be started prior to running ZTS." 1421 return 1422 elif is_freebsd; then 1423 log_must kill -s HUP $(</var/run/mountd.pid) 1424 1425 log_note "NFS server must be started prior to running ZTS." 1426 return 1427 fi 1428 1429 typeset nfs_fmri="svc:/network/nfs/server:default" 1430 if [[ $(svcs -Ho STA $nfs_fmri) != "ON" ]]; then 1431 # 1432 # Only really sharing operation can enable NFS server 1433 # to online permanently. 1434 # 1435 typeset dummy=/tmp/dummy 1436 1437 if [[ -d $dummy ]]; then 1438 log_must rm -rf $dummy 1439 fi 1440 1441 log_must mkdir $dummy 1442 log_must share $dummy 1443 1444 # 1445 # Waiting for fmri's status to be the final status. 1446 # Otherwise, in transition, an asterisk (*) is appended for 1447 # instances, unshare will reverse status to 'DIS' again. 1448 # 1449 # Waiting for 1's at least. 1450 # 1451 log_must sleep 1 1452 timeout=10 1453 while [[ timeout -ne 0 && $(svcs -Ho STA $nfs_fmri) == *'*' ]] 1454 do 1455 log_must sleep 1 1456 1457 ((timeout -= 1)) 1458 done 1459 1460 log_must unshare $dummy 1461 log_must rm -rf $dummy 1462 fi 1463 1464 log_note "Current NFS status: '$(svcs -Ho STA,FMRI $nfs_fmri)'" 1465} 1466 1467# 1468# To verify whether calling process is in global zone 1469# 1470# Return 0 if in global zone, 1 in non-global zone 1471# 1472function is_global_zone 1473{ 1474 if is_linux || is_freebsd; then 1475 return 0 1476 else 1477 typeset cur_zone=$(zonename 2>/dev/null) 1478 [ $cur_zone = "global" ] 1479 fi 1480} 1481 1482# 1483# Verify whether test is permitted to run from 1484# global zone, local zone, or both 1485# 1486# $1 zone limit, could be "global", "local", or "both"(no limit) 1487# 1488# Return 0 if permitted, otherwise exit with log_unsupported 1489# 1490function verify_runnable # zone limit 1491{ 1492 typeset limit=$1 1493 1494 [[ -z $limit ]] && return 0 1495 1496 if is_global_zone ; then 1497 case $limit in 1498 global|both) 1499 ;; 1500 local) log_unsupported "Test is unable to run from "\ 1501 "global zone." 1502 ;; 1503 *) log_note "Warning: unknown limit $limit - " \ 1504 "use both." 1505 ;; 1506 esac 1507 else 1508 case $limit in 1509 local|both) 1510 ;; 1511 global) log_unsupported "Test is unable to run from "\ 1512 "local zone." 1513 ;; 1514 *) log_note "Warning: unknown limit $limit - " \ 1515 "use both." 1516 ;; 1517 esac 1518 1519 reexport_pool 1520 fi 1521 1522 return 0 1523} 1524 1525# Return 0 if create successfully or the pool exists; $? otherwise 1526# Note: In local zones, this function should return 0 silently. 1527# 1528# $1 - pool name 1529# $2-n - [keyword] devs_list 1530 1531function create_pool #pool devs_list 1532{ 1533 typeset pool=${1%%/*} 1534 1535 shift 1536 1537 if [[ -z $pool ]]; then 1538 log_note "Missing pool name." 1539 return 1 1540 fi 1541 1542 if poolexists $pool ; then 1543 destroy_pool $pool 1544 fi 1545 1546 if is_global_zone ; then 1547 [[ -d /$pool ]] && rm -rf /$pool 1548 log_must zpool create -f $pool $@ 1549 fi 1550 1551 return 0 1552} 1553 1554# Return 0 if destroy successfully or the pool exists; $? otherwise 1555# Note: In local zones, this function should return 0 silently. 1556# 1557# $1 - pool name 1558# Destroy pool with the given parameters. 1559 1560function destroy_pool #pool 1561{ 1562 typeset pool=${1%%/*} 1563 typeset mtpt 1564 1565 if [[ -z $pool ]]; then 1566 log_note "No pool name given." 1567 return 1 1568 fi 1569 1570 if is_global_zone ; then 1571 if poolexists "$pool" ; then 1572 mtpt=$(get_prop mountpoint "$pool") 1573 1574 # At times, syseventd/udev activity can cause attempts 1575 # to destroy a pool to fail with EBUSY. We retry a few 1576 # times allowing failures before requiring the destroy 1577 # to succeed. 1578 log_must_busy zpool destroy -f $pool 1579 1580 [[ -d $mtpt ]] && \ 1581 log_must rm -rf $mtpt 1582 else 1583 log_note "Pool does not exist. ($pool)" 1584 return 1 1585 fi 1586 fi 1587 1588 return 0 1589} 1590 1591# Return 0 if created successfully; $? otherwise 1592# 1593# $1 - dataset name 1594# $2-n - dataset options 1595 1596function create_dataset #dataset dataset_options 1597{ 1598 typeset dataset=$1 1599 1600 shift 1601 1602 if [[ -z $dataset ]]; then 1603 log_note "Missing dataset name." 1604 return 1 1605 fi 1606 1607 if datasetexists $dataset ; then 1608 destroy_dataset $dataset 1609 fi 1610 1611 log_must zfs create $@ $dataset 1612 1613 return 0 1614} 1615 1616# Return 0 if destroy successfully or the dataset exists; $? otherwise 1617# Note: In local zones, this function should return 0 silently. 1618# 1619# $1 - dataset name 1620# $2 - custom arguments for zfs destroy 1621# Destroy dataset with the given parameters. 1622 1623function destroy_dataset # dataset [args] 1624{ 1625 typeset dataset=$1 1626 typeset mtpt 1627 typeset args=${2:-""} 1628 1629 if [[ -z $dataset ]]; then 1630 log_note "No dataset name given." 1631 return 1 1632 fi 1633 1634 if is_global_zone ; then 1635 if datasetexists "$dataset" ; then 1636 mtpt=$(get_prop mountpoint "$dataset") 1637 log_must_busy zfs destroy $args $dataset 1638 1639 [ -d $mtpt ] && log_must rm -rf $mtpt 1640 else 1641 log_note "Dataset does not exist. ($dataset)" 1642 return 1 1643 fi 1644 fi 1645 1646 return 0 1647} 1648 1649# 1650# Reexport TESTPOOL & TESTPOOL(1-4) 1651# 1652function reexport_pool 1653{ 1654 typeset -i cntctr=5 1655 typeset -i i=0 1656 1657 while ((i < cntctr)); do 1658 if ((i == 0)); then 1659 TESTPOOL=$ZONE_POOL/$ZONE_CTR$i 1660 if ! ismounted $TESTPOOL; then 1661 log_must zfs mount $TESTPOOL 1662 fi 1663 else 1664 eval TESTPOOL$i=$ZONE_POOL/$ZONE_CTR$i 1665 if eval ! ismounted \$TESTPOOL$i; then 1666 log_must eval zfs mount \$TESTPOOL$i 1667 fi 1668 fi 1669 ((i += 1)) 1670 done 1671} 1672 1673# 1674# Verify a given disk or pool state 1675# 1676# Return 0 is pool/disk matches expected state, 1 otherwise 1677# 1678function check_state # pool disk state{online,offline,degraded} 1679{ 1680 typeset pool=$1 1681 typeset disk=${2#$DEV_DSKDIR/} 1682 typeset state=$3 1683 1684 [[ -z $pool ]] || [[ -z $state ]] \ 1685 && log_fail "Arguments invalid or missing" 1686 1687 if [[ -z $disk ]]; then 1688 #check pool state only 1689 zpool get -H -o value health $pool | grep -qi "$state" 1690 else 1691 zpool status -v $pool | grep "$disk" | grep -qi "$state" 1692 fi 1693} 1694 1695# 1696# Get the mountpoint of snapshot 1697# For the snapshot use <mp_filesystem>/.zfs/snapshot/<snap> 1698# as its mountpoint 1699# 1700function snapshot_mountpoint 1701{ 1702 typeset dataset=${1:-$TESTPOOL/$TESTFS@$TESTSNAP} 1703 1704 if [[ $dataset != *@* ]]; then 1705 log_fail "Error name of snapshot '$dataset'." 1706 fi 1707 1708 typeset fs=${dataset%@*} 1709 typeset snap=${dataset#*@} 1710 1711 if [[ -z $fs || -z $snap ]]; then 1712 log_fail "Error name of snapshot '$dataset'." 1713 fi 1714 1715 echo $(get_prop mountpoint $fs)/.zfs/snapshot/$snap 1716} 1717 1718# 1719# Given a device and 'ashift' value verify it's correctly set on every label 1720# 1721function verify_ashift # device ashift 1722{ 1723 typeset device="$1" 1724 typeset ashift="$2" 1725 1726 zdb -e -lll $device | awk -v ashift=$ashift ' 1727 /ashift: / { 1728 if (ashift != $2) 1729 exit 1; 1730 else 1731 count++; 1732 } 1733 END { 1734 exit (count != 4); 1735 }' 1736} 1737 1738# 1739# Given a pool and file system, this function will verify the file system 1740# using the zdb internal tool. Note that the pool is exported and imported 1741# to ensure it has consistent state. 1742# 1743function verify_filesys # pool filesystem dir 1744{ 1745 typeset pool="$1" 1746 typeset filesys="$2" 1747 typeset zdbout="/tmp/zdbout.$$" 1748 1749 shift 1750 shift 1751 typeset dirs=$@ 1752 typeset search_path="" 1753 1754 log_note "Calling zdb to verify filesystem '$filesys'" 1755 zfs unmount -a > /dev/null 2>&1 1756 log_must zpool export $pool 1757 1758 if [[ -n $dirs ]] ; then 1759 for dir in $dirs ; do 1760 search_path="$search_path -d $dir" 1761 done 1762 fi 1763 1764 log_must zpool import $search_path $pool 1765 1766 if ! zdb -cudi $filesys > $zdbout 2>&1; then 1767 log_note "Output: zdb -cudi $filesys" 1768 cat $zdbout 1769 rm -f $zdbout 1770 log_fail "zdb detected errors with: '$filesys'" 1771 fi 1772 1773 log_must zfs mount -a 1774 log_must rm -rf $zdbout 1775} 1776 1777# 1778# Given a pool issue a scrub and verify that no checksum errors are reported. 1779# 1780function verify_pool 1781{ 1782 typeset pool=${1:-$TESTPOOL} 1783 1784 log_must zpool scrub $pool 1785 log_must wait_scrubbed $pool 1786 1787 typeset -i cksum=$(zpool status $pool | awk ' 1788 !NF { isvdev = 0 } 1789 isvdev { errors += $NF } 1790 /CKSUM$/ { isvdev = 1 } 1791 END { print errors } 1792 ') 1793 if [[ $cksum != 0 ]]; then 1794 log_must zpool status -v 1795 log_fail "Unexpected CKSUM errors found on $pool ($cksum)" 1796 fi 1797} 1798 1799# 1800# Given a pool, and this function list all disks in the pool 1801# 1802function get_disklist # pool 1803{ 1804 echo $(zpool iostat -v $1 | awk '(NR > 4) {print $1}' | \ 1805 grep -vEe '^-----' -e "^(mirror|raidz[1-3]|draid[1-3]|spare|log|cache|special|dedup)|\-[0-9]$") 1806} 1807 1808# 1809# Given a pool, and this function list all disks in the pool with their full 1810# path (like "/dev/sda" instead of "sda"). 1811# 1812function get_disklist_fullpath # pool 1813{ 1814 get_disklist "-P $1" 1815} 1816 1817 1818 1819# /** 1820# This function kills a given list of processes after a time period. We use 1821# this in the stress tests instead of STF_TIMEOUT so that we can have processes 1822# run for a fixed amount of time, yet still pass. Tests that hit STF_TIMEOUT 1823# would be listed as FAIL, which we don't want : we're happy with stress tests 1824# running for a certain amount of time, then finishing. 1825# 1826# @param $1 the time in seconds after which we should terminate these processes 1827# @param $2..$n the processes we wish to terminate. 1828# */ 1829function stress_timeout 1830{ 1831 typeset -i TIMEOUT=$1 1832 shift 1833 typeset cpids="$@" 1834 1835 log_note "Waiting for child processes($cpids). " \ 1836 "It could last dozens of minutes, please be patient ..." 1837 log_must sleep $TIMEOUT 1838 1839 log_note "Killing child processes after ${TIMEOUT} stress timeout." 1840 typeset pid 1841 for pid in $cpids; do 1842 ps -p $pid > /dev/null 2>&1 && 1843 log_must kill -USR1 $pid 1844 done 1845} 1846 1847# 1848# Verify a given hotspare disk is inuse or avail 1849# 1850# Return 0 is pool/disk matches expected state, 1 otherwise 1851# 1852function check_hotspare_state # pool disk state{inuse,avail} 1853{ 1854 typeset pool=$1 1855 typeset disk=${2#$DEV_DSKDIR/} 1856 typeset state=$3 1857 1858 cur_state=$(get_device_state $pool $disk "spares") 1859 1860 [ $state = $cur_state ] 1861} 1862 1863# 1864# Wait until a hotspare transitions to a given state or times out. 1865# 1866# Return 0 when pool/disk matches expected state, 1 on timeout. 1867# 1868function wait_hotspare_state # pool disk state timeout 1869{ 1870 typeset pool=$1 1871 typeset disk=${2#*$DEV_DSKDIR/} 1872 typeset state=$3 1873 typeset timeout=${4:-60} 1874 typeset -i i=0 1875 1876 while [[ $i -lt $timeout ]]; do 1877 if check_hotspare_state $pool $disk $state; then 1878 return 0 1879 fi 1880 1881 i=$((i+1)) 1882 sleep 1 1883 done 1884 1885 return 1 1886} 1887 1888# 1889# Verify a given vdev disk is inuse or avail 1890# 1891# Return 0 is pool/disk matches expected state, 1 otherwise 1892# 1893function check_vdev_state # pool disk state{online,offline,unavail,removed} 1894{ 1895 typeset pool=$1 1896 typeset disk=${2#*$DEV_DSKDIR/} 1897 typeset state=$3 1898 1899 cur_state=$(get_device_state $pool $disk) 1900 1901 [ $state = $cur_state ] 1902} 1903 1904# 1905# Wait until a vdev transitions to a given state or times out. 1906# 1907# Return 0 when pool/disk matches expected state, 1 on timeout. 1908# 1909function wait_vdev_state # pool disk state timeout 1910{ 1911 typeset pool=$1 1912 typeset disk=${2#*$DEV_DSKDIR/} 1913 typeset state=$3 1914 typeset timeout=${4:-60} 1915 typeset -i i=0 1916 1917 while [[ $i -lt $timeout ]]; do 1918 if check_vdev_state $pool $disk $state; then 1919 return 0 1920 fi 1921 1922 i=$((i+1)) 1923 sleep 1 1924 done 1925 1926 return 1 1927} 1928 1929# 1930# Check the output of 'zpool status -v <pool>', 1931# and to see if the content of <token> contain the <keyword> specified. 1932# 1933# Return 0 is contain, 1 otherwise 1934# 1935function check_pool_status # pool token keyword <verbose> 1936{ 1937 typeset pool=$1 1938 typeset token=$2 1939 typeset keyword=$3 1940 typeset verbose=${4:-false} 1941 1942 scan=$(zpool status -v "$pool" 2>/dev/null | awk -v token="$token:" '$1==token') 1943 if [[ $verbose == true ]]; then 1944 log_note $scan 1945 fi 1946 echo $scan | grep -qi "$keyword" 1947} 1948 1949# 1950# The following functions are instance of check_pool_status() 1951# is_pool_resilvering - to check if the pool resilver is in progress 1952# is_pool_resilvered - to check if the pool resilver is completed 1953# is_pool_scrubbing - to check if the pool scrub is in progress 1954# is_pool_scrubbed - to check if the pool scrub is completed 1955# is_pool_scrub_stopped - to check if the pool scrub is stopped 1956# is_pool_scrub_paused - to check if the pool scrub has paused 1957# is_pool_removing - to check if the pool removing is a vdev 1958# is_pool_removed - to check if the pool remove is completed 1959# is_pool_discarding - to check if the pool checkpoint is being discarded 1960# is_pool_replacing - to check if the pool is performing a replacement 1961# 1962function is_pool_resilvering #pool <verbose> 1963{ 1964 check_pool_status "$1" "scan" \ 1965 "resilver[ ()0-9A-Za-z:_-]* in progress since" $2 1966} 1967 1968function is_pool_resilvered #pool <verbose> 1969{ 1970 check_pool_status "$1" "scan" "resilvered " $2 1971} 1972 1973function is_pool_scrubbing #pool <verbose> 1974{ 1975 check_pool_status "$1" "scan" "scrub in progress since " $2 1976} 1977 1978function is_pool_error_scrubbing #pool <verbose> 1979{ 1980 check_pool_status "$1" "scrub" "error scrub in progress since " $2 1981 return $? 1982} 1983 1984function is_pool_scrubbed #pool <verbose> 1985{ 1986 check_pool_status "$1" "scan" "scrub repaired" $2 1987} 1988 1989function is_pool_scrub_stopped #pool <verbose> 1990{ 1991 check_pool_status "$1" "scan" "scrub canceled" $2 1992} 1993 1994function is_pool_error_scrub_stopped #pool <verbose> 1995{ 1996 check_pool_status "$1" "scrub" "error scrub canceled on " $2 1997 return $? 1998} 1999 2000function is_pool_scrub_paused #pool <verbose> 2001{ 2002 check_pool_status "$1" "scan" "scrub paused since " $2 2003} 2004 2005function is_pool_error_scrub_paused #pool <verbose> 2006{ 2007 check_pool_status "$1" "scrub" "error scrub paused since " $2 2008 return $? 2009} 2010 2011function is_pool_removing #pool 2012{ 2013 check_pool_status "$1" "remove" "in progress since " 2014} 2015 2016function is_pool_removed #pool 2017{ 2018 check_pool_status "$1" "remove" "completed on" 2019} 2020 2021function is_pool_discarding #pool 2022{ 2023 check_pool_status "$1" "checkpoint" "discarding" 2024} 2025function is_pool_replacing #pool 2026{ 2027 zpool status "$1" | grep -qE 'replacing-[0-9]+' 2028} 2029 2030function wait_for_degraded 2031{ 2032 typeset pool=$1 2033 typeset timeout=${2:-30} 2034 typeset t0=$SECONDS 2035 2036 while :; do 2037 [[ $(get_pool_prop health $pool) == "DEGRADED" ]] && break 2038 log_note "$pool is not yet degraded." 2039 sleep 1 2040 if ((SECONDS - t0 > $timeout)); then 2041 log_note "$pool not degraded after $timeout seconds." 2042 return 1 2043 fi 2044 done 2045 2046 return 0 2047} 2048 2049# 2050# Use create_pool()/destroy_pool() to clean up the information in 2051# in the given disk to avoid slice overlapping. 2052# 2053function cleanup_devices #vdevs 2054{ 2055 typeset pool="foopool$$" 2056 2057 for vdev in $@; do 2058 zero_partitions $vdev 2059 done 2060 2061 poolexists $pool && destroy_pool $pool 2062 create_pool $pool $@ 2063 destroy_pool $pool 2064 2065 return 0 2066} 2067 2068#/** 2069# A function to find and locate free disks on a system or from given 2070# disks as the parameter. It works by locating disks that are in use 2071# as swap devices and dump devices, and also disks listed in /etc/vfstab 2072# 2073# $@ given disks to find which are free, default is all disks in 2074# the test system 2075# 2076# @return a string containing the list of available disks 2077#*/ 2078function find_disks 2079{ 2080 # Trust provided list, no attempt is made to locate unused devices. 2081 if is_linux || is_freebsd; then 2082 echo "$@" 2083 return 2084 fi 2085 2086 2087 sfi=/tmp/swaplist.$$ 2088 dmpi=/tmp/dumpdev.$$ 2089 max_finddisksnum=${MAX_FINDDISKSNUM:-6} 2090 2091 swap -l > $sfi 2092 dumpadm > $dmpi 2>/dev/null 2093 2094 disks=${@:-$(echo "" | format -e 2>/dev/null | awk ' 2095BEGIN { FS="."; } 2096 2097/^Specify disk/{ 2098 searchdisks=0; 2099} 2100 2101{ 2102 if (searchdisks && $2 !~ "^$"){ 2103 split($2,arr," "); 2104 print arr[1]; 2105 } 2106} 2107 2108/^AVAILABLE DISK SELECTIONS:/{ 2109 searchdisks=1; 2110} 2111')} 2112 2113 unused="" 2114 for disk in $disks; do 2115 # Check for mounted 2116 grep -q "${disk}[sp]" /etc/mnttab && continue 2117 # Check for swap 2118 grep -q "${disk}[sp]" $sfi && continue 2119 # check for dump device 2120 grep -q "${disk}[sp]" $dmpi && continue 2121 # check to see if this disk hasn't been explicitly excluded 2122 # by a user-set environment variable 2123 echo "${ZFS_HOST_DEVICES_IGNORE}" | grep -q "${disk}" && continue 2124 unused_candidates="$unused_candidates $disk" 2125 done 2126 rm $sfi $dmpi 2127 2128# now just check to see if those disks do actually exist 2129# by looking for a device pointing to the first slice in 2130# each case. limit the number to max_finddisksnum 2131 count=0 2132 for disk in $unused_candidates; do 2133 if is_disk_device $DEV_DSKDIR/${disk}s0 && \ 2134 [ $count -lt $max_finddisksnum ]; then 2135 unused="$unused $disk" 2136 # do not impose limit if $@ is provided 2137 [[ -z $@ ]] && ((count = count + 1)) 2138 fi 2139 done 2140 2141# finally, return our disk list 2142 echo $unused 2143} 2144 2145function add_user_freebsd #<group_name> <user_name> <basedir> 2146{ 2147 typeset group=$1 2148 typeset user=$2 2149 typeset basedir=$3 2150 2151 # Check to see if the user exists. 2152 if id $user > /dev/null 2>&1; then 2153 return 0 2154 fi 2155 2156 # Assign 1000 as the base uid 2157 typeset -i uid=1000 2158 while true; do 2159 pw useradd -u $uid -g $group -d $basedir/$user -m -n $user 2160 case $? in 2161 0) break ;; 2162 # The uid is not unique 2163 65) ((uid += 1)) ;; 2164 *) return 1 ;; 2165 esac 2166 if [[ $uid == 65000 ]]; then 2167 log_fail "No user id available under 65000 for $user" 2168 fi 2169 done 2170 2171 # Silence MOTD 2172 touch $basedir/$user/.hushlogin 2173 2174 return 0 2175} 2176 2177# 2178# Delete the specified user. 2179# 2180# $1 login name 2181# 2182function del_user_freebsd #<logname> 2183{ 2184 typeset user=$1 2185 2186 if id $user > /dev/null 2>&1; then 2187 log_must pw userdel $user 2188 fi 2189 2190 return 0 2191} 2192 2193# 2194# Select valid gid and create specified group. 2195# 2196# $1 group name 2197# 2198function add_group_freebsd #<group_name> 2199{ 2200 typeset group=$1 2201 2202 # See if the group already exists. 2203 if pw groupshow $group >/dev/null 2>&1; then 2204 return 0 2205 fi 2206 2207 # Assign 1000 as the base gid 2208 typeset -i gid=1000 2209 while true; do 2210 pw groupadd -g $gid -n $group > /dev/null 2>&1 2211 case $? in 2212 0) return 0 ;; 2213 # The gid is not unique 2214 65) ((gid += 1)) ;; 2215 *) return 1 ;; 2216 esac 2217 if [[ $gid == 65000 ]]; then 2218 log_fail "No user id available under 65000 for $group" 2219 fi 2220 done 2221} 2222 2223# 2224# Delete the specified group. 2225# 2226# $1 group name 2227# 2228function del_group_freebsd #<group_name> 2229{ 2230 typeset group=$1 2231 2232 pw groupdel -n $group > /dev/null 2>&1 2233 case $? in 2234 # Group does not exist, or was deleted successfully. 2235 0|6|65) return 0 ;; 2236 # Name already exists as a group name 2237 9) log_must pw groupdel $group ;; 2238 *) return 1 ;; 2239 esac 2240 2241 return 0 2242} 2243 2244function add_user_illumos #<group_name> <user_name> <basedir> 2245{ 2246 typeset group=$1 2247 typeset user=$2 2248 typeset basedir=$3 2249 2250 log_must useradd -g $group -d $basedir/$user -m $user 2251 2252 return 0 2253} 2254 2255function del_user_illumos #<user_name> 2256{ 2257 typeset user=$1 2258 2259 if id $user > /dev/null 2>&1; then 2260 log_must_retry "currently used" 6 userdel $user 2261 fi 2262 2263 return 0 2264} 2265 2266function add_group_illumos #<group_name> 2267{ 2268 typeset group=$1 2269 2270 typeset -i gid=100 2271 while true; do 2272 groupadd -g $gid $group > /dev/null 2>&1 2273 case $? in 2274 0) return 0 ;; 2275 # The gid is not unique 2276 4) ((gid += 1)) ;; 2277 *) return 1 ;; 2278 esac 2279 done 2280} 2281 2282function del_group_illumos #<group_name> 2283{ 2284 typeset group=$1 2285 2286 groupmod -n $grp $grp > /dev/null 2>&1 2287 case $? in 2288 # Group does not exist. 2289 6) return 0 ;; 2290 # Name already exists as a group name 2291 9) log_must groupdel $grp ;; 2292 *) return 1 ;; 2293 esac 2294} 2295 2296function add_user_linux #<group_name> <user_name> <basedir> 2297{ 2298 typeset group=$1 2299 typeset user=$2 2300 typeset basedir=$3 2301 2302 log_must useradd -g $group -d $basedir/$user -m $user 2303 2304 # Add new users to the same group and the command line utils. 2305 # This allows them to be run out of the original users home 2306 # directory as long as it permissioned to be group readable. 2307 cmd_group=$(stat --format="%G" $(command -v zfs)) 2308 log_must usermod -a -G $cmd_group $user 2309 2310 return 0 2311} 2312 2313function del_user_linux #<user_name> 2314{ 2315 typeset user=$1 2316 2317 if id $user > /dev/null 2>&1; then 2318 log_must_retry "currently used" 6 userdel $user 2319 fi 2320} 2321 2322function add_group_linux #<group_name> 2323{ 2324 typeset group=$1 2325 2326 # Assign 100 as the base gid, a larger value is selected for 2327 # Linux because for many distributions 1000 and under are reserved. 2328 while true; do 2329 groupadd $group > /dev/null 2>&1 2330 case $? in 2331 0) return 0 ;; 2332 *) return 1 ;; 2333 esac 2334 done 2335} 2336 2337function del_group_linux #<group_name> 2338{ 2339 typeset group=$1 2340 2341 getent group $group > /dev/null 2>&1 2342 case $? in 2343 # Group does not exist. 2344 2) return 0 ;; 2345 # Name already exists as a group name 2346 0) log_must groupdel $group ;; 2347 *) return 1 ;; 2348 esac 2349 2350 return 0 2351} 2352 2353# 2354# Add specified user to specified group 2355# 2356# $1 group name 2357# $2 user name 2358# $3 base of the homedir (optional) 2359# 2360function add_user #<group_name> <user_name> <basedir> 2361{ 2362 typeset group=$1 2363 typeset user=$2 2364 typeset basedir=${3:-"/var/tmp"} 2365 2366 if ((${#group} == 0 || ${#user} == 0)); then 2367 log_fail "group name or user name are not defined." 2368 fi 2369 2370 case "$UNAME" in 2371 FreeBSD) 2372 add_user_freebsd "$group" "$user" "$basedir" 2373 ;; 2374 Linux) 2375 add_user_linux "$group" "$user" "$basedir" 2376 ;; 2377 *) 2378 add_user_illumos "$group" "$user" "$basedir" 2379 ;; 2380 esac 2381 2382 return 0 2383} 2384 2385# 2386# Delete the specified user. 2387# 2388# $1 login name 2389# $2 base of the homedir (optional) 2390# 2391function del_user #<logname> <basedir> 2392{ 2393 typeset user=$1 2394 typeset basedir=${2:-"/var/tmp"} 2395 2396 if ((${#user} == 0)); then 2397 log_fail "login name is necessary." 2398 fi 2399 2400 case "$UNAME" in 2401 FreeBSD) 2402 del_user_freebsd "$user" 2403 ;; 2404 Linux) 2405 del_user_linux "$user" 2406 ;; 2407 *) 2408 del_user_illumos "$user" 2409 ;; 2410 esac 2411 2412 [[ -d $basedir/$user ]] && rm -fr $basedir/$user 2413 2414 return 0 2415} 2416 2417# 2418# Select valid gid and create specified group. 2419# 2420# $1 group name 2421# 2422function add_group #<group_name> 2423{ 2424 typeset group=$1 2425 2426 if ((${#group} == 0)); then 2427 log_fail "group name is necessary." 2428 fi 2429 2430 case "$UNAME" in 2431 FreeBSD) 2432 add_group_freebsd "$group" 2433 ;; 2434 Linux) 2435 add_group_linux "$group" 2436 ;; 2437 *) 2438 add_group_illumos "$group" 2439 ;; 2440 esac 2441 2442 return 0 2443} 2444 2445# 2446# Delete the specified group. 2447# 2448# $1 group name 2449# 2450function del_group #<group_name> 2451{ 2452 typeset group=$1 2453 2454 if ((${#group} == 0)); then 2455 log_fail "group name is necessary." 2456 fi 2457 2458 case "$UNAME" in 2459 FreeBSD) 2460 del_group_freebsd "$group" 2461 ;; 2462 Linux) 2463 del_group_linux "$group" 2464 ;; 2465 *) 2466 del_group_illumos "$group" 2467 ;; 2468 esac 2469 2470 return 0 2471} 2472 2473# 2474# This function will return true if it's safe to destroy the pool passed 2475# as argument 1. It checks for pools based on zvols and files, and also 2476# files contained in a pool that may have a different mountpoint. 2477# 2478function safe_to_destroy_pool { # $1 the pool name 2479 2480 typeset pool="" 2481 typeset DONT_DESTROY="" 2482 2483 # We check that by deleting the $1 pool, we're not 2484 # going to pull the rug out from other pools. Do this 2485 # by looking at all other pools, ensuring that they 2486 # aren't built from files or zvols contained in this pool. 2487 2488 for pool in $(zpool list -H -o name) 2489 do 2490 ALTMOUNTPOOL="" 2491 2492 # this is a list of the top-level directories in each of the 2493 # files that make up the path to the files the pool is based on 2494 FILEPOOL=$(zpool status -v $pool | awk -v pool="/$1/" '$0 ~ pool {print $1}') 2495 2496 # this is a list of the zvols that make up the pool 2497 ZVOLPOOL=$(zpool status -v $pool | awk -v zvols="$ZVOL_DEVDIR/$1$" '$0 ~ zvols {print $1}') 2498 2499 # also want to determine if it's a file-based pool using an 2500 # alternate mountpoint... 2501 POOL_FILE_DIRS=$(zpool status -v $pool | \ 2502 awk '/\// {print $1}' | \ 2503 awk -F/ '!/dev/ {print $2}') 2504 2505 for pooldir in $POOL_FILE_DIRS 2506 do 2507 OUTPUT=$(zfs list -H -r -o mountpoint $1 | \ 2508 awk -v pd="${pooldir}$" '$0 ~ pd {print $1}') 2509 2510 ALTMOUNTPOOL="${ALTMOUNTPOOL}${OUTPUT}" 2511 done 2512 2513 2514 if [ ! -z "$ZVOLPOOL" ] 2515 then 2516 DONT_DESTROY="true" 2517 log_note "Pool $pool is built from $ZVOLPOOL on $1" 2518 fi 2519 2520 if [ ! -z "$FILEPOOL" ] 2521 then 2522 DONT_DESTROY="true" 2523 log_note "Pool $pool is built from $FILEPOOL on $1" 2524 fi 2525 2526 if [ ! -z "$ALTMOUNTPOOL" ] 2527 then 2528 DONT_DESTROY="true" 2529 log_note "Pool $pool is built from $ALTMOUNTPOOL on $1" 2530 fi 2531 done 2532 2533 if [ -z "${DONT_DESTROY}" ] 2534 then 2535 return 0 2536 else 2537 log_note "Warning: it is not safe to destroy $1!" 2538 return 1 2539 fi 2540} 2541 2542# 2543# Verify zfs operation with -p option work as expected 2544# $1 operation, value could be create, clone or rename 2545# $2 dataset type, value could be fs or vol 2546# $3 dataset name 2547# $4 new dataset name 2548# 2549function verify_opt_p_ops 2550{ 2551 typeset ops=$1 2552 typeset datatype=$2 2553 typeset dataset=$3 2554 typeset newdataset=$4 2555 2556 if [[ $datatype != "fs" && $datatype != "vol" ]]; then 2557 log_fail "$datatype is not supported." 2558 fi 2559 2560 # check parameters accordingly 2561 case $ops in 2562 create) 2563 newdataset=$dataset 2564 dataset="" 2565 if [[ $datatype == "vol" ]]; then 2566 ops="create -V $VOLSIZE" 2567 fi 2568 ;; 2569 clone) 2570 if [[ -z $newdataset ]]; then 2571 log_fail "newdataset should not be empty" \ 2572 "when ops is $ops." 2573 fi 2574 log_must datasetexists $dataset 2575 log_must snapexists $dataset 2576 ;; 2577 rename) 2578 if [[ -z $newdataset ]]; then 2579 log_fail "newdataset should not be empty" \ 2580 "when ops is $ops." 2581 fi 2582 log_must datasetexists $dataset 2583 ;; 2584 *) 2585 log_fail "$ops is not supported." 2586 ;; 2587 esac 2588 2589 # make sure the upper level filesystem does not exist 2590 destroy_dataset "${newdataset%/*}" "-rRf" 2591 2592 # without -p option, operation will fail 2593 log_mustnot zfs $ops $dataset $newdataset 2594 log_mustnot datasetexists $newdataset ${newdataset%/*} 2595 2596 # with -p option, operation should succeed 2597 log_must zfs $ops -p $dataset $newdataset 2598 block_device_wait 2599 2600 if ! datasetexists $newdataset ; then 2601 log_fail "-p option does not work for $ops" 2602 fi 2603 2604 # when $ops is create or clone, redo the operation still return zero 2605 if [[ $ops != "rename" ]]; then 2606 log_must zfs $ops -p $dataset $newdataset 2607 fi 2608 2609 return 0 2610} 2611 2612# 2613# Get configuration of pool 2614# $1 pool name 2615# $2 config name 2616# 2617function get_config 2618{ 2619 typeset pool=$1 2620 typeset config=$2 2621 2622 if ! poolexists "$pool" ; then 2623 return 1 2624 fi 2625 if [ "$(get_pool_prop cachefile "$pool")" = "none" ]; then 2626 zdb -e $pool 2627 else 2628 zdb -C $pool 2629 fi | awk -F: -v cfg="$config:" '$0 ~ cfg {sub(/^'\''/, $2); sub(/'\''$/, $2); print $2}' 2630} 2631 2632# 2633# Privated function. Random select one of items from arguments. 2634# 2635# $1 count 2636# $2-n string 2637# 2638function _random_get 2639{ 2640 typeset cnt=$1 2641 shift 2642 2643 typeset str="$@" 2644 typeset -i ind 2645 ((ind = RANDOM % cnt + 1)) 2646 2647 echo "$str" | cut -f $ind -d ' ' 2648} 2649 2650# 2651# Random select one of item from arguments which include NONE string 2652# 2653function random_get_with_non 2654{ 2655 typeset -i cnt=$# 2656 ((cnt =+ 1)) 2657 2658 _random_get "$cnt" "$@" 2659} 2660 2661# 2662# Random select one of item from arguments which doesn't include NONE string 2663# 2664function random_get 2665{ 2666 _random_get "$#" "$@" 2667} 2668 2669# 2670# The function will generate a dataset name with specific length 2671# $1, the length of the name 2672# $2, the base string to construct the name 2673# 2674function gen_dataset_name 2675{ 2676 typeset -i len=$1 2677 typeset basestr="$2" 2678 typeset -i baselen=${#basestr} 2679 typeset -i iter=0 2680 typeset l_name="" 2681 2682 if ((len % baselen == 0)); then 2683 ((iter = len / baselen)) 2684 else 2685 ((iter = len / baselen + 1)) 2686 fi 2687 while ((iter > 0)); do 2688 l_name="${l_name}$basestr" 2689 2690 ((iter -= 1)) 2691 done 2692 2693 echo $l_name 2694} 2695 2696# 2697# Get cksum tuple of dataset 2698# $1 dataset name 2699# 2700# sample zdb output: 2701# Dataset data/test [ZPL], ID 355, cr_txg 2413856, 31.0K, 7 objects, rootbp 2702# DVA[0]=<0:803046400:200> DVA[1]=<0:81199000:200> [L0 DMU objset] fletcher4 2703# lzjb LE contiguous unique double size=800L/200P birth=2413856L/2413856P 2704# fill=7 cksum=11ce125712:643a9c18ee2:125e25238fca0:254a3f74b59744 2705function datasetcksum 2706{ 2707 typeset cksum 2708 sync 2709 sync_all_pools 2710 zdb -vvv $1 | awk -F= -v ds="^Dataset $1 "'\\[' '$0 ~ ds && /cksum/ {print $7}' 2711} 2712 2713# 2714# Get the given disk/slice state from the specific field of the pool 2715# 2716function get_device_state #pool disk field("", "spares","logs") 2717{ 2718 typeset pool=$1 2719 typeset disk=${2#$DEV_DSKDIR/} 2720 typeset field=${3:-$pool} 2721 2722 zpool status -v "$pool" 2>/dev/null | \ 2723 awk -v device=$disk -v pool=$pool -v field=$field \ 2724 'BEGIN {startconfig=0; startfield=0; } 2725 /config:/ {startconfig=1} 2726 (startconfig==1) && ($1==field) {startfield=1; next;} 2727 (startfield==1) && ($1==device) {print $2; exit;} 2728 (startfield==1) && 2729 ($1==field || $1 ~ "^spares$" || $1 ~ "^logs$") {startfield=0}' 2730} 2731 2732# 2733# get the root filesystem name if it's zfsroot system. 2734# 2735# return: root filesystem name 2736function get_rootfs 2737{ 2738 typeset rootfs="" 2739 2740 if is_freebsd; then 2741 rootfs=$(mount -p | awk '$2 == "/" && $3 == "zfs" {print $1}') 2742 elif ! is_linux; then 2743 rootfs=$(awk '$2 == "/" && $3 == "zfs" {print $1}' \ 2744 /etc/mnttab) 2745 fi 2746 if [[ -z "$rootfs" ]]; then 2747 log_fail "Can not get rootfs" 2748 fi 2749 if datasetexists $rootfs; then 2750 echo $rootfs 2751 else 2752 log_fail "This is not a zfsroot system." 2753 fi 2754} 2755 2756# 2757# get the rootfs's pool name 2758# return: 2759# rootpool name 2760# 2761function get_rootpool 2762{ 2763 typeset rootfs=$(get_rootfs) 2764 echo ${rootfs%%/*} 2765} 2766 2767# 2768# To verify if the require numbers of disks is given 2769# 2770function verify_disk_count 2771{ 2772 typeset -i min=${2:-1} 2773 2774 typeset -i count=$(echo "$1" | wc -w) 2775 2776 if ((count < min)); then 2777 log_untested "A minimum of $min disks is required to run." \ 2778 " You specified $count disk(s)" 2779 fi 2780} 2781 2782function ds_is_volume 2783{ 2784 typeset type=$(get_prop type $1) 2785 [ $type = "volume" ] 2786} 2787 2788function ds_is_filesystem 2789{ 2790 typeset type=$(get_prop type $1) 2791 [ $type = "filesystem" ] 2792} 2793 2794# 2795# Check if Trusted Extensions are installed and enabled 2796# 2797function is_te_enabled 2798{ 2799 svcs -H -o state labeld 2>/dev/null | grep -q "enabled" 2800} 2801 2802# Return the number of CPUs (cross-platform) 2803function get_num_cpus 2804{ 2805 if is_linux ; then 2806 grep -c '^processor' /proc/cpuinfo 2807 elif is_freebsd; then 2808 sysctl -n kern.smp.cpus 2809 else 2810 psrinfo | wc -l 2811 fi 2812} 2813 2814# Utility function to determine if a system has multiple cpus. 2815function is_mp 2816{ 2817 [[ $(get_num_cpus) -gt 1 ]] 2818} 2819 2820function get_cpu_freq 2821{ 2822 if is_linux; then 2823 lscpu | awk '/CPU MHz/ { print $3 }' 2824 elif is_freebsd; then 2825 sysctl -n hw.clockrate 2826 else 2827 psrinfo -v 0 | awk '/processor operates at/ {print $6}' 2828 fi 2829} 2830 2831# Run the given command as the user provided. 2832function user_run 2833{ 2834 typeset user=$1 2835 shift 2836 2837 log_note "user: $user" 2838 log_note "cmd: $*" 2839 2840 typeset out=$TEST_BASE_DIR/out 2841 typeset err=$TEST_BASE_DIR/err 2842 2843 sudo -Eu $user env PATH="$PATH" ksh <<<"$*" >$out 2>$err 2844 typeset res=$? 2845 log_note "out: $(<$out)" 2846 log_note "err: $(<$err)" 2847 return $res 2848} 2849 2850# 2851# Check if the pool contains the specified vdevs 2852# 2853# $1 pool 2854# $2..n <vdev> ... 2855# 2856# Return 0 if the vdevs are contained in the pool, 1 if any of the specified 2857# vdevs is not in the pool, and 2 if pool name is missing. 2858# 2859function vdevs_in_pool 2860{ 2861 typeset pool=$1 2862 typeset vdev 2863 2864 if [[ -z $pool ]]; then 2865 log_note "Missing pool name." 2866 return 2 2867 fi 2868 2869 shift 2870 2871 # We could use 'zpool list' to only get the vdevs of the pool but we 2872 # can't reference a mirror/raidz vdev using its ID (i.e mirror-0), 2873 # therefore we use the 'zpool status' output. 2874 typeset tmpfile=$(mktemp) 2875 zpool status -v "$pool" | grep -A 1000 "config:" >$tmpfile 2876 for vdev in "$@"; do 2877 grep -wq ${vdev##*/} $tmpfile || return 1 2878 done 2879 2880 rm -f $tmpfile 2881 return 0 2882} 2883 2884function get_max 2885{ 2886 typeset -l i max=$1 2887 shift 2888 2889 for i in "$@"; do 2890 max=$((max > i ? max : i)) 2891 done 2892 2893 echo $max 2894} 2895 2896# Write data that can be compressed into a directory 2897function write_compressible 2898{ 2899 typeset dir=$1 2900 typeset megs=$2 2901 typeset nfiles=${3:-1} 2902 typeset bs=${4:-1024k} 2903 typeset fname=${5:-file} 2904 2905 [[ -d $dir ]] || log_fail "No directory: $dir" 2906 2907 # Under Linux fio is not currently used since its behavior can 2908 # differ significantly across versions. This includes missing 2909 # command line options and cases where the --buffer_compress_* 2910 # options fail to behave as expected. 2911 if is_linux; then 2912 typeset file_bytes=$(to_bytes $megs) 2913 typeset bs_bytes=4096 2914 typeset blocks=$(($file_bytes / $bs_bytes)) 2915 2916 for (( i = 0; i < $nfiles; i++ )); do 2917 truncate -s $file_bytes $dir/$fname.$i 2918 2919 # Write every third block to get 66% compression. 2920 for (( j = 0; j < $blocks; j += 3 )); do 2921 dd if=/dev/urandom of=$dir/$fname.$i \ 2922 seek=$j bs=$bs_bytes count=1 \ 2923 conv=notrunc >/dev/null 2>&1 2924 done 2925 done 2926 else 2927 command -v fio > /dev/null || log_unsupported "fio missing" 2928 log_must eval fio \ 2929 --name=job \ 2930 --fallocate=0 \ 2931 --minimal \ 2932 --randrepeat=0 \ 2933 --buffer_compress_percentage=66 \ 2934 --buffer_compress_chunk=4096 \ 2935 --directory="$dir" \ 2936 --numjobs="$nfiles" \ 2937 --nrfiles="$nfiles" \ 2938 --rw=write \ 2939 --bs="$bs" \ 2940 --filesize="$megs" \ 2941 "--filename_format='$fname.\$jobnum' >/dev/null" 2942 fi 2943} 2944 2945function get_objnum 2946{ 2947 typeset pathname=$1 2948 typeset objnum 2949 2950 [[ -e $pathname ]] || log_fail "No such file or directory: $pathname" 2951 if is_freebsd; then 2952 objnum=$(stat -f "%i" $pathname) 2953 else 2954 objnum=$(stat -c %i $pathname) 2955 fi 2956 echo $objnum 2957} 2958 2959# 2960# Sync data to the pool 2961# 2962# $1 pool name 2963# $2 boolean to force uberblock (and config including zpool cache file) update 2964# 2965function sync_pool #pool <force> 2966{ 2967 typeset pool=${1:-$TESTPOOL} 2968 typeset force=${2:-false} 2969 2970 if [[ $force == true ]]; then 2971 log_must zpool sync -f $pool 2972 else 2973 log_must zpool sync $pool 2974 fi 2975 2976 return 0 2977} 2978 2979# 2980# Sync all pools 2981# 2982# $1 boolean to force uberblock (and config including zpool cache file) update 2983# 2984function sync_all_pools #<force> 2985{ 2986 typeset force=${1:-false} 2987 2988 if [[ $force == true ]]; then 2989 log_must zpool sync -f 2990 else 2991 log_must zpool sync 2992 fi 2993 2994 return 0 2995} 2996 2997# 2998# Wait for zpool 'freeing' property drops to zero. 2999# 3000# $1 pool name 3001# 3002function wait_freeing #pool 3003{ 3004 typeset pool=${1:-$TESTPOOL} 3005 while true; do 3006 [[ "0" == "$(zpool list -Ho freeing $pool)" ]] && break 3007 log_must sleep 1 3008 done 3009} 3010 3011# 3012# Wait for every device replace operation to complete 3013# 3014# $1 pool name 3015# $2 timeout 3016# 3017function wait_replacing #pool timeout 3018{ 3019 typeset timeout=${2:-300} 3020 typeset pool=${1:-$TESTPOOL} 3021 for (( timer = 0; timer < $timeout; timer++ )); do 3022 is_pool_replacing $pool || break; 3023 sleep 1; 3024 done 3025} 3026 3027# Wait for a pool to be scrubbed 3028# 3029# $1 pool name 3030# $2 timeout 3031# 3032function wait_scrubbed #pool timeout 3033{ 3034 typeset timeout=${2:-300} 3035 typeset pool=${1:-$TESTPOOL} 3036 for (( timer = 0; timer < $timeout; timer++ )); do 3037 is_pool_scrubbed $pool && break; 3038 sleep 1; 3039 done 3040} 3041 3042# Backup the zed.rc in our test directory so that we can edit it for our test. 3043# 3044# Returns: Backup file name. You will need to pass this to zed_rc_restore(). 3045function zed_rc_backup 3046{ 3047 zedrc_backup="$(mktemp)" 3048 cp $ZEDLET_DIR/zed.rc $zedrc_backup 3049 echo $zedrc_backup 3050} 3051 3052function zed_rc_restore 3053{ 3054 mv $1 $ZEDLET_DIR/zed.rc 3055} 3056 3057# 3058# Setup custom environment for the ZED. 3059# 3060# $@ Optional list of zedlets to run under zed. 3061function zed_setup 3062{ 3063 if ! is_linux; then 3064 log_unsupported "No zed on $UNAME" 3065 fi 3066 3067 if [[ ! -d $ZEDLET_DIR ]]; then 3068 log_must mkdir $ZEDLET_DIR 3069 fi 3070 3071 if [[ ! -e $VDEVID_CONF ]]; then 3072 log_must touch $VDEVID_CONF 3073 fi 3074 3075 if [[ -e $VDEVID_CONF_ETC ]]; then 3076 log_fail "Must not have $VDEVID_CONF_ETC file present on system" 3077 fi 3078 EXTRA_ZEDLETS=$@ 3079 3080 # Create a symlink for /etc/zfs/vdev_id.conf file. 3081 log_must ln -s $VDEVID_CONF $VDEVID_CONF_ETC 3082 3083 # Setup minimal ZED configuration. Individual test cases should 3084 # add additional ZEDLETs as needed for their specific test. 3085 log_must cp ${ZEDLET_ETC_DIR}/zed.rc $ZEDLET_DIR 3086 log_must cp ${ZEDLET_ETC_DIR}/zed-functions.sh $ZEDLET_DIR 3087 3088 # Scripts must only be user writable. 3089 if [[ -n "$EXTRA_ZEDLETS" ]] ; then 3090 saved_umask=$(umask) 3091 log_must umask 0022 3092 for i in $EXTRA_ZEDLETS ; do 3093 log_must cp ${ZEDLET_LIBEXEC_DIR}/$i $ZEDLET_DIR 3094 done 3095 log_must umask $saved_umask 3096 fi 3097 3098 # Customize the zed.rc file to enable the full debug log. 3099 log_must sed -i '/\#ZED_DEBUG_LOG=.*/d' $ZEDLET_DIR/zed.rc 3100 echo "ZED_DEBUG_LOG=$ZED_DEBUG_LOG" >>$ZEDLET_DIR/zed.rc 3101 3102} 3103 3104# 3105# Cleanup custom ZED environment. 3106# 3107# $@ Optional list of zedlets to remove from our test zed.d directory. 3108function zed_cleanup 3109{ 3110 if ! is_linux; then 3111 return 3112 fi 3113 3114 for extra_zedlet; do 3115 log_must rm -f ${ZEDLET_DIR}/$extra_zedlet 3116 done 3117 log_must rm -fd ${ZEDLET_DIR}/zed.rc ${ZEDLET_DIR}/zed-functions.sh ${ZEDLET_DIR}/all-syslog.sh ${ZEDLET_DIR}/all-debug.sh ${ZEDLET_DIR}/state \ 3118 $ZED_LOG $ZED_DEBUG_LOG $VDEVID_CONF_ETC $VDEVID_CONF \ 3119 $ZEDLET_DIR 3120} 3121 3122# 3123# Check if ZED is currently running; if so, returns PIDs 3124# 3125function zed_check 3126{ 3127 if ! is_linux; then 3128 return 3129 fi 3130 zedpids="$(pgrep -x zed)" 3131 zedpids2="$(pgrep -x lt-zed)" 3132 echo ${zedpids} ${zedpids2} 3133} 3134 3135# 3136# Check if ZED is currently running, if not start ZED. 3137# 3138function zed_start 3139{ 3140 if ! is_linux; then 3141 return 3142 fi 3143 3144 # ZEDLET_DIR=/var/tmp/zed 3145 if [[ ! -d $ZEDLET_DIR ]]; then 3146 log_must mkdir $ZEDLET_DIR 3147 fi 3148 3149 # Verify the ZED is not already running. 3150 zedpids=$(zed_check) 3151 if [ -n "$zedpids" ]; then 3152 # We never, ever, really want it to just keep going if zed 3153 # is already running - usually this implies our test cases 3154 # will break very strangely because whatever we wanted to 3155 # configure zed for won't be listening to our changes in the 3156 # tmpdir 3157 log_fail "ZED already running - ${zedpids}" 3158 else 3159 log_note "Starting ZED" 3160 # run ZED in the background and redirect foreground logging 3161 # output to $ZED_LOG. 3162 log_must truncate -s 0 $ZED_DEBUG_LOG 3163 log_must eval "zed -vF -d $ZEDLET_DIR -P $PATH" \ 3164 "-s $ZEDLET_DIR/state -j 1 2>$ZED_LOG &" 3165 fi 3166 3167 return 0 3168} 3169 3170# 3171# Kill ZED process 3172# 3173function zed_stop 3174{ 3175 if ! is_linux; then 3176 return "" 3177 fi 3178 3179 log_note "Stopping ZED" 3180 while true; do 3181 zedpids=$(zed_check) 3182 [ ! -n "$zedpids" ] && break 3183 3184 log_must kill $zedpids 3185 sleep 1 3186 done 3187 return 0 3188} 3189 3190# 3191# Drain all zevents 3192# 3193function zed_events_drain 3194{ 3195 while [ $(zpool events -H | wc -l) -ne 0 ]; do 3196 sleep 1 3197 zpool events -c >/dev/null 3198 done 3199} 3200 3201# Set a variable in zed.rc to something, un-commenting it in the process. 3202# 3203# $1 variable 3204# $2 value 3205function zed_rc_set 3206{ 3207 var="$1" 3208 val="$2" 3209 # Remove the line 3210 cmd="'/$var/d'" 3211 eval sed -i $cmd $ZEDLET_DIR/zed.rc 3212 3213 # Add it at the end 3214 echo "$var=$val" >> $ZEDLET_DIR/zed.rc 3215} 3216 3217 3218# 3219# Check is provided device is being active used as a swap device. 3220# 3221function is_swap_inuse 3222{ 3223 typeset device=$1 3224 3225 if [[ -z $device ]] ; then 3226 log_note "No device specified." 3227 return 1 3228 fi 3229 3230 case "$UNAME" in 3231 Linux) 3232 swapon -s | grep -wq $(readlink -f $device) 3233 ;; 3234 FreeBSD) 3235 swapctl -l | grep -wq $device 3236 ;; 3237 *) 3238 swap -l | grep -wq $device 3239 ;; 3240 esac 3241} 3242 3243# 3244# Setup a swap device using the provided device. 3245# 3246function swap_setup 3247{ 3248 typeset swapdev=$1 3249 3250 case "$UNAME" in 3251 Linux) 3252 log_must eval "mkswap $swapdev > /dev/null 2>&1" 3253 log_must swapon $swapdev 3254 ;; 3255 FreeBSD) 3256 log_must swapctl -a $swapdev 3257 ;; 3258 *) 3259 log_must swap -a $swapdev 3260 ;; 3261 esac 3262 3263 return 0 3264} 3265 3266# 3267# Cleanup a swap device on the provided device. 3268# 3269function swap_cleanup 3270{ 3271 typeset swapdev=$1 3272 3273 if is_swap_inuse $swapdev; then 3274 if is_linux; then 3275 log_must swapoff $swapdev 3276 elif is_freebsd; then 3277 log_must swapoff $swapdev 3278 else 3279 log_must swap -d $swapdev 3280 fi 3281 fi 3282 3283 return 0 3284} 3285 3286# 3287# Set a global system tunable (64-bit value) 3288# 3289# $1 tunable name (use a NAME defined in tunables.cfg) 3290# $2 tunable values 3291# 3292function set_tunable64 3293{ 3294 set_tunable_impl "$1" "$2" Z 3295} 3296 3297# 3298# Set a global system tunable (32-bit value) 3299# 3300# $1 tunable name (use a NAME defined in tunables.cfg) 3301# $2 tunable values 3302# 3303function set_tunable32 3304{ 3305 set_tunable_impl "$1" "$2" W 3306} 3307 3308function set_tunable_impl 3309{ 3310 typeset name="$1" 3311 typeset value="$2" 3312 typeset mdb_cmd="$3" 3313 3314 eval "typeset tunable=\$$name" 3315 case "$tunable" in 3316 UNSUPPORTED) 3317 log_unsupported "Tunable '$name' is unsupported on $UNAME" 3318 ;; 3319 "") 3320 log_fail "Tunable '$name' must be added to tunables.cfg" 3321 ;; 3322 *) 3323 ;; 3324 esac 3325 3326 [[ -z "$value" ]] && return 1 3327 [[ -z "$mdb_cmd" ]] && return 1 3328 3329 case "$UNAME" in 3330 Linux) 3331 typeset zfs_tunables="/sys/module/zfs/parameters" 3332 echo "$value" >"$zfs_tunables/$tunable" 3333 ;; 3334 FreeBSD) 3335 sysctl vfs.zfs.$tunable=$value 3336 ;; 3337 SunOS) 3338 echo "${tunable}/${mdb_cmd}0t${value}" | mdb -kw 3339 ;; 3340 esac 3341} 3342 3343function save_tunable 3344{ 3345 [[ ! -d $TEST_BASE_DIR ]] && return 1 3346 [[ -e $TEST_BASE_DIR/tunable-$1 ]] && return 2 3347 echo "$(get_tunable """$1""")" > "$TEST_BASE_DIR"/tunable-"$1" 3348} 3349 3350function restore_tunable 3351{ 3352 [[ ! -e $TEST_BASE_DIR/tunable-$1 ]] && return 1 3353 val="$(cat $TEST_BASE_DIR/tunable-"""$1""")" 3354 set_tunable64 "$1" "$val" 3355 rm $TEST_BASE_DIR/tunable-$1 3356} 3357 3358# 3359# Get a global system tunable 3360# 3361# $1 tunable name (use a NAME defined in tunables.cfg) 3362# 3363function get_tunable 3364{ 3365 get_tunable_impl "$1" 3366} 3367 3368function get_tunable_impl 3369{ 3370 typeset name="$1" 3371 typeset module="${2:-zfs}" 3372 typeset check_only="$3" 3373 3374 eval "typeset tunable=\$$name" 3375 case "$tunable" in 3376 UNSUPPORTED) 3377 if [ -z "$check_only" ] ; then 3378 log_unsupported "Tunable '$name' is unsupported on $UNAME" 3379 else 3380 return 1 3381 fi 3382 ;; 3383 "") 3384 if [ -z "$check_only" ] ; then 3385 log_fail "Tunable '$name' must be added to tunables.cfg" 3386 else 3387 return 1 3388 fi 3389 ;; 3390 *) 3391 ;; 3392 esac 3393 3394 case "$UNAME" in 3395 Linux) 3396 typeset zfs_tunables="/sys/module/$module/parameters" 3397 cat $zfs_tunables/$tunable 3398 ;; 3399 FreeBSD) 3400 sysctl -n vfs.zfs.$tunable 3401 ;; 3402 SunOS) 3403 [[ "$module" -eq "zfs" ]] || return 1 3404 ;; 3405 esac 3406} 3407 3408# Does a tunable exist? 3409# 3410# $1: Tunable name 3411function tunable_exists 3412{ 3413 get_tunable_impl $1 "zfs" 1 3414} 3415 3416# 3417# Compute MD5 digest for given file or stdin if no file given. 3418# Note: file path must not contain spaces 3419# 3420function md5digest 3421{ 3422 typeset file=$1 3423 3424 case "$UNAME" in 3425 FreeBSD) 3426 md5 -q $file 3427 ;; 3428 *) 3429 typeset sum _ 3430 read -r sum _ < <(md5sum -b $file) 3431 echo $sum 3432 ;; 3433 esac 3434} 3435 3436# 3437# Compute SHA256 digest for given file or stdin if no file given. 3438# Note: file path must not contain spaces 3439# 3440function sha256digest 3441{ 3442 typeset file=$1 3443 3444 case "$UNAME" in 3445 FreeBSD) 3446 sha256 -q $file 3447 ;; 3448 *) 3449 typeset sum _ 3450 read -r sum _ < <(sha256sum -b $file) 3451 echo $sum 3452 ;; 3453 esac 3454} 3455 3456function new_fs #<args> 3457{ 3458 case "$UNAME" in 3459 FreeBSD) 3460 newfs "$@" 3461 ;; 3462 *) 3463 echo y | newfs -v "$@" 3464 ;; 3465 esac 3466} 3467 3468function stat_size #<path> 3469{ 3470 typeset path=$1 3471 3472 case "$UNAME" in 3473 FreeBSD) 3474 stat -f %z "$path" 3475 ;; 3476 *) 3477 stat -c %s "$path" 3478 ;; 3479 esac 3480} 3481 3482function stat_mtime #<path> 3483{ 3484 typeset path=$1 3485 3486 case "$UNAME" in 3487 FreeBSD) 3488 stat -f %m "$path" 3489 ;; 3490 *) 3491 stat -c %Y "$path" 3492 ;; 3493 esac 3494} 3495 3496function stat_ctime #<path> 3497{ 3498 typeset path=$1 3499 3500 case "$UNAME" in 3501 FreeBSD) 3502 stat -f %c "$path" 3503 ;; 3504 *) 3505 stat -c %Z "$path" 3506 ;; 3507 esac 3508} 3509 3510function stat_crtime #<path> 3511{ 3512 typeset path=$1 3513 3514 case "$UNAME" in 3515 FreeBSD) 3516 stat -f %B "$path" 3517 ;; 3518 *) 3519 stat -c %W "$path" 3520 ;; 3521 esac 3522} 3523 3524function stat_generation #<path> 3525{ 3526 typeset path=$1 3527 3528 case "$UNAME" in 3529 Linux) 3530 getversion "${path}" 3531 ;; 3532 *) 3533 stat -f %v "${path}" 3534 ;; 3535 esac 3536} 3537 3538# Run a command as if it was being run in a TTY. 3539# 3540# Usage: 3541# 3542# faketty command 3543# 3544function faketty 3545{ 3546 if is_freebsd; then 3547 script -q /dev/null env "$@" 3548 else 3549 script --return --quiet -c "$*" /dev/null 3550 fi 3551} 3552 3553# 3554# Produce a random permutation of the integers in a given range (inclusive). 3555# 3556function range_shuffle # begin end 3557{ 3558 typeset -i begin=$1 3559 typeset -i end=$2 3560 3561 seq ${begin} ${end} | sort -R 3562} 3563 3564# 3565# Cross-platform xattr helpers 3566# 3567 3568function get_xattr # name path 3569{ 3570 typeset name=$1 3571 typeset path=$2 3572 3573 case "$UNAME" in 3574 FreeBSD) 3575 getextattr -qq user "${name}" "${path}" 3576 ;; 3577 *) 3578 attr -qg "${name}" "${path}" 3579 ;; 3580 esac 3581} 3582 3583function set_xattr # name value path 3584{ 3585 typeset name=$1 3586 typeset value=$2 3587 typeset path=$3 3588 3589 case "$UNAME" in 3590 FreeBSD) 3591 setextattr user "${name}" "${value}" "${path}" 3592 ;; 3593 *) 3594 attr -qs "${name}" -V "${value}" "${path}" 3595 ;; 3596 esac 3597} 3598 3599function set_xattr_stdin # name value 3600{ 3601 typeset name=$1 3602 typeset path=$2 3603 3604 case "$UNAME" in 3605 FreeBSD) 3606 setextattr -i user "${name}" "${path}" 3607 ;; 3608 *) 3609 attr -qs "${name}" "${path}" 3610 ;; 3611 esac 3612} 3613 3614function rm_xattr # name path 3615{ 3616 typeset name=$1 3617 typeset path=$2 3618 3619 case "$UNAME" in 3620 FreeBSD) 3621 rmextattr -q user "${name}" "${path}" 3622 ;; 3623 *) 3624 attr -qr "${name}" "${path}" 3625 ;; 3626 esac 3627} 3628 3629function ls_xattr # path 3630{ 3631 typeset path=$1 3632 3633 case "$UNAME" in 3634 FreeBSD) 3635 lsextattr -qq user "${path}" 3636 ;; 3637 *) 3638 attr -ql "${path}" 3639 ;; 3640 esac 3641} 3642 3643function kstat # stat flags? 3644{ 3645 typeset stat=$1 3646 typeset flags=${2-"-n"} 3647 3648 case "$UNAME" in 3649 FreeBSD) 3650 sysctl $flags kstat.zfs.misc.$stat 3651 ;; 3652 Linux) 3653 cat "/proc/spl/kstat/zfs/$stat" 2>/dev/null 3654 ;; 3655 *) 3656 false 3657 ;; 3658 esac 3659} 3660 3661function get_arcstat # stat 3662{ 3663 typeset stat=$1 3664 3665 case "$UNAME" in 3666 FreeBSD) 3667 kstat arcstats.$stat 3668 ;; 3669 Linux) 3670 kstat arcstats | awk "/$stat/"' { print $3 }' 3671 ;; 3672 *) 3673 false 3674 ;; 3675 esac 3676} 3677 3678function punch_hole # offset length file 3679{ 3680 typeset offset=$1 3681 typeset length=$2 3682 typeset file=$3 3683 3684 case "$UNAME" in 3685 FreeBSD) 3686 truncate -d -o $offset -l $length "$file" 3687 ;; 3688 Linux) 3689 fallocate --punch-hole --offset $offset --length $length "$file" 3690 ;; 3691 *) 3692 false 3693 ;; 3694 esac 3695} 3696 3697function zero_range # offset length file 3698{ 3699 typeset offset=$1 3700 typeset length=$2 3701 typeset file=$3 3702 3703 case "$UNAME" in 3704 Linux) 3705 fallocate --zero-range --offset $offset --length $length "$file" 3706 ;; 3707 *) 3708 false 3709 ;; 3710 esac 3711} 3712 3713# 3714# Wait for the specified arcstat to reach non-zero quiescence. 3715# If echo is 1 echo the value after reaching quiescence, otherwise 3716# if echo is 0 print the arcstat we are waiting on. 3717# 3718function arcstat_quiescence # stat echo 3719{ 3720 typeset stat=$1 3721 typeset echo=$2 3722 typeset do_once=true 3723 3724 if [[ $echo -eq 0 ]]; then 3725 echo "Waiting for arcstat $1 quiescence." 3726 fi 3727 3728 while $do_once || [ $stat1 -ne $stat2 ] || [ $stat2 -eq 0 ]; do 3729 typeset stat1=$(get_arcstat $stat) 3730 sleep 0.5 3731 typeset stat2=$(get_arcstat $stat) 3732 do_once=false 3733 done 3734 3735 if [[ $echo -eq 1 ]]; then 3736 echo $stat2 3737 fi 3738} 3739 3740function arcstat_quiescence_noecho # stat 3741{ 3742 typeset stat=$1 3743 arcstat_quiescence $stat 0 3744} 3745 3746function arcstat_quiescence_echo # stat 3747{ 3748 typeset stat=$1 3749 arcstat_quiescence $stat 1 3750} 3751 3752# 3753# Given an array of pids, wait until all processes 3754# have completed and check their return status. 3755# 3756function wait_for_children #children 3757{ 3758 rv=0 3759 children=("$@") 3760 for child in "${children[@]}" 3761 do 3762 child_exit=0 3763 wait ${child} || child_exit=$? 3764 if [ $child_exit -ne 0 ]; then 3765 echo "child ${child} failed with ${child_exit}" 3766 rv=1 3767 fi 3768 done 3769 return $rv 3770} 3771 3772# 3773# Compare two directory trees recursively in a manner similar to diff(1), but 3774# using rsync. If there are any discrepancies, a summary of the differences are 3775# output and a non-zero error is returned. 3776# 3777# If you're comparing a directory after a ZIL replay, you should set 3778# LIBTEST_DIFF_ZIL_REPLAY=1 or use replay_directory_diff which will cause 3779# directory_diff to ignore mtime changes (the ZIL replay won't fix up mtime 3780# information). 3781# 3782function directory_diff # dir_a dir_b 3783{ 3784 dir_a="$1" 3785 dir_b="$2" 3786 zil_replay="${LIBTEST_DIFF_ZIL_REPLAY:-0}" 3787 3788 # If one of the directories doesn't exist, return 2. This is to match the 3789 # semantics of diff. 3790 if ! [ -d "$dir_a" -a -d "$dir_b" ]; then 3791 return 2 3792 fi 3793 3794 # Run rsync with --dry-run --itemize-changes to get something akin to diff 3795 # output, but rsync is far more thorough in detecting differences (diff 3796 # doesn't compare file metadata, and cannot handle special files). 3797 # 3798 # Also make sure to filter out non-user.* xattrs when comparing. On 3799 # SELinux-enabled systems the copied tree will probably have different 3800 # SELinux labels. 3801 args=("-nicaAHX" '--filter=-x! user.*' "--delete") 3802 3803 # NOTE: Quite a few rsync builds do not support --crtimes which would be 3804 # necessary to verify that creation times are being maintained properly. 3805 # Unfortunately because of this we cannot use it unconditionally but we can 3806 # check if this rsync build supports it and use it then. This check is 3807 # based on the same check in the rsync test suite (testsuite/crtimes.test). 3808 # 3809 # We check ctimes even with zil_replay=1 because the ZIL does store 3810 # creation times and we should make sure they match (if the creation times 3811 # do not match there is a "c" entry in one of the columns). 3812 if rsync --version | grep -q "[, ] crtimes"; then 3813 args+=("--crtimes") 3814 else 3815 log_note "This rsync package does not support --crtimes (-N)." 3816 fi 3817 3818 # If we are testing a ZIL replay, we need to ignore timestamp changes. 3819 # Unfortunately --no-times doesn't do what we want -- it will still tell 3820 # you if the timestamps don't match but rsync will set the timestamps to 3821 # the current time (leading to an itemised change entry). It's simpler to 3822 # just filter out those lines. 3823 if [ "$zil_replay" -eq 0 ]; then 3824 filter=("cat") 3825 else 3826 # Different rsync versions have different numbers of columns. So just 3827 # require that aside from the first two, all other columns must be 3828 # blank (literal ".") or a timestamp field ("[tT]"). 3829 filter=("grep" "-v" '^\..[.Tt]\+ ') 3830 fi 3831 3832 diff="$(rsync "${args[@]}" "$dir_a/" "$dir_b/" | "${filter[@]}")" 3833 rv=0 3834 if [ -n "$diff" ]; then 3835 echo "$diff" 3836 rv=1 3837 fi 3838 return $rv 3839} 3840 3841# 3842# Compare two directory trees recursively, without checking whether the mtimes 3843# match (creation times will be checked if the available rsync binary supports 3844# it). This is necessary for ZIL replay checks (because the ZIL does not 3845# contain mtimes and thus after a ZIL replay, mtimes won't match). 3846# 3847# This is shorthand for LIBTEST_DIFF_ZIL_REPLAY=1 directory_diff <...>. 3848# 3849function replay_directory_diff # dir_a dir_b 3850{ 3851 LIBTEST_DIFF_ZIL_REPLAY=1 directory_diff "$@" 3852} 3853 3854# 3855# Put coredumps into $1/core.{basename} 3856# 3857# Output must be saved and passed to pop_coredump_pattern on cleanup 3858# 3859function push_coredump_pattern # dir 3860{ 3861 ulimit -c unlimited 3862 case "$UNAME" in 3863 Linux) 3864 cat /proc/sys/kernel/core_pattern /proc/sys/kernel/core_uses_pid 3865 echo "$1/core.%e" >/proc/sys/kernel/core_pattern && 3866 echo 0 >/proc/sys/kernel/core_uses_pid 3867 ;; 3868 FreeBSD) 3869 sysctl -n kern.corefile 3870 sysctl kern.corefile="$1/core.%N" >/dev/null 3871 ;; 3872 *) 3873 # Nothing to output – set only for this shell 3874 coreadm -p "$1/core.%f" 3875 ;; 3876 esac 3877} 3878 3879# 3880# Put coredumps back into the default location 3881# 3882function pop_coredump_pattern 3883{ 3884 [ -s "$1" ] || return 0 3885 case "$UNAME" in 3886 Linux) 3887 typeset pat pid 3888 { read -r pat; read -r pid; } < "$1" 3889 echo "$pat" >/proc/sys/kernel/core_pattern && 3890 echo "$pid" >/proc/sys/kernel/core_uses_pid 3891 ;; 3892 FreeBSD) 3893 sysctl kern.corefile="$(<"$1")" >/dev/null 3894 ;; 3895 esac 3896} 3897