-- 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[];
}.