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