AlpineFSImpl.mesa
Implementation of the Alpine class of Cedar File System and the AlpineFS class of IO.STREAM.
Last Edited by: Taft, December 12, 1983 4:50 pm
Loose ends:
1. This is an interim implementation, based on AlpineInterimDirectory. Consequently, a number of AlpineFS facilities are not supported: versions, keeps, links, created time search, and all the general file manipulations. The full-blown implementation will be based on AlpineDirectory and will support everything.
2. Can remove closed check from FileDataFromRef once FS gives us the ability to change the fileData reference associated with an OpenFile.
3. Unless a transaction is provided by the caller, Open and Create perform directory access under a separate transaction which is immediately committed. This is to allow adequate concurrency in the face of the simpleminded directory locking done by AlpineInterimDirectory. Therefore, nothing can be said about consistency between the directory and the files. This problem will be revisited when AlpineDirectory is installed.
DIRECTORY
AlpFile,
AlpineEnvironment,
AlpineFS,
AlpineInterimDirectory,
AlpInstance,
AlpTransaction,
BasicTime USING [GMT, Now, nullGMT],
FileStream,
FS,
FSBackdoor USING [CreateFileProcs, CreateProcsOpenFile, FileProcs, GetClientFileAndProcs],
IO USING [CreateStreamProcs, Error, PutFR, StreamProcs],
List USING [Assoc],
Process USING [Detach],
ProcessProps USING [GetPropList],
Rope USING [Compare, Fetch, Length, Substr],
RPC USING [CallFailed],
SafeStorage USING [EnableFinalization, EstablishFinalization, FinalizationQueue, FQNext, NewFQ];
AlpineFSImpl: CEDAR MONITOR
IMPORTS AlpFile, AlpineInterimDirectory, AlpInstance, AlpTransaction, BasicTime, FileStream, FS, FSBackdoor, IO, List, Process, ProcessProps, Rope, RPC, SafeStorage
EXPORTS AlpineFS =
BEGIN OPEN AlpineFS;
Private types and constants
FileData: TYPE = REF FileDataObject; -- Instance data for an Alpine OpenFile
FileDataObject: TYPE = RECORD [
alpFile: AlpFile.Handle ← NIL, -- NIL means file has been closed
options: FileOptions,
access: Lock, -- FS Lock; nothing to do with Alpine LockOption
lock: AlpFile.LockOption, -- only the ifConflict field is valid; mode must be re-read from Alpine
universalFile: AlpFile.UniversalFile ← AlpineEnvironment.nullUniversalFile,
fullFName: ROPE, -- canonical form of name used to open file,
attachedTo: ROPENIL, -- name of actual file opened if fullFName was a link; NIL otherwise
size: AlpFile.PageCount ← -1, -- size of file in pages; -1 if not known yet
byteLength: AlpineEnvironment.ByteCount ← -1, -- -1 if not known yet
createTime: BasicTime.GMT ← BasicTime.nullGMT -- nullGMT if not known yet
];
wordsPerPage: CARDINAL = AlpineEnvironment.wordsPerPage;
bytesPerPage: CARDINAL = AlpineEnvironment.bytesPerPage;
Error handling
ProduceError: PUBLIC PROCEDURE [code: ErrorCode, explanation: ROPE] =
BEGIN
ERROR FS.Error[[group: errorTable[code].group, code: errorTable[code].atom, explanation: explanation]];
END;
ErrorTable: TYPE = ARRAY ErrorCode OF RECORD [group: ErrorGroup, atom: ATOM];
errorTable: REF ErrorTable = NEW [ErrorTable ← [
badByteCount: [client, $badByteCount],
invalidOpenFile: [client, $invalidOpenFile],
notImplemented: [client, $notImplemented],
unknownPage: [client, $unknownPage],
unwritableProperty: [client, $unwritableProperty],
wrongLock: [client, $wrongLock],
accessDenied: [environment, $accessDenied],
badCredentials: [environment, $badCredentials],
circularLink: [environment, $circularLink],
danglingLink: [environment, $danglingLink],
lockConflict: [environment, $lockConflict],
lockTimeout: [environment, $lockTimeout],
other: [environment, $other],
quotaExceeded: [environment, $quotaExceeded],
regServersUnavailable: [environment, $regServersUnavailable],
remoteCallFailed: [environment, $remoteCallFailed],
serverBusy: [environment, $serverBusy],
serverInaccessible: [environment, $serverInaccessible],
transAborted: [environment, $transAborted],
volumeFull: [environment, $volumeFull],
cantUpdateTiogaFile: [user, $cantUpdateTiogaFile],
fileAlreadyExists: [user, $fileAlreadyExists],
illegalName: [user, $illegalName],
noKeeps: [user, $noKeeps],
ownerNotFound: [user, $ownerNotFound],
patternNotAllowed: [user, $patternNotAllowed],
unknownCreatedTime: [user, $unknownCreatedTime],
unknownFile: [user, $unknownFile],
unknownServer: [user, $unknownServer]]];
Opening files
This implementation is strictly interim and will have to be entirely redone when AlpineInterimDirectory is replaced by AlpineDirectory.
Open: PUBLIC PROCEDURE [name: ROPE, wantedCreatedTime: BasicTime.GMT ← BasicTime.nullGMT, access: Lock ← $read, lock: AlpFile.LockOption ← [$none, $wait], options: FileOptions ← [], wDir: ROPENIL, transHandle: AlpTransaction.Handle ← NIL] RETURNS [file: OpenFile] =
BEGIN
RETURN [OpenInternal[name: name, createOptions: $oldOnly, access: access, lock: lock, options: options, wDir: wDir, transHandle: transHandle]];
END;
Create: PUBLIC PROCEDURE [name: ROPE, pages: INT ← 10, keep: CARDINAL ← 0, options: FileOptions ← [], wDir: ROPENIL, transHandle: AlpTransaction.Handle ← NIL] RETURNS [file: OpenFile] =
BEGIN
RETURN [OpenInternal[name: name, createOptions: $newOnly, pages: pages, options: options, wDir: wDir, transHandle: transHandle]];
END;
OpenOrCreate: PUBLIC PROCEDURE [name: ROPE, pages: INT ← 10, keep: CARDINAL ← 0, options: FileOptions ← [], wDir: ROPENIL, transHandle: AlpTransaction.Handle ← NIL] RETURNS [OpenFile] =
BEGIN
RETURN [OpenInternal[name: name, createOptions: $none, pages: pages, options: options, wDir: wDir, transHandle: transHandle]];
END;
OpenInternal: PROCEDURE [name: ROPE, createOptions: AlpineInterimDirectory.CreateOptions, pages: INT ← -1, access: Lock ← $write, lock: AlpFile.LockOption ← [$write, $wait], options: FileOptions, wDir: ROPE, transHandle: AlpTransaction.Handle] RETURNS [file: OpenFile] =
BEGIN
callerSuppliedTrans: BOOLEAN ← transHandle#NIL;
fullFName: ROPE;
cp: FS.ComponentPositions;
fileData: FileData;
dirTrans: AlpTransaction.Handle ← NIL;
createdFile: BOOLEAN;
IF lock.mode=$none THEN lock.mode ← SELECT access FROM
$read => $read,
$write => $write,
ENDCASE => ERROR;
[fullFName: fullFName, cp: cp] ← DecomposeFileName[name: name, wDir: wDir];
transHandle ← EstablishTransaction[serverName: fullFName.Substr[start: cp.server.start, len: cp.server.length], existingTrans: transHandle];
BEGIN ENABLE FS.Error =>
IF ~callerSuppliedTrans THEN
BEGIN ENABLE RPC.CallFailed => CONTINUE;
[] ← transHandle.Finish[abort];
IF dirTrans#NIL THEN [] ← dirTrans.Finish[abort];
END;
AlpineOpen: PROCEDURE =
BEGIN
dirTrans ← IF callerSuppliedTrans THEN transHandle ELSE AlpTransaction.Create[transHandle.inst];
[universalFile: fileData.universalFile, createdFile: createdFile] ← AlpineInterimDirectory.OpenUnderTrans[transHandle: dirTrans, fileName: fullFName, createOptions: createOptions];
IF ~callerSuppliedTrans THEN
IF dirTrans.Finish[commit]#commit THEN {
dirTrans ← NIL; ProduceError[$transAborted, "Transaction aborted"]};
dirTrans ← NIL;
[handle: fileData.alpFile, fileID: fileData.universalFile.fileID] ← AlpFile.Open[transHandle: transHandle, universalFile: fileData.universalFile, access: IF access=$read THEN $readOnly ELSE $readWrite, lock: lock, recoveryOption: options.recoveryOption, referencePattern: options.referencePattern];
END;
fileData ← NEW [FileDataObject ← [options: options, access: access, lock: lock, fullFName: fullFName]];
CallAlpine[AlpineOpen];
file ← FSBackdoor.CreateProcsOpenFile[clientFile: fileData, fileProcs: fileProcs[access]];
AddPackageRef[fileData]; -- Remove this when new SafeStorage is available
SafeStorage.EnableFinalization[fileData];
IF ~createdFile AND options.updateCreateTime AND access=$write THEN
SetByteCountAndCreatedTime[clientFile: fileData, created: BasicTime.Now[]];
END;
END;
fileProcs: ARRAY FS.Lock OF REF FSBackdoor.FileProcs = [
read: FSBackdoor.CreateFileProcs[GetClass: GetClass, SameFile: SameFile, GetName: GetName, GetInfo: GetInfo, SetPageCount: NIL, SetByteCountAndCreatedTime: NIL, Read: Read, Write: NIL, Close: Close],
write: FSBackdoor.CreateFileProcs[GetClass: GetClass, SameFile: SameFile, GetName: GetName, GetInfo: GetInfo, SetPageCount: SetPageCount, SetByteCountAndCreatedTime: SetByteCountAndCreatedTime, Read: Read, Write: Write, Close: Close]];
Operations on open files
GetClass: PROCEDURE [clientFile: REF ANY] RETURNS [ATOM] =
BEGIN
RETURN [$AlpineFS];
END;
SameFile: PROCEDURE [clientFile1, clientFile2: REF ANY] RETURNS [BOOLEAN] =
BEGIN
WITH clientFile2 SELECT FROM
file2Data: FileData => RETURN [FileDataFromRef[clientFile1].universalFile = file2Data.universalFile];
ENDCASE => RETURN [FALSE];
END;
GetName: PROCEDURE [clientFile: REF ANY] RETURNS [fullFName, attachedTo: ROPE] =
BEGIN
fileData: FileData = FileDataFromRef[clientFile];
RETURN [fullFName: fileData.fullFName, attachedTo: fileData.attachedTo];
END;
GetInfo: PROCEDURE [clientFile: REF ANY] RETURNS [keep: CARDINAL, pages, bytes: INT, created: BasicTime.GMT, lock: Lock] =
BEGIN
GetInfoInner: PROCEDURE =
BEGIN
fileData: FileData = FileDataFromRef[clientFile];
IF fileData.byteLength=-1 OR fileData.createTime=BasicTime.nullGMT THEN
BEGIN
props: LIST OF AlpFile.PropertyValuePair = fileData.alpFile.ReadProperties[desiredProperties: [byteLength: TRUE, createTime: TRUE], lock: [$read, fileData.lock.ifConflict]];
FOR item: LIST OF AlpFile.PropertyValuePair ← props, item.rest UNTIL item=NIL DO
TRUSTED { -- allegedly unsafe discrimination
WITH prop: item.first SELECT FROM
byteLength => fileData.byteLength ← prop.byteLength;
createTime => fileData.createTime ← prop.createTime;
ENDCASE => ERROR};
ENDLOOP;
END;
keep ← 0;
pages ← GetPageCount[fileData];
bytes ← fileData.byteLength;
created ← fileData.createTime;
lock ← fileData.access;
END;
CallAlpine[GetInfoInner];
END;
SetPageCount: PROCEDURE [clientFile: REF ANY, pages: INT] =
BEGIN
SetPageCountInner: PROCEDURE =
BEGIN
fileData: FileData = FileDataFromRef[clientFile];
IF pages<0 THEN ProduceError[$unknownPage, "Attempted to set page count to an illegal value"];
IF pages#fileData.size THEN fileData.alpFile.SetSize[size: pages, lock: [$write, fileData.lock.ifConflict]];
fileData.size ← pages;
END;
CallAlpine[SetPageCountInner];
END;
SetByteCountAndCreatedTime: PROCEDURE [clientFile: REF ANY, bytes: INT ← -1, created: BasicTime.GMT ← BasicTime.nullGMT] =
BEGIN
SetByteCountAndCreatedTimeInner: PROCEDURE =
BEGIN
fileData: FileData = FileDataFromRef[clientFile];
props: LIST OF AlpFile.PropertyValuePair ← NIL;
IF bytes#-1 THEN
BEGIN
IF bytes<0 OR bytes>GetPageCount[fileData]*bytesPerPage THEN
ProduceError[$badByteCount, "Attempted to set byte count to an illegal value"];
props ← CONS[[byteLength[bytes]], props];
END;
IF created#BasicTime.nullGMT THEN props ← CONS[[createTime[created]], props];
IF props#NIL THEN fileData.alpFile.WriteProperties[properties: props, lock: [$write, fileData.lock.ifConflict]];
IF bytes#-1 THEN fileData.byteLength ← bytes;
IF created#BasicTime.nullGMT THEN fileData.createTime ← created;
END;
CallAlpine[SetByteCountAndCreatedTimeInner];
END;
Read: UNSAFE PROCEDURE [clientFile: REF ANY, from, nPages: INT, to: LONG POINTER] =
BEGIN
ReadInner: PROCEDURE = TRUSTED
BEGIN
fileData: FileData = FileDataFromRef[clientFile];
IF nPages<0 THEN ProduceError[$unknownPage, "Attempted to use a negative page number"];
WHILE nPages#0 DO
pageRun: AlpFile.PageRun ← [firstPage: from, count: MIN[nPages, CARDINAL.LAST/wordsPerPage]];
pageBuffer: AlpFile.RESULTPageBuffer ← DESCRIPTOR[to, pageRun.count*wordsPerPage];
fileData.alpFile.ReadPages[pageRun: pageRun, pageBuffer: pageBuffer, lock: [$read, fileData.lock.ifConflict]];
nPages ← nPages-pageRun.count;
from ← from+pageRun.count;
to ← to+pageBuffer.LENGTH;
ENDLOOP;
END;
CallAlpine[ReadInner];
END;
Write: PROCEDURE [clientFile: REF ANY, to, nPages: INT, from: LONG POINTER] =
BEGIN
WriteInner: PROCEDURE = TRUSTED
BEGIN
fileData: FileData = FileDataFromRef[clientFile];
IF nPages<0 THEN ProduceError[$unknownPage, "Attempted to use a negative page number"];
WHILE nPages#0 DO
pageRun: AlpFile.PageRun ← [firstPage: to, count: MIN[nPages, CARDINAL.LAST/wordsPerPage]];
pageBuffer: AlpFile.VALUEPageBuffer ← DESCRIPTOR[from, pageRun.count*wordsPerPage];
fileData.alpFile.WritePages[pageRun: pageRun, pageBuffer: pageBuffer, lock: [$write, fileData.lock.ifConflict]];
nPages ← nPages-pageRun.count;
to ← to+pageRun.count;
from ← from+pageBuffer.LENGTH;
ENDLOOP;
END;
CallAlpine[WriteInner];
END;
Close: PROCEDURE [clientFile: REF ANY] =
BEGIN
fileData: FileData = FileDataFromRef[clientFile];
CloseInner: PROCEDURE =
BEGIN
fileData.alpFile.Close[];
IF fileData.options.finishTransOnClose THEN
BEGIN
outcome: AlpTransaction.Outcome = fileData.alpFile.trans.Finish[commit];
IF outcome#commit THEN ProduceError[$transAborted, "Transaction aborted"];
END;
Destroy[fileData];
END;
CallAlpine[CloseInner !
FS.Error => {
IF fileData.options.finishTransOnClose AND error.code#$transAborted THEN
[] ← fileData.alpFile.trans.Finish[abort ! RPC.CallFailed => CONTINUE];
Destroy[fileData]}];
END;
GetFileOptions: PUBLIC PROCEDURE [file: OpenFile] RETURNS [options: FileOptions] =
BEGIN
RETURN [FileDataFromOpenFile[file].options];
END;
SetFileOptions: PUBLIC PROCEDURE [file: OpenFile, options: FileOptions] =
BEGIN
SetFileOptionsInner: PROCEDURE =
BEGIN
fileData: FileData = FileDataFromOpenFile[file];
IF options.referencePattern#fileData.options.referencePattern THEN fileData.alpFile.SetReferencePattern[options.referencePattern];
fileData.options.referencePattern ← options.referencePattern;
fileData.options.finishTransOnClose ← options.finishTransOnClose;
END;
CallAlpine[SetFileOptionsInner];
END;
GetLockOption: PUBLIC PROCEDURE [file: OpenFile] RETURNS [lock: AlpFile.LockOption] =
BEGIN
GetLockOptionInner: PROCEDURE = {
lock ← FileDataFromOpenFile[file].alpFile.GetLockOption[]};
CallAlpine[GetLockOptionInner];
END;
SetLockOption: PUBLIC PROCEDURE [file: OpenFile, lock: AlpFile.LockOption] =
BEGIN
SetLockOptionInner: PROCEDURE = {
FileDataFromOpenFile[file].alpFile.SetLockOption[lock]};
CallAlpine[SetLockOptionInner];
END;
ReadProperties: PUBLIC PROCEDURE [file: OpenFile, desiredProperties: AlpFile.PropertySet ← AlpFile.allProperties] RETURNS [properties: LIST OF AlpFile.PropertyValuePair] =
BEGIN
ReadPropertiesInner: PROCEDURE =
BEGIN
fileData: FileData = FileDataFromOpenFile[file];
properties ← fileData.alpFile.ReadProperties[desiredProperties: desiredProperties, lock: [$read, fileData.lock.ifConflict]];
END;
CallAlpine[ReadPropertiesInner];
END;
WriteProperties: PUBLIC PROCEDURE [file: OpenFile, properties: LIST OF AlpFile.PropertyValuePair] =
BEGIN
WritePropertiesInner: PROCEDURE =
BEGIN
fileData: FileData = FileDataFromOpenFile[file];
fileData.alpFile.WriteProperties[properties: properties, lock: [$write, fileData.lock.ifConflict]];
END;
CallAlpine[WritePropertiesInner];
END;
Abort: PUBLIC PROCEDURE [file: OpenFile] =
BEGIN
fileData: FileData = FileDataFromOpenFile[file];
AbortInner: PROCEDURE =
BEGIN
[] ← FileDataFromOpenFile[file].alpFile.trans.Finish[abort];
Destroy[fileData];
END;
CallAlpine[AbortInner ! FS.Error => Destroy[fileData]];
END;
GetAlpFileHandle: PUBLIC PROCEDURE [file: OpenFile] RETURNS [handle: AlpFile.Handle] =
BEGIN
RETURN [FileDataFromOpenFile[file].alpFile];
END;
File streams
StreamClassData: TYPE = REF StreamClassDataObject;
StreamClassDataObject: TYPE = RECORD [
options: StreamOptions];
StreamOpen: PUBLIC PROCEDURE [name: ROPE, accessOptions: AccessOptions ← $read, streamOptions: StreamOptions ← [], keep: CARDINAL ← 1, createByteCount: ByteCount ← 2560, streamBufferParms: StreamBufferParms ← defaultStreamBufferParms, extendFileProc: ExtendFileProc ← NIL, wDir: ROPENIL, transHandle: AlpTransaction.Handle ← NIL] RETURNS [STREAM] =
BEGIN
file: OpenFile = SELECT accessOptions FROM
$read => Open[name: name, options: [referencePattern: $sequential, finishTransOnClose: streamOptions.finishTransOnClose], wDir: wDir, transHandle: transHandle],
$create => Create[name: name, pages: FS.PagesForBytes[createByteCount], keep: keep, options: [referencePattern: $sequential, finishTransOnClose: streamOptions.finishTransOnClose], wDir: wDir, transHandle: transHandle], -- do it this way once versions are implemented
$create, $append => OpenOrCreate[name: name, pages: FS.PagesForBytes[createByteCount], keep: keep, options: [referencePattern: $sequential, finishTransOnClose: streamOptions.finishTransOnClose], wDir: wDir, transHandle: transHandle],
$write => Open[name: name, access: $write, options: [referencePattern: $sequential, finishTransOnClose: streamOptions.finishTransOnClose], wDir: wDir, transHandle: transHandle],
ENDCASE => ERROR;
RETURN[StreamFromOpenFile[openFile: file, accessRights: IF accessOptions = $read THEN $read ELSE $write, initialPosition: IF accessOptions = $append THEN $end ELSE $start, streamOptions: streamOptions, streamBufferParms: streamBufferParms, extendFileProc: extendFileProc !
FS.Error => FS.Close[file]]];
END;
StreamFromOpenFile: PUBLIC PROCEDURE [openFile: OpenFile, accessRights: Lock ← $read, initialPosition: InitialPosition ← $start, streamOptions: StreamOptions ← [], streamBufferParms: StreamBufferParms ← defaultStreamBufferParms, extendFileProc: ExtendFileProc ← NIL] RETURNS [self: STREAM] =
BEGIN
fileData: FileData = FileDataFromOpenFile[openFile];
fileData.options.finishTransOnClose ← streamOptions.finishTransOnClose;
self ← FileStream.StreamFromOpenFile[openFile: openFile, accessRights: accessRights, initialPosition: initialPosition, streamOptions: [tiogaRead: streamOptions.tiogaRead, truncatePagesOnClose: streamOptions.truncatePagesOnClose, closeFSOpenFileOnClose: streamOptions.closeFSOpenFileOnClose], streamBufferParms: streamBufferParms, extendFileProc: extendFileProc];
self.streamProcs ← streamProcs[accessRights];
FileStream.SetStreamClassData[self, NEW [StreamClassDataObject ← [options: streamOptions]]];
FileStream.SetFinalizationProc[self, FinalizeStream];
END;
OpenFileFromStream: PUBLIC PROCEDURE [self: STREAM] RETURNS [OpenFile] =
BEGIN
RETURN [FileStream.OpenFileFromStream[self]];
END;
StreamFromOpenStream: PUBLIC PROCEDURE [self: STREAM] RETURNS [stream: STREAM] =
BEGIN
stream ← FileStream.StreamFromOpenStream[self];
stream.streamProcs ← streamProcs[read];
FileStream.SetStreamClassData[stream, GetStreamClassData[self]];
END;
ErrorFromStream: PUBLIC PROCEDURE [self: STREAM] RETURNS [ErrorDesc] =
BEGIN
RETURN [FileStream.ErrorFromStream[self]];
END;
StreamFlush: PROCEDURE [self: STREAM] =
BEGIN
FileStream.Flush[self];
IF GetStreamClassData[self].commitAndReopenTransOnFlush THEN
BEGIN ENABLE FS.Error => {FileStream.SaveStreamError[self, error]; GOTO failed};
file: OpenFile = FileStream.OpenFileFromStream[self];
fileData: FileData = FileDataFromOpenFile[file];
StreamFlushInner: PROCEDURE =
BEGIN
outcome: AlpTransaction.Outcome = fileData.alpFile.trans.Finish[requestedOutcome: commit, continue: TRUE];
IF outcome#commit THEN ProduceError[$transAborted, "Transaction aborted"];
END;
CallAlpine[StreamFlushInner];
EXITS
failed => ERROR IO.Error[$Failure, self];
END;
END;
StreamClose: PROCEDURE [self: STREAM, abort: BOOLFALSE] =
BEGIN ENABLE FS.Error => {FileStream.SaveStreamError[self, error]; GOTO failed};
IF abort AND GetStreamClassData[self].finishTransOnClose THEN
BEGIN
file: OpenFile = FileStream.OpenFileFromStream[self];
fileData: FileData = FileDataFromOpenFile[file];
fileData.options.finishTransOnClose ← FALSE;
FileStream.Close[self, abort];
Abort[file ! FS.Error => CONTINUE];
END
ELSE FileStream.Close[self, abort];
EXITS
failed => ERROR IO.Error[$Failure, self];
END;
streamProcs: ARRAY FS.Lock OF REF IO.StreamProcs = [
read: IO.CreateStreamProcs[variety: $input, class: $AlpineFS, getChar: FileStream.GetChar, endOf: FileStream.EndOf, charsAvail: FileStream.CharsAvail, unsafeGetBlock: FileStream.UnsafeGetBlock, flush: StreamFlush, reset: FileStream.Reset, close: StreamClose, getIndex: FileStream.GetIndex, setIndex: FileStream.SetIndex, backup: FileStream.Backup, getLength: FileStream.GetLength],
write: IO.CreateStreamProcs[variety: $inputOutput, class: $AlpineFS, getChar: FileStream.GetChar, endOf: FileStream.EndOf, charsAvail: FileStream.CharsAvail, unsafeGetBlock: FileStream.UnsafeGetBlock, putChar: FileStream.PutChar, putBlock: FileStream.PutBlock, unsafePutBlock: FileStream.UnsafePutBlock, flush: StreamFlush, reset: FileStream.Reset, close: StreamClose, getIndex: FileStream.GetIndex, setIndex: FileStream.SetIndex, backup: FileStream.Backup, getLength: FileStream.GetLength, setLength: FileStream.SetLength, eraseChar: FileStream.EraseChar]];
General file manipulation
FileInfo: PUBLIC PROCEDURE [name: ROPE, wantedCreatedTime: BasicTime.GMT ← BasicTime.nullGMT, wDir: ROPENIL, transHandle: AlpTransaction.Handle ← NIL] RETURNS [fullFName, attachedTo: ROPE, keep: CARDINAL, bytes: INT, created: BasicTime.GMT] =
BEGIN
NotImplemented[];
END;
EnumerateForInfo: PUBLIC PROCEDURE [pattern: ROPE, proc: InfoProc, wDir: ROPENIL, transHandle: AlpTransaction.Handle ← NIL] =
BEGIN
NotImplemented[];
END;
EnumerateForNames: PUBLIC PROCEDURE [pattern: ROPE, proc: NameProc, wDir: ROPENIL, transHandle: AlpTransaction.Handle ← NIL] =
BEGIN
NotImplemented[];
END;
Delete: PUBLIC PROCEDURE [name: ROPE, wantedCreatedTime: BasicTime.GMT ← BasicTime.nullGMT, wDir: ROPENIL, transHandle: AlpTransaction.Handle ← NIL] =
BEGIN
NotImplemented[];
END;
Copy: PUBLIC PROCEDURE [from, to: ROPE, wantedCreatedTime: BasicTime.GMT ← BasicTime.nullGMT, wDir: ROPENIL, transHandle: AlpTransaction.Handle ← NIL] RETURNS [toFName: ROPE] =
BEGIN
NotImplemented[];
END;
Rename: PUBLIC PROCEDURE [from, to: ROPE, wantedCreatedTime: BasicTime.GMT ← BasicTime.nullGMT, wDir: ROPENIL, transHandle: AlpTransaction.Handle ← NIL] RETURNS [toFName: ROPE] =
BEGIN
NotImplemented[];
END;
SetKeep: PUBLIC PROCEDURE [name: ROPE, keep: CARDINAL ← 1, wDir: ROPENIL, transHandle: AlpTransaction.Handle ← NIL] =
BEGIN
NotImplemented[];
END;
CreateLink: PUBLIC PROCEDURE [name: ROPE, referent: ROPE, wDir: ROPENIL, transHandle: AlpTransaction.Handle ← NIL] RETURNS [fullFName, referentName: ROPE] =
BEGIN
NotImplemented[];
END;
NotImplemented: PROCEDURE =
BEGIN
ProduceError[$notImplemented, "Attempted operation that is not yet implemented"];
END;
Private procedures
FileDataFromOpenFile: PROCEDURE [file: OpenFile] RETURNS [data: FileData] =
! FS.Error[$invalidOpenFile, $notImplemented];
Verifies that the OpenFile is of the AlpineFS class and is open; then returns its FileData reference. Called from non-generic AlpineFS procedures that have been passed an OpenFile directly.
BEGIN
WITH FSBackdoor.GetClientFileAndProcs[file].clientFile SELECT FROM
fileData: FileData =>
BEGIN
IF fileData.alpFile=NIL THEN ProduceError[$invalidOpenFile, "Attempted to access an OpenFile that has been closed"];
RETURN [fileData];
END;
ENDCASE => ProduceError[$notImplemented, "Attempted to perform AlpineFS operation on a non-Alpine OpenFile"];
END;
FileDataFromRef: PROCEDURE [clientFile: REF ANY] RETURNS [data: FileData] =
! FS.Error[$invalidOpenFile, $notImplemented];
Verifies that clientFile is an AlpineFS FileData record, then returns it. Called from generic FS procedures whose OpenFile handles have already been dereferenced.
BEGIN
WITH clientFile SELECT FROM
fileData: FileData =>
BEGIN
IF fileData.alpFile=NIL THEN ProduceError[$invalidOpenFile, "Attempted to access an OpenFile that has been closed"];
RETURN [fileData];
END;
ENDCASE => ProduceError[$notImplemented, "Attempted to perform AlpineFS operation on a non-Alpine OpenFile"];
END;
GetStreamClassData: PROCEDURE [self: STREAM] RETURNS [data: StreamClassData] = INLINE{
RETURN [NARROW [FileStream.GetStreamClassData[self]]]};
Destroy: PROCEDURE [fileData: FileData] =
BEGIN
fileData.alpFile ← NIL;
fileData.fullFName ← fileData.attachedTo ← NIL;
END;
CallAlpine: PROCEDURE [proc: PROCEDURE] =
! FS.Error;
Calls proc, catching any Alpine or RPC error that results and translating it to an appropriate FS error.
BEGIN
error: RECORD [code: ErrorCode, explanation: ROPE];
bug: RECORD [atom: ATOM, explanation: ROPE];
BEGIN
proc[ !
AlpInstance.Failed => {
error ← SELECT why FROM
$alpineDownOrCommunications, $alpineDown => [$serverInaccessible, "Cannot establish contact with server"],
$authenticateFailed => [$badCredentials, "Credentials rejected by Grapevine or Alpine"],
$badCallee => [$unknownServer, "No such server exists"],
$grapevineDownOrCommunications => [$regServersUnavailable, "Operation failed due to Grapevine unavailability"],
$mismatch => [$serverInaccessible, "Alpine server interface missing or incompatible"],
ENDCASE => ERROR;
GOTO failed};
AlpFile.AccessFailed => {
error ← SELECT missingAccess FROM
$handleReadWrite => [$wrongLock, "Attempted to write on file with only read access"],
$spaceQuota => [$quotaExceeded, "Requested operation would exceed owner's disk space quota"],
ENDCASE => [$accessDenied, "Insufficient permission to perform requested operation"];
GOTO failed};
AlpFile.LockFailed => {
error ← SELECT why FROM
$conflict => [$lockConflict, "Lock cannot be set because a conflicting lock is already set by another transaction"],
$timeout => [$lockTimeout, "Lock timed out, perhaps due to deadlock"],
ENDCASE => ERROR;
GOTO failed};
AlpFile.OperationFailed => {
error ← SELECT why FROM
$busy => [$serverBusy, "Server is too busy to start another transaction"],
$damagedLeaderPage => [$other, "Damaged file leader page"],
$insufficientSpace => [$volumeFull, "Server disk volume is full"],
$nonexistentFilePage => [$unknownPage, "Attempted to read or write a page that is beyond end of file"],
$quotaExceeded => [$quotaExceeded, "Requested operation would exceed owner's disk space quota"],
$regServersUnavailable => [$regServersUnavailable, "Operation failed due to Grapevine unavailability"],
$unwritableProperty => [$unwritableProperty, "Attempted to write the type or version property"],
ENDCASE => [ , NIL];
IF error.explanation#NIL THEN GOTO failed;
bug ← [$inexplicableError, IO.PutFR["AlpFile.OperationFailure[%g]", [cardinal[LOOPHOLE[why, CARDINAL]]]]];
GOTO bug};
AlpFile.StaticallyInvalid => {
bug ← [$staticallyInvalid, "AlpFile.StaticallyInvalid"];
GOTO bug};
AlpFile.Unknown => {
error ← SELECT what FROM
$coordinator => [$serverInaccessible, "Server cannot communicate with transaction coordinator"],
$openFileID, -- assume that this is caused by a transaction abort as opposed to a local programming blunder
$transID => [$transAborted, "Transaction aborted"],
$owner => [$ownerNotFound, "No such owner exists on the server"],
ENDCASE => [ , NIL];
IF error.explanation#NIL THEN GOTO failed;
bug ← [$inexplicableError, IO.PutFR["AlpFile.Unknown[%g]", [cardinal[LOOPHOLE[what, CARDINAL]]]]];
GOTO bug};
AlpFile.PossiblyDamaged => RESUME; -- since this is not implemented yet!
AlpineInterimDirectory.Error => {
error ← SELECT why FROM
$authenticateFailed => [$badCredentials, "Credentials rejected by Grapevine or Alpine"],
$damaged => [$other, "Damaged directory or file leader page"],
$fileAlreadyExists => [$fileAlreadyExists, "File with that name already exists"],
$fileNotFound => [$unknownFile, "No such file exists"],
$insufficientPermission => [$accessDenied, "Insufficient permission to perform requested operation"],
$lockFailed => [$lockConflict, "File lock cannot be set"],
$ownerNotFound => [$ownerNotFound, "No such owner exists on the server"],
$ownerRecordFull => [$other, "Failed to insert into owner data base"],
$quota => [$quotaExceeded, "Requested operation would exceed owner's disk space quota"],
$remoteCallFailed => [$remoteCallFailed, "Communication with server has broken down"],
$regServersUnavailable => [$regServersUnavailable, "Access check failed due to Grapevine unavailability"],
$serverBusy => [$serverBusy, "Server is too busy to start another transaction"],
$serverNotFound => [$unknownServer, "No such server exists"],
$transAborted => [$transAborted, "Transaction aborted"],
ENDCASE => ERROR;
GOTO failed};
RPC.CallFailed => {
error ← [$remoteCallFailed, "Communication with server has broken down"];
GOTO failed}
];
EXITS
failed => ProduceError[code: error.code, explanation: error.explanation];
bug => ERROR FS.Error[[group: bug, code: bug.atom, explanation: bug.explanation]];
END;
END;
EstablishTransaction: PROCEDURE [serverName: ROPE, existingTrans: AlpTransaction.Handle ← NIL] RETURNS [transHandle: AlpTransaction.Handle] =
! FS.Error[$badCredentials, $regServersUnavailable, $remoteCallFailed, $serverBusy, $serverInaccessible, $transAborted, $unknownServer];
Establishes preconditions for performing operations on the server identified by serverName. If existingTrans=NIL, a transaction is created with that server as coordinator and returned as transHandle. If existingTrans#NIL, a worker is created on that server for the existing transaction, whose coordinator need not be on the same server; existingTrans is returned as transHandle.
BEGIN
EstablishTransactionInner: PROCEDURE =
BEGIN
IF existingTrans=NIL THEN
BEGIN
instance: AlpInstance.Handle = AlpInstance.Create[fileStore: serverName];
transHandle ← AlpTransaction.Create[instHandle: instance];
END
ELSE BEGIN
AlpTransaction.CreateWorker[handle: transHandle, coordinator: transHandle.inst.fileStore];
transHandle ← existingTrans;
END;
END;
IF existingTrans#NIL AND serverName.Compare[existingTrans.inst.fileStore, FALSE]=equal THEN RETURN [existingTrans]
ELSE CallAlpine[EstablishTransactionInner];
END;
DecomposeFileName: PROCEDURE [name: ROPE, wDir: ROPENIL] RETURNS [fullFName: ROPE, cp: FS.ComponentPositions] =
! FS.Error[$illegalName];
Parses name and converts it to canonical form, applying wDir if necessary. Returns as cp a description of the structure of the returned fullFName; see the definition of FS.ComponentPositions. Requires that the server and directory parts exist and be non-empty.
BEGIN
dirOmitted: BOOLEAN;
IF wDir.Length[]=0 THEN
BEGIN
c: CHAR = name.Fetch[0];
IF c#'[ AND c#'/ THEN
IF (wDir ← NARROW [List.Assoc[key: $AlpineWorkingDirectory, aList: ProcessProps.GetPropList[]]]).Length[]=0 THEN GOTO missing;
END;
[fullFName: fullFName, cp: cp, dirOmitted: dirOmitted] ← FS.ExpandName[name: name, wDir: wDir];
IF dirOmitted OR cp.server.length=0 OR cp.dir.length=0 THEN GOTO missing;
EXITS
missing => ProduceError[$illegalName, "Server or directory part missing"];
END;
ExtractServerName: PROCEDURE [name: ROPE, wDir: ROPENIL] RETURNS [serverName: ROPE] =
! FS.Error[$illegalName];
BEGIN
fullFName: ROPE;
cp: FS.ComponentPositions;
[fullFName: fullFName, cp: cp] ← DecomposeFileName[name: name, wDir: wDir];
RETURN [fullFName.Substr[start: cp.server.start, len: cp.server.length]];
END;
GetPageCount: PROCEDURE [fileData: FileData] RETURNS [pages: AlpFile.PageCount] =
! Untranslated Alpine errors;
BEGIN
IF (pages ← fileData.size)<0 THEN pages ← fileData.size ← fileData.alpFile.GetSize[lock: [$read, fileData.lock.ifConflict]];
END;
Initialization and finalization
All the PackageRef garbage will go away when new SafeStorage is available. Therefore, this implementation is quick and dirty
packageRefList: LIST OF FileData ← NIL;
AddPackageRef: ENTRY PROCEDURE [fileData: FileData] =
BEGIN
packageRefList ← CONS[fileData, packageRefList];
END;
RemovePackageRef: ENTRY PROCEDURE [fileData: FileData] =
BEGIN
prev: LIST OF FileData ← NIL;
FOR item: LIST OF FileData ← packageRefList, item.rest UNTIL item=NIL DO
IF item.first=fileData THEN
BEGIN
IF prev=NIL THEN packageRefList ← item.rest ELSE prev.rest ← item.rest;
item.first ← NIL;
item.rest ← NIL;
EXIT;
END;
prev ← item;
ENDLOOP;
END;
FinalizeStream: FileStream.FinalizationProc --[openFile: FS.OpenFile, data: REF ANY, closed: BOOL]-- =
The stream class finalization procedure called from FileStream when a StreamData record is finalized.
BEGIN
streamClassData: StreamClassData = NARROW [data];
IF ~closed AND streamClassData.finishTransOnClose THEN
BEGIN ENABLE FS.Error => CONTINUE;
IF streamClassData.abortTransOnFinalize THEN Abort[openFile] ELSE Close[openFile];
END;
END;
FinalizeFileProcess: PROCEDURE =
This process finalizes FileDataObjects.
BEGIN
DO
Finish: PROCEDURE =
BEGIN
fileData.alpFile.Close[];
IF fileData.options.finishTransOnClose THEN [] ← fileData.alpFile.trans.Finish[commit];
END;
fileData: FileData ← NARROW[finalizationQueue.FQNext[]];
IF fileData.alpFile#NIL THEN CallAlpine[Finish ! FS.Error => CONTINUE];
Destroy[fileData];
RemovePackageRef[fileData]; -- Remove this when new SafeStorage is available
fileData ← NIL;
ENDLOOP;
END;
finalizationQueue: SafeStorage.FinalizationQueue = SafeStorage.NewFQ[];
Initialization
SafeStorage.EstablishFinalization[type: CODE[FileDataObject], npr: 1, fq: finalizationQueue];
TRUSTED {Process.Detach[FORK FinalizeFileProcess]};
END.