
     ! "SCANDISK"    UTILITY FOR SDOS DISKS

     ! REVISION HISTORY

     ! WRITTEN ......
     ! MAJOR REVISION 08/13/85 JOE MURRAY
     !         ADDED PUTTING BAD CLUSTERS IN 'BADCLUSTERS.SYS`

     DIM Type$(5),            Sector$(4096),      Disk$(50)
     DIM ReTryCount$(5),      Bytes3$(3),         ErrorMessage$(80)
     DIM DiskID$(32),         Device$(6),         DirEntry$(32)
     DIM TwoBytes$(2),        Start$(20),         Size$(10)
     DIM Stats$(19),          LastBadSector$(7),  Temp$(5)
     DIM LastBadCluster$(7),  SixBytes$(6),       Map$(5)
     DIM BadLsn$(5),          Answer$(5)

     DIM ReTryCount/50/,      Sector/0/,          ErrorCount/0/
     DIM ValidateFlag/0/

     DIM GetLastBadLsn$/:0F, :0E, :00, :10/
     DIM GetErrorStats$/:0F, :0E, :00, :11/

     DIM Dismount$/:0E, :0E, :00, :11/
     DIM IsConsole$/:1A, :02/
     DIM UnProtect$/:0E, :0E, :00, :10/
     DIM DumpBuffers$/:0E, :04, :00, :01/
     DIM GetType$/:0F, :0E, :00, :04/
     DIM GetParams$/:0F, :0E, :00, :05/
     DIM Seterror$/:13, :04, :00, :00/
     DIM Closelog$/:08, :0E, :00, :00/

     DIM ScanFile/1/
     DIM Console/2/
     DIM ErrorFile/3/
     DIM BootFile/4/
     DIM DirecFile/5/
     DIM Disk/6/
     DIM Clock/7/

     !---------------------------------------------------------------

DEF  CurrentTime
     ! Returns time in seconds since midnite
     READ # Clock, SixBytes$
     X = (SixBytes$(1) * 65536 + SixBytes$(2) ** 8 + SixBytes$(3)) / 60
     RETURN X
END

     !---------------------------------------------------------------

DEF  Hexx$(Def1)

     Def1a = Def1
     K = 0

     WHILE Def1a > :FFFF DO
          Def1a = Def1a - 65536
          K = K + 1
     END       \! WHILE Def1a > :FFFF DO

     RETURN ':' CAT HEX$(K)(4, 2) CAT HEX$(Def1a)(2, 4)

END

     !---------------------------------------------------------------

DEF  MOD64(Def2)

     Def2a = Def2
     K = 0

     WHILE Def2a > :FFFF DO
          Def2a = Def2a - 65536
          K = K + 1
     END       \! WHILE Def2a > :FFFF DO

     RETURN Def2a & :3F

END

     !---------------------------------------------------------------

DEF  TWOBYTEVALUE(Def3$) = Def3$(1) ** 8 + Def3$(2)

     !---------------------------------------------------------------

DEF  AlreadyBad(Def4)
     POSITION # Disk, HCLocation
     FOR I = 1 TO NBPS * BadClustersHCSIC / 2 - 1
          READ # Disk, TwoBytes$
          IF TwoByteValue(TwoBytes$) = Def4 THEN RETURN TRUE
!         WE CANNOT ASSUME THE BADCLUSTERS.SYS FILE IS NOT SPARCE!
     NEXT I

     RETURN FALSE

END

     !---------------------------------------------------------------

DEF  DoFileActive

     IF ERROR WHEN Syscall IsConsole$
     THEN
          ON ERROR GOTO ErrorTrap
          RETURN TRUE
     ELSE
          ON ERROR GOTO ErrorTrap
          RETURN FALSE
     FI

END

     !---------------------------------------------------------------

