// BFSTestEx.bcpl -- Exercise command.  Derived from TFUExercise.bcpl.
// Copyright Xerox Corporation 1982
// Last modified March 28, 1982  4:05 PM by Boggs

get "AltoFileSys.d"
get "Disks.d"
get "BFS.d"

external
[
// outgoing procedures
Exercise; uBlockCheck

// incoming procedures from OS modules
BFSInit; CloseDisk; OpenFile; DeleteFile
Closes; Resets; Puts; Gets; Endofs
FileLength; PositionPage; PositionPtr
ReadBlock; WriteBlock; BlockCheck; XferError
PutTemplate; Ws; GetNumber; Confirm
Allocate; Free; MoveBlock; SetBlock; Zero
DoubleAdd; CallSwat; Random; EtherRcvr
Idle; MyIdle; Noop

// incoming statics
keys; dsp; sysZone; sysDisk
]

static
[
dataCycle		//data pattern generator state
errorStop
errorCount
]

manifest
[
filePages = 101		//length of test files

// file operations
opWrite = 0
opRead = 1
opPosition = 2
opDelete = 3
opCopy = 4
maxOp = 4
]

structure String: [ length byte; char↑1,1 byte ]

//----------------------------------------------------------------------------
let Exercise() be
//----------------------------------------------------------------------------
[
// Ask the user about her intentions
sysDisk = BFSInit(sysZone, true, 0)
if sysDisk eq 0 then
   [
   Ws("*NCan't init the disk.  Is it on?  Is it formatted (use ERASE)?")
   return
   ]
let nPasses = GetNumber(".  How many passes? ", 10)
Idle = MyIdle
EtherRcvr(true)

// make the test files
Ws("*NCreating files ")
let nFiles = 0
   [
   nFiles = nFiles +1
   CreateOneFile(nFiles)
   PutTemplate(dsp, "$D ", nFiles)
   ] repeatwhile sysDisk>>BFSDSK.freePages ugr filePages+20

for pass = 1 to nPasses do
   [
   PutTemplate(dsp, "*N*NPass $UD: ", pass)
   for file = 1 to nFiles do
      [
      unless Endofs(keys) do
         [
         Gets(keys)
         Ws("[Command: ")
         switchon Gets(keys) into
            [
            case $Q: case $q:
               [ Ws("Quit] "); pass = nPasses; break ]
            case $S: case $s:
               [ Ws("Stop On Error] "); errorStop = true; endcase ]
            default:
               [ Ws("? StopOnError or Quit] "); endcase ]
            ]
         ]
      let op, opName, oFile, s1, s2 = MRandom(maxOp+1), nil, nil, 0, 0
      s1 = op eq opDelete? 0, OpenOneFile(file)
      switchon op into
         [
         case opWrite: [ opName = "Write"; docase -1 ] 
         case opRead: [ opName = "Read"; docase -1 ]
         case opCopy:
            [
            oFile = MRandom(nFiles)+1 repeatwhile oFile eq file
            s2 = OpenOneFile(oFile)
            opName = "Copy"; docase -1
            ]
         case -1:
            [
            PutTemplate(dsp, "$S $D ", opName, file)
            if op eq opCopy then PutTemplate(dsp, "to $D ", oFile)
            TransferData(op, s1, s2)
            endcase
            ]
// Exercise (cont'd)

         case opPosition:
            [
            PutTemplate(dsp, "Position $D ", file)
            let npages = GetNPages(s1)
            for i = 0 to 20 do
               [
               let pPage = MRandom(npages) +1
               PositionPage(s1, pPage)
               let a = Gets(s1)
               if a ne pPage then CheckError(lv a, pPage, 0)
               PositionPtr(s1, (BFSwordsPerPage-1)*2)
               if MRandom(3) then Puts(s1, a)//one third of time cause a write
               ]
            endcase
            ]
         case opDelete:
            [
            PutTemplate(dsp, "Delete $D ", file)
            DeleteOneFile(file); CreateOneFile(file)
            endcase
            ]
         ]
      if s1 then Closes(s1)
      if s2 then Closes(s2)
      ]  //file loop
   ]  //pass loop

// now delete all the test files
Ws("*N*NDeleting files ")
for file = 1 to nFiles do
   [ DeleteOneFile(file); PutTemplate(dsp, "$D ", file) ]

CloseDisk(sysDisk)
EtherRcvr(false)
Idle = Noop
PutTemplate(dsp, "*NThere were $UD errors.", errorCount)
]

