! *** S D O S D I S K V A L I D A T E ***
!
!     PROGRAM TO VALIDATE AND REPAIR SDOS 1.0 FILE-SYSTEM STRUCTURED DISKS.
!
!     REVISION HISTORY:
!
!     EDITED 12/26/79 TO PRINT STATUS ON DISMOUNT
!                     ALSO, 'DISK:' IS DEFAULT FOR DEVICE
!     EDITED 12/28/79 TO NOT BLOW UP WHEN DOING ABOVE
!
!     EDITED 03/06/80 TO WORK UNDER BASIC VERSION 1.4
!     EDITED 7/21/80  TO WORK UNDER BASIC V1.4G
!     EDITED 2/4/81   TO WORK WITH BASIC V14H
!                     REVISED SDOSDISKVAL.PAS2 TO ENHANCE RECOVERY OF DIRECTORY
!
!     V1.1G  12/23/82 Change to operate with SDOS Version 1.1
!                     Handle DISKMAP.SYS for 40Mb disk, NSPC=5, NBPS=512
!                     Fix DISK READ error in DIRECTORY.SYS crashes program
!                     Added logic to validate BOOT.SYS and SDOS.SYS files
!                     Ensured that USERSPACE of :7C00 is enough.
!
!     V1.1H  11/29/83 Fixed so getting bad LSN would do correct status
!                     call and therefore show Disk error status. (RCW)
!
!    Future improvements possible:
!         add flag indicating user chose NOT to fix something, and message
!             at end to the effect that the flag is set, file structure unsafe.
!         check that same file name (especially DIRECTORY.SYS) is not in
!             directory twice.
!         compute contiguity of files and display statistics.
!
!^L
    PROGRAM ORIGIN :3400

    DATA ORIGIN :2E00

    COMMON Q$[34],DEVICE$[20],FNAME$[16],FNAME2$[16],OP$[12],DIR$[32]
    COMMON DIR2$[32],DEVDIR$[30]
    COMMON BYTE$[1],TWOB$[2],MINUS1$[2]/:FF,:FF/
    COMMON NBPS,NLSN,NLCN,NSPC,HEADERBYTE,HCN,HCN2,HCSIC,HCSIC2
    COMMON MODF/0/,MOD2F,DISMOUNT$[4]/:E,:4,1,:11/,UNPROT$[4]/:E,:4,3,:10/
    COMMON NSPT,NTPC,NCYL,NOTBADS/0/,NLCNS,NLCNS2,STAT$[23]/:F,:E,2,5/
    COMMON SETMAP$[8]/:E,8,2,:12,0,0,0,2/,BUFFER$[20],BUFAD$/0,0/
    COMMON UNPROT2$[3]/:E,:4,2,:10/
    COMMON MAXFSIZE,GETPOS$[14]/:F,:E,1,0/,HCN$[2],HCSIC$[1],LCNS$[2],FSIZE$[4]
    COMMON PROT$[1],EMPTY$[6],PROT,FSIZE,CKSUM,BYTES,BAD,KILL,POS
    COMMON AZ$/"ABCDEFGHIJKLMNOPQRSTUVWXYZ$"/
    COMMON PER09$/".0123456789"/
    COMMON OPTIONS

! COMMON BETWEEN SDOSDISKVALIDATE AND SDOSDISKVAL.PAS2
    COMMON BADLSN$[3],DIRLSN

! COMMON WITH SDOSDISKVAL.PAS2 AND SDOSDISKAL.PAS3
    COMMON UPDATEBOOT/0/,FFFFFF$/:FF,:FF,:FF/
    COMMON FFFF$/:FF,:FF/,ERTYPE

! STORAGE LOCAL TO PASS 1
    DIM BOOT$(512),BOOTDIRLSN/28/
    DIM SCGETTYPE$/:F,:E,1,:4/
    DIM GETBADLSN$/:F,:E,1,:10/
    DIM GETSTAT$/:F,:E,1,:11/
    DIM HEXDIGITS$/"0123456789ABCDEF"/

!^L
!   SDOSVALIDATE SECTION 1
!       CHECKS BOOT FILE AND DIRECTORY
!       FIXES BOOT FILE IF NEEDED
!       CALLS PASS2 IF DIRECTORY CANNOT BE FOUND OR NEEDS FIXING
!       ELSE CALLS PASS3 IF THERE IS A BAD LSN OR OPTIONS ARE NOT <CR>
!       IF NOTHING ELSE, CALLS PASS4

    DEF MSB(X1)=INT(X1/256)
    DEF LSB(X2)=INT(X2-256*MSB(X2))

    LEN(BOOT$)=MAXLEN(BOOT$)
    LEN(SETMAP$)=8
    LEN(BUFFER$)=20
    LEN(STAT$)=16
    LEN(GETPOS$)=14

