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, Equal, 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; GetUserProperty: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, property: UserProperty, lock: LockOption _ [read, wait]] RETURNS [propertyValue: UserPropertyValuePair] = 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] _ GetUserPropertyInternal[lp: lp, property: property]; END; -- Work FileLock.AcquirePropertyLock[fileInstance: fileInstance, property: stringName, requested: lock, minimum: read]; 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; GetUserPropertyList: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, desiredProperties: UserProperties, lock: LockOption _ [read, wait]] RETURNS [properties: UserPropertyValuePairs _ NIL] = BEGIN Work: LeaderPageWork --[lp: LONG POINTER TO LeaderPageRecord, nPages: CARDINAL]-- = BEGIN offset: LeaderPageOffset _ Find[lp, 0, userProperty]; WHILE offset # 0 DO prop: LONG POINTER TO PropertyRep[userProperty] _ LOOPHOLE[lp+offset]; property: String _ StringFromRep[@prop.userPropertyValue.name]; userVal: LONG POINTER TO UserValueRep _ LOOPHOLE[@prop.userPropertyValue+RepFromString[NIL, property, FALSE]]; WITH v: userVal SELECT v.type FROM longCardinal => properties _ CONS[[property, longCardinal[v.longCardinal]], properties]; rope => properties _ CONS[[property, rope[StringFromRep[@v.rope]]], properties]; ENDCASE => ERROR; offset _ Find[lp, offset+prop.size, userProperty]; ENDLOOP; END; -- Work IF desiredProperties = NIL THEN { PerformWork[fileInstance: fileInstance, work: Work, intention: read] } ELSE FOR p: UserProperties _ desiredProperties, p.rest WHILE p # NIL DO properties _ CONS[GetUserProperty[fileInstance: fileInstance, property: p.first, lock: lock], properties]; 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 => { file: FileMap.Handle _ FileInstance.GetFileHandle[fileInstance]; committedHighWaterMark: PageCount _ LogMap.GetCommittedHighWaterMark[file]; IF committedHighWaterMark=LAST[PageCount] THEN BEGIN committedHighWaterMark _ NARROW[LeaderPage.GetProperty[fileInstance, highWaterMark], PropertyValuePair[highWaterMark]].highWaterMark; LogMap.SetCommittedHighWaterMark[file, committedHighWaterMark] END; [] _ FileInstance.SetHighWaterMark[fileInstance, highWaterMark]; }; version => ERROR Error[unwritableProperty]; ENDCASE; PerformWork[fileInstance: fileInstance, work: Work, intention: write]; END; SetUserProperty: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, propertyValue: UserPropertyValuePair, lock: LockOption _ [update, wait]] = BEGIN Work: LeaderPageWork --[lp: LONG POINTER TO LeaderPageRecord, nPages: CARDINAL]-- = BEGIN IF validateBeforeEveryOperation THEN ValidateInternal[lp, nPages]; SetUserPropertyInternal[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: stringName, requested: lock, minimum: update]; 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 FileLog.LogChangeHWM[fileInstance, LogMap.GetCommittedHighWaterMark[file], newHighWaterMark]; SetProperty[fileInstance: fileInstance, propertyValue: [highWaterMark[newHighWaterMark]], lock: [write, wait]] 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, 0, 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; FindUserProp: PROCEDURE [lp: LONG POINTER TO LeaderPageRecord, property: UserProperty] RETURNS [offset: LeaderPageOffset] = BEGIN offset _ Find[lp, 0, userProperty]; WHILE offset#0 DO prop: LONG POINTER TO PropertyRep[userProperty] _ LOOPHOLE[lp+offset]; propName: String _ StringFromRep[@prop.userPropertyValue.name]; IF Rope.Equal[propName, property, FALSE] THEN RETURN; offset _ Find[lp, offset+prop.size, userProperty]; ENDLOOP; END; GetUserPropertyInternal: PROCEDURE [lp: LONG POINTER TO LeaderPageRecord, property: UserProperty] RETURNS [found: BOOLEAN _ FALSE, propertyValue: UserPropertyValuePair] = BEGIN offset: LeaderPageOffset = FindUserProp[lp, property]; IF offset#0 THEN { prop: LONG POINTER TO PropertyRep[userProperty] _ LOOPHOLE[lp+offset]; userVal: LONG POINTER TO UserValueRep _ LOOPHOLE[@prop.userPropertyValue+RepFromString[NIL, property, FALSE]]; found _ TRUE; WITH v: userVal SELECT v.type FROM longCardinal => propertyValue _ [property, longCardinal[v.longCardinal]]; rope => propertyValue _ [property, rope[StringFromRep[@v.rope]]]; ENDCASE => ERROR; RETURN; }; 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, start: 0, 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; SetUserPropertyInternal: PROCEDURE [lp: LONG POINTER TO LeaderPageRecord, nPages: CARDINAL, propertyValue: UserPropertyValuePair] = BEGIN offset: LeaderPageOffset = FindUserProp[lp: lp, property: propertyValue.property]; prop: LONG POINTER TO PropertyRep _ LOOPHOLE[lp+offset]; size: CARDINAL = UserRepSize[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 _ userProperty; WITH pp: prop SELECT userProperty FROM userProperty => { val: LONG POINTER TO UserValueRep _ LOOPHOLE[@pp.userPropertyValue.name + RepFromString[@pp.userPropertyValue.name, propertyValue.property]]; val.type _ PickledUserTypeMap[propertyValue.propertyType]; WITH pV: propertyValue SELECT FROM longCardinal => WITH v: val SELECT longCardinal FROM longCardinal => v.longCardinal _ pV.longCardinal; ENDCASE; rope => WITH v: val SELECT rope FROM rope => [] _ RepFromString[@v.rope, pV.rope]; ENDCASE; ENDCASE => ERROR; } ENDCASE; 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; UserRepSize: PROCEDURE [propertyValue: UserPropertyValuePair] RETURNS [size: CARDINAL] = BEGIN sizeCommon: CARDINAL = 1; -- size of common part of PropertyRep record size _ sizeCommon+RepFromString[NIL, propertyValue.property, FALSE]+( WITH propertyValue SELECT FROM longCardinal => 1+1, rope => 1+RepFromString[NIL, rope, FALSE], 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, start: LeaderPageOffset, pickledProperty: PickledProperty] RETURNS [offset: LeaderPageOffset] = BEGIN offset _ MAX[start, 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. ' LeaderPageImpl.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Last edited by: MBrown on January 30, 1984 12:17:43 pm PST Taft on June 3, 1983 11:12 am Kolling on June 3, 1983 1:37 pm Hauser, March 8, 1985 10:37:36 am PST Carl Hauser, June 26, 1986 4:58:17 pm PDT LeaderPage. If not in LogMap, fall through and do the work to get the version from the base. Note: Finalize and CarryOut need not serialize access to the file by multiple clients in the same transaction, because only one client at a time can access the FileStore while the transaction is in this late stage. However, exclusion of access under other transactions (by locking) is still required. No need to do any of this stuff if the file was not modified by this transaction or if recovery is in progress. Note: during recovery, the version property will be locked when the WriteLeaderPage record is encountered in the log, which will occur before CarryOut is called. File deleted by this transaction: no need to update leader page This transaction created the file: ordinarily, no need to update the version at all, since the leader page was initialized with a default version of 1. However, if the client has changed deltaVersion, then write the new version directly to the base. vmPageSetHandle ensures continued existence of vmPageSet; Pre-existing file: must write a leader page log record containing the incremented version number. Leader page not otherwise updated by this transaction. Copy the leader page from the base file into a temporary LeaderPageRecord (this also causes the committed version to be remembered in the LogMap if it is not already known). Debugging switches, settable by client FileLog. The version lock covers the actual logging and writing of the leader page, so there is no need to lock the other properties. (This is just as well, since we don't have any way of knowing whether the other properties are supposed to be locked or not.) Also no need to fiddle with the highWaterMark, since the correct new value is already contained in the logged leader page; we just let it remain undefined in both the FileInstance and the LogMap. Private procedures Obtains a pointer to the file's leader page (or a copy thereof, after doing appropriate FilePageMgr and/or LogMap operations), enters the FileHandle's monitor, and calls work[lp: LONG POINTER TO LeaderPageRecord]. Expects appropriate locking to have been done already. The alreadySet error will no longer be raised by SetLeaderPageHandle; there may be circumstances where it should be, so I'll leave this here for now. This can happen only if some other process set the LeaderPageHandle concurrently. Just discard the newly-created one and use the one that is already there. Creates and returns LeaderPageObject for file; reads the file's leader page into it (from the base file), and remembers the committed version in the LogMap if it is not already known. Should be called only for an existing (as opposed to newly-created) file, and the leader page must be at least read-locked by the caller. ReadLeaderPages need not be protected by the FileObject's monitor, because the base file's leader page is immutable while the current transaction is in progress. Returns offset of specified property, or zero if not found. ! Error {full, nameTooLong}; Leader space will have to be extended before setting the new property value; ! Error {full, nameTooLong}; Leader space will have to be extended before setting the new property value; ! Error {nameTooLong}; ! Error {nameTooLong}; Returns offset of specified property, or zero if not found. Module initialization Appendix 1: leader page locking Access to the contents of the leader page itself (in the base file system) and of the cached copy (in the LogMap) is serialized by explicitly entering the FileObject's monitor. All code outside that monitor is intended to work properly even in the face of concurrent access to the same file within the same transaction. Transaction consistency is accomplished as follows. There are two locks, one for the version property and one for all other properties. When the first change is made to a property other than the version, a copy of the leader page is read and cached in the LogMap. Everything in this copy except for version is covered by the lock. The version is read-only from the client's point of view and is changed only during transaction commit. During phase 1 of commit, a write lock is placed on the version. If no other properties were written by the same transaction, the leader page is read from the base file, the updated version number stored in it, and it is written into the log. But if other properties were written by the same transaction, the updated version is instead stored in the cached copy and that is written into the log. In either case, during phase 2 the logged copy is rewritten into the base before the version lock (and the other properties lock, if acquired) is released. Note that the cached copy, if present, is not written into the log until phase 1 of commit; correctness depends on this, as will become apparent shortly. Additionally, there is an efficiency advantage in doing things this way: changing multiple properties during a transaction will not result in writing multiple leader page records to the log. The crucial observation here is that if the file was changed in any way by this transaction (including changing properties other than the version), then the version lock covers the creation, logging, and rewriting of the new version of the leader page. Note that if this transaction does not change any property besides the version, some other transaction may be making changes to the properties (and have a copy of the leader page cached in the LogMap). This means that the version property in the cached leader page may become obsolete. Therefore, the commited version number is maintained separately in the LogMap (and is updated only at commit time while the version is write-locked). Correct recovery depends on the fact that leader pages (for committed transactions) are logged in strictly increasing order of version number, and each is followed by the commit record for the same transaction, with no intervening leader page records for the same file. This is enforced by the write lock on the version, which is set before writing the leader page record and is not removed until after commit. Appendix 2: high water mark property maintenance Handling of the highWaterMark property is also somewhat tricky, because it is desirable to maintain it properly without having to lock it and check it during every page write. To accomplish this, uncommitted and committed highWaterMark values are maintained in the FileInstance and LogMap, respectively. Things are complicated by the fact that a file's LogMap does not exist (and its cached committed highWaterMark therefore unknown) until the first write is performed. The following pseudo-program sketches the basic algorithm: Abbreviations: LM = LogMap, FI = FileInstance, LP = LeaderPage, hwm = highWaterMark. hwm has a distinguished nil value whose representation is actually LAST[PageCount]. When a LM or FI is created, its hwm is initialized to nil. LM.hwm = committed hwm; nil => unknown (not present in LM) FI.hwm = highest page written during this trans since last explicit sethwm, or last explicit sethwm; nil => no page writes or sethwms in this trans. LP.hwm = last explicit hwm in this trans if any, else committed hwm; nil does not occur. Important invariant: if LP.hwm has been changed by trans then LM.hwm contains the actual committed hwm (not nil). In fact, if this transaction has done any hwm manipulations at all, including just write pages, LM.hwm contains the actual committed hwm (not nil). This stronger invariant may not be important. The following operations are the only ones which touch the hwm in any way. AlpineFile.Create: [Assert: trans has a whole-file write lock on file.] LM.hwm _ FI.hwm _ LP.hwm _ 0; [Note: opening a file does not perform any operations on the hwm.] AlpineFile.WritePages, in which pageNumber is the last page to be written: FI.hwm _ IF FI.hwm=nil THEN 1+pageNumber ELSE MAX[FI.hwm, 1+pageNumber]; target _ log; -- assume we will have to write into the log IF FI.lockMode>=read THEN { [Assert: trans holds at least a whole file read lock, which also covers hwm property] IF LM.hwm=nil THEN LM.hwm _ LP.hwm; -- ensure committed hwm is in LM IF pageNumber>=LM.hwm THEN target _ base -- can write directly to base --}; LeaderPage.SetProperty[hwm], in which client explicitly changes hwm property to hwmNew: AcquirePropertyLock[hwm, update]; IF LM.hwm=nil THEN LM.hwm _ LP.hwm; -- ensure committed hwm is in LM FI.hwm _ LP.hwm _ hwmNew; LeaderPage.GetProperty[hwm]: AcquirePropertyLock[hwm, read]; result _ IF FI.hwm=nil THEN LP.hwm ELSE FI.hwm; LeaderPage.Finalize: IF FI.hwm#nil THEN { AcquirePropertyLock[hwm, read]; IF FI.hwm>LM.hwm THEN { increase in committed hwm value, due to WritePages, SetProperty, or both AcquirePropertyLock[hwm, write]; LP.hwm _ FI.hwm; writeChangeHWM[LM.hwm, FI.hwm] } ELSE decreasing or unchanging. Claim: can decrease only if SetProperty was called, in which case hwm is already locked LP.hwm _ FI.hwm; }; LeaderPage.CarryOut: IF FI.hwm#nil THEN LM.hwm _ FI.hwm; Change History: Hauser February 12, 1985: Changes to handle multiple leader pages: LeaderPageObject becomes a variable-sized object; hence, allocate LeaderPageObjects only after we know how many leader pages there are. Hauser, March 8, 1985 10:37:22 am PST Added copyright. Carl Hauser, August 26, 1985 5:13:37 pm PDT The highWaterMark code in SetProperty and Finalize looked like it couldn't work, and violated stated invariants. Changed to correspond to the invariants. Will it work as well now? Also changed the comments at the end about hwm. changes to: SetProperty added call to LogMap.SetCommittedHighWaterMark, Finalize reworked highWaterMark code. Κ«– "cedar" style˜šœ™Icodešœ Οmœ1™<—šœ™Jšœ*™*Jšœ™Jšœ™K™%K™)—J˜unitšΟk ˜ K˜K˜ K˜ K˜Kšœžœ˜Kšœ žœ˜,Kšœ žœ ˜K˜ K˜ K˜K˜K˜ K˜ K˜ K˜K˜Kšœžœ ˜Kšžœžœ˜!Kšœžœ,˜6Kšœ žœ˜"K˜—šœž˜Kšžœž˜₯Kšžœ&˜-Kšžœžœ˜(K˜Kšœžœ˜(K˜Kšœžœžœ˜.Kšœžœ!ž˜B—J˜J˜šœ ™ šΟn œžœž œ&˜BKšž˜K˜@K˜IK˜TšŸœΟcΠck Πcs œ˜SKšž˜K˜:K˜'K˜šžœžœ ž˜%K˜YKšžœ˜—K˜1K˜4Kšžœžœ˜9Kšžœ ˜ —šžœžœž˜5Kšœ5žœžœ˜S—Kšžœ0žœžœ˜=K˜FK˜3K˜?Kšžœ˜—šŸ œžœž œZžœ%˜£Kšž˜K˜@šŸœ ‘ ’ œ˜SKšž˜Kšœžœ˜Kšžœžœ˜BK˜_Kšžœžœ3˜AKšžœ ˜ —K˜mšžœ ž˜˜Kšžœ J˜QK˜MK˜4šžœžœ ž˜+šžœžœž˜Kšœ!žœ%˜IKšžœžœ˜——Kšžœ˜Kšžœ˜—˜ Kšž˜K˜>Kšžœ žœžœ˜.JšœP™PKšžœ˜—Kšžœ˜—K˜4Kšžœ˜—šŸœžœž œ^žœ)˜―Kšž˜K˜@šŸœ ‘ ’ œ˜SKšž˜Kšœžœ˜Kšžœžœ˜BK˜cKšžœ ˜ —K˜oK˜4Kšžœ˜—šŸœžœž œFžœžœ$žœžœžœžœ˜ΝKšž˜šžœž œžœ ž˜0šžœž˜#Kšœ$žœX˜€—Kšžœ˜—Kšžœ˜—š Ÿœžœž œižœ'žœ˜ΒKšž˜šŸœ ‘ ’ œ˜SKšž˜Kšœ5˜5šžœ ž˜Kš œžœžœžœžœ ˜FKšœ?˜?Kš œ žœžœžœžœ'žœ žœ˜nšžœ žœžœ˜#Kšœžœ7˜XKšœžœ7˜PKšžœžœ˜—Kšœ2˜2Kšžœ˜—Kšžœ ˜ —šžœžœžœ˜!K˜DK˜—šžœ/žœž˜GKšœ žœY˜jKšžœ˜—Kšžœ˜—šŸ œžœž œk˜ˆKšž˜šŸœ ‘ ’ œ˜SKšž˜Kšžœžœ˜BK˜JKšžœžœ˜>K˜1Kšžœ ˜ —K˜@K˜}šžœžœž˜˜Kšœ@˜@K˜Kšžœžœ ž ˜4Kšœžœf˜…Kšœ>˜>Kšžœ˜—K˜@K˜—˜ Kšžœ˜ —Kšžœ˜—K˜FKšžœ˜—šŸœžœž œo˜Kšž˜šŸœ ‘ ’ œ˜SKšž˜Kšžœžœ˜BK˜NKšžœžœ˜>K˜1Kšžœ ˜ —K˜@K˜qK˜FKšžœ˜—š Ÿœžœž œ3žœžœ8˜“Kšž˜šžœžœž˜K˜WK˜!Kšžœ˜—Kšžœ˜—šŸœžœž œ&˜@Kšž˜K˜@Kšžœ˜J˜—Jšœ­™­šŸœžœž œ&˜@Kšž˜Kšœžœžœ.˜HJšœ’™’šžœžœž˜2Kšž˜K˜@K˜IK˜TK˜šžœž˜Jšœ?™?Kšœ.žœžœ˜C—šžœž˜ K˜Jšžœžœ ž˜(Kšžœ !˜(šžœ;ž˜AKšœ]˜]—K˜nKšžœ˜—K˜všžœžœž˜6Kšž˜Jšœω™ωKšœ ˜7šžœ ž˜Kšž˜K˜!K˜"K™9K˜AKšœm˜mKšœ^žœ˜dKšžœ˜—Kšž˜—šžœž˜ Jšœa™aK˜K˜Kšœžœžœžœ˜%K˜Bšžœ žœž˜Jšœε™εK˜#—Kšœ˜K˜1K˜K˜ZKšœ[˜[K˜KKšœ.žœžœ˜DKšžœ˜—K˜IKšžœ˜—Kšžœ˜—Kšžœ˜—šŸœžœž œ&˜@Kšž˜Kšœžœžœ.˜Hšžœžœ 5˜MKšž˜K˜@K˜IK˜GK˜K˜K˜K˜`šžœž˜Kšž˜K˜!K˜"K˜XKšœ5˜5K˜@Kšœ€˜€Kšœ^žœ˜dK˜"Kšžœ˜—K˜hšžœžœ ž˜%K˜K—Kšœ0žœ˜AKšžœ˜—Kšžœ˜—Lšœžœžœžœ˜2—J˜J˜šœ&™&Lšœžœžœžœ˜5Lšœžœžœžœ˜1—J˜J˜šœ™šŸœžœž œb˜ŠKšž˜šžœž˜Kšž˜K˜@K˜IJšœΎ™ΎK˜vK˜KKšžœ˜—Kšžœ˜——J˜J˜šœ™LšŸœžœž œžœžœžœžœ˜ZšŸ œž œ]˜sJšœ³žœK™Kšž˜KšŸœžœž œžœ˜4K˜@K˜IK˜TK˜TKšœžœžœžœ˜%Kšœž ˜š žœ žœžœžœž˜BKšžœ $˜+K˜!K˜"Kšœ$žœžœžœ˜\šž˜Kšœžœ˜šž˜Kšœž˜˜?Kšœžœ˜?—K˜K˜!˜Kš œžœ žœžœžœ˜EKšžœTžœ˜a—KšœPžœ˜VKšžœžœžœžœ˜K˜*Kšžœ˜—Kšžœ˜—Kšž˜—šžœžœ E˜Qšžœ žœž˜Kšž˜K˜#˜MK™•šœžœžœ˜*Jšœ›™›K˜CKšžœ žœžœžœ˜Kšžœ˜ ——Kšžœ˜—šžœ˜Kšœžœ˜K˜ šž˜Kšœžœ˜K˜K˜˜Kš œžœ žœžœžœ˜G—Kšžœžœžœžœ˜Jšœ9˜9Jšœ†˜†Jšœ˜K˜M—Kšžœ˜Kšžœ˜—Kšžœ˜—Kšžœ˜—šŸœž œžœ!˜ZJšœδ™δKšž˜K˜!K˜"Kšœžœžœžœ˜%K˜AKšœ˜Kšœ;˜;Kšœ€˜€KšœNžœ˜Tšžœ*ž˜0Kšž˜K˜!Kšœžœ˜K˜HKšžœžœžœ˜Kšœ0žœ5˜kKšžœ˜—Kšžœ˜—šŸœž œžœžœžœ'žœ žœžœ%˜žKšž˜K˜?Kšœžœžœžœ ˜"Kšœ8˜8Kšžœ žœžœ˜Kšœžœ ˜šžœžœž˜#K˜7K˜MK˜@K˜QK˜8K˜KK˜GK˜.Kšžœžœ˜—Kšœžœ˜ Kšžœ˜K˜—š Ÿ œž œžœžœžœ+žœ˜{Jšœ;™;Kšž˜Kšœ#˜#šžœ ž˜Kš œžœžœžœžœ ˜FKšœ?˜?Kšžœ žœžœžœ˜5Kšœ3˜3Kšžœ˜—Kšžœ˜—šŸœž œžœžœžœ+žœ žœžœ)˜ͺKšž˜Kšœ6˜6šžœ žœ˜Kš œžœžœžœžœ ˜FKš œ žœžœžœžœ'žœ žœ˜nKšœžœ˜ šžœ žœžœ˜#KšœI˜IKšœA˜AKšžœžœ˜—Kšžœ˜K˜—Kšžœ˜—š Ÿœž œžœžœžœ˜AJšžœ˜"Kšž˜Kšœžœžœžœ˜4Kšœžœ˜Kšœ žœ˜šžœž˜Kšœ.žœžœ˜OKšžœžœžœžœ˜EK˜Kšœžœžœ>˜]Kšžœ˜—Kšžœ˜—š Ÿ œž œžœžœžœ žœ˜TKšž˜š Ÿœžœž œžœžœž˜1K˜'—Kšœžœ˜Kš žœžœžœžœžœ+˜PKšžœ˜—š Ÿœž œžœžœžœžœ%˜{Jšœ™Kšž˜K˜MK˜TKš œžœžœžœžœ ˜8Kšœžœ˜(Kš œžœžœ žœ žœ ˜Sšžœ7ž ˜CJ™LJšžœ ˜Jšžœ˜—Kšžœ žœ ˜0Kšœžœ˜K˜K˜ šžœžœž˜˜ šžœ žœ ž˜$Kšœ*žœ˜2——˜ šžœ žœ ž˜$Kšœ>žœ˜F——˜šžœ žœž˜'Kšœ3žœ˜;——˜šžœ žœž˜&KšœHžœ˜P——˜šžœ žœž˜Kšœ/žœ˜7——˜ šžœ žœ ž˜$KšœBžœ˜J——˜ šžœ žœ ž˜$Kšœ>žœ˜F——˜ šžœ žœ ž˜!Kšœ!žœ˜)——Kšžœžœ˜—K˜"Kšžœ˜—š Ÿœž œžœžœžœžœ)˜ƒJšœ™Kšž˜KšœR˜RKš œžœžœžœžœ ˜8Kšœžœ˜,Kš œžœžœ žœ žœ ˜Sšžœ7ž ˜CJ™LJšžœ ˜Jšžœ˜—Kšžœ žœ ˜0Kšœžœ˜K˜K˜šžœ žœž˜&šœ˜Kš œžœžœžœžœa˜Kšœ:˜:šžœžœž˜"šœžœžœžœ˜5Kšœ2žœ˜:—šœžœžœž˜$Kšœ.žœ˜6—Kšžœžœ˜—K˜—Kšžœ˜—K˜"Kšžœ˜K˜—šŸœž œ$žœžœ˜PKšž˜Kšœ žœ ,˜Gšœžœžœž˜%˜1Kšžœ˜—˜Kšœžœžœ ˜7—˜Kšœžœ žœ ˜,—˜ Kšœžœžœ ˜5—˜ Kšœžœžœ ˜1—Kšžœžœ˜—Kšžœ˜—šŸ œž œ(žœžœ˜XKšž˜Kšœ žœ ,˜Gšœ žœžœ˜Ešžœžœž˜J˜Jšœžœžœ˜*Jšžœžœ˜——Kšžœ˜—šŸœž œžœžœžœ.žœžœžœžœ˜ŠJšœ™Kšž˜Kšœžœ˜Kšœ žœžœžœ˜7Kšœžœžœ˜šžœ6žœ žœž˜PKšœ žœ4˜HK˜K˜K˜!Kšžœ˜—Kšžœžœ˜Kšžœ˜—šŸ œž œžœžœžœ"žœžœžœžœ˜zJšœ™Kšž˜š œ ‘ ‘ ‘ ‘ œž˜KK˜'—Kšœžœ˜"Kšœžœ˜Kšžœžœžœžœ˜AKšœžœžœ7˜Kšžœž˜ Kšž˜K˜Kšžœ žœ9˜IKšžœ˜—Kšžœ˜—š Ÿœž œžœžœžœNžœ˜–Jšœ;™;Kšž˜Kšœ žœ˜"šžœž˜Kš œžœžœžœžœ ˜8Kšžœžœžœ˜+K˜šž˜Kšžœ˜—Kšžœ˜—Kšžœ˜—š Ÿœž œžœžœžœ.˜TKšž˜Kš œžœžœžœžœ ˜8Kšœžœ ˜KšœT˜TK˜Kšžœ˜—šŸœž œžœ%˜dKšž˜šžœ˜šžœ ž˜K˜K˜,K˜$Kšœžœ˜$Kšœžœ˜Kšœžœ˜ Kšœžœ˜ K˜Kšžœžœ˜——Kšžœ˜—šŸœ ‘ œ˜MKšž˜K˜Kš œžœžœžœžœžœžœ˜Lš žœžœžœžœ&žœž˜|Kšžœ '˜>—K˜šžœž˜Kš œžœžœžœžœ ˜8š žœ žœžœžœžœž˜ZKšžœ ˜,—šžœ%ž˜+Kšžœ ˜+—Kšžœ'žœ˜0K˜Kšžœ˜—Kšžœžœžœ˜/Kšžœ žœžœ ˜YKšžœ˜K˜—š Οbœžœžœ žœžœ!˜\šž˜JšœΟsœ˜