blob: 43e4003b4d9b0e3cc57e19bdac12328a1ad73ead [file] [log] [blame]
Thiemo Nageldeb84732019-07-17 15:14:34 +02001#!/bin/sh
2# Copyright 2014 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6# This runs from the factory install/reset shim. This MUST be run
7# from USB, in developer mode. This script contains functions to erase
8# securly the disk and verify it has been erased properly.
9
10# if used inside the test harness, 2 variables are defined:
Thiemo Nageleb494272019-09-04 12:20:08 +020011# TEST_DELAY: to reduce the amount of time checking for the eMMC to be ready
Thiemo Nageldeb84732019-07-17 15:14:34 +020012# again,
13# TEST_FIO_OUTPUT: to send the output of fio to a known file. By default,
14# if the file is generated with $(mktemp fio_output_XXXXXX)
15
aysusayine3eb9872019-08-07 12:31:49 +020016. /usr/share/misc/chromeos-common.sh
17
18# Return value for partial success when strict is set.
19STRICT_RETURN_VALUE=100
20
21
22# Outputs the NVMe namespace block devices that belong to the given
23# character device.
24# Arguments:
25# - NVMe character device without preceding /dev/ (ex: nvme0)
26# Outputs NVMe namespaces' block devices (ex: /dev/nvme0n1)
27get_nvme_block_devs() {
28 local nvme="$1"
29 local block_dev
30 for block_dev in /sys/block/${nvme}*; do
31 echo "/dev/${block_dev##*/}"
32 done
33}
34
35# Outputs the size of given NVMe character device
36# Arguments:
37# - NVMe character device (ex: /dev/nvme0)
38get_nvme_char_device_size() {
39 local disk="$1"
40 local dev_size="$(nvme id-ctrl --output-format=json "${dev}" | jq ".tnvmcap")"
41 if [ ${dev_size} -eq 0 ]; then
42 # This may not be exact size of the disk because there might be some
43 # unused blocks.
44 local blk blk_size
45 for blk in $(get_nvme_block_devs "${disk##*/}"); do
46 blk_size="$(blockdev --getsize64 ${blk})"
47 : $(( dev_size += blk_size ))
48 done
49 fi
50 echo "${dev_size}"
51}
52
53# Get the supported erase mode for ATA disk.
54# Arguments:
55# - ATA device to query
56# Outputs supported erase mode: "--security-erase" or
57# "--security-erase-enhanced", empty string if not supported or unknown.
58get_ata_supported_erase_mode() {
59 local disk="$1"
60 local func
61 local supported_mode
62 for func in "supported" "supported: enhanced erase"; do
63 hdparm -I "${disk}" \
64 | grep -A10 "^Security" \
65 | grep -q "^[[:blank:]]\+${func}$"
66 if [ $? -eq 0 ]; then
67 if [ "${func}" = "supported" ]; then
68 supported_mode="--security-erase"
69 else
70 supported_mode="--security-erase-enhanced"
71 fi
72 fi
73 done
74 echo "${supported_mode}"
75}
76
77# Check if the ATA device supports block erase sanitize.
78# Arguments:
79# - device to query
80# Return true if the operation is supported, false if not supported.
81is_sata_sanitize_supported() {
82 local dev="$1"
83 hdparm -I "${dev}" | grep -q "BLOCK_ERASE_EXT command"
84 return $?
85}
86
87# Check if the most recent sanitize operation was completed successfully
88# Arguments:
89# - device to query
90# This is blocking until the sanitize has finished. Return true if the most
91# recent sanitize operation was successful, false otherwise.
92is_sata_sanitize_successful() {
93 local dev="$1"
94 hdparm --sanitize-status "${disk}" \
95 | grep -iq "Last Sanitize Operation Completed Without Error"
96}
97
98# Check if the NVMe device supports block erase sanitize.
99# Arguments:
100# - device to query (block device or character device)
101# Return true if the operation is supported, false if not supported.
102is_nvme_sanitize_supported() {
103 local dev="$1"
104 # Check bit #1. If it is set to 1, device supports block erase sanitize.
105 local sanicap=$(nvme id-ctrl --output-format=json "${dev}" \
106 | jq ".sanicap")
107 test $(( sanicap / 2 % 2 )) -eq 1
108 return $?
109}
110
111# Check if the device is currently being sanitized.
112# Arguments:
113# - device to query
114# Return true if is being sanitized, false otherwise.
115is_nvme_sanitize_in_progress() {
116 local dev="$1"
117 # Check last 3 bits of the sanitize status value. If it is set to 010, the
118 # sanitize operation is in progress.
119 local status=$(nvme sanitize-log "${dev}" \
120 | grep "(SSTAT)" \
121 | grep -oEi "(0x)?[[:xdigit:]]+$")
122 test $(( status % 8 )) -eq 2
123 return $?
124}
125
126# Check if the most recent sanitize operation was completed successfully
127# Arguments:
128# - device to query
129# Return true if the most recent sanitize operation was successful, false
130# otherwise.
131is_nvme_sanitize_successful() {
132 local dev="$1"
133 # Check last 3 bits of the sanitize status value. If it is set to 001,
134 # sanitize operation was successful.
135 local status=$(nvme sanitize-log "${dev}" \
136 | grep "(SSTAT)" \
137 | grep -oEi "(0x)?[[:xdigit:]]+$")
138 test $(( status % 8 )) -eq 1
139 return $?
140}
141
142# Output the progress of nvme sanitize command. This value indicates the
143# fraction complete of the sanitize operation. The value is the numerator of
144# the fraction complete that has 65536 as its denominator. This value is set
145# to FFFFh (65536) if there is no sanitize in progress.
146# Arguments:
147# - device to query
148get_nvme_sanitize_progress() {
149 local dev="$1"
150 nvme sanitize-log "${dev}" | grep "(SPROG)" | grep -oEi "[[:xdigit:]]+$"
Thiemo Nageldeb84732019-07-17 15:14:34 +0200151}
152
153# Return useful bits of the MMC status
154#
155# Return some status bits that indicates if the device has completed
156# outstanding commands.
157#
158# if 'mmc status get' fails returns 0, which is an invalid status.
159get_mmc_status() {
160 local status
161 status=$(mmc status get "$1" | sed -nre 's/^SEND_STATUS response: (.*)/\1/p')
162 # The state is defined in chapter 6.13 of eMMC rev 5.
163 # Ideally, we should check that all the error bits to be set to 0.
164 # Now, reading in more details, eMMC device are not garantee to be always
165 # valid (see X or R mode) or some bit are reserved.
166 # Therefore, we limit only to flags that are always valid:
167 # bit 6: EXCEPTION_EVENT: set to 0
Thiemo Nageleb494272019-09-04 12:20:08 +0200168 # bit 8: READY_FOR_DATA: set to 1 (0 while sanitizing)
Thiemo Nageldeb84732019-07-17 15:14:34 +0200169 # bit 9-12: CURRENT_STATE: set to 4 (Tran), set to 7 (Prg while sanitizing)
170 printf "0x%08x\n" $((status & 0x00001F40))
171}
172
Thiemo Nageleb494272019-09-04 12:20:08 +0200173# Erase an MMC device using firmware functions
Thiemo Nageldeb84732019-07-17 15:14:34 +0200174#
175# Ask the device to trim all sectors.
176# Then, ask the device to physically erase all trimmed sectors.
aysusayine3eb9872019-08-07 12:31:49 +0200177# Arguments:
178# - block device to erase
179# - strict: if set to "1", then only returns success if sanitize successfully
180# physically erases the device when security is supported. If left
181# empty or set to "0", returns success when sanitize succeeds
182# whether security is supported or not.
Thiemo Nageldeb84732019-07-17 15:14:34 +0200183secure_erase_mmc() {
184 local disk="$1"
aysusayine3eb9872019-08-07 12:31:49 +0200185 local strict="$2"
Thiemo Nageldeb84732019-07-17 15:14:34 +0200186 local delay=${TEST_DELAY:-5}
187 local count
188 local secure
189 local rc
190
Thiemo Nageleb494272019-09-04 12:20:08 +0200191 # Mark all location as unused -- try secure first.
192 for secure in "-s" ""; do
193 blkdiscard ${secure} "${disk}"
Thiemo Nageldeb84732019-07-17 15:14:34 +0200194 rc=$?
195 if [ ${rc} -eq 0 ]; then
196 break
197 fi
198 done
199 if [ ${rc} -ne 0 ]; then
200 echo "security not supported, just doing overwrite"
Thiemo Nageldeb84732019-07-17 15:14:34 +0200201 fi
202
Thiemo Nageldeb84732019-07-17 15:14:34 +0200203 # Physically erase unused locations.
Thiemo Nageleb494272019-09-04 12:20:08 +0200204 # 0x00000900 equals to READY_FOR_DATA=1 and CURRENT_STATE=4 (Tran)
Thiemo Nageldeb84732019-07-17 15:14:34 +0200205 mmc_orig_status=$(get_mmc_status "${disk}")
206 if [ "${mmc_orig_status}" != "0x00000900" ]; then
207 echo "Not ready for sanitize: status ${mmc_orig_status}."
208 return 1
209 fi
aysusayine3eb9872019-08-07 12:31:49 +0200210
211 mmc sanitize "${disk}" || return $?
Thiemo Nageldeb84732019-07-17 15:14:34 +0200212
213 count=120 # wait up to 10 minutes
214 mmc_status="0xffffffff"
215 while [ "${mmc_status}" != "${mmc_orig_status}" -a ${count} -gt 0 ]; do
216 sleep "${delay}"
217 mmc_status=$(get_mmc_status "${disk}")
aysusayine3eb9872019-08-07 12:31:49 +0200218 : $(( count -= 1 ))
Thiemo Nageldeb84732019-07-17 15:14:34 +0200219 done
220
221 if [ "${mmc_status}" != "${mmc_orig_status}" ]; then
222 echo "Device is stuck sanitizing: status ${mmc_status}."
223 return 1
224 fi
aysusayine3eb9872019-08-07 12:31:49 +0200225
226 if [ "${strict}" = "1" ] && [ ${rc} -ne 0 ]; then
227 return ${STRICT_RETURN_VALUE}
228 else
229 return 0
230 fi
Thiemo Nageldeb84732019-07-17 15:14:34 +0200231}
232
233# Erase an ATA device using internal firmware function
234#
235# To trigger the ATA SECURE ERASE function, the disk must be
236# in security mode SEC4 (aka locked) or SEC5 (aka secured).
237# Disks are usually in SEC1 (unsecured).
238# First put the disk in SEC5 then Erase it, that put it back in SEC1.
aysusayine3eb9872019-08-07 12:31:49 +0200239# Arguments:
240# - block device to erase
241# - strict: if set to "1", then only returns success if enhanced security
242# erase is supported and succeeds. Security-erase does not guarantee
243# to erase unallocated sectors whereas enhanced security erase does.
244# If left empty or set to "0", returns success if any of the
245# security erase succeed.
Thiemo Nageldeb84732019-07-17 15:14:34 +0200246secure_erase_sata() {
247 local disk="$1"
aysusayine3eb9872019-08-07 12:31:49 +0200248 local strict="$2"
Thiemo Nageldeb84732019-07-17 15:14:34 +0200249 local temp_password="chromeos"
aysusayine3eb9872019-08-07 12:31:49 +0200250 local erase_mode="$(get_ata_supported_erase_mode "${disk}")"
251 local partial_success_return_val=0
Thiemo Nageldeb84732019-07-17 15:14:34 +0200252
aysusayine3eb9872019-08-07 12:31:49 +0200253 if [ "${strict}" = "1" ]; then
254 partial_success_return_val=${STRICT_RETURN_VALUE}
255 fi
256
257 if is_sata_sanitize_supported "${disk}"; then
258 hdparm --yes-i-know-what-i-am-doing --sanitize-block-erase "${disk}"
259 is_sata_sanitize_successful "${disk}" && return 0
260 fi
261
262 if [ -n "${erase_mode}" ]; then
Thiemo Nageldeb84732019-07-17 15:14:34 +0200263 hdparm --user-master u --security-set-pass \
aysusayine3eb9872019-08-07 12:31:49 +0200264 "${temp_password}" "${disk}" || return $?
265 hdparm --user-master u "${erase_mode}" \
Thiemo Nageldeb84732019-07-17 15:14:34 +0200266 "${temp_password}" "${disk}" || return $?
267 else
268 echo "security not supported, just doing overwrite"
269 fi
aysusayine3eb9872019-08-07 12:31:49 +0200270 return ${partial_success_return_val}
Thiemo Nageldeb84732019-07-17 15:14:34 +0200271}
272
aysusayine3eb9872019-08-07 12:31:49 +0200273# Erase an NVMe device.
Thiemo Nageldeb84732019-07-17 15:14:34 +0200274#
aysusayine3eb9872019-08-07 12:31:49 +0200275# Use nvme sanitize if it is supported. Otherwise try nvme format with crypto
276# mode, if that fails, then try to do with user data mode with a timeout
277# proportional to the size of the device.
278# Arguments:
279# - character device to erase
280# - strict: if set to "1", then only returns success if sanitize successfully
281# physically erases the device. If left empty or set to "0", returns
282# success if either sanitize or nvme format succeeds.
Thiemo Nageldeb84732019-07-17 15:14:34 +0200283secure_erase_nvme() {
284 local disk="$1"
aysusayine3eb9872019-08-07 12:31:49 +0200285 local strict="$2"
Thiemo Nageldeb84732019-07-17 15:14:34 +0200286 local ses_user="1" # 0: no secure, 1: user data erase, 2: cryptographic erase
287 local ses_crypto="2"
aysusayine3eb9872019-08-07 12:31:49 +0200288 local base_timeout_in_ms=$(( 60 * 1000 )) # base timeout for 1 minute
289 local dev_size="$(get_nvme_char_device_size "${disk}")"
290 local bytes_per_ms=$(( 100 * 1024 )) # 100 MB/s
291 # Maximum time without any progress during sanitize operation
292 local sanitize_timeout_in_sec=$(( 20 * 60 )) # sanitize timeout for 20 minutes
293 # Maximum time to finish format operation. Let timeout be proportional to the
294 # size of device.
295 local format_timeout_in_ms=$(( base_timeout_in_ms + dev_size / bytes_per_ms ))
296 local progress current_progress
297 # "During a sanitize operation, the host may periodically examine the
298 # Sanitize Status log page to check for progress, however, the host
299 # should limit this polling (e.g., to at most once every several minutes)
300 # to avoid interfering with the progress of the sanitize operation itself."
301 # See section 8.15 in
302 # https://www.nvmexpress.org/wp-content/uploads/NVM_Express_Revision_1.3.pdf
303 # Because of the reason above need to sleep and check the progress during
304 # sanitize operation. We use 10 seconds for sleeping because the device we
305 # tested this code on executes the command quite fast. The whole full disk
306 # wipe process takes a lot of time so we don't want to increase it even more
307 # by picking a larger value.
308 local sleep_time_in_sec=10
309
310 # Sanitize is preferred over format because it guarantees physical data
311 # destruction.
312 if is_nvme_sanitize_supported "${disk}"; then
313 nvme sanitize "${disk}" --ause --sanact=0x02
314 count="${sanitize_timeout_in_sec}"
315 while [ ${count} -gt 0 ] && is_nvme_sanitize_in_progress "${disk}"; do
316 # See the comment above.
317 sleep "${sleep_time_in_sec}"
318 current_progress="$(get_nvme_sanitize_progress "${disk}")"
319 if [ "${progress}" = "${current_progress}" ]; then
320 : $(( count -= sleep_time_in_sec ))
321 else
322 count="${sanitize_timeout_in_sec}"
323 fi
324 progress="${current_progress}"
325 done
326 is_nvme_sanitize_successful "${disk}" && return 0
327
328 # If block erase sanitize operation fails, issue sanitize command with the
329 # 'Exit Failure Mode' action to recover from failure.
330 nvme sanitize "${disk}" --sanact=0x01
331 count="${sanitize_timeout_in_sec}"
332 progress=0
333 while [ ${count} -gt 0 ] && is_nvme_sanitize_in_progress "${disk}"; do
334 # Check if the sanitize command executed before continuing.
335 # Also see the comment above.
336 sleep "${sleep_time_in_sec}"
337 current_progress="$(get_nvme_sanitize_progress "${disk}")"
338 if [ "${progress}" = "${current_progress}" ]; then
339 : $(( count -= sleep_time_in_sec ))
340 else
341 count="${sanitize_timeout_in_sec}"
342 fi
343 progress="${current_progress}"
344 done
345 fi
346
347 # If strict is set to 1, only sanitize should return success.
348 # We want to be able to distinguish between the successes in strict mode
349 # in order to inform users if their device is physically erased or not.
350 local success_return_value=0
351 if [ "${strict}" = "1" ]; then
352 success_return_value=${STRICT_RETURN_VALUE}
353 fi
Thiemo Nageldeb84732019-07-17 15:14:34 +0200354
355 # Format with crypto mode
aysusayine3eb9872019-08-07 12:31:49 +0200356 nvme format "${disk}" --ses "${ses_crypto}" && return ${success_return_value}
Thiemo Nageldeb84732019-07-17 15:14:34 +0200357
358 # Format with userdata mode
aysusayine3eb9872019-08-07 12:31:49 +0200359 # 0xffffffff means format all namespaces of the given character device.
360 nvme format "${disk}" --namespace-id=0xffffffff --ses "${ses_user}" \
361 --timeout "${format_timeout_in_ms}"
362 local return_val=$?
363 test ${return_val} -eq 0 && return ${success_return_value}
364 return ${return_val}
Thiemo Nageldeb84732019-07-17 15:14:34 +0200365}
366
aysusayine3eb9872019-08-07 12:31:49 +0200367# Erase a device using its internal firmware function.
Thiemo Nageldeb84732019-07-17 15:14:34 +0200368#
369# Arguments:
aysusayine3eb9872019-08-07 12:31:49 +0200370# - device to erase ("/dev/sda", "/dev/mmcblk0", "/dev/nvme0").
371# - strict: if set to 1 then only returns success if the physical data
372# destruction is guaranteed by the command set that is used. If unset
373# or set to "0", returns success if either physical data destruction
374# is guaranteed or not.
Thiemo Nageldeb84732019-07-17 15:14:34 +0200375# Returns:
aysusayine3eb9872019-08-07 12:31:49 +0200376# 0 if the erase is either not supported or completed.
Thiemo Nageldeb84732019-07-17 15:14:34 +0200377# !0 if the erase process could not complete or failed.
378secure_erase() {
379 local disk="$1"
aysusayine3eb9872019-08-07 12:31:49 +0200380 local strict="$2"
Thiemo Nageldeb84732019-07-17 15:14:34 +0200381 local disk_type=$(get_device_type "${disk}")
Thiemo Nageleb494272019-09-04 12:20:08 +0200382 # Identify if MMC or SATA.
Thiemo Nageldeb84732019-07-17 15:14:34 +0200383 case "$disk_type" in
384 MMC)
aysusayine3eb9872019-08-07 12:31:49 +0200385 secure_erase_mmc "${disk}" "${strict}"
Thiemo Nageldeb84732019-07-17 15:14:34 +0200386 ;;
387 ATA)
aysusayine3eb9872019-08-07 12:31:49 +0200388 secure_erase_sata "${disk}" "${strict}"
Thiemo Nageldeb84732019-07-17 15:14:34 +0200389 ;;
390 NVME)
aysusayine3eb9872019-08-07 12:31:49 +0200391 secure_erase_nvme "${disk}" "${strict}"
Thiemo Nageldeb84732019-07-17 15:14:34 +0200392 ;;
393 *)
394 echo "Unable to identify the type of disk: -${disk_type}-"
395 return 1
396 esac
397}
398
aysusayine3eb9872019-08-07 12:31:49 +0200399# Use fio to write/verify a pattern.
Thiemo Nageldeb84732019-07-17 15:14:34 +0200400#
401# The first and last 1M of the disk are zeroed, the rest is written
402# with a random patter fio can verify latter.
403#
404# Argument
aysusayine3eb9872019-08-07 12:31:49 +0200405# - disk: the device to erase
406# - disk_size: the size of the device
407# - disk_op: "write" to write over the SSD, "verify" to check the SSD
408# has been overwritten properly, "verify_disk_wipe" to check
409# the SSD has been zeroed.
Thiemo Nageldeb84732019-07-17 15:14:34 +0200410# Returns:
411# fio error code if fio could run, 1 otherwise.
412perform_fio_op() {
413 # Globals, used by factory_secure.fio
414 local disk="$1"
415 local disk_size="$2"
416 local disk_op="$3"
417
418 local dev_main_area_end=$(( ${disk_size} - 1048576 ))
419 local block_size=1048576
420 local fio_err=0
421 local fio_output="${TEST_FIO_OUTPUT}"
422 local fio_regex='/^(secure|fio)/s/.* err= *([[:digit:]]+).*/\1/p'
423 local input
424
425 export FIO_DEV="${disk}"
426 export FIO_DEV_MAIN_AREA_SIZE=$(( ${disk_size} - 2097152 ))
aysusayine3eb9872019-08-07 12:31:49 +0200427 export OFFSET="1m"
428 export VERIFY="md5"
Thiemo Nageldeb84732019-07-17 15:14:34 +0200429
430 if [ -z "${fio_output}" ]; then
aysusayin5278d6f2019-08-21 15:33:48 +0200431 fio_output="$(mktemp -t fio_output_XXXXXX)"
Thiemo Nageldeb84732019-07-17 15:14:34 +0200432 fi
433 case "$disk_op" in
434 write)
435 export FIO_VERIFY_ONLY=0
436 # Erase the begining an the end of the drive. Write random first
437 # to ensure the data is scrambled.
438 for input in "urandom" "zero"; do
439 dd bs="${block_size}" of="${disk}" oflag=dsync iflag=fullblock \
440 if=/dev/${input} count=1
441 dd bs="${block_size}" of="${disk}" oflag=dsync iflag=fullblock \
442 if=/dev/${input} seek=$(( dev_main_area_end / ${block_size} ))
443 done
444 ;;
445 verify)
446 export FIO_VERIFY_ONLY=1
447 ;;
aysusayine3eb9872019-08-07 12:31:49 +0200448 verify_disk_wipe)
449 export FIO_VERIFY_ONLY=1
450 export VERIFY="pattern"
451 export FIO_DEV_MAIN_AREA_SIZE=${disk_size}
452 export OFFSET="0"
453 ;;
Thiemo Nageldeb84732019-07-17 15:14:34 +0200454 *)
455 echo "Unsupported operation: -${disk_op}-"
456 return 1
457 esac
458
aysusayin5278d6f2019-08-21 15:33:48 +0200459 local fio_script="$(mktemp -t fio_config_XXXXXX)"
Thiemo Nageldeb84732019-07-17 15:14:34 +0200460 cat >"${fio_script}" <<HERE
Thiemo Nageldeb84732019-07-17 15:14:34 +0200461[secure]
462filename=\${FIO_DEV}
463ioengine=libaio
464iodepth=32
465direct=1
466readwrite=write
467bs=256k
468
aysusayine3eb9872019-08-07 12:31:49 +0200469offset=\${OFFSET}
Thiemo Nageldeb84732019-07-17 15:14:34 +0200470size=\${FIO_DEV_MAIN_AREA_SIZE}
471do_verify=1
aysusayine3eb9872019-08-07 12:31:49 +0200472verify=\${VERIFY}
473verify_pattern=0x0
Thiemo Nageldeb84732019-07-17 15:14:34 +0200474verify_only=\${FIO_VERIFY_ONLY}
475HERE
476 # Write a pattern on the media for future verification.
aysusayine3eb9872019-08-07 12:31:49 +0200477 # fio configuration file use DEV, DEV_MAIN_AREA_SIZE and VERIFY_ONLY.
478 fio "${fio_script}" --output "${fio_output}" --aux-path="${TMPDIR:-/tmp}"
Thiemo Nageldeb84732019-07-17 15:14:34 +0200479 rm -f "${fio_script}"
480 fio_err=$(sed -nr "${fio_regex}" "${fio_output}")
481 if [ -z "${fio_err}" ]; then
482 echo "-- output of fio not understood --"
483 cat "${fio_output}"
484 fio_err=1
485 elif [ ${fio_err} -ne 0 ]; then
486 cat "${fio_output}"
487 if [ $FIO_VERIFY_ONLY -eq 0 ]; then
488 echo "-- writing pattern failed --"
489 echo "The storage device is not working properly."
490 else
491 # Check if we fail to read the device, or the pattern is wrong.
492 echo "-- verifying pattern failed --"
493 if [ ${fio_err} -eq 84 ]; then
494 echo -n "The storage device has either been tampered with or "
495 echo "not securely erased properly."
496 else
497 echo "Storage device broken: unable to read some sector from it."
498 fi
499 fi
500 else
501 rm "${fio_output}"
502 fi
503 return ${fio_err}
504}