// Install.Bcpl -- OS install sequence 
// Copyright Xerox Corporation 1979, 1980, 1981
// Last modified June 9, 1982  1:25 AM by Boggs

get "AltoFileSys.d"
get "Disks.d"
get "Bfs.d"
get "Streams.d"
get "SysDefs.d"
get "AltoDefs.d"
get "SysInternals.d"
get "BcplFiles.d"

external
[
// outgoing procedures
Install; VerifyInstalled

// incoming procedures
Password; EraseDisk; ExtendDisk
YesNo; GetString; ReadNumber; GetNthLevel
JuntaReturn; CreateSysDisk; CloseDisk

OpenFile; ReadBlock
GetCompleteFa; PositionPage
FileLength; FilePos
Gets; Puts; Resets; Closes
Ws; Wns; BFSWriteDiskDescriptor

CharWidth; EraseBits

RealDiskDA
Zero; MoveBlock; BitBlt
Usc; DoubleAdd; StartIO; MyFrame
DisableInterrupts; EnableInterrupts
Timer; ReadCalendar

CallSwat; OutLd; OsFinish

// incoming statics
OsFinishSafeAdr; OsFinishCode
ErrorLogAddress; EventVector
UserName; UserPassword; OsBuffer
lvParitySweepCount; lvParityPhantomEnable
juntaTable; sysDisk; lvAbortFlag
dsp; keys; sysMECR; dirVersions
]

manifest
[
logWordsPerPage = 8
wordsPerPage = 1 lshift logWordsPerPage
]

structure PORT:		// Must match Pup package definition
[
net byte; host byte
soc↑1,2 word
]

//Layout of memory when OutLd'ed on the disk (e.g., the way it will
// look when InLd'ed again - these are not file addresses):
//	page 0 -- has pointers to Bcpl runtime routines
//	page 1 -- I/O stuff + mask tables initialized
//	1000b -- User Name and Disk name (see AskUser in this file)
//		(Note: this position is "published" to the Executive,
//		which reads the boot file to find the disk name.)
//	1200b -- Password information (see AskUser, TellUser in this file)
//	1375b-1376b -- local time conversion parameters
//	1377b -- the length of HiddenFTP.Run file saved in core
//	1400b to xxxx -- HiddenFTP.Run file saved during install
//		so it can initialize a virgin disk.
//	StartOS -- a piece of machine language code just for starting
//		up the first (once-only) time -- thereafter not needed.
//	JuntaReturn -- the lowest piece of the "OS".
//	JuntaReturn to initStackLimit -- the "initialization" code
//		for the OS, including bootinit, install, etc.
//	initStackLimit to stackRoot -- the initial system stack.
//	stackRoot to 176777b -- the REAL OS: the part that is around
//		when the Executive gets control.