//----------------------------------------------------------------------------
and TransferData(op, s1, s2) be
//----------------------------------------------------------------------------
// Transfer a bunch of data.  Go for entire length of s1 file.
[
// Allocate a BIG buffer:
let bufLen = 77777B
let buf = Allocate(sysZone, bufLen, lv bufLen)
if buf eq 0 then buf = Allocate(sysZone, bufLen)

let goodData = 0
if op eq opWrite then
   [
   dataCycle = (dataCycle+1) & 37B
   goodData = 1 lshift (dataCycle & 17B)
   if (dataCycle & 20B) ne 0 then goodData = not goodData
   ]

let fl = vec 1; GetNPages(s1, fl)
// Convert fl from bytes to words--will count remaining words to do
fl!1 = fl!1 rshift 1 + fl!0 lshift 15; fl!0 = fl!0 rshift 1
let cp = vec 1; Zero(cp, 2)

while fl!0 ne 0 % fl!1 ne 0 do
   [
   let doCount = bufLen
   if fl!0 eq 0 & fl!1 ule bufLen then doCount = fl!1
   let written = false
   test op eq opWrite
      ifso  //write s1
         [
         SprinkleData(buf, doCount, cp, goodData, true)
         WriteBlock(s1, buf, doCount)
         SprinkleData(buf, doCount, cp, goodData, false)
         ]
      ifnot  //read s1, may write s2
         [
         ReadBlock(s1, buf, doCount)
         goodData = SprinkleData(buf, doCount, cp, goodData, false)
         if op eq opCopy then  //write s2
            [
            WriteBlock(s2, buf, doCount)
            SprinkleData(buf, doCount, cp, goodData, false)
            ]
         ]

   // double subtract doCount from fl
   let donec = vec 1; donec!0 = -1; donec!1 = -doCount;
   DoubleAdd(fl, donec)

   // double add doCount to cp
   donec!0 = 0; donec!1 = doCount
   DoubleAdd(cp, donec)
   ]

Free(sysZone, buf)
]

//----------------------------------------------------------------------------
and SprinkleData(buf, bufLen, cp, goodData, write) = valof
//----------------------------------------------------------------------------
// When known data is written on a file, the first word is the page
//  number, then come 254 words of constant data, and then the page
//  number again.  But because the buffer in core is not aligned on 
//  page boundaries, the setting and checking of it is a bit messy!
[
let page = cp!0 lshift 8 + cp!1 rshift 8 +1
let phase = (cp!1) & BFSwordsPerPage-1

// Following 3 in order for microcode
let p, val, nWords = buf-phase, nil, nil

let bufEnd = buf+bufLen
   [  //repeatuntil p eq bufEnd
   for s = 0 to 2 do if p ne bufEnd then
      [
      // s=0 => first word of page.  Contains page number.
      // s=1 => body of page.  Contains goodData.
      // s=2 => last word of page.  Contains page number.
      nWords = s eq 1? BFSwordsPerPage-2, 1
      if p+nWords ugr buf then
         [
         if p uls buf then [ nWords = nWords-(buf-p); p = buf ]
         if p+nWords ugr bufEnd then nWords = bufEnd-p
         val = s eq 1? goodData, page
         test write
            ifso SetBlock(p, val, nWords)
            ifnot
               [
               let pOffset = 0  //s = 0
               if s eq 2 then pOffset = BFSwordsPerPage-1
               if s eq 1 then
                  [
                  pOffset = 1
                  // If goodData is zero, we don't know what the pattern
                  //  should be.  All patterns have all but one bit on
                  //  or all but one bit off; check that this is so, then
                  //  assume that this must be the pattern.
                  if goodData eq 0 then
                     [
                     let error = true
                     for i = 0 to 1 do for j = 0 to 15 do
                        [
                        let bitV = 1 lshift j
                        if i ne 0 then bitV = not bitV
                        if bitV eq p!0 then error = false
                        ]
                     if error then CheckError(p, 1, pOffset)
                     goodData = p!0
                     val = goodData
                     ]
                  ]

               // Compare nWords, starting at p, to value val.
               // Return 0 if ok, otherwise pointer to first diff.
               let ans = BlockCheck(lv p)  //only report first diff
               if ans ne 0 then CheckError(ans, val, pOffset+ans-p)
               ]
         ]
      p = p+nWords
      ]
   page = page+1
   ] repeatuntil p eq bufEnd
resultis goodData
]

