// BFSNewDisk.Bcpl -- Procedures to create a virgin Alto File System
//  on a Diablo disk, and extend an existing file system.
// Copyright Xerox Corporation 1979, 1980, 1982
// Last modified April 27, 1982  11:00 PM by Boggs

get "Streams.d"
get "AltoFileSys.d"
get "Disks.d"
get "Bfs.d"
get "SysDefs.d"

external
[
// outgoing procedures
BFSNewDisk; BFSExtendDisk

// incoming procedures
OpenFile
PositionPage; GetCurrentFa; TruncateDiskStream
CreateDiskStream; WriteBlock; SetFilePos; FilePos
ReadLeaderPage; WriteLeaderPage
Closes; Resets; Puts; Gets; Allocate; Free
Zero; MoveBlock; SysErr; DefaultArgs; Usc; Max
CreateDiskFile; AssignDiskPage; ReleaseDiskPage; ActOnDiskPages
InitializeDiskCBZ; GetDiskCb; DoDiskCommand; CloseDisk
BFSInit; BFSAssignDiskPage; BFSWriteDiskDescriptor
ReadDDPage

// incoming statics
freePageFp; oneBits
]

manifest
[
defaultDirLen = 5000  //default size of SysDir in words
bDir = 100000b rshift offset SN.directory
]

//----------------------------------------------------------------------------
let BFSNewDisk(diskZone, driveNum, nDisks, nTracks, dirLen, nSectors;
    numargs na) = valof
//----------------------------------------------------------------------------
// Creates a virgin Alto file system on the specified disk.
// nDisks is 2 to make a file system that occupies two packs.
// nTracks may be anything up to the physical size of the disk.
// nSectors may be 14 on D0s and Dorados.
[
DefaultArgs(lv na, -1, 0, 0, 0, defaultDirLen, 0)

// Init the disk object
let disk = BFSInit(diskZone, true, driveNum, 0, true)
if disk eq 0 resultis false
// BFSInit fills in DSK with the physical shape of the disk.
// The user may want some other logical shape, such as two packs.
if nDisks ne 0 then disk>>BFSDSK.nDisks = nDisks
if nTracks ne 0 then disk>>BFSDSK.nTracks = nTracks
if nSectors ne 0 then disk>>BFSDSK.nSectors = nSectors

// # of pages in this file system
let diskSize = disk>>BFSDSK.nDisks * disk>>BFSDSK.nTracks *
 disk>>BFSDSK.nHeads * disk>>BFSDSK.nSectors

// Allocate a page sized temporary bit table and
//  plug in our own AssignDiskPage procedure for initial allocations.
// This procedure will put assigned addresses in tempBT.
// We must not allocate more than 255 pages here, or we
//  will have to figure out a new way around this Catch-22.
let buf = Allocate(diskZone, BFSwordsPerPage)
Zero(buf, BFSwordsPerPage)
disk>>BFSDSK.initmode = buf
disk>>DSK.AssignDiskPage = NewDiskAssignPage

// It's too late to back out now:
ClearDisk(disk, 0, diskSize-1)

let fpSysDir = disk>>DSK.fpSysDir
let fpDiskDescriptor = disk>>DSK.fpDiskDescriptor

// Create SysDir.  Its SN must be 100 with the directory bit set.
// Its leader page must be at virtual address 1.
(lv disk>>BFSDSK.lastSn)!1 = 99  //init last SN used
disk>>BFSDSK.lastPageAlloc = 0	//skip page 0
let dir = NewDiskCreateFile(disk, "SysDir.", fpSysDir, diskZone, 0, bDir)
let pos = FilePos(dir)
SetFilePos(dir, 0, dirLen lshift 1)  //make nice contiguous dir
SetFilePos(dir, 0, pos)

// Create DiskDescriptor and make sure it is consecutively allocated.
let ddStream = NewDiskCreateFile(disk, "DiskDescriptor.", fpDiskDescriptor,
 diskZone, dir, 0)
let diskBTsize = (diskSize-1) rshift 4 +1  //total words in BT
disk>>BFSDSK.diskBTsize = diskBTsize

// Extend disk descriptor and fill it with zeros.
// DD file is made big enough to handle a Double 44 filesystem without
//  having to extend it.  KDH.diskBTsize tells how many words are
//  really part of the bit table; the remaining ones should be ignored.
PositionPage(ddStream, 5)
let q, r = diskSize rshift 4, diskSize & 17B
SetFilePos(ddStream, 0, (lKDHeader+q) lshift 1)
// Last word of BT needs special treatment since number of pages
//  on disk may not be a mulitple of 16.
if r ne 0 then Puts(ddStream, -1 rshift r)

// DDMgr needs VDAs of disk descriptor pages to function.
// This is normally done by BFSInit, but DiskDescriptor didn't exist then.
for i = 1 to (lKDHeader+diskBTsize-1) rshift BFSlnWordsPerPage +1 do
   [
   PositionPage(ddStream, i)
   let fa = vec lFA
   GetCurrentFa(ddStream, fa)
   disk>>BFSDSK.VDAdiskDD↑i = fa>>FA.da
   ]
Closes(ddStream)

// BFSNewDisk (cont'd)

// Fill out rest of directory with empties of small enough size.
   [
   let len = dirLen - FilePos(dir) rshift 1
   if len le 0 then break
   if len gr 100 then len = 100
   let a = vec 1
   a>>DV.type = dvTypeFree
   a>>DV.length = len
   WriteBlock(dir, a, len)
   ] repeat
TruncateDiskStream(dir)

// Now put back the normal AssignDiskPage procedure and mark the pages
//  we have allocated in the bit table in the normal way.
disk>>DSK.AssignDiskPage = BFSAssignDiskPage
for i = 1 to buf!0 do
   if AssignDiskPage(disk, buf!i-1) ne buf!i then
      SysErr(disk, ecBadAssignPage)
// Assign page zero.  This is slightly tricky since zero = eofDA+1
disk>>BFSDSK.lastPageAlloc = 0
if AssignDiskPage(disk, eofDA) ne 0 then SysErr(disk, ecBadAssignPage)

// Install the disk shape parameters as a file property in the leader
//  page of SysDir.  This is mostly for Mesa's benefit.
ReadLeaderPage(dir, buf)
let fProp = buf + buf>>LD.propertyBegin
fProp>>FPROP.type = fpropTypeDShape
fProp>>FPROP.length = lDSHAPE+1
let dShape = fProp+1
dShape>>DSHAPE.nDisks = disk>>BFSDSK.nDisks
dShape>>DSHAPE.nTracks = disk>>BFSDSK.nTracks
dShape>>DSHAPE.nHeads = disk>>BFSDSK.nHeads
dShape>>DSHAPE.nSectors = disk>>BFSDSK.nSectors
WriteLeaderPage(dir, buf)

Closes(dir)
Free(diskZone, buf)

// Flush DiskDescriptor out to the disk
CloseDisk(disk)

resultis true
]

