-- Copyright (C) 1979, 1980, 1984, 1985 by Xerox Corporation. All rights reserved. -- FTPUserFiles.mesa, HGM, 15-Sep-85 16:11:06 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: LONG UNSPECIFIED, source: LONG 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: LONG UNSPECIFIED, destination: LONG 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 foo: LONG POINTER ¬ NIL; -- 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, foo]; enumerateState ¬ inactive; ResetPropertyList[propertyList]; property ¬ FIRST[FileProperty]; value ¬ absoluteValue; filePrimitives.ReadFile[ftpuser.fileSystem, fileHandle, SendBlock, foo]; -- 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