generic: racing getxattr requests against xattr add/remove
[xfstests-dev.git] / check
1 #!/bin/bash
2 #
3 # Control script for QA
4 #
5 # Copyright (c) 2000-2002,2006 Silicon Graphics, Inc.  All Rights Reserved.
6 #
7 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License as
9 # published by the Free Software Foundation.
10 #
11 # This program is distributed in the hope that it would be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write the Free Software Foundation,
18 # Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19 #
20 #
21
22 tmp=/tmp/$$
23 status=0
24 needwrap=true
25 needsum=true
26 n_try=0
27 try=""
28 n_bad=0
29 sum_bad=0
30 bad=""
31 notrun=""
32 interrupt=true
33 diff="diff -u"
34 showme=false
35 have_test_arg=false
36 randomize=false
37 export here=`pwd`
38 xfile=""
39 brief_test_summary=false
40
41 DUMP_OUTPUT=false
42
43 # start the initialisation work now
44 iam=check
45
46 export MSGVERB="text:action"
47 export QA_CHECK_FS=${QA_CHECK_FS:=true}
48
49 # number of diff lines from a failed test, 0 for whole output
50 export DIFF_LENGTH=${DIFF_LENGTH:=10}
51
52 # by default don't output timestamps
53 timestamp=${TIMESTAMP:=false}
54
55 rm -f $tmp.list $tmp.tmp $tmp.grep $here/$iam.out $tmp.xlist
56
57 SRC_GROUPS="generic shared"
58 export SRC_DIR="tests"
59
60 usage()
61 {
62     echo "Usage: $0 [options] [testlist]"'
63
64 check options
65     -nfs                test NFS
66     -cifs               test CIFS
67     -overlay            test overlay
68     -tmpfs              test TMPFS
69     -l                  line mode diff
70     -udiff              show unified diff (default)
71     -n                  show me, do not run tests
72     -T                  output timestamps
73     -r                  randomize test order
74     -d                  dump test output to stdout
75     -b                  brief test summary
76     --large-fs          optimise scratch device for large filesystems
77     -s section          run only specified section from config file
78     -S section          exclude the specified section from the config file
79
80 testlist options
81     -g group[,group...] include tests from these groups
82     -x group[,group...] exclude tests from these groups
83     -X exclude_file     exclude individual tests
84     -E external_file    exclude individual tests
85     [testlist]          include tests matching names in testlist
86
87 testlist argument is a list of tests in the form of <test dir>/<test name>.
88
89 <test dir> is a directory under tests that contains a group file,
90 with a list of the names of the tests in that directory.
91
92 <test name> may be either a specific test file name (e.g. xfs/001) or
93 a test file name match pattern (e.g. xfs/*).
94
95 group argument is either a name of a tests group to collect from all
96 the test dirs (e.g. quick) or a name of a tests group to collect from
97 a specific tests dir in the form of <test dir>/<group name> (e.g. xfs/quick).
98
99 exclude_file argument refers to a name of a file inside each test directory.
100 for every test dir where this file is found, the listed test names are
101 excluded from the list of tests to run from that test dir.
102
103 external_file argument is a path to a single file containing a list of tests
104 to exclude in the form of <test dir>/<test name>.
105
106 examples:
107  check xfs/001
108  check -g quick
109  check -g xfs/quick
110  check -x stress xfs/*
111  check -X .exclude -g auto
112  check -E ~/.xfstests.exclude
113 '
114             exit 0
115 }
116
117 get_sub_group_list()
118 {
119         local d=$1
120         local grp=$2
121
122         test -s "$SRC_DIR/$d/group" || return 1
123
124         local grpl=$(sed -n < $SRC_DIR/$d/group \
125                 -e 's/#.*//' \
126                 -e 's/$/ /' \
127                 -e "s;^\($VALID_TEST_NAME\).* $grp .*;$SRC_DIR/$d/\1;p")
128         echo $grpl
129 }
130
131 get_group_list()
132 {
133         local grp=$1
134         local grpl=""
135         local sub=$(dirname $grp)
136
137         if [ -n "$sub" -a "$sub" != "." -a -d "$SRC_DIR/$sub" ]; then
138                 # group is given as <subdir>/<group> (e.g. xfs/quick)
139                 grp=$(basename $grp)
140                 get_sub_group_list $sub $grp
141                 return
142         fi
143
144         for d in $SRC_GROUPS $FSTYP; do
145                 if ! test -d "$SRC_DIR/$d" ; then
146                         continue
147                 fi
148                 grpl="$grpl $(get_sub_group_list $d $grp)"
149         done
150         echo $grpl
151 }
152
153 # Find all tests, excluding files that are test metadata such as group files.
154 # It matches test names against $VALID_TEST_NAME defined in common/rc
155 get_all_tests()
156 {
157         touch $tmp.list
158         for d in $SRC_GROUPS $FSTYP; do
159                 if ! test -d "$SRC_DIR/$d" ; then
160                         continue
161                 fi
162                 ls $SRC_DIR/$d/* | \
163                         grep -v "\..*" | \
164                         grep "^$SRC_DIR/$d/$VALID_TEST_NAME"| \
165                         grep -v "group\|Makefile" >> $tmp.list 2>/dev/null
166         done
167 }
168
169 # takes the list of tests to run in $tmp.list, and removes the tests passed to
170 # the function from that list.
171 trim_test_list()
172 {
173         test_list="$*"
174
175         rm -f $tmp.grep
176         numsed=0
177         for t in $test_list
178         do
179             if [ $numsed -gt 100 ]; then
180                 grep -v -f $tmp.grep <$tmp.list >$tmp.tmp
181                 mv $tmp.tmp $tmp.list
182                 numsed=0
183                 rm -f $tmp.grep
184             fi
185             echo "^$t\$" >>$tmp.grep
186             numsed=`expr $numsed + 1`
187         done
188         grep -v -f $tmp.grep <$tmp.list >$tmp.tmp
189         mv $tmp.tmp $tmp.list
190 }
191
192
193 _wallclock()
194 {
195     date "+%s"
196 }
197
198 _timestamp()
199 {
200     now=`date "+%T"`
201     echo -n " [$now]"
202 }
203
204 _prepare_test_list()
205 {
206         unset list
207         # Tests specified on the command line
208         if [ -s $tmp.arglist ]; then
209                 cat $tmp.arglist > $tmp.list
210         else
211                 touch $tmp.list
212         fi
213
214         # Specified groups to include
215         for group in $GROUP_LIST; do
216                 list=$(get_group_list $group)
217                 if [ -z "$list" ]; then
218                         echo "Group \"$group\" is empty or not defined?"
219                         exit 1
220                 fi
221
222                 for t in $list; do
223                         grep -s "^$t\$" $tmp.list >/dev/null || \
224                                                         echo "$t" >>$tmp.list
225                 done
226         done
227
228         if ! $have_test_arg && [ -z "$GROUP_LIST" ]; then
229                 # no test numbers, do everything
230                 get_all_tests
231         fi
232
233         # Specified groups to exclude
234         for xgroup in $XGROUP_LIST; do
235                 list=$(get_group_list $xgroup)
236                 if [ -z "$list" ]; then
237                         echo "Group \"$xgroup\" is empty or not defined?"
238                         exit 1
239                 fi
240
241                 trim_test_list $list
242         done
243
244         # sort the list of tests into numeric order
245         list=`sort -n $tmp.list | uniq`
246         rm -f $tmp.list $tmp.tmp $tmp.grep
247
248         if $randomize
249         then
250                 list=`echo $list | awk -f randomize.awk`
251         fi
252 }
253
254 # Process command arguments first.
255 while [ $# -gt 0 ]; do
256         case "$1" in
257         -\? | -h | --help) usage ;;
258
259         -nfs)           FSTYP=nfs ;;
260         -cifs)          FSTYP=cifs ;;
261         -overlay)       FSTYP=overlay ;;
262         -tmpfs)         FSTYP=tmpfs ;;
263
264         -g)     group=$2 ; shift ;
265                 GROUP_LIST="$GROUP_LIST ${group//,/ }"
266                 ;;
267
268         -x)     xgroup=$2 ; shift ;
269                 XGROUP_LIST="$XGROUP_LIST ${xgroup//,/ }"
270                 ;;
271
272         -X)     xfile=$2; shift ;
273                 for d in $SRC_GROUPS $FSTYP; do
274                         [ -f $SRC_DIR/$d/$xfile ] || continue
275                         for f in `sed "s/#.*$//" $SRC_DIR/$d/$xfile`; do
276                                 echo $d/$f >> $tmp.xlist
277                         done
278                 done
279                 ;;
280         -E)     xfile=$2; shift ;
281                 if [ -f $xfile ]; then
282                         sed "s/#.*$//" "$xfile" >> $tmp.xlist
283                 fi
284                 ;;
285         -s)     RUN_SECTION="$RUN_SECTION $2"; shift ;;
286         -S)     EXCLUDE_SECTION="$EXCLUDE_SECTION $2"; shift ;;
287         -l)     diff="diff" ;;
288         -udiff) diff="$diff -u" ;;
289
290         -n)     showme=true ;;
291         -r)     randomize=true ;;
292
293         -T)     timestamp=true ;;
294         -d)     DUMP_OUTPUT=true ;;
295         -b)     brief_test_summary=true;;
296
297         --large-fs) export LARGE_SCRATCH_DEV=yes ;;
298         --extra-space=*) export SCRATCH_DEV_EMPTY_SPACE=${r#*=} ;;
299
300         -*)     usage ;;
301         *)      # not an argument, we've got tests now.
302                 have_test_arg=true ;;
303         esac
304
305         # if we've found a test specification, the break out of the processing
306         # loop before we shift the arguments so that this is the first argument
307         # that we process in the test arg loop below.
308         if $have_test_arg; then
309                 break;
310         fi
311
312         shift
313 done
314
315 # we need common/config, source it after processing args, overlay needs FSTYP
316 # set before sourcing common/config
317 if ! . ./common/config; then
318         echo "$iam: failed to source common/config"
319         exit 1
320 fi
321
322 # Process tests from command line now.
323 if $have_test_arg; then
324         while [ $# -gt 0 ]; do
325                 case "$1" in
326                 -*)     echo "Arguments before tests, please!"
327                         status=1
328                         exit $status
329                         ;;
330                 *)      # Expand test pattern (e.g. xfs/???, *fs/001)
331                         list=$(cd $SRC_DIR; echo $1)
332                         for t in $list; do
333                                 test_dir=`dirname $t`
334                                 test_dir=${test_dir#$SRC_DIR/*}
335                                 test_name=`basename $t`
336                                 group_file=$SRC_DIR/$test_dir/group
337
338                                 if egrep -q "^$test_name" $group_file; then
339                                         # in group file ... OK
340                                         echo $SRC_DIR/$test_dir/$test_name \
341                                                 >>$tmp.arglist
342                                 else
343                                         # oops
344                                         echo "$t - unknown test, ignored"
345                                 fi
346                         done
347                         ;;
348                 esac
349
350                 shift
351         done
352 fi
353
354 # we need common/rc
355 if ! . ./common/rc
356 then
357     echo "check: failed to source common/rc"
358     exit 1
359 fi
360
361 if [ `id -u` -ne 0 ]
362 then
363     echo "check: QA must be run as root"
364     exit 1
365 fi
366
367 _wipe_counters()
368 {
369         n_try="0"
370         n_bad="0"
371         unset try notrun bad
372 }
373
374 _wrapup()
375 {
376         seq="check"
377         check="$RESULT_BASE/check"
378
379         if $showme; then
380         :
381         elif $needwrap; then
382                 if [ -f $check.time -a -f $tmp.time ]; then
383                         cat $check.time $tmp.time  \
384                                 | $AWK_PROG '
385                                 { t[$1] = $2 }
386                                 END {
387                                         if (NR > 0) {
388                                                 for (i in t) print i " " t[i]
389                                         }
390                                 }' \
391                                 | sort -n >$tmp.out
392                         mv $tmp.out $check.time
393                 fi
394
395                 echo "" >>$check.log
396                 date >>$check.log
397
398                 echo "SECTION       -- $section" >>$tmp.summary
399                 echo "=========================" >>$tmp.summary
400                 if [ ! -z "$n_try" -a $n_try != 0 ]; then
401                         if [ $brief_test_summary == "false" ]; then
402                                 echo "Ran:$try"
403                                 echo "Ran:$try" >>$tmp.summary
404                         fi
405                         echo "Ran:$try" >>$check.log
406                 fi
407
408                 $interrupt && echo "Interrupted!" >>$check.log
409
410                 if [ ! -z "$notrun" ]; then
411                         if [ $brief_test_summary == "false" ]; then
412                                 echo "Not run:$notrun"
413                                 echo "Not run:$notrun" >>$tmp.summary
414                         fi
415                         echo "Not run:$notrun" >>$check.log
416                 fi
417
418                 if [ ! -z "$n_bad" -a $n_bad != 0 ]; then
419                         echo "Failures:$bad"
420                         echo "Failed $n_bad of $n_try tests"
421                         echo "Failures:$bad" >>$check.log
422                         echo "Failed $n_bad of $n_try tests" >>$check.log
423                         echo "Failures:$bad" >>$tmp.summary
424                         echo "Failed $n_bad of $n_try tests" >>$tmp.summary
425                 else
426                         echo "Passed all $n_try tests"
427                         echo "Passed all $n_try tests" >>$check.log
428                         echo "Passed all $n_try tests" >>$tmp.summary
429                 fi
430                 echo "" >>$tmp.summary
431                 needwrap=false
432         fi
433
434         sum_bad=`expr $sum_bad + $n_bad`
435         _wipe_counters
436         rm -f /tmp/*.rawout /tmp/*.out /tmp/*.err /tmp/*.time
437         if ! $OPTIONS_HAVE_SECTIONS; then
438                 rm -f $tmp.*
439         fi
440 }
441
442 _summary()
443 {
444         _wrapup
445         if $showme; then
446                 :
447         elif $needsum; then
448                 count=`wc -L $tmp.summary | cut -f1 -d" "`
449                 cat $tmp.summary
450                 needsum=false
451         fi
452         rm -f $tmp.*
453 }
454
455 _check_filesystems()
456 {
457         if [ -f ${RESULT_DIR}/require_test ]; then
458                 _check_test_fs || err=true
459                 rm -f ${RESULT_DIR}/require_test*
460         fi
461         if [ -f ${RESULT_DIR}/require_scratch ]; then
462                 _check_scratch_fs || err=true
463                 rm -f ${RESULT_DIR}/require_scratch*
464         fi
465 }
466
467 _prepare_test_list
468
469 if $OPTIONS_HAVE_SECTIONS; then
470         trap "_summary; exit \$status" 0 1 2 3 15
471 else
472         trap "_wrapup; exit \$status" 0 1 2 3 15
473 fi
474
475 for section in $HOST_OPTIONS_SECTIONS; do
476         OLD_FSTYP=$FSTYP
477         OLD_MOUNT_OPTIONS=$MOUNT_OPTIONS
478         get_next_config $section
479
480         # Do we need to run only some sections ?
481         if [ ! -z "$RUN_SECTION" ]; then
482                 skip=true
483                 for s in $RUN_SECTION; do
484                         if [ $section == $s ]; then
485                                 skip=false
486                                 break;
487                         fi
488                 done
489                 if $skip; then
490                         continue
491                 fi
492         fi
493
494         # Did this section get excluded?
495         if [ ! -z "$EXCLUDE_SECTION" ]; then
496                 skip=false
497                 for s in $EXCLUDE_SECTION; do
498                         if [ $section == $s ]; then
499                                 skip=true
500                                 break;
501                         fi
502                 done
503                 if $skip; then
504                         continue
505                 fi
506         fi
507
508         mkdir -p $RESULT_BASE
509         if [ ! -d $RESULT_BASE ]; then
510                 echo "failed to create results directory $RESULT_BASE"
511                 status=1
512                 exit
513         fi
514
515         if $OPTIONS_HAVE_SECTIONS; then
516                 echo "SECTION       -- $section"
517         fi
518
519         if $RECREATE_TEST_DEV || [ "$OLD_FSTYP" != "$FSTYP" ]; then
520                 echo "RECREATING    -- $FSTYP on $TEST_DEV"
521                 _test_unmount 2> /dev/null
522                 if ! _test_mkfs >$tmp.err 2>&1
523                 then
524                         echo "our local _test_mkfs routine ..."
525                         cat $tmp.err
526                         echo "check: failed to mkfs \$TEST_DEV using specified options"
527                         status=1
528                         exit
529                 fi
530                 out=`_mount_or_remount_rw "$MOUNT_OPTIONS" $TEST_DEV $TEST_DIR`
531                 if [ $? -ne 1 ]; then
532                         echo $out
533                         status=1
534                         exit
535                 fi
536                 _prepare_test_list
537         elif [ "$OLD_MOUNT_OPTIONS" != "$MOUNT_OPTIONS" ]; then
538                 _test_unmount 2> /dev/null
539                 out=`_mount_or_remount_rw "$MOUNT_OPTIONS" $TEST_DEV $TEST_DIR`
540                 if [ $? -ne 1 ]; then
541                         echo $out
542                         status=1
543                         exit
544                 fi
545         fi
546
547         init_rc
548
549         seq="check"
550         check="$RESULT_BASE/check"
551
552         # don't leave old full output behind on a clean run
553         rm -f $check.full
554
555         [ -f $check.time ] || touch $check.time
556
557         # print out our test configuration
558         echo "FSTYP         -- `_full_fstyp_details`"
559         echo "PLATFORM      -- `_full_platform_details`"
560         if [ ! -z "$SCRATCH_DEV" ]; then
561           echo "MKFS_OPTIONS  -- `_scratch_mkfs_options`"
562           echo "MOUNT_OPTIONS -- `_scratch_mount_options`"
563         fi
564         echo
565         needwrap=true
566
567         if [ ! -z "$SCRATCH_DEV" ]; then
568           _scratch_unmount 2> /dev/null
569           # call the overridden mkfs - make sure the FS is built
570           # the same as we'll create it later.
571
572           if ! _scratch_mkfs >$tmp.err 2>&1
573           then
574               echo "our local _scratch_mkfs routine ..."
575               cat $tmp.err
576               echo "check: failed to mkfs \$SCRATCH_DEV using specified options"
577               status=1
578               exit
579           fi
580
581           # call the overridden mount - make sure the FS mounts with
582           # the same options that we'll mount with later.
583           if ! _scratch_mount >$tmp.err 2>&1
584           then
585               echo "our local mount routine ..."
586               cat $tmp.err
587               echo "check: failed to mount \$SCRATCH_DEV using specified options"
588               status=1
589               exit
590           fi
591         fi
592
593         seqres="$check"
594         _check_test_fs
595
596         for seq in $list
597         do
598             err=false
599             if [ ! -f $seq ]; then
600                 # Try to get full name in case the user supplied only seq id
601                 # and the test has a name. A bit of hassle to find really
602                 # the test and not its sample output or helping files.
603                 bname=$(basename $seq)
604                 full_seq=$(find $(dirname $seq) -name $bname* -executable |
605                     awk '(NR == 1 || length < length(shortest)) { shortest = $0 }\
606                         END { print shortest }')
607                 if [ -f $full_seq ] \
608                     && [ x$(echo $bname | grep -o "^$VALID_TEST_ID") != x ]; then
609                     seq=$full_seq
610                 fi
611             fi
612
613             # the filename for the test and the name output are different.
614             # we don't include the tests/ directory in the name output.
615             export seqnum=`echo $seq | sed -e "s;$SRC_DIR/;;"`
616
617             # Similarly, the result directory needs to replace the tests/
618             # part of the test location.
619             group=`dirname $seq`
620             if $OPTIONS_HAVE_SECTIONS; then
621                 export RESULT_DIR=`echo $group | sed -e "s;$SRC_DIR;${RESULT_BASE}/$section;"`
622                 seqres="$RESULT_BASE/$section/$seqnum"
623             else
624                 export RESULT_DIR=`echo $group | sed -e "s;$SRC_DIR;$RESULT_BASE;"`
625                 seqres="$RESULT_BASE/$seqnum"
626             fi
627
628             mkdir -p $RESULT_DIR
629
630             echo -n "$seqnum"
631
632                 if $showme; then
633                         echo
634                         continue
635                 fi
636
637                 if [ ! -f $seq ]; then
638                         echo " - no such test?"
639                 else
640                 # really going to try and run this one
641                 #
642                 rm -f $seqres.out.bad
643
644                 # check if we really should run it
645                 if [ -s $tmp.xlist ]; then
646                         if grep $seqnum $tmp.xlist > /dev/null 2>&1 ; then
647                                 echo "       [expunged]"
648                                 continue
649                         fi
650                 fi
651
652                 # slashes now in names, sed barfs on them so use grep
653                 lasttime=`grep -w ^$seqnum $check.time | awk '// {print $2}'`
654                 if [ "X$lasttime" != X ]; then
655                         echo -n " ${lasttime}s ..."
656                 else
657                         echo -n "       "       # prettier output with timestamps.
658                 fi
659                 rm -f core $seqres.notrun
660
661                 start=`_wallclock`
662                 $timestamp && echo -n " ["`date "+%T"`"]"
663                 [ ! -x $seq ] && chmod u+x $seq # ensure we can run it
664                 $LOGGER_PROG "run xfstest $seqnum"
665                 if [ -w /dev/kmsg ]; then
666                         export date_time=`date +"%F %T"`
667                         echo "run fstests $seqnum at $date_time" > /dev/kmsg
668                         # _check_dmesg depends on this log in dmesg
669                         touch ${RESULT_DIR}/check_dmesg
670                 fi
671                 if [ "$DUMP_OUTPUT" = true ]; then
672                         ./$seq 2>&1 | tee $tmp.rawout
673                         # Because $? would get tee's return code
674                         sts=${PIPESTATUS[0]}
675                 else
676                         ./$seq >$tmp.rawout 2>&1
677                         sts=$?
678                 fi
679                 $timestamp && _timestamp
680                 stop=`_wallclock`
681
682                 _fix_malloc <$tmp.rawout >$tmp.out
683                 rm -f $tmp.rawout
684
685                 if [ -f core ]
686                 then
687                     echo -n " [dumped core]"
688                     mv core $RESULT_BASE/$seqnum.core
689                     err=true
690                 fi
691
692                 if [ -f $seqres.notrun ]
693                 then
694                     $timestamp || echo -n " [not run] "
695                     $timestamp && echo " [not run]" && echo -n "        $seqnum -- "
696                     cat $seqres.notrun
697                     notrun="$notrun $seqnum"
698                 else
699                     if [ $sts -ne 0 ]
700                     then
701                         echo -n " [failed, exit status $sts]"
702                         err=true
703                     fi
704                     if [ ! -f $seq.out ]
705                     then
706                         echo " - no qualified output"
707                         err=true
708                     else
709
710                         # coreutils 8.16+ changed quote formats in error messages from
711                         # `foo' to 'foo'. Filter old versions to match the new version.
712                         sed -i "s/\`/\'/g" $tmp.out
713                         if diff $seq.out $tmp.out >/dev/null 2>&1
714                         then
715                             if $err
716                             then
717                                 :
718                             else
719                                 echo "$seqnum `expr $stop - $start`" >>$tmp.time
720                                 echo -n " `expr $stop - $start`s"
721                             fi
722                             echo ""
723                         else
724                             echo " - output mismatch (see $seqres.out.bad)"
725                             mv $tmp.out $seqres.out.bad
726                             $diff $seq.out $seqres.out.bad | {
727                                 if test "$DIFF_LENGTH" -le 0; then
728                                         cat
729                                 else
730                                         head -n "$DIFF_LENGTH"
731                                         echo "..."
732                                         echo "(Run '$diff $seq.out $seqres.out.bad'" \
733                                                 " to see the entire diff)"
734                                 fi; } | \
735                                 sed -e 's/^\(.\)/    \1/'
736                             err=true
737                         fi
738                     fi
739                     try="$try $seqnum"
740                     n_try=`expr $n_try + 1`
741                     _check_filesystems
742                     _check_dmesg || err=true
743                 fi
744
745             fi
746
747             # come here for each test, except when $showme is true
748             #
749             if $err
750             then
751                 bad="$bad $seqnum"
752                 n_bad=`expr $n_bad + 1`
753                 quick=false
754             fi
755
756             seq="after_$seqnum"
757         done
758         _wrapup
759         echo
760
761         _test_unmount 2> /dev/null
762         _scratch_unmount 2> /dev/null
763 done
764
765 interrupt=false
766 status=`expr $sum_bad`
767 exit