DEF  Error$(ErrorNumber)

     IF ERROR WHEN
          OPEN # ErrorFile, 'ERRORMSGS.SYS'
          POSITION # ErrorFile, ErrorNumber * 3
          READ # ErrorFile, Bytes3$
          TEMP = Bytes3$(1) * 65536 + Bytes3$(2) ** 8 + Bytes3$(3)
          POSITION # ErrorFile, TEMP
          READ # ErrorFile, ErrorMessage$
          CLOSE # ErrorFile
          RETURN ErrorMessage$(1, FIND(ErrorMessage$, CHR$(:0D)) - 1)
     THEN
          ON ERROR GOTO ErrorTrap
          RETURN 'Error number' CAT NUM$(ErrorNumber)
     ELSE
          ON ERROR GOTO ErrorTrap
     FI

END

     !---------------------------------------------------------------

DEF  TimeString$(TS1)

     DIM TS$(8)
     TS = INT(TS1)
     TS$ = '00:00:00'
     TShour = INT(TS / 3600)
     TS = TS - TShour * 3600
     TSminute = INT(TS / 60)
     TS = TS - TSminute * 60
     TS$(1, 2) = NUMF$('##', TShour)
     TS$(4, 2) = NUMF$('##', TSminute)
     TS$(7, 2) = NUMF$('##', TS)
     FOR TSindex = 1 TO 8
          IF TS$(TSindex) = :20 THEN TS$(TSindex) = :30
     NEXT TSindex
     RETURN TS$
END

     !---------------------------------------------------------------

     BadClustersFileOk = FALSE
     BadClustersHCOk = FALSE
     ValidateFlag = FALSE
     Dismounted = FALSE
     DiskFlag = FALSE
     StatFlag = FALSE
     Mounted = FALSE
     DirecOk = FALSE

     OPEN # Clock, 'CLOCK:'
     CREATE # Console, 'CONSOLE:'

     SYSCALL # Console, GetType$, '', Type$

     IF COL(0) <> 1
     THEN
          INPUT '' Disk$
          PRINT 'SCAN DISK v1.1   Copyright (C) 1980 Software Dynamics'
     ELSE
          PRINT 'SCAN DISK v1.1   Copyright (C) 1980 Software Dynamics'
          INPUT 'Scan which disk or file (default is DISK:)? ' Disk$
     FI

     PRINT

     IF FIND(Disk$, ':') = 0 THEN Disk$ = 'DISK:' CAT Disk$

     IF ERROR WHEN
          OPEN # ScanFile, Disk$
     THEN
          PRINT
          PRINT Error$(ERR); ' occurred when I tried to OPEN '; Disk$
          EXIT
     FI
     ON ERROR GOTO ErrorTrap

     SYSCALL # ScanFile, GetType$, '', Type$

     IF Type$(1) <> 0 AND Type$(1) <> 1 then
          PRINT Disk$; ' cannot be scanned!'
          ERROR 104
     FI

     IF Type$[1] = 1
     THEN
          ! Disk$ is a DISK DEVICE
          Device$ = Disk$
          DiskFlag = TRUE
          SYSCALL # ScanFile, UnProtect$
     ELSE
          ! Disk$ is not a DISK DEVICE
          Device$ = Disk$(1, FIND(Disk$, ':'))
          DiskFlag = FALSE
     FI

     OPEN # Disk, Device$

     IF ERROR WHEN
          SYSCALL # Disk, Dismount$
     THEN
          ON ERROR GOTO ErrorTrap
          PRINT CHR$(:7)
          PRINT '********** WARNING *******';
          PRINT ' I cannot dismount '; Device$;
          PRINT '**************************'
          Dismounted = FALSE
     ELSE
          ON ERROR GOTO ErrorTrap
          Dismounted = TRUE
     FI

     SYSCALL # Disk, UnProtect$
     SYSCALL # Disk, GetParams$, '', Size$

     NBPS = Size$(1) * 256 + Size$(2)
     PRINT 'Sector size is'; NBPS; 'bytes.  ';
     NSPT = Size$(3) * 256 + Size$(4)
     PRINT 'Number of sectors per track is'; NSPT; '.'
     NTPC = Size$(5) * 256 + Size$(6)
     PRINT 'Number tracks per cylinder is'; NTPC; '.';
     NCYL = Size$(7) * 256 + Size$(8)
     PRINT '  Number of cylinders is'; NCYL; '.'
     TotalSectors = NSPT * NTPC * NCYL
     PRINT 'Total number of sectors is'; NUM$(TotalSectors);
     PRINT ' ('; Hexx$(TotalSectors); ').  '

     IF ERROR WHEN
          ! THIS SHOULD MOUNT THE DISK IF IT IS NOT ALREADY MOUNTED
          OPEN # BootFile, Device$ CAT 'BOOT.SYS'
     THEN
          ON ERROR GOTO ErrorTrap
          IF ERR = 1011 OR ERR = 1000 OR ERR = 1018 OR ...