//----------------------------------------------------------------------------
and BFSExtendDisk(zone, disk, nDisks, nTracks) be
//----------------------------------------------------------------------------
// 'disk' is the file system to extend.  Presumably either 'nTracks' or
//  'nDisks' or both is bigger than the corresponding current parameter.
[
let pgsPerCyl = disk>>BFSDSK.nSectors * disk>>BFSDSK.nHeads
let firstVDA = pgsPerCyl * disk>>BFSDSK.nTracks * disk>>BFSDSK.nDisks
let lastVDA = pgsPerCyl * nTracks * nDisks -1
if Usc(firstVDA, lastVDA) ge 0 return  // nothing to do

let buf = Allocate(zone, BFSwordsPerPage); Zero(buf, BFSwordsPerPage)
disk>>BFSDSK.initmode = buf
disk>>DSK.AssignDiskPage = NewDiskAssignPage
disk>>BFSDSK.nTracks = nTracks
disk>>BFSDSK.nDisks = nDisks

ClearDisk(disk, firstVDA, lastVDA)
disk>>DSK.AssignDiskPage = ExtendDiskAssignPage

let ddStream = OpenFile("DiskDescriptor.", ksTypeReadWrite, wordItem,
 0, disk>>BFSDSK.fpDiskDescriptor, 0, zone, 0, disk)
let q, r = firstVDA rshift 4, firstVDA & 17B
SetFilePos(ddStream, 0, (lKDHeader+q) lshift 1)
if r ne 0 then
   [
   let bits = Gets(ddStream)
   SetFilePos(ddStream, 0, (lKDHeader+q) lshift 1)
   Puts(ddStream, bits & not -1 rshift r)
   ]
q, r = (lastVDA+1) rshift 4, (lastVDA+1) & 17B
for i = FilePos(ddStream) rshift 1 to lKDHeader+q do
   Puts(ddStream, 0)  //fill with zeros
if r ne 0 then Puts(ddStream, -1 rshift r)
let diskBTsize = lastVDA rshift 4 +1  //total words in BT
for i = 1 to (lKDHeader+diskBTsize-1) rshift BFSlnWordsPerPage +1 do
   [
   PositionPage(ddStream, i)
   let fa = vec lFA; GetCurrentFa(ddStream, fa)
   disk>>BFSDSK.VDAdiskDD↑i = fa>>FA.da
   ]
Closes(ddStream)
disk>>BFSDSK.diskBTsize = diskBTsize
disk>>DSK.AssignDiskPage = BFSAssignDiskPage
for i = 1 to buf!0 do
   [
   let vda = AssignDiskPage(disk, buf!i-1)
   if vda ne buf!i then ReleaseDiskPage(disk, vda)
   ]
disk>>BFSDSK.freePages = lastVDA - firstVDA + disk>>BFSDSK.freePages +1

let DAs = vec 2; DAs!1 = 1
ActOnDiskPages(disk, 0, DAs+1, disk>>DSK.fpSysDir, 0, 0, DCreadD, 0, 0, buf)
let fProp = buf + buf>>LD.propertyBegin
fProp>>FPROP.type = fpropTypeDShape
fProp>>FPROP.length = lDSHAPE+1
let dShape = fProp+1
dShape>>DSHAPE.nDisks = disk>>BFSDSK.nDisks
dShape>>DSHAPE.nTracks = disk>>BFSDSK.nTracks
dShape>>DSHAPE.nHeads = disk>>BFSDSK.nHeads
dShape>>DSHAPE.nSectors = disk>>BFSDSK.nSectors
ActOnDiskPages(disk, 0, DAs+1, disk>>DSK.fpSysDir, 0, 0, DCwriteD, 0, 0, buf)
Free(zone, buf)

BFSWriteDiskDescriptor(disk)
]

