-- STPSubrImpl.Mesa, last edit March 8, 1983 12:37 pm -- Pilot 6.0/ Mesa 7.0 DIRECTORY CIFS: TYPE USING[Close, create, GetFC, Open, OpenFile, replace, write], ConvertUnsafe: TYPE USING[ToRope], CWF: TYPE USING [SWF1, SWF4, WF0, WF1, WF2, WFCR], DCSFileTypes: TYPE USING [tLeaderPage], Directory: TYPE USING [CreateFile, DeleteFile, Error, Handle, ignore, Lookup, Rename], Environment: TYPE USING [bytesPerPage], File: TYPE USING [Capability, nullCapability, Permissions, read, SetSize], FileStream: TYPE USING [Create, GetLength, SetLeaderPropertiesForCapability], FQ: TYPE USING[FileQuery, Result], Heap: TYPE USING[systemZone], Inline: TYPE USING [BITNOT, LowHalf], IO: TYPE USING[card, PutF, string, UserAbort], LongString: TYPE USING [EquivalentString], Process: TYPE USING [Pause, SecondsToTicks], Space: TYPE USING [Create, Delete, Handle, LongPointer, Map, nullHandle, virtualMemory], UnsafeSTP: TYPE USING [Connect, Create, CreateRemoteStream, DesiredProperties, Destroy, Error, ErrorCode, FileInfo, GetFileInfo, Handle, Login, NextFileName, Open, SetDesiredProperties, Type], UnsafeSTPOps: TYPE USING [FindFileType, Handle, SetPListItem], STPSubr: TYPE USING [RetrieveProcType, StpState], Stream: TYPE USING [Delete, EndOfStream, GetBlock, Handle, PutBlock], String: TYPE USING [AppendChar], Subr: TYPE USING [AbortMyself, Any, CopyString, CursorInWindow, EndsIn, errorflg, FileError, FreeString, GetCreateDate, GetNameandPassword, LongZone, NewStream, Prefix, Read, SetRemoteFilenameProp, strcpy, SubStrCopy, TTYProcs, Write], UserCredentialsUnsafe: TYPE USING[GetUserCredentials], UserTerminal: TYPE USING [cursor, CursorArray, GetCursorPattern, SetCursorPattern]; STPSubrImpl: PROGRAM IMPORTS CIFS, ConvertUnsafe, CWF, Directory, File, FileStream, FQ, Heap, Inline, IO, LongString, Process, Space, STP: UnsafeSTP, STPOps: UnsafeSTPOps, Stream, String, Subr, UserCredentialsUnsafe, UserTerminal EXPORTS STPSubr SHARES File = { -- MDS USAGE !!! useCIFS: BOOL ← TRUE; connseq: LONG POINTER TO ConnSeqRecord ← NIL; maxNumberOfTries: CARDINAL ← 10; -- # tries to get connected if rejecting -- endof MDS USAGE !!! -- connection table MAXCONNS: CARDINAL = 8; -- we use the username and password from Profile.userName, userPassword ConnSeqRecord: TYPE = RECORD[ size: CARDINAL ← 0, -- # of connections body: SEQUENCE maxsize: CARDINAL OF ConnTable ]; ConnTable: TYPE = RECORD[ stphandle: STP.Handle ← NIL, host: LONG STRING ← NIL ]; SetUseOfCIFS: PUBLIC PROC[shouldUseCIFS: BOOL] = { useCIFS ← shouldUseCIFS; -- in a procedure to make sure the module is started }; EnumerateForRetrieve: PUBLIC PROC[host, filePattern: LONG STRING, enumProc: STPSubr.RetrieveProcType, h: Subr.TTYProcs, onlyOne: BOOL] ={ stp: STP.Handle; remoteStream: Stream.Handle ← NIL; remoteName: LONG STRING ← NIL; skipRest, tryAgain: BOOL; shortFilePattern: STRING ← [100]; desiredProperties: STP.DesiredProperties ← ALL[FALSE]; Cleanup: PROC = { IF remoteName ~= NIL THEN Heap.systemZone.FREE[@remoteName]; IF remoteStream ~= NIL THEN Stream.Delete[remoteStream]; remoteStream ← NIL; }; -- nested procedure needed to handle retrys of entire enumerates TryIt: PROC = { skipRest ← FALSE; remoteName ← NIL; remoteStream ← STP.CreateRemoteStream[stp, shortFilePattern, read ! STP.Error => IF HandleSTPError[stp, code, error, h] THEN RETRY ]; DO ENABLE UNWIND => Cleanup[]; IF remoteName ~= NIL THEN Heap.systemZone.FREE[@remoteName]; -- may generate STP.Error! remoteName ← STP.NextFileName[remoteStream]; IF remoteName = NIL THEN EXIT; IF skipRest THEN LOOP; skipRest ← enumProc[remoteName, stp, remoteStream]; ENDLOOP; Cleanup[]; }; Subr.strcpy[shortFilePattern, filePattern]; desiredProperties[directory] ← TRUE; desiredProperties[nameBody] ← TRUE; desiredProperties[version] ← TRUE; desiredProperties[createDate] ← TRUE; desiredProperties[size] ← TRUE; DO tryAgain ← FALSE; stp ← Connect[host: host, h: h, onlyOne: onlyOne]; STP.SetDesiredProperties[stp, desiredProperties]; TryIt[ ! STP.Error => IF HandleSTPError[stp, code, error, h] THEN { tryAgain ← TRUE; CONTINUE; } ELSE IF code = connectionClosed THEN { CWF.WF1["Connection to %s timed out.\n"L, host]; tryAgain ← TRUE; CONTINUE; }; ]; IF NOT tryAgain THEN EXIT; stp ← ForceClosed[stp]; IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself; ENDLOOP; }; -- if not found, raises Subr.FileError, for both remote and local files -- filename looks like [host]<dir>name GeneralOpen: PUBLIC PROC[filename: LONG STRING, h: Subr.TTYProcs, access: File.Permissions, fileType: STP.Type, -- FileType -- createtime: LONG CARDINAL] RETURNS[sh: Stream.Handle, stphandle: STP.Handle] = { host: STRING ← [30]; sfn: STRING ← [100]; tryAgain: BOOL; desiredProperties: STP.DesiredProperties ← ALL[FALSE]; sh ← NIL; -- local case IF filename[0] ~= '[ AND filename[0] ~= '< THEN { sh ← Subr.NewStream[filename, access]; RETURN[sh, NIL]; }; -- remote file name Subr.strcpy[sfn, filename]; StripHost[host, sfn]; StartSTP[]; DO tryAgain ← FALSE; stphandle ← Connect[host, h, FALSE]; desiredProperties[directory] ← TRUE; desiredProperties[nameBody] ← TRUE; desiredProperties[version] ← TRUE; desiredProperties[createDate] ← TRUE; desiredProperties[size] ← TRUE; -- CWF.WF1["%s ... "L, filename]; STP.SetDesiredProperties[stphandle, desiredProperties]; sh ← STP.CreateRemoteStream[stp: stphandle, file: sfn, access: IF access = Subr.Read THEN read ELSE write, fileType: IF access = Subr.Read THEN unknown ELSE (IF fileType = unknown THEN GetFileType[sfn] ELSE fileType), creation: LOOPHOLE[createtime] ! 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 { CWF.WF1["Connection to %s timed out.\n"L, host]; tryAgain ← TRUE; CONTINUE; } ELSE IF HandleSTPError[stphandle,code,error,h] THEN RETRY]; IF NOT tryAgain THEN EXIT; stphandle ← ForceClosed[stphandle]; IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself; ENDLOOP; }; -- just like GeneralOpen but the filename can be a pattern, e.g. with * in it PatternGeneralOpen: PUBLIC PROC[filepattern: STRING, proc: STPSubr.RetrieveProcType, h: Subr.TTYProcs] = { host: STRING ← [30]; sfn: STRING ← [100]; -- local case IF filepattern[0] ~= '[ AND filepattern[0] ~= '< THEN { sh: Stream.Handle; sh ← Subr.NewStream[filepattern, Subr.Read ! Subr.FileError => GOTO err]; [] ← proc[filepattern, NIL, sh]; Stream.Delete[sh]; RETURN; }; Subr.strcpy[sfn, filepattern]; StripHost[host, sfn]; EnumerateForRetrieve[host, sfn, proc, h, TRUE ! STP.Error => IF code = noSuchFile THEN { CWF.WF2["Error - %s: %s.\n\n"L, filepattern, error]; GOTO err; } ]; EXITS err => Subr.errorflg ← TRUE; }; -- may raise Subr.FileError CachedOpen: PUBLIC PROC[host, directory, shortname: LONG STRING, version: CARDINAL, wantcreatetime: LONG CARDINAL, h: Subr.TTYProcs, stpState: STPSubr.StpState, wantExplicitVersion, onlyOne, tryDollars: BOOL] RETURNS[sh: Stream.Handle, cap: File.Capability] = { cap ← CachedRetrieve[host, directory, shortname, version, wantcreatetime, h, stpState, wantExplicitVersion, onlyOne, tryDollars]; sh ← NIL; IF cap ~= File.nullCapability THEN sh ← FileStream.Create[[cap.fID, File.read]] ELSE -- this case can only happen if the user said no to the retrieval -- in which case the existing file is used sh ← Subr.NewStream[shortname, Subr.Read]; RETURN[sh, cap]; }; CachedRetrieve: PUBLIC PROC[host, directory, shortname: LONG STRING, version: CARDINAL, wantcreatetime: LONG CARDINAL, h: Subr.TTYProcs, stpState: STPSubr.StpState, wantExplicitVersion, onlyOne, tryDollars: BOOL] RETURNS[cap: File.Capability] = { localOnly: BOOL ← host = NIL OR host.length = 0; localcreatetime: LONG CARDINAL ← 0; fileNotRetrieved: BOOL ← FALSE; cap ← File.nullCapability; { cap ← Directory.Lookup[fileName: shortname, permissions: Directory.ignore ! Directory.Error => GOTO notonlocal]; localcreatetime ← Subr.GetCreateDate[cap]; IF localcreatetime = wantcreatetime OR (wantcreatetime = 0 AND localOnly) THEN RETURN[cap]; EXITS notonlocal => NULL; }; -- try with $$ on end IF tryDollars THEN { sDollar: STRING ← [100]; CWF.SWF1[sDollar, "%s$$"L, shortname]; cap ← Directory.Lookup[fileName: sDollar, permissions: Directory.ignore ! Directory.Error => GOTO notonlocaldollar]; localcreatetime ← Subr.GetCreateDate[cap]; IF localcreatetime = wantcreatetime THEN RETURN[cap]; EXITS notonlocaldollar => NULL; }; -- look on remote server IF NOT localOnly THEN { targetFileName: STRING ← [125]; remoteVersion: CARDINAL; remoteCreateTime: LONG CARDINAL; OneFile: STPSubr.RetrieveProcType = { info: STP.FileInfo ← STP.GetFileInfo[stp]; localname: STRING ← [100]; skipRest ← TRUE; IF tryDollars AND localcreatetime ~= 0 THEN CWF.SWF1[localname, "%s$$"L, shortname] ELSE { IF stpState.checkForOverwrite AND localcreatetime ~= 0 THEN SELECT CheckForOverwrite[shortname, localcreatetime, targetFileName, remoteCreateTime, h] FROM no => { fileNotRetrieved ← TRUE; RETURN; }; substitute => { cap ← Directory.Lookup[fileName: shortname, permissions: Directory.ignore ! Directory.Error => CONTINUE]; -- the file on the local disk is used RETURN; }; yes => NULL; all => stpState.checkForOverwrite ← FALSE; ENDCASE => ERROR; Subr.strcpy[localname, shortname]; }; CWF.WF1["Retrieving %s "L, targetFileName]; IF tryDollars AND localcreatetime ~= 0 THEN CWF.WF1["(as %s)"L, localname] ELSE IF remoteCreateTime < localcreatetime THEN { dollar: STRING ← [100]; CWF.SWF1[dollar, "%s$$"L, shortname]; Directory.DeleteFile[dollar ! Directory.Error => CONTINUE]; Directory.Rename[oldName: shortname, newName: dollar]; CWF.WF1["(local version renamed to %s)"L, dollar]; }; CWF.WF0[" ... "L]; [cap,] ← WriteStreamToDisk[remoteStream, localname, info.size, h]; Subr.SetRemoteFilenameProp[cap, targetFileName]; FileStream.SetLeaderPropertiesForCapability[cap: cap, create: LOOPHOLE[remoteCreateTime]]; CWF.WF1["%lu bytes.\n"L, @info.size]; RETURN; }; { fres: FQ.Result; [fres: fres, remoteVersion: remoteVersion, remoteCreateTime: remoteCreateTime] ← FQ.FileQuery[host, directory, shortname, version, wantcreatetime, wantExplicitVersion, h, targetFileName]; SELECT fres FROM foundCorrectVersion => { vstring: STRING; sfn: STRING ← [100]; IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself; IF localcreatetime = remoteCreateTime THEN RETURN; -- already on disk vstring ← IF Subr.Prefix[host, "maxc"L] THEN ";"L ELSE "!"L; CWF.SWF4[sfn, "<%s>%s%s%u"L, directory, shortname, vstring, @remoteVersion]; EnumerateForRetrieve[host, sfn, OneFile, h, onlyOne]; IF fileNotRetrieved THEN cap ← File.nullCapability; }; foundWrongVersion => ERROR Subr.FileError[wrongVersion]; notFound => ERROR Subr.FileError[notFound]; ENDCASE => ERROR; }}; }; CheckForOverwrite: PROC[localname: LONG STRING, localcreate: LONG CARDINAL, remotename: LONG STRING, remotecreate: LONG CARDINAL, h: Subr.TTYProcs] RETURNS[answer: {all, yes, no, substitute}] = { ch: CHAR; h.out.PutF["Ok to retrieve %s, dated %t,\n\tand overwrite %s of %t ", IO.string[remotename], IO.card[remotecreate], IO.string[localname], IO.card[localcreate]]; ch ← h.Confirm[h.in, h.out, h.data, "", 'y]; IF ch = 'q THEN SIGNAL Subr.AbortMyself ELSE IF ch = 'y THEN answer ← yes ELSE IF ch = 'l THEN answer ← substitute ELSE IF ch = 'a THEN answer ← all ELSE answer ← no; }; StartSTP: PROC = { longzone: UNCOUNTED ZONE; IF connseq ~= NIL THEN RETURN; longzone ← Subr.LongZone[]; connseq ← longzone.NEW[ConnSeqRecord[MAXCONNS]]; }; StopSTP: PUBLIC PROC = { ENABLE UNWIND => connseq ← NIL; longzone: UNCOUNTED ZONE; IF connseq = NIL THEN RETURN; longzone ← Subr.LongZone[]; FOR i: CARDINAL IN [0..connseq.size) DO IF connseq[i].stphandle ~= NIL THEN CloseAndFree[i]; ENDLOOP; longzone.FREE[@connseq]; }; Connect: PUBLIC PROC[host: LONG STRING, h: Subr.TTYProcs, onlyOne: BOOL] RETURNS[stphandle: STP.Handle] = { free: CARDINAL; StartSTP[]; IF onlyOne THEN { IF connseq.size > 0 AND connseq[0].host ~= NIL AND NOT LongString.EquivalentString[host, connseq[0].host] THEN { CloseAndFree[0]; IF connseq.size = 1 THEN connseq.size ← 0; }; }; free ← connseq.size; { FOR i: CARDINAL IN [0..connseq.size) DO IF connseq[i].host ~= NIL AND connseq[i].stphandle ~= NIL AND LongString.EquivalentString[host,connseq[i].host] AND LOOPHOLE[connseq[i].stphandle, STPOps.Handle].remoteStream = NIL THEN { free ← i; GOTO leave; }; IF connseq[i].host = NIL THEN free ← i; ENDLOOP; IF free = connseq.size THEN { IF connseq.size >= connseq.maxsize THEN { CWF.WF0["Error - to many conns\n"L]; RETURN[NIL]; }; connseq.size ← connseq.size + 1; }; connseq[free] ← [NIL, NIL]; -- in case MakeSTPHandle signals connseq[free].stphandle ← MakeSTPHandle[host, h]; connseq[free].host ← Subr.CopyString[host]; EXITS leave => NULL; }; STP.SetDesiredProperties[connseq[free].stphandle, ALL[FALSE]]; RETURN[connseq[free].stphandle]; }; ForceClosed: PUBLIC PROC[stphandle: STP.Handle] RETURNS[STP.Handle] = { FOR i: CARDINAL IN [0 .. connseq.size) DO IF stphandle = connseq[i].stphandle THEN CloseAndFree[i]; ENDLOOP; RETURN[NIL]; }; CloseAndFree: PROC[i: CARDINAL] = { CWF.WF1["Closing connection to %s ... "L, connseq[i].host]; connseq[i].stphandle ← STP.Destroy[connseq[i].stphandle ! STP.Error => CONTINUE]; CWF.WF0["closed.\n"L]; Subr.FreeString[connseq[i].host]; connseq[i].host ← NIL; }; CheckStarted: PROC = { IF connseq = NIL THEN ERROR; }; -- the procedures below can be called without using the Connect[] stphandles NPAGEBUFFER: CARDINAL = 6; -- may raise CIFS.Error[fileBusy] WriteStreamToDisk: PUBLIC PROC[remotesh: Stream.Handle, localfilename: LONG STRING, byteLengthHint: LONG CARDINAL, h: Subr.TTYProcs] RETURNS[cap: File.Capability, nbytes: LONG CARDINAL] = { nxfer, npages: CARDINAL; stopit, flip: BOOL ← FALSE; space: Space.Handle ← Space.nullHandle; localsh: Stream.Handle ← NIL; buffer: LONG POINTER ← NIL; ca: UserTerminal.CursorArray ← ALL[0]; cursorX, cursorY: INTEGER; openFile: CIFS.OpenFile; ftp: UserTerminal.CursorArray ← [ 177400B, 177400B, 177400B, 177400B, 177400B, 177400B, 177400B, 177400B, 000377B, 000377B, 000377B, 000377B, 000377B, 000377B, 000377B, 000377B]; Cleanup: PROC = { IF space ~= Space.nullHandle THEN Space.Delete[space]; space ← Space.nullHandle; IF localsh ~= NIL THEN Stream.Delete[localsh]; localsh ← NIL; IF flip THEN UserTerminal.SetCursorPattern[ca]; IF useCIFS AND openFile ~= NIL THEN CIFS.Close[openFile]; openFile ← NIL; }; npages ← Inline.LowHalf[byteLengthHint/Environment.bytesPerPage] + 2; nbytes ← 0; cap ← File.nullCapability; IF useCIFS THEN { openFile ← CIFS.Open[ConvertUnsafe.ToRope[localfilename], CIFS.replace+CIFS.create+CIFS.write]; cap ← CIFS.GetFC[openFile]; } ELSE { cap ← Directory.CreateFile[localfilename, DCSFileTypes.tLeaderPage, npages ! Directory.Error => { IF type = fileAlreadyExists THEN { cap ← Directory.Lookup[fileName: localfilename, permissions: Directory.ignore]; IF npages > 2 THEN File.SetSize[cap, npages]; } ELSE ERROR Subr.FileError[notFound]; CONTINUE }]; }; localsh ← FileStream.Create[[cap.fID, Subr.Write]]; space ← Space.Create[NPAGEBUFFER, Space.virtualMemory]; Space.Map[space]; buffer ← Space.LongPointer[space]; IF Subr.CursorInWindow[h] THEN { [cursorX, cursorY] ← UserTerminal.cursor↑; ca ← UserTerminal.GetCursorPattern[]; UserTerminal.SetCursorPattern[ftp]; flip ← TRUE; }; WHILE NOT stopit DO ENABLE UNWIND => Cleanup[]; [bytesTransferred: nxfer] ← Stream.GetBlock[remotesh, [buffer, 0, NPAGEBUFFER*Environment.bytesPerPage] ! STP.Error => IF code = noSuchFile THEN { CWF.WF1["\n\tError - %s not found.\n"L, localfilename]; EXIT; }; Stream.EndOfStream => { stopit ← TRUE; nxfer ← nextIndex; CONTINUE } ]; IF nxfer = 0 THEN EXIT; Stream.PutBlock[localsh, [buffer, 0, nxfer]]; 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; Cleanup[]; }; Store: PUBLIC PROC[stphandle: STP.Handle, remoteName: LONG STRING, localCap: File.Capability, createDate: LONG CARDINAL, h: Subr.TTYProcs] RETURNS[nbytes: LONG CARDINAL] = { ft: STP.Type; remName: STRING ← [100]; localStream, remoteStream: Stream.Handle ← NIL; len: LONG CARDINAL; lstr: STRING ← [30]; Cleanup: PROC = { IF localStream ~= NIL THEN Stream.Delete[localStream]; localStream ← NIL; IF remoteStream ~= NIL THEN Stream.Delete[remoteStream]; remoteStream ← NIL; }; { ENABLE UNWIND => Cleanup[]; desiredProperties: STP.DesiredProperties ← ALL[FALSE]; nbytes ← 0; Subr.strcpy[remName, remoteName]; ft ← GetFileType[remoteName]; localStream ← FileStream.Create[[localCap.fID, Subr.Read]]; -- this will read the entire local file to check for 8-th bit IF ft = unknown THEN ft ← STPOps.FindFileType[localStream]; desiredProperties[directory] ← TRUE; desiredProperties[nameBody] ← TRUE; desiredProperties[version] ← TRUE; STP.SetDesiredProperties[stphandle, desiredProperties]; remoteStream ← STP.CreateRemoteStream[stp: stphandle, file: remName, access: write, fileType: ft, creation: LOOPHOLE[createDate] ! STP.Error => IF HandleSTPError[stphandle, code, error, h] THEN RETRY]; len ← FileStream.GetLength[localStream]; CWF.SWF1[lstr, "%lu"L, @len]; STPOps.SetPListItem[LOOPHOLE[stphandle, STPOps.Handle].plist, "Size"L, lstr]; nbytes ← WriteDiskToStream[localStream, remoteStream, h]; }; Cleanup[]; }; WriteDiskToStream: PROC[localsh, remotesh: Stream.Handle, h: Subr.TTYProcs] RETURNS[nbytes: LONG CARDINAL] = { nxfer: CARDINAL; stopit, flip: BOOL ← FALSE; space: Space.Handle ← Space.nullHandle; buffer: LONG POINTER ← NIL; cursorX, cursorY: INTEGER; ca: UserTerminal.CursorArray ← ALL[0]; ftp: UserTerminal.CursorArray ← [ 177400B, 177400B, 177400B, 177400B, 177400B, 177400B, 177400B, 177400B, 000377B, 000377B, 000377B, 000377B, 000377B, 000377B, 000377B, 000377B]; Cleanup: PROC = { IF space ~= Space.nullHandle THEN Space.Delete[space]; space ← Space.nullHandle; IF flip THEN UserTerminal.SetCursorPattern[ca]; }; nbytes ← 0; space ← Space.Create[NPAGEBUFFER, Space.virtualMemory]; Space.Map[space]; buffer ← Space.LongPointer[space]; IF Subr.CursorInWindow[h] THEN { [cursorX, cursorY] ← UserTerminal.cursor↑; ca ← UserTerminal.GetCursorPattern[]; UserTerminal.SetCursorPattern[ftp]; flip ← TRUE; }; WHILE NOT stopit DO ENABLE UNWIND => Cleanup[]; [bytesTransferred: nxfer] ← Stream.GetBlock[localsh, [buffer, 0, NPAGEBUFFER*Environment.bytesPerPage] ! Stream.EndOfStream => { stopit ← TRUE; nxfer ← nextIndex; CONTINUE } ]; IF nxfer = 0 THEN EXIT; Stream.PutBlock[remotesh, [buffer, 0, nxfer]]; nbytes ← nbytes + nxfer; 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; Cleanup[]; }; -- takes sfn, strips off the host, puts the host in "host" -- leaves the remainder in sfn StripHost: PROC[host, sfn: STRING] = { IF sfn[0] ~= '[ THEN { Subr.strcpy[host, "Ivy"L]; RETURN; }; host.length ← 0; FOR i: CARDINAL IN [1..sfn.length) DO IF sfn[i] = '] THEN { FOR j: CARDINAL IN [1 .. i-1] DO String.AppendChar[host, sfn[j]]; ENDLOOP; Subr.SubStrCopy[sfn, sfn, i+1]; RETURN; }; ENDLOOP; CWF.WF1["Error - %s is not a valid remote file name.\n"L, sfn]; }; NSECPAUSE: CARDINAL = 10; -- # seconds to wait SetNumberOfConnectTries: PUBLIC PROC[nTries: CARDINAL] = { maxNumberOfTries ← nTries; }; MakeSTPHandle: PUBLIC PROC[host: LONG STRING, h: Subr.TTYProcs] RETURNS[stphandle: STP.Handle] = { herald: LONG STRING; shorthost: STRING ← [30]; user: STRING ← [40]; password: STRING ← [40]; ntries: CARDINAL ← 0; IF host = NIL OR host.length = 0 THEN { CWF.WF0["Error: remote host has not been specified.\n"L]; RETURN[NIL]; }; stphandle ← STP.Create[]; CWF.WF1["Opening connection to %s.\n"L, host]; Subr.strcpy[shorthost, host]; herald ← STP.Open[stphandle, shorthost ! STP.Error => IF code = connectionRejected THEN { CWF.WF1["Connection rejected by %s.\n"L, host]; ntries ← ntries + 1; IF ntries < maxNumberOfTries THEN { CWF.WF0["Will pause and try again. (Type control-DEL to abort.)\n"L]; IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself; Process.Pause[Process.SecondsToTicks[NSECPAUSE]]; IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself; RETRY; } ELSE CWF.WF0["Connection rejected too many times.\n"L]; }]; CWF.WF1["%s\n"L,herald]; Heap.systemZone.FREE[@herald]; -- userName and userPassword may be NIL at this point STPLogin[stphandle]; }; STPLogin: PROC[stphandle: STP.Handle] = { -- STP.Login[stphandle, NameAndPasswordOps.userName, NameAndPasswordOps.userPassword]; n: STRING ← [50]; p: STRING ← [50]; UserCredentialsUnsafe.GetUserCredentials[name: n, password: p]; STP.Login[stphandle, n, p]; }; -- can be called by programs that are not using the connection table in this module HandleSTPError: PUBLIC PROC[stphandle: STP.Handle, stpError: STP.ErrorCode, message: LONG STRING, h: Subr.TTYProcs] RETURNS[retryit: BOOL] ={ IF stpError = illegalUserPassword OR stpError = illegalUserName OR stpError = illegalUserAccount OR -- misspelled: credentailsMissing stpError = credentailsMissing THEN { SetPass[stphandle, h, message]; RETURN[TRUE]; } ELSE IF stpError = illegalConnectName OR stpError = illegalConnectPassword OR stpError = accessDenied THEN { SetOtherPass[stphandle, h, message]; RETURN[TRUE]; }; RETURN[FALSE]; }; -- can be called by programs that are not using the connection table in this module SetPass: PROC[stphandle: STP.Handle, h: Subr.TTYProcs, error: LONG STRING] = { IF error ~= NIL THEN CWF.WF1["Error - %s\n"L,error]; CWF.WF0["Enter "L]; Subr.GetNameandPassword[login, NIL, NIL, h]; CWF.WFCR[]; IF stphandle ~= NIL THEN STPLogin[stphandle]; }; -- can be called by programs that are not using the connection table in this module SetOtherPass: PROC[stphandle: STP.Handle, h: Subr.TTYProcs, error: LONG STRING] = { user2: STRING ← [40]; password2: STRING ← [40]; IF error ~= NIL THEN CWF.WF1["Error - %s\n"L,error]; CWF.WF0["Enter Connect "L]; Subr.GetNameandPassword[connect, user2, password2, h]; IF stphandle ~= NIL AND user2.length > 0 THEN STP.Connect[stphandle, user2, IF password2.length = 0 THEN NIL ELSE password2]; -- by convention, if not connect name and no password is supplied -- then we abort IF user2.length = 0 AND password2.length = 0 THEN { IF h.Confirm[h.in, h.out, h.data, "\nType y to retry with different login name, n to abort ", 'n] = 'n THEN SIGNAL Subr.AbortMyself; CWF.WF0["Enter "L]; Subr.GetNameandPassword[login, NIL, NIL, h]; CWF.WFCR[]; STPLogin[stphandle]; }; }; -- can be called by programs that are not using the connection table in this module AddUserName: PUBLIC PROC[sto: LONG STRING, spat: STRING, h: Subr.TTYProcs] = { -- u: STRING; -- p: STRING ← NameAndPasswordOps.userPassword; u: STRING ← [50]; p: STRING ← [50]; lengthzero: BOOL; UserCredentialsUnsafe.GetUserCredentials[name: NIL, password: p]; lengthzero ← p = NIL OR p.length = 0; DO -- u ← NameAndPasswordOps.userName; UserCredentialsUnsafe.GetUserCredentials[name: u, password: NIL]; IF u = NIL OR u.length = 0 OR Subr.Any[u,' ] OR Subr.Prefix[u, "CedarUser"L] OR lengthzero THEN { lengthzero ← FALSE; CWF.WF0["Enter "L]; Subr.GetNameandPassword[login, NIL, NIL, h]; CWF.WFCR[]; } ELSE EXIT; ENDLOOP; IF sto ~= NIL THEN CWF.SWF1[sto,spat,u]; }; GetFileType: PROC[filename: LONG STRING] RETURNS[filetype: STP.Type] = { name: STRING ← [100]; Subr.strcpy[name, filename]; FOR i: CARDINAL IN [0..name.length) DO IF name[i] = '! OR name[i] = '; THEN { name.length ← i; EXIT; }; ENDLOOP; filetype ← unknown; IF Subr.EndsIn[name,".mesa"L] OR Subr.EndsIn[name,".bravo"L] OR Subr.EndsIn[name,".config"L] OR Subr.EndsIn[name,".cm"L] OR Subr.EndsIn[name, ".mail"L] OR Subr.EndsIn[name, ".df"L] THEN filetype ← text; IF Subr.EndsIn[name,".bcd"L] OR Subr.EndsIn[name,".press"L] OR Subr.EndsIn[name,".run"L] THEN filetype ← binary; }; }.