&                          ERR = 1029 OR ERR = 1050
          THEN
               ! I DO NOT CARE
               Mounted = FALSE
               PRINT
               PRINT 'I cannot mount '; Disk$; '.';
               PRINT '  Please be patient, the scan will be slow.'
          ELSE
               ERROR ERR
          FI
     ELSE
          ON ERROR GOTO ErrorTrap
          Mounted = TRUE
          READ # BootFile @ 22, TwoBytes$
          Map$ = HEX$(TwoByteValue(TwoBytes$))
          READ # BootFile @ 32, DiskID$
          FOR I = LEN(DiskID$) TO 1 STEP -1
               IF DiskID$(I) <> :20 THEN EXIT I
               LEN(DiskID$) = I - 1
          NEXT I
          IF LEN(DiskID$) = 0 THEN DiskID$ = Disk$
          PRINT DiskID$; ' is mounted!'
          PRINT 'Map algorithm is '; Map$
          SYSCALL # BootFile, GetParams$, '', Size$
          NSPC = Size$(3)
          NBPC = NSPC * NBPS
          CLOSE # BootFile
          TotalClusters = INT(TotalSectors / NSPC)
          PRINT 'Total number of clusters is';
          PRINT NUM$(TotalClusters);
          PRINT ' ('; Hexx$(TotalClusters); ').'
          PRINT 'Cluster size is'; NSPC; 'sectors.  ';
          PRINT 'Cluster size is'; NBPC; 'bytes.  '
     FI

     IF DiskFlag = TRUE AND Mounted = TRUE THEN
          IF ERROR WHEN
               OPEN # DirecFile, Device$ CAT 'DIRECTORY.SYS'
          THEN
               ON ERROR GOTO ErrorTrap
               DirecOk = FALSE
          ELSE
               ON ERROR GOTO 100
               DirecOk = TRUE
          FI
     FI

     IF DirecOk = TRUE THEN
          DONE = FALSE
          WHILE DONE = FALSE DO
               READ # DirecFile, DirEntry$
               IF EOF(DirecFile) THEN
                    PRINT
                    PRINT 'CAN NOT FIND THE BAD CLUSTERS FILE!'
                    BadClustersFileOk = FALSE
                    DONE = TRUE
               FI
               IF DirEntry$(19) <> 0 THEN
                    IF Direntry$(1, 15) = 'BADCLUSTERS.SYS' THEN
                         BadClustersFileOk = TRUE
                         DONE = TRUE
                    FI
               FI
          END  \! WHILE DONE = FALSE DO
     FI

     IF BadClustersFileOk = TRUE THEN
          HCNumber = TwoByteValue(DirEntry$(17, 2))
          BadClustersHCSIC = DirEntry$(19)
          PRINT 'Header cluster of BADCLUSTERS.SYS is at ';
          PRINT Hexx$(HCNumber)
     FI

     IF DiskFlag = TRUE AND Mounted = TRUE AND DirecOk = TRUE THEN
          IF ERROR WHEN
               HCLocation = HCNumber * NSPC * NBPS + 2
               POSITION # Disk, HCLocation
               BadClusters = 0
               FOR I = 1 TO NBPS * BadClustersHCSIC / 2 - 1
                    READ # Disk, TwoBytes$
                    IF TwoByteValue(TwoBytes$) <> :FFFF
                         THEN BadClusters = BadClusters + 1
               NEXT I
          THEN
               ON ERROR GOTO ErrorTrap
               BadClustersHCOk = FALSE
          ELSE
               ON ERROR GOTO ErrorTrap
               BadClustersHCOk = TRUE
               PRINT 'Number of bad clusters is'; NUM$(BadClusters); '.'
          FI
          ON ERROR GOTO ErrorTrap
     FI

     !---------------------------------------------------------------

     ! CALCULATE THE TIME TO READ A SECTOR

     LEN(Sector$) = NBPS
     POSITION # ScanFile, 0

     IF ERROR WHEN
          StartTime = CurrentTime
          FOR I = 1 TO NSPT * NTPC * 10
               READ # ScanFile, Sector$[1, NBPS]
          NEXT I
          FinishTime = CurrentTime
     THEN
          ON ERROR GOTO ErrorTrap
          TimeFlag = 0
     ELSE
          ON ERROR GOTO ErrorTrap
          TimeFlag = (FinishTime - StartTime) / (NSPT * NTPC * 10)
     FI

     !---------------------------------------------------------------

