-- File: PupBootServerCold.mesa, Last Edit: HGM March 19, 1981 4:19 AM DIRECTORY Inline USING [LowHalf], InlineDefs USING [BcplLongNumber, BcplToMesaLongNumber, MesaToBcplLongNumber], Process USING [Detach, SetTimeout, MsecToTicks, Pause, Yield], Runtime USING [IsBound], Storage USING [CopyString, Free, FreeString, Node], String USING [ AppendChar, AppendLongNumber, AppendNumber, AppendString, EquivalentString, InvalidNumber, StringToOctal, SubString, SubStringDescriptor], StringDefs USING [BcplSTRING], System USING [ GetClockPulses, GreenwichMeanTime, GetGreenwichMeanTime, Pulses, PulsesToMicroseconds], Time USING [AppendCurrent, Current, Append, Unpack], File USING [Capability, nullCapability], Space USING [nullHandle], CmFile USING [Close, GetNextToken, NextItem, OpenSection], Event USING [Item, Reason, AddNotifier], Put USING [Line], Window USING [Handle], StatsDefs USING [StatCounterIndex, StatIncr, StatGetCounter], Clock USING [TimeIsKnown], George USING [ CreateInputStream, CreateOutputStream, Destroy, DeleteFileFromDisk, EnumerateDirectory, GetWords, GetLength, Handle, NameToCapability, PutWords, SetIndex], Indirect USING [GetParmFileName], BootServerDefs USING [ BootFile, BootFileObject, BootFileHeader, timeNotKnown, GetPointerToBootTable, SendBootDir, BreatherOn, BreatherOff, KillSpace, PupBootServer, UpdatePicture, BootStatsEntry, bootStatsReply, bootVersion], MiscServerDefs USING [ PupMiscServerOn, PupMiscServerOff, IgnoreThisPacket, SetBootServer], Slosh USING [ AddProcs, CopyFile, RecvFile, RecvStatus, RetransmissionInterval, Why], BufferDefs USING [], DriverDefs USING [Network], PupDefs USING [ GetFreePupBuffer, AppendHostName, PupAddress, PupBuffer, SendPup, PupSocket, PupSocketDestroy, PupSocketID, PupSocketMake, ReturnFreePupBuffer, SecondsToTocks, SetPupContentsWords, GetPupContentsBytes, UniqueLocalPupSocketID, UniqueLocalPupAddress, ReturnPup, defaultNumberOfNetworks, GetHopsToNetwork], PupTypes USING [miscSrvSoc, PupNetID, allNets, allHosts]; PupBootServerCold: MONITOR LOCKS lock IMPORTS Inline, InlineDefs, Process, Runtime, Storage, String, System, Time, Space, CmFile, Event, Put, Clock, George, Indirect, StatsDefs, BootServerDefs, MiscServerDefs, Slosh, PupDefs EXPORTS BufferDefs, BootServerDefs SHARES BufferDefs = BEGIN OPEN BootServerDefs; -- EXPORTed TYPEs Network: PUBLIC TYPE = DriverDefs.Network; lock: PUBLIC MONITORLOCK; useCount: CARDINAL ← 0; running, booting, slowBooting, pleaseStop, probing, sloshing: PUBLIC BOOLEAN ← FALSE; longRangeMode: BOOLEAN ← FALSE; msg: PUBLIC Window.Handle ← NIL; slosheeHost, slosheeFileName: PUBLIC STRING ← NIL; first: POINTER TO BootFile ← GetPointerToBootTable[]; probeTicks: CARDINAL ← 1*3600/5; -- 1 hour eventItem: Event.Item ← [eventMask: 177777B, eventProc: Broom]; statLife: PUBLIC StatsDefs.StatCounterIndex; statBootNew: PUBLIC StatsDefs.StatCounterIndex; statBootDir: PUBLIC StatsDefs.StatCounterIndex; statFile: PUBLIC StatsDefs.StatCounterIndex; statFileSent: PUBLIC StatsDefs.StatCounterIndex; statFileSentSlow: PUBLIC StatsDefs.StatCounterIndex; statFileTroubles: PUBLIC StatsDefs.StatCounterIndex; statFileNeverStarted: PUBLIC StatsDefs.StatCounterIndex; statBusyDisk: PUBLIC StatsDefs.StatCounterIndex; statBusyBooting: PUBLIC StatsDefs.StatCounterIndex; statMicrocodeBooted: PUBLIC StatsDefs.StatCounterIndex; statUnknown: PUBLIC StatsDefs.StatCounterIndex; verbose: BOOLEAN = TRUE; PupBootServerOn: PUBLIC PROCEDURE = BEGIN IF (useCount ← useCount + 1) = 1 THEN BEGIN running ← TRUE; Starter[]; END; FixupPicture[]; END; Starter: PROCEDURE = BEGIN pleaseStop ← FALSE; [] ← FindBootFiles[]; SweepDirectory[]; CheckDates[]; PrintMissingFiles[]; MiscServerDefs.PupMiscServerOn[]; MiscServerDefs.SetBootServer[BootServerDefs.PupBootServer]; Process.Detach[FORK BootServerDefs.BreatherOn[]]; Process.Detach[FORK Prober[]]; END; PupBootServerOff: PUBLIC PROCEDURE = BEGIN IF useCount # 0 AND (useCount ← useCount - 1) = 0 THEN BEGIN running ← FALSE; Stopper[]; END; FixupPicture[]; END; Stopper: PROCEDURE = BEGIN pleaseStop ← TRUE; MiscServerDefs.SetBootServer[MiscServerDefs.IgnoreThisPacket]; BreatherOff[]; WHILE booting OR slowBooting OR probing OR sloshing DO Process.Pause[Process.MsecToTicks[100]]; ENDLOOP; MiscServerDefs.PupMiscServerOff[]; ForgetBootFiles[]; END; FixupPicture: PROCEDURE = BEGIN IF msg # NIL AND Runtime.IsBound[UpdatePicture] THEN UpdatePicture[]; END; Newer: PROCEDURE [him, me: System.GreenwichMeanTime] RETURNS [BOOLEAN] = BEGIN IF him = BootServerDefs.timeNotKnown THEN RETURN[FALSE]; RETURN[him > me]; -- FIX THIS FOR EPOC STUFF END; --For more on boot files see the BuildBoot documentation -- Boot files created after late Dec 78 have a time stamp stored in words 3+4. -- OutLd leaves it zero. GateControl smashes it to "now". --There are two kinds of boot file. --B-Files produced by BuildBoot.run: -- File page 1: DiskBoot loader -- File page 2: locations #0-#377 -- File page 3: locations #1000 - #1377 -- File page 4: locations #1400 - #1777 -- ... -- File page n: locations #(n-1)B7 - #(n-1)B7+#377 --B-Files have 0 in the second data word --B-Files are started by jmp @0 when loading is complete --S-Files produced by Swat OutLd: -- File page 1: Special loader -- File page 2: locations #1000 - #1377 -- File page 3: locations #1400 - #1777 -- ... -- File page 253: locations #176400-176777 -- File page 254: locations #400 - #777 -- File page 255: locations #0 - #377 --S-Files have a non-zero value in the second data word --Some S-Files can be started by jmp @0. --This is the kind we use, but we re-format them first -- This routine converts an S-format file into a B-format file if necessary. There are problems with Sys.boot. For obvious reasons, we don't want to "fix" it. BlessBootFile: PROCEDURE [bf: BootFile] = BEGIN old, new: George.Handle ← NIL; buffer: ARRAY [0..256) OF WORD; scratchName: STRING = [40]; scratch: File.Capability ← File.nullCapability; bfh: POINTER TO BootServerDefs.BootFileHeader = LOOPHOLE[@buffer]; old ← George.CreateInputStream[bf.file]; BEGIN IF George.GetWords[old, @buffer, 256] # 256 THEN GOTO Empty; bf.bytes ← George.GetLength[old]; -- positions to EOF bf.pages ← Inline.LowHalf[(bf.bytes + 511)/512]; bf.create ← LOOPHOLE[InlineDefs.BcplToMesaLongNumber[bfh.timeStamp]]; IF buffer[1] # 0 THEN BEGIN -- fixup S-format file text: STRING = [100]; String.AppendString[scratchName, bf.fileName]; String.AppendString[scratchName, "$$$"L]; scratch ← George.NameToCapability[scratchName, 256]; Time.AppendCurrent[text]; String.AppendString[text, " Reformatting "L]; String.AppendString[text, bf.fileName]; String.AppendChar[text, '.]; LogString[text]; George.Destroy[old]; IF Slosh.CopyFile[to: scratch, from: bf.file] # statusStoreOk THEN GOTO DiskFull; new ← George.CreateInputStream[scratch]; old ← George.CreateOutputStream[bf.file]; IF George.GetWords[new, @buffer, 256] # 256 THEN GOTO Empty; buffer[1] ← 0; BEGIN -- smash crufty time stamp to now -- Note that our clock may be wrong at this point. If it is fast, other boot servers will ignore our file. If it is slow, we will go get a new boot file. I think that will work out ok. bfh.timeStamp ← InlineDefs.MesaToBcplLongNumber[Time.Current[]]; bf.create ← LOOPHOLE[InlineDefs.BcplToMesaLongNumber[bfh.timeStamp]]; END; George.PutWords[old, @buffer, 256]; -- garbage DiskBoot loader THROUGH [2..255] DO IF George.GetWords[new, @buffer, 256] # 256 THEN GOTO Short; ENDLOOP; George.PutWords[old, @buffer, 256]; -- locations 0B to 377B George.SetIndex[new, 2*256]; THROUGH [2..253] DO [] ← George.GetWords[new, @buffer, 256]; George.PutWords[old, @buffer, 256]; ENDLOOP; George.Destroy[new]; -- It may have shrunk if there was trash on the end of Swatee when creating a Mesa boot file George.Destroy[old]; -- Truncate file old ← George.CreateInputStream[bf.file]; bf.bytes ← George.GetLength[old]; -- positions to EOF bf.pages ← Inline.LowHalf[(bf.bytes + 511)/512]; END; EXITS DiskFull => BEGIN text: STRING = [100]; Time.AppendCurrent[text]; String.AppendString[text, " Disk full while copying over "L]; String.AppendString[text, bf.fileName]; LogString[text]; bf.file ← File.nullCapability; bf.unknown ← TRUE; END; Empty, Short => BEGIN text: STRING = [100]; Time.AppendCurrent[text]; String.AppendString[text, " "L]; String.AppendString[text, bf.fileName]; IF new = NIL THEN String.AppendString[text, " is empty."L] ELSE String.AppendString[text, " is SHORT. ******"L]; LogString[text]; IF new # NIL THEN George.Destroy[new]; bf.file ← File.nullCapability; bf.unknown ← TRUE; END; END; George.Destroy[old]; IF scratch # File.nullCapability THEN George.DeleteFileFromDisk[scratch]; END; -- This stuff should probably migrate to another file StartProbingForBootFiles: PUBLIC ENTRY PROCEDURE = BEGIN longRangeMode ← FALSE; ProbeForBootFiles[]; END; StartLongRangeProbingForBootFiles: PUBLIC ENTRY PROCEDURE = BEGIN longRangeMode ← TRUE; ProbeForBootFiles[]; END; Prober: ENTRY PROCEDURE = BEGIN counter: CARDINAL ← 120/5; -- initial probe 2 min after startup delay: CONDITION; tryHarder: CARDINAL ← 0; Process.SetTimeout[@delay, Process.MsecToTicks[5000]]; UNTIL pleaseStop DO WAIT delay; IF (counter ← counter - 1) = 0 THEN BEGIN tryHarder ← tryHarder + 1; longRangeMode ← (tryHarder MOD 24) = 0; ProbeForBootFiles[]; counter ← probeTicks; END; ENDLOOP; END; ProbeForBootFiles: INTERNAL PROCEDURE = BEGIN IF probing OR sloshing THEN RETURN; probing ← TRUE; FOR bf: BootFile ← first↑, bf.next UNTIL bf = NIL DO bf.tries ← 0; ENDLOOP; IF longRangeMode THEN Process.Detach[FORK LongRangeProbe[]] ELSE Process.Detach[FORK ShortRangeProbe[]]; END; ProbeSomeMore: ENTRY PROCEDURE = BEGIN IF probing OR sloshing THEN RETURN; probing ← TRUE; IF longRangeMode THEN Process.Detach[FORK LongRangeProbe[]] ELSE Process.Detach[FORK ShortRangeProbe[]]; END; ShortRangeProbe: PROCEDURE = BEGIN OPEN PupDefs; FixupPicture[]; ProbeOne[PupTypes.allNets]; probing ← FALSE; FixupPicture[]; IF ~sloshing THEN BootServerDefs.SendBootDir[NIL]; END; LongRangeProbe: PROCEDURE = BEGIN FixupPicture[]; FOR net: CARDINAL IN [1..PupDefs.defaultNumberOfNetworks) DO IF PupDefs.GetHopsToNetwork[[net]] > 3 THEN LOOP; ProbeOne[[net]]; IF sloshing THEN EXIT; ENDLOOP; probing ← FALSE; FixupPicture[]; IF ~sloshing THEN BootServerDefs.SendBootDir[NIL]; END; ProbeOne: PROCEDURE [net: PupTypes.PupNetID] = BEGIN OPEN PupDefs; b: PupBuffer; from: PupSocketID ← UniqueLocalPupSocketID[]; socket: PupSocket; socket ← PupSocketMake[ from, [net, PupTypes.allHosts, PupTypes.miscSrvSoc], SecondsToTocks[2]]; THROUGH [0..5) UNTIL pleaseStop OR sloshing DO b ← GetFreePupBuffer[]; b.pupType ← bootDirReq; SetPupContentsWords[b, 0]; socket.put[b]; UNTIL pleaseStop OR sloshing DO b ← socket.get[]; IF b = NIL THEN EXIT; IF b.pupType # bootDirReply THEN BEGIN ReturnFreePupBuffer[b]; LOOP; END; LookAtBootDir[b]; ENDLOOP; ENDLOOP; PupSocketDestroy[socket]; END; LookAtBootDir: PUBLIC ENTRY PROCEDURE [b: PupDefs.PupBuffer] = BEGIN OPEN PupDefs; bf: BootFile ← NIL; where: PupAddress ← b.source; name: STRING = [256]; word, size, end: CARDINAL; timeStamp: System.GreenwichMeanTime; timeStampLocation: LONG POINTER TO InlineDefs.BcplLongNumber; now: System.GreenwichMeanTime ← System.GetGreenwichMeanTime[]; heNeedsOne: BOOLEAN ← FALSE; IF pleaseStop THEN GOTO Stopping; IF sloshing THEN GOTO AlreadySloshing; BEGIN network: Network ← b.network; IF network.netNumber.b = b.source.net AND network.hostNumber = b.source.host THEN GOTO FromMe; END; end ← GetPupContentsBytes[b]/2; word ← 0; UNTIL word >= end OR bf # NIL DO timeStampLocation ← LOOPHOLE[@b.pupWords[word + 1]]; timeStamp ← LOOPHOLE[InlineDefs.BcplToMesaLongNumber[timeStampLocation↑]]; StringCopy[LOOPHOLE[@b.pupWords[word + 1 + 2]], name]; size ← ((1 + name.length) + 1)/2; FOR bf ← first↑, bf.next UNTIL bf = NIL DO IF bf.code >= 100000B THEN LOOP; IF bf.inTransit THEN LOOP; IF bf.code # b.pupWords[word] THEN LOOP; IF ~String.EquivalentString[name, bf.fileName] THEN LOOP; IF Newer[timeStamp, now] THEN LOOP; -- Don't propagate garbage IF ~bf.unknown AND bf.create # BootServerDefs.timeNotKnown AND Newer[bf.create, timeStamp] THEN heNeedsOne ← TRUE; IF ~bf.unknown AND ~Newer[timeStamp, bf.create] THEN LOOP; IF bf.tries > 1 THEN LOOP; EXIT; ENDLOOP; word ← word + size + 1 + 2; ENDLOOP; IF ~Clock.TimeIsKnown[] THEN GOTO TimeNotKnown; IF sloshing THEN GOTO Sloshing; -- This is just a hack to let him get the new version sooner. It should all work ok without this code. IF heNeedsOne THEN BEGIN b.dest.socket ← PupTypes.miscSrvSoc; BootServerDefs.SendBootDir[b]; END ELSE ReturnFreePupBuffer[b]; -- Don't call AppendHostName (or friends) from here. The lock is still locked. IF bf # NIL THEN BEGIN sloshing ← TRUE; bf.tries ← bf.tries + 1; Process.Detach[FORK GetNewBootFile[bf, where]]; RETURN; END; EXITS AlreadySloshing, FromMe, Stopping, Sloshing, TimeNotKnown => ReturnFreePupBuffer[b]; END; StringCopy: PROCEDURE [s: LONG POINTER TO StringDefs.BcplSTRING, d: STRING] = BEGIN i: CARDINAL; d.length ← s.length; FOR i IN [0..s.length) DO d[i] ← s.char[i]; ENDLOOP; END; GetNewBootFile: PROCEDURE [bf: BootFile, where: PupDefs.PupAddress] = BEGIN OPEN PupDefs; hisName: STRING = [30]; from: PupAddress ← UniqueLocalPupAddress[@where]; status: Slosh.RecvStatus; Ask: PROCEDURE = BEGIN b: PupBuffer ← GetFreePupBuffer[]; b.source ← from; b.dest ← where; b.pupID ← [0, bf.code]; SendPup[b, bootFileSend, 0]; END; AppendHostName[hisName, where]; slosheeHost ← hisName; slosheeFileName ← bf.fileName; FixupPicture[]; IF verbose THEN StartupMessage[bf, where]; DoSomeYields[]; status ← Slosh.RecvFile[ msg, bf.fileName, "Boot.scratch$"L, bf.file, from, Ask]; IF status # statusStoreOk THEN BEGIN n: CARDINAL ← Slosh.RetransmissionInterval[]; IF status = statusDiskFull THEN bf.tries ← bf.tries + 1; THROUGH [0..n) UNTIL pleaseStop DO Process.Pause[Process.MsecToTicks[1000]]; ENDLOOP; END; sloshing ← FALSE; slosheeHost ← slosheeFileName ← NIL; FixupPicture[]; IF pleaseStop OR probing THEN RETURN; DoSomeYields[]; ProbeSomeMore[]; END; StartupMessage: PROCEDURE [bf: BootFile, where: PupDefs.PupAddress] = BEGIN text: STRING = [100]; Time.AppendCurrent[text]; String.AppendString[text, " Found "L]; IF ~bf.unknown THEN String.AppendString[text, "newer version of "L]; String.AppendString[text, bf.fileName]; String.AppendString[text, " (#"L]; String.AppendNumber[text, bf.code, 8]; String.AppendString[text, ") on "L]; PupDefs.AppendHostName[text, where]; String.AppendChar[text, '.]; LogString[text]; END; Checker: PROCEDURE [why: Slosh.Why, fileName: STRING, file: File.Capability] = BEGIN SELECT why FROM check => BEGIN FOR bf: BootFile ← first↑, bf.next UNTIL bf = NIL DO IF String.EquivalentString[bf.fileName, fileName] THEN BEGIN -- inspect things here if we can think of anything to do END; ENDLOOP; END; release => BEGIN FOR bf: BootFile ← first↑, bf.next UNTIL bf = NIL DO IF bf.file = file THEN BEGIN IF bf.space # Space.nullHandle THEN KillSpace[bf]; bf.file ← File.nullCapability; bf.unknown ← TRUE; bf.inTransit ← TRUE; END; ENDLOOP; END; arrived => BEGIN parmFileName: STRING ← NIL; IF Runtime.IsBound[Indirect.GetParmFileName] THEN parmFileName ← Indirect.GetParmFileName[]; IF parmFileName = NIL THEN parmFileName ← "BootServer.txt"L; IF String.EquivalentString[parmFileName, fileName] AND running THEN BEGIN text: STRING = [150]; Time.AppendCurrent[text]; String.AppendString[ text, " BootServer restarting because a new version of "L]; String.AppendString[text, parmFileName]; String.AppendString[text, " arrived."L]; LogString[text]; DoSomeYields[]; Stopper[]; Starter[]; END; FOR bf: BootFile ← first↑, bf.next UNTIL bf = NIL DO IF String.EquivalentString[bf.fileName, fileName] THEN BEGIN text: STRING = [150]; StatsDefs.StatIncr[statBootNew]; -- could check to be sure bf.file=File.nullCapability bf.file ← file; bf.unknown ← FALSE; BlessBootFile[bf]; SendBootDir[NIL]; Time.AppendCurrent[text]; String.AppendString[text, " "L]; String.AppendString[text, bf.fileName]; String.AppendString[text, " (#"L]; String.AppendNumber[text, bf.code, 8]; String.AppendString[text, ") was created on "L]; IF bf.create = BootServerDefs.timeNotKnown THEN String.AppendString[text, "???"L] ELSE Time.Append[text, Time.Unpack[bf.create]]; String.AppendChar[text, '.]; LogString[text]; DoSomeYields[]; bf.inTransit ← FALSE; END; ENDLOOP; END; failed => BEGIN FOR bf: BootFile ← first↑, bf.next UNTIL bf = NIL DO IF String.EquivalentString[bf.fileName, fileName] THEN BEGIN bf.inTransit ← FALSE; END; ENDLOOP; END; ENDCASE => ERROR; RETURN; END; BootServerStats: PUBLIC PROCEDURE [b: PupDefs.PupBuffer] = BEGIN OPEN StatsDefs; BcplCounter: PROCEDURE [s: StatCounterIndex] RETURNS [InlineDefs.BcplLongNumber] = BEGIN RETURN[InlineDefs.MesaToBcplLongNumber[StatGetCounter[s]]]; END; bse: LONG POINTER TO BootStatsEntry ← LOOPHOLE[@b.pupWords]; bse↑ ← [version: bootVersion, directories: BcplCounter[statBootDir], fastSends: BcplCounter[statFileSent], slowSends: BcplCounter[statFileSentSlow], filesRecv: BcplCounter[statBootNew]]; PupDefs.ReturnPup[b, bootStatsReply, 2*SIZE[BootStatsEntry]]; END; ParameterError: ERROR [s: STRING] = CODE; GetOctal: PROCEDURE [ss: String.SubString] RETURNS [CARDINAL] = BEGIN ENABLE String.InvalidNumber => ERROR ParameterError["Octal number expected: "L]; token: STRING = [100]; IF ~CmFile.GetNextToken[ss, token] THEN SIGNAL String.InvalidNumber; RETURN[String.StringToOctal[token]]; END; GetName: PROCEDURE [ss: String.SubString] RETURNS [STRING] = BEGIN token: STRING = [100]; IF ~CmFile.GetNextToken[ss, token] THEN ERROR ParameterError["Another Token expected: "L]; RETURN[Storage.CopyString[token]]; END; FindBootFiles: PROCEDURE RETURNS [BOOLEAN] = BEGIN sectionName: STRING = "BootServer"L; parmFileName: STRING ← NIL; IF Runtime.IsBound[Indirect.GetParmFileName] THEN parmFileName ← Indirect.GetParmFileName[]; IF parmFileName = NIL THEN parmFileName ← "BootServer.txt"L; IF ~CmFile.OpenSection[parmFileName, sectionName] THEN BEGIN Message["Can't find [BootServer] section in "L, parmFileName]; RETURN[FALSE]; END; DO ss: String.SubStringDescriptor; name, arg: STRING ← NIL; text: STRING = [200]; [name, arg] ← CmFile.NextItem[]; IF arg # NIL THEN ss ← [base: arg, offset: 0, length: arg.length]; IF name = NIL THEN EXIT; IF name[0] = '; THEN { Storage.FreeString[name]; Storage.FreeString[arg]; LOOP; }; BEGIN ENABLE ParameterError => BEGIN Message[s, name, ": "L, arg]; CONTINUE; END; code: WORD ← GetOctal[@ss]; fileName: STRING ← GetName[@ss]; String.AppendString[text, "Boot file number "L]; String.AppendNumber[text, code, 8]; String.AppendString[text, " is "L]; String.AppendString[text, fileName]; String.AppendString[text, "."L]; LogString[text]; AddBootFile[code, fileName]; END; Storage.FreeString[name]; Storage.FreeString[arg]; ENDLOOP; CmFile.Close[parmFileName]; RETURN[TRUE]; END; AddBootFile: PROCEDURE [code: WORD, fileName: STRING] = BEGIN bf: BootFile ← Storage.Node[SIZE[BootFileObject]]; last: BootFile; bf↑ ← [next: NIL, code: code, create: BootServerDefs.timeNotKnown, file: File.nullCapability, fileName: fileName, ms: 0, count: 0, pages: 0, bytes: 0, space: Space.nullHandle, tries: 0, unknown:TRUE, inTransit: TRUE]; IF first↑ = NIL THEN first↑ ← bf ELSE BEGIN FOR last ← first↑, last.next UNTIL last.next = NIL DO ENDLOOP; last.next ← bf; END; END; SweepDirectory: PROCEDURE = BEGIN pulses: System.Pulses; files: CARDINAL ← 0; text: STRING = [200]; CheckOne: PROCEDURE [file: File.Capability, name: STRING] RETURNS [BOOLEAN] = BEGIN files ← files+1; FOR bf: BootFile ← first↑, bf.next UNTIL bf = NIL DO IF String.EquivalentString[bf.fileName, name] THEN BEGIN bf.file ← file; bf.unknown ← FALSE; END; ENDLOOP; RETURN[FALSE]; -- the same name may be in more than one slot END; pulses ← System.GetClockPulses[]; George.EnumerateDirectory[CheckOne]; pulses ← System.Pulses[System.GetClockPulses[] - pulses]; String.AppendString[text, "It took "L]; String.AppendLongNumber[text, System.PulsesToMicroseconds[pulses]/1000, 10]; String.AppendString[text, " ms to scan the directory which contained "L]; String.AppendNumber[text, files, 10]; String.AppendString[text, " files."L]; LogString[text]; DoSomeYields[]; END; CheckDates: PROCEDURE = BEGIN pulses: System.Pulses; files: CARDINAL ← 0; text: STRING = [200]; pulses ← System.GetClockPulses[]; FOR bf: BootFile ← first↑, bf.next UNTIL bf = NIL DO IF ~bf.unknown THEN BlessBootFile[bf]; bf.inTransit ← FALSE; files ← files+1; DoSomeYields[]; ENDLOOP; pulses ← System.Pulses[System.GetClockPulses[] - pulses]; String.AppendString[text, "It took "L]; String.AppendLongNumber[text, System.PulsesToMicroseconds[pulses]/1000, 10]; String.AppendString[text, " ms to check the dates in "L]; String.AppendNumber[text, files, 10]; String.AppendString[text, " files."L]; LogString[text]; DoSomeYields[]; END; PrintMissingFiles: PROCEDURE = BEGIN FOR bf: BootFile ← first↑, bf.next UNTIL bf = NIL DO IF bf.unknown THEN BEGIN text: STRING = [100]; Time.AppendCurrent[text]; String.AppendString[text, " BootServer: "L]; String.AppendString[text, bf.fileName]; String.AppendString[text, " (#"L]; String.AppendNumber[text, bf.code, 8]; String.AppendString[text, ") is not on this disk."L]; LogString[text]; DoSomeYields[]; END; ENDLOOP; END; ForgetBootFiles: PROCEDURE = BEGIN UNTIL first↑ = NIL DO bf: BootFile ← first↑; first↑ ← bf.next; IF bf.space # Space.nullHandle THEN KillSpace[bf]; Storage.FreeString[bf.fileName]; Storage.Free[bf]; ENDLOOP; END; EnumerateBootTable: PUBLIC ENTRY PROCEDURE [proc: PROCEDURE [BootFile]] = BEGIN FOR bf: BootFile ← first↑, bf.next UNTIL bf = NIL DO proc[bf]; ENDLOOP; END; Message: PROCEDURE [s1, s2, s3, s4: STRING ← NIL] = BEGIN text: STRING = [200]; String.AppendString[text, "BootServer: "L]; String.AppendString[text, s1]; IF s2 # NIL THEN String.AppendString[text, s2]; IF s3 # NIL THEN String.AppendString[text, s3]; IF s4 # NIL THEN String.AppendString[text, s4]; String.AppendChar[text, '.]; LogString[text]; END; LogString: PROCEDURE [text: STRING] = BEGIN IF msg # NIL THEN Put.Line[msg, text]; Put.Line[NIL, text]; END; DoSomeYields: PROCEDURE = BEGIN THROUGH [0..100) DO Process.Yield[]; ENDLOOP; END; Broom: PROCEDURE [why: Event.Reason] = BEGIN SELECT why FROM makeImage, makeCheck, stopMesa => IF running THEN Stopper[]; startImage, restartCheck, continueCheck => IF running THEN Starter[]; ENDCASE => NULL; END; -- initialization Event.AddNotifier[@eventItem]; Slosh.AddProcs[Checker]; END.