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