// BFSInit.bcpl
// Copyright Xerox Corporation 1979, 1980
// Last modified November 17, 1980  6:22 PM by Boggs

get "AltoFileSys.d"
get "Disks.d"
get "BFS.d"
get "Streams.d"
get "AltoDefs.d"

external
[
// outgoing procedures
BFSInit; BFSTryDisk

// incoming procedures from other BFS modules
BFSActOnPages; BFSVirtualDA; BFSRealDA; BFSNonEx
BfsInitializeCbStorage; BfsDoDiskCommand; BfsGetCb
BFSWritePages; BFSCreateFile; BFSDeletePages
BFSAssignDiskPage; BFSReleaseDiskPage; BFSCreateDDMgr; BFSClose

// incoming procedures from the OS
ActOnDiskPages; VirtualDiskDA; OpenDD
Zero; MoveBlock; DoubleAdd; Usc
DefaultArgs; Allocate; Free; ReturnFrom
ReadBlock; Closes; Gets; GetCurrentFa; PositionPage
OpenFile; SetWorkingDir
]

//----------------------------------------------------------------------------
let BFSInit(diskZone, allocate, driveNum, ddMgr, freshDisk,
    tempZone; numargs na) = valof
//----------------------------------------------------------------------------
// diskZone is the free storage zone from which permanent disk structures
//  are allocated.
// If allocate is false, operations involving page allocation/deallocation
//  are not permitted.
// DriveNum is the physical unit number on which SysDir starts.
//  It may include a partition number (see Bfs.d).
// If ddMgr is zero, a ddMgr is created.
// If freshDisk is true, DiskDescriptor is NOT read.
// TempZone (which defaults to diskZone) is used for temporary storage
//  needs during BFSInit.
[
DefaultArgs(lv na, -1, 0, 0, 0, false, diskZone)

// First see if the drive is on line
unless BFSTryDisk(driveNum, 0) resultis 0

// Setup structures needed for subsequent accesses
let disk = Allocate(diskZone, lBFSDSK); Zero(disk, lBFSDSK)
disk>>BFSDSK.zone = diskZone
disk>>BFSDSK.initmode = allocate

disk>>DSK.ActOnDiskPages = BFSActOnPages
disk>>DSK.WriteDiskPages = BFSWritePages
disk>>DSK.CreateDiskFile = BFSCreateFile
disk>>DSK.DeleteDiskPages = BFSDeletePages
disk>>DSK.AssignDiskPage = BFSAssignDiskPage
disk>>DSK.ReleaseDiskPage = BFSReleaseDiskPage
disk>>DSK.CloseDisk = BFSClose
disk>>DSK.VirtualDiskDA = BFSVirtualDA
disk>>DSK.RealDiskDA = BFSRealDA
disk>>DSK.InitializeDiskCBZ = BfsInitializeCbStorage
disk>>DSK.DoDiskCommand = BfsDoDiskCommand
disk>>DSK.GetDiskCb = BfsGetCb

// Deny access in certain cases
// WriteDiskPages is NOT included because it has several uses
//  that do not require modifications to the bit table.
unless allocate do
   [
   disk>>DSK.CreateDiskFile = BFSNonEx
   disk>>DSK.DeleteDiskPages = BFSNonEx
   disk>>DSK.AssignDiskPage = BFSNonEx
   disk>>DSK.ReleaseDiskPage = BFSNonEx
   ]

// FP's
disk>>DSK.fpSysDir = lv disk>>BFSDSK.fpBFSSysDirblk
disk>>DSK.fpDiskDescriptor = lv disk>>BFSDSK.fpBFSDDblk
disk>>DSK.fpWorkingDir = lv disk>>BFSDSK.fpBFSWDblk

// Other parameters
disk>>DSK.lnPageSize = BFSlnWordsPerPage
disk>>DSK.driveNumber = driveNum
disk>>DSK.retryCount = 8
disk>>DSK.totalErrors = 0
disk>>DSK.diskKd = lv disk>>BFSDSK.kd
disk>>DSK.lengthCBZ = lBFSCBZ
disk>>DSK.lengthCB = lCB

// BFSInit (cont'd)

// This is the physical shape of the disk.
// If we read DiskDescriptor down below,
//  we may install a different logical shape
disk>>BFSDSK.nDisks = driveNum<<DriveNumber.drive eq 1? 1,
 BFSTryDisk(driveNum+1, 0)? 2, 1
disk>>BFSDSK.nTracks = BFSTryDisk(driveNum, 203)? BFS44NTracks, BFS31NTracks
disk>>BFSDSK.nHeads = BFSmNHeads
disk>>BFSDSK.nSectors = BFSTryDisk(driveNum, 0, 13)? 14, BFSmNSectors

// Setup directory fp and default working dir
let fpBFSSysDir = lv disk>>BFSDSK.fpBFSSysDirblk
MoveBlock(fpBFSSysDir, table [ 100000b; 100; 1; 0; 1 ], lFP)
disk>>DSK.nameWorkingDir = lv disk>>BFSDSK.WDNameblk
SetWorkingDir("<SysDir.", fpBFSSysDir, disk)

unless freshDisk do  // Read the disk descriptor
   [
   // Quick blunder check:
   let DAs = vec 2; DAs!1 = 1
   let buf = Allocate(tempZone, BFSwordsPerPage)
   unless ActOnDiskPages(disk, 0, DAs+1, fpBFSSysDir, 0, 0, DCreadD,
    0, 0, buf, 0, lv BFSInitError, true) eq 0 do
      [ Free(tempZone, buf); Free(diskZone, disk); resultis 0 ]  //no SysDir

   // Get the disk shape from the DSHAPE file property in SysDir's
   // leader page, if present.
   if buf>>LD.propertyBegin ge offset LD.leaderProps/16 &
    buf>>LD.propertyBegin + buf>>LD.propertyLength le
    (offset LD.leaderProps + size LD.leaderProps)/16 then
      [
      let fProp = buf + buf>>LD.propertyBegin
      let maxFProp = fProp + buf>>LD.propertyLength
      until fProp>>FPROP.type eq 0 do
         [
         let length = fProp>>FPROP.length
         if length eq 0 % fProp+length gr maxFProp break
         if fProp>>FPROP.type eq fpropTypeDShape then
            [
            if length ne lDSHAPE+1 break
            let dShape = fProp+1
            disk>>BFSDSK.nDisks = dShape>>DSHAPE.nDisks
            disk>>BFSDSK.nTracks = dShape>>DSHAPE.nTracks
            disk>>BFSDSK.nHeads = dShape>>DSHAPE.nHeads
            disk>>BFSDSK.nSectors = dShape>>DSHAPE.nSectors
            ]
         fProp = fProp + length
         ]
      ]

   // If there are two disks, then try reading a sector on DP1.
   // If we get a header check error, then its probably DP0 of some
   //  other file system.  This is by no means a fool-proof check.
   if disk>>BFSDSK.nDisks eq 2 then
      [
      let realDA = 0; realDA<<DA.disk = 1
      let DAs = vec 2; DAs!1 = VirtualDiskDA(disk, lv realDA)
      unless ActOnDiskPages(disk, 0, DAs+1, 0, 0, 0, DCreadLD,
       0, 0, buf, 0, lv BFSInitError, true) eq 0 do
         [ Free(tempZone, buf); Free(diskZone, disk); resultis 0 ]
      ]
   Free(tempZone, buf)

// BFSInit (cont'd)

   let dds = OpenFile("DiskDescriptor", ksTypeReadOnly, wordItem, 0,
      disk>>DSK.fpDiskDescriptor, 0, tempZone, 0, disk)
   if dds eq 0 then [ Free(diskZone, disk); resultis 0 ]  //no DiskDescriptor
   ReadBlock(dds, disk>>DSK.diskKd, lKDHeader)  //KDH

   // Bump the serial number to try to avoid duplicates
   let snAdr = lv disk>>BFSDSK.lastSn
   DoubleAdd(snAdr, table [ 0; 5 ])
   snAdr>>SN.directory = 0  // make sure the terrifying bits are off
   snAdr>>SN.random = 0
   snAdr>>SN.nolog = 0

   // Don't be too gullible:
   if Usc(disk>>BFSDSK.defaultVersionsKept, 7) gr 0 then
      disk>>BFSDSK.defaultVersionsKept = 7

   // count free pages in the file system
   let freePages = 0
   let diskBTsize = disk>>BFSDSK.diskBTsize
   for i = 1 to diskBTsize do
      [
      let w = Gets(dds)
      test w eq 0
         ifso freePages = freePages +16
         ifnot if w ne -1 then
            for j = 0 to 15 do
               if ((w rshift j) & 1) eq 0 then
                  freePages = freePages +1
      ]
   disk>>BFSDSK.freePages = freePages

   // Save virtual disk addresses of disk descriptor pages
   for i = 1 to (lKDHeader+diskBTsize-1) rshift BFSlnWordsPerPage +1 do
      [
      PositionPage(dds, i)
      let fa = vec lFA
      GetCurrentFa(dds, fa)
      disk>>BFSDSK.VDAdiskDD↑i = fa>>FA.da
      ]

   Closes(dds)
   ]

// set up DD manager, if needed
if allocate then
   [
   if ddMgr eq 0 then
      ddMgr = BFSCreateDDMgr(diskZone)
   disk>>BFSDSK.ddMgr = ddMgr
   OpenDD(ddMgr, disk)
   ]

// Return handle on disk object
resultis disk
]