//----------------------------------------------------------------------------
and NewDiskCreateFile(disk, name, fp, zone, dirStream, word1) = valof
//----------------------------------------------------------------------------
//Creates the file on the disk and appends a directory entry to dirStream.
//If dirStream is zero, the new file itself is assumed to be the directory
// and the entry is appended to it.  Returns an open stream.
[
CreateDiskFile(disk, name, fp, 0, word1)

// Make stream
let s = CreateDiskStream(fp, 0, 0, 0, 0, zone, 0, disk)
if s eq 0 then SysErr(disk, ecEssentialFile)

// Now make directory entry
if dirStream eq 0 then dirStream = s	//SysDir is first
let dv = vec lDV
dv>>DV.type = dvTypeFile
let lName = (name>>STRING.length+2) rshift 1
dv>>DV.length = lDV+lName
MoveBlock(lv dv>>DV.fp, fp, lFP)
WriteBlock(dirStream, dv, lDV)
WriteBlock(dirStream, name, lName)

// return stream
resultis s
]

//----------------------------------------------------------------------------
and NewDiskAssignPage(disk, vda, nil) = valof
//----------------------------------------------------------------------------
// The AssignDiskPage procedure used while creating the initial files
//  (SysDir and DiskDescriptor).
// Keeps a list of assigned pages in the buffer pointed to by the
//  initmode word in the disk structure.
// Number of allocated pages is in buf!0, followed by that many VDAs.
[
vda = vda eq eofDA? disk>>BFSDSK.lastPageAlloc+1, vda+1
disk>>BFSDSK.lastPageAlloc = vda
let tempBT = disk>>BFSDSK.initmode

// search for first unused slot, and make sure page not already assigned
let assigned = false
for i = 1 to tempBT!0 if vda eq tempBT!i then [ assigned = true; break ]
unless assigned do  //assign the page
   [
   if tempBT!0 eq 255 then SysErr(disk, ecBadAssignPage)  //full!
   tempBT!0 = tempBT!0 +1  //bump count of pages allocated
   tempBT!(tempBT!0) = vda  //remember vda of allocated page
   resultis vda
   ]
] repeat

