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