-- 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