-- file: FTPUserFiles.mesa, Edit: HGM July 31, 1980 5:33 PM -- Copyright Xerox Corporation 1979, 1980 DIRECTORY FTPDefs, FTPPrivateDefs, Ascii USING [NUL], String USING [AppendChar, EquivalentString]; FTPUserFiles: PROGRAM IMPORTS String, FTPDefs, FTPPrivateDefs EXPORTS FTPDefs, FTPPrivateDefs SHARES FTPDefs = BEGIN OPEN FTPDefs, FTPPrivateDefs; FTPEnumerateFiles: PUBLIC PROCEDURE [ftpuser: FTPUser, remoteFiles: STRING, intent: Intent, processFile: PROCEDURE [UNSPECIFIED, STRING, VirtualFilename, FileInfo], processFileData: UNSPECIFIED] = BEGIN -- SendBlock procedure SendBlock: PROCEDURE [unused: UNSPECIFIED, source: POINTER, byteCount: CARDINAL] = BEGIN -- Note: Required initial conditions are: -- Property list reset, property=FIRST[FileProperty], and value.length=0. -- check for premature end of file IF byteCount = 0 AND (property # FIRST[FileProperty] OR value.length # 0) THEN Abort[unexpectedEndOfFile]; -- consume source data bytePointerObject ← [source, FALSE, byteCount]; UNTIL bytePointerObject.count = 0 DO SELECT character ← LOOPHOLE[LoadByte[@bytePointerObject]] FROM spooledPropertyTerminator => BEGIN WriteProperty[propertyList, property, value]; value.length ← 0; IF property < LAST[FileProperty] THEN property ← SUCC[property] ELSE BEGIN -- read absolute and virtual filenames and file information ReadFilename[file, propertyList, NIL, NIL]; ReadVirtualFilename[@virtualFilenameObject, propertyList]; ReadFileInfo[propertyList, @fileInfoObject]; -- present filename to client for processing WriteProperty[propertyList, serverFilename, file]; processFile[processFileData, file, @virtualFilenameObject, @fileInfoObject]; -- advance to next property list ResetPropertyList[propertyList]; property ← FIRST[FileProperty]; END; END; ENDCASE => String.AppendChar[value, character]; ENDLOOP; END; -- ReceiveBlock procedure ReceiveBlock: PROCEDURE [unused: UNSPECIFIED, destination: POINTER, maxWordCount: CARDINAL] RETURNS [actualByteCount: CARDINAL] = BEGIN -- Note: Required initial conditions are: -- property=LAST[FileProperty], value=NIL, and index=LAST[CARDINAL]. -- produce destination data bytePointerObject ← [destination, FALSE, bytesPerWord*maxWordCount]; UNTIL bytePointerObject.count = 0 OR endOfFile DO SELECT TRUE FROM (value # NIL AND index < value.length) => BEGIN character ← value[index]; index ← index + 1; END; (property < LAST[FileProperty]) => BEGIN character ← spooledPropertyTerminator; property ← LOOPHOLE[LOOPHOLE[property, CARDINAL] + 1]; value ← propertyList[property]; index ← 0; END; ENDCASE => BEGIN character ← IF index = LAST[CARDINAL] THEN Ascii.NUL ELSE spooledPropertyTerminator; [mark, code] ← GetCommand[ftper]; SELECT mark FROM markHereIsPropertyList => BEGIN GetPropertyList[ftper, propertyList]; property ← FIRST[FileProperty]; value ← propertyList[property]; index ← 0; END; markNo => BEGIN GetEOC[ftper]; AbortWithExplanation[CodeToSignal[code], ftper.inputString]; END; markEndOfCommand => endOfFile ← TRUE; ENDCASE => Abort[illegalProtocolSequence]; END; IF character # Ascii.NUL THEN StoreByte[@bytePointerObject, LOOPHOLE[character]]; ENDLOOP; -- compute actual byte count for caller actualByteCount ← bytesPerWord*maxWordCount - bytePointerObject.count; END; -- local constants filePrimitives: FilePrimitives = ftpuser.filePrimitives; ftper: FTPer = ftpuser.ftper; propertyList: PropertyList = ftpuser.propertyList; file: STRING = [maxStringLength]; device: STRING = [maxStringLength]; directory: STRING = [maxStringLength]; name: STRING = [maxStringLength]; version: STRING = [maxStringLength]; absoluteValue: STRING = [maxStringLength]; -- local variables enumerateState: {inactive, initiated} ← inactive; mark, code: Byte; fileHandle: FileHandle ← NIL; virtualFilenameObject: VirtualFilenameObject ← [device: device, directory: directory, name: name, version: version]; fileInfoObject: FileInfoObject; bytePointerObject: BytePointerObject; property: FileProperty; value: STRING; index: CARDINAL; character: CHARACTER; endOfFile: BOOLEAN ← FALSE; -- verify purpose and state VerifyPurposeAndState[ftpuser, files, connected]; -- send command mark ← SELECT intent FROM retrieval => markRetrieve, deletion => markDelete, ENDCASE => markDirectory; -- enumeration, renaming, unspecified PutCommand[ftper, mark, 0]; -- construct property list containing absolute and virtual filenames and credentials ResetPropertyList[propertyList]; WriteFilename[remoteFiles, propertyList, NIL, NIL, ftpuser.primaryPropertyList]; -- send property list and EOC PutPropertyList[ftper, propertyList]; PutEOC[ftper]; -- modify state to control reentry IF intent # unspecified THEN BEGIN ftpuser.state ← enumeratingFiles; ftpuser.intent ← intent; END; BEGIN ENABLE UNWIND => BEGIN ENABLE FTPError => IF ftpError IN CommunicationError THEN CONTINUE; IF fileHandle # NIL THEN filePrimitives.CloseFile[ftpuser.fileSystem, fileHandle, TRUE]; IF enumerateState = initiated THEN BEGIN IF intent#enumeration AND propertyList[serverFilename]#NIL THEN -- unwinding before starting to retrieve (or delete), reject offered file PutCommandAndEOC[ftper, markNo, codeDontSendFile]; [] ← SkipRestOfFiles[ftpuser, intent IN [retrieval..deletion]]; END; ftpuser.state ← connected; END; -- process files in-line IF intent IN [enumeration..deletion] THEN DO [mark, code] ← GetCommand[ftper]; SELECT mark FROM markHereIsPropertyList => BEGIN -- note enumerate initiated enumerateState ← initiated; -- receive property list and EOC GetPropertyList[ftper, propertyList]; IF intent # enumeration THEN GetEOC[ftper]; -- read absolute and virtual filenames and file information ReadFilename[file, propertyList, NIL, NIL]; ReadVirtualFilename[@virtualFilenameObject, propertyList]; ReadFileInfo[propertyList, @fileInfoObject]; -- present filename to caller for processing WriteProperty[propertyList, serverFilename, file]; processFile[processFileData, file, @virtualFilenameObject, @fileInfoObject]; -- bypass file if not already retrieved/deleted IF intent # enumeration AND propertyList[serverFilename] # NIL THEN PutCommandAndEOC[ftper, markNo, codeDontSendFile]; END; markNo => BEGIN -- note enumerate inactive enumerateState ← inactive; -- receive EOC GetEOC[ftper]; -- abort AbortWithExplanation[CodeToSignal[code], ftper.inputString]; END; markEndOfCommand => EXIT; ENDCASE => Abort[illegalProtocolSequence]; ENDLOOP -- process files out-of-line ELSE -- renaming, unspecified BEGIN -- Note: file.length=0, requesting creation of scratch file. [fileHandle, ] ← filePrimitives.OpenFile[ftpuser.fileSystem, file, writeThenRead, FALSE, NIL]; property ← LAST[FileProperty]; value ← NIL; index ← LAST[CARDINAL]; enumerateState ← initiated; -- tell catch phrase what we are doing filePrimitives.WriteFile[ftpuser.fileSystem, fileHandle, ReceiveBlock, NIL]; enumerateState ← inactive; ResetPropertyList[propertyList]; property ← FIRST[FileProperty]; value ← absoluteValue; filePrimitives.ReadFile[ftpuser.fileSystem, fileHandle, SendBlock, NIL]; -- Note: aborted=TRUE, requesting deletion of scratch file. filePrimitives.CloseFile[ftpuser.fileSystem, fileHandle, TRUE]; END; -- reset state END; -- enable ftpuser.state ← connected; END; FTPDeleteFile: PUBLIC PROCEDURE [ftpuser: FTPUser, remoteFile: STRING] = BEGIN OPEN ftpuser; -- local constants nextFile: STRING = propertyList[serverFilename]; -- verify purpose and state VerifyPurposeAndState[ftpuser, files, IF state = connected THEN connected ELSE enumeratingFiles]; -- initiate and sustain remote delete IF state = connected THEN BEGIN -- send delete command PutCommand[ftper, markDelete, 0]; -- construct property list containing absolute and virtual filenames and credentials ResetPropertyList[propertyList]; WriteFilename[remoteFile, propertyList, NIL, NIL, primaryPropertyList]; -- send property list and EOC PutPropertyList[ftper, propertyList]; PutEOC[ftper]; -- sustain remote delete GetSpecificCommand[ftper, markHereIsPropertyList]; GetPropertyList[ftper, propertyList]; GetEOC[ftper]; END -- verify enumeration intent and filename ELSE BEGIN IF intent # deletion THEN Abort[illegalProcedureCallSequence]; IF nextFile = NIL OR ~String.EquivalentString[remoteFile, nextFile] THEN Abort[filenameUnexpected]; WriteProperty[propertyList, serverFilename, NIL]; END; -- delete file PutCommandAndEOC[ftper, markYes, 0]; -- terminate remote delete GetSpecificCommand[ftper, markYes]; IF state = connected THEN FinishMultiFileOperation[ftpuser]; END; FTPRenameFile: PUBLIC PROCEDURE [ftpuser: FTPUser, currentFile, newFile: STRING] = BEGIN OPEN ftpuser; -- local constants nextFile: STRING = propertyList[serverFilename]; -- verify purpose and state VerifyPurposeAndState[ftpuser, files, IF state = connected THEN connected ELSE enumeratingFiles]; -- verify enumeration intent and filename IF state = enumeratingFiles THEN BEGIN IF intent # renaming THEN Abort[illegalProcedureCallSequence]; IF nextFile = NIL OR ~String.EquivalentString[currentFile, nextFile] THEN Abort[filenameUnexpected]; WriteProperty[propertyList, serverFilename, NIL]; END; -- send rename command PutCommand[ftper, markRename, 0]; -- construct and send property list containing current absolute and virtual filenames and credentials ResetPropertyList[propertyList]; WriteFilename[currentFile, propertyList, NIL, NIL, primaryPropertyList]; PutPropertyList[ftper, propertyList]; -- construct and send property list containing new absolute and virtual filenames ResetPropertyList[propertyList]; WriteFilename[newFile, propertyList, NIL, NIL, secondaryPropertyList]; PutPropertyList[ftper, propertyList]; -- rename file PutEOC[ftper]; -- terminate remote rename GetYesAndEOC[ftper]; END; -- **********************! Filename Primitives !*********************** FTPSetFilenameDefaults: PUBLIC PROCEDURE [ftpuser: FTPUser, status: Status, virtualFilename: VirtualFilename] = BEGIN OPEN ftpuser; -- local constants propertyList: PropertyList = IF status = primary THEN primaryPropertyList ELSE secondaryPropertyList; -- record virtual filename in appropriate property list WriteVirtualFilename[virtualFilename, propertyList, FALSE]; END; FTPNoteFilenameUsed: PUBLIC PROCEDURE [ftpuser: FTPUser, absoluteFilename: STRING, virtualFilename: VirtualFilename] = BEGIN OPEN ftpuser; -- return absolute filename IF absoluteFilename # NIL THEN ReadFilename[absoluteFilename, propertyList, NIL, NIL]; -- return virtual filename IF virtualFilename # NIL THEN ReadVirtualFilename[virtualFilename, propertyList]; END; -- **********************! Protocol Subroutine !*********************** FinishMultiFileOperation: PUBLIC PROCEDURE [ftpuser: FTPUser] = BEGIN IF SkipRestOfFiles[ftpuser,TRUE] THEN Abort[fileGroupDesignatorUnexpected]; END; SkipRestOfFiles: PROCEDURE [ftpuser: FTPUser, sayNo: BOOLEAN] RETURNS [fileBypassed: BOOLEAN] = BEGIN OPEN ftpuser; mark, code: Byte; fileBypassed ← FALSE; DO [mark, code] ← GetCommand[ftper]; SELECT mark FROM markHereIsPropertyList => BEGIN GetPropertyList[ftper, propertyList]; IF sayNo THEN BEGIN GetEOC[ftper]; PutCommandAndEOC[ftper, markNo, codeDontSendFile]; fileBypassed ← TRUE; END; END; markNo => BEGIN GetEOC[ftper]; AbortWithExplanation[CodeToSignal[code], ftper.inputString]; END; markEndOfCommand => EXIT; ENDCASE => Abort[illegalProtocolSequence]; ENDLOOP; END; StoreByte: PUBLIC PROCEDURE [dstBytePointer: BytePointer, byte: Byte] = INLINE BEGIN -- Note: Doesn't check for byte pointer exhaustion. -- local constants dBP: BytePointer = dstBytePointer; dWord: Word = dBP.address; -- store byte IF dBP.offset THEN dWord.rhByte ← byte ELSE dWord.lhByte ← byte; -- advance address and offset IF ~(dBP.offset ← ~dBP.offset) THEN dBP.address ← dBP.address + 1; -- decrement byte count dBP.count ← dBP.count - 1; END; LoadByte: PUBLIC PROCEDURE [srcBytePointer: BytePointer] RETURNS [byte: Byte] = INLINE BEGIN -- Note: Doesn't check for byte pointer exhaustion. -- local constants sBP: BytePointer = srcBytePointer; sWord: Word = sBP.address; -- load byte byte ← IF sBP.offset THEN sWord.rhByte ELSE sWord.lhByte; -- advance address and offset IF ~(sBP.offset ← ~sBP.offset) THEN sBP.address ← sBP.address + 1; -- decrement byte count sBP.count ← sBP.count - 1; END; -- **********************! Main Program !*********************** -- no operation END. -- of FTPUserFiles