100  ! MAIN PROGRAM STARTS HERE
     INPUT 'Enter starting sector (<CR> defaults to 0): ' Start$

     IF LEN(Start$) > 0
     THEN
          IF Start$(1, 1) = ':' AND LEN(Start$) > 9 THEN
               ERROR 104
          FI
          IF Start$(1, 1) = ':' AND LEN(Start$) > 5
          THEN
               Temp$ = ':' CAT RIGHT$(Start$, LEN(Start$) - 3)
               Start$ = Start$(1, LEN(Start$) - 4)
               Sector = VAL(Start$) * 65536 + VAL(Temp$)
          ELSE
               Sector = VAL(Start$)
          FI
     ELSE
          Sector = 0
     FI

     Start = Sector
     POSITION # ScanFile, Sector * NBPS
     LEN(Sector$) = NBPS

     !---------------------------------------------------------------

     IF TimeFlag = 0
     THEN
          PRINT 'I cannot compute the time required.'
     ELSE
          PRINT 'The current time is '; TIME$
          PRINT 'The time required for this scan will be about ';
          PRINT TimeString$(TimeFlag * (NSPT * NTPC * NCYL - Start))
     FI

     PRINT
     WRITE # Console, Hexx$(Sector), CHR$(:0D)

     !---------------------------------------------------------------

     REPEAT
200       IF ERROR WHEN
               READ # ScanFile, Sector$[1, NBPS]
          THEN
               ON ERROR GOTO ErrorTrap
               GOSUB ReportError
          ELSE
               ON ERROR GOTO ErrorTrap
          FI

          IF EOF(ScanFile) THEN
               IF Start <> Sector THEN
                    PRINT
                    PRINT 'Last sector of media is ';
                    PRINT Hexx$(Sector - 1);
                    PRINT '   ('; NUM$(Sector - 1); ' )'
                    PRINT 'Total sectors with errors is';
                    PRINT NUM$(ErrorCount)
               FI
               PRINT
               IF Dismounted = TRUE THEN
                    IF ERROR WHEN
                         SYSCALL # ScanFile, GetErrorStats$, '', Stats$
                    THEN
                         ON ERROR GOTO ErrorTrap
                         PRINT 'No error statistics are available.'
                    ELSE
                         ON ERROR GOTO ErrorTrap
                         PRINT 'Seek error count ';
                         PRINT Stats$[1] * 256 + Stats$[2];
                         PRINT 'Last seek status ';
                         PRINT Hex$(Stats$[3] * 256 + Stats$[4])
                         PRINT 'Read error count ';
                         PRINT Stats$[9] * 256 + Stats$[10];
                         PRINT 'Last read status ';
                         PRINT Hex$(Stats$[11] * 256 + Stats$[12])
                    FI
               FI
               PRINT
               IF ValidateFlag = TRUE THEN
                    IF DoFileActive THEN
                         SYSCALL Closelog$
                         CLOSE # 0
                         OPEN # 0, 'CONSOLE:'
                         PRINT
                         PRINT '*** Do File Aborted ***'
                         PRINT
                    FI
                    CHAIN 'SDOSDISKVALIDATE'
               FI
               EXIT
          FI

          IF MOD64(Sector) = 0 THEN
               WRITE # Console, Hexx$(Sector), CHR$(:0D)
          FI

          Sector = Sector + 1

     END       \! OF REPEAT

     !---------------------------------------------------------------

