FileActionsImpl.mesa
Copyright © 1984, 1985 by Xerox Corporation. All rights reserved.
Carl Hauser, March 26, 1987 9:36:04 am PST
Last edited by:
Taft on May 25, 1983 4:59 pm
MBrown on February 1, 1984 5:00:04 pm PST
Kolling on May 31, 1983 5:39 pm
Last Edited by: Kupfer, February 28, 1985 1:22:25 pm PST
Loose ends:
1. When we get around to implementing the resumable signal PossiblyDamaged, the catch of ANY in EstablishTransactionContext will need to be reconsidered.
2. If client can predict what OpenFileID will be returned by Open or Create, he may be able to sneak in by virtue of the temporary access provided for AccessControl operations. Is this worth fixing? If so, how?
3. Similarly, if client can predict what FileID will be generated by Create, he may be able to do an Open that references the leader page before it has been initialized, with undefined results.
4. Should we prevent a client from setting locks inconsistent with the client's access to the file (e.g., write locks on a read-only file, or any locks on a read-protected file)? Permitting this enables a client to tie up an arbitrary file with locks and thereby interfere with other clients' access.
5. Create needs to restrict file types.
DIRECTORY
AccessControl,
AlpineEnvironment,
AlpineFile,
AlpineInternal,
FileInstance,
FileLock,
FileLog,
FileMap,
FilePageMgr,
FilePrivate,
LeaderPage,
LogMap,
OpenFileMap,
Rope USING [Cat, Equal, ROPE],
SkiPatrolHooks USING [FileInfoToTransID],
SkiPatrolLog USING [notice, OpFailureInfo],
TransactionMap,
VolumeGroup;
FileActionsImpl: PROGRAM
IMPORTS AccessControl, AlpineFile, FileInstance, FileLock, FileLog, FileMap, FilePageMgr, FilePrivate, LeaderPage, LogMap, OpenFileMap, Rope, SkiPatrolHooks, SkiPatrolLog, TransactionMap, VolumeGroup
EXPORTS AlpineFile, FileLog, FilePrivate =
BEGIN OPEN AlpineFile;
AlpineFile.
Open: PUBLIC PROCEDURE [conversation: Conversation, transID: TransID, universalFile: UniversalFile, access: AccessRights ← readOnly, lock: LockOption ← [intendRead, wait], recoveryOption: RecoveryOption ← log, referencePattern: ReferencePattern ← random] RETURNS [openFileID: OpenFileID, fileID: FileID] =
BEGIN
Work: FilePrivate.TransactionWork --[trans, pUpdateCost]-- =
BEGIN
openFile: OpenFileMap.Handle;
fileInstance: FileInstance.Handle;
file: FileMap.Handle;
actualVolumeID: VolumeID;
description: LogMap.FileDescription;
stage: {nothing, handleRegistered, fileLocked} ← nothing;
nameOwnerProperties: LIST OF AlpineEnvironment.PropertyValuePair; -- holds file name and owner name
fileName: Rope.ROPE;  -- gets the name from nameOwnerProperties
fileOwner: Rope.ROPE;  -- gets the owner from nameOwnerProperties
nameOwnerPropertyList: AlpineFile.PropertySet;
BEGIN ENABLE { -- Extra nesting so above variables accessible in ENABLE and EXITS clauses
PossiblyDamaged => NULL;
UNWIND, AccessFailed, OperationFailed, StaticallyInvalid, Unknown => {
IF stage>=fileLocked THEN FileLock.ReleaseFileLock[fileInstance];
IF stage>=handleRegistered THEN OpenFileMap.Unregister[openFile];
stage ← nothing;
}};
BEGIN -- More extra nesting so ENABLE in force during EXITS clause
IF access NOT IN AccessRights OR lock.mode NOT IN AlpineEnvironment.LockMode OR recoveryOption NOT IN RecoveryOption OR referencePattern NOT IN ReferencePattern THEN ERROR StaticallyInvalid;
[openFile, openFileID] ← OpenFileMap.Register[conversation: conversation, trans: trans, volumeID: universalFile.volumeID, fileID: universalFile.fileID];
stage ← handleRegistered;
fileInstance ← OpenFileMap.GetFileInstanceHandle[openFile];
file ← FileInstance.GetFileHandle[fileInstance];
Ensure that volumeID identifies an actual volume (as opposed to a volume group), and read-lock the volume group as a side-effect. (Read-locking the volume group is required before several of the AccessControl operations that can be performed on open files, though it is not required during Open itself.)
[volumeID: actualVolumeID] ← VolumeGroup.Identify[volID: universalFile.volumeID, trans: trans, lock: [read, lock.ifConflict] !
VolumeGroup.Failed => GOTO unknownVolumeID];
IF universalFile.volumeID#actualVolumeID THEN GOTO unknownVolumeID;
Acquire file lock before calling AccessControl, because it must be able to read the leader page via this OpenFileID. Also this ensures that the file won't disappear out from under us once we have determined that it in fact exists.
FileLock.AcquireFileLock[fileInstance: fileInstance, requested: lock, minimum: intendRead];
stage ← fileLocked;
description ← LogMap.DescribeFile[file, trans];
IF description.registered AND ~description.exists THEN GOTO unknownFileID;
OpenFileMap.SetAccessRights[openFile, readOnly];
LeaderPage.Validate[fileInstance !
LeaderPage.Error => GOTO damagedLeaderPage];
IF ~AccessControl.PermissionToAccessFile[conversation: conversation, transHandle: trans, openFileID: openFileID, requestedAccess: access, lockOption: lock] THEN
ERROR AccessFailed[IF access=readOnly THEN fileRead ELSE fileModify];
OpenFileMap.SetAccessRights[openFile, access];
OpenFileMap.SetRecoveryOption[openFile, recoveryOption];
OpenFileMap.SetReferencePattern[openFile, referencePattern];
Record the file's human-legible name. We can't do this when we register the FileMap.Handle (the information isn't available then), so we might as well wait until we can use GetPropertyList. If the stringName is NIL, then we record the owner name to get some grasp of what the file is.
nameOwnerPropertyList[owner] ← nameOwnerPropertyList[stringName] ← TRUE;
nameOwnerProperties ← LeaderPage.GetPropertyList[fileInstance: fileInstance, desiredProperties: nameOwnerPropertyList];
WHILE nameOwnerProperties # NIL DO
WITH nameOwnerProperties.first SELECT FROM
stringName => fileName ← stringName;
owner => fileOwner ← owner;
ENDCASE => ERROR;
nameOwnerProperties ← nameOwnerProperties.rest;
ENDLOOP;
IF fileName = NIL THEN fileName ← Rope.Cat["(unnamed file owned by ", fileOwner, ")"];
FileMap.SetName[file, fileName];
Must log the setting of the lock, since opening a file does not give rise to any log records which would cause the lock to be set during recovery. Note: do not do this until all possibility of error has passed.
IF lock.mode IN [update..write] THEN
FileLog.LogFileLock[fileInstance: fileInstance];
fileID ← universalFile.fileID;
EXITS
damagedLeaderPage => {
logProc: PROC [SkiPatrolLog.OpFailureInfo];
IF (logProc ← SkiPatrolLog.notice.operationFailed) # NIL THEN
logProc[[
what: damagedLeaderPage,
transID: transID,
where: "FileActionsImpl.Open",
message: ""
]];
ERROR OperationFailed[damagedLeaderPage];
};
unknownFileID => ERROR Unknown[fileID];
unknownVolumeID => ERROR Unknown[volumeID];
END;
END;
END; -- Work
FilePrivate.EstablishTransactionContext[conversation, transID, Work];
END;
Create: PUBLIC PROCEDURE [conversation: Conversation, transID: TransID, volumeID: VolOrVolGroupID, owner: OwnerName, initialSize: PageCount, recoveryOption: RecoveryOption ← log, referencePattern: ReferencePattern ← random] RETURNS [openFileID: OpenFileID, universalFile: UniversalFile] =
BEGIN
Work: FilePrivate.TransactionWork --[trans, pUpdateCost]-- =
BEGIN
openFile: OpenFileMap.Handle;
fileInstance: FileInstance.Handle;
file: FileMap.Handle;
volumeGroupID: AlpineEnvironment.VolumeGroupID;
stage: {nothing, spaceChanged, handleRegistered} ← nothing;
BEGIN ENABLE { -- Extra nesting so above variables accessible in ENABLE and EXITS clauses
PossiblyDamaged => NULL;
UNWIND, AccessFailed, OperationFailed, StaticallyInvalid, Unknown => {
IF stage >= handleRegistered THEN OpenFileMap.Unregister[openFile];
IF stage >= spaceChanged THEN
IF ~AccessControl.ChangeSpaceViaOwner[transHandle: trans, volGroupID: volumeGroupID, ownerName: owner, nPages: -(initialSize+1)] THEN ERROR;
stage ← nothing;
}};
BEGIN -- More extra nesting so ENABLE in force during EXITS clause
IF owner=NIL OR initialSize NOT IN [0..AlpineEnvironment.maxPagesPerFile] OR --type NOT IN [ ... legal Alpine file types ... ] OR-- recoveryOption NOT IN RecoveryOption OR referencePattern NOT IN ReferencePattern THEN
ERROR StaticallyInvalid;
Note: as a side-effect, this read-locks the volume group, which is required by several of the AccessControl procedures which we call subsequently.
[volumeID: universalFile.volumeID, volumeGroupID: volumeGroupID] ← VolumeGroup.Identify[volID: volumeID, trans: trans, lock: [read, fail] !
VolumeGroup.Failed => GOTO unknownVolumeID];
IF ~AccessControl.PermissionToCreateFilesForThisOwner[conversation: conversation, transHandle: trans, volGroupID: volumeGroupID, ownerName: owner] THEN ERROR AccessFailed[ownerCreate];
IF ~AccessControl.ChangeSpaceViaOwner[transHandle: trans, volGroupID: volumeGroupID, ownerName: owner, nPages: initialSize+1] THEN ERROR AccessFailed[spaceQuota];
stage ← spaceChanged;
IF universalFile.volumeID=AlpineEnvironment.nullVolumeID THEN
universalFile.volumeID ← VolumeGroup.SelectVolumeForCreate[volumeGroupID, initialSize+1 !
VolumeGroup.Failed => GOTO insufficientSpace];
{
Create1: SAFE PROC [fileID: AlpineEnvironment.FileID] = TRUSTED {
IF stage = handleRegistered THEN {
-- cleanup volatile structures from last call
OpenFileMap.Unregister[openFile];
stage ← spaceChanged;
};
universalFile.fileID ← fileID;
[openFile, openFileID] ← OpenFileMap.Register[conversation: conversation, trans: trans, volumeID: universalFile.volumeID, fileID: fileID];
stage ← handleRegistered;
fileInstance ← OpenFileMap.GetFileInstanceHandle[openFile];
file ← FileInstance.GetFileHandle[fileInstance];
FileLock.AcquireFileLock[fileInstance: fileInstance, requested: [write, fail], minimum: write];
FileLog.LogCreate[fileInstance: fileInstance, initialSize: initialSize, owner: owner];
};
FilePageMgr.Create[
volumeID: universalFile.volumeID, initialSize: initialSize, proc: Create1 !
FilePageMgr.VolumeTooFragmented => GOTO volumeFragmented;
FilePageMgr.InsufficientSpaceOnVolume => GOTO insufficientSpace];
LogMap.RegisterCreate[file: file, trans: trans];
};
pUpdateCost^ ← 20 + initialSize/5; -- Creating a file has a substantial fixed overhead and a relatively small additional cost proportional to the initial size of the file
OpenFileMap.SetAccessRights[openFile, readWrite];
OpenFileMap.SetRecoveryOption[openFile, recoveryOption];
OpenFileMap.SetReferencePattern[openFile, referencePattern];
[] ← FileInstance.SetHighWaterMark[fileInstance, 0];
LogMap.SetCommittedHighWaterMark[file, 0];
LeaderPage.Initialize[fileInstance];
LeaderPage.SetProperty[fileInstance: fileInstance, propertyValue: [owner[owner]]];
FOR access: AccessRights IN AccessRights DO
accessList: AccessList = AccessControl.GetDefaultForFileAccessList[transHandle: trans, volGroupID: volumeGroupID, ownerName: owner, accessListType: access];
LeaderPage.SetProperty[fileInstance: fileInstance, propertyValue: IF access=readOnly THEN [readAccess[accessList]] ELSE [modifyAccess[accessList]]];
ENDLOOP;
EXITS
unknownVolumeID => ERROR Unknown[volumeID];
volumeFragmented => {
logProc: PROC [SkiPatrolLog.OpFailureInfo];
IF (logProc ← SkiPatrolLog.notice.operationFailed) # NIL THEN
logProc[[
what: volumeFragmented,
transID: transID,
where: "FileActionsImpl.Create",
message: ""
]];
ERROR OperationFailed[volumeFragmented];
};
insufficientSpace => {
logProc: PROC [SkiPatrolLog.OpFailureInfo];
IF (logProc ← SkiPatrolLog.notice.operationFailed) # NIL THEN
logProc[[
what: insufficientSpace,
transID: transID,
where: "FileActionsImpl.Create",
message: ""
]];
ERROR OperationFailed[insufficientSpace];
};
END;
END;
END; -- Work
FilePrivate.EstablishTransactionContext[conversation: conversation, transID: transID, work: Work, workLevel: hard]; -- This must start work with workLevel=hard because it dirties the owner data base
END;
Close: PUBLIC PROCEDURE [conversation: Conversation, openFileID: OpenFileID] =
BEGIN
Work: FilePrivate.OpenFileWork --[openFile, fileInstance, file, trans, pUpdateCost]-- =
BEGIN
OpenFileMap.Unregister[openFile];
END; -- Work
FilePrivate.EstablishOpenFileContext[conversation: conversation, openFileID: openFileID, work: Work, concurrency: exclusive]; -- This must start work with concurrency=exclusive so as to avoid unexpected errors in Delete and SetSize caused by the OpenFileID becoming invalid in mid-execution
END;
Delete: PUBLIC PROCEDURE [conversation: Conversation, openFileID: OpenFileID] =
BEGIN
Work: FilePrivate.OpenFileWork --[openFile, fileInstance, file, trans, pUpdateCost]-- =
BEGIN
uncommittedSize, committedSize: PageCount;
IF OpenFileMap.GetAccessRights[openFile]#readWrite THEN
ERROR AccessFailed[handleReadWrite];
FileLock.AcquireFileLock[fileInstance: fileInstance, requested: [update, OpenFileMap.GetLockOption[openFile].ifConflict], minimum: update];
FilePrivate.AcquireFileInterlock[file]; -- Do this AFTER acquiring lock so we don't wait with the file interlock set
Credit the file's size (plus leader page) to the owner's space quota. Note that we use the uncommitted size (as viewed from this transaction), since if that differs from the committed size then the transaction must previously have done a SetSize, which already adjusted the space quota by the amount of the change.
committedSize ← LogMap.GetCommittedSize[file];
IF committedSize=LAST[PageCount] THEN committedSize ← FilePageMgr.GetSize[file];
uncommittedSize ← LogMap.GetUncommittedSize[file: file, trans: trans];
IF uncommittedSize=LAST[PageCount] THEN uncommittedSize ← committedSize;
IF ~AccessControl.ChangeSpaceViaOpenFileID[conversation: conversation, transHandle: trans, openFileID: openFileID, nPages: -(uncommittedSize+1)] THEN ERROR;
Ensure that any committed deferred updates to the file have been carried out so as to avoid confusion after commit resulting from attempts to perform updates to a nonexistent file. Note that there cannot be any uncommitted updates for any transaction besides this one, because the file is locked. Uncommitted updates for this transaction are no problem because they are simply ignored at commit time. Note: if this transaction previously shortened the file, pages beyond uncommittedSize were already carried out by SetSize. Therefore, flush only up to uncommittedSize. This is important because LogMap will raise an error if the pageRun extends beyond uncommittedSize.
FilePrivate.PerformAnyCommittedIntentions[fileInstance: fileInstance, pageRun: [firstPage: 0, count: uncommittedSize]];
Now actually register the delete intention.
FileLog.LogDelete[fileInstance];
LogMap.RegisterDelete[file: file, trans: trans];
FileInstance.SetMaxDeltaVersion[fileInstance, 1]; -- note update
OpenFileMap.Unregister[openFile];
END; -- Work
FilePrivate.EstablishOpenFileContext[conversation: conversation, openFileID: openFileID, work: Work, workLevel: hard]; -- This must start work with workLevel=hard because it dirties the owner data base. Start with concurrency=normal until locks have been set, then upgrade to concurrency=exclusive so as to avoid inconsistencies in changing the space quota.
END;
GetUniversalFile: PUBLIC PROCEDURE [conversation: Conversation, openFileID: OpenFileID] RETURNS [universalFile: UniversalFile] =
BEGIN
Work: FilePrivate.OpenFileWork --[openFile, fileInstance, file, trans, pUpdateCost]-- =
BEGIN
universalFile.volumeID ← FileMap.GetVolumeID[file];
universalFile.fileID ← FileMap.GetFileID[file];
END; -- Work
FilePrivate.EstablishOpenFileContext[conversation, openFileID, Work];
END;
GetTransID: PUBLIC PROCEDURE [conversation: Conversation, openFileID: OpenFileID] RETURNS [transID: TransID] =
BEGIN
Work: FilePrivate.OpenFileWork --[openFile, fileInstance, file, trans, pUpdateCost]-- =
BEGIN
transID ← TransactionMap.GetTransID[FileInstance.GetTransHandle[fileInstance]];
END; -- Work
FilePrivate.EstablishOpenFileContext[conversation, openFileID, Work];
END;
GetAccessRights: PUBLIC PROCEDURE [conversation: Conversation, openFileID: OpenFileID] RETURNS [access: AccessRights] =
BEGIN
Work: FilePrivate.OpenFileWork --[openFile, fileInstance, file, trans, pUpdateCost]-- =
BEGIN
access ← OpenFileMap.GetAccessRights[openFile];
END; -- Work
FilePrivate.EstablishOpenFileContext[conversation, openFileID, Work];
END;
GetLockOption: PUBLIC PROCEDURE [conversation: Conversation, openFileID: OpenFileID] RETURNS [lock: LockOption] =
BEGIN
Work: FilePrivate.OpenFileWork --[openFile, fileInstance, file, trans, pUpdateCost]-- =
BEGIN
lock ← OpenFileMap.GetLockOption[openFile];
END; -- Work
FilePrivate.EstablishOpenFileContext[conversation, openFileID, Work];
END;
SetLockOption: PUBLIC PROCEDURE [conversation: Conversation, openFileID: OpenFileID, lock: LockOption] =
BEGIN
Work: FilePrivate.OpenFileWork --[openFile, fileInstance, file, trans, pUpdateCost]-- =
BEGIN
IF lock.mode NOT IN AlpineEnvironment.LockMode THEN ERROR StaticallyInvalid;
This will set the mode to the MAX of lock.mode and its existing mode.
FileLock.AcquireFileLock[fileInstance: fileInstance, requested: lock, minimum: FileInstance.GetLockMode[fileInstance]];
OpenFileMap.SetLockOption[ openFile, [FileInstance.GetLockMode[fileInstance], lock.ifConflict]];
END; -- Work
FilePrivate.EstablishOpenFileContext[conversation, openFileID, Work];
END;
GetRecoveryOption: PUBLIC PROCEDURE [conversation: Conversation, openFileID: OpenFileID] RETURNS [recoveryOption: RecoveryOption] =
BEGIN
Work: FilePrivate.OpenFileWork --[openFile, fileInstance, file, trans, pUpdateCost]-- =
BEGIN
recoveryOption ← OpenFileMap.GetRecoveryOption[openFile];
END; -- Work
FilePrivate.EstablishOpenFileContext[conversation, openFileID, Work];
END;
GetReferencePattern: PUBLIC PROCEDURE [conversation: Conversation, openFileID: OpenFileID] RETURNS [referencePattern: ReferencePattern] =
BEGIN
Work: FilePrivate.OpenFileWork --[openFile, fileInstance, file, trans, pUpdateCost]-- =
BEGIN
referencePattern ← OpenFileMap.GetReferencePattern[openFile];
END; -- Work
FilePrivate.EstablishOpenFileContext[conversation, openFileID, Work];
END;
SetReferencePattern: PUBLIC PROCEDURE [ conversation: Conversation, openFileID: OpenFileID, referencePattern: ReferencePattern] =
BEGIN
Work: FilePrivate.OpenFileWork --[openFile, fileInstance, file, trans, pUpdateCost]-- =
BEGIN
IF referencePattern NOT IN ReferencePattern THEN ERROR StaticallyInvalid;
OpenFileMap.SetReferencePattern[openFile, referencePattern];
END; -- Work
FilePrivate.EstablishOpenFileContext[conversation, openFileID, Work];
END;
ReadProperties: PUBLIC PROCEDURE [conversation: Conversation, openFileID: OpenFileID, desiredProperties: PropertySet ← allProperties, lock: LockOption ← [read, wait]] RETURNS [properties: LIST OF PropertyValuePair] =
BEGIN
Work: FilePrivate.OpenFileWork --[openFile, fileInstance, file, trans, pUpdateCost]-- =
BEGIN
IF lock.mode NOT IN AlpineEnvironment.LockMode THEN
ERROR StaticallyInvalid;
properties ← LeaderPage.GetPropertyList[fileInstance: fileInstance, desiredProperties: desiredProperties, lock: lock];
END; -- Work
FilePrivate.EstablishOpenFileContext[conversation, openFileID, Work];
END;
WriteProperties: PUBLIC PROCEDURE [conversation: Conversation, openFileID: OpenFileID, properties: LIST OF PropertyValuePair, lock: LockOption ← [update, wait]] =
BEGIN
Work: FilePrivate.OpenFileWork --[openFile, fileInstance, file, trans, pUpdateCost]-- =
BEGIN
IF lock.mode NOT IN AlpineEnvironment.LockMode THEN ERROR StaticallyInvalid;
FOR item: LIST OF PropertyValuePair ← properties, item.rest UNTIL item=NIL DO
IF item.first.property NOT IN Property THEN ERROR StaticallyInvalid;
SELECT item.first.property FROM
modifyAccess, readAccess => NULL;
ENDCASE =>
IF OpenFileMap.GetAccessRights[openFile] # readWrite THEN ERROR AccessFailed[handleReadWrite];
ENDLOOP;
FOR item: LIST OF PropertyValuePair ← properties, item.rest UNTIL item=NIL DO
ENABLE
LeaderPage.Error => IF errorType=full THEN GOTO leaderPageFull;
WITH propertyValue: item.first SELECT FROM
highWaterMark =>
BEGIN
IF propertyValue.highWaterMark NOT IN [0..AlpineEnvironment.maxPagesPerFile] THEN ERROR StaticallyInvalid;
LeaderPage.SetProperty[fileInstance: fileInstance, propertyValue: propertyValue, lock: lock];
END;
modifyAccess, readAccess =>
BEGIN
IF ~AccessControl.PermissionToModifyFileAccessList[conversation: conversation, transHandle: trans, openFileID: openFileID] THEN ERROR AccessFailed[ownerCreate];
LeaderPage.SetProperty[fileInstance: fileInstance, propertyValue: propertyValue, lock: lock];
END;
owner =>
BEGIN
size: PageCount;
oldOwner: PropertyValuePair[owner];
volumeGroupID: AlpineEnvironment.VolumeGroupID ← VolumeGroup.Identify[volID: FileMap.GetVolumeID[file]].volumeGroupID;
stage: {nothing, ownerChanged, spaceChanged} ← nothing;
BEGIN ENABLE
UNWIND, AccessFailed => {
IF stage>=ownerChanged THEN
LeaderPage.SetProperty[fileInstance: fileInstance, propertyValue: oldOwner, lock: lock];
IF stage>=spaceChanged THEN
IF ~AccessControl.ChangeSpaceViaOwner[transHandle: trans, volGroupID: volumeGroupID, ownerName: propertyValue.owner, nPages: -(size+1)] THEN ERROR;
stage ← nothing};
IF propertyValue.owner=NIL THEN ERROR StaticallyInvalid;
IF ~AccessControl.PermissionToModifyFileAccessList[conversation: conversation, transHandle: trans, openFileID: openFileID] THEN ERROR AccessFailed[ownerCreate];
IF ~AccessControl.PermissionToCreateFilesForThisOwner[conversation: conversation, transHandle: trans, volGroupID: volumeGroupID, ownerName: propertyValue.owner] THEN ERROR AccessFailed[ownerCreate];
size ← AlpineFile.GetSize[conversation: conversation, openFileID: openFileID];
Change owner name in leader page, but remember the old one in case we later need to back out.
oldOwner ← NARROW[LeaderPage.GetProperty[fileInstance: fileInstance, property: owner, lock: [read, lock.ifConflict]], PropertyValuePair[owner]];
IF NOT Rope.Equal[oldOwner.owner, propertyValue.owner, FALSE] THEN {
LeaderPage.SetProperty[fileInstance: fileInstance, propertyValue: propertyValue, lock: lock];
stage ← ownerChanged;
Increase space used by new owner.
IF ~AccessControl.ChangeSpaceViaOwner[transHandle: trans, volGroupID: volumeGroupID, ownerName: propertyValue.owner, nPages: size+1] THEN
ERROR AccessFailed[spaceQuota];
stage ← spaceChanged;
Decrease space used by old owner.
IF ~AccessControl.ChangeSpaceViaOpenFileID[conversation: conversation, transHandle: trans, openFileID: openFileID, nPages: -(size+1)] THEN ERROR;
};
END;
END;
version => {
logProc: PROC [SkiPatrolLog.OpFailureInfo];
IF (logProc ← SkiPatrolLog.notice.operationFailed) # NIL THEN
logProc[[
what: unwritableProperty,
transID: SkiPatrolHooks.FileInfoToTransID[conversation, openFileID],
where: "FileActionsImpl.WriteProperties",
message: "tried to write 'version' property"
]];
ERROR OperationFailed[unwritableProperty];
};
ENDCASE =>
LeaderPage.SetProperty[fileInstance: fileInstance, propertyValue: propertyValue, lock: lock];
REPEAT
leaderPageFull => {
logProc: PROC [SkiPatrolLog.OpFailureInfo];
IF (logProc ← SkiPatrolLog.notice.operationFailed) # NIL THEN
logProc[[
what: insufficientSpace,
transID: SkiPatrolHooks.FileInfoToTransID[conversation, openFileID],
where: "FileActionsImpl.WriteProperties",
message: ""
]];
ERROR OperationFailed[insufficientSpace];
};
ENDLOOP;
pUpdateCost^ ← 1; -- Need to assign some cost to this update, even though it doesn't actually write anything in the log until commit time
END; -- Work
FilePrivate.EstablishOpenFileContext[conversation, openFileID, Work];
END;
UnlockVersion: PUBLIC PROCEDURE [conversation: Conversation, openFileID: OpenFileID] =
BEGIN
Work: FilePrivate.OpenFileWork --[openFile, fileInstance, file, trans, pUpdateCost]-- =
BEGIN
FileLock.ReleasePropertyLock[fileInstance: fileInstance, property: version];
END; -- Work
FilePrivate.EstablishOpenFileContext[conversation, openFileID, Work];
END;
IncrementVersion: PUBLIC PROCEDURE [conversation: Conversation, openFileID: OpenFileID, increment: LONG CARDINAL] =
BEGIN
Work: FilePrivate.OpenFileWork --[openFile, fileInstance, file, trans, pUpdateCost]-- =
BEGIN
IF increment<=0 THEN ERROR StaticallyInvalid;
FileInstance.SetMaxDeltaVersion[fileInstance, increment];
pUpdateCost^ ← 1;
END; -- Work
FilePrivate.EstablishOpenFileContext[conversation, openFileID, Work];
END;
GetSize: PUBLIC PROCEDURE [conversation: Conversation, openFileID: OpenFileID, lock: LockOption ← [read, wait]] RETURNS [size: PageCount] =
BEGIN
Work: FilePrivate.OpenFileWork --[openFile, fileInstance, file, trans, pUpdateCost]-- =
BEGIN
IF lock.mode NOT IN AlpineEnvironment.LockMode THEN ERROR StaticallyInvalid;
FileLock.AcquireSizeLock[fileInstance: fileInstance, requested: lock, minimum: read];
size ← LogMap.GetUncommittedSize[file, trans]; -- non-nil iff changed by trans
IF size=LAST[PageCount] THEN size ← LogMap.GetCommittedSize[file];
IF size=LAST[PageCount] THEN size ← FilePageMgr.GetSize[file];
END; -- Work
FilePrivate.EstablishOpenFileContext[conversation, openFileID, Work];
END;
SetSize: PUBLIC PROCEDURE [conversation: Conversation, openFileID: OpenFileID, size: PageCount, lock: LockOption ← [update, wait]] =
BEGIN
Work: FilePrivate.OpenFileWork --[openFile, fileInstance, file, trans, pUpdateCost]-- =
BEGIN
committedSize, uncommittedSize, actualSize, highWaterMark: PageCount;
failure: AlpineEnvironment.OperationFailure;
IF lock.mode NOT IN AlpineEnvironment.LockMode OR size NOT IN [0..AlpineEnvironment.maxPagesPerFile] THEN ERROR StaticallyInvalid;
IF OpenFileMap.GetAccessRights[openFile] # readWrite THEN ERROR AccessFailed[handleReadWrite];
FileLock.AcquireSizeLock[fileInstance: fileInstance, requested: lock, minimum: update];
actualSize ← FilePageMgr.GetSize[file];
committedSize ← LogMap.GetCommittedSize[file];
IF committedSize=LAST[PageCount] THEN
LogMap.SetCommittedSize[file: file, size: (committedSize ← actualSize)];
IF size<committedSize THEN
New size less than committed: must acquire whole-file lock.
FileLock.AcquireFileLock[fileInstance: fileInstance, requested: lock, minimum: update];
FilePrivate.AcquireFileInterlock[file]; -- do this AFTER acquiring lock(s) so we don't wait with the file interlock set
uncommittedSize ← LogMap.GetUncommittedSize[file, trans];
IF uncommittedSize=LAST[PageCount] THEN uncommittedSize ← actualSize;
IF ~AccessControl.ChangeSpaceViaOpenFileID[conversation: conversation, transHandle: trans, openFileID: openFileID, nPages: size-uncommittedSize] THEN ERROR AccessFailed[spaceQuota];
If the file is being shortened, ensure that any committed deferred updates to the portion of the file that will be truncated have been carried out so as to avoid confusion after commit resulting from attempts to perform updates to nonexistent pages. Note that there cannot be any uncommitted updates for any transaction besides this one, because the file is locked. Uncommitted updates for this transaction are no problem because they are simply ignored at commit time.
IF size<uncommittedSize THEN
FilePrivate.PerformAnyCommittedIntentions[fileInstance: fileInstance, pageRun: [firstPage: size, count: uncommittedSize-size]];
Must log intention before lengthening file so that if we crash, recovery will reset size to its original value.
pUpdateCost^ ← 5+ABS[size-uncommittedSize]/5;
FileLog.LogSetSize[fileInstance: fileInstance, old: committedSize, new: size];
FileInstance.SetMaxDeltaVersion[fileInstance, 1]; -- note update
IF size>actualSize THEN
BEGIN
FilePageMgr.SetSize[fileHandle: file, size: size !
FilePageMgr.VolumeTooFragmented => { failure ← volumeFragmented; GOTO setSizeFailure };
FilePageMgr.InsufficientSpaceOnVolume => { failure ← insufficientSpace; GOTO setSizeFailure }];
EXITS
setSizeFailure => {
logProc: PROC [SkiPatrolLog.OpFailureInfo]; -- (used to prevent race condition)
give back the allocation before raising error
IF ~AccessControl.ChangeSpaceViaOpenFileID[conversation: conversation, transHandle: trans, openFileID: openFileID, nPages: uncommittedSize-size] THEN ERROR;
Must log another SetSize intention to restore file to its original uncommitted size so that if trans runs to completion but we crash during commit, recovery won't think we have to lengthen the file.
FileLog.LogSetSize[fileInstance: fileInstance, old: committedSize, new: uncommittedSize];
IF (logProc ← SkiPatrolLog.notice.operationFailed) # NIL THEN
logProc[[
what: failure,
transID: SkiPatrolHooks.FileInfoToTransID[conversation, openFileID],
where: "FileActionsImpl.SetSize",
message: ""
]];
ERROR OperationFailed[failure];
};
END;
LogMap.SetUncommittedSize[file: file, trans: trans, size: size];
If highWaterMark is beyond the new end of the file then reset it to the end.
highWaterMark ← NARROW[LeaderPage.GetProperty[fileInstance: fileInstance, property: highWaterMark, lock: [read, lock.ifConflict]], PropertyValuePair[highWaterMark]].highWaterMark;
IF highWaterMark>size THEN
LeaderPage.SetProperty[fileInstance: fileInstance, propertyValue: [highWaterMark[highWaterMark]], lock: lock];
END; -- Work
FilePrivate.EstablishOpenFileContext[conversation: conversation, openFileID: openFileID, work: Work, workLevel: hard]; -- This must start work with workLevel=hard because it dirties the owner data base. Start with concurrency=normal until locks have been set, then upgrade to concurrency=exclusive so as to avoid inconsistencies in changing the space quota.
END;
UnlockFile: PUBLIC PROCEDURE [conversation: Conversation, openFileID: OpenFileID, retainCacheLocks: BOOLEAN] =
BEGIN
Work: FilePrivate.OpenFileWork --[openFile, fileInstance, file, trans, pUpdateCost]-- =
BEGIN
FileLock.ReleaseFile[fileInstance: fileInstance, retainCacheLocks: retainCacheLocks];
END; -- Work
FilePrivate.EstablishOpenFileContext[conversation, openFileID, Work];
END;
UserProperty: TYPE = AlpineEnvironment.UserProperty;
UserPropertyValuePair: TYPE = AlpineEnvironment.UserPropertyValuePair;
UserProperties: TYPE = AlpineEnvironment.UserProperties;
UserPropertyValuePairs: TYPE = AlpineEnvironment.UserPropertyValuePairs;
ReadUserProperties: PUBLIC PROCEDURE [conversation: Conversation, openFileID: OpenFileID, desiredProperties: UserProperties, lock: LockOption ← [read, wait]] RETURNS [properties: UserPropertyValuePairs] ~ {
Work: FilePrivate.OpenFileWork --[openFile, fileInstance, file, trans, pUpdateCost]-- =
BEGIN
IF lock.mode NOT IN AlpineEnvironment.LockMode THEN
ERROR StaticallyInvalid;
properties ← LeaderPage.GetUserPropertyList[fileInstance: fileInstance, desiredProperties: desiredProperties, lock: lock];
END; -- Work
FilePrivate.EstablishOpenFileContext[conversation, openFileID, Work];
};
WriteUserProperties: PUBLIC PROCEDURE [conversation: Conversation, openFileID: OpenFileID, properties: UserPropertyValuePairs, lock: LockOption ← [write, wait]] ~ {
Work: FilePrivate.OpenFileWork --[openFile, fileInstance, file, trans, pUpdateCost]-- =
BEGIN
IF lock.mode NOT IN AlpineEnvironment.LockMode THEN ERROR StaticallyInvalid;
IF OpenFileMap.GetAccessRights[openFile] # readWrite THEN ERROR AccessFailed[handleReadWrite];
FOR p: UserPropertyValuePairs ← properties, p.rest WHILE p # NIL DO
ENABLE
LeaderPage.Error => IF errorType=full THEN GOTO leaderPageFull;
LeaderPage.SetUserProperty[fileInstance: fileInstance, propertyValue: p.first, lock: lock];
REPEAT
leaderPageFull => {
logProc: PROC [SkiPatrolLog.OpFailureInfo];
IF (logProc ← SkiPatrolLog.notice.operationFailed) # NIL THEN
logProc[[
what: insufficientSpace,
transID: SkiPatrolHooks.FileInfoToTransID[conversation, openFileID],
where: "FileActionsImpl.WriteProperties",
message: ""
]];
ERROR OperationFailed[insufficientSpace];
};
ENDLOOP;
pUpdateCost^ ← 1; -- Need to assign some cost to this update, even though it doesn't actually write anything in the log until commit time
END; -- Work
FilePrivate.EstablishOpenFileContext[conversation, openFileID, Work];
};
FileLog.
RecoverCreate: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, initialSize: PageCount, outcome: FileLog.TransState] =
BEGIN
Note: no need to call AccessControl.ChangeSpaceViaOwner again, because the change to the owner data base was logged separately and will therefore be recovered.
file: FileMap.Handle = FileInstance.GetFileHandle[fileInstance];
trans: TransactionMap.Handle = FileInstance.GetTransHandle[fileInstance];
IF outcome=ready THEN
BEGIN
actualSize: PageCount;
FileLock.AcquireFileLock[fileInstance: fileInstance, requested: [write, fail], minimum: write];
Since trans is not yet committed and files are not shortened until after commit, the file must still be at least initialSize pages long.
actualSize ← FilePageMgr.GetSize[file ! FilePageMgr.NoSuchFile => GOTO ignore];
IF actualSize<initialSize THEN ERROR;
END;
Register the creation unconditionally so that if the transaction eventually aborts the file (if it still exists) will be deleted.
LogMap.RegisterCreate[file: file, trans: trans];
EXITS
ignore => NULL;  -- (SkiPatrolLog will ignore this, too)
END;
RecoverDelete: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, outcome: FileLog.TransState] =
BEGIN
file: FileMap.Handle = FileInstance.GetFileHandle[fileInstance];
trans: TransactionMap.Handle = FileInstance.GetTransHandle[fileInstance];
uncommittedSize: PageCount;
IF outcome=ready THEN
FileLock.AcquireFileLock[fileInstance: fileInstance, requested: [write, fail], minimum: write];
Carry out any committed deferred writes.
uncommittedSize ← LogMap.GetUncommittedSize[file: file, trans: trans];
IF uncommittedSize=LAST[PageCount] THEN uncommittedSize ← LogMap.GetCommittedSize[file];
IF uncommittedSize=LAST[PageCount] THEN uncommittedSize ← FilePageMgr.GetSize[file ! FilePageMgr.NoSuchFile => GOTO alreadyDeleted];
IF uncommittedSize#LAST[PageCount] THEN
FilePrivate.PerformAnyCommittedIntentions[fileInstance: fileInstance, pageRun: [firstPage: 0, count: uncommittedSize]];
LogMap.RegisterDelete[file: file, trans: trans];
EXITS
alreadyDeleted => NULL;
END;
RecoverSetSize: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, old, new: PageCount, outcome: FileLog.TransState] =
BEGIN
file: FileMap.Handle = FileInstance.GetFileHandle[fileInstance];
trans: TransactionMap.Handle = FileInstance.GetTransHandle[fileInstance];
actualSize: PageCount = FilePageMgr.GetSize[file ! FilePageMgr.NoSuchFile => GOTO alreadyDeleted];
IF outcome=ready THEN
BEGIN
FileLock.AcquireSizeLock[fileInstance: fileInstance, requested: [write, fail], minimum: write];
IF new<old THEN
FileLock.AcquireFileLock[fileInstance: fileInstance, requested: [write, fail], minimum: write];
Since trans is not yet committed and files are not shortened until after commit, the file must still be at least MAX[old, new] pages long. -- the preceding statement is FALSE! A log record may have been written showing the file being lengthened, but the File.SetSize to actually lengthen it never happened. This situation has occurred. -- Hauser, May 22, 1986
IF actualSize<MAX[old, new] THEN ERROR;
END;
SELECT new FROM
< actualSize =>
Carry out any committed deferred writes to the portion of the file being truncated.
IF new<old THEN
FilePrivate.PerformAnyCommittedIntentions[fileInstance: fileInstance, pageRun: [firstPage: new, count: old-new]];
> actualSize =>
This can happen if the recovered actions include both an increase and a decrease.
FilePageMgr.SetSize[file, new];
ENDCASE;
Register the change unconditionally so that if the transaction eventually aborts the change will be undone (if necessary).
LogMap.SetCommittedSize[file: file, size: old];
LogMap.SetUncommittedSize[file: file, trans: trans, size: new];
EXITS
alreadyDeleted => NULL;
END;
FilePrivate.
PrepareForCommit: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle] =
BEGIN
file: FileMap.Handle = FileInstance.GetFileHandle[fileInstance];
trans: TransactionMap.Handle = FileInstance.GetTransHandle[fileInstance];
description: LogMap.FileDescription = LogMap.DescribeFile[file, trans];
IF FileInstance.GetDeltaVersion[fileInstance]#0 THEN
BEGIN
LeaderPage.Finalize[fileInstance];
As the last act before commit, force out any dirty pages belonging to the base file if the file was created by this transaction or if there were writes above highWaterMark. These must be written stably before commit, since there is no record of them in the log. Such pages cannot exist during recovery; and forcing them out is pointless if the file has been deleted by this transaction.
IF (description.created OR FileInstance.GetHighWaterMark[fileInstance] > LogMap.GetCommittedHighWaterMark[file]) AND ~FilePrivate.recovering AND ~description.deleted THEN
FilePageMgr.ForceOutFile[file];
END;
END;
PerformImmediateFileActions: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle] =
BEGIN
file: FileMap.Handle = FileInstance.GetFileHandle[fileInstance];
trans: TransactionMap.Handle = FileInstance.GetTransHandle[fileInstance];
description: LogMap.FileDescription = LogMap.DescribeFile[file, trans];
IF description.registered AND ~description.deleted THEN
BEGIN
LeaderPage.CarryOut[fileInstance: fileInstance];
IF description.sizeChanged THEN
BEGIN
Size changed by this transaction. Note: compare desired size with actual size (rather than with committed size), since client may have extended file and later shortened it under the same transaction.
currentSize: PageCount = FilePageMgr.GetSize[file];
newSize: PageCount = LogMap.GetUncommittedSize[file: file, trans: trans];
Size increases are never deferred, so we should not encounter an intention to lengthen the file. (This is true during recovery as well.)
IF newSize>currentSize THEN ERROR;
IF newSize<currentSize THEN FilePageMgr.SetSize[fileHandle: file, size: newSize];
LogMap.SetCommittedSize[file: file, size: newSize];
LogMap.SetUncommittedSize[file: file, trans: NIL, size: LAST[PageCount]];
END;
At this point there should be nothing in the LogMap belonging to trans except possibly for deferred delete and page write intentions.
END;
END;
PerformDeferredFileActions: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle] =
BEGIN
file: FileMap.Handle = FileInstance.GetFileHandle[fileInstance];
trans: TransactionMap.Handle = FileInstance.GetTransHandle[fileInstance];
description: LogMap.FileDescription = LogMap.DescribeFile[file, trans];
IF description.registered THEN
BEGIN
IF description.deleted THEN FilePageMgr.Delete[file]
ELSE FilePrivate.PerformDeferredPageActions[fileInstance];
LogMap.UnregisterTrans[file, trans];
END;
END;
UndoFileActions: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle] =
BEGIN
file: FileMap.Handle = FileInstance.GetFileHandle[fileInstance];
trans: TransactionMap.Handle = FileInstance.GetTransHandle[fileInstance];
description: LogMap.FileDescription = LogMap.DescribeFile[file, trans];
IF description.registered THEN
BEGIN
IF description.created THEN FilePageMgr.Delete[file ! FilePageMgr.NoSuchFile => IF FilePrivate.recovering THEN CONTINUE]
ELSE IF description.sizeChanged THEN
BEGIN
If file was extended beyond its committed size then truncate it.
currentSize: PageCount = FilePageMgr.GetSize[file];
committedSize: PageCount = LogMap.GetCommittedSize[file: file];
SELECT committedSize FROM
> currentSize =>
Size decreases are always deferred, so we should never encounter an undo request whose effect would be to lengthen the file (except perhaps during recovery, if the aborted transaction is followed by a committed one which shortened the file).
IF ~FilePrivate.recovering THEN ERROR;
< currentSize =>
FilePageMgr.SetSize[fileHandle: file, size: committedSize];
ENDCASE;
END;
LogMap.UnregisterTrans[file, trans];
END;
END;
END.
CHANGE LOG
Edited on July 25, 1984 10:05:07 am PDT, by Kupfer
Added SkiPatrolLog probes and some comments.
changes to: DIRECTORY, Open, Create, WriteProperties, SetSize, RecoverCreate, FileActionsImpl
Edited on August 6, 1984 3:15:20 pm PDT, by Kupfer
Remove the possible race condition in SkiPatrolLog probes by assigning the PROC to a temporary variable.
changes to: Open, Create, WriteProperties, SetSize, Create, DIRECTORY
Edited on February 28, 1985 1:14:42 pm PST, by Kupfer
Add code to Open to record the file's name after all the permissions and such have been verified.