SDOSDISKVALIDATE: ! Begin main program
    IF COL(0)>1
    THEN
        INPUT "" DEVICE$
        PRINT "(101) SDOS Disk Validate and Repair, V1.1h"
    ELSE
        PRINT "(101) SDOS Disk Validate and Repair, V1.1h"
        INPUT "(102) Validate which disk (DISK: is default): " DEVICE$
    FI
    IF DEVICE$="" THEN DEVICE$="DISK:"
    OPEN #1,DEVICE$
    SYSCALL #1,SCGETTYPE$,'',BADLSN$
    IF BADLSN$[1]<>1 THEN Error 1058 \ ! Device must be a disk
    CALL SYSCALL(GETBADLSN$,"",BADLSN$)
    CALL SYSCALL(GETSTAT$,'',BUFFER$)
    IF 0<>BUFFER$[1]!BUFFER$[2]!BUFFER$[5]!BUFFER$[6]!BUFFER$[9]!BUFFER$[10]
    THEN
        PRINT "(116) Error totals:"
        PRINT "Operation","Error count","Status"
        PRINT "---------","-----------","------"
        PRINT "Read",BUFFER$[9]**8+BUFFER$[10],...
&                    HEX$(BUFFER$[11]**8+BUFFER$[12])
        PRINT "Write",BUFFER$[5]**8+BUFFER$[6],...
&                     HEX$(BUFFER$[7]**8+BUFFER$[8])
        PRINT "Seek",BUFFER$[1]**8+BUFFER$[2],...
&                    HEX$(BUFFER$[3]**8+BUFFER$[4])
        PRINT
        PRINT "Last Bad LSN = :";Hex$(BUFFER$[16])[4,2];...
&             Hex$(BUFFER$[17]*256+BUFFER$[18])[2,4]
        PRINT
        PRINT "      A total of";
        PRINT (BUFFER$[1]+BUFFER$[5]+BUFFER$[9])*256+...
&             BUFFER$[2]+BUFFER$[6]+BUFFER$[10];
        PRINT "I/O errors out of";
        PRINT BUFFER$[15]+BUFFER$[14]**8+BUFFER$[13]*256*256;
        PRINT "operations."
    ELSE
        PRINT "(117) A total of";
        PRINT BUFFER$[15]+BUFFER$[14]**8+BUFFER$[13]*256*256;
        PRINT "I/O operations."
    FI

    CALL SYSCALL(DISMOUNT$)
    PRINT "(103) Device dismounted."
105 INPUT "(104) Options (N=Noisy, V=Verify, <CR>=none)? " Q$
    IF UPPERCASE$(Q$)="N" THEN LET OPTIONS=1\GOTO 106
    IF UPPERCASE$(Q$)="V" THEN LET OPTIONS=2\GOTO 106
    IF Q$<>"" THEN 105
    OPTIONS=0
106 SYSCALL #1,UNPROT2$
    SYSCALL #1,STAT$,"",BUFFER$
    NBPS=BUFFER$[1]*256+BUFFER$[2]
    NSPT=256*BUFFER$[3]+BUFFER$[4]
    NTPC=256*BUFFER$[5]+BUFFER$[6]
    NCYL=256*BUFFER$[7]+BUFFER$[8]
    NLSN=NSPT*NTPC*NCYL

    IF NBPS>LEN(BOOT$)
    THEN
        PRINT "*** SECTOR SIZE TOO BIG TO HANDLE ***"
        ERROR 104
    FI
    IF ERROR WHEN
        READ #1@0,BOOT$[1,NBPS]
    THEN
        IF ERR<>1045 AND ERR<>1046 THEN ERROR
108     PRINT "(105) Unable to read Boot sector."
        INPUT "      Write GARBAGE Boot sector and then try to reconstruct it? " Q$
        GOSUB 1000\ON INVALIDANSWER GOTO 108
        IF Q$="N" THEN ERROR
        LEN(BOOT$)=NBPS
        FOR I=1 TO LEN(BOOT$) DO BOOT$(I)=:A5 \ ! SET TO A VALUE
        RESTORE #1,0\WRITE #1,BOOT$[1,NBPS]
    FI
60  NSPC=BOOT$[18]
    IF BOOT$[17]=:10 THEN 61
605 PRINT "(109) File System Version number is not Version 1.0"
    INPUT "      Change File System Version number to 1.0? " Q$
    GOSUB 1000\ON INVALIDANSWER GOTO 605
    IF Q$="N"
    THEN
        PRINT "      Cannot validate/repair anything but Version 1.0 File Systems"
        EXIT
    FI
    BOOT$[17]=:10
