!***************************************************************************
!DSKACT.BAS - DISK ACCOUNTING  (a "Verify Plus" program)
! by Dave Heyliger - AMUS Staf
!
!   DSKACT was created to discover file differences over a given time. These
! file differences include: new PPNs, new files, deleted PPNs, deleted files,
! and hash code differences (if any) of existing files. DSKACT will produce
! three user-defined output filespecs that contain the following:
!       1) A list of all deleted files in full filespec format.
!       2) A list of all added files in full filespec format.
!       3) A list of all files whose hash codes have changed.
!
!   This program was originally created to discover what files were added/
! changed to the AM3000 when software testing was performed. The output file-
! specs make it easy to discover what needs to be deleted/restored, etc...
! to get the computer "back to normal". However, there are many uses for this
! program. For example, if you are nervous about a VIRUS, well, this is great
! protection. In addition, the output files are easily modified to be used
! with a .CMD file etc, for ERASING or what-have-you.
!
!   To execute DSKACT, the following must be true:
!       1) You have an "old" file containing a logical device's files + hashes
!          that was created with the AMOS command
!                       ".DIR/D/H/K file.ext=Dev#:[]"
!          executed from the OPR: account.
!       2) You have created a "new" file containing the same as in (1) above
!          created in the same fashion as (1) above (usually the next day,
!          week, or what-have-you). The name of this file is different, of
!          course.
!
!       Note: you do NOT need to do the entire device. For example, to just
!             check certain PPNs on a logical, you could use the following:
!                       ".DIR/D/H/K file.ext = Dev#:[P,PN]"
!             Also, as a rule of thumb, the directory file size in blocks will
!             be the "Grand total" number of files divided by 10.
!
!   As an example run of DSKACT, let's use the AMUS DAILY BACKUP command file.
!   For example, in AMUS' DAILY BACKUP I do the following for DSK2: from the
!   OPR: account.
!       1) Make a "new" directory file with ".DIR/D/H/K DISK2.NEW = DSK2:[]".
!          The "/K" option will erase DISK2.NEW if already present before
!          creating DISK2.NEW (just in case). DISK2.OLD is assumed to already
!          exist.
!       2) .RUN DSKACT, which requires the following input:
!               "old" file list: DISK2.OLD
!               "new" file list: DISK2.NEW
!               filename to contain deleted files: DISK2.DEL
!               filename to contain added files: DISK2.ADD
!               filename to contain hash differences: DISK2.HSH
!   Once DSKACT is complete, DISK2.DEL, DISK2.ADD, and DISK2.HSH contain all
!   of the disk's activity in full filespec format.
!       3) Finally, I ".RENAME DISK2.OLD=DISK2.NEW/D" to first delete the
!          "old" file listing and then rename the "new" file list to ".OLD".
!          The output files are then examined, saved, or whatever you want!
!
!***************************************************************************

MAP1    old'file,S,10,""                ! file containing yesterdays files
MAP1    new'file,S,10,""                ! file containing todays files
MAP1    del'file,S,10,""                ! file containing deleted files
MAP1    added'file,S,10,""              ! file containing added files
MAP1    hash'file,S,10,""               ! file containing hash-differences

MAP1    old'mfd'account,S,20            ! old MFD account spec
MAP1    new'mfd'account,S,20            ! new MFD account spec

MAP1    filespec,S,50                   ! individual filespec

MAP1    old'list(800),S,50              ! array holding yesterday's PPN files
MAP1    new'list(800),S,50              ! array holding today's PPN files

MAP1    old'count,F,6                   ! number of files in a yesterday's PPN
MAP1    new'count,F,6                   ! number of files in today's PPN
MAP1    old'index,F,6                   ! index into old'list array
MAP1    new'index,F,6                   ! index into new'list array

MAP1    old'next'list,S,50              ! filename of "next PPN" - yesterday
MAP1    new'next'list,S,50              ! filename of "next PPN" - today

MAP1    files'dsk,S,10                  ! the "Dev#:" of the filespec
MAP1    files'ppn,S,10                  ! the "PPN" of the filepsec
MAP1    files'account,S,20              ! the "Dev#:[PPN] of the filespec"

MAP1    old'project,S,3                 ! the P in Ppn of an old filespec
MAP1    old'prg'number,S,3              ! the PN in pPN of an old filespec
MAP1    new'project,S,3                 ! similar as above, but for new files
MAP1    new'prg'number,S,3

MAP1    temp,F,6                        ! a stupid floating point number