//----------------------------------------------------------------------------
let Install(onceFlag) be
//----------------------------------------------------------------------------
[
test onceFlag
   ifso	//Set some default names
      [
      MoveBlock(UserName, "NoName", 4)
      MoveBlock(userDiskName, "NoName", 4)
      MoveBlock(1004b, "New Os Disk", 6)
      diskPassword!0 = 0  //no password
      Zero(1375b, 2)  //no time conversion params in an uninstalled OS
      ]
   ifnot
      [
      MoveBlock(1375b, timeParams, 2)  //Sys.boot ← current time params
      AskUser()  //Find out his ambitions
      ]

// Almost anything may have happened to the disk by here.
// It may have been erased, a new disk may have been slipped in...
CreateSysDisk()

if onceFlag then
   [
   // There is this special, small version of Ftp that the OS keeps
   // inside of itself for use by EraseDisk, called FtpOs.run.
   let s = OpenFile("FtpOs.Run.", ksTypeReadOnly)
   let gotit = false
   if s then
      [
      // Read FtpOs from the disk into spare memory area.
      FileLength(s)  //move quickly to the end
      let p = vec 1; FilePos(s, p)  //and remember where it is
      let FTPLen = (p!0 lshift 15) + (p!1 rshift 1)
      // make sure it will fit:
      if Usc(JuntaReturn-ftpOs, FTPLen) gr 0 then
         [
         Resets(s)
         ReadBlock(s, ftpOs, FTPLen)
         ftpOs!-1 = FTPLen
         // Following patch is to fool CallSubsys when it is called on this
         // file.  It is important that FTP's CFA be returned properly.
         // Note that the patch is "undone" in EraseDisk when writing the
         // file out during a full disk init operation.
         ftpOs>>SV.H.length = ftpOs>>SV.H.length+2
         gotit = true
         ]
      Closes(s)
      ]
   unless gotit do CallSwat("Failed to install FtpOs.Run")
   // this is of course, nonfatal, you can proceed;
   // you just can't erase a disk without it.
   ]

// Install (cont'd)

// Build junta entries
OsFinishSafeAdr = JuntaReturn-wordsPerPage  //1 page used for buffer
juntaTable>>JT.jReturn = JuntaReturn
if (JuntaReturn & 377B) ne 0 then CallSwat("OS loaded wrong")
let i = 0
while GetNthLevel(i, (lv juntaTable>>JT.jTable)+2*i) do i = i +1
juntaTable>>JT.jLevels = i

// Create the S0-boot file and then OutLd on it.
// An OutLd file is 257 pages long:
// File page:   0       1       2      3  ...  253      254     255     256
// Contents:  leader  loader  page2  page3...page 253  page 1  page 0  empty
let s = OpenFile("Sys.Boot", 0, 0, verLatestCreate)
let bootFp = vec lCFA; GetCompleteFa(s, bootFp)
RealDiskDA(sysDisk, bootFp>>CFA.fa.da, lv bootFp>>CFA.fp.leaderVirtualDa)
PositionPage(s, JuntaReturn rshift logWordsPerPage)  //Write the first part
let jrCFA = vec lCFA; GetCompleteFa(s, jrCFA)  //Find out where we are
PositionPage(s, 255+1)	//Write the last part
Closes(s)	//And done

// In case it was changed by any file creations so far...
BFSWriteDiskDescriptor(sysDisk)

// Now make the boot label for CounterJunta.
// Don't do it if this is the very first install -- that way
//  this file will never appear "installed" on any disk.
let p = lv juntaTable>>JT.BootLabel
Zero(p, lDL)
unless onceFlag do
   [
   MoveBlock(lv p>>DL.fileId.serialNumber, lv jrCFA>>CFA.fp.serialNumber, lSN)
   p>>DL.fileId.version = jrCFA>>CFA.fp.version
   // Page containing JuntaReturn
   RealDiskDA(sysDisk, jrCFA>>CFA.fa.da, lv p>>DL.next)
   // So Exec knows where booted from
   p>>DL.previous = jrCFA>>CFA.fp.leaderVirtualDa
   ]

onceFlag = false  //If loop, be sure to make Junta BootLabel

// Try to keep user from saving password on disk in any way
Zero(UserPassword, UserPassword!-1)
Zero(OsBuffer+4, OsBuffer!-1)
Resets(keys)

// Install (cont'd)

// Save page 1 in a vector in our stack.
// Cold starting does not restore it, so we must.
let page1 = vec 256; MoveBlock(page1, 400b, 256)  //used in two places

// If we are started by InLd or BootFrom, we will "return" from OutLd with:
//	flag = 0 if we OutLded ourself onto the disk
//	flag = 1 if we InLded from the disk (normal case)
//	flag = 2 if we booted from the disk (BootFrom)
// If we are started cold, we will "materialize" at ColdStart with:
//	flag = 3 if we cold started (EtherBoot)
let flag = 0
let mess = vec 25  //This will contain a message when OutLd returns

// Now set up a way to return
@0 = 3			// jmp @0 to cold start
@1 = MyFrame()		// stack frame for cold starting
@2 = ColdStart		// -> cold start OS initialization
@3 = 030001b		// lda 2 1
@4 = 002002b		// jmp @2

ColdStart:
test flag eq 3
   ifso for p = 0 to 6 by 2 do  //Restore page 1 selectively
      [  //save and restore locations 400b-427b, etc
      let ta = table [ 400b; 427b; 431b; 520b; 524b; 567b; 600b; 777b ]
      for i = ta!p to ta!(p+1) do @i = page1!(i-400b)
      ]
   ifnot
      [
      // OutLd must be done with interrupts off.  Otherwise, it might
      //  write out some state (e.g., active) which only pertains inside
      //  an interrupt.
      DisableInterrupts()
      flag = 3  //this is the value saved in the file
      // Now actually save the state of the machine on the bloody boot file.
      flag = OutLd(bootFp, mess)
      ]

// Try to keep user from saving his password on disk in any way
Zero(UserPassword, UserPassword!-1); Resets(keys)

// Install (cont'd)

// Initialize the other memory banks if they are present
if (table [ 61014B; 1401B ])()<<VERS.eng eq 3 then
   [
   @MECR = -1  //correction on, don't report any errors
   for bank = 1 to 3 do
      [
      bankRegs!0 = bank
      let bbt = vec 16; bbt = (bbt+1) & -2; Zero(bbt, 16)
      bbt>>BBT.dBank = 1
      bbt>>BBT.sBank = 1
      //127 "scan lines" of 512 words each.
      //Skip I/O area in high memory.
      bbt>>BBT.dbmr = 512
      bbt>>BBT.sbmr = 512
      bbt>>BBT.dw = 512 * 16
      bbt>>BBT.dh = 127
      BitBlt(bbt)
      ]
   ]

@MESR = 0
@MECR = sysMECR

// Flush any interrupts that happened during boot
let temp = @activeInterrupts
@activeInterrupts = 0
EnableInterrupts()
@wakeupsWaiting = 0
@activeInterrupts = temp

@diskAddress = -1  //invalidate the disk arm position.

// Install (cont'd)

if flag eq 0 then  //Just installed
   [
   // Move page 1 of the boot file to real disk address 0.
   // In spite of the name, bootFp contains a real DA for page 1.
   let label = vec lDL  //reuse the page1 vector above
   DoDiskCmd(readLD, bootFp>>CFA.fp.leaderVirtualDa, page1, label)
   ReadCalendar(page1+3)  //install creation date in the boot loader
   DoDiskCmd(writeHLD, 0, page1, label)
   DoDiskCmd(writeD, bootFp>>CFA.fp.leaderVirtualDa, page1, label)
   flag = 2  //behave as if we were just booted
   ]

if flag ne 1 then  //booted from the disk or the net (rather than InLded)
   [
   // Create the default boot message.
   let defaultbootmess = vec 25; Zero(defaultbootmess, 25)
   defaultbootmess>>EVM.type = eventBooted
   defaultbootmess>>EVM.length = 1
   MoveBlock(mess, defaultbootmess, 25)

   //if the local time parameters in core look unreasonable then
   //   if the local time parameters in the boot image are ok then
   //      install them otherwise
   //      zero the time to force the Exec to do a SetTime
   let LTPOK(ltp) = ltp>>LTP.beginDST ge 1 & ltp>>LTP.beginDST le 366 &
      ltp>>LTP.endDST ge 1 & ltp>>LTP.endDST le 366 &
      ltp>>LTP.beginDST le ltp>>LTP.endDST & ltp>>LTP.zoneH le 13

   unless LTPOK(timeParams) test LTPOK(1375b)
      ifso MoveBlock(timeParams, 1375b, 2)
      ifnot Zero(timeParams, 10b)
   ]

if flag eq 3 then  //started cold by Jmp @0
   [
   Ws("*n*n*n*n*n   Do you want to install this Operating System? ")
   // If we come here, need to try do read password sector from
   //  disk that is spinning in the machine:
   if YesNo() loop
   let la = vec lDL; DoDiskCmd(readLD, 0, 1000b, la, 2)
   ]

// Install (cont'd)

// Copy events from mess (returned by InLd) into EventVector,
//  where OsMain will process them.  Some are processed here.
OsFinishCode = fcOK
let p = 0
let act = mess
let install = false
for i = 1 to EventVector!-1 do  // loop is guaranteed to terminate
   [
   if @act eq 0 break
   let l = act>>EVM.length
   switchon act>>EVM.type into
      [
      case eventBooted:
         [ TellUser(); endcase ]
      case eventExecuteCode:
         [
         (act+1)()	//call message as a Bcpl procedure
         act = act+l
         loop
         ]
      case eventInstall: 
         [
         install = true
         act = act+l	//Bypass this event
         loop
         ]
      ]
   if p+l+1 gr EventVector!-1 break
   MoveBlock(EventVector+p, act, l)
   p = p+l; act = act+l
   ]
EventVector!p = 0
unless install break	//Go run the OS!
] repeat