//----------------------------------------------------------------------------
and ExtendDiskAssignPage(disk, vda, nil) = valof
//----------------------------------------------------------------------------
// During extension, no new files are created, so vda can't be eofDA.
[
if vda ls disk>>BFSDSK.diskBTsize lshift 4 then
   [
   vda = BFSAssignDiskPage(disk, vda)
   ReadDDPage(disk>>BFSDSK.ddMgr, disk, 1)  //flush it out
   vda = vda eq -1? disk>>BFSDSK.diskBTsize lshift 4, vda-1
   ]
resultis NewDiskAssignPage(disk, vda)
]

//----------------------------------------------------------------------------
and ClearDisk(disk, firstVDA, lastVDA) be
//----------------------------------------------------------------------------
// The idea here is to initialize every page in the file system.
// The simple way is to write 'free' in every page, but this will make
//  incorrigable pages free again, causing future problems.
// It is the job of a disk certification program such as DiEx to test
//  each page and mark it incorrigable, and we shouldn't disturb them.
// If the disk has never been formatted before, we should assume that
//  all pages are good and set them all to free.
[
let tempBT = disk>>BFSDSK.initmode  //temp bit table
let cbz = vec CBzoneLength

// Read each page to see if it is incorrigable or unreadable.
// If there are more than 20 of them then assume the disk is
//  unformatted and abandon the read pass.
// Pages 0 and 1 are not checked and are unconditionally marked free.
// They are critical, and must be good or else chuck the disk!
let buf1 = vec 256  //I wish I could disable data record transfers
InitializeDiskCBZ(disk, cbz, Max(2, firstVDA), CBzoneLength,
 RetryRead, lv ClearDiskError)
cbz>>CBZ.cleanupRoutine = ClearDiskCleanup
RetryRead: test tempBT!0 le 20
   ifso
      [
      for page = cbz>>CBZ.currentPage to lastVDA do
         DoDiskCommand(disk, GetDiskCb(disk, cbz), buf1, page,
          freePageFp, page, DCreadLD)
      while cbz>>CBZ.head ne 0 do GetDiskCb(disk, cbz)
      ]
   ifnot Zero(tempBT, 256)  //abandon read pass, assume all pages are good
Zero(buf1, 256)  //write zeros in data records

// Write each page, marking it free or incorrigable.
let incorrigableFp = table [ -2; -2; -2 ]
InitializeDiskCBZ(disk, cbz, firstVDA, CBzoneLength, RetryWrite)
RetryWrite:
   [
   for page = cbz>>CBZ.currentPage to lastVDA do
      [
      let bad = false
      for i = 1 to tempBT!0 if page eq tempBT!i then [ bad = true; break ]
      DoDiskCommand(disk, GetDiskCb(disk, cbz), buf1, page,
       (bad? incorrigableFp, freePageFp), page, DCwriteHLD)
      ]
   while cbz>>CBZ.head ne 0 do GetDiskCb(disk, cbz)
   ]
]

//----------------------------------------------------------------------------
and ClearDiskCleanup(nil, cb, cbz) be
//----------------------------------------------------------------------------
// Cleanup routine called during the read pass.
// If the label says incorrigable, mark the page used in the bit table.
[
let label = lv cb>>CB.labelAddress>>DL.fileId
for i = 0 to 2 do if label!i ne -2 return
// The page is bad and should be marked used in the bit table.
ClearDiskError(nil, cb)
]

//----------------------------------------------------------------------------
and ClearDiskError(nil, cb, nil) be
//----------------------------------------------------------------------------
// Error routine called for a hard error during the read pass.
// Also called from ClearDiskCleanup for a sector labelled incorrigable.
[
let vda = cb>>CB.truePageNumber
let disk = cb>>CB.cbz>>CBZ.disk
if AssignDiskPage(disk, vda-1) ne vda then SysErr(disk, ecBadAssignPage)
// Zero the label so ClearDiskCleanup doesn't think it incorrigable
//  and try to mark it in use again.
Zero(lv cb>>CB.labelAddress>>DL.fileId, lFID)
]