13  GOSUB 600\BOOT$[32]=CKSUM
    OPTIONS=2\UPDATEBOOT=1
61  GOSUB 600\IF BOOT$[32]=CKSUM THEN 71
615 PRINT "(106) BOOT:CHECKSUM is in error."
    INPUT "      Correct it? " Q$
    GOSUB 1000\ON INVALIDANSWER GOTO 615
    IF Q$="N" THEN PRINT "(107) Boot Checksum Failure."\EXIT
    PRINT "(108) BOOT:CHECKSUM corrected."\GOTO 13

FIXNSPC: REM FIRST, FIGURE OUT WHAT SDOSDISKINIT WOULD HAVE SUGGESTED
    FOR CLUSTERSIZE=1 TO 255
        IF 1<=(NBPS**-1)*CLUSTERSIZE^2/(NLSN-1) THEN EXIT CLUSTERSIZE
    NEXT CLUSTERSIZE
    PRINT "(118) Enter new value for BOOT:NSPC (default=";CLUSTERSIZE;"): ";
    INPUT '' Q$
    IF LEN(Q$)=0
    THEN NSPC=CLUSTERSIZE
    ELSE
        ON ERROR GOTO FIXNSPC
        LET NSPC=VAL(Q$)
    FI
    LET BOOT$[18]=NSPC
    ON ERROR GOTO 0
    UPDATEBOOT=1
71  IF NSPC=0 THEN PRINT "(110) BOOT:NSPC=0"\ GOTO FIXNSPC
    NLCN=INT(NLSN/NSPC)
    IF NLCN>65536 THEN
        PRINT "(119) NLCN>65536 because BOOT:NSPC is too small."\ GOTO FIXNSPC
    FI
    IF OPTIONS=0 THEN 714
    PRINT "(111) NLSN=";NLSN;"NLCN=";NLCN;"BOOT:NSPC=";NSPC;
    PRINT "BOOT:MAPALGORITHM=";HEX$(BOOT$[23]*256+BOOT$[24])
714 REM CHECK THAT BOOT:DIRLSN POINTS TO DIRECTORY
    LET DIRLSN=(BOOT$[BOOTDIRLSN]**8+BOOT$[BOOTDIRLSN+1])*256+BOOT$[BOOTDIRLSN+2]
    WHILE DIRLSN=0 OR DIRLSN>=NLSN DO
        PRINT "(121) BOOT:DIRLSN is invalid."
        LET DIRLSN=INT(NLCN/2)*NSPC
        PRINT "      Enter new value for BOOT:DIRLSN (default is ";HEX$(MSB(DIRLSN));HEX$(LSB(DIRLSN))[4,2];"): ";
        INPUT "" Q$
        IF LEN(Q$)>0
        THEN
            LET Q$=UPPERCASE$(Q$)
            LET DIRLSN=0
            FOR I=1+(Q$[1]=ASC(":")) TO LEN(Q$)
                LET HEXDIGIT=FIND(HEXDIGITS$,Q$[I,1])
                IF HEXDIGIT=0 THEN DIRLSN=0\EXIT I
                ELSE DIRLSN=DIRLSN*16+HEXDIGIT-1
             NEXT I
        FI
        IF DIRLSN<2^24 THEN
            BOOT$[BOOTDIRLSN]=MSB(MSB(DIRLSN))
            BOOT$[BOOTDIRLSN+1]=LSB(MSB(DIRLSN))
            BOOT$[BOOTDIRLSN+2]=LSB(DIRLSN)
            GOSUB 600
            BOOT$[32]=CKSUM
            UPDATEBOOT=TRUE
        FI
    END
    SYSCALL #1,SETMAP$,BOOT$[23,2]
    IF ERROR WHEN
        RESTORE #1,DIRLSN*NBPS
        READ #1,FNAME$
    THEN
        IF ERR=1045
        THEN
            PRINT "(122) Disk Read Error on DIRECTORY.SYS directory entry."
            GOTO 70
        ELSE ERROR
    FI
    IF FNAME$="DIRECTORY.SYS   " THEN 15
    ELSE
        PRINT "(124) BOOT:DIRLSN doesn't point to DIRECTORY.SYS directory entry."
        GOTO 70
    FI

70 REM THERE IS SOMETHING WRONG WITH THE DIRECTORY ENTRY FOR DIRECTORY.SYS
   REM ERTYPE=1 --> "DIRECTORY.SYS " NAME FOUND, BUT CRITICAL DATA IS DAMAGED
   REM ERTYPE=2 --> CAN'T FIND NAME OF DIRECTORY.SYS
   REM ERTYPE=3 --> SOME OF DIRECTORY.SYS DATA IS DAMAGED
   REM ERTYPE=4 --> "DISK READ ERROR TRYING TO FETCH ENTRY"
   GOSUB 172
   PRINT "(112) Chaining to SDOSDISKVAL.PAS2"
   CHAIN "SDOSDISKVAL.PAS2"

