-- 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.