//----------------------------------------------------------------------------
and MRandom(modulus) = (Random() & 77777B) rem modulus
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
and uBlockCheck(lvP) = (table [ 63400B; 1401B ])(lvP)
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
and BlockCheck(lvP) = valof
//----------------------------------------------------------------------------
[
let address, value, count = lvP!0, lvP!1, lvP!2
for i = 0 to count-1 if address!i ne value resultis address+i
resultis 0
]

//----------------------------------------------------------------------------
and CheckError(adr, goodVal, blockOffset) be
//----------------------------------------------------------------------------
[
PutTemplate(dsp, "[Data error: $UOb s/b $UOb at $UOb]",
 @adr, goodVal, blockOffset)
@adr = goodVal
FinishError()
]

//----------------------------------------------------------------------------
and GetNPages(s, fl; numargs na) = valof
//----------------------------------------------------------------------------
// Puts s1's length in bytes in fl!0 and fl!1.
// Returns s1's length in pages.
[
let tfl = vec 1
if na eq 1 then fl = tfl
FileLength(s, fl)
Resets(s)
let npages = fl!0 lshift 7 + fl!1 rshift 9	//512 bytes per page
if npages ne filePages-1 then
   [
   Ws("[File length error]")
   FinishError()
   ]
resultis npages
]

//----------------------------------------------------------------------------
and FinishError() be
//----------------------------------------------------------------------------
[
if errorStop then [ Ws(" Type any character to proceed "); Gets(keys) ]
errorCount = errorCount +1
]

//----------------------------------------------------------------------------
and OpenOneFile(i) = valof
//----------------------------------------------------------------------------
[
let fn = vec 10; MakeFn(i, fn)
resultis OpenFile(fn, 0, 0, 0, 0, ExSysErr)
]

//----------------------------------------------------------------------------
and CreateOneFile(i) be
//----------------------------------------------------------------------------
[
let s = OpenOneFile(i)
PositionPage(s, filePages)
TransferData(opWrite, s)
Closes(s)
]

//----------------------------------------------------------------------------
and DeleteOneFile(i) be
//----------------------------------------------------------------------------
[
let fn = vec 10; MakeFn(i, fn)
DeleteFile(fn, 0, ExSysErr)
]

//----------------------------------------------------------------------------
and MakeFn(i, fn) be
//----------------------------------------------------------------------------
[
MoveBlock(fn, "test.xxx", 5)
let div = 100
for j = 6 to 8 do
   [
   fn>>String.char↑j = $0+(i/div)
   i = i rem div
   div = div/10
   ]
]

//----------------------------------------------------------------------------
and ExSysErr(nil, code, cb) be
//----------------------------------------------------------------------------
[
test code eq ecUnRecovDiskError
   ifso XferError(nil, cb, nil)
   ifnot PutTemplate(dsp, "[SysErr code $UD]", code)
FinishError()
]