DIRECTORY AccessControl, AlpineEnvironment, AlpineFile, AlpineInternal, FileInstance, FileLock, FileLog, FileMap, FilePageMgr, FilePrivate, LeaderPage, LogMap, OpenFileMap, Rope USING [Cat, 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 { 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; 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 _ LeaderPage.GetProperty[fileInstance: fileInstance, property: owner, lock: [read, lock.ifConflict]]; 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 sizeactualSize 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] = BEGIN Work: FilePrivate.OpenFileWork --[openFile, fileInstance, file, trans, pUpdateCost]-- = BEGIN FileLock.ReleaseFile[fileInstance: fileInstance]; END; -- Work FilePrivate.EstablishOpenFileContext[conversation, openFileID, Work]; END; 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 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 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 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 ¼FileActionsImpl.mesa Copyright c 1984, 1985 by Xerox Corporation. All rights reserved. Carl Hauser, June 3, 1985 10:43:33 am PDT 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. AlpineFile. 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.) 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. 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. 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. Note: as a side-effect, this read-locks the volume group, which is required by several of the AccessControl procedures which we call subsequently. -- cleanup volatile structures from last call 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. 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. Now actually register the delete intention. This will set the mode to the MAX of lock.mode and its existing mode. Change owner name in leader page, but remember the old one in case we later need to back out. Increase space used by new owner. Decrease space used by old owner. New size less than committed: must acquire whole-file lock. 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. Must log intention before lengthening file so that if we crash, recovery will reset size to its original value. give back the allocation before raising 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. If highWaterMark is beyond the new end of the file then reset it to the end. FileLog. Note: no need to call AccessControl.ChangeSpaceViaOwner again, because the change to the owner data base was logged separately and will therefore be recovered. Since trans is not yet committed and files are not shortened until after commit, the file must still be at least initialSize pages long. Register the creation unconditionally so that if the transaction eventually aborts the file (if it still exists) will be deleted. Carry out any committed deferred writes. 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. Carry out any committed deferred writes to the portion of the file being truncated. This can happen if the recovered actions include both an increase and a decrease. Register the change unconditionally so that if the transaction eventually aborts the change will be undone (if necessary). FilePrivate. 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. 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. Size increases are never deferred, so we should not encounter an intention to lengthen the file. (This is true during recovery as well.) At this point there should be nothing in the LogMap belonging to trans except possibly for deferred delete and page write intentions. If file was extended beyond its committed size then truncate it. 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). 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. Ê4– "cedar" style˜Jšœ™šœ Ïmœ7™BIcode™)—J˜šœ™Jšœ™Jšœ)™)Jšœ™—J™8J™J™J™™ JšœYÏkœ=™™JšœÔ™ÔJšœÁ™ÁJšœ­™­Jšœ'™'J™—unitšž ˜ K˜K˜K˜ K˜K˜ K˜ K˜K˜K˜ K˜ K˜ K˜K˜ Kšœžœžœ˜Kšœžœ˜)Kšœ žœ˜+K˜K˜ —šœž˜KšžœÀ˜ÇKšžœ#˜*Kšžœžœ ˜—J˜šœ ™ šÏnœžœž œéžœ+˜±Kšž˜šŸœÏcœ˜Kšžœ ˜ —K˜EKšžœ˜—šŸœžœž œk˜„Kšž˜šŸœ 6œ˜WKšž˜K˜EKšœ,˜,Kšžœ žœžœžœžœžœ(žœžœ˜‚Kšžœ3žœžœ˜^K˜WK˜'K˜.šžœžœ ž˜%K˜H—šžœž˜Jšœ;™;K˜W—Kšœ(  ¡ ?˜wK˜9Kšžœžœ žœ˜EKšžœžœžœ˜µJšœ×™×šžœž˜K˜—Jšœo™oKšœžœ˜-K˜NKšœ3 ˜Ašžœž˜Kšž˜˜2KšœAžœ˜WKšœHžœ˜_—šž˜˜Kšœ žœ  #˜PKšœ-™-Kšžœžœžœ˜œJšœÆ™ÆK˜Yšžœ3žœž˜=šœ ˜ Jšœ˜KšœD˜DJšœ!˜!Jšœ ˜ J˜——Kšžœ˜Kšžœ˜——Kšžœ˜—K˜@JšœL™LKšœžœ˜³šžœž˜K˜n—Kšžœ ˜ —Kšœw î˜åKšžœ˜—L˜šŸ œžœž œ7˜SKšž˜šŸœ 6œ˜WKšž˜K˜1Kšžœ ˜ —K˜EKšžœ˜——J˜J˜šœ™šŸ œžœž œ[˜zKšž˜JšœŸ™ŸK˜@K˜Išžœž˜Kšž˜K˜K˜_Jšœˆ™ˆKšœBžœ ˜OKšžœžœžœ˜%Kšžœ˜—Jšœ™K˜0šž˜Kšœ žœ '˜8—Kšžœ˜—šŸ œžœž œC˜bKšž˜K˜@K˜IK˜šžœž˜K˜_—Jšœ(™(K˜FKšžœžœ žœ1˜XKšžœžœ žœHžœ˜„šžœžœ ž˜'K˜w—K˜0šž˜Kšœžœ˜—Kšžœ˜—šŸœžœž œX˜xKšž˜K˜@K˜IKšœMžœ˜bšžœž˜Kšž˜K˜_šžœ ž˜K˜_—Jšœqžœ™ŠKšžœ žœ žœžœ˜'Kšžœ˜—šžœž˜˜JšœS™Sšžœ ž˜K˜q——˜JšœQ™QK˜—Kšžœ˜—Jšœz™zK˜/K˜?šž˜Kšœžœ˜—Kšžœ˜——J˜J˜šœ ™ šŸœžœž œ&˜HKšž˜K˜@K˜IK˜Gšžœ.ž˜4Kšž˜K˜"Jšœ‚™‚š žœžœWžœžœž˜ªKšœ˜—Kšžœ˜—Kšžœ˜—šŸœžœž œ&˜SKšž˜K˜@K˜IK˜Gšžœžœž˜7Kšž˜K˜0šžœž˜Kšž˜JšœÈ™ÈK˜3K˜IJšœ‰™‰Kšžœžœžœ˜"Kšžœžœ6˜QK˜3Kšœ-žœžœ ˜IKšžœ˜—Jšœ…™…Kšžœ˜—Kšžœ˜—šŸœžœž œ&˜RKšž˜K˜@K˜IK˜Gšžœž˜Kšž˜Kšžœžœ˜4Kšžœ6˜:K˜$Kšžœ˜—Kšžœ˜—šŸœžœž œ&˜GKšž˜K˜@K˜IK˜Gšžœž˜Kšž˜Kš žœžœ5žœžœžœ˜xšžœžœž˜$Kšž˜Jšœ@™@K˜3K˜?šžœž˜˜Jšœñ™ñKšžœžœžœ˜&—˜K˜;—Kšžœ˜—Kšžœ˜—K˜$Kšžœ˜—Kšžœ˜—Lšžœ˜Lšžœž˜ ™2J™,Jšœ ¢Q™]—™2J™hJšœ ¢9™E—J™™5Jšœ ¢œP™a—J™J™——…—tð¨à