1#
2# This file and its contents are supplied under the terms of the
3# Common Development and Distribution License ("CDDL"), version 1.0.
4# You may only use this file in accordance with the terms of version
5# 1.0 of the CDDL.
6#
7# A full copy of the text of the CDDL should have accompanied this
8# source.  A copy of the CDDL is also available via the Internet at
9# http://www.illumos.org/license/CDDL.
10#
11
12#
13# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
14# Use is subject to license terms.
15# Copyright (c) 2012, 2016 by Delphix. All rights reserved.
16# Copyright 2016 Nexenta Systems, Inc.
17# Copyright (c) 2016, 2017 by Intel Corporation. All rights reserved.
18# Copyright (c) 2017 Lawrence Livermore National Security, LLC.
19# Copyright (c) 2017 Datto Inc.
20# Copyright (c) 2017 Open-E, Inc. All Rights Reserved.
21#
22
23#
24# Returns SCSI host number for the given disk
25#
26function get_scsi_host #disk
27{
28	typeset disk=$1
29	ls /sys/block/${disk}/device/scsi_device | cut -d : -f 1
30}
31
32#
33# Cause a scan of all scsi host adapters by default
34#
35# $1 optional host number
36#
37function scan_scsi_hosts
38{
39	typeset hostnum=${1}
40
41	if is_linux; then
42		if [[ -z $hostnum ]]; then
43			for host in /sys/class/scsi_host/host*; do
44				log_must eval "echo '- - -' > $host/scan"
45			done
46		else
47			log_must eval \
48			    "echo /sys/class/scsi_host/host$hostnum/scan" \
49			    > /dev/null
50			log_must eval \
51			    "echo '- - -' > /sys/class/scsi_host/host$hostnum/scan"
52		fi
53	fi
54}
55
56#
57# Wait for newly created block devices to have their minors created.
58#
59function block_device_wait
60{
61	if is_linux; then
62		udevadm trigger
63		udevadm settle
64	fi
65}
66
67#
68# Check if the given device is physical device
69#
70function is_physical_device #device
71{
72	typeset device=${1#$DEV_DSKDIR}
73	device=${device#$DEV_RDSKDIR}
74
75	if is_linux; then
76		[[ -b "$DEV_DSKDIR/$device" ]] && \
77		[[ -f /sys/module/loop/parameters/max_part ]]
78		return $?
79	else
80		echo $device | egrep "^c[0-F]+([td][0-F]+)+$" > /dev/null 2>&1
81		return $?
82	fi
83}
84
85#
86# Check if the given device is a real device (ie SCSI device)
87#
88function is_real_device #disk
89{
90	typeset disk=$1
91	[[ -z $disk ]] && log_fail "No argument for disk given."
92
93	if is_linux; then
94		lsblk $DEV_RDSKDIR/$disk -o TYPE 2>/dev/null | \
95		    egrep disk >/dev/null
96		return $?
97	fi
98}
99
100#
101# Check if the given device is a loop device
102#
103function is_loop_device #disk
104{
105	typeset disk=$1
106	[[ -z $disk ]] && log_fail "No argument for disk given."
107
108	if is_linux; then
109		lsblk $DEV_RDSKDIR/$disk -o TYPE 2>/dev/null | \
110		    egrep loop >/dev/null
111		return $?
112	fi
113}
114
115#
116# Check if the given device is a multipath device and if there is a symbolic
117# link to a device mapper and to a disk
118# Currently no support for dm devices alone without multipath
119#
120function is_mpath_device #disk
121{
122	typeset disk=$1
123	[[ -z $disk ]] && log_fail "No argument for disk given."
124
125	if is_linux; then
126		lsblk $DEV_MPATHDIR/$disk -o TYPE 2>/dev/null | \
127		   egrep mpath >/dev/null
128		if (($? == 0)); then
129			readlink $DEV_MPATHDIR/$disk > /dev/null 2>&1
130			return $?
131		else
132			return $?
133		fi
134	fi
135}
136
137# Set the slice prefix for disk partitioning depending
138# on whether the device is a real, multipath, or loop device.
139# Currently all disks have to be of the same type, so only
140# checks first disk to determine slice prefix.
141#
142function set_slice_prefix
143{
144	typeset disk
145	typeset -i i=0
146
147	if is_linux; then
148		while (( i < $DISK_ARRAY_NUM )); do
149			disk="$(echo $DISKS | nawk '{print $(i + 1)}')"
150			if ( is_mpath_device $disk ) && [[ -z $(echo $disk | awk 'substr($1,18,1)\
151			     ~ /^[[:digit:]]+$/') ]] || ( is_real_device $disk ); then
152				export SLICE_PREFIX=""
153				return 0
154			elif ( is_mpath_device $disk || is_loop_device \
155			    $disk ); then
156				export SLICE_PREFIX="p"
157				return 0
158			else
159				log_fail "$disk not supported for partitioning."
160			fi
161			(( i = i + 1))
162		done
163	fi
164}
165
166#
167# Set the directory path of the listed devices in $DISK_ARRAY_NUM
168# Currently all disks have to be of the same type, so only
169# checks first disk to determine device directory
170# default = /dev (linux)
171# real disk = /dev (linux)
172# multipath device = /dev/mapper (linux)
173#
174function set_device_dir
175{
176	typeset disk
177	typeset -i i=0
178
179	if is_linux; then
180		while (( i < $DISK_ARRAY_NUM )); do
181			disk="$(echo $DISKS | nawk '{print $(i + 1)}')"
182			if is_mpath_device $disk; then
183				export DEV_DSKDIR=$DEV_MPATHDIR
184				return 0
185			else
186				export DEV_DSKDIR=$DEV_RDSKDIR
187				return 0
188			fi
189			(( i = i + 1))
190		done
191	else
192		export DEV_DSKDIR=$DEV_RDSKDIR
193	fi
194}
195
196#
197# Get the directory path of given device
198#
199function get_device_dir #device
200{
201	typeset device=$1
202
203	if ! $(is_physical_device $device) ; then
204		if [[ $device != "/" ]]; then
205			device=${device%/*}
206		fi
207		if [[ -b "$DEV_DSKDIR/$device" ]]; then
208			device="$DEV_DSKDIR"
209		fi
210		echo $device
211	else
212		echo "$DEV_DSKDIR"
213	fi
214}
215
216#
217# Get persistent name for given disk
218#
219function get_persistent_disk_name #device
220{
221	typeset device=$1
222	typeset dev_id
223
224	if is_linux; then
225		if is_real_device $device; then
226			dev_id="$(udevadm info -q all -n $DEV_DSKDIR/$device \
227			    | egrep disk/by-id | nawk '{print $2; exit}' \
228			    | nawk -F / '{print $3}')"
229			echo $dev_id
230		elif is_mpath_device $device; then
231			dev_id="$(udevadm info -q all -n $DEV_DSKDIR/$device \
232			    | egrep disk/by-id/dm-uuid \
233			    | nawk '{print $2; exit}' \
234			    | nawk -F / '{print $3}')"
235			echo $dev_id
236		else
237			echo $device
238		fi
239	else
240		echo $device
241	fi
242}
243
244#
245# Online or offline a disk on the system
246#
247# First checks state of disk. Test will fail if disk is not properly onlined
248# or offlined. Online is a full rescan of SCSI disks by echoing to every
249# host entry.
250#
251function on_off_disk # disk state{online,offline} host
252{
253	typeset disk=$1
254	typeset state=$2
255	typeset host=$3
256
257	[[ -z $disk ]] || [[ -z $state ]] &&  \
258	    log_fail "Arguments invalid or missing"
259
260	if is_linux; then
261		if [[ $state == "offline" ]] && ( is_mpath_device $disk ); then
262			dm_name="$(readlink $DEV_DSKDIR/$disk \
263			    | nawk -F / '{print $2}')"
264			slave="$(ls /sys/block/${dm_name}/slaves \
265			    | nawk '{print $1}')"
266			while [[ -n $slave ]]; do
267				#check if disk is online
268				lsscsi | egrep $slave > /dev/null
269				if (($? == 0)); then
270					slave_dir="/sys/block/${dm_name}"
271					slave_dir+="/slaves/${slave}/device"
272					ss="${slave_dir}/state"
273					sd="${slave_dir}/delete"
274					log_must eval "echo 'offline' > ${ss}"
275					log_must eval "echo '1' > ${sd}"
276					lsscsi | egrep $slave > /dev/null
277						if (($? == 0)); then
278							log_fail "Offlining" \
279							    "$disk failed"
280						fi
281				fi
282				slave="$(ls /sys/block/$dm_name/slaves \
283				    2>/dev/null | nawk '{print $1}')"
284			done
285		elif [[ $state == "offline" ]] && ( is_real_device $disk ); then
286			#check if disk is online
287			lsscsi | egrep $disk > /dev/null
288			if (($? == 0)); then
289				dev_state="/sys/block/$disk/device/state"
290				dev_delete="/sys/block/$disk/device/delete"
291				log_must eval "echo 'offline' > ${dev_state}"
292				log_must eval "echo '1' > ${dev_delete}"
293				lsscsi | egrep $disk > /dev/null
294					if (($? == 0)); then
295						log_fail "Offlining $disk" \
296						    "failed"
297					fi
298			else
299				log_note "$disk is already offline"
300			fi
301		elif [[ $state == "online" ]]; then
302			#force a full rescan
303			scan_scsi_hosts $host
304			block_device_wait
305			if is_mpath_device $disk; then
306				dm_name="$(readlink $DEV_DSKDIR/$disk \
307				    | nawk -F / '{print $2}')"
308				slave="$(ls /sys/block/$dm_name/slaves \
309				    | nawk '{print $1}')"
310				lsscsi | egrep $slave > /dev/null
311				if (($? != 0)); then
312					log_fail "Onlining $disk failed"
313				fi
314			elif is_real_device $disk; then
315				block_device_wait
316				typeset -i retries=0
317				while ! lsscsi | egrep -q $disk; do
318					if (( $retries > 2 )); then
319						log_fail "Onlining $disk failed"
320						break
321					fi
322					(( ++retries ))
323					sleep 1
324				done
325			else
326				log_fail "$disk is not a real dev"
327			fi
328		else
329			log_fail "$disk failed to $state"
330		fi
331	fi
332}
333
334#
335# Simulate disk removal
336#
337function remove_disk #disk
338{
339	typeset disk=$1
340	on_off_disk $disk "offline"
341	block_device_wait
342}
343
344#
345# Simulate disk insertion for the given SCSI host
346#
347function insert_disk #disk scsi_host
348{
349	typeset disk=$1
350	typeset scsi_host=$2
351	on_off_disk $disk "online" $scsi_host
352	block_device_wait
353}
354
355#
356# Load scsi_debug module with specified parameters
357# $blksz can be either one of: < 512b | 512e | 4Kn >
358#
359function load_scsi_debug # dev_size_mb add_host num_tgts max_luns blksz
360{
361	typeset devsize=$1
362	typeset hosts=$2
363	typeset tgts=$3
364	typeset luns=$4
365	typeset blksz=$5
366
367	[[ -z $devsize ]] || [[ -z $hosts ]] || [[ -z $tgts ]] || \
368	    [[ -z $luns ]] || [[ -z $blksz ]] && \
369	    log_fail "Arguments invalid or missing"
370
371	case "$5" in
372		'512b')
373			typeset sector=512
374			typeset blkexp=0
375		;;
376		'512e')
377			typeset sector=512
378			typeset blkexp=3
379		;;
380		'4Kn')
381			typeset sector=4096
382			typeset blkexp=0
383		;;
384		*) log_fail "Unsupported blksz value: $5" ;;
385	esac
386
387	if is_linux; then
388		modprobe -n scsi_debug
389		if (($? != 0)); then
390			log_unsupported "Platform does not have scsi_debug"
391			    "module"
392		fi
393		lsmod | egrep scsi_debug > /dev/null
394		if (($? == 0)); then
395			log_fail "scsi_debug module already installed"
396		else
397			log_must modprobe scsi_debug dev_size_mb=$devsize \
398			    add_host=$hosts num_tgts=$tgts max_luns=$luns \
399			    sector_size=$sector physblk_exp=$blkexp
400			block_device_wait
401			lsscsi | egrep scsi_debug > /dev/null
402			if (($? == 1)); then
403				log_fail "scsi_debug module install failed"
404			fi
405		fi
406	fi
407}
408
409#
410# Unload scsi_debug module, if needed.
411#
412function unload_scsi_debug
413{
414	log_must_retry "in use" 5 modprobe -r scsi_debug
415}
416
417#
418# Get scsi_debug device name.
419# Returns basename of scsi_debug device (for example "sdb").
420#
421function get_debug_device
422{
423	for i in {1..10} ; do
424		val=$(lsscsi | nawk '/scsi_debug/ {print $6; exit}' | cut -d / -f3)
425
426		# lsscsi can take time to settle
427		if [ "$val" != "-" ] ; then
428			break
429		fi
430		sleep 1
431	done
432	echo "$val"
433}
434
435#
436# Get actual devices used by the pool (i.e. linux sdb1 not sdb).
437#
438function get_pool_devices #testpool #devdir
439{
440	typeset testpool=$1
441	typeset devdir=$2
442	typeset out=""
443
444	if is_linux; then
445		out=$(zpool status -P $testpool |grep ${devdir} | awk '{print $1}')
446		out=$(echo $out | sed -e "s|${devdir}/||g" | tr '\n' ' ')
447	fi
448	echo $out
449}
450