BEGIN:
       ! Get the files necessary for operation
       INPUT "Enter old FILE master list file: ", old'file
       INPUT "Enter new FILE master list file: ", new'file
       INPUT "Enter filespec which will contain DELETED files: ", del'file
       INPUT "Enter filespec which will contain ADDED files: ", added'file
       INPUT "Enter filepsec which will contain HASH differences: ", hash'file

       ! Open both MFD files, plus the DIR/D files (OLD and NEW).
       ! Also create an output file which will contain the stats
       OPEN #1,old'file,INPUT          ! open yesterdays file list
       OPEN #2,new'file,INPUT          ! open todays file list
       OPEN #3,del'file,OUTPUT         ! create new report file
       OPEN #4,added'file,OUTPUT       ! create new report file
       OPEN #5,hash'file,OUTPUT        ! create new report file

       ! Clear the screen, and initialize for first time through
       ? TAB(-1,0); TAB(-1,29);
       old'next'list = ""
       new'next'list = ""

       ! Get one file from ".OLD" list, including the file's account
       INPUT LINE #1, filespec
       IF EOF(1) &
          THEN old'next'list = "." &
          ELSE CALL GET'FILES'ACCOUNT : &
               old'next'list = filespec : &
               old'mfd'account = files'account

       ! Get one file from ".NEW" list, including the file's account
       INPUT LINE #2, filespec
       IF EOF(2) &
          THEN new'next'list = "." &
          ELSE CALL GET'FILES'ACCOUNT : &
               new'next'list = filespec : &
               new'mfd'account = files'account




       ! The following LOOP is repeated and is the main body of the prg.
       ! It analyses each PPN, discovering an ADDED, DELETED, or HASH change
       !
       ! Test condition: If both files EOF, time to exit to dot
LOOP:   IF old'next'list = "." &
          THEN IF new'next'list = "." &
               THEN ? TAB(-1,28) : &
                    END

       ! Test condition: If ".OLD" at EOF, ".NEW" must have new files
       IF old'next'list = "." &
          THEN CALL LIST'NEW'PPN : &
               GOTO L2

       ! Test condition: If ".NEW" at EOF, ".OLD" must have deleted files
       IF new'next'list = "." &
          THEN CALL LIST'DELETED'PPN : &
               GOTO L1

       ! Final test condition: if accounts the same, look for account file
       !   differences ELSE the accounts are different, so report account
       !   addition or deletion.
       IF old'mfd'account = new'mfd'account &
          THEN CALL FIND'DIFFERENCES &
          ELSE CALL ADDED'OR'DELETED'PPN

       ! Once an account has been analysed, set new account specifications
       !   and get the file's account for both ".OLD" and ".NEW" filespecs
L1:     IF old'next'list = "." &
          THEN GOTO L2
       filespec = old'next'list
       CALL GET'FILES'ACCOUNT
       old'mfd'account = files'account
L2:
       IF new'next'list = "." &
          THEN GOTO LOOP
       filespec = new'next'list
       CALL GET'FILES'ACCOUNT
       new'mfd'account = files'account

       ! Finally, check the new PPN listing as before
       GOTO LOOP




!SUBROUTINE FIND'DIFFERENCES
!       On entry: ".OLD" and ".NEW" files contain the same account
!       On exit:  Changes in the given account are recorded
!
!
FIND'DIFFERENCES:
       ? TAB(6,30) "Reading " old'mfd'account  ! display current PPN
       CALL GET'OLD'PPN'FILES                  ! get old files for the PPN
       CALL GET'NEW'PPN'FILES                  ! get new files for the PPN
       ? TAB(6,30) "                       ";  ! clear out above message
       CALL COMPARE'PPN'FILES                  ! compare files in the PPN
       CALL OUTPUT'DIFFERENCES                 ! display the differences
       RETURN                                  ! and return



!SUBROUTINE GET'OLD'PPN'FILES
!       On entry: ".OLD" file has 1 or more files in the current account
!                 being analysed.
!       On exit:  All files in the current account are in the old'list array.
!                 In addition, old'next'list contains next filespec in ".OLD"
GET'OLD'PPN'FILES:

       ! Establish first member of the array with old'next'list
       old'count = 1
       old'list(old'count) = old'next'list

       ! Get another file from ".OLD"  If EOF, then mark end of list with "."
       !    and return
