ReleaseImpl.Mesa
last edit March 16, 1983 6:54 pm
last edit May 23, 1983 9:03 pm, Russ Atkinson (short STRING to LONG STRING)
last edit June 27, 1983 3:20 pm, Doug Wyatt (suppress some CameFrom Warnings)
last edit July 22, 1983 3:06 pm, Doug Wyatt (UserExec.AskUser => IOMisc.AskUser)
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
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, PutChar, PutF, PutRope, ResetUserAbort, SetUserAbort, STREAM, string, UserAbort, UserAborted],
IOMisc: TYPE USING[AskUser],
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, AllocateString, 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],
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, IOMisc, Labels, LeafSubr, LongString, Menus, MessageWindow, Process, ReleaseSupport, RopeInline, STP: UnsafeSTP, STPSubr, Stream, Subr, Time, TypeScript, ViewerEvents, ViewerOps, ViewerIO, ViewerTools
= {
LC: TYPE = LONG CARDINAL; -- try to not overflow poor Pass3
LS: TYPE = LONG STRING; -- try to not overflow poor Pass3
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
number of entries in all df files
MAXALLFILES: CARDINAL = 6000;
entryHeight: CARDINAL = 15;
entryVSpace: CARDINAL = 10;
entryHSpace: CARDINAL = 10;
number of bytes in an IFS page
(all page counts are in IFS pages, not Pilot pages)
bytesPerIFSPage: CARDINAL = 2048;
size in pages for btree (used to be 100)
InitialNumberOfPhase1BTreePages: CARDINAL = 1080;
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.TSNIL,
tty: Subr.TTYProcs,  -- for typeScript
in: STREAMNIL,   -- to the dribble stream
out: STREAMNIL,   -- to the dribble stream
logFile: STREAMNIL,  -- 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: BOOLFALSE,
phase: EPhase ← One,
priority: Process.Priority ← Process.priorityNormal,
checkOverwrite: BOOLFALSE,
verbose: REF BOOLNIL,
nProbablePages: LC ← 0,
nProbableFiles: CARDINAL ← 0,
dfseqall: DFSubr.DFSeq ← NIL,
useOldPhase1FileCache: BOOLTRUE,
useOldPhase3FileCache: BOOLTRUE,
updateBTree: BOOLTRUE,
oldPhase1FileCacheExists: BOOLFALSE,
oldestBcdDate: LC ← 0,
btree data
bTreeCap: File.Capability ← File.nullCapability,
bTreeHandle: BTreeDefs.BTreeHandle ← NULL
];
NEXCEPTIONS: CARDINAL = 100;
BVal: TYPE = RECORD[
version: CARDINAL ← 0,
nIfsPages: CARDINAL ← 0
];
EXSeq: TYPE = LONG POINTER TO EXSeqRecord;
EXSeqRecord: TYPE = RECORD[
size: CARDINAL ← 0,
body: SEQUENCE maxsize: CARDINAL OF EXItem
];
EXItem: TYPE = RECORD[
host: LSNIL, -- "Indigo"
directory: LSNIL, -- "Cedar>Pilot"
shortname: LSNIL-- "FileName.Mesa"
];
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: LS ← Subr.AllocateString[100];
dfFileRef: Rope.Text;
starttime: LC ← Time.Current[];
DoPhase: PROC[phase: EPhase] = {
time: LC ← 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];
};
{ENABLE {
UNWIND => {Subr.FreeString[topdffilename]; 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;
};
};
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.\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;
};
Subr.FreeString[topdffilename];
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;
};
VerifyConsistency: PROC[topdffilename: LS] = {
Phase 1
nconflicts, nmissing, nentries, npages: CARDINAL;
CardOut[g.out, "Remember: max entries in flat DF files = %d\n", MAXALLFILES];
g.out.PutRope["(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: LS] RETURNS [dfseq: DFSubr.DFSeq] = {
time: LC ← 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];
};
VerifySpanningTree: PROC[dfseq: DFSubr.DFSeq, topdffilename: LS] = {
must be sorted by shortname
df, dfj: DFSubr.DF;
natsign, nreleaseas: CARDINAL;
out: STREAM ← FileIO.Open["SpanningTree.List$", overwrite];
out.PutRope["Verify spanning tree rule: each DF file has exactly one 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[];
StrungOut[out, "\nFile %s referenced by:\n", 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;
StrungOut[out, " %s", dfj.recorder];
IF dfj.readonly THEN out.PutRope[" (readonly)"];
out.PutRope[","];
}
ELSE EXIT;
j ← j + 1;
ENDLOOP;
out.PutRope["\n"];
IF natsign = 0 AND NOT LongString.EquivalentString[df.shortname, topdffilename] THEN
StrungOut[out, "\tWarning - no Includes for %s anywhere.\n", df.shortname];
IF nreleaseas ~= 1 THEN {
CardOut[out, "\tError - %d ReleaseAs statements for ", nreleaseas];
StrungOut[out, "%s (not counting self references), exactly one required.\n", df.shortname];
};
ENDLOOP;
out.Close[];
};
CheckForExistence: PROC[dfseq: DFSubr.DFSeq] RETURNS[nmissing, nentries: CARDINAL] = {
invariant: after completion, df.version = 0 means highest version or no such file, otherwise df.version is the correct one
dfseq must be sorted
df: DFSubr.DF;
inCache: BOOL;
nIfsPages, remoteVersion, i: CARDINAL ← 0;
remoteCreateTime: LC ← 0;
fres: FQ.Result;
starttime: LC ← 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;
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];
};
LookupInOldFileCache: PROC
[df: DFSubr.DF] RETURNS[inCache: BOOL, nIfsPages: CARDINAL] = {
specialPrefix is removed
bval: BVal ← [];
len: CARDINAL;
specialPrefix: STRING ← "[Indigo]<PreCedar>"L;
file, sfn: LSNIL;
inCache ← FALSE;
nIfsPages ← 0;
IF df.createtime = 0
OR NOT g.oldPhase1FileCacheExists
OR NOT g.useOldPhase1FileCache THEN RETURN;
file ← Subr.AllocateString[100];
sfn ← Subr.AllocateString[100];
{ENABLE UNWIND => {Subr.FreeString[file]; Subr.FreeString[sfn]};
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]]];
}; -- of ENABLE UNWIND
Subr.FreeString[file]; Subr.FreeString[sfn];
IF len = BTreeDefs.KeyNotFound THEN RETURN;
df.version ← bval.version;
nIfsPages ← bval.nIfsPages;
RETURN[TRUE, nIfsPages];
};
InsertIntoCache: PROC[df: DFSubr.DF, nIfsPages: CARDINAL] = {
specialPrefix is removed
bval: BVal ← [version: df.version, nIfsPages: nIfsPages];
specialPrefix: STRING ← "[Indigo]<PreCedar>"L;
sfn: LS ← Subr.AllocateString[100];
file: LS ← Subr.AllocateString[100];
{ENABLE UNWIND => {Subr.FreeString[file]; Subr.FreeString[sfn]};
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]]];
}; -- of ENABLE UNWIND
Subr.FreeString[file]; Subr.FreeString[sfn];
};
CheckFile: PROC
[df: DFSubr.DF]
RETURNS[fres: FQ.Result, nIfsPages, remoteVersion: CARDINAL, remoteCreateTime: LC] = {
remoteByteLength: LC;
targetFileName: LS ← Subr.AllocateString[100];
{ENABLE UNWIND => {Subr.FreeString[targetFileName]};
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 => {
StrungOut[g.out, "\nError - %s of ", targetFileName];
CardOut[g.out, "%t not found.\n", df.createtime];
};
notFound => {
StrungOut[g.out, "\nError - %s not found.\n", 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;
};
}; -- of ENABLE UNWIND
Subr.FreeString[targetFileName];
};
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];
};
SetDFFileCreateTimes: PROC[dfseq: DFSubr.DFSeq, topdffilename: LS] = {
for phase 3: make up create times for the DF files we will write out
time: LC ← 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;
};
ParseExceptionList: PROC[filename: LS] RETURNS[exseq: EXSeq] = {
sh: Stream.Handle;
line: LS ← Subr.AllocateString[100];
host: LS ← Subr.AllocateString[100];
directory: LS ← Subr.AllocateString[100];
shortname: LS ← Subr.AllocateString[100];
longzone: UNCOUNTED ZONE ← Subr.LongZone[];
Cleanup: PROC = {
Subr.FreeString[line]; Subr.FreeString[host];
Subr.FreeString[directory]; Subr.FreeString[shortname];
};
exseq ← longzone.NEW[EXSeqRecord[NEXCEPTIONS]];
{ENABLE
UNWIND => Cleanup[];
sh ← Subr.NewStream[filename, Subr.Read
! Subr.FileError => {
CWF.WF1["Warning - Cannot open %s.\n"L, filename];
GOTO return
}];
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 return => {};
}; -- of ENABLE UNWIND
Cleanup[];
};
EXPrefix: PROC[dfHost, dfDir, exHost, exDir: LS] RETURNS[BOOL] = {
RETURN[
LongString.EquivalentString[dfHost, exHost]
AND Subr.Prefix[dfDir, exDir]
AND (
dfDir.length=exDir.length
OR (dfDir.length>exDir.length AND dfDir[exDir.length] = '>)
)
];
};
CheckExceptions: PROC[dfseq: DFSubr.DFSeq] = {
dfseq must be sorted
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: BOOLFALSE;
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 EXPrefix[dfcur.host, dfcur.directory, exseq[i].host, exseq[i].directory]
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 - 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];
};
CheckReleaseAs: PROC[dfseq: DFSubr.DFSeq] = {
also complains if there is a CameFrom without an explicit date
destSeq: EXSeq ← ParseExceptionList["Release.Destinations"L];
exceptSeq: EXSeq ← NIL;
IF destSeq = NIL OR destSeq.size = 0 THEN {
CWF.WF0["No destination list found on 'Release.Destinations'.\n"L];
RETURN;
};
FOR i: CARDINAL IN [0 .. dfseq.size) DO
df: DFSubr.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 .. destSeq.size) DO
dest: EXItem = destSeq[j];
IF EXPrefix[df.host, df.directory, dest.host, dest.directory] THEN EXIT;
REPEAT
FINISHED => {
DKW: don't complain about CameFrom's on ExceptionList directories
IF exceptSeq = NIL THEN exceptSeq ← ParseExceptionList["Release.ExceptionList"L];
FOR k: CARDINAL IN [0 .. exceptSeq.size) DO
ex: EXItem = exceptSeq[k];
IF EXPrefix[df.host, df.directory, ex.host, ex.directory] THEN EXIT;
REPEAT
FINISHED => CWF.WF4[
"CameFrom Warning - %s in %s is a CameFrom, but is on [%s]<%s>, not the official release directory.\n"L, df.shortname, df.recorder, df.host, df.directory];
ENDLOOP;
};
ENDLOOP;
};
IF df.releaseDirectory ~= NIL
AND NOT df.cameFrom
THEN {
FOR j: CARDINAL IN [0 .. destSeq.size) DO
dest: EXItem = destSeq[j];
IF EXPrefix[df.releaseHost, df.releaseDirectory, dest.host, dest.directory] 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 not a DF file or a boot file.\n"L,
df.shortname, df.releaseDirectory];
};
ENDLOOP;
FreeExceptionList[@destSeq];
IF exceptSeq#NIL THEN FreeExceptionList[@exceptSeq];
};
CheckBcdDates: PROC[dfseq: DFSubr.DFSeq] = {
called after the CheckFiles to look at bcd dates
f: BOOLTRUE;
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 {
CardOut[
g.out,
"These files were created before the earliest Bcd date (%t):\n",
g.oldestBcdDate];
f ← FALSE;
};
StrungOut[g.out, "\t%s of ", df.shortname];
CardOut[g.out, "%t in", df.createtime];
StrungOut[g.out, "%s\n", 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];
};
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];
};
CleanupBTree: PROC = {
IF g.bTreeCap ~= File.nullCapability THEN {
BTreeSupportExtraDefs.CloseFile[BTreeDefs.ReleaseBTree[g.bTreeHandle]];
g.bTreeCap ← File.nullCapability;
g.oldPhase1FileCacheExists ← FALSE;
};
};
MakeBTreeDesc: PROC [s: LS] RETURNS [d: BTreeDefs.Desc] = {
RETURN[DESCRIPTOR[LOOPHOLE[s, LONG POINTER], (s.length + 1)/2 + 2]]};
IsFirstGEQ: BTreeDefs.TestKeys = {
aS: LS = LOOPHOLE[BASE[a]];
bS: LS = 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]
};
AreTheyEQ: BTreeDefs.TestKeys = {
aS: LS = LOOPHOLE[BASE[a]];
bS: LS = 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]
};
BuildOuter: PROC = {
this is called by START code
menu: Menus.Menu ← Menus.CreateMenu[];
g ← NEW[Global ← []];
g.verbose ← NEW[BOOLTRUE];
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: STREAM] = {
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: STREAM, data: REF ANY, msg: ROPE, dch: CHAR]
RETURNS [CHAR] = CHECKED {
value: ATOM;
value ← IOMisc.AskUser[
msg: msg, in: in, out: out,
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[];
};
StrungOut: PROC [out: STREAM, msg: ROPE, string: LS] = {
this crappy little routine is here to avoid storage overflow in Pass 3 of the compiler
out.PutF[msg, IO.string[string]];
};
CardOut: PROC [out: STREAM, msg: ROPE, card: LC] = {
this crappy little routine is here to avoid storage overflow in Pass 3 of the compiler
out.PutF[msg, IO.card[card]];
};
FreeDFSeqAll: PROC = {
dfseq: DFSubr.DFSeq ← g.dfseqall;
DFSubr.FreeDFSeq[@dfseq];
g.dfseqall ← NIL;
};
MyAbort: Menus.MenuProc = CHECKED {
g.in.SetUserAbort[];
};
TemporaryStop: PROC = {
just terminates connections, etc.; does not free memory
CleanupBTree[];
STPSubr.StopSTP[];
LeafSubr.StopLeaf[];
Flush[];
};
NullTTYProc: PROC[ch: CHAR] = {}; -- prints nothing
MyDestroy: ViewerEvents.EventProc = TRUSTED {
cannot print anything in this, monitor is locked
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[];
}.