1#!/bin/bash 2# 3# (C) Copyright 2014 Suriyan Ramasami 4# 5# SPDX-License-Identifier: GPL-2.0+ 6# 7 8# Invoke this test script from U-Boot base directory as ./test/fs/fs-test.sh 9# It currently tests the fs/sb and native commands for ext4 and fat partitions 10# Expected results are as follows: 11# EXT4 tests: 12# fs-test.sb.ext4.out: Summary: PASS: 24 FAIL: 0 13# fs-test.ext4.out: Summary: PASS: 24 FAIL: 0 14# fs-test.fs.ext4.out: Summary: PASS: 24 FAIL: 0 15# FAT16 tests: 16# fs-test.sb.fat16.out: Summary: PASS: 24 FAIL: 0 17# fs-test.fat16.out: Summary: PASS: 21 FAIL: 3 18# fs-test.fs.fat16.out: Summary: PASS: 21 FAIL: 3 19# FAT32 tests: 20# fs-test.sb.fat32.out: Summary: PASS: 24 FAIL: 0 21# fs-test.fat32.out: Summary: PASS: 21 FAIL: 3 22# fs-test.fs.fat32.out: Summary: PASS: 21 FAIL: 3 23# Total Summary: TOTAL PASS: 204 TOTAL FAIL: 12 24 25# pre-requisite binaries list. 26PREREQ_BINS="md5sum mkfs mount umount dd fallocate mkdir" 27 28# All generated output files from this test will be in $OUT_DIR 29# Hence everything is sandboxed. 30OUT_DIR="sandbox/test/fs" 31 32# Location of generated sandbox u-boot 33UBOOT="./sandbox/u-boot" 34 35# Our mount directory will be in the sandbox 36MOUNT_DIR="${OUT_DIR}/mnt" 37 38# The file system image we create will have the $IMG prefix. 39IMG="${OUT_DIR}/3GB" 40 41# $SMALL_FILE is the name of the 1MB file in the file system image 42SMALL_FILE="1MB.file" 43 44# $BIG_FILE is the name of the 2.5GB file in the file system image 45BIG_FILE="2.5GB.file" 46 47# $MD5_FILE will have the expected md5s when we do the test 48# They shall have a suffix which represents their file system (ext4/fat16/...) 49MD5_FILE="${OUT_DIR}/md5s.list" 50 51# $OUT shall be the prefix of the test output. Their suffix will be .out 52OUT="${OUT_DIR}/fs-test" 53 54# Full Path of the 1 MB file that shall be created in the fs image. 55MB1="${MOUNT_DIR}/${SMALL_FILE}" 56GB2p5="${MOUNT_DIR}/${BIG_FILE}" 57 58# ************************ 59# * Functions start here * 60# ************************ 61 62# Check if the prereq binaries exist, or exit 63function check_prereq() { 64 for prereq in $PREREQ_BINS; do 65 if [ ! -x "`which $prereq`" ]; then 66 echo "Missing $prereq binary. Exiting!" 67 exit 68 fi 69 done 70 71 # We use /dev/urandom to create files. Check if it exists. 72 if [ ! -c /dev/urandom ]; then 73 echo "Missing character special /dev/urandom. Exiting!" 74 exit 75 fi 76} 77 78# If 1st param is "clean", then clean out the generated files and exit 79function check_clean() { 80 if [ "$1" = "clean" ]; then 81 rm -rf "$OUT_DIR" 82 echo "Cleaned up generated files. Exiting" 83 exit 84 fi 85} 86 87# Generate sandbox U-Boot - gleaned from /test/dm/test-dm.sh 88function compile_sandbox() { 89 unset CROSS_COMPILE 90 NUM_CPUS=$(cat /proc/cpuinfo |grep -c processor) 91 make O=sandbox sandbox_config 92 make O=sandbox -s -j${NUM_CPUS} 93 94 # Check if U-Boot exists 95 if [ ! -x "$UBOOT" ]; then 96 echo "$UBOOT does not exist or is not executable" 97 echo "Build error?" 98 echo "Please run this script as ./test/fs/`basename $0`" 99 exit 100 fi 101} 102 103# Clean out all generated files other than the file system images 104# We save time by not deleting and recreating the file system images 105function prepare_env() { 106 rm -f ${MD5_FILE}.* ${OUT}.* 107 mkdir -p ${OUT_DIR} 108} 109 110# 1st parameter is the name of the image file to be created 111# 2nd parameter is the filesystem - fat16 ext4 etc 112# -F cant be used with fat as it means something else. 113function create_image() { 114 # Create image if not already present - saves time, while debugging 115 case "$2" in 116 fat16) 117 MKFS_OPTION="-F 16" 118 FS_TYPE="fat" 119 ;; 120 fat32) 121 MKFS_OPTION="-F 32" 122 FS_TYPE="fat" 123 ;; 124 ext4) 125 MKFS_OPTION="-F" 126 FS_TYPE="ext4" 127 ;; 128 esac 129 130 if [ ! -f "$1" ]; then 131 fallocate -l 3G "$1" &> /dev/null 132 if [ $? -ne 0 ]; then 133 echo fallocate failed - using dd instead 134 dd if=/dev/zero of=$1 bs=1024 count=$((3 * 1024 * 1024)) 135 if [ $? -ne 0 ]; then 136 echo Could not create empty disk image 137 exit $? 138 fi 139 fi 140 mkfs -t "$FS_TYPE" $MKFS_OPTION "$1" &> /dev/null 141 if [ $? -ne 0 -a "$FS_TYPE" = "fat" ]; then 142 # If we fail and we did fat, try vfat. 143 mkfs -t vfat $MKFS_OPTION "$1" &> /dev/null 144 fi 145 if [ $? -ne 0 ]; then 146 echo Could not create filesystem 147 exit $? 148 fi 149 fi 150} 151 152# 1st parameter is image file 153# 2nd parameter is file system type - fat16/ext4/... 154# 3rd parameter is name of small file 155# 4th parameter is name of big file 156# 5th parameter is fs/nonfs/sb - to dictate generic fs commands or 157# otherwise or sb hostfs 158# 6th parameter is the directory path for the files. Its "" for generic 159# fs and ext4/fat and full patch for sb hostfs 160# UBOOT is set in env 161function test_image() { 162 addr="0x01000008" 163 length="0x00100000" 164 165 case "$2" in 166 fat*) 167 FPATH="" 168 PREFIX="fat" 169 WRITE="write" 170 ;; 171 172 ext4) 173 # ext4 needs absolute path 174 FPATH="/" 175 PREFIX="ext4" 176 WRITE="write" 177 ;; 178 179 *) 180 echo "Unhandled filesystem $2. Exiting!" 181 exit 182 ;; 183 esac 184 185 case "$5" in 186 fs) 187 PREFIX="" 188 WRITE="save" 189 SUFFIX=" 0:0" 190 ;; 191 192 nonfs) 193 SUFFIX=" 0:0" 194 ;; 195 196 sb) 197 PREFIX="sb " 198 WRITE="save" 199 SUFFIX="fs -" 200 ;; 201 202 *) 203 echo "Unhandled mode $5. Exiting!" 204 exit 205 ;; 206 207 esac 208 209 # sb always uses full path to mointpoint, irrespective of filesystem 210 if [ "$5" = "sb" ]; then 211 FPATH=${6}/ 212 fi 213 214 FILE_WRITE=${3}.w 215 FILE_SMALL=$3 216 FILE_BIG=$4 217 218 # In u-boot commands, <interface> stands for host or hostfs 219 # hostfs maps to the host fs. 220 # host maps to the "sb bind" that we do 221 222 $UBOOT << EOF 223sb=$5 224setenv bind 'if test "\$sb" != sb; then sb bind 0 "$1"; fi' 225run bind 226# Test Case 1 - ls 227${PREFIX}ls host${SUFFIX} $6 228# 229# We want ${PREFIX}size host 0:0 $3 for host commands and 230# sb size hostfs - $3 for hostfs commands. 231# 1MB is 0x0010 0000 232# Test Case 2a - size of small file 233${PREFIX}size host${SUFFIX} ${FPATH}$FILE_SMALL 234printenv filesize 235setenv filesize 236# Test Case 2b - size of small file via a path using '..' 237${PREFIX}size host${SUFFIX} ${FPATH}SUBDIR/../$FILE_SMALL 238printenv filesize 239setenv filesize 240 241# 2.5GB (1024*1024*2500) is 0x9C40 0000 242# Test Case 3 - size of big file 243${PREFIX}size host${SUFFIX} ${FPATH}$FILE_BIG 244printenv filesize 245setenv filesize 246 247# Notes about load operation 248# If I use 0x01000000 I get DMA misaligned error message 249# Last two parameters are size and offset. 250 251# Test Case 4a - Read full 1MB of small file 252${PREFIX}load host${SUFFIX} $addr ${FPATH}$FILE_SMALL 253printenv filesize 254# Test Case 4b - Read full 1MB of small file 255md5sum $addr \$filesize 256setenv filesize 257 258# Test Case 5a - First 1MB of big file 259${PREFIX}load host${SUFFIX} $addr ${FPATH}$FILE_BIG $length 0x0 260printenv filesize 261# Test Case 5b - First 1MB of big file 262md5sum $addr \$filesize 263setenv filesize 264 265# fails for ext as no offset support 266# Test Case 6a - Last 1MB of big file 267${PREFIX}load host${SUFFIX} $addr ${FPATH}$FILE_BIG $length 0x9C300000 268printenv filesize 269# Test Case 6b - Last 1MB of big file 270md5sum $addr \$filesize 271setenv filesize 272 273# fails for ext as no offset support 274# Test Case 7a - One from the last 1MB chunk of 2GB 275${PREFIX}load host${SUFFIX} $addr ${FPATH}$FILE_BIG $length 0x7FF00000 276printenv filesize 277# Test Case 7b - One from the last 1MB chunk of 2GB 278md5sum $addr \$filesize 279setenv filesize 280 281# fails for ext as no offset support 282# Test Case 8a - One from the start 1MB chunk from 2GB 283${PREFIX}load host${SUFFIX} $addr ${FPATH}$FILE_BIG $length 0x80000000 284printenv filesize 285# Test Case 8b - One from the start 1MB chunk from 2GB 286md5sum $addr \$filesize 287setenv filesize 288 289# fails for ext as no offset support 290# Test Case 9a - One 1MB chunk crossing the 2GB boundary 291${PREFIX}load host${SUFFIX} $addr ${FPATH}$FILE_BIG $length 0x7FF80000 292printenv filesize 293# Test Case 9b - One 1MB chunk crossing the 2GB boundary 294md5sum $addr \$filesize 295setenv filesize 296 297# Generic failure case 298# Test Case 10 - 2MB chunk from the last 1MB of big file 299${PREFIX}load host${SUFFIX} $addr ${FPATH}$FILE_BIG 0x00200000 0x9C300000 300printenv filesize 301# 302 303# Read 1MB from small file 304${PREFIX}load host${SUFFIX} $addr ${FPATH}$FILE_SMALL 305# Write it back to test the writes 306# Test Case 11a - Check that the write succeeded 307${PREFIX}${WRITE} host${SUFFIX} $addr ${FPATH}$FILE_WRITE \$filesize 308mw.b $addr 00 100 309${PREFIX}load host${SUFFIX} $addr ${FPATH}$FILE_WRITE 310# Test Case 11b - Check md5 of written to is same as the one read from 311md5sum $addr \$filesize 312setenv filesize 313# 314 315# Next test case checks writing a file whose dirent 316# is the first in the block, which is always true for "." 317# The write should fail, but the lookup should work 318# Test Case 12 - Check directory traversal 319${PREFIX}${WRITE} host${SUFFIX} $addr ${FPATH}. 0x10 320 321# Read 1MB from small file 322${PREFIX}load host${SUFFIX} $addr ${FPATH}$FILE_SMALL 323# Write it via "same directory", i.e. "." dirent 324# Test Case 13a - Check directory traversal 325${PREFIX}${WRITE} host${SUFFIX} $addr ${FPATH}./${FILE_WRITE}2 \$filesize 326mw.b $addr 00 100 327${PREFIX}load host${SUFFIX} $addr ${FPATH}./${FILE_WRITE}2 328# Test Case 13b - Check md5 of written to is same as the one read from 329md5sum $addr \$filesize 330setenv filesize 331mw.b $addr 00 100 332${PREFIX}load host${SUFFIX} $addr ${FPATH}${FILE_WRITE}2 333# Test Case 13c - Check md5 of written to is same as the one read from 334md5sum $addr \$filesize 335setenv filesize 336# 337reset 338 339EOF 340} 341 342# 1st argument is the name of the image file. 343# 2nd argument is the file where we generate the md5s of the files 344# generated with the appropriate start and length that we use to test. 345# It creates the necessary files in the image to test. 346# $GB2p5 is the path of the big file (2.5 GB) 347# $MB1 is the path of the small file (1 MB) 348# $MOUNT_DIR is the path we can use to mount the image file. 349function create_files() { 350 # Mount the image so we can populate it. 351 mkdir -p "$MOUNT_DIR" 352 sudo mount -o loop,rw "$1" "$MOUNT_DIR" 353 354 # Create a subdirectory. 355 sudo mkdir -p "$MOUNT_DIR/SUBDIR" 356 357 # Create big file in this image. 358 # Note that we work only on the start 1MB, couple MBs in the 2GB range 359 # and the last 1 MB of the huge 2.5GB file. 360 # So, just put random values only in those areas. 361 if [ ! -f "${GB2p5}" ]; then 362 sudo dd if=/dev/urandom of="${GB2p5}" bs=1M count=1 \ 363 &> /dev/null 364 sudo dd if=/dev/urandom of="${GB2p5}" bs=1M count=2 seek=2047 \ 365 &> /dev/null 366 sudo dd if=/dev/urandom of="${GB2p5}" bs=1M count=1 seek=2499 \ 367 &> /dev/null 368 fi 369 370 # Create a small file in this image. 371 if [ ! -f "${MB1}" ]; then 372 sudo dd if=/dev/urandom of="${MB1}" bs=1M count=1 \ 373 &> /dev/null 374 fi 375 376 # Delete the small file copies which possibly are written as part of a 377 # previous test. 378 sudo rm -f "${MB1}.w" 379 sudo rm -f "${MB1}.w2" 380 381 # Generate the md5sums of reads that we will test against small file 382 dd if="${MB1}" bs=1M skip=0 count=1 2> /dev/null | md5sum > "$2" 383 384 # Generate the md5sums of reads that we will test against big file 385 # One from beginning of file. 386 dd if="${GB2p5}" bs=1M skip=0 count=1 \ 387 2> /dev/null | md5sum >> "$2" 388 389 # One from end of file. 390 dd if="${GB2p5}" bs=1M skip=2499 count=1 \ 391 2> /dev/null | md5sum >> "$2" 392 393 # One from the last 1MB chunk of 2GB 394 dd if="${GB2p5}" bs=1M skip=2047 count=1 \ 395 2> /dev/null | md5sum >> "$2" 396 397 # One from the start 1MB chunk from 2GB 398 dd if="${GB2p5}" bs=1M skip=2048 count=1 \ 399 2> /dev/null | md5sum >> "$2" 400 401 # One 1MB chunk crossing the 2GB boundary 402 dd if="${GB2p5}" bs=512K skip=4095 count=2 \ 403 2> /dev/null | md5sum >> "$2" 404 405 sync 406 sudo umount "$MOUNT_DIR" 407 rmdir "$MOUNT_DIR" 408} 409 410# 1st parameter is the text to print 411# if $? is 0 its a pass, else a fail 412# As a side effect it shall update env variable PASS and FAIL 413function pass_fail() { 414 if [ $? -eq 0 ]; then 415 echo pass - "$1" 416 PASS=$((PASS + 1)) 417 else 418 echo FAIL - "$1" 419 FAIL=$((FAIL + 1)) 420 fi 421} 422 423# 1st parameter is the string which leads to an md5 generation 424# 2nd parameter is the file we grep, for that string 425# 3rd parameter is the name of the file which has md5s in it 426# 4th parameter is the line # in the md5 file that we match it against 427# This function checks if the md5 of the file in the sandbox matches 428# that calculated while generating the file 429# 5th parameter is the string to print with the result 430check_md5() { 431 # md5sum in u-boot has output of form: 432 # md5 for 01000008 ... 01100007 ==> <md5> 433 # the 7th field is the actual md5 434 md5_src=`grep -A2 "$1" "$2" | grep "md5 for" | tr -d '\r'` 435 md5_src=($md5_src) 436 md5_src=${md5_src[6]} 437 438 # The md5 list, each line is of the form: 439 # - <md5> 440 # the 2nd field is the actual md5 441 md5_dst=`sed -n $4p $3` 442 md5_dst=($md5_dst) 443 md5_dst=${md5_dst[0]} 444 445 # For a pass they should match. 446 [ "$md5_src" = "$md5_dst" ] 447 pass_fail "$5" 448} 449 450# 1st parameter is the name of the output file to check 451# 2nd parameter is the name of the file containing the md5 expected 452# 3rd parameter is the name of the small file 453# 4th parameter is the name of the big file 454# 5th paramter is the name of the written file 455# This function checks the output file for correct results. 456function check_results() { 457 echo "** Start $1" 458 459 PASS=0 460 FAIL=0 461 462 # Check if the ls is showing correct results for 2.5 gb file 463 grep -A7 "Test Case 1 " "$1" | egrep -iq "2621440000 *$4" 464 pass_fail "TC1: ls of $4" 465 466 # Check if the ls is showing correct results for 1 mb file 467 grep -A7 "Test Case 1 " "$1" | egrep -iq "1048576 *$3" 468 pass_fail "TC1: ls of $3" 469 470 # Check size command on 1MB.file 471 egrep -A3 "Test Case 2a " "$1" | grep -q "filesize=100000" 472 pass_fail "TC2: size of $3" 473 # Check size command on 1MB.file via a path using '..' 474 egrep -A3 "Test Case 2b " "$1" | grep -q "filesize=100000" 475 pass_fail "TC2: size of $3 via a path using '..'" 476 477 # Check size command on 2.5GB.file 478 egrep -A3 "Test Case 3 " "$1" | grep -q "filesize=9c400000" 479 pass_fail "TC3: size of $4" 480 481 # Check read full mb of 1MB.file 482 grep -A4 "Test Case 4a " "$1" | grep -q "filesize=100000" 483 pass_fail "TC4: load of $3 size" 484 check_md5 "Test Case 4b " "$1" "$2" 1 "TC4: load from $3" 485 486 # Check first mb of 2.5GB.file 487 grep -A4 "Test Case 5a " "$1" | grep -q "filesize=100000" 488 pass_fail "TC5: load of 1st MB from $4 size" 489 check_md5 "Test Case 5b " "$1" "$2" 2 "TC5: load of 1st MB from $4" 490 491 # Check last mb of 2.5GB.file 492 grep -A4 "Test Case 6a " "$1" | grep -q "filesize=100000" 493 pass_fail "TC6: load of last MB from $4 size" 494 check_md5 "Test Case 6b " "$1" "$2" 3 "TC6: load of last MB from $4" 495 496 # Check last 1mb chunk of 2gb from 2.5GB file 497 grep -A4 "Test Case 7a " "$1" | grep -q "filesize=100000" 498 pass_fail "TC7: load of last 1mb chunk of 2GB from $4 size" 499 check_md5 "Test Case 7b " "$1" "$2" 4 \ 500 "TC7: load of last 1mb chunk of 2GB from $4" 501 502 # Check first 1mb chunk after 2gb from 2.5GB file 503 grep -A4 "Test Case 8a " "$1" | grep -q "filesize=100000" 504 pass_fail "TC8: load 1st MB chunk after 2GB from $4 size" 505 check_md5 "Test Case 8b " "$1" "$2" 5 \ 506 "TC8: load 1st MB chunk after 2GB from $4" 507 508 # Check 1mb chunk crossing the 2gb boundary from 2.5GB file 509 grep -A4 "Test Case 9a " "$1" | grep -q "filesize=100000" 510 pass_fail "TC9: load 1MB chunk crossing 2GB boundary from $4 size" 511 check_md5 "Test Case 9b " "$1" "$2" 6 \ 512 "TC9: load 1MB chunk crossing 2GB boundary from $4" 513 514 # Check 2mb chunk from the last 1MB of 2.5GB file loads 1MB 515 grep -A5 "Test Case 10 " "$1" | grep -q "filesize=100000" 516 pass_fail "TC10: load 2MB from the last 1MB of $4 loads 1MB" 517 518 # Check 1mb chunk write 519 grep -A2 "Test Case 11a " "$1" | grep -q '1048576 bytes written' 520 pass_fail "TC11: 1MB write to $3.w - write succeeded" 521 check_md5 "Test Case 11b " "$1" "$2" 1 \ 522 "TC11: 1MB write to $3.w - content verified" 523 524 # Check lookup of 'dot' directory 525 grep -A4 "Test Case 12 " "$1" | grep -q 'Unable to write file' 526 pass_fail "TC12: 1MB write to . - write denied" 527 528 # Check directory traversal 529 grep -A2 "Test Case 13a " "$1" | grep -q '1048576 bytes written' 530 pass_fail "TC13: 1MB write to ./$3.w2 - write succeeded" 531 check_md5 "Test Case 13b " "$1" "$2" 1 \ 532 "TC13: 1MB read from ./$3.w2 - content verified" 533 check_md5 "Test Case 13c " "$1" "$2" 1 \ 534 "TC13: 1MB read from $3.w2 - content verified" 535 536 echo "** End $1" 537} 538 539# Takes in one parameter which is "fs" or "nonfs", which then dictates 540# if a fs test (size/load/save) or a nonfs test (fatread/extread) needs to 541# be performed. 542function test_fs_nonfs() { 543 echo "Creating files in $fs image if not already present." 544 create_files $IMAGE $MD5_FILE_FS 545 546 OUT_FILE="${OUT}.$1.${fs}.out" 547 test_image $IMAGE $fs $SMALL_FILE $BIG_FILE $1 "" \ 548 > ${OUT_FILE} 2>&1 549 # strip out noise from fs code 550 grep -v -e "File System is consistent\|update journal finished" \ 551 -e "reading .*\.file\|writing .*\.file.w" \ 552 < ${OUT_FILE} > ${OUT_FILE}_clean 553 check_results ${OUT_FILE}_clean $MD5_FILE_FS $SMALL_FILE \ 554 $BIG_FILE 555 TOTAL_FAIL=$((TOTAL_FAIL + FAIL)) 556 TOTAL_PASS=$((TOTAL_PASS + PASS)) 557 echo "Summary: PASS: $PASS FAIL: $FAIL" 558 echo "--------------------------------------------" 559} 560 561# ******************** 562# * End of functions * 563# ******************** 564 565check_clean "$1" 566check_prereq 567compile_sandbox 568prepare_env 569 570# Track TOTAL_FAIL and TOTAL_PASS 571TOTAL_FAIL=0 572TOTAL_PASS=0 573 574# In each loop, for a given file system image, we test both the 575# fs command, like load/size/write, the file system specific command 576# like: ext4load/ext4size/ext4write and the sb load/ls/save commands. 577for fs in ext4 fat16 fat32; do 578 579 echo "Creating $fs image if not already present." 580 IMAGE=${IMG}.${fs}.img 581 MD5_FILE_FS="${MD5_FILE}.${fs}" 582 create_image $IMAGE $fs 583 584 # sb commands test 585 echo "Creating files in $fs image if not already present." 586 create_files $IMAGE $MD5_FILE_FS 587 588 # Lets mount the image and test sb hostfs commands 589 mkdir -p "$MOUNT_DIR" 590 case "$fs" in 591 fat*) 592 uid="uid=`id -u`" 593 ;; 594 *) 595 uid="" 596 ;; 597 esac 598 sudo mount -o loop,rw,$uid "$IMAGE" "$MOUNT_DIR" 599 sudo chmod 777 "$MOUNT_DIR" 600 601 OUT_FILE="${OUT}.sb.${fs}.out" 602 test_image $IMAGE $fs $SMALL_FILE $BIG_FILE sb `pwd`/$MOUNT_DIR \ 603 > ${OUT_FILE} 2>&1 604 sudo umount "$MOUNT_DIR" 605 rmdir "$MOUNT_DIR" 606 607 check_results $OUT_FILE $MD5_FILE_FS $SMALL_FILE $BIG_FILE 608 TOTAL_FAIL=$((TOTAL_FAIL + FAIL)) 609 TOTAL_PASS=$((TOTAL_PASS + PASS)) 610 echo "Summary: PASS: $PASS FAIL: $FAIL" 611 echo "--------------------------------------------" 612 613 test_fs_nonfs nonfs 614 test_fs_nonfs fs 615done 616 617echo "Total Summary: TOTAL PASS: $TOTAL_PASS TOTAL FAIL: $TOTAL_FAIL" 618echo "--------------------------------------------" 619if [ $TOTAL_FAIL -eq 0 ]; then 620 echo "PASSED" 621 exit 0 622else 623 echo "FAILED" 624 exit 1 625fi 626