<> <> <> <> <> <> <> <> <<>> <<>> <<>> <> <<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; <> 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]; <> [volumeID: actualVolumeID] _ VolumeGroup.Identify[volID: universalFile.volumeID, trans: trans, lock: [read, lock.ifConflict] ! VolumeGroup.Failed => GOTO unknownVolumeID]; IF universalFile.volumeID#actualVolumeID THEN GOTO unknownVolumeID; <> 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]; <> 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]; <> 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; <> [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 <> 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; <> FilePrivate.PerformAnyCommittedIntentions[fileInstance: fileInstance, pageRun: [firstPage: 0, count: uncommittedSize]]; <> 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; <> 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]; <> 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; <> IF ~AccessControl.ChangeSpaceViaOwner[transHandle: trans, volGroupID: volumeGroupID, ownerName: propertyValue.owner, nPages: size+1] THEN ERROR AccessFailed[spaceQuota]; stage _ spaceChanged; <> 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> 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 size> 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) <> IF ~AccessControl.ChangeSpaceViaOpenFileID[conversation: conversation, transHandle: trans, openFileID: openFileID, nPages: uncommittedSize-size] THEN ERROR; <> 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]; <> 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]; }; <> RecoverCreate: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, initialSize: PageCount, outcome: FileLog.TransState] = BEGIN <> 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]; <> actualSize _ FilePageMgr.GetSize[file ! FilePageMgr.NoSuchFile => GOTO ignore]; IF actualSize> 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]; <> 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> <> END; SELECT new FROM < actualSize => <> IF new actualSize => <> FilePageMgr.SetSize[file, new]; ENDCASE; <> LogMap.SetCommittedSize[file: file, size: old]; LogMap.SetUncommittedSize[file: file, trans: trans, size: new]; EXITS alreadyDeleted => NULL; END; <> 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]; <> 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 <> currentSize: PageCount = FilePageMgr.GetSize[file]; newSize: PageCount = LogMap.GetUncommittedSize[file: file, trans: trans]; <> IF newSize>currentSize THEN ERROR; IF newSize> 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 <> currentSize: PageCount = FilePageMgr.GetSize[file]; committedSize: PageCount = LogMap.GetCommittedSize[file: file]; SELECT committedSize FROM > currentSize => <> IF ~FilePrivate.recovering THEN ERROR; < currentSize => FilePageMgr.SetSize[fileHandle: file, size: committedSize]; ENDCASE; END; LogMap.UnregisterTrans[file, trans]; END; END; END. CHANGE LOG <> <> <> <> <> <> <<>> <> <> <<>> <<>>