//----------------------------------------------------------------------------
and VerifyInstalled() be
//----------------------------------------------------------------------------
// Check to see if system is installed. If not, zero junta label.
[
let p = lv juntaTable>>JT.BootLabel
// If the label is zero, then this OS has not ever been installed
// and there is no point in making needless references to the disk.
if p>>DL.next ne 0 then
   [
   // Read the page that CounterJunta would read first.
   // Manually check that its label is correct for this disk.
   let data, label = vec 256, vec lDL
   DoDiskCmd(readLD, p>>DL.next, data, label)
   let laid = lv label>>DL.fileId
   let jtid = lv p>>DL.fileId
   let match = true
   for i = 0 to lFID-1 if laid!i ne jtid!i then match = false
   if match return
   ]
Zero(p, lDL)
]

//----------------------------------------------------------------------------
and DoDiskCmd(cmd, realDA, data, label, numPages; numargs na) be
//----------------------------------------------------------------------------
// We may not have a DSK object for DP0 yet
[
if na ls 5 then numPages = 1
for page = 1 to numPages do
   [
   for tries = 1 to 10 do
      [
      let kcb = vec lKCB; Zero(kcb, lKCB)
      kcb>>KCB.command = cmd
      kcb>>KCB.headerAddress = lv kcb>>KCB.header
      kcb>>KCB.labelAddress = label
      kcb>>KCB.dataAddress = data
      kcb>>KCB.diskAddress = realDA
      @diskCommand = kcb	//Spin the disk
      while (kcb>>KCB.status & DSTdoneBits) eq 0 loop
      if (kcb>>KCB.status & DSTgoodStatusMask) eq DSTgoodStatus break
      if tries eq 10 then
         [
         Ws("*N   The disk doesn't work.  ")
         Ws("Type any character and I will try again.")
         Gets(keys)
         tries = 1
         ]
      ]
   realDA = label>>DL.next      //Prepare to go ahead
   ]
]

