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