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