// This procedure is passed to ActOnDiskPages by BFSInit when
//  attempting to read the leader page of sysDir to see if it is there.
// It may quite legitimately not be, and we don't want to go into Swat.
//----------------------------------------------------------------------------
and BFSInitError(nil, nil, nil) be ReturnFrom(ActOnDiskPages, -1)
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
and BFSTryDisk(drive, cylinder, sector; numargs na) = valof
//----------------------------------------------------------------------------
// If trackNumber is 0, returns true if disk is on line
// If trackNumber is 203, returns true if disk is model 44
[
// for Dorados and D0s, verify the partition number
let partition = drive<<DriveNumber.partition
if partition ne 0 then
   [
   let currentPartition = SetPartition(0)  // remember where we are...
   unless SetPartition(partition) resultis false  // no such partition
   SetPartition(currentPartition)  // get back to where we were
   ]

// try seeking to a cylindar address in that partition
let kcb = vec lKCB; Zero(kcb, lKCB)
kcb>>KCB.command = seekOnly
kcb>>KCB.command.partition = partition
kcb>>KCB.diskAddress.disk = drive  // <<DriveNumber.drive
kcb>>KCB.diskAddress.track = cylinder
if na ge 3 then kcb>>KCB.diskAddress.sector = sector
until @diskCommand eq 0 loop
@diskCommand = kcb
until kcb>>KCB.status.done loop
resultis (kcb>>KCB.status & DSTgoodStatusMask) eq DSTgoodStatus
]

//----------------------------------------------------------------------------
and SetPartition(partition) = (table [ 61037b; 1401b ])(partition)
//----------------------------------------------------------------------------