//----------------------------------------------------------------------------
and AskUser() be
//----------------------------------------------------------------------------
// Ask the user for all his installation options and
//  squirrel them away appropriately.
[
// Be sure to dump the input buffer, in case by mistake the
//  system was installed previously with typeahead pending.
Resets(keys)
Ws("*N*N*N*N*N   Do you want the long installation dialog?")
let longDialogue = YesNo()

if longDialogue then
   [
   if (table [ 61014b; 1401b ])()<<VERS.eng gr 3 then
      [
      // See if he wants to change partitions.
      Ws("   The current disk partition is ")
      Wns(dsp, (table [ 61037b; 1401b ])(0))
      Ws(".*N   Do you want to install onto another partition?")
      unless YesNo() break
      Ws("   Type the new disk partition number: ")
      (table [ 61037b; 1401b ])(ReadNumber())
      ] repeat

   // See if he wants disk wiped first!!!
   Ws("   Do you want to ERASE a disk before installing?")
   test YesNo()
      ifso EraseDisk()
      ifnot
         [
         Ws("   Do you want to extend this file system?")
         if YesNo() then ExtendDisk()
         ]
   ]

// Fiddle with file versions if this OS has the versioned directory module.
// As of OS17, file versions are only supported in a little known version
//  of the OS called 'NewOsV.boot".
CreateSysDisk()
let dvKept = 0
if dirVersions then
   [
   dvKept = sysDisk>>BFSDSK.defaultVersionsKept
   if longDialogue then
      [
      Ws("   The disk is configured ")
      test dvKept eq 0
         ifso Ws("with the multiple-version feature disabled.")
         ifnot [ Ws("to keep "); Wns(dsp, dvKept); Ws(" versions of files.") ]
      Ws("*N   Do you want to change this setting? ")
      if YesNo() then
         [
         Ws("   How many versions of files do you normally wish to keep? ")
         dvKept = (ReadNumber()) & 7b
         if dvKept eq 1 then dvKept = 0	   //User is confused!!
         ]
      ]
   ]
if dvKept ne sysDisk>>BFSDSK.defaultVersionsKept then
   sysDisk>>BFSDSK.defaultVersionsKept = dvKept
CloseDisk(sysDisk)

if longDialogue then
   [
   // See if he wants to change the error reporting address:
   Ws("   Do you want to disable error logging through the net?")
   test YesNo()
      ifnot
         [
         Ws("   Do you want to change the error logging address (currently [")
         Wns(dsp, ErrorLogAddress>>PORT.net, 1, 8); Ws("#")
         Wns(dsp, ErrorLogAddress>>PORT.host, 1, 8); Ws("#")
         Wns(dsp, ErrorLogAddress>>PORT.soc↑2, 1, 8); Ws("])?")
         unless YesNo() break
         Ws("      Network number: ")
         ErrorLogAddress>>PORT.net = ReadNumber(8)
         Ws("      Host number: ")
         ErrorLogAddress>>PORT.host = ReadNumber(8)
         Ws("      Socket number: ")
         ErrorLogAddress>>PORT.soc↑2 = ReadNumber(8)
         ] repeat
      ifso Zero(ErrorLogAddress, 3)

// AskUser (cont'd)

   // Memory Error parameters
   @lvParityPhantomEnable = true
   @lvParitySweepCount = 176777b
   sysMECR = defaultMECR
   Ws("   Do you want to change memory error parameters? ")
   if YesNo() then
      [
      Ws("   Do you want to disable phantom parity error reporting? ")
      if YesNo() then @lvParityPhantomEnable = false
      Ws("   Do you want to disable error correction? ")
      sysMECR = YesNo()? 177775b, 177777b
      Ws("   Do you want to disable error reporting? ")
      test YesNo()
         ifso @lvParitySweepCount = 0
         ifnot
            [
            Ws("   Do you want to report single-bit errors? ")
            if YesNo() then sysMECR = sysMECR & 177767b
            Ws("   Do you want to report double-bit errors? ")
            if YesNo() then sysMECR = sysMECR & 177773b
            ]
      ]
   ]		

// DiskName string starts in the word after UserName string.
// They are left at advertised places in the Sys.boot file.
let p = userDiskName
let oldVal = vec 60; MoveBlock(oldVal, p, 60)
Ws("   What is your name: ")
let l = GetString(p, oldVal)
if l gr UserName!-1 then  //also kept up in Level 0
   [ l = UserName!-1; p>>STRING.length = l*2-1 ]
MoveBlock(UserName, p, l)
p = p+l
MoveBlock(oldVal, oldVal+oldVal>>STRING.length/2+1, 60)
Ws("   Please give your disk a name: ")
l = GetString(p, oldVal)
p = p+l
@p = 0

// DiskPassword is kept in encryped form at an advertised place
// in Sys.boot, but it is NOT kept in clear text ANYWHERE.
diskPassword!0 = false
Ws("   Do you wish to give the disk a password?")
if YesNo() then
   [
   Ws("   What is the password: ")
   let ps = vec 20; GetString(ps)
   Password(ps, diskPassword, true)
   Zero(ps, 20)	//In case stack stays around and is OutLd'ed intact!
   ]

Resets(dsp)
]