ErrorTrap:

     IF ERR = 1 AND ELN > 100 THEN
          PRINT
          GOTO 100
     FI

     PRINT
     PRINT Error$(ERR); ' occured at line'; ELN; ' ('; Hexx$(ELN); ')'

     IF DoFileActive THEN
          SYSCALL Closelog$
          CLOSE # 0
          OPEN # 0, 'CONSOLE:'
          PRINT
          PRINT '*** Do File Aborted ***'
          PRINT
     FI

     IF ValidateFlag = TRUE THEN CHAIN 'SDOSDISKVALIDATE'

     EXIT

     !---------------------------------------------------------------

ReportError:  ! WE GOT AN ERROR SO COME TO HERE

     IF Sector > NSPC * TotalClusters THEN ReturnFromReportError

     IF ERROR WHEN
          SYSCALL # Disk, GetErrorStats$, '', Stats$
          SYSCALL # Disk, GetLastBadLsn$, '', BadLsn$
     THEN
          ON ERROR GOTO ErrorTrap
          StatFlag = FALSE
     ELSE
          ON ERROR GOTO ErrorTrap
          StatFlag = TRUE
          LastBadSector$ = ':' CAT HEX$(BadLsn$(1))(4, 2) CAT ...
&              HEX$(BadLsn$(2))(4, 2) CAT HEX$(BadLsn$(3))(4, 2)
          IF LastBadSector$ <> Hexx$(Sector) THEN
               GOTO ReturnFromReportError
          FI
     FI

     PRINT
     PRINT
     PRINT
     PRINT Error$(ERR);
     PRINT ' occurred while reading sector ';
     PRINT Hexx$(Sector); ' of '; Disk$; '.'

     LastBadCluster$ = Hexx$(INT(Sector / NSPC))
     PRINT 'This is in cluster number '; LastBadCluster$

     !     READ ERROR     DEVICE TIMED OUT
     IF ERR <> 1045 AND ERR <> 1042
     THEN
          PRINT 'I will skip this sector.'
          GOTO ReturnFromReportError
     FI

     IF StatFlag = FALSE
     THEN
           PRINT 'No error statistics are available.'
     ELSE
          PRINT 'Seek error count ';
          PRINT Stats$[1] * 256 + Stats$[2];
          PRINT 'Last seek status ';
          PRINT Hex$(Stats$[3] * 256 + Stats$[4])
          PRINT 'Read error count ';
          PRINT Stats$[9] * 256 + Stats$[10];
          PRINT 'Last read status ';
          PRINT Hex$(Stats$[11] * 256 + Stats$[12])
          PRINT 'Last bad sector number '; LastBadSector$
     FI

     IF BadClustersHCOk = TRUE THEN
          IF AlreadyBad(INT(Sector / NSPC)) THEN
               PRINT 'This cluster is already in BADCLUSTERS.SYS'
               GOTO ReturnFromReportError
          FI
     FI

     ErrorCount = ErrorCount + 1

     PRINT 'How many times to retry (Default is 0)? ';
     INPUT # Console, ReTryCount$

     IF ERROR WHEN
          ReTryCount = VAL(ReTryCount$)
     THEN
          ON ERROR GOTO ErrorTrap
          ReTryCount = 0
     ELSE
          ON ERROR GOTO ErrorTrap
     FI

     GotIt = FALSE
     FOR Index = 1 TO ReTryCount
          IF ERROR WHEN
               POSITION # ScanFile, Sector * NBPS
          THEN
               ON ERROR GOTO ErrorTrap
               PRINT
               PRINT Error$(ERR); ' occurred when I tried ';
               PRINT 'to POSITION for re-try at sector ';
               PRINT Hexx$(Sector); '.  I will skip this sector.'
               GOTO ReturnFromReportError
          ELSE
               ON ERROR GOTO ErrorTrap
          FI
          IF ERROR WHEN
               READ # ScanFile, Sector$[1, NBPS]
          THEN
               ON ERROR GOTO ErrorTrap
               !     READ ERROR     DEVICE TIMED OUT
               IF ERR <> 1045 AND ERR <> 1042 THEN ERROR FI
               CYCLE Index
          ELSE
               ON ERROR GOTO ErrorTrap
               GotIt = TRUE
               EXIT Index
          FI
     NEXT Index

     IF GotIt = FALSE
     THEN
          IF ReTryCount > 0 THEN PRINT "I can't seem to get it..." FI
          PRINT 'Stomp on it? ';
          INPUT # Console, Answer$
          LEN(Sector$) = NBPS
          FOR I = 1 TO LEN(Sector$) DO Sector$(I) = ASC '%'
     ELSE
          PRINT 'I got the sector!'
          PRINT 'Write it back? ';
          INPUT # Console, Answer$
     FI

     IF UPPERCASE$(Answer$) = 'YES'
     THEN
          ! WRITE IT BACK OUT (HOPEFULLY IN A MORE READABLE FASHION)

          IF ERROR WHEN
               POSITION # ScanFile, Sector * NBPS
          THEN
               ON ERROR GOTO ErrorTrap
               PRINT
               PRINT Error$(ERR);
               PRINT ' occurred when I tried to POSITION at sector ';
               PRINT Hexx$(Sector); '.  I will skip this sector.'
               GOTO ReturnFromReportError
          ELSE
               ON ERROR GOTO ErrorTrap
          FI

          IF ERROR WHEN
               WRITE # ScanFile, Sector$
               SYSCALL # ScanFile, DumpBuffers$
          THEN
               ON ERROR GOTO ErrorTrap
               ! DEVICE TIMED OUT OR WRITE ERROR OR READ ERROR
