-- Copyright (C) 1984 by Xerox Corporation. All rights reserved. -- BootServerFileTajo.mesa, HGM, 4-Jun-84 22:39:29 DIRECTORY Ascii USING [CR], Checksum USING [ComputeChecksum], Environment USING [bytesPerPage, wordsPerPage], File USING [nullFile], Inline USING [LowHalf], MFile USING [ AcquireTemp, AddNotifyProc, CopyFileHandle, Error, GetLength, GetProperties, GetTimes, Handle, ReadOnly, Release, ReleaseChoice, Rename, RemoveNotifyProc, SetAccess, SetTimes, SwapNames], MSegment USING [Address, Create, Handle, Delete], MStream USING [Copy, Create, GetLength, Handle, WriteOnly], Put USING [Text], Space USING [Map, nullInterval, Unmap], SpecialMFile USING [GetCapaWithAccess, LeaderPages], Stream USING [Block, Delete, GetBlock, Handle, PutBlock, SetPosition], String USING [AppendChar, AppendString, Equivalent], Time USING [Append, AppendCurrent, Current, Unpack], BootServer USING [AppendBFN], BootServerFriends USING [ BootFile, EnumerateBootTable, pagesPerSwapUnit, ReleaseFile, timeNotKnown], PupWireFormat USING [ BcplLongNumber, BcplToMesaLongNumber, MesaToBcplLongNumber]; BootServerFileTajo: MONITOR IMPORTS Checksum, Inline, MFile, MSegment, MStream, Put, Space, SpecialMFile, Stream, String, Time, BootServer, BootServerFriends, PupWireFormat EXPORTS BootServerFriends = BEGIN BootFile: TYPE = BootServerFriends.BootFile; FileHandle: PUBLIC TYPE = MFile.Handle; FileAlreadyActive: ERROR = CODE; FileNotLocked: ERROR = CODE; FileLocked: ERROR = CODE; FileBusy: ERROR = CODE; ConfusionInFlushOneSpace: ERROR = CODE; PageCountUnderflow: ERROR = CODE; pagesInVM: PUBLIC CARDINAL ¬ 0; maxPagesInVM: PUBLIC CARDINAL ¬ 4000; -- SmallTalk is using 3000 page boot files! ActivateFileSystem: PUBLIC PROCEDURE = BEGIN MFile.AddNotifyProc[Arrived, ["*", null, readOnly], NIL]; MFile.AddNotifyProc[Died, ["*", null, writeOnly], NIL]; END; DeactivateFileSystem: PUBLIC PROCEDURE = BEGIN MFile.RemoveNotifyProc[Arrived, ["*", null, readOnly], NIL]; MFile.RemoveNotifyProc[Died, ["*", null, writeOnly], NIL]; END; ActivateFile: PUBLIC ENTRY PROCEDURE [bf: BootFile] = BEGIN IF bf.handle # NIL THEN ERROR FileAlreadyActive; bf.handle ¬ MFile.ReadOnly[ bf.fileName, [PleaseReleaseFile, bf] ! MFile.Error => CONTINUE]; IF bf.handle = NIL THEN RETURN; BlessBootFile[bf]; IF bf.handle = NIL THEN RETURN; bf.unknown ¬ FALSE; bf.file ¬ SpecialMFile.GetCapaWithAccess[bf.handle]; bf.bytes ¬ MFile.GetLength[bf.handle]; bf.pages ¬ Inline.LowHalf[ (bf.bytes + Environment.bytesPerPage - 1)/Environment.bytesPerPage]; END; BlessBootFile: PROCEDURE [bf: BootServerFriends.BootFile] = BEGIN IF bf.pup THEN BEGIN BlessPupBootFile[bf]; RETURN; END; IF ~TestFileChecksum[bf.handle] THEN BEGIN text: STRING = [100]; Time.AppendCurrent[text]; String.AppendString[text, " "L]; String.AppendString[text, bf.fileName]; String.AppendString[text, " has a bad file checksum"L]; LogString[text]; MFile.Release[bf.handle]; bf.handle ¬ NIL; bf.file ¬ File.nullFile; bf.unknown ¬ TRUE; RETURN; END; bf.create ¬ MFile.GetTimes[bf.handle].create; END; DeactivateFile: PUBLIC ENTRY PROCEDURE [bf: BootFile] = BEGIN IF bf.space # Space.nullInterval THEN ERROR FileBusy; IF bf.useCount # 0 THEN ERROR FileBusy; IF bf.handle = NIL THEN RETURN; bf.file ¬ File.nullFile; MFile.Release[bf.handle]; bf.handle ¬ NIL; END; ReadStreamFromBootFile: PUBLIC ENTRY PROCEDURE [bf: BootFile] RETURNS [Stream.Handle] = BEGIN IF bf.useCount = 0 THEN ERROR FileNotLocked; RETURN[ MStream.Create[ MFile.CopyFileHandle[bf.handle, []], [PleaseReleaseStream, bf]]]; END; SetupSpace: PUBLIC ENTRY PROCEDURE [bf: BootFile] = BEGIN IF bf.useCount = 0 THEN ERROR FileNotLocked; IF bf.space # Space.nullInterval THEN RETURN; UNTIL (pagesInVM + bf.pages) < maxPagesInVM DO IF pagesInVM = 0 THEN EXIT; -- In case SmallTalk gets even bigger!! FlushOneSpace[bf]; ENDLOOP; bf.space ¬ Space.Map[ window: [bf.file, SpecialMFile.LeaderPages[], bf.pages], class: file, access: readOnly, life: alive, swapUnits: [uniform[BootServerFriends.pagesPerSwapUnit]] ]; pagesInVM ¬ pagesInVM + bf.pages; END; FlushOneSpace: INTERNAL PROCEDURE [first: BootFile] = BEGIN hit: BootFile ¬ NIL; FOR bf: BootFile ¬ first, bf.next UNTIL bf = NIL DO IF bf.useCount > 0 THEN LOOP; IF bf.space # Space.nullInterval THEN hit ¬ bf; ENDLOOP; IF hit = NIL THEN ERROR ConfusionInFlushOneSpace; KillSpaceInternal[hit]; END; KillSpace: PUBLIC ENTRY PROCEDURE [bf: BootFile] = BEGIN IF bf.useCount # 0 THEN ERROR FileLocked; KillSpaceInternal[bf]; END; KillSpaceInternal: INTERNAL PROCEDURE [bf: BootFile] = BEGIN IF bf.pages > pagesInVM THEN ERROR PageCountUnderflow; [] ¬ Space.Unmap[bf.space.pointer, return]; bf.space ¬ Space.nullInterval; pagesInVM ¬ pagesInVM - bf.pages; END; -- Routines used to receive new boot files from other serers CreateTempFile: PUBLIC PROCEDURE RETURNS [fh: MFile.Handle, sh: Stream.Handle] = BEGIN fh ¬ NIL; -- In case volume full (or whatever) sh ¬ NIL; fh ¬ MFile.AcquireTemp[binary, 200*LONG[Environment.bytesPerPage] ! MFile.Error => CONTINUE]; IF fh = NIL THEN RETURN; sh ¬ MStream.Create[MFile.CopyFileHandle[fh, []], []]; END; MakeTempFileIntoBootFile: PUBLIC PROCEDURE [bf: BootFile, fh: MFile.Handle] RETURNS [ok: BOOLEAN] = BEGIN -- ** Check to see if we like it ok ¬ TRUE; IF TRUE THEN -- BUGS AND KROCKS ******************** BEGIN to, from: Stream.Handle; to ¬ MStream.WriteOnly[bf.fileName, [], binary]; from ¬ MStream.Create[MFile.CopyFileHandle[fh, [], readOnly], []]; [] ¬ MStream.Copy[from: from, to: to, bytes: LAST[LONG CARDINAL]]; Stream.Delete[from]; Stream.Delete[to]; MFile.Release[fh]; RETURN; END; MFile.SetAccess[fh, rename]; IF bf.handle = NIL THEN BEGIN MFile.Rename[fh, bf.fileName] -- This doesn't work yet.... END ELSE BEGIN old: MFile.Handle ¬ MFile.CopyFileHandle[bf.handle, []]; MFile.SetAccess[ old, rename ! MFile.Error => BEGIN ok ¬ FALSE; CONTINUE; END]; IF ok THEN MFile.SwapNames[fh, old]; MFile.Release[old]; END; MFile.Release[fh]; END; DeleteTempFile: PUBLIC PROCEDURE [fh: MFile.Handle] = BEGIN MFile.Release[fh]; END; -- Routines to process files retrieved via FTP or stored via FTPServe -- (also used indirectly if we retrieve it ourselves) -- Beware, these should not use the same LOCK or things will get tangled PleaseReleaseFile: PROCEDURE [file: MFile.Handle, instanceData: BootFile] RETURNS [MFile.ReleaseChoice] = BEGIN BEGIN name: STRING = [100]; [] ¬ MFile.GetProperties[file, name]; Hack[name, "ReleaseFile"L]; END; BootServerFriends.ReleaseFile[instanceData]; RETURN[goAhead]; END; PleaseReleaseStream: PROCEDURE [stream: MStream.Handle, instanceData: BootFile] RETURNS [MFile.ReleaseChoice] = BEGIN BEGIN name: STRING = [100]; [] ¬ MFile.GetProperties[instanceData.handle, name]; Hack[name, "ReleaseStream"L]; END; RETURN[later]; END; Arrived: PROCEDURE [ name: LONG STRING, file: MFile.Handle, clientInstanceData: LONG POINTER] RETURNS [BOOLEAN] = BEGIN Check: PROCEDURE [bf: BootFile] = BEGIN IF ~bf.inTransit AND ~bf.unknown THEN RETURN; IF ~String.Equivalent[name, bf.fileName] THEN RETURN; ActivateFile[bf]; bf.inTransit ¬ FALSE; IF bf.handle = NIL THEN RETURN; AnnounceArrival[bf]; END; BootServerFriends.EnumerateBootTable[Check]; RETURN[FALSE]; END; AnnounceArrival: PROCEDURE [bf: BootFile] = BEGIN text: STRING = [150]; Time.AppendCurrent[text]; String.AppendString[text, " "L]; String.AppendString[text, bf.fileName]; String.AppendString[text, " (#"L]; BootServer.AppendBFN[text, bf.code]; String.AppendString[text, ") was created on "L]; IF bf.create = BootServerFriends.timeNotKnown THEN String.AppendString[text, "???"L] ELSE Time.Append[text, Time.Unpack[bf.create]]; LogString[text]; END; Died: PROCEDURE [ name: LONG STRING, file: MFile.Handle, clientInstanceData: LONG POINTER] RETURNS [BOOLEAN] = BEGIN Check: PROCEDURE [bf: BootFile] = BEGIN IF ~bf.inTransit THEN RETURN; IF ~String.Equivalent[name, bf.fileName] THEN RETURN; bf.inTransit ¬ FALSE; END; IF file = NIL THEN BEGIN -- File was deleted BootServerFriends.EnumerateBootTable[Check]; END ELSE NULL; -- File exists, Arrived will notice it RETURN[FALSE]; END; Hack: PROCEDURE [name: LONG STRING, s: STRING] = BEGIN Message[s, " "L, name]; END; -- Leftover Pup stuff --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 BootFileHeader: TYPE = RECORD [ -- disk boot loader is first page other: ARRAY [0..3) OF WORD, timeStamp: PupWireFormat.BcplLongNumber]; BlessPupBootFile: PROCEDURE [bf: BootServerFriends.BootFile] = BEGIN old, new: MStream.Handle ¬ NIL; buffer: ARRAY [0..Environment.wordsPerPage) OF WORD; finger: Stream.Block = [LOOPHOLE[LONG[@buffer]], 0, Environment.bytesPerPage]; bfh: POINTER TO BootFileHeader = LOOPHOLE[@buffer]; old ¬ MStream.Create[MFile.CopyFileHandle[bf.handle, []], []]; BEGIN bytes: CARDINAL; [bytes, ] ¬ Stream.GetBlock[old, finger]; IF bytes # Environment.bytesPerPage THEN GOTO Empty; bf.bytes ¬ MStream.GetLength[old]; bf.pages ¬ Inline.LowHalf[ (bf.bytes + Environment.bytesPerPage - 1)/Environment.bytesPerPage]; bf.create ¬ LOOPHOLE[PupWireFormat.BcplToMesaLongNumber[bfh.timeStamp]]; IF bf.create # MFile.GetTimes[bf.handle].create THEN BEGIN text: STRING = [200]; Time.AppendCurrent[text]; String.AppendString[text, " Fixing Create Time for "L]; String.AppendString[text, bf.fileName]; String.AppendString[text, ". Was: "L]; Time.Append[text, Time.Unpack[MFile.GetTimes[bf.handle].create]]; String.AppendString[text, ", Will be: "L]; Time.Append[text, Time.Unpack[bf.create]]; LogString[text]; MFile.SetTimes[file: bf.handle, create: bf.create]; END; IF buffer[1] # 0 THEN BEGIN -- fixup S-format file text: STRING = [100]; scratch: MFile.Handle ¬ MFile.AcquireTemp[ binary, bf.pages ! MFile.Error => GOTO DiskFull]; MFile.SetTimes[file: scratch, create: bf.create]; Stream.Delete[old]; Time.AppendCurrent[text]; String.AppendString[text, " Reformatting "L]; String.AppendString[text, bf.fileName]; LogString[text]; old ¬ MStream.Create[MFile.CopyFileHandle[bf.handle, [], readOnly], []]; new ¬ MStream.Create[MFile.CopyFileHandle[scratch, [], writeOnly], []]; [] ¬ MStream.Copy[from: old, to: new, bytes: LAST[LONG CARDINAL]]; Stream.Delete[new]; Stream.Delete[old]; new ¬ MStream.Create[MFile.CopyFileHandle[scratch, [], readOnly], []]; old ¬ MStream.Create[MFile.CopyFileHandle[bf.handle, [], writeOnly], []]; [bytes, ] ¬ Stream.GetBlock[new, finger]; IF bytes # Environment.bytesPerPage 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 ¬ PupWireFormat.MesaToBcplLongNumber[Time.Current[]]; bf.create ¬ LOOPHOLE[PupWireFormat.BcplToMesaLongNumber[bfh.timeStamp]]; END; Stream.PutBlock[old, finger]; -- garbage DiskBoot loader THROUGH [2..255] DO [bytes, ] ¬ Stream.GetBlock[new, finger]; IF bytes # Environment.bytesPerPage THEN GOTO Short; ENDLOOP; Stream.PutBlock[old, finger]; -- locations 0B to 377B Stream.SetPosition[new, Environment.bytesPerPage]; THROUGH [2..253] DO [] ¬ Stream.GetBlock[new, finger]; Stream.PutBlock[old, finger]; ENDLOOP; Stream.Delete[new]; -- It may have shrunk if there was trash on the end of Swatee when creating a Mesa boot file Stream.Delete[old]; -- Truncate file old ¬ MStream.Create[MFile.CopyFileHandle[bf.handle, []], []]; bf.bytes ¬ MStream.GetLength[old]; bf.pages ¬ Inline.LowHalf[ (bf.bytes + Environment.bytesPerPage - 1)/Environment.bytesPerPage]; 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]; MFile.Release[bf.handle]; bf.handle ¬ NIL; bf.file ¬ File.nullFile; 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 Stream.Delete[new]; MFile.Release[bf.handle]; bf.handle ¬ NIL; bf.file ¬ File.nullFile; bf.unknown ¬ TRUE; END; END; Stream.Delete[old]; END; Message: PROCEDURE [s1, s2, s3, s4: LONG 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]; LogString[text]; END; LogString: PROCEDURE [text: LONG STRING] = BEGIN String.AppendChar[text, '.]; String.AppendChar[text, Ascii.CR]; Put.Text[NIL, text]; END; TestFileChecksum: PROCEDURE [file: MFile.Handle] RETURNS [ok: BOOLEAN] = BEGIN seg: MSegment.Handle; words: LONG CARDINAL; buffer: LONG POINTER; cs: WORD; words ¬ (MFile.GetLength[file] + 1)/2; seg ¬ MSegment.Create[MFile.CopyFileHandle[file, []], [], 0]; buffer ¬ MSegment.Address[seg]; BEGIN loc: LONG POINTER ¬ buffer + words - 1; IF words > 100*Environment.wordsPerPage THEN BEGIN -- 64K limit on microcode hunk: CARDINAL ¬ 20*Environment.wordsPerPage; finger: LONG POINTER ¬ buffer; left: LONG CARDINAL ¬ words; cs ¬ 0; UNTIL left = 0 DO IF left < hunk THEN hunk ¬ Inline.LowHalf[left]; cs ¬ Checksum.ComputeChecksum[cs, hunk, finger]; left ¬ left - hunk; finger ¬ finger + hunk; ENDLOOP; END ELSE cs ¬ Checksum.ComputeChecksum[0, Inline.LowHalf[words], buffer]; ok ¬ cs = 0; END; MSegment.Delete[seg]; END; END.