-- Release23Impl.Mesa, last edit March 17, 1983 10:16 am
-- Pilot 6.0/ Mesa 7.0
DIRECTORY
BcdOps: TYPE USING[BcdBase],
BTreeDefs: TYPE USING[BTreeHandle, CreateAndInitializeBTree, Desc, Insert,
KeyNotFound, Lookup, ReleaseBTree, TestKeys],
BTreeSupportExtraDefs: TYPE USING[CloseFile, OpenFile],
ConvertUnsafe: TYPE USING[ToRope],
CS: TYPE USING[MakeTS, PTimeStamp],
CWF: TYPE USING [SWF1, SWF2, SWF3, WF0, WF1, WF2, WF3, WF4, WFCR],
DateAndTimeUnsafe: TYPE USING[Parse],
DFSubr: TYPE USING [AllocateDFSeq, DF, DFSeq, FreeDFSeq, LookupDF, NextDF,
ParseStream, StripLongName, WriteOut],
Directory: TYPE USING[Error, Lookup, ignore],
Environment: TYPE USING [bytesPerPage],
File: TYPE USING[Capability, nullCapability],
FileIO: TYPE USING[Open],
FQ: TYPE USING[FileQueryBangH, Result],
Heap: TYPE USING [Error],
Inline: TYPE USING [BITNOT, BITOR, LowHalf],
IO: TYPE USING[card, Close, Handle, Flush, GetLength, PFCodeProc, Put, PutChar,
PutF, PutFR, rope, SetPFCodeProc, SetIndex, string, UserAbort],
LongString: TYPE USING [EquivalentString, StringToDecimal],
ReleaseSupport: TYPE USING [],
Rope: TYPE USING[Length, Lower, Fetch, Flatten, ROPE, Text],
RTBcd: TYPE USING[VersionID],
Space: TYPE USING [Create, Delete, Handle, Kill, LongPointer, Map,
nullHandle, virtualMemory],
UnsafeSTP: TYPE USING [Connect, CreateRemoteStream, DesiredProperties,Destroy, Error,
FileInfo, GetFileInfo, Handle, SetDesiredProperties],
UnsafeSTPOps: TYPE USING [Handle, SetPListItem],
STPSubr: TYPE USING [CachedOpen, HandleSTPError, MakeSTPHandle, StopSTP,
StpStateRecord],
Stream: TYPE USING [Delete, EndOfStream, GetBlock, Handle, PutBlock],
Subr: TYPE USING [AbortMyself, CopyString, CursorInWindow, EndsIn, FileError, FreeString,
GetLine, GetNameandPassword, LongZone, NewFile, NewStream, Prefix, Read,
ReadWrite, strcpy, StripLeadingBlanks, SubStrCopy, TTYProcs],
TimeStamp: TYPE USING[Stamp],
UserTerminal: TYPE USING [cursor, CursorArray, GetCursorPattern, SetCursorPattern],
VerifyDFInterface: TYPE USING [VerifyBcds];
Release23Impl: PROGRAM
IMPORTS BTreeDefs, BTreeSupportExtraDefs, ConvertUnsafe, CS, CWF, DateAndTimeUnsafe,
DFSubr, Directory, FileIO, FQ, Heap,
Inline, IO, LongString, Rope, Space, STP: UnsafeSTP, STPOps: UnsafeSTPOps,
STPSubr, Stream, Subr,
UserTerminal, VerifyDFInterface
EXPORTS ReleaseSupport
SHARES IO = {
-- max number of entries in a single df file
MAXFILES: CARDINAL = 500;
-- in phase 3, remote copy to copy, # pages
-- cannot be larger than 128, since 512*128 = 64k = SIZE[CARDINAL]
NPAGESTOCOPY: CARDINAL = 127;
-- number of bytes in an IFS page
-- (all page counts are in IFS pages, not Pilot pages)
bytesPerIFSPage: CARDINAL = 2048;
DFMapSeqRecord: TYPE = RECORD[
size: CARDINAL ← 0,
zone: UNCOUNTED ZONE ← NULL, -- zone for strings below
body: SEQUENCE maxsize: CARDINAL OF RECORD[
shortname: LONG STRING ← NIL, -- e.g. "Rigging.DF"
lhsHost: LONG STRING ← NIL, -- the released position we are overriding
lhsDirectory: LONG STRING ← NIL,
rhsHost: LONG STRING ← NIL, -- the working posn we want it to refer to
rhsDirectory: LONG STRING ← NIL
]
];
Rw: TYPE = {read, write, none};
Global: TYPE = RECORD[
nDFFilesStored: CARDINAL ← 0, -- the # actually written
nFilesToRelease: CARDINAL ← 0, -- the number that are being ReleaseAs'd
nPagesToRelease: LONG CARDINAL ← 0, -- the number of above
nFilesStored: CARDINAL ← 0, -- the number actually copied this time
nPagesStored: LONG CARDINAL ← 0, -- pages for above
nFilesNotStored: CARDINAL ← 0, -- the number that would have been copied
nPagesNotStored: LONG CARDINAL ← 0, -- pages for above
nFilesSkipped: CARDINAL ← 0, -- number not being released (e.g. CameFrom)
--
copySpace: Space.Handle ← Space.nullHandle,
-- BTree stuff
oldPhase3FileCacheExists: BOOL ← FALSE,
useOldPhase3FileCache: BOOL ← FALSE,
updateBTree: BOOL ← TRUE,
phase3BTreeHandle: BTreeDefs.BTreeHandle ← NULL,
phase3BTreeCap: File.Capability ← File.nullCapability,
dfmap: LONG POINTER TO DFMapSeqRecord ← NIL,
in, out: IO.Handle ← NIL,
verbose: REF BOOL ← NIL,
stp: ARRAY Rw OF STP.Handle ← ALL[NIL],
stpHost: ARRAY Rw OF Rope.Text ← ALL[NIL],
connectName: LONG STRING ← NIL,
connectPassword: LONG STRING ← NIL,
versionMapPrefix: Rope.Text ← NIL,
versionMapFile: IO.Handle ← NIL,
dfseqIndex: ARRAY CHAR['a .. 'z] OF CARDINAL ← ALL[0]
];
-- the btree caches work as follows:
-- if there was one, oldPhase3FileCacheExists is TRUE
-- if the user wants lookups from the btree, useOldPhase3FileCache is TRUE
-- if the user wants insertions into the btree, updateBTree is TRUE
-- lookups only use the btree if both oldPhase3FileCacheExists and
-- useOldPhase3FileCache are true
-- insertions only occur if updateBTree is TRUE
-- MDS usage
g: REF Global ← NEW[Global ← [versionMapPrefix: "[Indigo]<Cedar>"]];
-- endof MDS usage
-- Phase 2
VerifySufficiency: PUBLIC PROC[topdffilename: LONG STRING, h: Subr.TTYProcs,
outhandle: IO.Handle, checkForOverwrite: BOOL] = {
dfseq: DFSubr.DFSeq ← NIL;
sh: Stream.Handle;
df: DFSubr.DF;
{
ENABLE UNWIND => DFSubr.FreeDFSeq[@dfseq];
stpStateRecord: STPSubr.StpStateRecord ← [checkForOverwrite: checkForOverwrite];
g.out ← outhandle;
CWF.WF1["Opening %s.\n"L, topdffilename];
[sh] ← STPSubr.CachedOpen[host: NIL, directory: NIL, shortname: topdffilename, version: 0,
wantcreatetime: 0, h: h, wantExplicitVersion: FALSE,
onlyOne: TRUE, stpState: @stpStateRecord
! Subr.FileError => GOTO notfound];
dfseq ← DFSubr.AllocateDFSeq[maxEntries: MAXFILES, zoneType: shared];
DFSubr.ParseStream[sh, dfseq, topdffilename, NIL, FALSE, FALSE, FALSE, h];
Stream.Delete[sh];
FOR i: CARDINAL IN [0 .. dfseq.size) DO
IF outhandle.UserAbort[] THEN SIGNAL Subr.AbortMyself;
df ← @dfseq[i];
IF df.atsign
AND NOT df.readonly
AND NOT df.cameFrom
THEN
VerifyThisPackage[df.host, df.directory, df.shortname,
df.version, df.createtime, h, checkForOverwrite, df.criterion = none];
ENDLOOP;
STPSubr.StopSTP[]; -- may have timed out
DFSubr.FreeDFSeq[@dfseq];
EXITS
notfound => CWF.WF1["Error - can't open %s.\n"L, topdffilename];
}};
-- this is called once for each of the first level of the tree below the root
-- that is, for each of the Includes of the root DF for the release
-- host and directory may be NIL
VerifyThisPackage: PROC[host, directory, shortname: LONG STRING,
version: CARDINAL, createtime: LONG CARDINAL, h: Subr.TTYProcs,
checkForOverwrite, wantExplicitVersion: BOOL] = {
dfseq: DFSubr.DFSeq ← NIL;
sh: Stream.Handle;
plus, nonLeaf: BOOL ← FALSE;
df: DFSubr.DF;
stpStateRecord: STPSubr.StpStateRecord ← [checkForOverwrite: checkForOverwrite];
{
ENABLE UNWIND => DFSubr.FreeDFSeq[@dfseq];
Flush[];
CWF.WF1["\nOpening %s.\n"L, shortname];
[sh] ← STPSubr.CachedOpen[host: host, directory: directory,
shortname: shortname, version: version,
wantcreatetime: createtime, h: h, wantExplicitVersion: wantExplicitVersion,
onlyOne: TRUE, stpState: @stpStateRecord
! Subr.FileError => GOTO notfound];
dfseq ← DFSubr.AllocateDFSeq[maxEntries: MAXFILES, zoneType: shared];
DFSubr.ParseStream[sh, dfseq, shortname, NIL, FALSE, FALSE, FALSE, h];
Stream.Delete[sh];
FOR i: CARDINAL IN [0 .. dfseq.size) DO
df ← @dfseq[i];
IF df.atsign AND NOT df.readonly AND NOT df.cameFrom THEN
nonLeaf ← TRUE;
ENDLOOP;
-- at this point all nested DF files have been verified
plus ← FALSE;
FOR i: CARDINAL IN [0 .. dfseq.size) DO
IF dfseq[i].topmark THEN plus ← TRUE;
ENDLOOP;
DFSubr.FreeDFSeq[@dfseq];
IF NOT nonLeaf AND NOT plus THEN {
CWF.WF1["No + files in %s, a file that Includes no other DF files.\n"L, shortname];
RETURN;
};
CWF.WF1["VerifyDF of %s started.\n"L, shortname];
Flush[];
VerifyDFInterface.VerifyBcds[bcdfilename: NIL, dffilename: shortname,
h: h, checkForOverwrite: checkForOverwrite, printFlattened: FALSE,
useHugeZone: FALSE, wantRTVersionID: RTBcd.VersionID
! Heap.Error => {
CWF.WF0["Error - Heap.Error!!!!!.\n"L];
CONTINUE;
}
];
CWF.WF1["VerifyDF of %s complete.\n"L, shortname];
STPSubr.StopSTP[]; -- may have timed out
EXITS
notfound => {
IF host ~= NIL THEN CWF.WF3["Error - can't open [%s]<%s>%s\n"L,
host, directory, shortname]
ELSE CWF.WF1["Error - can't open %s.\n"L, shortname];
};
}};
-- Phase 3
-- at this point we know all the files exist and are consistent
-- thus we go ahead and store the files using STP in their
-- new release directories, producing new DF files as we go
-- this is done bottom up-recursive, roughly as SModel does it
--
-- ASSUMES the dfseqall is sorted by shortname
TransferFiles: PUBLIC PROC[topdffilename: LONG STRING, dfseqall: DFSubr.DFSeq,
h: Subr.TTYProcs, inhandle, outhandle, logFileHandle: IO.Handle,
checkForOverwrite, usePhase3BTree, updateBTree: BOOL, verbosePtr: REF BOOL] = {
dfseq: DFSubr.DFSeq ← NIL;
outofspace: BOOL;
df: DFSubr.DF;
connectName: STRING ← [100];
connectPassword: STRING ← [100];
Cleanup: PROC = {
g.out.PutF["Summary for phase 3: (page counts are in 2048 bytes)\n"];
g.out.PutF["\t%d non-DF files being released, %d pages in those files,\n",
IO.card[g.nFilesToRelease], IO.card[g.nPagesToRelease]];
g.out.PutF["\t%d non-DF files actually copied, %d pages in those files,\n",
IO.card[g.nFilesStored], IO.card[g.nPagesStored]];
g.out.PutF["\t%d non-DF files in release position, %d pages in those files,\n",
IO.card[g.nFilesNotStored], IO.card[g.nPagesNotStored]];
g.out.PutF["\t%d DF files written, %d files skipped entirely.\n",
IO.card[g.nDFFilesStored], IO.card[g.nFilesSkipped]];
IF g.copySpace ~= Space.nullHandle THEN Space.Delete[g.copySpace];
g.copySpace ← Space.nullHandle;
Flush[];
[] ← Close[read];
[] ← Close[write];
IF g.versionMapFile ~= NIL THEN g.versionMapFile.Close[];
g.versionMapFile ← NIL;
STPSubr.StopSTP[];
CleanupBTree[];
DFSubr.FreeDFSeq[@dfseq];
};
{
ENABLE UNWIND => Cleanup[];
notFound: BOOL ← FALSE;
g.verbose ← verbosePtr;
g.in ← inhandle;
g.out ← outhandle;
g.updateBTree ← updateBTree;
g.useOldPhase3FileCache ← usePhase3BTree;
IF usePhase3BTree OR updateBTree THEN
MakeBTree[];
g.nPagesStored ← g.nPagesNotStored ← g.nPagesToRelease ← 0;
g.nFilesStored ← g.nFilesNotStored ← g.nFilesToRelease ← g.nFilesSkipped ← g.nDFFilesStored ← 0;
IF dfseqall = NIL THEN {
CWF.WF0["Error - Phase 1 must precede Phase 3 without a Reset in between.\n"L];
RETURN;
};
BuildIndex[dfseqall]; -- build search index used by phase 3
IF g.in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
dfseq ← DFSubr.AllocateDFSeq[maxEntries: 1, zoneType: shared];
df ← DFSubr.NextDF[dfseq];
df.shortname ← Subr.CopyString[topdffilename, dfseq.dfzone];
df.atsign ← TRUE;
Flush[];
CWF.WF0["For Indigo, Enter Probable Connect "L]; -- supply "Cedar", no password
Subr.GetNameandPassword[connect, connectName, connectPassword, h];
g.connectName ← Subr.CopyString[connectName];
g.connectPassword ← Subr.CopyString[connectPassword];
g.out.Put[IO.string["Appending version map file for bcds on 'Release.VersionMapFile$'.\n"L]];
[] ← Directory.Lookup[fileName: "Release.VersionMapFile$"L, permissions: Directory.ignore
! Directory.Error => {
notFound ← TRUE;
CONTINUE;
}];
g.versionMapFile ← FileIO.Open["Release.VersionMapFile$",
IF notFound THEN overwrite ELSE write];
g.versionMapFile.SetPFCodeProc['a, PrintACode];
IF notFound THEN
g.versionMapFile.PutF["%s\n", IO.rope[g.versionMapPrefix]]
ELSE
g.versionMapFile.SetIndex[g.versionMapFile.GetLength[]]; -- sets to end
outofspace ← RecursiveStoreDF[dfseq, NIL, dfseqall, h, checkForOverwrite];
Cleanup[];
}};
-- raised when a connection has timed out
ConnectionClosedError: ERROR[rw: Rw] = CODE;
-- raised when conn. password is needed
ConnectCredentialsError: ERROR[rw: Rw] = CODE;
-- topdfouter may be NIL
RecursiveStoreDF: PROC[dfseqouter: DFSubr.DFSeq, topdfouter: DFSubr.DF,
dfseqall: DFSubr.DFSeq, h: Subr.TTYProcs, checkForOverwrite: BOOL]
RETURNS[outofspace: BOOL] = {
sh: Stream.Handle;
dfouter: DFSubr.DF;
stpStateRecord: STPSubr.StpStateRecord ← [checkForOverwrite: checkForOverwrite];
outofspace ← FALSE;
FOR i: CARDINAL IN [0 .. dfseqouter.size) DO
dfouter ← @dfseqouter[i];
IF dfouter.atsign AND NOT dfouter.readonly
AND (dfouter.releaseDirectory = NIL OR NOT dfouter.cameFrom) THEN {
-- is a non-readonly DF file (may be a CameFrom DF file)
dfseqinner: DFSubr.DFSeq ← NIL;
o: BOOL;
{
ENABLE UNWIND => DFSubr.FreeDFSeq[@dfseqinner];
-- recur on lower DF file
-- once it returns, we know the files are all stored and
-- the release directories have been swapped
-- now this level (parent) can store the DF file
IF g.in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
dfseqinner ← DFSubr.AllocateDFSeq[maxEntries: MAXFILES,
zoneType: shared];
-- this call may get connectionClosed, but will catch it internally
[sh] ← STPSubr.CachedOpen[host: dfouter.host, directory: dfouter.directory,
shortname: dfouter.shortname, version: dfouter.version,
wantcreatetime: dfouter.createtime, h: h,
wantExplicitVersion: dfouter.criterion = none,
onlyOne: TRUE, stpState: @stpStateRecord
! Subr.FileError => GOTO err];
CWF.WF1["Opening %s.\n"L, dfouter.shortname];
IF g.in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
DFSubr.ParseStream[sh, dfseqinner, dfouter.shortname, NIL,
FALSE, FALSE, FALSE, h];
Stream.Delete[sh];
o ← RecursiveStoreDF[dfseqinner, dfouter, dfseqall, h, checkForOverwrite];
SetCreateDateAndVersionFromPhaseOne[dfouter, dfseqall];
outofspace ← outofspace OR o;
IF dfouter.releaseDirectory = NIL AND topdfouter ~= NIL THEN {
CWF.WF2["No release directory for %s in %s.\n"L,
dfouter.shortname, topdfouter.shortname];
DFSubr.FreeDFSeq[@dfseqinner];
LOOP;
};
-- ConnectionClosedError and connect-passwd errors are caught at an inner level
IF NOT outofspace THEN StoreDFFile[dfouter, dfseqinner, h];
DFSubr.FreeDFSeq[@dfseqinner];
Flush[];
EXITS
err => {
IF dfouter.host ~= NIL THEN CWF.WF3["Error - can't open [%s]<%s>%s"L,
dfouter.host, dfouter.directory, dfouter.shortname]
ELSE CWF.WF1["Error - can't open %s.\n"L, dfouter.shortname];
};
};
LOOP;
};
IF dfouter.atsign AND dfouter.readonly AND Subr.EndsIn[dfouter.shortname, ".df"L] THEN
-- only works for ReadOnly entries
-- handle case where special map to working directory is needed
CoerceDFLocToAnother[dfouter, dfseqouter];
IF dfouter.cameFrom AND dfouter.releaseDirectory ~= NIL THEN {
IF g.verbose↑ THEN
CWF.WF4["Leaving [%s]<%s>%s in %s alone.\n"L,
dfouter.host, dfouter.directory, dfouter.shortname, topdfouter.shortname];
g.nFilesSkipped ← g.nFilesSkipped + 1;
Flush[];
}
ELSE { -- is either a readonly df file or not a DF file
SetCreateDateAndVersionFromPhaseOne[dfouter, dfseqall];
IF dfouter.releaseDirectory = NIL THEN {
IF NOT dfouter.readonly THEN
CWF.WF2["Error - no release directory specified for %s in %s.\n"L,
dfouter.shortname, topdfouter.shortname]
ELSE FixupEntriesWithoutReleaseAs[dfouter, dfseqouter, dfseqall];
Flush[];
}
-- skipping self reference to DF file
ELSE IF NOT LongString.EquivalentString[dfouter.shortname,
topdfouter.shortname] THEN {
smashrw: Rw ← none;
-- handles more than one connection
-- and one of the connections times out (connectionClosed)
DO
CopyRemoteFile[dfouter, h
! ConnectionClosedError => {
CWF.WF1["Connection to %s timed out.\n"L, dfouter.host];
smashrw ← rw;
CONTINUE;
};
];
-- have to do this outside STP monitor lock
IF smashrw ~= none THEN
smashrw ← Close[smashrw]
ELSE
EXIT;
ENDLOOP;
Flush[];
}
ELSE {
-- if it is a self reference, then
-- swap directory entries even though it is not correct
SwapSides[dfouter];
};
};
ENDLOOP;
};
-- looks up the create time from the phase 1 data
-- handles ConnectionClosedError and Connect-passwd internally
StoreDFFile: PROC[dfouter: DFSubr.DF, dfseqinner: DFSubr.DFSeq, h: Subr.TTYProcs] = {
sfnnew: STRING ← [100];
dfinner: DFSubr.DF;
smashrw: Rw ← none;
host: LONG STRING ← NIL;
dfouter.version ← 0;
SwapSides[dfouter]; -- beware, this call must be executed exactly once
-- get the entry for the inner DF file
-- this is usually a self entry
dfinner ← DFSubr.LookupDF[dfseqinner, dfouter.shortname];
IF dfinner ~= NIL THEN {
IF dfouter.createtime = 0 THEN
dfouter.createtime ← dfinner.createtime
ELSE dfinner.createtime ← dfouter.createtime;
dfinner.version ← 0;
IF dfouter.host ~= NIL THEN {
Subr.FreeString[dfinner.host, dfseqinner.dfzone];
dfinner.host ← Subr.CopyString[dfouter.host, dfseqinner.dfzone];
Subr.FreeString[dfinner.directory, dfseqinner.dfzone];
dfinner.directory ← Subr.CopyString[dfouter.directory,
dfseqinner.dfzone];
Subr.FreeString[dfinner.releaseHost, dfseqinner.dfzone];
dfinner.releaseHost ← Subr.CopyString[dfouter.releaseHost,
dfseqinner.dfzone];
Subr.FreeString[dfinner.releaseDirectory, dfseqinner.dfzone];
dfinner.releaseDirectory ← Subr.CopyString[dfouter.releaseDirectory,
dfseqinner.dfzone];
dfinner.cameFrom ← dfouter.cameFrom;
}
-- dfinner has already had its sides swapped (SwapSides[])
-- by nested RecursiveStore[]
};
IF dfouter.host ~= NIL THEN {
host ← dfouter.host;
CWF.SWF2[sfnnew, "<%s>%s"L, dfouter.directory, dfouter.shortname]
}
ELSE IF dfinner ~= NIL THEN {
host ← dfinner.host;
CWF.SWF2[sfnnew, "<%s>%s"L, dfinner.directory, dfinner.shortname]
}
ELSE {
CWF.WF1["Error - don't know where to store %s.\n"L, dfouter.shortname];
RETURN;
};
DO
dfouter.version ← ReallyStoreDFFile[host, sfnnew, dfouter.createtime, dfseqinner, h
! ConnectionClosedError => {
CWF.WF1["Connection to %s timed out.\n"L, dfouter.host];
smashrw ← rw;
CONTINUE;
};
ConnectCredentialsError => {
-- does NOT close the connection, simply re-starts
CWF.WFCR[];
LOOP
}
];
-- have to do this outside STP monitor lock
IF smashrw ~= none THEN
smashrw ← Close[smashrw]
ELSE
EXIT;
ENDLOOP;
g.nDFFilesStored ← g.nDFFilesStored + 1;
};
-- may raise ConnectionClosedError or ConnectCredentialsError
ReallyStoreDFFile: PROC[host, filename: LONG STRING, createtime: LONG CARDINAL,
dfseqinner: DFSubr.DFSeq, h: Subr.TTYProcs] RETURNS[version: CARDINAL] = {
sh: Stream.Handle ← NIL;
info: STP.FileInfo;
shortFileName: STRING ← [100];
desiredProperties: STP.DesiredProperties ← ALL[FALSE];
{
-- can't catch UNWIND
ENABLE {
ConnectionClosedError => {
IF sh ~= NIL THEN Stream.Delete[sh];
sh ← NIL;
};
STP.Error => {
IF code = noSuchFile THEN {
CWF.WF2["Error - %s: %s.\n\n"L, filename, error];
ERROR Subr.FileError[notFound];
}
ELSE IF code = connectionClosed THEN {
ERROR ConnectionClosedError[write];
}
ELSE IF code = illegalConnectName
OR code = illegalConnectPassword
OR code = accessDenied THEN {
-- can't just attach RETRY here, must go back
-- to the point where the remote file was opened
[] ← STPSubr.HandleSTPError[g.stp[write], code,
error, h];
ERROR ConnectCredentialsError[write];
};
};
};
Subr.strcpy[shortFileName, filename];
version ← 0;
CWF.WF1["Storing %s "L, filename];
-- STP.Error is caught above
Open[host, write, h];
desiredProperties[directory] ← TRUE;
desiredProperties[nameBody] ← TRUE;
desiredProperties[version] ← TRUE;
STP.SetDesiredProperties[g.stp[write], desiredProperties];
sh ← STP.CreateRemoteStream[stp: g.stp[write], file: shortFileName, access: write,
fileType: text, creation: LOOPHOLE[createtime]
! STP.Error => IF STPSubr.HandleSTPError[g.stp[write], code, error, h] THEN RETRY];
DFSubr.WriteOut[dfseq: dfseqinner, topLevelFile: NIL, outputStream: sh, print: FALSE];
Stream.Delete[sh];
sh ← NIL;
info ← STP.GetFileInfo[g.stp[write]];
version ← LongString.StringToDecimal[info.version];
CWF.WF1["!%s\n"L, info.version];
}};
CopyRemoteFile: PROC[dfouter: DFSubr.DF, h: Subr.TTYProcs] = {
nIfsPages: CARDINAL;
ok, inCache: BOOL;
vers: CARDINAL;
[ok, inCache, vers, nIfsPages] ← AlreadyExistsInCorrectVersion[dfouter.releaseHost,
dfouter.releaseDirectory, dfouter.shortname, dfouter.createtime, h];
IF ok THEN {
IF g.verbose↑ THEN {
CWF.WF4["Correct version of [%s]<%s>%s already stored, %u pages "L,
dfouter.releaseHost, dfouter.releaseDirectory,
dfouter.shortname, @nIfsPages];
CWF.WF1["%s\n"L, IF inCache THEN " (In cache)"L ELSE ""L];
};
SwapSides[dfouter];
dfouter.version ← vers;
g.nFilesNotStored ← g.nFilesNotStored + 1;
g.nPagesNotStored ← g.nPagesNotStored + nIfsPages;
g.nFilesToRelease ← g.nFilesToRelease + 1;
g.nPagesToRelease ← g.nPagesToRelease + nIfsPages;
RETURN;
};
DO
nIfsPages ← CopyRemoteFilesUsingSTP[dfouter, h
! ConnectCredentialsError => {
CWF.WFCR[];
LOOP
}];
EXIT;
ENDLOOP;
g.nFilesStored ← g.nFilesStored + 1;
g.nPagesStored ← g.nPagesStored + nIfsPages;
g.nFilesToRelease ← g.nFilesToRelease + 1;
g.nPagesToRelease ← g.nPagesToRelease + nIfsPages;
};
-- may raise ConnectCredentialsError
CopyRemoteFilesUsingSTP: PROC[dfouter: DFSubr.DF, h: Subr.TTYProcs] RETURNS[nIfsPages: CARDINAL] = {
buffer: LONG POINTER;
nxfer: CARDINAL;
stopit: BOOL ← FALSE;
shin, shout: Stream.Handle ← NIL;
info: STP.FileInfo;
ca: UserTerminal.CursorArray ← ALL[0];
cursorX, cursorY: INTEGER;
flip: BOOL ← FALSE;
sfnold: STRING ← [100];
sfnnew: STRING ← [100];
nbytes: LONG CARDINAL ← 0;
ftp: UserTerminal.CursorArray ← [
177400B, 177400B, 177400B, 177400B,
177400B, 177400B, 177400B, 177400B,
000377B, 000377B, 000377B, 000377B,
000377B, 000377B, 000377B, 000377B];
bcdBase: BcdOps.BcdBase;
versionStamp: TimeStamp.Stamp;
desiredProperties: STP.DesiredProperties ← ALL[FALSE];
-- called on unwind and when exiting normally,
-- but not for ConnectCredentialsError
Cleanup: PROC = {
IF shin ~= NIL THEN Stream.Delete[shin];
shin ← NIL;
IF shout ~= NIL THEN Stream.Delete[shout];
shout ← NIL;
IF flip THEN UserTerminal.SetCursorPattern[ca];
};
nIfsPages ← 0;
IF dfouter.version = 0 THEN
CWF.SWF2[sfnold, "<%s>%s!H"L, dfouter.directory, dfouter.shortname]
ELSE CWF.SWF3[sfnold, "<%s>%s!%u"L, dfouter.directory, dfouter.shortname, @dfouter.version];
CWF.SWF2[sfnnew, "<%s>%s"L, dfouter.releaseDirectory, dfouter.shortname];
IF g.verbose↑ THEN
CWF.WF1["Copy %s"L, sfnold];
Open[dfouter.host, read, h];
desiredProperties ← ALL[FALSE];
desiredProperties[directory] ← TRUE;
desiredProperties[nameBody] ← TRUE;
desiredProperties[version] ← TRUE;
desiredProperties[createDate] ← TRUE;
desiredProperties[size] ← TRUE;
STP.SetDesiredProperties[g.stp[read], desiredProperties];
shin ← STP.CreateRemoteStream[stp: g.stp[read], file: sfnold, access: read
! STP.Error => IF code = noSuchFile THEN {
CWF.WF2["Error - %s: %s.\n\n"L, sfnold, error];
ERROR Subr.FileError[notFound];
}
ELSE IF code = connectionClosed THEN
ERROR ConnectionClosedError[read]
ELSE IF STPSubr.HandleSTPError[g.stp[read], code, error, h] THEN RETRY
];
IF g.verbose↑ THEN CWF.WF0["\n\tto "L];
Open[dfouter.releaseHost, write, h];
desiredProperties ← ALL[FALSE];
desiredProperties[directory] ← TRUE;
desiredProperties[nameBody] ← TRUE;
desiredProperties[version] ← TRUE;
STP.SetDesiredProperties[g.stp[write], desiredProperties];
shout ← STP.CreateRemoteStream[stp: g.stp[write], file: sfnnew, access: write,
fileType: text, creation: LOOPHOLE[dfouter.createtime]
! STP.Error => IF code = noSuchFile THEN {
CWF.WF2["Error - %s: %s.\n\n"L, sfnnew, error];
ERROR Subr.FileError[notFound];
}
ELSE IF code = connectionClosed THEN
ERROR ConnectionClosedError[write]
ELSE IF STPSubr.HandleSTPError[g.stp[write], code, error, h] THEN RETRY
];
CWF.WF1["%s ... "L, sfnnew];
IF g.copySpace = Space.nullHandle THEN {
g.copySpace ← Space.Create[NPAGESTOCOPY, Space.virtualMemory];
Space.Map[g.copySpace];
};
IF Subr.CursorInWindow[h] THEN {
[cursorX, cursorY] ← UserTerminal.cursor↑;
ca ← UserTerminal.GetCursorPattern[];
UserTerminal.SetCursorPattern[ftp];
flip ← TRUE;
};
buffer ← Space.LongPointer[g.copySpace];
bcdBase ← buffer;
{
-- can't catch unwind
ENABLE UNWIND => Cleanup[];
WHILE NOT stopit DO
[bytesTransferred: nxfer] ← Stream.GetBlock[shin,
[buffer, 0, NPAGESTOCOPY*Environment.bytesPerPage]
! STP.Error => IF code = noSuchFile THEN {
CWF.WF1["\n\tError - %s not found.\n"L, sfnold];
GOTO out;
}
ELSE IF code = connectionClosed THEN
ERROR ConnectionClosedError[read];
Stream.EndOfStream => {
stopit ← TRUE;
nxfer ← nextIndex;
CONTINUE
}
];
IF nbytes = 0 THEN {
lstr: STRING ← [100];
info ← STP.GetFileInfo[g.stp[read]];
IF Subr.EndsIn[dfouter.shortname, ".Bcd"L]
OR Subr.EndsIn[dfouter.shortname, ".Symbols"L] THEN
versionStamp ← bcdBase.version
ELSE -- use create time
versionStamp ← [net: 0, host: 0, time: DateAndTimeUnsafe.Parse[info.create].dt];
CWF.SWF1[lstr, "%lu"L, @info.size];
STPOps.SetPListItem[LOOPHOLE[g.stp[write], STPOps.Handle].plist, "Size"L, lstr];
};
Stream.PutBlock[shout, [buffer, 0, nxfer]
! STP.Error =>
IF code = connectionClosed THEN
ERROR ConnectionClosedError[write]
ELSE IF code = illegalConnectName
OR code = illegalConnectPassword
OR code = accessDenied THEN {
-- can't just attach RETRY here, must go back
-- to the point where the remote file was opened
[] ← STPSubr.HandleSTPError[g.stp[write], code,
error, h];
ERROR ConnectCredentialsError[write];
};
];
nbytes ← nbytes + nxfer;
-- only flips if not moved
IF flip AND cursorX = UserTerminal.cursor↑.x
AND cursorY = UserTerminal.cursor↑.y THEN {
-- code from Cursor.Invert
bits: UserTerminal.CursorArray ← UserTerminal.GetCursorPattern[];
FOR i: CARDINAL IN [0..16) DO
bits[i] ← Inline.BITNOT[bits[i]];
ENDLOOP;
UserTerminal.SetCursorPattern[bits];
};
ENDLOOP;
Space.Kill[g.copySpace];
info ← STP.GetFileInfo[g.stp[write]];
IF info.version ~= NIL THEN { -- if there is a version number
dfouter.version ← LongString.StringToDecimal[info.version];
CWF.WF1["!%s"L, info.version];
}
ELSE dfouter.version ← 0;
Cleanup[]; -- up here in case Twinkle runs out of space and the Stream.Delete gens. error
CWF.WF1[", %lu bytes.\n"L, @nbytes];
nIfsPages ← Inline.LowHalf[(nbytes/bytesPerIFSPage)+2];
SwapSides[dfouter];
IF g.updateBTree THEN
InsertIntoCache[dfouter.host, dfouter.directory, dfouter.shortname,
dfouter.version, dfouter.createtime, nIfsPages]; -- record existence
AddToVersionMap[dfouter.host, dfouter.directory, dfouter.shortname,
dfouter.version, versionStamp];
EXITS
out => Cleanup[];
}};
AddToVersionMap: PROC[host, directory, shortname: LONG STRING,
version: CARDINAL, bcdVers: TimeStamp.Stamp] = {
i: INT;
file: Rope.ROPE ← IO.PutFR["[%s]<%s>%s!%d", IO.string[host], IO.string[directory],
IO.string[shortname], IO.card[version]];
IF file.Length[] < g.versionMapPrefix.Length[] THEN {
g.versionMapFile.PutF["%a %s\n", CS.MakeTS[bcdVers], IO.rope[file]];
RETURN;
};
i ← 0;
WHILE i < g.versionMapPrefix.Length[] DO
IF Rope.Lower[file.Fetch[i]] ~= Rope.Lower[g.versionMapPrefix.Fetch[i]] THEN {
g.versionMapFile.PutF["%a %s\n", CS.MakeTS[bcdVers], IO.rope[file]];
RETURN;
};
i ← i + 1;
ENDLOOP;
file ← Rope.Flatten[file, i];
g.versionMapFile.PutF["%a %s\n", CS.MakeTS[bcdVers], IO.rope[file]];
};
AlreadyExistsInCorrectVersion: PROC[host, directory, shortname: LONG STRING,
createtime: LONG CARDINAL, h: Subr.TTYProcs]
RETURNS[foundonremote, inCache: BOOL, remoteVersion, nIfsPages: CARDINAL] = {
fres: FQ.Result;
remoteByteLength: LONG CARDINAL;
foundonremote ← FALSE;
IF g.in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
-- first look in BTree
[inCache, remoteVersion, nIfsPages] ←
LookupInOldFileCache[host, directory, shortname, createtime];
IF inCache THEN RETURN[TRUE, TRUE, remoteVersion, nIfsPages];
-- now look on server
[fres: fres, remoteVersion: remoteVersion, remoteByteLength: remoteByteLength] ←
FQ.FileQueryBangH[host, directory, shortname, createtime, h];
SELECT fres FROM
foundCorrectVersion => { -- found with right create time
foundonremote ← TRUE;
nIfsPages ← (remoteByteLength/bytesPerIFSPage) + 2;
IF g.updateBTree THEN -- record existence in cache
InsertIntoCache[host, directory, shortname, remoteVersion, createtime, nIfsPages];
};
notFound, foundWrongVersion => NULL; -- not found
ENDCASE => ERROR;
};
-- these two procedures, SetCreateDateAndVersionFromPhaseOne
-- and FixupEntriesWithoutReleaseAs are the procedures that search
-- the phase 1 data structure, dfseqall
SetCreateDateAndVersionFromPhaseOne: PROC[df: DFSubr.DF, dfseqall: DFSubr.DFSeq] = {
dfall: DFSubr.DF;
start, stopPlusOne: CARDINAL;
[start, stopPlusOne] ← ObtainStartAndStopIndicesFromDFSeq[df.shortname, dfseqall];
FOR i: CARDINAL IN [start .. stopPlusOne) DO
dfall ← @dfseqall[i];
IF dfall.createtime ~= 0
AND LongString.EquivalentString[dfall.shortname, df.shortname]
AND LongString.EquivalentString[dfall.directory, df.directory]
AND LongString.EquivalentString[dfall.host, df.host]
THEN {
df.createtime ← dfall.createtime;
df.version ← dfall.version;
RETURN;
};
ENDLOOP;
df.version ← 0;
-- this is not an error
g.out.PutF["Warning- can't find create date for %s.\n", IO.string[df.shortname]];
};
-- note that it doesn't check create times
-- called when we don't know where this will be stored
FixupEntriesWithoutReleaseAs: PROC[df: DFSubr.DF, dfseq, dfseqall: DFSubr.DFSeq] = {
dfall: DFSubr.DF;
start, stopPlusOne: CARDINAL;
[start, stopPlusOne] ← ObtainStartAndStopIndicesFromDFSeq[df.shortname, dfseqall];
FOR i: CARDINAL IN [start .. stopPlusOne) DO
dfall ← @dfseqall[i];
IF dfall.releaseDirectory ~= NIL
AND NOT dfall.cameFrom
AND LongString.EquivalentString[dfall.shortname, df.shortname]
AND LongString.EquivalentString[dfall.directory, df.directory]
AND LongString.EquivalentString[dfall.host, df.host]
AND NOT LongString.EquivalentString[dfall.shortname, dfall.recorder] THEN {
df.releaseHost ← Subr.CopyString[dfall.releaseHost, dfseq.dfzone];
df.releaseDirectory ← Subr.CopyString[dfall.releaseDirectory, dfseq.dfzone];
df.createtime ← dfall.createtime;
df.version ← dfall.version;
SwapSides[df];
RETURN;
};
ENDLOOP;
-- not found, leave alone
-- this is not an error
g.out.PutF["Warning- appears %s is not being released.\n", IO.string[df.shortname]];
};
ObtainStartAndStopIndicesFromDFSeq: PROC[shortname: LONG STRING, dfseqall: DFSubr.DFSeq]
RETURNS[start, stopPlusOne: CARDINAL] = {
ch: CHAR ← Rope.Lower[shortname[0]];
IF ch NOT IN ['a .. 'z] THEN {
g.out.PutF["Cant find index for %s\n", IO.string[shortname]];
-- this is a perfect default value to return, is not an error
RETURN[0, dfseqall.size];
};
start ← g.dfseqIndex[ch];
stopPlusOne ← IF ch = 'z THEN dfseqall.size ELSE g.dfseqIndex[ch+1];
IF stopPlusOne = 0 THEN {
g.out.PutF["Cant find upper bound\n"];
stopPlusOne ← dfseqall.size;
};
};
BuildIndex: PROC[dfseqall: DFSubr.DFSeq] = {
dfall: DFSubr.DF;
ch, newch: CHAR ← 'a;
g.dfseqIndex['a] ← 0;
FOR i: CARDINAL IN [0 .. dfseqall.size) DO
dfall ← @dfseqall[i];
newch ← Rope.Lower[dfall.shortname[0]];
IF newch < ch THEN {
g.out.PutF["Warning - Bad sort order for %s\n", IO.string[dfall.shortname]];
LOOP;
};
IF newch > ch THEN {
FOR c: CHAR['a .. 'z] IN (ch .. newch] DO -- in case of gaps
g.dfseqIndex[c] ← i;
ENDLOOP;
ch ← newch;
};
ENDLOOP;
FOR c: CHAR['a .. 'z] IN (ch .. 'z] DO
g.dfseqIndex[c] ← dfseqall.size;
ENDLOOP;
};
SwapSides: PROC[df: DFSubr.DF] = {
s: LONG STRING;
IF df.cameFrom THEN ERROR; -- should never be called if is CameFrom
df.cameFrom ← NOT df.cameFrom;
s ← df.releaseHost;
df.releaseHost ← df.host;
df.host ← s;
s ← df.directory;
df.directory ← df.releaseDirectory;
df.releaseDirectory ← s;
};
BVal: TYPE = RECORD[
version: CARDINAL ← 0,
nIfsPages: CARDINAL ← 0
];
LookupInOldFileCache: PROC[host, directory, shortname: LONG STRING,
createtime: LONG CARDINAL] RETURNS[inCache: BOOL, version, nIfsPages: CARDINAL] = {
bval: BVal ← [];
sfn: STRING ← [100];
len: CARDINAL;
specialPrefix: STRING ← "[Indigo]<Cedar>"L;
file: STRING ← [100];
-- the version # is written onto version
inCache ← FALSE;
version ← 0;
nIfsPages ← 0;
IF createtime = 0
OR NOT g.oldPhase3FileCacheExists
OR NOT g.useOldPhase3FileCache THEN RETURN;
CWF.SWF3[file, "[%s]<%s>%s"L, host, directory, shortname];
IF Subr.Prefix[file, specialPrefix] THEN
Subr.SubStrCopy[file, file, specialPrefix.length];
CWF.SWF2[sfn, "%lu\000%s"L, @createtime, file];
len ← BTreeDefs.Lookup[g.phase3BTreeHandle, MakeBTreeDesc[sfn], DESCRIPTOR[@bval, SIZE[BVal]]];
IF len = BTreeDefs.KeyNotFound THEN RETURN;
RETURN[TRUE, bval.version, bval.nIfsPages];
};
InsertIntoCache: PROC[host, directory, shortname: LONG STRING, version: CARDINAL,
createtime: LONG CARDINAL, nIfsPages: CARDINAL] = {
bval: BVal ← [version: version, nIfsPages: nIfsPages];
sfn: STRING ← [100];
file: STRING ← [100];
specialPrefix: STRING ← "[Indigo]<Cedar>"L;
CWF.SWF3[file, "[%s]<%s>%s"L, host, directory, shortname];
IF Subr.Prefix[file, specialPrefix] THEN
Subr.SubStrCopy[file, file, specialPrefix.length];
CWF.SWF2[sfn, "%lu\000%s"L, @createtime, file];
BTreeDefs.Insert[g.phase3BTreeHandle, MakeBTreeDesc[sfn], DESCRIPTOR[@bval, SIZE[BVal]]];
};
-- size in pages for btree (used to be 100)
InitialNumberOfPhase3BTreePages: CARDINAL = 1000;
MakeBTree: PROC = {
g.oldPhase3FileCacheExists ← TRUE;
g.phase3BTreeCap ← Directory.Lookup[fileName: "ReleaseTool.Phase3BTreeFile$"L, permissions: Directory.ignore
! Directory.Error => {
g.oldPhase3FileCacheExists ← FALSE;
CONTINUE;
}];
IF NOT g.oldPhase3FileCacheExists THEN
g.phase3BTreeCap ← Subr.NewFile["ReleaseTool.Phase3BTreeFile$"L, Subr.ReadWrite,
InitialNumberOfPhase3BTreePages];
g.phase3BTreeHandle ← BTreeDefs.CreateAndInitializeBTree[
fileH: BTreeSupportExtraDefs.OpenFile[g.phase3BTreeCap],
initializeFile: NOT g.oldPhase3FileCacheExists,
isFirstGreaterOrEqual: IsFirstGEQ, areTheyEqual: AreTheyEQ];
};
CleanupBTree: PROC = {
IF g.phase3BTreeCap ~= File.nullCapability THEN {
BTreeSupportExtraDefs.CloseFile[BTreeDefs.ReleaseBTree[g.phase3BTreeHandle]];
g.phase3BTreeCap ← File.nullCapability;
g.oldPhase3FileCacheExists ← 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;
-- (entry must be readonly)
-- this maps lines like
-- Directory [Indigo]<Cedar>Top>
-- to be
-- Directory [Indigo]<PreCedar>Top>
-- for selected DF files. This working directory is then mapped back to the
-- new version on Cedar>Top
-- these are in the file "Release.DFLocations"
-- format:
-- [Indigo]<Cedar>Top>X.df [Indigo]<PreCedar>Top>X.df
--
CoerceDFLocToAnother: PROC[df: DFSubr.DF, dfseq: DFSubr.DFSeq] = {
IF g.dfmap = NIL THEN ReadInAndParseDFMap[];
-- if dfmap.size = 0, then couldn't open Release.DFLocations
IF NOT df.readonly THEN ERROR;
FOR i: CARDINAL IN [0 .. g.dfmap.size) DO
IF LongString.EquivalentString[g.dfmap[i].shortname, df.shortname]
AND LongString.EquivalentString[g.dfmap[i].lhsHost, df.host]
AND LongString.EquivalentString[g.dfmap[i].lhsDirectory, df.directory]
THEN {
CWF.WF3["Mapping reference to [%s]<%s>%s\n"L,
df.host, df.directory, df.shortname];
CWF.WF3["\tinto a reference to [%s]<%s>%s.\n"L,
g.dfmap[i].rhsHost, g.dfmap[i].rhsDirectory, df.shortname];
Subr.FreeString[df.host, dfseq.dfzone];
Subr.FreeString[df.directory, dfseq.dfzone];
df.host ← Subr.CopyString[g.dfmap[i].rhsHost, dfseq.dfzone];
df.directory ← Subr.CopyString[g.dfmap[i].rhsDirectory, dfseq.dfzone];
-- in case a CameFrom is hanging on it
df.cameFrom ← FALSE;
Subr.FreeString[df.releaseHost, dfseq.dfzone]; -- if present
Subr.FreeString[df.releaseDirectory, dfseq.dfzone];
df.releaseHost ← df.releaseDirectory ← NIL;
RETURN;
};
ENDLOOP;
};
NMAPENTRIES: CARDINAL = 100;
ReadInAndParseDFMap: PROC = {
i: CARDINAL;
sh: Stream.Handle;
stemp: STRING ← [100];
line: STRING ← [100];
host: STRING ← [100];
directory: STRING ← [100];
shortname: STRING ← [100];
longzone: UNCOUNTED ZONE ← Subr.LongZone[];
g.dfmap ← longzone.NEW[DFMapSeqRecord[NMAPENTRIES]];
g.dfmap.zone ← longzone;
sh ← Subr.NewStream["Release.DFLocations"L, Subr.Read
! Subr.FileError => {
CWF.WF0["No mapping of locations - Cannot open Release.DFLocations.\n"L];
GOTO out
}];
CWF.WF0["Reading DF mapping from file Release.DFLocations.\n"L];
WHILE Subr.GetLine[sh, line] DO
IF line.length = 0
OR Subr.Prefix[line, "//"L]
OR Subr.Prefix[line, "--"L] THEN LOOP;
IF g.dfmap.size > g.dfmap.maxsize THEN {
CWF.WF0["Error - too many DFLocations.\n"L];
EXIT;
};
i ← 0;
WHILE i < line.length AND line[i] ~= ' AND line[i] ~= '\t DO
i ← i + 1;
ENDLOOP;
IF i >= line.length THEN {
CWF.WF1["Error - this line needs two file names on it: %s.\n"L, line];
EXIT;
};
Subr.strcpy[stemp, line];
stemp.length ← i;
Subr.SubStrCopy[line, line, i];
Subr.StripLeadingBlanks[line];
[] ← DFSubr.StripLongName[stemp, host, directory, shortname, FALSE];
g.dfmap[g.dfmap.size].shortname ← Subr.CopyString[shortname, longzone];
g.dfmap[g.dfmap.size].lhsHost ← Subr.CopyString[host, longzone];
g.dfmap[g.dfmap.size].lhsDirectory ← Subr.CopyString[directory, longzone];
[] ← DFSubr.StripLongName[line, host, directory, shortname, FALSE];
IF NOT LongString.EquivalentString[shortname, g.dfmap[g.dfmap.size].shortname] THEN {
CWF.WF1["Error - line including %s does not have shortnames that match.\n"L, line];
LOOP;
};
g.dfmap[g.dfmap.size].rhsHost ← Subr.CopyString[host, longzone];
g.dfmap[g.dfmap.size].rhsDirectory ← Subr.CopyString[directory, longzone];
g.dfmap.size ← g.dfmap.size + 1;
ENDLOOP;
Stream.Delete[sh];
EXITS
out => NULL;
};
Open: PROC[host: LONG STRING, rw: Rw, h: Subr.TTYProcs] = {
IF g.stp[rw] ~= NIL THEN
STP.SetDesiredProperties[g.stp[rw], ALL[FALSE]];
IF g.stp[rw] = NIL
OR NOT LongString.EquivalentString[LOOPHOLE[g.stpHost[rw]], host] THEN {
IF g.stp[rw] ~= NIL THEN [] ← Close[rw];
g.stp[rw] ← STPSubr.MakeSTPHandle[host, h];
g.stpHost[rw] ← ConvertUnsafe.ToRope[host];
-- only connects if releasing to Indigo, this is a crock
IF rw = write AND g.connectName ~= NIL
AND LongString.EquivalentString[host, "Indigo"L] THEN {
shortConnectName: STRING ← [100];
shortConnectPassword: STRING ← [100];
Subr.strcpy[shortConnectName, g.connectName];
IF g.connectPassword ~= NIL THEN
Subr.strcpy[shortConnectPassword, g.connectPassword];
STP.Connect[g.stp[rw], shortConnectName, shortConnectPassword];
};
};
};
Close: PROC[rw: Rw] RETURNS[alwaysNone: Rw] = {
IF g.stp[rw] ~= NIL THEN {
g.out.PutF["Closing connection to %s\n", IO.rope[g.stpHost[rw]]];
g.stp[rw] ← STP.Destroy[g.stp[rw] ! STP.Error => CONTINUE];
g.stpHost[rw] ← NIL;
};
RETURN[none];
};
Flush: PROC =
{
g.out.Flush[];
};
PrintACode: IO.PFCodeProc = TRUSTED {
WITH v: val SELECT FROM
refAny => {
pts: CS.PTimeStamp ← NARROW[LOOPHOLE[v.value, REF ANY]];
hex: PACKED ARRAY [0 .. 12) OF [0 .. 16) ← LOOPHOLE[pts↑];
FOR i: CARDINAL IN [0 .. 12) DO
IF hex[i] IN [0 .. 9] THEN
stream.PutChar['0 + hex[i]]
ELSE
stream.PutChar['A + (hex[i] - 10)];
ENDLOOP;
};
ENDCASE => ERROR;
};
}.