-- AlpineFile.mesa
-- Last edited by
--   Taft on January 25, 1983 11:15 am
--   MBrown on January 21, 1983 10:39 pm
--   Kolling on February 18, 1983 5:40 pm

DIRECTORY
  AlpineEnvironment,
  File USING [Type],
  FileTypes USING [tUntypedFile];

AlpineFile: DEFINITIONS =
  BEGIN
  
  AccessList: TYPE = AlpineEnvironment.AccessList;
  AccessRights: TYPE = AlpineEnvironment.AccessRights;
  Conversation: TYPE = AlpineEnvironment.Conversation;
  FileID: TYPE  = AlpineEnvironment.FileID;
  LockOption: TYPE = AlpineEnvironment.LockOption;
  OpenFileID: TYPE = AlpineEnvironment.OpenFileID;
  OwnerName: TYPE = AlpineEnvironment.OwnerName;
  PageCount: TYPE = AlpineEnvironment.PageCount;
  PageNumber: TYPE = AlpineEnvironment.PageNumber;
  RecoveryOption: TYPE = AlpineEnvironment.RecoveryOption;
  ReferencePattern: TYPE = AlpineEnvironment.ReferencePattern;
  String: TYPE = AlpineEnvironment.String;
  TransID: TYPE = AlpineEnvironment.TransID;
  VolOrVolGroupID: TYPE = AlpineEnvironment.VolOrVolGroupID;
  VolumeID: TYPE = AlpineEnvironment.VolumeID;


  -- Procedures which create OpenFileIDs

  Open: PROCEDURE [
    conversation: Conversation, transID: TransID, volumeID: VolumeID, fileID: FileID,
    access: AccessRights ← readOnly, lock: LockOption ← [intendRead, wait],
    recoveryOption: RecoveryOption ← log,
    referencePattern: ReferencePattern ← random]
    RETURNS [openFileID: OpenFileID];
    --! AccessFailed {fileRead, fileModify}, LockFailed, OperationFailed {damagedLeaderPage},
    --  PossiblyDamaged, StaticallyInvalid, Unknown {volumeID, fileID, transID};
    -- Opens an existing file described by volume and file for access under trans, and
    -- returns an OpenFileID which designates the open file.
    -- The client is required to be a member of the access control list implied by access
    -- (readAccess or modifyAccess); and if access=readOnly, the client is restricted to
    -- read-only operations on that OpenFileID.
    -- The entire file is locked in lock.mode (a wait or a Lock.Failed can result).
    -- lock.ifConflict is also remembered and is used when performing certain file actions
    -- (e.g., Delete) which do not take a LockMode as an argument.

  standardFile: File.Type = FileTypes.tUntypedFile;

  Create: PROCEDURE [
    conversation: Conversation, transID: TransID, volumeID: VolOrVolGroupID,
    owner: OwnerName, initialSize: PageCount, type: File.Type ← standardFile,
    recoveryOption: RecoveryOption ← log,
    referencePattern: ReferencePattern ← random]
    RETURNS [openFileID: OpenFileID];
    --! AccessFailed {ownerCreate, spaceQuota},
    --  OperationFailed {insufficientSpace, reservedType}, StaticallyInvalid,
    --  Unknown {volumeID, transID, owner};
    -- Creates and opens a new file under trans.  volumeID may be either a specific
    -- VolumeID or a VolumeGroupID; in the latter case, the server will choose among
    -- the volumes of the group.
    -- The initial size of the file is specified by initialSize; note that this specifies
    -- the number of data pages, and does not include any file overhead (e.g., leader page).
    -- The space is charged against owner in the owner data base; the client is required
    -- to be a member of owner's create access control list, and the allocation must not
    -- exceed owner's quota.
    -- The file is created with a Pilot FileType of type, and with Pilot attributes
    -- immutable=FALSE and temporary=FALSE.  All file properties are set to default values,
    -- which are: byteLength, highWaterMark: 0; createTime: now; readAccess: "World";
    -- modifyAccess: "Owner" + owner's create access control list;
    -- owner: owner specified in call; stringName: empty string; version: 1.
    -- If the call is successful, returns an OpenFileID with accessRights=readWrite and
    -- lockOption = [write, fail].


  -- Note: for all remaining operations, Unknown[openFileID] may be raised if:
  -- 1. The OpenFileID is no longer known to Alpine because it has been closed (either
  --    explicitly or as a result of committing or aborting the transaction).
  -- 2. The file it refers to has been deleted by this transaction, perhaps using
  --    a different OpenFileID.


  -- Procedures which destroy OpenFileIDs

  Close: PROCEDURE [conversation: Conversation, openFileID: OpenFileID];
    --! Unknown {openFileID, transID};
    -- Breaks the association between openFileID and its file.  The number of simultaneous
    -- open files permitted on one FileStore is large but not unlimited; clients are
    -- encouraged to close files which are no longer needed, particularly when referencing
    -- many files under one transaction.  Note that closing a file does not terminate
    -- the enclosing transaction and does not release any locks on the file; nor does it
    -- restrict the client's ability to later re-open the file under the same transaction.

  Delete: PROCEDURE [conversation: Conversation, openFileID: OpenFileID];
    --! AccessFailed {handleReadWrite}, LockFailed, Unknown {openFileID, transID};
    -- First locks the entire file in update mode; then deletes the file.  The file
    -- must have been opened with access=readWrite.  The OpenFileID is made invalid
    -- by this procedure.  Note that, like all destructive update actions, the deletion
    -- does not occur until the transaction is committed; however, subsequent attempts
    -- to access the file under this transaction will fail with Unknown[openFileID].
    -- The owner's allocation is not credited with the deleted pages until commit time.


  -- Procedures which access open file state

  GetVolumeID: PROCEDURE [conversation: Conversation, openFileID: OpenFileID]
    RETURNS [volumeID: VolumeID];
    --! Unknown {openFileID, transID};

  GetFileID: PROCEDURE [conversation: Conversation, openFileID: OpenFileID]
    RETURNS [fileID: FileID];
    --! Unknown {openFileID, transID};

  GetTransID: PROCEDURE [conversation: Conversation, openFileID: OpenFileID]
    RETURNS [transID: TransID];
    --! Unknown {openFileID, transID};

  GetAccessRights: PROCEDURE [conversation: Conversation, openFileID: OpenFileID]
    RETURNS [access: AccessRights];
    --! Unknown {openFileID, transID};

  GetLockOption: PROCEDURE [conversation: Conversation, openFileID: OpenFileID]
    RETURNS [lock: LockOption];
    --! Unknown {openFileID, transID};
    -- This may return a lock mode stronger than the one with which the file was
    -- originally opened if operations have been performed which upgrade the lock.
    -- Such operations may have been performed on either this OpenFileID or some
    -- other OpenFileID referring to the same file under the same transaction.

  SetLockOption: PROCEDURE [
    conversation: Conversation, openFileID: OpenFileID, lock: LockOption];
    --! LockFailed, StaticallyInvalid, Unknown {openFileID, transID};
    -- This actually changes the file lock, so it may wait or fail according to
    -- lock.ifConflict.  File locks may only be upgraded by this means; attempts
    -- to downgrade a lock are ignored.

  GetRecoveryOption: PROCEDURE [conversation: Conversation, openFileID: OpenFileID]
    RETURNS [recoveryOption: RecoveryOption];
    --! Unknown {openFileID, transID};

  GetReferencePattern: PROCEDURE [conversation: Conversation, openFileID: OpenFileID]
    RETURNS [referencePattern: ReferencePattern];
    --! Unknown {openFileID, transID};

  SetReferencePattern: PROCEDURE [
    conversation: Conversation, openFileID: OpenFileID, referencePattern: ReferencePattern];
    --! StaticallyInvalid, Unknown {openFileID, transID};


  -- Note: for operations which reference the contents of a file (data or properties),
  -- it is possible that several clients may be accessing the file concurrently under
  -- the same transaction (using the same or different OpenFileIDs).  Alpine makes no
  -- attempt to prevent or adjudicate conflicting access to the same data in this situation,
  -- except to assure that reads and writes of single individual file pages and properties
  -- are atomic with respect to each other.


  -- Procedures which access pages of the file

  PageRun: TYPE = AlpineEnvironment.PageRun;
  PageBuffer: TYPE = LONG DESCRIPTOR FOR ARRAY OF WORD;
  VALUEPageBuffer: TYPE = PageBuffer;
  RESULTPageBuffer: TYPE = PageBuffer;

  maxPagesPerRun: CARDINAL = LAST[CARDINAL]/AlpineEnvironment.wordsPerPage;
    -- This is all that can be described with a DESCRIPTOR (PageBuffer).
    -- This restriction applies only to ReadPages and WritePages.
    
  ReadPages: PROCEDURE [
    conversation: Conversation, openFileID: OpenFileID, pageRun: PageRun,
    pageBuffer: RESULTPageBuffer, lock: LockOption ← [read, wait]];
    --! OperationFailed {inconsistentDescriptor, nonexistentFilePage}, LockFailed,
    --  StaticallyInvalid, Unknown {openFileID, transID};
    -- Reads data from the pages described by pageRun of the file associated with
    -- openFileID, and puts it contiguously into client memory in the block described
    -- by pageBuffer (whose length must be consistent with pageRun.count or else
    -- OperationFailed[inconsistentDescriptor] is raised).  If the entire file is not
    -- already locked in at least the mode specified by lock, sets locks in that mode
    -- on the individual pages, and upgrades the file lock to the corresponding
    -- intention mode if necessary.

  WritePages: PROCEDURE [
    conversation: Conversation, openFileID: OpenFileID, pageRun: PageRun,
    pageBuffer: VALUEPageBuffer, lock: LockOption ← [write, wait]];
    --! AccessFailed {handleReadWrite}, LockFailed,
    --  OperationFailed {nonexistentFilePage}, StaticallyInvalid, Unknown {openFileID, transID};
    -- Writes data from client memory in the block described by pageBuffer to the pages
    -- described by pageRun of the file associated with openFileID.  If the entire file
    -- is not already locked in at least the mode specified by lock, sets locks in that
    -- mode on the individual pages, and upgrades the file lock to the corresponding
    -- intention mode if necessary.  The file must have been opened with access=readWrite.
    
  LockPages: PROCEDURE [
    conversation: Conversation, openFileID: OpenFileID, pageRun: PageRun,
    lock: LockOption ← [read, wait]];
    --! LockFailed, StaticallyInvalid, Unknown {openFileID, transID};
    -- Explicitly sets locks on the specified pages of the file.

  UnlockPages: PROCEDURE [
    conversation: Conversation, openFileID: OpenFileID, pageRun: PageRun];
    --! StaticallyInvalid, Unknown {openFileID, transID};
    -- If the specified pages of the file are locked in a mode no stronger than read,
    -- then removes those locks.  It is the client's responsibility to assure consistency
    -- of any subsequent operations whose behavior depends on the data which was read
    -- under those locks.  Note that locks are reference-counted and are not
    -- removed until one UnlockPages has been done for each LockPages or ReadPages
    -- previously performed on the same pages.  Attempts to remove nonexistent locks
    -- or locks stronger than read are ignored without error indication.  File intention
    -- locks that were set while acquiring the page locks are not released.  


  -- Procedures which access properties of the file

  Property: TYPE = AlpineEnvironment.Property;
  PropertyValuePair: TYPE = AlpineEnvironment.PropertyValuePair;

  PropertySet: TYPE = PACKED ARRAY Property OF FalseBool;
  FalseBool: TYPE = BOOLEAN ← FALSE;
  allProperties: PropertySet = ALL [TRUE];

  ByteCount: TYPE = AlpineEnvironment.ByteCount;
  FileVersion: TYPE = AlpineEnvironment.FileVersion;

  ReadProperties: PROCEDURE [
    conversation: Conversation, openFileID: OpenFileID,
    desiredProperties: PropertySet ← allProperties,
    lock: LockOption ← [read, wait]]
    RETURNS [properties: LIST OF PropertyValuePair];
    --! LockFailed, StaticallyInvalid, Unknown {openFileID, transID};
    -- Reads the properties specified by desiredProperties, ordered as in the declaration
    -- of Property.  Locks those properties as specified by lock.  Currently, all properties
    -- with the exception of version are treated together with respect to locking;
    -- the version has a separate lock.  Note that reading the version will prevent
    -- any other transaction which updates the file from committing, unless the version
    -- lock is later removed (by UnlockVersion).

  WriteProperties: PROCEDURE [
    conversation: Conversation, openFileID: OpenFileID,
    properties: LIST OF PropertyValuePair, lock: LockOption ← [write, wait]];
    --! AccessFailed {handleReadWrite, ownerCreate, spaceQuota}, LockFailed,
    --  OperationFailed {insufficientSpace, unwritableProperty}, StaticallyInvalid,
    --  Unknown {openFileID, owner};
    -- Writes the supplied properties, after first locking them in the specified mode.
    -- The type and version properties may not be written by this means.
    -- To write the byteLength, createTime, highWaterMark, and stringName properties
    -- requires that the OpenFileID be open with access=readWrite.  To write readAccess
    -- or modifyAccess additionally requires that the client be the file's owner or a
    -- member of the create access control list for the file's owner.  To write owner
    -- additionally requires that the client be a member of the create access control
    -- list for the new owner; and the disk space occupied by the file is credited to
    -- the old owner and charged to the new one.
    -- If there is insufficient space in the leader page to represent the new properties,
    -- OperationFailed[insufficientSpace] is raised.  Note that if multiple properties
    -- are written by one call and an error occurs, some of the properties may nevertheless
    -- have been written successfully.

  UnlockVersion: PROCEDURE [conversation: Conversation, openFileID: OpenFileID];
    --! Unknown {openFileID, transID};
    -- Unlocks a read lock previously set on the version property.  All comments on
    -- UnlockPages apply here also.

  IncrementVersion: PROCEDURE [
    conversation: Conversation, openFileID: OpenFileID, increment: LONG INTEGER];
    --! AccessFailed {handleReadWrite}, StaticallyInvalid, Unknown {openFileID, transID};
    -- Arranges that at transaction commit time, increment will be added to the version
    -- property, instead of 1 or 0 (depending on whether or not the transaction has
    -- performed any updates to the file).  Note that the change in version number
    -- is not visible, even to the transaction that incremented it, until commit time.

  GetSize: PROCEDURE [
    conversation: Conversation, openFileID: OpenFileID, lock: LockOption ← [read, wait]]
    RETURNS [size: PageCount];
    --! LockFailed, Unknown {openFileID, transID};
    -- Returns the file's size, after setting a lock on the size property.  Note that
    -- the returned value is the number of data pages, and does not include any file
    -- overhead (e.g., leader page).

  SetSize: PROCEDURE [
    conversation: Conversation, openFileID: OpenFileID, size: PageCount,
    lock: LockOption ← [write, wait]];
    --! AccessFailed {handleReadWrite, spaceQuota}, LockFailed,
    --  OperationFailed {insufficientSpace}, StaticallyInvalid, Unknown {openFileID, transID};
    -- Changes the file's size to the new size specified, after setting an update or write
    -- lock on it.  Additionally, decreasing the file's size locks the entire file in the
    -- same mode.  Note that the size is the number of data pages, and does not include
    -- any file overhead (e.g., leader page).  If the new size is less than the file's
    -- high water mark, sets the high water mark equal to size.
    -- Requires that the OpenFileID be open with access=readWrite.  Appropriately adjusts
    -- the disk space charged to the file's owner; however, it is not required that
    -- the client be a member of the owner's create access control list.
    -- Note that allocation is consumed immediately when a file's size is increased;
    -- but when the size is decreased, the allocation is not credited with the freed
    -- pages until transaction commit time.


  -- Exceptions

  AccessFailed: ERROR [missingAccess: AlpineEnvironment.NeededAccess];

  LockFailed: ERROR [why: AlpineEnvironment.LockFailure];
    -- See AlpineEnvironment for a description of LockFailure.

  OperationFailed: ERROR [why: AlpineEnvironment.OperationFailure];

  StaticallyInvalid: ERROR;
    -- Raised by any operation if passed unreasonable arguments, where unreasonableness
    -- may be determined statically and does not depend on the state of the file system.
    -- The most common cause is an enumerated or subrange argument out of bounds, which
    -- can generally occur only if the client passed it through a LOOPHOLE.

  Unknown: ERROR [what: AlpineEnvironment.UnknownType];

  PossiblyDamaged: SIGNAL;

  END.