-- file: STPsF.mesa - Simple/Stream Transfer Protocol -- Server stuff in here -- Edited by: -- Smokey on: 11-Mar-81 17:07:41 -- Schmidt on: 4-Apr-82 15:04:49 -- (remove Put, Tool, Window dependencies) DIRECTORY Directory: TYPE USING [DeleteFile, Error, GetProps, Rename], Format: TYPE USING [DecimalFormat, LongNumber], HeapString: TYPE USING [Replace], PupStream: TYPE USING [ CreatePupByteStreamListener, DestroyPupListener, PupAddress, PupPackageDestroy, PupPackageMake, PupListener, RejectThisRequest, StreamClosing, veryLongWait], Segments: TYPE USING [EnumerateDirectory, FileNameProblem, FPHandle, InsertFile, ModifyFile], Storage: TYPE USING [CopyString, EmptyString, FreeString, FreeStringNil], STP: TYPE USING [Create, Destroy, Error, GetFileTimes], STPOps: TYPE USING [ CollectCode, CollectString, GetCommand, GetFile, GetPList, Handle, MakePList, MakeRemoteName, markDelete, markDirectory, markEOC, markHereIsPList, markIAmVersion, markNo, markRename, markRetrieve, markNewStore, markYes, MyGetMark, NameToPList, Object, PList, PutCommand, PutFile, PutPList, ResetPList, SetFileDates], STPReplyCode: TYPE USING[badCommand, fileBusy, fileNotFound, illegalCommand, illegalNameBody, notCompleted, null], Stream: TYPE USING [Handle, SetSST, SubSequenceType, TimeOut], Streams: TYPE USING [CreateStream, Destroy, GetLength, NewStream, Read, SetIndex, Write], Time: TYPE USING [Append, Packed, Unpack], TTY: TYPE USING [Create, Handle, PutChar, PutLine, PutOctal, PutString]; STPsF: PROGRAM IMPORTS Directory, Format, HeapString, PupStream, Segments, Storage, STP, STPOps, Stream, Streams, Time, TTY EXPORTS STP = BEGIN OPEN PupStream, STPOps; -- Global Data Object: PUBLIC TYPE = STPOps.Object; OneListenerPerCustomer: ERROR = CODE; pupListener: PupListener ← NIL; PutChar: PROCEDURE [w: TTY.Handle, c: CHARACTER] = INLINE { IF LOOPHOLE[w, POINTER] # NIL THEN TTY.PutChar[w, c]}; PutLine: PROCEDURE [w: TTY.Handle, s: STRING] = INLINE { IF LOOPHOLE[w, POINTER] # NIL THEN TTY.PutLine[w, s]}; PutOctal: PROCEDURE [w: TTY.Handle, n: UNSPECIFIED] = INLINE { IF LOOPHOLE[w, POINTER] # NIL THEN TTY.PutOctal[w, n]}; PutText: PROCEDURE [w: TTY.Handle, s: STRING] = INLINE { IF LOOPHOLE[w, POINTER] # NIL THEN TTY.PutString[w, s]}; -- Public Interface Routines CreateListener: PUBLIC PROCEDURE [ server: PROCEDURE[Stream.Handle, PupAddress] ← DefaultServer, checker: PROCEDURE[PupAddress] ← DefaultChecker, connections: CARDINAL ← 1] = BEGIN IF pupListener = NIL THEN BEGIN PupStream.PupPackageMake[]; pupListener ← CreatePupByteStreamListener[ local: [0,3], proc: server, ticks: veryLongWait, filter: checker]; maxConnections ← connections; END ELSE ERROR OneListenerPerCustomer; END; DestroyListener: PUBLIC PROCEDURE = BEGIN IF pupListener # NIL THEN BEGIN PupStream.DestroyPupListener[pupListener]; PupStream.PupPackageDestroy[]; END; END; -- Server stuff DefaultChecker: PUBLIC PROCEDURE [address: PupAddress] = BEGIN END; DefaultServer: PUBLIC PROCEDURE [byteStream: Stream.Handle, address: PupAddress] = BEGIN STPServer[LOOPHOLE[NIL], byteStream, address]; END; STPServer: PUBLIC PROCEDURE [w: TTY.Handle, byteStream: Stream.Handle, address: PupAddress] = BEGIN -- this guy gets FORKed for every accepted RFC file: STRING ← NIL; stream: Stream.Handle ← NIL; mark: Stream.SubSequenceType; stp: STPOps.Handle ← STP.Create[]; stp.byteStream ← byteStream; stp.plist ← STPOps.MakePList[]; stp.gotMark ← FALSE; PutText[w, "Connection opened to ["L]; PutOctal[w, address.net]; PutText[w, "#, "L]; PutOctal[w, address.host]; PutText[w, "# ["L]; PutOctal[w, address.socket.a]; PutText[w, "#, "L]; PutOctal[w, address.socket.b]; PutLine[w, "#]]"L]; DO ENABLE PupStream.StreamClosing, Stream.TimeOut => {stp.byteStream ← NIL; PutLine[w, " ...Connection Closed"L]; EXIT}; SELECT mark ← STPOps.MyGetMark[stp] FROM markDelete => DoFiles[w, stp, delete]; markDirectory => DoFiles[w, stp, directory]; markIAmVersion => BEGIN [] ← STPOps.CollectCode[stp]; STPOps.CollectString[stp, @stp.remoteString]; IF ~NextEOC[stp] THEN EXIT; STPOps.PutCommand[stp, markIAmVersion, 1C, "Cedar STP: You called?"L]; END; markNewStore => BEGIN STPOps.ResetPList[stp.plist]; STPOps.GetPList[stp, TRUE]; file ← STPOps.MakeRemoteName[stp.plist, alto]; IF ~Segments.ModifyFile[file] THEN STPOps.PutCommand[stp, markNo, STPReplyCode.fileBusy, "can't be modified"L] ELSE BEGIN IF ~Storage.EmptyString[file] THEN stream ← Streams.NewStream[file, Streams.Write ! Segments.FileNameProblem[] => CONTINUE]; IF stream # NIL THEN BEGIN STPOps.PutPList[stp, markHereIsPList]; IF STPOps.GetFile[stp, stream, file ! STP.Error => EXIT] AND NextEOC[stp] THEN BEGIN PutCommand[stp, markYes, STPReplyCode.null, "Transfer completed"L]; PutText[w, " Stored: "L]; PutProperties[w, stp.plist]; END ELSE PutCommand[stp, markNo, STPReplyCode.notCompleted, "Store not completed"L]; STPOps.SetFileDates[stp, stream]; Streams.Destroy[stream]; stream ← NIL; END ELSE PutCommand[stp, markNo, STPReplyCode.illegalNameBody, "Illegal Filename"L]; END; file ← Storage.FreeStringNil[file]; END; markRename => BEGIN newName: STRING ← NIL; failed: BOOLEAN ← FALSE; STPOps.ResetPList[stp.plist]; STPOps.GetPList[stp, FALSE]; file ← STPOps.MakeRemoteName[stp.plist, alto]; STPOps.ResetPList[stp.plist]; STPOps.GetPList[stp, TRUE]; newName ← STPOps.MakeRemoteName[stp.plist, alto]; IF Segments.ModifyFile[file] THEN Directory.Rename[file, newName ! Directory.Error => {failed ← TRUE; CONTINUE}] ELSE failed ← TRUE; IF failed THEN PutCommand[stp, markNo, STPReplyCode.null, "Rename failed"L] ELSE BEGIN PutCommand[stp, markYes, STPReplyCode.null, "... renamed"L]; PutText[w, " Renamed: "L]; PutText[w, file]; PutText[w, " to be "L]; PutLine[w, newName]; END; file ← Storage.FreeStringNil[file]; newName ← Storage.FreeStringNil[file]; END; markRetrieve => DoFiles[w, stp, retrieve]; ENDCASE => BEGIN STPOps.PutCommand[stp, markNo, STPReplyCode.badCommand, "Bad or Unimplemented Command"L]; EXIT; END; ENDLOOP; Storage.FreeString[file]; IF stream # NIL THEN Streams.Destroy[stream]; stp ← STP.Destroy[stp]; currentConnections ← currentConnections - 1; END; DoFiles: PROCEDURE [w: TTY.Handle, stp: STPOps.Handle, type : {delete, directory, retrieve}] = BEGIN file: STRING ← NIL; stream: Stream.Handle ← NIL; mark: Stream.SubSequenceType; code: CHARACTER; foundAtLeastOne: BOOLEAN ← FALSE; pendingName: STRING ← NIL; DoPendingDelete: PROCEDURE = BEGIN IF pendingName = NIL THEN RETURN; Directory.DeleteFile[pendingName]; pendingName ← Storage.FreeStringNil[pendingName]; END; PerFile: PROCEDURE [cap: Segments.FPHandle, name: STRING] RETURNS[BOOLEAN] = BEGIN ENABLE UNWIND => BEGIN Storage.FreeString[file]; IF stream # NIL THEN Streams.Destroy[stream]; END; foundAtLeastOne ← TRUE; STPOps.ResetPList[stp.plist]; SELECT type FROM delete => BEGIN DoPendingDelete[]; SetPropertiesFromFP[stp, name, cap]; STPOps.PutPList[stp, markHereIsPList, TRUE]; [mark, code] ← STPOps.GetCommand[stp, @stp.remoteString]; IF ~NextEOC[stp] THEN BEGIN STPOps.PutCommand[stp, markNo, STPReplyCode.illegalCommand, "Protocol Error"L]; RETURN[TRUE]; END; IF mark = markYes THEN BEGIN IF ~Segments.ModifyFile[name] THEN STPOps.PutCommand[stp, markNo, STPReplyCode.fileBusy, "can't be modified"L, FALSE] ELSE BEGIN PutText[w, " Deleted: "L]; PutProperties[w, stp.plist]; pendingName ← Storage.CopyString[name]; STPOps.PutCommand[stp, markYes, STPReplyCode.null, "file deleted"L, FALSE] END; END; END; directory => BEGIN SetPropertiesFromFP[stp, name, cap]; STPOps.PutPList[stp, markHereIsPList, FALSE]; END; retrieve => BEGIN stream ← Streams.CreateStream[Segments.InsertFile[cap, Streams.Read], Streams.Read]; IF stream # NIL THEN BEGIN STPOps.ResetPList[stp.plist]; SetPropertiesFromStream[stp, name, stream]; STPOps.PutPList[stp, markHereIsPList]; [mark, code] ← STPOps.GetCommand[stp, @stp.remoteString]; IF ~NextEOC[stp] THEN BEGIN STPOps.PutCommand[stp, markNo, STPReplyCode.illegalCommand, "Protocol Error"L]; RETURN[TRUE]; END; IF mark = markYes THEN STPOps.PutFile[stp, stream, name, FALSE]; Streams.Destroy[stream]; stream ← NIL; PutText[w, " Retrieved: "L]; PutProperties[w, stp.plist]; END ELSE PutCommand[stp, markNo, STPReplyCode.fileNotFound, "File not found"L]; END; ENDCASE => ERROR; RETURN[FALSE]; END; STPOps.GetPList[stp, TRUE]; file ← STPOps.MakeRemoteName[stp.plist, alto]; Segments.EnumerateDirectory[PerFile, file]; IF type = delete THEN DoPendingDelete[]; file ← Storage.FreeStringNil[file]; IF foundAtLeastOne THEN Stream.SetSST[stp.byteStream, markEOC] ELSE STPOps.PutCommand[stp, markNo, STPReplyCode.fileNotFound, "File not found"L]; END; NextEOC: PROCEDURE [stp: STPOps.Handle] RETURNS [BOOLEAN] = BEGIN IF MyGetMark[stp] = markEOC THEN RETURN[TRUE] ELSE BEGIN STPOps.PutCommand[stp, markNo, STPReplyCode.illegalCommand, "Protocol Error"L]; RETURN[FALSE]; END; END; PutProperties: PROCEDURE [w: TTY.Handle, plist: STPOps.PList] = BEGIN OPEN Storage; IF ~EmptyString[plist[nameBody]] THEN PutText[w, plist[nameBody]] ELSE IF ~EmptyString[plist[serverName]] THEN PutText[w, plist[serverName]]; IF ~EmptyString[plist[version]] THEN {PutChar[w, '!]; PutText[w, plist[version]]}; PutText[w, " Type "L]; PutText[w, IF ~EmptyString[plist[type]] THEN plist[type] ELSE "unknown"L]; IF ~EmptyString[plist[byteSize]] THEN {PutText[w, ", ByteSize "L]; PutText[w, plist[byteSize]]}; PutText[w, "..."L]; IF ~EmptyString[plist[size]] THEN {PutText[w, plist[size]]; PutText[w, " bytes"L]}; PutChar[w, '\n]; END; SetPropertiesFromStream: PROCEDURE [stp: STPOps.Handle, file: STRING, stream: Stream.Handle] = BEGIN length: LONG CARDINAL ← Streams.GetLength[stream]; create, read, write: Time.Packed; Streams.SetIndex[stream, 0]; -- undo GetLength [create: create, read: read, write: write] ← STP.GetFileTimes[stream]; SetTimesPlus[stp, file, length, create, read, write]; END; SetPropertiesFromFP: PROCEDURE [stp: STPOps.Handle, file: STRING, fp: Segments.FPHandle] = BEGIN length: LONG CARDINAL; create, read, write: Time.Packed; [createDate: create, readDate: read, writeDate: write, byteLength: length, parent: ] ← Directory.GetProps[fp↑, file]; SetTimesPlus[stp, file, length, create, read, write]; END; SetTimesPlus: PROCEDURE [ stp: STPOps.Handle, file:STRING, length: LONG CARDINAL, create, read, write: Time.Packed] = BEGIN proc: PROCEDURE [s: STRING] = {HeapString.Replace[@stp.plist[size], s]}; temp: STRING ← [24]; Time.Append[temp,Time.Unpack[create]]; HeapString.Replace[@stp.plist[createDate], temp]; temp.length ← 0; Time.Append[temp,Time.Unpack[write]]; HeapString.Replace[@stp.plist[writeDate], temp]; temp.length ← 0; Time.Append[temp,Time.Unpack[read]]; HeapString.Replace[@stp.plist[readDate], temp]; NameToPList[stp.plist, file, alto]; HeapString.Replace[@stp.plist[byteSize], "8"L]; Format.LongNumber[length, Format.DecimalFormat, proc]; END; -- Testing code currentConnections: CARDINAL ← 0; maxConnections: CARDINAL ← 0; logSW: TTY.Handle ← TTY.Create["STPServer.Log"L]; CheckConnectionRequest: PUBLIC PROCEDURE [address: PupAddress] = BEGIN IF currentConnections < maxConnections THEN currentConnections ← currentConnections + 1 ELSE ERROR RejectThisRequest["Connection rejected - Too many connections"]; END; CreateSTPServer: PUBLIC PROCEDURE [byteStream: Stream.Handle, address: PupAddress] = BEGIN -- this guy gets FORKed for every accepted RFC STPServer[logSW, byteStream, address]; END; CreateListener[CreateSTPServer, CheckConnectionRequest, 1]; END. -- of STPsF