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