15  REM FOUND WHAT APPEARS TO BE "DIRECTORY.SYS" DIRECTORY ENTRY
    REM DO QUICK CHECK ON PURPORTED DIRECTORY.SYS HEADER BLOCK
    READ #1,HCN$,HCSIC$,LCNS$,FSIZE$,PROT$,EMPTY$
DIRECTORYSYSENTRYFOUND: REM FOUND THE FILENAME "DIRECTORY.SYS " WHERE IT WAS EXPECTED
    NLCNS=LCNS$[1]**8+LCNS$[2]
    HCSIC=HCSIC$[1]
    IF HCSIC=0 OR NLCNS=0 THEN 70
    IF NLCNS>INT(NLSN/NSPC) THEN 70
    HCN=HCN$[1]*256+HCN$[2]
    IF HCN=0 OR HCN>=NLCN THEN 70
    RESTORE #1,HCN*NBPS*NSPC
    IF ERROR WHEN
        READ #1,TWOB$
    THEN
        IF ERR=1045
        THEN
            PRINT "(123) Disk Read Error on 1st sector of DIRECTORY.SYS header cluster."
            GOTO 70
        ELSE ERROR
    FI
    LCN=TWOB$[1]*256+TWOB$[2]
    IF HCN<>LCN THEN 70
    IF HCSIC>NSPC THEN 70
    REM SCAN PURPORTED DIRECTORY.SYS HEADER CLUSTER, COUNT LCNS
    REM CHECK THAT BOOT:DIRLSN IS MENTIONED BY SOME LCN
    DIRLSNINCLUDED=FALSE
    ACTUALNLCNS=1
    FOR I=1 TO HCSIC*NBPS/2-1
        READ #1,TWOB$
        LET LCN=TWOB$[1]**8+TWOB$[2]
        IF LCN<>:FFFF
        THEN
            REM VALID LCN, COUNT IT
            ACTUALNLCNS=ACTUALNLCNS+1
            IF LCN=INT(DIRLSN/NSPC) THEN DIRLSNINCLUDED=TRUE FI
        ELSE IF ACTUALNLCNS<NLCNS THEN 70
    NEXT I
    IF DIRLSNINCLUDED=FALSE THEN 70
    IF ACTUALNLCNS<>NLCNS THEN 70
    LET FILESIZE=(FSIZE$[1]**8+FSIZE$[2])*65536+FSIZE$[3]**8+FSIZE$[4]
    IF FILESIZE>(HCSIC*NBPS/2-1)*NSPC*NBPS THEN 70
    IF FILESIZE/(NSPC*NBPS)<>INT(FILESIZE/(NSPC*NBPS)) THEN 70
    IF FILESIZE<NSPC*NBPS THEN 70
    REM IF WE GET HERE, THEN SDOS SHOULD HAVE NO TROUBLE OPENING DIRECTORY.SYS
    PRINT "(115) DIRECTORY.SYS directory entry Validated."
    REM ANYTHING ELSE WRONG WITH DIRECTORY.SYS CANBE FIXED BY ...PAS4
19  GOSUB 172
    IF OPTIONS<>0 OR BADLSN$<>FFFFFF$
    THEN
        PRINT "(114) Chaining to SDOSDISKVAL.PAS3"
        CHAIN "SDOSDISKVAL.PAS3"
    FI
    PRINT "(113) Chaining to SDOSDISKVAL.PAS4"
    CHAIN "SDOSDISKVAL.PAS4"

172 ON ERROR GOTO 0
    IF UPDATEBOOT=0 THEN RETURN
    INPUT "(120) Update Boot Sector (default=YES)? " Q$
    GOSUB 1000\ON INVALIDANSWER GOTO 172
    IF Q$="N" THEN RETURN
    GOSUB 600
    LET BOOT$[32]=CKSUM
    RESTORE #1,0
    WRITE #1,BOOT$[1,NBPS]
    RETURN

600 CKSUM=0
    FOR I=17 TO 31
        CKSUM=CKSUM+BOOT$[I]
    NEXT I
    CKSUM=:FF XOR (CKSUM&:FF)
    RETURN

1000 ! IS THAT A GOOD RESPONSE????
     IF Q$=""
     THEN
         INVALIDANSWER=0
         RETURN
     ELSE
         LET Q$=UPPERCASE$(Q$)[1,1] \  REM DON'T CARE ABOUT "ES" OR "O" OR ....
         LET INVALIDANSWER=Q$<>"N" AND Q$<>"Y"
         RETURN
     FI

END
