<> <> <> <> <> <> <> <> DIRECTORY AlpineEnvironment, AlpineFile, AlpineInline, AlpineInternal, Basics USING [bytesPerWord], BasicTime USING [Now, ToNSTime, FromNSTime], CountedVM USING [Handle], FileInstance, FileLock, FileLog, FileMap, FilePageMgr, FilePrivate, LeaderPage, LeaderPageFormat, LogMap, PrincOpsUtils USING [LongCopy], RPC USING [maxShortStringLength], Rope USING [ActionType, FromProc, Length, Map], SafeStorage USING [GetSystemZone], TransactionMap; LeaderPageImpl: PROGRAM IMPORTS AlpineFile, AlpineInline, BasicTime, FileInstance, FileLock, FileLog, FileMap, FilePageMgr, FilePrivate, LeaderPage, LogMap, PrincOpsUtils, Rope, SafeStorage EXPORTS AlpineInternal, FileLog, LeaderPage = BEGIN OPEN LeaderPage, LeaderPageFormat; String: TYPE = AlpineEnvironment.String; LeaderPageHandle: TYPE = REF LeaderPageObject; LeaderPageObject: PUBLIC TYPE = LeaderPageFormat.LeaderPageObject; <> Initialize: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle] = BEGIN file: FileMap.Handle = FileInstance.GetFileHandle[fileInstance]; trans: TransactionMap.Handle = FileInstance.GetTransHandle[fileInstance]; description: LogMap.FileDescription = LogMap.DescribeFile[file: file, trans: trans]; Work: LeaderPageWork --[lp: LONG POINTER TO LeaderPageRecord, nPages: CARDINAL]-- = BEGIN AlpineInline.LongZero[lp, AlpineEnvironment.wordsPerPage]; lp.dataStart _ lp.dataEnd _ offsetData; lp.seal _ sealLeaderPageRecord; FOR property: Property IN Property DO SetPropertyInternal[lp: lp, nPages: 1, propertyValue: GetDefaultPropertyValue[property]]; ENDLOOP; FileInstance.SetMaxDeltaVersion[fileInstance, 1]; [] _ FileInstance.SetHighWaterMark[fileInstance, 0]; IF validateAfterEveryUpdate THEN ValidateInternal[lp, 1]; END; -- Work IF ~description.registered OR ~description.created OR LogMap.GetLeaderPageHandle[file: file, trans: trans]#NIL THEN Error[notVirginFile]; IF FileInstance.GetLockMode[fileInstance] # write THEN ERROR; PerformWork[fileInstance: fileInstance, work: Work, intention: write]; LogMap.SetCommittedVersion[file: file, version: 0]; LogMap.SetCommittedHighWaterMark[file: file, highWaterMark: 0]; END; GetProperty: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, property: Property, lock: LockOption _ [read, wait]] RETURNS [propertyValue: PropertyValuePair] = BEGIN file: FileMap.Handle _ FileInstance.GetFileHandle[fileInstance]; Work: LeaderPageWork --[lp: LONG POINTER TO LeaderPageRecord, nPages: CARDINAL]-- = BEGIN found: BOOLEAN; IF validateBeforeEveryOperation THEN ValidateInternal[lp, nPages]; [found: found, propertyValue: propertyValue] _ GetPropertyInternal[lp: lp, property: property]; IF ~found THEN propertyValue _ GetDefaultPropertyValue[property]; END; -- Work FileLock.AcquirePropertyLock[fileInstance: fileInstance, property: property, requested: lock, minimum: read]; SELECT property FROM highWaterMark => BEGIN -- writes beyold old highWaterMark may not be reflected in leader page yet cachedHighWaterMark: PageCount = FileInstance.GetHighWaterMark[fileInstance]; PerformWork[fileInstance: fileInstance, work: Work]; IF cachedHighWaterMark#LAST[PageCount] THEN WITH propertyValue SELECT FROM highWaterMark => highWaterMark _ MAX[highWaterMark, cachedHighWaterMark]; ENDCASE => ERROR; RETURN; END; version => BEGIN version: FileVersion = LogMap.GetCommittedVersion[file: file]; IF version#0 THEN RETURN [[version[version]]]; <> END; ENDCASE; PerformWork[fileInstance: fileInstance, work: Work]; END; GetPropertyList: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, desiredProperties: PropertySet _ ALL [TRUE], lock: LockOption _ [read, wait]] RETURNS [propertyList: LIST OF PropertyValuePair _ NIL] = BEGIN FOR property: Property DECREASING IN Property DO IF desiredProperties[property] THEN propertyList _ FilePrivate.stdPZone.CONS[GetProperty[fileInstance: fileInstance, property: property, lock: lock], propertyList]; ENDLOOP; END; SetProperty: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, propertyValue: PropertyValuePair, lock: LockOption _ [update, wait]] = BEGIN Work: LeaderPageWork --[lp: LONG POINTER TO LeaderPageRecord, nPages: CARDINAL]-- = BEGIN IF validateBeforeEveryOperation THEN ValidateInternal[lp, nPages]; SetPropertyInternal[lp: lp, nPages: nPages, propertyValue: propertyValue]; IF validateAfterEveryUpdate THEN ValidateInternal[lp, nPages]; FileInstance.SetMaxDeltaVersion[fileInstance, 1]; END; -- Work file: FileMap.Handle _ FileInstance.GetFileHandle[fileInstance]; FileLock.AcquirePropertyLock[fileInstance: fileInstance, property: propertyValue.property, requested: lock, minimum: update]; WITH propertyValue SELECT FROM highWaterMark => [] _ FileInstance.SetHighWaterMark[fileInstance, highWaterMark]; version => ERROR Error[unwritableProperty]; ENDCASE; PerformWork[fileInstance: fileInstance, work: Work, intention: write]; END; SetPropertyList: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, propertyList: LIST OF PropertyValuePair, lock: LockOption _ [update, wait]] = BEGIN WHILE propertyList#NIL DO SetProperty[fileInstance: fileInstance, propertyValue: propertyList.first, lock: lock]; propertyList _ propertyList.rest; ENDLOOP; END; Validate: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle] = BEGIN PerformWork[fileInstance: fileInstance, work: ValidateInternal]; END; <> Finalize: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle] = BEGIN deltaVersion: LONG INTEGER = FileInstance.GetDeltaVersion[fileInstance]; <> IF deltaVersion#0 AND ~FilePrivate.recovering THEN BEGIN file: FileMap.Handle = FileInstance.GetFileHandle[fileInstance]; trans: TransactionMap.Handle = FileInstance.GetTransHandle[fileInstance]; description: LogMap.FileDescription = LogMap.DescribeFile[file: file, trans: trans]; version: FileVersion; IF description.deleted THEN <> LogMap.SetLeaderPageHandle[file: file, trans: NIL, leaderPage: NIL] ELSE BEGIN newHighWaterMark: PageCount _ FileInstance.GetHighWaterMark[fileInstance]; IF newHighWaterMark#LAST[PageCount] THEN BEGIN -- highWaterMark may have changed IF newHighWaterMark > LogMap.GetCommittedHighWaterMark[file] THEN SetProperty[fileInstance: fileInstance, propertyValue: [highWaterMark[newHighWaterMark]], lock: [write, wait]] ELSE BEGIN newHighWaterMark _ NARROW[GetProperty[fileInstance: fileInstance, property: highWaterMark], PropertyValuePair[highWaterMark]].highWaterMark; [] _ FileInstance.SetHighWaterMark[fileInstance, newHighWaterMark]; END; END; FileLock.AcquirePropertyLock[fileInstance: fileInstance, property: version, requested: [write, wait], minimum: write]; IF description.registered AND description.created THEN BEGIN <> version _ deltaVersion; -- since this is the first one IF version#1 THEN BEGIN vmPageSet: FilePageMgr.VMPageSet; vmPageSetHandle: CountedVM.Handle; <> [vmPageSet, vmPageSetHandle] _ FilePageMgr.ReadLeaderPages[file]; SetPropertyInternal[lp: vmPageSet.pages, nPages: vmPageSet.pageRun.count, propertyValue: [version[version]]]; FilePageMgr.ReleaseVMPageSet[vMPageSet: vmPageSet, releaseState: writeIndividualNoWait, keep: TRUE]; END; END ELSE BEGIN <> leaderPage: LeaderPageHandle; recordID: FileLog.LogRecordID; lp: LONG POINTER TO LeaderPageRecord; leaderPage _ LogMap.GetLeaderPageHandle[file: file, trans: trans]; IF leaderPage=NIL THEN <> leaderPage _ ReadLeaderPages[file]; lp _ @leaderPage.record; version _ LogMap.GetCommittedVersion[file: file]; version _ version+deltaVersion; SetPropertyInternal[lp: lp, nPages: leaderPage.nPages, propertyValue: [version[version]]]; recordID _ FileLog.LogWriteLeaderPages[fileInstance: fileInstance, leaderPage: leaderPage]; LogMap.RegisterLeaderPage[file: file, trans: trans, logRecordID: recordID]; LogMap.SetLeaderPageHandle[file: file, trans: NIL, leaderPage: NIL]; END; LogMap.SetUncommittedVersion[file: file, trans: trans, version: version]; END; END; END; CarryOut: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle] = BEGIN deltaVersion: LONG INTEGER = FileInstance.GetDeltaVersion[fileInstance]; IF deltaVersion#0 THEN -- nonzero means file was updated by this transaction BEGIN file: FileMap.Handle = FileInstance.GetFileHandle[fileInstance]; trans: TransactionMap.Handle = FileInstance.GetTransHandle[fileInstance]; highWaterMark: PageCount = FileInstance.GetHighWaterMark[fileInstance]; location: LogMap.Location; recordID: FileLog.LogRecordID; leaderPage: LeaderPageHandle; [location: location, logRecordID: recordID] _ LogMap.LocateLeaderPage[file: file, trans: trans]; IF location=log THEN BEGIN vmPageSet: FilePageMgr.VMPageSet; vmPageSetHandle: CountedVM.Handle; leaderPage _ FileLog.LogReadLeaderPages[fileInstance: fileInstance, recordID: recordID]; FilePageMgr.SetLeaderSize[ file, leaderPage.nPages ]; [vmPageSet, vmPageSetHandle] _ FilePageMgr.UseLeaderPages[file]; PrincOpsUtils.LongCopy[to: vmPageSet.pages, from: @leaderPage.record, nwords: leaderPage.nPages*AlpineEnvironment.wordsPerPage]; FilePageMgr.ReleaseVMPageSet[vMPageSet: vmPageSet, releaseState: writeIndividualNoWait, keep: TRUE]; LogMap.UnregisterLeaderPage[file]; END; LogMap.SetCommittedVersion[file: file, version: LogMap.GetUncommittedVersion[file: file, trans: trans]]; IF highWaterMark#LAST[PageCount] THEN LogMap.SetCommittedHighWaterMark[file: file, highWaterMark: highWaterMark]; LogMap.SetUncommittedVersion[file: file, trans: NIL, version: 0]; END; END; Error: PUBLIC ERROR [errorType: ErrorType] = CODE; <> validateBeforeEveryOperation: PUBLIC BOOLEAN _ FALSE; validateAfterEveryUpdate: PUBLIC BOOLEAN _ FALSE; <> RecoverWriteLeaderPage: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, recordID: FileLog.LogRecordID, outcome: FileLog.TransState] = BEGIN IF outcome#aborted THEN BEGIN file: FileMap.Handle = FileInstance.GetFileHandle[fileInstance]; trans: TransactionMap.Handle = FileInstance.GetTransHandle[fileInstance]; <> FileLock.AcquirePropertyLock[fileInstance: fileInstance, property: version, requested: [write, wait], minimum: write]; LogMap.RegisterLeaderPage[file: file, trans: trans, logRecordID: recordID]; END; END; <> LeaderPageWork: TYPE = PROCEDURE [lp: LONG POINTER TO LeaderPageRecord, nPages: CARDINAL]; PerformWork: PROCEDURE [fileInstance: FileInstance.Handle, work: LeaderPageWork, intention: {read, write} _ read] = <> BEGIN DoWork: SAFE PROCEDURE = TRUSTED {work[lp, nPages]}; file: FileMap.Handle = FileInstance.GetFileHandle[fileInstance]; trans: TransactionMap.Handle = FileInstance.GetTransHandle[fileInstance]; description: LogMap.FileDescription = LogMap.DescribeFile[file: file, trans: trans]; leaderPage: LeaderPageHandle _ LogMap.GetLeaderPageHandle[file: file, trans: trans]; lp: LONG POINTER TO LeaderPageRecord; nPages: CARDINAL; IF leaderPage=NIL AND (intention=read OR description.created) THEN BEGIN -- Perform the operation on the base vmPageSet: FilePageMgr.VMPageSet; vmPageSetHandle: CountedVM.Handle; release: FilePageMgr.ReleaseState = IF intention=read THEN clean ELSE writeIndividualNoWait; BEGIN again: BOOLEAN; DO again _ FALSE; [vmPageSet, vmPageSetHandle] _ FilePageMgr.ReadLeaderPages[file ! FilePageMgr.NoSuchFile => ERROR AlpineFile.Unknown[fileID] ]; lp _ vmPageSet.pages; nPages _ vmPageSet.pageRun.count; FileMap.Enter[file, DoWork ! LeaderPage.Error => IF errorType=full THEN {again _ TRUE; CONTINUE}; UNWIND => FilePageMgr.ReleaseVMPageSet[vMPageSet: vmPageSet, releaseState: release, keep: TRUE]]; FilePageMgr.ReleaseVMPageSet[vMPageSet: vmPageSet, releaseState: release, keep: TRUE]; IF NOT again THEN EXIT; FilePageMgr.SetLeaderSize[file, nPages+1]; ENDLOOP; END; END ELSE BEGIN -- Perform the operation on the LeaderPageObject cached in the LogMap IF leaderPage=NIL THEN BEGIN leaderPage _ ReadLeaderPages[file]; LogMap.SetLeaderPageHandle[file: file, trans: trans, leaderPage: leaderPage ! <> LogMap.Error => IF error=alreadySet THEN { <> leaderPage _ LogMap.GetLeaderPageHandle[file: file, trans: trans]; IF leaderPage=NIL THEN ERROR; CONTINUE}]; END; BEGIN again: BOOLEAN; newLeaderPage: LeaderPageHandle; DO again _ FALSE; lp _ @leaderPage.record; nPages _ leaderPage.nPages; FileMap.Enter[file, DoWork ! LeaderPage.Error => IF errorType=full THEN {again _ TRUE; CONTINUE} ]; IF NOT again THEN EXIT; newLeaderPage _ newLeaderPageObject[leaderPage.nPages+1]; PrincOpsUtils.LongCopy[to: @newLeaderPage.record, from: @leaderPage.record, nwords: leaderPage.nPages*AlpineEnvironment.wordsPerPage]; leaderPage _ newLeaderPage; LogMap.SetLeaderPageHandle[file: file, trans: trans, leaderPage: leaderPage]; ENDLOOP; END; END; END; ReadLeaderPages: PROCEDURE [file: FileMap.Handle] RETURNS [leaderPage: LeaderPageHandle] = <> BEGIN vmPageSet: FilePageMgr.VMPageSet; vmPageSetHandle: CountedVM.Handle; lp: LONG POINTER TO LeaderPageRecord; [vmPageSet, vmPageSetHandle] _ FilePageMgr.ReadLeaderPages[file]; lp _ vmPageSet.pages; leaderPage _ newLeaderPageObject[vmPageSet.pageRun.count]; PrincOpsUtils.LongCopy[to: @leaderPage.record, from: vmPageSet.pages, nwords: leaderPage.nPages*AlpineEnvironment.wordsPerPage]; FilePageMgr.ReleaseVMPageSet[vMPageSet: vmPageSet, releaseState: clean, keep: TRUE]; IF LogMap.GetCommittedVersion[file: file]=0 THEN BEGIN propertyValue: PropertyValuePair; found: BOOLEAN; [found, propertyValue] _ GetPropertyInternal[lp: lp, property: version]; IF ~found THEN ERROR; LogMap.SetCommittedVersion[file: file, version: NARROW[propertyValue, PropertyValuePair[version]].version]; END; END; GetPropertyInternal: PROCEDURE [lp: LONG POINTER TO LeaderPageRecord, property: Property] RETURNS [found: BOOLEAN _ FALSE, propertyValue: PropertyValuePair] = BEGIN pickledProperty: PickledProperty = propertyPickleMap[property]; prop: LONG POINTER TO PropertyRep; offset: LeaderPageOffset = Find[lp, pickledProperty]; IF offset=0 THEN RETURN; prop _ LOOPHOLE[lp+offset]; WITH prop SELECT prop.property FROM byteLength => propertyValue _ [byteLength[byteLength]]; createTime => propertyValue _ [createTime[BasicTime.FromNSTime[createTime]]]; highWaterMark => propertyValue _ [highWaterMark[highWaterMark]]; modifyAccess => propertyValue _ [modifyAccess[AccessListFromRep[@modifyAccess]]]; owner => propertyValue _ [owner[StringFromRep[@owner]]]; readAccess => propertyValue _ [readAccess[AccessListFromRep[@readAccess]]]; stringName => propertyValue _ [stringName[StringFromRep[@stringName]]]; version => propertyValue _ [version[version]]; ENDCASE => ERROR; found _ TRUE; END; AccessListFromRep: PROCEDURE [rep: LONG POINTER TO AccessListRep] RETURNS [accessList: AccessList] = BEGIN string: LONG POINTER TO StringRep _ @rep.principals; tail: AccessList _ NIL; accessList _ NIL; THROUGH [0..rep.count) DO accessItem: AccessList = FilePrivate.stdPZone.CONS[StringFromRep[string], NIL]; IF tail=NIL THEN accessList _ accessItem ELSE tail.rest _ accessItem; tail _ accessItem; string _ string + SIZE[CARDINAL] + (string.length+Basics.bytesPerWord-1)/Basics.bytesPerWord; ENDLOOP; END; StringFromRep: PROCEDURE [rep: LONG POINTER TO StringRep] RETURNS [string: String] = BEGIN Fetch: SAFE PROCEDURE RETURNS [c: CHAR] = TRUSTED {c _ rep.text[index]; index _ index+1}; index: CARDINAL _ 0; RETURN [IF rep.length=0 THEN NIL ELSE Rope.FromProc[len: rep.length, p: Fetch]]; END; SetPropertyInternal: PROCEDURE [lp: LONG POINTER TO LeaderPageRecord, nPages: CARDINAL, propertyValue: PropertyValuePair] = <> BEGIN pickledProperty: PickledProperty = propertyPickleMap[propertyValue.property]; offset: LeaderPageOffset = Find[lp: lp, pickledProperty: pickledProperty]; prop: LONG POINTER TO PropertyRep _ LOOPHOLE[lp+offset]; size: CARDINAL = RepSize[propertyValue]; wordsRequired: CARDINAL = lp.dataEnd - (IF offset#0 THEN prop.size ELSE 0) + size; IF wordsRequired > nPages*AlpineEnvironment.wordsPerPage THEN BEGIN <> ERROR Error[full]; END; IF offset#0 THEN Delete[lp: lp, offset: offset]; prop _ LOOPHOLE[lp+lp.dataEnd]; prop.size _ size; prop.property _ pickledProperty; WITH propertyValue SELECT FROM byteLength => WITH pp: prop SELECT byteLength FROM byteLength => pp.byteLength _ byteLength; ENDCASE; createTime => WITH pp: prop SELECT createTime FROM createTime => pp.createTime _ BasicTime.ToNSTime[createTime]; ENDCASE; highWaterMark => WITH pp: prop SELECT highWaterMark FROM highWaterMark => pp.highWaterMark _ highWaterMark; ENDCASE; modifyAccess => WITH pp: prop SELECT modifyAccess FROM modifyAccess => [] _ RepFromAccessList[@pp.modifyAccess, modifyAccess]; ENDCASE; owner => WITH pp: prop SELECT owner FROM owner => [] _ RepFromString[@pp.owner, owner]; ENDCASE; readAccess => WITH pp: prop SELECT readAccess FROM readAccess => [] _ RepFromAccessList[@pp.readAccess, readAccess]; ENDCASE; stringName => WITH pp: prop SELECT stringName FROM stringName => [] _ RepFromString[@pp.stringName, stringName]; ENDCASE; version => WITH pp: prop SELECT version FROM version => pp.version _ version; ENDCASE; ENDCASE => ERROR; lp.dataEnd _ lp.dataEnd+prop.size; END; RepSize: PROCEDURE [propertyValue: PropertyValuePair] RETURNS [size: CARDINAL] = BEGIN sizeCommon: CARDINAL = 1; -- size of common part of PropertyRep record size _ WITH propertyValue SELECT FROM byteLength, createTime, highWaterMark, version => SIZE[PropertyRep[byteLength]], modifyAccess => RepFromAccessList[NIL, modifyAccess, FALSE]+sizeCommon, owner => RepFromString[NIL, owner, FALSE]+sizeCommon, readAccess => RepFromAccessList[NIL, readAccess, FALSE]+sizeCommon, stringName => RepFromString[NIL, stringName, FALSE]+sizeCommon, ENDCASE => ERROR; END; RepFromAccessList: PROCEDURE [rep: LONG POINTER TO AccessListRep, accessList: AccessList, copy: BOOLEAN _ TRUE] RETURNS [size: CARDINAL] = <> BEGIN count: CARDINAL _ 0; stringRep: LONG POINTER TO StringRep _ @rep.principals; size _ SIZE[CARDINAL]; FOR accessItem: AccessList _ accessList, accessItem.rest WHILE accessItem#NIL DO sizeString: CARDINAL = RepFromString[stringRep, accessItem.first, copy]; count _ count+1; size _ size+sizeString; stringRep _ stringRep+sizeString; ENDLOOP; IF copy THEN rep.count _ count; END; RepFromString: PROCEDURE [rep: LONG POINTER TO StringRep, string: String, copy: BOOLEAN _ TRUE] RETURNS [size: CARDINAL] = <> BEGIN Store: Rope.ActionType --[c: CHAR] RETURNS [quit: BOOL _ FALSE]-- = TRUSTED {rep.text[index] _ c; index _ index+1}; length: INT = Rope.Length[string]; index: CARDINAL _ 0; IF length>RPC.maxShortStringLength THEN ERROR Error[nameTooLong]; size _ SIZE[CARDINAL] + (length+Basics.bytesPerWord-1)/Basics.bytesPerWord; IF copy THEN BEGIN rep.length _ length; IF length#0 THEN [] _ Rope.Map[base: string, len: length, action: Store]; END; END; Find: PROCEDURE [lp: LONG POINTER TO LeaderPageRecord, pickledProperty: PickledProperty] RETURNS [offset: LeaderPageOffset] = <> BEGIN offset _ lp.dataStart; WHILE offset offset _ 0; ENDLOOP; END; Delete: PROCEDURE [lp: LONG POINTER TO LeaderPageRecord, offset: LeaderPageOffset] = BEGIN prop: LONG POINTER TO PropertyRep = LOOPHOLE[lp+offset]; size: CARDINAL = prop.size; PrincOpsUtils.LongCopy[to: prop, from: prop+size, nwords: lp.dataEnd-(offset+size)]; lp.dataEnd _ lp.dataEnd-size; END; GetDefaultPropertyValue: PROCEDURE [property: Property] RETURNS [propertyValue: PropertyValuePair] = BEGIN RETURN [ SELECT property FROM byteLength => [byteLength[0]], createTime => [createTime[BasicTime.Now[]]], highWaterMark => [highWaterMark[0]], modifyAccess => [modifyAccess[NIL]], owner => [owner[NIL]], readAccess => [readAccess[NIL]], stringName => [stringName[NIL]], version => [version[1]], ENDCASE => ERROR]; END; ValidateInternal: LeaderPageWork --[lp: LONG POINTER TO LeaderPageRecord]-- = BEGIN offset: LeaderPageOffset; definedPropertiesSet: PACKED ARRAY PickledProperty OF BOOLEAN _ ALL [FALSE]; IF lp.seal#sealLeaderPageRecord OR lp.dataStart NOT IN [2..AlpineEnvironment.wordsPerPage) OR lp.dataEndlp.dataEnd OR prop.property NOT IN PickledProperty THEN ERROR Error[damaged]; -- Malformed property IF definedPropertiesSet[prop.property] THEN ERROR Error[damaged] -- Duplicate property ELSE definedPropertiesSet[prop.property] _ TRUE; offset _ offset+prop.size; ENDLOOP; IF offset#lp.dataEnd THEN ERROR Error[damaged]; IF ~definedPropertiesSet[version] THEN ERROR Error[damaged]; -- Version property missing END; newLeaderPageObject: PUBLIC PROC [nPages: CARDINAL] RETURNS [leaderPage: LeaderPageHandle] = BEGIN leaderPage _ leaderPageZone.NEW[LeaderPageObject[nPages-1]]; leaderPage.nPages _ nPages; END; <> leaderPageZone: ZONE = SafeStorage.GetSystemZone[]; -- This zone is only for LeaderPageObjects END. <<>> <<>> <> <<>> <> <<>> <> <<>> <> <<>> <> <<>> <> <<>> <<>> <> <<>> <> <<>> <> <<>> <> <<>> < unknown (not present in LM)>> < no page writes this trans.>> <> <<>> <> <<>> <> <<>> <> <<[Assert: trans has a whole-file write lock on file.]>> <> <<>> <<[Note: opening a file does not perform any operations on the hwm.]>> <<>> <> <> <> <=read THEN {>> <<[Assert: trans holds at least a whole file read lock, which also covers hwm property]>> <> <LM.hwm THEN target _ base -- can write directly to base --};>> <<>> <> <> <> <> <<>> <> <> <<>> <> <> <> <> <LM.hwm THEN {>> <> <> <> <> <> <> <<>> <> <> <<>> <> <> <> <> <<>>