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