-- ReleaseImpl.Mesa, last edit March 16, 1983 6:54 pm -- Pilot 6.0/ Mesa 7.0 -- Phase Meaning -- 1 verify consistency of all DF files: -- make sure there are no version conflicts and -- all files exist on remote servers -- 2 verify sufficiency: run VerifyDF on all DF files -- 3 transfer files to new homes, produce new DF files -- note that only Phase 2 involves looking at the internals of the files -- Phase 2 and 3 are in Release23Impl.Mesa -- Phase 4 is in Release4Impl.Mesa DIRECTORY BTreeDefs: TYPE USING[BTreeHandle, CreateAndInitializeBTree, Desc, Insert, KeyNotFound, Lookup,ReleaseBTree, TestKeys], BTreeSupportDefs: TYPE USING[FileHandle--, SetLength--], BTreeSupportExtraDefs: TYPE USING[CloseFile, OpenFile], Buttons: TYPE USING [Button, ButtonProc, Create], Containers: TYPE USING [ChildXBound, Container, Create], CWF: TYPE USING [SetWriteProcedure, SWF1, SWF2, SWF3, WF0, WF1, WF2, WF3, WF4, WFC, WFCR], DateAndTime: TYPE USING [Parse], Directory: TYPE USING[Error, Lookup, ignore], DFSubr: TYPE USING [AllocateDFSeq, DF, DFSeq, FlattenDF, FreeDFSeq, SortByFileName, StripLongName, TooManyEntries], File: TYPE USING [Capability, nullCapability], FileIO: TYPE USING[minimumStreamBufferParms, Open], FQ: TYPE USING[FileQuery, Result], IFSFile: TYPE USING [CantOpen, Error, UnableToLogin], Inline: TYPE USING[BITOR], IO: TYPE USING[card, Close, CreateDribbleStream, Flush, Handle, Put, PutChar, PutF, ResetUserAbort, rope, SetUserAbort, string, UserAbort, UserAborted], Labels: TYPE USING [Create, Label, Set], LeafSubr: TYPE USING [PrintLeafAccessFailure, PrintLeafProblem, StopLeaf], LongString: TYPE USING [EquivalentString], Menus: TYPE USING [CreateEntry, CreateMenu, InsertMenuEntry, Menu, MenuProc], MessageWindow: TYPE USING[Append, Blink], Process: TYPE USING [Detach, Priority, priorityBackground, priorityForeground, priorityNormal, SetPriority], ReleaseSupport: TYPE USING [TransferFiles, VerifySufficiency], Rope: TYPE USING[ROPE, Text], RopeInline: TYPE USING[InlineFlatten], UnsafeSTP: TYPE USING [Error], STPSubr: TYPE USING [SetNumberOfConnectTries, StopSTP], Stream: TYPE USING [Delete, Handle], Subr: TYPE USING [AbortMyself, Any, CopyString, debugflg, EndsIn, errorflg, FileError, FreeHugeZone, FreeString, GetLine, LongZone, MakeTTYProcs, NewFile, NewStream, PagesUsedInHugeZone, Prefix, Read, ReadWrite, strcpy, SubStrCopy, SubrInit, SubrStop, TTYProcs], Time: TYPE USING [Current], TypeScript: TYPE USING[Create, Destroy, TS], UserExec: TYPE USING[AskUser], ViewerClasses: TYPE USING [Viewer], ViewerEvents: TYPE USING[EventProc, EventRegistration, RegisterEventProc, UnRegisterEventProc], ViewerOps: TYPE USING [PaintViewer, SetMenu, SetOpenHeight], ViewerIO: TYPE USING[CreateViewerStreams], ViewerTools: TYPE USING [MakeNewTextViewer, GetContents, SetSelection]; ReleaseImpl: MONITOR IMPORTS BTreeDefs, BTreeSupportExtraDefs, Buttons, Containers, CWF, DateAndTime, DFSubr, Directory, FileIO, FQ, IFSFile, Inline, IO, Labels, LeafSubr, LongString, Menus, MessageWindow, Process, ReleaseSupport, RopeInline, STP: UnsafeSTP, STPSubr, Stream, Subr, Time, TypeScript, UserExec, ViewerEvents, ViewerOps, ViewerIO, ViewerTools = { -- number of entries in all df files MAXALLFILES: CARDINAL = 6500; -- number of bytes in an IFS page -- (all page counts are in IFS pages, not Pilot pages) bytesPerIFSPage: CARDINAL = 2048; EPhase: TYPE = {One, Two, Three, OneThree}; -- all global data in a record to help scoping Global: TYPE = RECORD[ -- viewers data container: Containers.Container ← NIL, typeScript: TypeScript.TS ← NIL, tty: Subr.TTYProcs, -- for typeScript in: IO.Handle ← NIL, -- to the dribble stream out: IO.Handle ← NIL, -- to the dribble stream logFile: IO.Handle ← NIL, -- to the log file -- fields fileNameButton: Buttons.Button ← NIL, fileNameViewer: ViewerClasses.Viewer ← NIL, phaseLabel: Labels.Label ← NIL, phaseButton: Buttons.Button ← NIL, priorityLabel: Labels.Label ← NIL, priorityButton: Buttons.Button ← NIL, useOldPhase1FileLabel: Labels.Label ← NIL, useOldPhase1FileButton: Buttons.Button ← NIL, useOldPhase3FileLabel: Labels.Label ← NIL, useOldPhase3FileButton: Buttons.Button ← NIL, updateBTreeLabel: Labels.Label ← NIL, updateBTreeButton: Buttons.Button ← NIL, verboseLabel: Labels.Label ← NIL, verboseButton: Buttons.Button ← NIL, -- program data busy: BOOL ← FALSE, phase: EPhase ← One, priority: Process.Priority ← Process.priorityNormal, checkOverwrite: BOOL ← FALSE, verbose: REF BOOL ← NIL, nProbablePages: LONG CARDINAL ← 0, nProbableFiles: CARDINAL ← 0, dfseqall: DFSubr.DFSeq ← NIL, useOldPhase1FileCache: BOOL ← TRUE, useOldPhase3FileCache: BOOL ← TRUE, updateBTree: BOOL ← TRUE, oldPhase1FileCacheExists: BOOL ← FALSE, oldestBcdDate: LONG CARDINAL ← 0, -- btree data bTreeCap: File.Capability ← File.nullCapability, bTreeHandle: BTreeDefs.BTreeHandle ← NULL ]; -- mds usage g: REF Global; destroyEventRegistration: ViewerEvents.EventRegistration; -- end of mds usage Go: Menus.MenuProc = TRUSTED { p: PROCESS; IF g.busy THEN { CWF.WF0["Still busy - try later.\n"L]; RETURN; }; p ← FORK ChooseAmong[g.phase]; Process.Detach[p]; }; ChooseAmong: ENTRY PROC[phase: EPhase] = { ENABLE UNWIND => g.busy ← FALSE; topdffilename: STRING ← [100]; dfFileRef: Rope.Text; starttime: LONG CARDINAL ← Time.Current[]; { ENABLE { UNWIND => TemporaryStop[]; IFSFile.Error, IFSFile.UnableToLogin => { LeafSubr.PrintLeafProblem[reason]; GOTO leave; }; IFSFile.CantOpen => { LeafSubr.PrintLeafAccessFailure[reason]; GOTO leave; }; ABORTED, Subr.AbortMyself, IO.UserAborted => { g.in.ResetUserAbort[]; CWF.WF0["\nRelease Aborted.\n"L]; GOTO leave; }; STP.Error => { CWF.WF0["FTP Error. "L]; IF error ~= NIL THEN CWF.WF2["message: %s, code %u in Stp.Mesa\n"L, error, @code]; Subr.errorflg ← TRUE; GOTO leave; }; }; DoPhase: PROC[phase: EPhase] = { time: LONG CARDINAL ← Time.Current[]; Process.SetPriority[g.priority]; SELECT phase FROM One => CWF.WF0["Phase One: Verify consistency of all the DF files:\nStart by reading all DF files.\n"L]; Two => CWF.WF0["Phase Two: Verify sufficiency by invoking VerifyDF on all the DF files:\n"L]; Three => CWF.WF0["Phase Three: Transfer remote files and fix up DF files to match.\n"L]; ENDCASE => ERROR; CWF.WF1["Phase started at %lt.\n"L, @time]; Subr.errorflg ← Subr.debugflg ← FALSE; SELECT phase FROM One => VerifyConsistency[topdffilename]; Two => ReleaseSupport.VerifySufficiency[topdffilename, g.tty, g.out, g.checkOverwrite]; Three => ReleaseSupport.TransferFiles[topdffilename, g.dfseqall, g.tty, g.in, g.out, g.logFile, g.checkOverwrite, g.useOldPhase3FileCache, g.updateBTree, g.verbose]; ENDCASE => ERROR; TemporaryStop[]; time ← Time.Current[]; CWF.WF1["Phase ended at %lt.\n"L, @time]; }; STPSubr.SetNumberOfConnectTries[64000]; -- infinite number g.in.ResetUserAbort[]; g.busy ← TRUE; [] ← CWF.SetWriteProcedure[ToolTTYProc]; dfFileRef ← RopeInline.InlineFlatten[ViewerTools.GetContents[g.fileNameViewer]]; IF dfFileRef = NIL THEN { CWF.WF0["Error - must specify a df file name.\n"L]; GOTO leave; }; IF NOT Subr.Any[LOOPHOLE[dfFileRef], '.] THEN CWF.SWF1[topdffilename, "%s.DF"L, LOOPHOLE[dfFileRef]] ELSE Subr.strcpy[topdffilename, LOOPHOLE[dfFileRef]]; IF NOT Subr.EndsIn[topdffilename, ".df"L] THEN { CWF.WF1["Error - %s must be a DF file name.\n"L, topdffilename]; GOTO leave; }; IF phase = OneThree THEN { DoPhase[One]; CWF.WFCR[]; PrintSeparator[]; DoPhase[Three]; CWF.WFCR[]; } ELSE DoPhase[phase]; EXITS leave => NULL; }; TemporaryStop[]; starttime ← Time.Current[] - starttime; CWF.WF1["\nTotal elapsed time for ReleaseTool %lr."L,@starttime]; IF Subr.errorflg THEN CWF.WF0["\tErrors logged."L]; CWF.WFCR[]; PrintSeparator[]; g.busy ← FALSE; }; -- Phase 1 VerifyConsistency: PROC[topdffilename: LONG STRING] = { nconflicts, nmissing, nentries, npages: CARDINAL; g.out.PutF["Remember: max entries in flat DF files = %d\n", IO.card[MAXALLFILES]]; g.out.Put[IO.rope["(Do not Destroy this window. Destroy the upper one only.)\n"]]; -- make btree only if it will be used IF g.useOldPhase1FileCache OR g.updateBTree THEN MakeBTree[]; g.nProbablePages ← 0; g.nProbableFiles ← 0; IF g.dfseqall ~= NIL THEN { hz: UNCOUNTED ZONE ← g.dfseqall.dfzone; -- DANGEROUS: make sure there are no outstanding pointers FreeDFSeqAll[]; hz ← Subr.FreeHugeZone[hz]; -- we know it is a huge zone }; g.dfseqall ← ReadDF[topdffilename]; IF g.dfseqall.size = 0 THEN { FreeDFSeqAll[]; RETURN; }; npages ← Subr.PagesUsedInHugeZone[g.dfseqall.dfzone]; CWF.WF1["%u pages used in HugeZone.\n"L, @npages]; Flush[]; CWF.WF0["Sorting the flattened DF files.\n"L]; -- this sorts with shortname highest precedence DFSubr.SortByFileName[g.dfseqall, 0, g.dfseqall.size-1, TRUE]; -- sorting must precede CheckExceptions, CheckForExistence, and VerifySpanningTree Flush[]; VerifySpanningTree[g.dfseqall, topdffilename]; -- sorting must precede CheckExceptions and CheckForExistence Flush[]; CheckExceptions[g.dfseqall]; Flush[]; CheckReleaseAs[g.dfseqall]; Flush[]; [nmissing, nentries] ← CheckForExistence[g.dfseqall]; CWF.WF0["Sorting the flattened DF files by shortname.\n"L]; -- this step is necessary since phase 3 depends on this being sorted by shortname DFSubr.SortByFileName[g.dfseqall, 0, g.dfseqall.size-1, TRUE]; Flush[]; CheckBcdDates[g.dfseqall]; Flush[]; nconflicts ← CheckConflicts[g.dfseqall]; SetDFFileCreateTimes[g.dfseqall, topdffilename]; CWF.WF2["Summary for Phase 1: %u conflicting versions, %u files/versions not found.\n"L, @nconflicts, @nmissing]; CWF.WF4["Total %u entries, %u unique entries\n\t%lu probable pages (2048 byte pgs) in release of %u probable files.\n"L, @g.dfseqall.size, @nentries, @g.nProbablePages, @g.nProbableFiles]; npages ← Subr.PagesUsedInHugeZone[g.dfseqall.dfzone]; CWF.WF1["%u pages used in HugeZone.\n"L, @npages]; Flush[]; }; ReadDF: PROC[topdffilename: LONG STRING] RETURNS[dfseq: DFSubr.DFSeq] = { time: LONG CARDINAL ← Time.Current[]; dfseq ← DFSubr.AllocateDFSeq[maxEntries: MAXALLFILES, zoneType: huge]; IF g.in.UserAbort[] THEN SIGNAL Subr.AbortMyself; -- the nested entries are definitely NOT to be forced to be readonly! DFSubr.FlattenDF[dfseq: dfseq, dffilename: topdffilename, h: g.tty, checkForOverwrite: g.checkOverwrite, setRecorder: TRUE, printStatus: g.verbose↑, allowForceReadOnly: FALSE, skipCameFrom: TRUE, tryDollars: FALSE ! DFSubr.TooManyEntries => { CWF.WF0["Error - too many entries for the one data structure that holds every DF entry.\n"L]; CWF.WF0["Increase its size and try again.\n"L]; CONTINUE; } ]; time ← Time.Current[] - time; Flush[]; CWF.WF1["Time to read in all the DF files: %lr.\n"L, @time]; }; -- must be sorted by shortname VerifySpanningTree: PROC[dfseq: DFSubr.DFSeq, topdffilename: LONG STRING] = { df, dfj: DFSubr.DF; natsign, nreleaseas: CARDINAL; out: IO.Handle ← FileIO.Open["SpanningTree.List$", overwrite]; out.PutF["Verify spanning tree rule: every DF file is mentioned once and only once with a ReleaseAs clause. (skipping CameFroms)\n"]; FOR i: CARDINAL IN [0 .. dfseq.size) DO dfseq[i].eval ← FALSE; ENDLOOP; FOR i: CARDINAL IN [0 .. dfseq.size) DO j: CARDINAL; df ← @dfseq[i]; IF df.eval OR NOT Subr.EndsIn[df.shortname, ".df"L] OR df.parentCameFrom OR df.cameFrom THEN LOOP; natsign ← nreleaseas ← 0; -- this is a df we have not seen before (not nested in a cameFrom DF) Flush[]; out.PutF["\nFile %s is referenced by:\n", IO.string[df.shortname]]; j ← i; DO IF j >= dfseq.size THEN EXIT; dfj ← @dfseq[j]; IF dfj.shortname.length = df.shortname.length AND LongString.EquivalentString[dfj.shortname, df.shortname] THEN { IF dfj.atsign AND NOT dfj.readonly THEN natsign ← natsign + 1; IF dfj.releaseDirectory ~= NIL AND NOT dfj.cameFrom AND NOT dfj.readonly AND NOT LongString.EquivalentString[dfj.shortname, dfj.recorder] THEN nreleaseas ← nreleaseas + 1; dfj.eval ← TRUE; out.PutF[" %s", IO.string[dfj.recorder]]; IF dfj.readonly THEN out.PutF[" (readonly)"]; out.PutF[","]; } ELSE EXIT; j ← j + 1; ENDLOOP; out.PutF["\n"]; IF natsign = 0 AND NOT LongString.EquivalentString[df.shortname, topdffilename] THEN out.PutF["\tWarning - no Includes for %s anywhere.\n", IO.string[df.shortname]]; IF nreleaseas ~= 1 THEN out.PutF["\tError - there were %d ReleaseAs statements for %s (not counting self references). There should be exactly one.\n", IO.card[nreleaseas], IO.string[df.shortname]]; ENDLOOP; out.Close[]; }; -- invariant: after completion, df.version = 0 means -- highest version or no such file, otherwise df.version is the correct one -- -- dfseq must be sorted CheckForExistence: PROC[dfseq: DFSubr.DFSeq] RETURNS[nmissing, nentries: CARDINAL] = { df: DFSubr.DF; inCache: BOOL; nIfsPages, remoteVersion, i: CARDINAL ← 0; remoteCreateTime: LONG CARDINAL ← 0; fres: FQ.Result; starttime: LONG CARDINAL ← Time.Current[]; -- check for existence nmissing ← nentries ← 0; -- now verify each and every file is out there CWF.WF0["Now make sure all those files are really out there.\n"L]; IF NOT g.oldPhase1FileCacheExists THEN { CWF.WF0["(Running priorityForeground.)\n"L]; Process.SetPriority[Process.priorityForeground]; }; i ← 0; WHILE i < dfseq.size DO df ← @dfseq[i]; -- skip if same create or adjacent entries are for ~= or > IF i > 0 AND ((df.createtime > 0 AND dfseq[i-1].createtime = df.createtime) OR (df.criterion ~= none AND dfseq[i-1].criterion ~= none)) AND LongString.EquivalentString[dfseq[i-1].shortname, df.shortname] AND LongString.EquivalentString[dfseq[i-1].directory, df.directory] AND LongString.EquivalentString[dfseq[i-1].host, df.host] THEN { df.version ← dfseq[i-1].version; i ← i + 1; -- skip this one, go to next; LOOP; }; nentries ← nentries + 1; IF g.in.UserAbort[] THEN SIGNAL Subr.AbortMyself; -- Flush[]; IF g.verbose↑ THEN CWF.WF4["Check [%s]<%s>%s in %s ... "L, df.host, df.directory, df.shortname, df.recorder] ELSE CWF.WFC['&]; nIfsPages ← 0; remoteCreateTime ← 0; remoteVersion ← 0; inCache ← FALSE; fres ← notFound; IF g.oldPhase1FileCacheExists AND g.useOldPhase1FileCache AND df.createtime ~= 0 THEN { [inCache, nIfsPages] ← LookupInOldFileCache[df]; fres ← foundCorrectVersion; -- highversion and highdate have no value }; IF NOT inCache THEN [fres, nIfsPages, remoteVersion, remoteCreateTime] ← CheckFile[df]; SELECT fres FROM foundCorrectVersion => { IF g.verbose↑ THEN CWF.WF1["ok. %s\n"L, IF inCache THEN "(in cache)"L ELSE ""L]; IF df.releaseDirectory ~= NIL AND NOT df.cameFrom THEN { g.nProbableFiles ← g.nProbableFiles + 1; g.nProbablePages ← g.nProbablePages + nIfsPages; }; IF NOT inCache AND g.updateBTree THEN InsertIntoCache[df, nIfsPages]; }; foundWrongVersion => { -- fixup for phase 3: might as well get the highest ones -- note this may screw up consistency checking df.createtime ← remoteCreateTime; df.version ← remoteVersion; }; notFound => nmissing ← nmissing + 1; ENDCASE => ERROR; i ← i + 1; ENDLOOP; IF NOT g.oldPhase1FileCacheExists THEN { Process.SetPriority[g.priority]; CWF.WF0["\n(Priority reset.)\n"L]; }; starttime ← Time.Current[] - starttime; CWF.WF1["Total time for CheckFiles %lr.\n"L,@starttime]; }; BVal: TYPE = RECORD[ version: CARDINAL ← 0, nIfsPages: CARDINAL ← 0 ]; -- specialPrefix is removed LookupInOldFileCache: PROC[df: DFSubr.DF] RETURNS[inCache: BOOL, nIfsPages: CARDINAL] = { bval: BVal ← []; sfn: STRING ← [100]; len: CARDINAL; specialPrefix: STRING ← "[Indigo]<PreCedar>"L; file: STRING ← [100]; inCache ← FALSE; nIfsPages ← 0; IF df.createtime = 0 OR NOT g.oldPhase1FileCacheExists OR NOT g.useOldPhase1FileCache THEN RETURN; CWF.SWF3[file, "[%s]<%s>%s"L, df.host, df.directory, df.shortname]; IF Subr.Prefix[file, specialPrefix] THEN Subr.SubStrCopy[file, file, specialPrefix.length]; CWF.SWF2[sfn, "%lu\000%s"L, @df.createtime, file]; len ← BTreeDefs.Lookup[g.bTreeHandle, MakeBTreeDesc[sfn], DESCRIPTOR[@bval, SIZE[BVal]]]; IF len = BTreeDefs.KeyNotFound THEN RETURN; df.version ← bval.version; nIfsPages ← bval.nIfsPages; RETURN[TRUE, nIfsPages]; }; -- specialPrefix is removed InsertIntoCache: PROC[df: DFSubr.DF, nIfsPages: CARDINAL] = { bval: BVal ← [version: df.version, nIfsPages: nIfsPages]; specialPrefix: STRING ← "[Indigo]<PreCedar>"L; sfn: STRING ← [100]; file: STRING ← [100]; IF df.version = 0 THEN ERROR; CWF.SWF3[file, "[%s]<%s>%s"L, df.host, df.directory, df.shortname]; IF Subr.Prefix[file, specialPrefix] THEN Subr.SubStrCopy[file, file, specialPrefix.length]; CWF.SWF2[sfn, "%lu\000%s"L, @df.createtime, file]; BTreeDefs.Insert[g.bTreeHandle, MakeBTreeDesc[sfn], DESCRIPTOR[@bval, SIZE[BVal]]]; }; CheckFile: PROC[df: DFSubr.DF] RETURNS[fres: FQ.Result, nIfsPages, remoteVersion: CARDINAL, remoteCreateTime: LONG CARDINAL] = { remoteByteLength: LONG CARDINAL; targetFileName: STRING ← [100]; nIfsPages ← 0; [fres: fres, remoteVersion: remoteVersion, remoteCreateTime: remoteCreateTime, remoteByteLength: remoteByteLength] ← FQ.FileQuery[df.host, df.directory, df.shortname, df.version, df.createtime, df.criterion = none AND df.createtime = 0, g.tty, targetFileName]; SELECT fres FROM foundCorrectVersion => { IF df.createtime > 0 AND df.version > 0 AND df.version ~= remoteVersion THEN { CWF.WF1["\n(Warning %s: "L, df.shortname]; CWF.WF4["Create date %lt has version !%u, but %s refers to !%u)\n"L, @remoteCreateTime, @remoteVersion, df.recorder, @df.version]; }; df.version ← remoteVersion; nIfsPages ← (remoteByteLength/bytesPerIFSPage) + 2; }; foundWrongVersion => { g.out.PutF["\nError - %s of %t not found.\n", IO.string[targetFileName], IO.card[df.createtime]]; }; notFound => { g.out.PutF["\nError - %s not found.\n", IO.string[targetFileName]]; }; ENDCASE => ERROR; IF df.createtime = 0 AND remoteCreateTime > 0 THEN { -- handles cases where there is a naked entry df.createtime ← remoteCreateTime; df.version ← remoteVersion; }; }; CheckConflicts: PROC[dfseq: DFSubr.DFSeq] RETURNS[nconflicts: CARDINAL] = { df, dflast: DFSubr.DF; nconflicts ← 0; CWF.WF0["Check consistency of DF files.\n"L]; FOR i: CARDINAL IN [1 .. dfseq.size) DO dflast ← @dfseq[i-1]; df ← @dfseq[i]; IF dflast.createtime ~= df.createtime AND dflast.createtime > 0 AND df.createtime > 0 AND LongString.EquivalentString[dflast.shortname, df.shortname] THEN { Flush[]; CWF.WF2["%s: Conflict- %s "L, dflast.shortname, dflast.recorder]; CWF.WF3["has an entry for [%s]<%s>%s"L, dflast.host, dflast.directory, dflast.shortname]; CWF.WF4[" dated %lt,\n\tbut %s has an entry for [%s]<%s>"L, @dflast.createtime, df.recorder, df.host, df.directory]; CWF.WF2["%s dated %lt.\n"L, df.shortname, @df.createtime]; nconflicts ← nconflicts + 1; }; ENDLOOP; IF nconflicts > 0 THEN CWF.WF1["Warning - there were %u conflicts.\n"L, @nconflicts] ELSE CWF.WF0["Success - there were no conflicts among the files.\n"L]; }; -- for phase 3: make up create times for the DF files we will write out SetDFFileCreateTimes: PROC[dfseq: DFSubr.DFSeq, topdffilename: LONG STRING] = { time: LONG CARDINAL ← Time.Current[]; df: DFSubr.DF; FOR i: CARDINAL IN [0..dfseq.size) DO df ← @dfseq[i]; IF df.atsign AND df.releaseDirectory ~= NIL AND NOT df.cameFrom AND NOT df.readonly AND Subr.EndsIn[df.shortname, ".df"L] AND (NOT LongString.EquivalentString[df.shortname, df.recorder] OR LongString.EquivalentString[df.shortname, topdffilename]) THEN { df.createtime ← time; df.version ← 0; time ← time + 1; FOR j: CARDINAL IN [0 .. dfseq.size) DO IF i ~= j AND dfseq[j].shortname.length = df.shortname.length AND LongString.EquivalentString[dfseq[j].shortname, df.shortname] THEN { IF LongString.EquivalentString[dfseq[j].host, df.host] AND LongString.EquivalentString[dfseq[j].directory, df.directory] THEN { dfseq[j].createtime ← df.createtime; dfseq[j].version ← 0; } ELSE CWF.WF4["Warning: %s refers to a version of [%s]<%s>%s that is not being released.\n"L, dfseq[j].recorder, dfseq[j].host, dfseq[j].directory, dfseq[j].shortname]; }; ENDLOOP; }; ENDLOOP; }; NEXCEPTIONS: CARDINAL = 100; EXSeq: TYPE = LONG POINTER TO EXSeqRecord; EXSeqRecord: TYPE = RECORD[ size: CARDINAL ← 0, body: SEQUENCE maxsize: CARDINAL OF RECORD[ host: LONG STRING ← NIL, -- "Ivy" directory: LONG STRING ← NIL, -- "APilot>Pilot>Private" shortname: LONG STRING ← NIL -- "FileName.Mesa" ] ]; ParseExceptionList: PROC[filename: STRING] RETURNS[exseq: EXSeq] = { sh: Stream.Handle; line: STRING ← [100]; host: STRING ← [100]; directory: STRING ← [100]; shortname: STRING ← [100]; longzone: UNCOUNTED ZONE ← Subr.LongZone[]; exseq ← longzone.NEW[EXSeqRecord[NEXCEPTIONS]]; sh ← Subr.NewStream[filename, Subr.Read ! Subr.FileError => { CWF.WF1["Warning - Cannot open %s.\n"L, filename]; GOTO out }]; WHILE Subr.GetLine[sh, line] DO IF line.length = 0 OR Subr.Prefix[line, "//"L] OR Subr.Prefix[line, "--"L] THEN LOOP; [] ← DFSubr.StripLongName[line, host, directory, shortname, FALSE]; IF exseq.size > exseq.maxsize THEN { CWF.WF1["Error - too many exception list entries in %s.\n"L, filename]; EXIT; }; exseq[exseq.size] ← [host: Subr.CopyString[host, longzone], directory: Subr.CopyString[directory, longzone], shortname: IF shortname.length > 0 THEN Subr.CopyString[shortname, longzone] ELSE NIL]; exseq.size ← exseq.size + 1; ENDLOOP; Stream.Delete[sh]; EXITS out => NULL; }; -- dfseq must be sorted CheckExceptions: PROC[dfseq: DFSubr.DFSeq] = { exseq: EXSeq ← NIL; i, j: CARDINAL; dfcur, dfinner: DFSubr.DF; nrelease, ncamefrom: CARDINAL; skip: BOOL; i ← 0; WHILE i < dfseq.size DO IF g.in.UserAbort[] THEN SIGNAL Subr.AbortMyself; dfcur ← @dfseq[i]; nrelease ← ncamefrom ← 0; j ← i; skip ← FALSE; WHILE j < dfseq.size AND LongString.EquivalentString[dfcur.shortname, dfseq[j].shortname] AND LongString.EquivalentString[dfcur.directory, dfseq[j].directory] AND LongString.EquivalentString[dfcur.host, dfseq[j].host] DO dfinner ← @dfseq[j]; IF dfinner.readonly THEN { IF dfinner.releaseDirectory ~= NIL AND NOT dfinner.cameFrom THEN CWF.WF3["Warning- %s in %s is ReadOnly but also has a %s statement.\n"L, dfinner.shortname, dfinner.recorder, IF dfinner.cameFrom THEN "CameFrom"L ELSE "ReleaseAs"L]; }; IF dfinner.releaseDirectory ~= NIL AND NOT dfinner.cameFrom THEN { IF LongString.EquivalentString[dfseq[j].directory, dfseq[j].releaseDirectory] AND LongString.EquivalentString[dfseq[j].host, dfseq[j].releaseHost] THEN CWF.WF2["Warning - %s in %s has directory and release directory that are identical.\n"L, dfseq[j].shortname, dfseq[j].recorder]; nrelease ← nrelease + 1; }; IF NOT dfinner.readonly AND dfinner.releaseDirectory = NIL THEN { Flush[]; CWF.WF2["Error - %s in %s is not ReadOnly and has no release directory.\n"L, dfinner.shortname, dfinner.recorder]; skip ← TRUE; }; IF dfinner.cameFrom THEN ncamefrom ← ncamefrom + 1; j ← j + 1; ENDLOOP; -- if there are no releaseAs statements and no CameFrom statements, -- then this if ReadOnly everywhere -- (Remember that the sort is not stable, so we can't simply look at dfcur) IF nrelease = 0 AND NOT skip AND ncamefrom = 0 THEN { ex: BOOL ← FALSE; IF exseq = NIL THEN { CWF.WF0["Reading exception list From 'Release.ExceptionList'.\n"L]; exseq ← ParseExceptionList["Release.ExceptionList"L]; }; FOR i: CARDINAL IN [0 .. exseq.size) DO IF Subr.Prefix[dfcur.directory, exseq[i].directory] AND LongString.EquivalentString[dfcur.host, exseq[i].host] AND (dfcur.directory.length = exseq[i].directory.length OR (exseq[i].directory.length < dfcur.directory.length AND dfcur.directory[exseq[i].directory.length] = '>)) AND ( exseq[i].shortname = NIL OR LongString.EquivalentString[dfcur.shortname, exseq[i].shortname]) THEN { ex ← TRUE; EXIT; }; ENDLOOP; IF NOT ex THEN { Flush[]; CWF.WF3["Error - there are no ReleaseAs statements for [%s]<%s>%s.\n\t(referenced in"L, dfcur.host, dfcur.directory, dfcur.shortname]; FOR k: CARDINAL IN [i .. j) DO CWF.WF1[" %s"L, dfseq[k].recorder]; ENDLOOP; CWF.WF0[").\n"L]; }; }; i ← j; ENDLOOP; IF exseq ~= NIL THEN FreeExceptionList[@exseq]; }; -- also complains if there is a CameFrom without an explicit date CheckReleaseAs: PROC[dfseq: DFSubr.DFSeq] = { exseq: EXSeq ← ParseExceptionList["Release.Destinations"L]; df: DFSubr.DF; IF exseq = NIL OR exseq.size = 0 THEN { CWF.WF0["No destination list found on 'Release.Destinations'.\n"L]; RETURN; }; FOR i: CARDINAL IN [0 .. dfseq.size) DO df ← @dfseq[i]; IF df.cameFrom THEN { IF df.createtime = 0 THEN CWF.WF3["Warning - %s contains an entry for %s in a CameFrom Directory, but it has no create time for %s.\n"L, df.recorder, df.shortname, df.shortname]; -- look for CameFrom's that are on directories that are not release directories -- this looks suspicious FOR j: CARDINAL IN [0 .. exseq.size) DO IF Subr.Prefix[df.directory, exseq[j].directory] AND LongString.EquivalentString[df.host, exseq[j].host] AND (df.directory.length = exseq[j].directory.length OR (exseq[j].directory.length < df.directory.length AND df.directory[exseq[j].directory.length] = '>)) THEN EXIT; REPEAT FINISHED => CWF.WF4["CameFrom Warning - %s in %s is on [%s]<%s>, but is is a CameFrom and that is not the official release directory.\n"L, df.shortname, df.recorder, df.releaseHost, df.releaseDirectory]; ENDLOOP; }; IF df.releaseDirectory ~= NIL AND NOT df.cameFrom THEN { FOR j: CARDINAL IN [0 .. exseq.size) DO IF Subr.Prefix[df.releaseDirectory, exseq[j].directory] AND LongString.EquivalentString[df.releaseHost, exseq[j].host] AND (df.releaseDirectory.length = exseq[j].directory.length OR (exseq[j].directory.length < df.releaseDirectory.length AND df.releaseDirectory[exseq[j].directory.length] = '>)) THEN EXIT; REPEAT FINISHED => CWF.WF4["Warning - %s in %s is released onto [%s]<%s>, not the official release directory.\n"L, df.shortname, df.recorder, df.releaseHost, df.releaseDirectory]; ENDLOOP; -- special case for >Top> directories IF NOT Subr.EndsIn[df.shortname, ".df"L] AND NOT Subr.EndsIn[df.shortname, ".boot"L] AND Subr.EndsIn[df.releaseDirectory, ">Top"L] THEN CWF.WF2["Warning - %s is released onto %s but is neither a DF file nor a boot file.\n"L, df.shortname, df.releaseDirectory]; }; ENDLOOP; FreeExceptionList[@exseq]; }; -- called after the CheckFiles to look at bcd dates CheckBcdDates: PROC[dfseq: DFSubr.DFSeq] = { f: BOOL ← TRUE; df: DFSubr.DF; FOR i: CARDINAL IN [0 .. dfseq.size) DO df ← @dfseq[i]; IF Subr.EndsIn[df.shortname, ".bcd"L] AND df.createtime < g.oldestBcdDate THEN { IF f THEN { g.out.PutF["The following files were created before the earliest Bcd date (%t):\n", IO.card[g.oldestBcdDate]]; f ← FALSE; }; g.out.PutF["\t%s of %t in %s\n", IO.string[df.shortname], IO.card[df.createtime], IO.string[df.recorder]]; }; ENDLOOP; }; FreeExceptionList: PROC[pexseq: POINTER TO EXSeq] = { FOR i: CARDINAL IN [0 .. pexseq.size) DO Subr.FreeString[pexseq[i].host]; Subr.FreeString[pexseq[i].directory]; Subr.FreeString[pexseq[i].shortname]; ENDLOOP; Subr.LongZone[].FREE[pexseq]; }; -- PrintSeparator: PROC = { CWF.WF0["===============================\n"L]; }; -- size in pages for btree (used to be 100) InitialNumberOfPhase1BTreePages: CARDINAL = 1080; MakeBTree: PROC = { fileHandle: BTreeSupportDefs.FileHandle; g.oldPhase1FileCacheExists ← TRUE; g.bTreeCap ← Directory.Lookup[fileName: "ReleaseTool.Phase1BTreeFile$"L, permissions: Directory.ignore ! Directory.Error => { g.oldPhase1FileCacheExists ← FALSE; CONTINUE; }]; IF NOT g.oldPhase1FileCacheExists THEN g.bTreeCap ← Subr.NewFile["ReleaseTool.Phase1BTreeFile$"L, Subr.ReadWrite, InitialNumberOfPhase1BTreePages]; fileHandle ← BTreeSupportExtraDefs.OpenFile[g.bTreeCap]; g.bTreeHandle ← BTreeDefs.CreateAndInitializeBTree[fileH: fileHandle, initializeFile: NOT g.oldPhase1FileCacheExists, isFirstGreaterOrEqual: IsFirstGEQ, areTheyEqual: AreTheyEQ]; -- IF NOT g.oldPhase1FileCacheExists THEN -- BTreeSupportDefs.SetLength[fileHandle, InitialNumberOfPhase1BTreePages]; }; CleanupBTree: PROC = { IF g.bTreeCap ~= File.nullCapability THEN { BTreeSupportExtraDefs.CloseFile[BTreeDefs.ReleaseBTree[g.bTreeHandle]]; g.bTreeCap ← File.nullCapability; g.oldPhase1FileCacheExists ← FALSE; }; }; MakeBTreeDesc: PROC [s: STRING] RETURNS [d: BTreeDefs.Desc] = INLINE {RETURN[DESCRIPTOR[LOOPHOLE[s, POINTER], (s.length + 1)/2 + 2]]}; IsFirstGEQ: BTreeDefs.TestKeys = BEGIN aS: LONG STRING = LOOPHOLE[BASE[a]]; bS: LONG STRING = LOOPHOLE[BASE[b]]; FOR i: CARDINAL IN [0..MIN[aS.length, bS.length]) DO aC: CHAR = Inline.BITOR[aS[i], 40B]; bC: CHAR = Inline.BITOR[bS[i], 40B]; SELECT aC FROM > bC => RETURN [TRUE]; < bC => RETURN [FALSE]; ENDCASE; ENDLOOP; RETURN [aS.length >= bS.length] END; AreTheyEQ: BTreeDefs.TestKeys = BEGIN aS: LONG STRING = LOOPHOLE[BASE[a]]; bS: LONG STRING = LOOPHOLE[BASE[b]]; IF aS.length ~= bS.length THEN RETURN [FALSE]; FOR i: CARDINAL IN [0..aS.length) DO IF Inline.BITOR[aS[i], 40B] ~= Inline.BITOR[bS[i], 40B] THEN RETURN [FALSE]; ENDLOOP; RETURN [TRUE] END; entryHeight: CARDINAL = 15; entryVSpace: CARDINAL = 10; entryHSpace: CARDINAL = 10; -- this is called by START code BuildOuter: PROC = { menu: Menus.Menu ← Menus.CreateMenu[]; g ← NEW[Global ← []]; g.verbose ← NEW[BOOL ← TRUE]; -- any Bcd before this is assumed to be Cedar 3.2 or earlier [dt: g.oldestBcdDate] ← DateAndTime.Parse["4-Aug-82 8:00:00 PDT"]; g.container ← Containers.Create[info: [name: "ReleaseTool", iconic: FALSE, scrollable: FALSE]]; Menus.InsertMenuEntry[menu, Menus.CreateEntry[" Go", Go]]; Menus.InsertMenuEntry[menu, Menus.CreateEntry["Abort", MyAbort]]; Menus.InsertMenuEntry[menu, Menus.CreateEntry["Reset", MyReset]]; ViewerOps.SetMenu[g.container, menu]; -- this is the set of user buttons BuildUserInput[]; ViewerOps.PaintViewer[g.container, all]; -- this is the typescript part SetupTypescriptPart[]; [g.in, g.out, g.logFile] ← SetUpLogStreams[g.typeScript]; g.tty ← Subr.MakeTTYProcs[g.in, g.out, g.typeScript, MyConfirm]; [] ← CWF.SetWriteProcedure[ToolTTYProc]; destroyEventRegistration ← ViewerEvents.RegisterEventProc[MyDestroy, destroy]; }; BuildUserInput: PROC = { heightSoFar: CARDINAL; l: ViewerClasses.Viewer; CreateButton: PROC[bname, lname: Rope.Text, newLine: BOOL] RETURNS[button: Buttons.Button, label: Labels.Label] = { x: CARDINAL; IF newLine THEN { IF l = NIL THEN heightSoFar ← entryVSpace/2 ELSE heightSoFar ← heightSoFar + entryVSpace + l.wh; x ← 0; } ELSE x ← l.wx + l.ww + entryHSpace; l ← button ← Buttons.Create[info: [name: bname, parent: g.container, border: FALSE, wx: x, wy: heightSoFar], proc: PushButton]; IF lname ~= NIL THEN l ← label ← Labels.Create[info: [name: lname, parent: g.container, wx: button.wx + button.ww + entryHSpace, wy: heightSoFar, border: TRUE]]; }; -- first line [g.phaseButton, g.phaseLabel] ← CreateButton["Phase:", "OneThree", TRUE]; IF g.phase = One THEN Labels.Set[g.phaseLabel, "One"]; [g.useOldPhase1FileButton, g.useOldPhase1FileLabel] ← CreateButton["UseOldPhase1BTree:", "FALSE", FALSE]; IF g.useOldPhase1FileCache THEN Labels.Set[g.useOldPhase1FileLabel, "TRUE"]; [g.useOldPhase3FileButton, g.useOldPhase3FileLabel] ← CreateButton["UseOldPhase3BTree:", "FALSE", FALSE]; IF g.useOldPhase3FileCache THEN Labels.Set[g.useOldPhase3FileLabel, "TRUE"]; [g.updateBTreeButton, g.updateBTreeLabel] ← CreateButton["UpdateBTrees:", "FALSE", FALSE]; IF g.updateBTree THEN Labels.Set[g.updateBTreeLabel, "TRUE"]; -- second line [g.priorityButton, g.priorityLabel] ← CreateButton["Priority:", "Background", TRUE]; IF g.priority = Process.priorityNormal THEN Labels.Set[g.priorityLabel, "Normal"]; [g.verboseButton, g.verboseLabel] ← CreateButton["Verbose:", "FALSE", FALSE]; IF g.verbose↑ THEN Labels.Set[g.verboseLabel, "TRUE"]; [g.fileNameButton,] ← CreateButton["DFFileName:", NIL, FALSE]; l ← g.fileNameViewer ← ViewerTools.MakeNewTextViewer[info: [parent: g.container, wx: l.wx+l.ww+entryHSpace, wy: heightSoFar, ww: 150, wh: entryHeight, data: NIL, scrollable: FALSE, border: FALSE], paint: FALSE]; Containers.ChildXBound[g.container, g.fileNameViewer]; heightSoFar ← heightSoFar+entryVSpace+l.wh; ViewerOps.SetOpenHeight[g.container, heightSoFar]; }; SetupTypescriptPart: PROC = { -- rule: Rules.Rule; -- now the line above the typescript -- rule ← Rules.Create[g.container, 0, heightSoFar, 0, 1]; -- Containers.ChildXBound[g.container, rule]; -- heightSoFar ← heightSoFar+entryVSpace; -- now the typescript -- g.typeScript ← TypeScript.CreateChild[parent: g.container, -- x: 0, y: heightSoFar, w: 0, h: 800, border: FALSE]; 800 due to viewers bug -- Containers.ChildXBound[g.container, g.typeScript]; -- Containers.ChildYBound[g.container, g.typeScript]; -- ViewerOps.SetOpenHeight[g.container, heightSoFar + 200]; -- -- create separate typescript due to lack of Split command in TypeScript.CreateChild g.typeScript ← TypeScript.Create[info: [name: "Release Tool TypeScript", iconic: FALSE]]; }; PushButton: Buttons.ButtonProc = CHECKED { SELECT NARROW[parent, ViewerClasses.Viewer] FROM g.phaseButton => { phaseString: ARRAY EPhase OF Rope.Text ← [ "One", "Two", "Three", "OneThree"]; g.phase ← IF g.phase = OneThree THEN One ELSE SUCC[g.phase]; Labels.Set[g.phaseLabel, phaseString[g.phase]]; }; g.priorityButton => { g.priority ← IF g.priority = Process.priorityNormal THEN Process.priorityBackground ELSE Process.priorityNormal; Labels.Set[g.priorityLabel, IF g.priority = Process.priorityNormal THEN "Normal" ELSE "Background"]; }; g.fileNameButton => { ViewerTools.SetSelection[g.fileNameViewer, NIL]; }; g.verboseButton => { g.verbose↑ ← NOT g.verbose↑; Labels.Set[g.verboseLabel, IF g.verbose↑ THEN "TRUE" ELSE "FALSE"]; }; g.useOldPhase1FileButton => { g.useOldPhase1FileCache ← NOT g.useOldPhase1FileCache; Labels.Set[g.useOldPhase1FileLabel, IF g.useOldPhase1FileCache THEN "TRUE" ELSE "FALSE"]; }; g.useOldPhase3FileButton => { g.useOldPhase3FileCache ← NOT g.useOldPhase3FileCache; Labels.Set[g.useOldPhase3FileLabel, IF g.useOldPhase3FileCache THEN "TRUE" ELSE "FALSE"]; }; g.updateBTreeButton => { g.updateBTree ← NOT g.updateBTree; Labels.Set[g.updateBTreeLabel, IF g.updateBTree THEN "TRUE" ELSE "FALSE"]; }; ENDCASE => ERROR; }; SetUpLogStreams: PROC[ts: TypeScript.TS] RETURNS[in, out, file: IO.Handle] = { -- since the file will be flushed frequently, we use the minimum file ← FileIO.Open[fileName: "ReleaseTool.Log", accessOptions: overwrite, streamBufferParms: FileIO.minimumStreamBufferParms]; [in, out] ← ViewerIO.CreateViewerStreams[viewer: ts, name: "Release Tool TypeScript", editedStream: FALSE]; out ← IO.CreateDribbleStream[out, file]; }; MyConfirm: SAFE PROC[in, out: IO.Handle, data: REF ANY, msg: Rope.ROPE, dch: CHAR] RETURNS[CHAR] = CHECKED { value: ATOM; value ← UserExec.AskUser[msg: msg, viewer: NARROW[data], keyList: LIST[$Yes, $No, $All, $Local, $Quit]]; -- order is important SELECT value FROM $All => RETURN['a]; $Local => RETURN['l]; $No => RETURN['n]; $Quit => RETURN['q]; $Yes => RETURN['y]; ENDCASE => ERROR; }; ToolTTYProc: PROC[ch: CHAR] = { g.out.PutChar[ch]; }; Flush: PROC = { g.out.Flush[]; }; FreeDFSeqAll: PROC = { dfseq: DFSubr.DFSeq ← g.dfseqall; DFSubr.FreeDFSeq[@dfseq]; g.dfseqall ← NIL; }; MyAbort: Menus.MenuProc = CHECKED { g.in.SetUserAbort[]; }; -- just terminates connections, etc. -- does not free memory TemporaryStop: PROC = { CleanupBTree[]; STPSubr.StopSTP[]; LeafSubr.StopLeaf[]; Flush[]; }; NullTTYProc: PROC[ch: CHAR] = {}; -- prints nothing -- cannot print anything in this, monitor is locked MyDestroy: ViewerEvents.EventProc = TRUSTED { p: PROCESS; IF g = NIL OR event ~= destroy OR viewer ~= g.container THEN RETURN; IF g.busy THEN { MessageWindow.Append[message: "ReleaseTool still busy, try later.", clearFirst: TRUE]; MessageWindow.Blink[]; RETURN; }; [] ← CWF.SetWriteProcedure[NullTTYProc]; -- turn off printing g.busy ← TRUE; CWF.WF0["Destroying ReleaseTool.\n"L]; TemporaryStop[]; -- does flush FreeDFSeqAll[]; Subr.SubrStop[]; p ← FORK DestroyTypeScript[g.typeScript]; -- separate process for monitor lock Process.Detach[p]; g.logFile.Close[]; g↑ ← []; g ← NIL; ViewerEvents.UnRegisterEventProc[destroyEventRegistration, destroy]; }; DestroyTypeScript: PROC[ts: TypeScript.TS] = { TypeScript.Destroy[ts]; -- destroys the typescript window }; MyReset: Menus.MenuProc = TRUSTED { ENABLE UNWIND => g.busy ← FALSE; IF g.busy THEN { MessageWindow.Append[message: "ReleaseTool still busy, try later.", clearFirst: TRUE]; MessageWindow.Blink[]; RETURN; }; g.busy ← TRUE; CWF.WF0["Resetting memory... "L]; TemporaryStop[]; FreeDFSeqAll[]; Subr.SubrStop[]; [] ← Subr.SubrInit[256]; CWF.WF0["ReleaseTool memory reset.\n"L]; PrintSeparator[]; g.busy ← FALSE; }; BuildOuter[]; }.