//----------------------------------------------------------------------------
and TellUser() be		// Dialog when booting
//----------------------------------------------------------------------------
// If the disk has a password, identify the disk and check the password.
[
if diskPassword!0 eq 0 return   //no password
@activeInterrupts = @activeInterrupts & not swatInterruptBit  //no aborts
Ws("*N*N*N   User name: "); Ws(userDiskName)
Ws(".  Disk name: "); Ws(userDiskName + (@userDiskName rshift 9) +1)
Ws(".  Password: "); let ps = vec 20
Resets(keys)  //no type-ahead allowed!
   [
   let i = 1
      [
      let c = Gets(keys)
      if c eq $*N % c eq $*S break
      ps>>STRING.char↑i = c
      ps>>STRING.length = i
      test c eq 10b % c eq 1  //BS or Ctrl-A
         ifso [ if i ne 1 then i = i-1; Ws("\") ]
         ifnot i = i+1
      ] repeat
   if Password(ps, diskPassword, false) break
   Ws("*N   Incorrect, try again: ")
   ] repeat
let maxLen = (UserPassword!-1) lshift 1 -1
if ps>>STRING.length gr maxLen then ps>>STRING.length = maxLen
MoveBlock(UserPassword, ps, UserPassword!-1)  //so we move a little extra...
@activeInterrupts = @activeInterrupts % swatInterruptBit  //aborts ok again
]