GOPF10: INPUT LINE #1,filespec
       IF EOF(1) &
         THEN old'next'list = "." : &
              old'list(old'count+1) = "." : &
              RETURN

       ! If NOT EOF, then get the files account and make sure it is the same
       !   as the current Dev#:[PPN]. If different, save the filespec just
       !   read in old'next'list, and return
       CALL GET'FILES'ACCOUNT
       IF files'account # old'mfd'account &
          THEN old'next'list = filespec : RETURN

       ! Filespec read in is in the current PPN - place filespec into array
       !   and read another filespec
       old'count = old'count + 1
       old'list(old'count) = filespec
       GOTO GOPF10


!SUBROUTINE GET'NEW'PPN'FILES
!       On entry: ".NEW" file has 1 or more files in the current account
!                 being analysed.
!       On exit:  All files in the current account are in the new'list array.
!                 In addition, new'next'list contains next filespec in ".NEW"
!
GET'NEW'PPN'FILES:
       ! Establish first member of the array with new'next'list
       new'count = 1
       new'list(new'count) = new'next'list

       ! Get another file from ".NEW"  If EOF, then mark end of list with "."
       !    and return
GNPF1:  INPUT LINE #2,filespec
       IF EOF(2) &
         THEN new'next'list = "." : &
              new'list(new'count+1) = "." : &
              RETURN

       ! If NOT EOF, then get the files account and make sure it is the same
       !   as the current Dev#:[PPN]. If different, save the filespec just
       !   read in new'next'list, and return
       CALL GET'FILES'ACCOUNT
       IF files'account # new'mfd'account &
          THEN new'next'list = filespec : RETURN

       ! Filespec read in is in the current PPN - place filespec into array
       !   and read another filespec
       new'count = new'count + 1
       new'list(new'count) = filespec
       GOTO GNPF1



!SUBROUTINE GET'FILES'ACCOUNT
!       On entry: filespec contains a "Dev#:filename.ext[P,PN]"
!       On exit:  files'account contains the "Dev#:[P,PN]"
!
GET'FILES'ACCOUNT:
       files'dsk = LEFT(filespec,INSTR(1,filespec,":"))
       files'ppn = MID(filespec,INSTR(1,filespec,"["),INSTR(1,filespec,"]"))
       files'account = files'dsk + files'ppn
       RETURN



!SUBROUTINE COMPARE'PPN'FILES
!       On entry: both arrays called old'list() and new'list() contains
!                 files from a given account
!       On exit:  Differences are recorded in output files
!
COMPARE'PPN'FILES:

       ! Display the header message to the screen, and initialize the array
       !   index to "1".
       ? TAB(5,19); "Currently working on: ";
       i = 1

       ! Scan the "new'list" array for the current file in the "old'list"
       !   array. If and when they match, make both array elements null
       !   strings (""). To speed things up, bypass all elements in the
       !   new'list array if they have already been nulled. Display the
       !   old'list element while scanning.