!!!!!               IF ERR = 1042 OR ERR = 1046 OR ERR = 1045
               PRINT
               PRINT Error$(ERR);
               PRINT ' occurred when I tried to WRITE at sector ';
               PRINT Hexx$(Sector); '.  I will skip this sector.'
               GOTO ReturnFromReportError
          ELSE
               ON ERROR GOTO ErrorTrap
               IF GotIt = FALSE
               THEN
                    PRINT 'I STOMPED on sector '; Hexx$(Sector);
                    PRINT ' of '; Disk$; '.  '
                    PRINT 'The data in that sector is now garbage.'
                    IF DoFileActive THEN ERROR 104 FI
               ELSE
                    PRINT 'I wrote the data back on sector ';
                    PRINT Hexx$(Sector); ' of '; Disk$; '.  '
                    PRINT 'The data in that sector should be OK.'
               FI
               IF Sector > 100
               THEN
                    Sector = Sector - 100
               ELSE
                    Sector = 0
               FI
          FI
     ELSE
          IF BadClustersHCOk = TRUE
          THEN
               PRINT 'Do you want to add cluster '; LastBadCluster$;
               PRINT ' to BADCLUSTERS.SYS? ';
               INPUT # Console, Answer$
               IF UPPERCASE$(Answer$) = 'YES' THEN
                    LEN(Temp$) = 2
                    Temp$(1) = VAL(':' CAT LastBadCluster$(4, 2))
                    Temp$(2) = VAL(':' CAT LastBadCluster$(6, 2))
                    FOR Index = 0 TO NBPS * BadClustersHCSIC / 2 - 2
                         POSITION # Disk, HCLocation + Index * 2
                         READ # Disk, TwoBytes$
                         IF TwoByteValue(TwoBytes$) = :FFFF THEN
                              POSITION # Disk, HCLocation + Index * 2
                              WRITE # Disk, Temp$
                              SYSCALL # Disk, DumpBuffers$
                              ValidateFlag = TRUE
                              EXIT Index
                         FI
                    NEXT Index
                    IF Index > NBPS * BadClustersHCSIC / 2 - 2
                    THEN
                         PRINT 'Header sector full of bad clusters.'
                         PRINT 'I did not the cluster to BADCLUSTERS.SYS'
                    ELSE
                         PRINT 'I added '; LastBadCluster$; ' to BADCLUSTERS.SYS'
                    FI
               FI
          ELSE
               PRINT 'I am not able to add to BADCLUSTERS.SYS'
          FI
     FI

ReturnFromReportError:

     ON ERROR GOTO ErrorTrap

     WRITE # Console, Hexx$(Sector + 1), CHR$(:0D)
     POSITION # ScanFile, (Sector + 1) * NBPS

     RETURN

     !---------------------------------------------------------------

     END