CPF10:  FOR j = 1 to new'count
          IF new'list(j) = "" THEN NEXT j
       ? TAB(5,41); : ? LEFT(old'list(i),30);

       ! If the new'list element is "", then get the next element. Then,
       !   if we are at the end of the new'list array, the old'list element
       !   will NOT be nulled to "". Then, if not at the end of the old'list,
       !   get the next file in the old'list and restart at CPF10
CPF20:  IF new'list(j) = "" &
          THEN j = j + 1 : &
               IF j > new'count &
                  THEN i = i + 1 : &
                       IF i <= old'count &
                          THEN GOTO CPF10 &
                          ELSE RETURN

       ! However, if the new'list element is NOT "", see if the old'list &
       !   new'list element filenames match. If they match, record hash code
       !   changes if necessary, null both strings to "", and check end cond.
       ! And, (finally) if the new'list element is NOT "" and it DOESN'T
       !   match, then get the next new'list element, check end cond. & act
       IF LEFT(old'list(i),30) = LEFT(new'list(j),30) &
          THEN CALL COMPARE'HASH'TOTALS : &
               old'list(i) = "" : &
               new'list(j) = "" : &
               i = i + 1 : &
               IF i <= old'count &
                  THEN GOTO CPF10 &
                  ELSE RETURN &
          ELSE j = j + 1 : &
               IF j > new'count  &
                  THEN i = i + 1 : &
                       IF i <= old'count &
                          THEN GOTO CPF10 &
                          ELSE RETURN &
                  ELSE GOTO CPF20



!SUBROUTINE OUTPUT'DIFFERENCES
!       On entry: both arrays old'list() and new'list() contain filenames
!                 if and only if the filenames were added/deleted
!       On exit:  these files will be placed in the correct list output file
!
OUTPUT'DIFFERENCES:
       ? "               "

       ! Output all non-null strings in old'list to DELETED filespec
       FOR i = 1 to old'count
          IF old'list(i) # "" &
             THEN ? #3, LEFT(old'list(i),30)
       NEXT i

       ! Output all non-null strings in new'list to ADDED filespec
       FOR i = 1 to new'count
          IF new'list(i) # "" &
             THEN ? #4, LEFT(new'list(i),30)
       NEXT i

       RETURN



!SUBROUTINE COMPARE'HASH'TOTALS
!       On entry: ".OLD" filespec matched a ".NEW" filespec
!       On exit:  If hash code totals are different, this difference is
!                 recorded in the proper output filespec
!
COMPARE'HASH'TOTALS:
       IF MID(old'list(i),31,15) # MID(new'list(j),31,15) &
          THEN ? #5, LEFT(old'list(i),30)
       RETURN



!SUBROUTINE ADDED'OR'DELETED'PPN
!       On entry: file accounts didn't match between ".OLD" and ".NEW"
!       On exit:  depending on the P and PN values, either deleted or
!                 added files listed in proper output filespec.
!
ADDED'OR'DELETED'PPN:

       ! Get the P and PN of the old MFD account
       temp = INSTR(1,old'mfd'account,",")
       old'project = MID(old'mfd'account,7,temp-7)
       old'prg'number = MID(old'mfd'account, &
          temp+1,LEN(old'mfd'account)-temp-1)

       ! Get the P and PN of the new MFD account
       temp = INSTR(1,new'mfd'account,",")
       new'project = MID(new'mfd'account,7,temp-7)
       new'prg'number = MID(new'mfd'account, &
          temp+1,LEN(new'mfd'account)-temp-1)

       ! If the old P is larger than the new P, must be a new PPN
       IF VAL(old'project) > VAL(new'project) &
          THEN CALL LIST'NEW'PPN : &
               RETURN

       ! If the old P is less than the new P, must be a deleted PPN
       IF VAL(old'project) < VAL(new'project) &
          THEN CALL LIST'DELETED'PPN : &
               RETURN

       ! Else, the PN will give it away as to new or old PPN
       IF VAL(new'prg'number) < VAL(old'prg'number) &
          THEN CALL LIST'NEW'PPN &
          ELSE CALL LIST'DELETED'PPN

       RETURN



!SUBROUTINE LIST'NEW'PPN
!       On entry: A new PPN has been found in ".NEW"
!       On exit:  The contents of this PPN are listed in added output filespec
!                 and the next filespec is contained in new'next'list
!
LIST'NEW'PPN:

       ! Output what's been new
       ? TAB(6,30); "New PPN Addition - ";
       ?  new'mfd'account;

       ! Then output all new files as long as the PPN doesn't change
       ! If it changes, record the changes and return
       ? #4, LEFT(new'next'list,30)
LNP1:   INPUT LINE #2,filespec
       IF EOF(2) &
          THEN new'next'list = "." : &
               ? TAB(6,30); "                                 "; : &
               RETURN
       CALL GET'FILES'ACCOUNT
       IF files'account # new'mfd'account &
          THEN new'next'list = filespec : &
               CALL GET'FILES'ACCOUNT : &
               new'mfd'account = files'account : &
               ? TAB(6,30); "                                 "; : &
               RETURN
       ? #4, LEFT(filespec,30)
       GOTO LNP1



!SUBROUTINE LIST'DELETED'PPN
!       On entry: An old PPN has been deleted
!       On exit:  The contents of this PPN are listed in deleted output file
!                 and the next filespec is contained in old'next'list
!
LIST'DELETED'PPN:

       ! Output the PPN that was deleted
       ? TAB (6,30); "PPN Deletion - ";
       ? old'mfd'account;
       ? #3, LEFT(old'next'list,30)

       ! Output each file in the PPN until the PPN changes. When it does
       ! change, record the change and return
LDP1:   INPUT LINE #1,filespec
       IF EOF(1) &
         THEN old'next'list = "." : &
              ? TAB(6,30); "                                  "; : &
              RETURN
       CALL GET'FILES'ACCOUNT
       IF files'account # old'mfd'account &
          THEN old'next'list = filespec : &
               CALL GET'FILES'ACCOUNT : &
               old'mfd'account = files'account : &
               ? TAB(6,30); "                                 "; : &
               RETURN
       ? #3,LEFT(filespec,30)
       GOTO LDP1