-- FilePageMgrMainImpl.mesa -- Last edited by -- Kolling on February 16, 1984 12:02:55 pm PST -- MBrown on January 31, 1984 9:01:07 pm PST DIRECTORY AlpineEnvironment USING[FileID, PageCount, PageNumber, PageRun, VolumeID, VolOrVolGroupID], AlpineInternal USING[FileHandle], AlpineZones USING[static], Basics USING[LongDivMod], File USING[Create, Delete, Error, FindVolumeFromID, FP, GetProperties, Handle, Info, Open, PropertyStorage, RC, SetSize, Volume, VolumeID], FileMap USING[GetNext, GetVolumeIDAndFileID, Handle, VerifyFilePageMgrHandle], FilePageMgr USING[DirtyNoWaitReleaseState, DirtyWaitReleaseState, ReleaseState, VMPageSet], FilePageMgrIO USING[DoIO, GetNext, IORequest, IOType, LogError, RegisterRequest], FilePageMgrLru USING[CheckCacheInCleanState, GetOtherChunkFromLruList, GetOurChunkFromLruList, InitializeLruLists, LruListPlace, PutMappedChunkOnLruList, PutUnmappedChunkOnLruList, RelinkChunkAsLruOnLruList, SweepItem, UsingTooMuchOfCache, WaitToSweep], FilePageMgrPrivateChunk USING[Chunk, ChunkFilePageCount, ChunkVMPageCount, ChunkType, ClientChunkType, NLeaderFilePages, RefChunk], FilePageMgrPrivateFile USING[FPMFileObject, LeaderFilePageNumber, VolumeState], OrderedSymbolTable USING[CreateTable, Delete, Initialize, Insert, Lookup, LookupLargest, LookupNextLarger, LookupNextSmaller, LookupProc, LookupSmallest, Table], Process USING[Detach, GetCurrent], VM USING[AddressForPageNumber, Allocate, Interval, MakeUnchanged, PageCount, PageNumber, PageNumberForAddress, Pin, State]; FilePageMgrMainImpl: CEDAR MONITOR LOCKS fpmFileHandle USING fpmFileHandle: FPMFileHandle IMPORTS AZ: AlpineZones, Basics, File, FileMap, FpmIO: FilePageMgrIO, FpmL: FilePageMgrLru, OST: OrderedSymbolTable, Process, VM EXPORTS AlpineInternal, FilePageMgr, FilePageMgrPrivateChunk SHARES FilePageMgr = BEGIN OPEN AI: AlpineInternal, AE: AlpineEnvironment, Fpm: FilePageMgr, FpmPC: FilePageMgrPrivateChunk, FpmPF: FilePageMgrPrivateFile; VolOrVolGroupID: TYPE = AE.VolOrVolGroupID; FileID: TYPE = AE.FileID; FPMFileHandle: TYPE = REF FPMFileObject; FPMFileObject: PUBLIC TYPE = FpmPF.FPMFileObject; RefChunk: TYPE = REF Chunk; Chunk: PUBLIC TYPE = FpmPC.Chunk; -- Monitors can only be nested in this order: -- FPMFileObject monitor -- RedBlackTreeImpl -- FilePageMgrLruImpl -- FileMap's FileObject monitor -- No process ever holds more than one FPMFileObject monitor. InsufficientSpaceOnVolume: PUBLIC -- ABSTRACTION -- ERROR = CODE; NoSuchFile: PUBLIC -- CALLING -- ERROR = CODE; NoSuchVolume: PUBLIC -- CALLING -- ERROR = CODE; PageRunArgIllegal: PUBLIC -- CALLING -- ERROR = CODE; PageRunExtendsPastEof: PUBLIC -- CALLING -- ERROR = CODE; SizeArgIllegal: PUBLIC -- CALLING -- ERROR = CODE; VolumeTooFragmented: PUBLIC -- ABSTRACTION -- ERROR = CODE; VolumeWentOffline: PUBLIC -- ABSTRACTION -- ERROR = CODE; VolumeError: ARRAY FpmPF.VolumeState OF ERROR _ [online: InternalFilePageMgrLogicError, wentOffline: VolumeWentOffline, nonExist: NoSuchVolume]; InternalFilePageMgrLogicError: PUBLIC -- PROGRAMMING -- ERROR = CODE; Okay: ERROR = CODE; -- for our own use. ReadDone: CONDITION; WriteDone: CONDITION; ReadPages: PUBLIC PROCEDURE[fileHandle: AI.FileHandle, pageRun: AE.PageRun] RETURNS [vMPageSet: Fpm.VMPageSet] = BEGIN -- non system fatal errors: NoSuchFile, NoSuchVolume, PageRunArgIllegal, PageRunExtendsPastEof, VolumeWentOffline. vMPageSet _ BasicGetPages[fileHandle, pageRun, read, normal].vMPageSet; END; ReadLogPages: PUBLIC PROCEDURE[fileHandle: AI.FileHandle, pageRun: AE.PageRun] RETURNS [vMPageSet: Fpm.VMPageSet] = BEGIN -- non system fatal errors: NoSuchFile, NoSuchVolume, PageRunArgIllegal, PageRunExtendsPastEof, VolumeWentOffline. vMPageSet _ BasicGetPages[fileHandle, pageRun, read, log].vMPageSet; END; ReadLeaderPages: PUBLIC SAFE PROCEDURE[fileHandle: AI.FileHandle] RETURNS [vMPageSet: Fpm.VMPageSet] = BEGIN -- non system fatal errors: NoSuchFile, NoSuchVolume, VolumeWentOffline. vMPageSet _ BasicGetPages[fileHandle, [0, 0], read, leader].vMPageSet; END; UsePages: PUBLIC PROCEDURE[fileHandle: AI.FileHandle, pageRun: AE.PageRun] RETURNS [vMPageSet: Fpm.VMPageSet] = BEGIN -- non system fatal errors: NoSuchFile, NoSuchVolume, PageRunArgIllegal, PageRunExtendsPastEof, VolumeWentOffline. vMPageSet _ BasicGetPages[fileHandle, pageRun, use, normal].vMPageSet; END; UseLogPages: PUBLIC PROCEDURE[fileHandle: AI.FileHandle, pageRun: AE.PageRun] RETURNS [vMPageSet: Fpm.VMPageSet] = BEGIN -- non system fatal errors: NoSuchFile, NoSuchVolume, PageRunArgIllegal, PageRunExtendsPastEof, VolumeWentOffline. vMPageSet _ BasicGetPages[fileHandle, pageRun, use, log].vMPageSet; END; UseLeaderPages: PUBLIC PROCEDURE[fileHandle: AI.FileHandle] RETURNS [vMPageSet: Fpm.VMPageSet] = BEGIN -- non system fatal errors: NoSuchFile, NoSuchVolume, VolumeWentOffline. vMPageSet _ BasicGetPages[fileHandle, [0, 0], use, leader].vMPageSet; END; MyPageRun: TYPE = RECORD[firstPage: AE.PageNumber, count: NAT, chunkStartFilePage: AE.PageNumber]; ReadReadAheadOrUse: TYPE = {read, readAhead, use}; -- the parameters ReadReadAheadOrUse and ChunkType are used by BasicGetPages as follows: -- ChunkType: -- (a) each chunk type may have a different size, which affects the -- conversion of the client's page number, etc. to the chunk -- page number, etc. -- (b) tells which lru list to go to to get a new chunk. -- (c) special handling for leader (due to its being handled differently by File). -- ReadReadAheadOrUse (except leader): -- (a) read may do a read, use doesn't have to read unless partial chunk, readAhead's -- caller will do the reads in a bunch. BasicGetPages: PROCEDURE[fileHandle: AI.FileHandle, pageRun: AE.PageRun, readReadAheadOrUse: ReadReadAheadOrUse, chunkType: FpmPC.ClientChunkType] RETURNS [vMPageSet: Fpm.VMPageSet, nFilePagesToRead: AE.PageCount] = -- the peculiar structure is to avoid monitor conflicts. BEGIN -- non system fatal errors: NoSuchFile, NoSuchVolume, PageRunArgIllegal, PageRunExtendsPastEof, VolumeWentOffline. fpmFileHandle: FPMFileHandle _ GetFilePageMgrHandle[fileHandle, FALSE]; myPageRun: MyPageRun; otherRefChunk: RefChunk _ NIL; errors: ERROR; done: BOOLEAN; IF ((chunkType # leader) AND ((pageRun.firstPage < 0) OR (pageRun.count = 0))) THEN ERROR PageRunArgIllegal; myPageRun _ [firstPage: pageRun.firstPage, count: pageRun.count, chunkStartFilePage: ChunkStartFilePage[pageRun.firstPage, chunkType]]; DO [errors, done, nFilePagesToRead, vMPageSet] _ MonitoredFindOldOrGiveNewChunk[fpmFileHandle, fileHandle, otherRefChunk, myPageRun, readReadAheadOrUse, chunkType]; IF errors # Okay THEN ERROR errors; IF done THEN BEGIN IF ((nFilePagesToRead # 0) AND (readReadAheadOrUse # readAhead)) THEN BEGIN refChunk: RefChunk _ vMPageSet.refChunk; FpmIO.DoIO[read, fpmFileHandle.lowerHandle, [[refChunk.startFilePageNumber], nFilePagesToRead, VMAddressForPageNumber[refChunk.startVMPageNumber]]]; MakeAllPagesInChunkClean[refChunk]; MonitoredSetChunkValidAfterIO[fpmFileHandle, vMPageSet.refChunk, readCompleted]; END; RETURN; END; IF otherRefChunk = NIL THEN otherRefChunk _ MonitoredOtherGetNewChunk[chunkType] ELSE ERROR InternalFilePageMgrLogicError; ENDLOOP; END; -- try to find the requested chunk. If not found, maybe we have been given one we can use. Inc the useCount. MonitoredFindOldOrGiveNewChunk: ENTRY PROCEDURE[fpmFileHandle: FPMFileHandle, fileHandle: AI.FileHandle, otherRefChunk: RefChunk, myPageRun: MyPageRun, readReadAheadOrUse: ReadReadAheadOrUse, chunkType: FpmPC.ClientChunkType] RETURNS [errors: ERROR, done: BOOLEAN, nFilePagesToRead: AE.PageCount, vMPageSet: Fpm.VMPageSet] = -- values of errors are {NoSuchFile, NoSuchVolume, PageRunExtendsPastEof, VolumeWentOffline} BEGIN -- non system fatal errors: none. refChunk: RefChunk; new, wholeChunk: BOOLEAN; chunkTrueSize: AE.PageCount; [errors, refChunk] _ GetMappedChunk[fpmFileHandle, myPageRun, readReadAheadOrUse]; IF errors # Okay THEN BEGIN IF otherRefChunk # NIL THEN FpmL.PutUnmappedChunkOnLruList[otherRefChunk]; RETURN; END; new _ (refChunk = NIL); SELECT TRUE FROM new => IF otherRefChunk = NIL THEN RETURN[Okay, FALSE, 0, [NIL, [0, 0], NIL]] ELSE BEGIN refChunk _ otherRefChunk; MapChunk[fpmFileHandle, fileHandle, myPageRun.chunkStartFilePage, refChunk]; IF refChunk.chunkType = leader THEN BEGIN propStorage: File.PropertyStorage _ File.GetProperties[ fpmFileHandle.lowerHandle]; propVMPageNumber: VM.PageNumber _ VM.PageNumberForAddress[LOOPHOLE[propStorage, LONG POINTER]]; IF VMAddressForPageNumber[propVMPageNumber] # LOOPHOLE[propStorage, LONG POINTER] THEN ERROR; -- File, you turkey! You promised!! refChunk.startVMPageNumber _ propVMPageNumber; END; END; NOT new => IF otherRefChunk # NIL THEN FpmL.PutUnmappedChunkOnLruList[otherRefChunk]; ENDCASE; done _ TRUE; refChunk.useCount _ refChunk.useCount + 1; nFilePagesToRead _ 0; [vMPageSet, wholeChunk, chunkTrueSize] _ SetUpVmPageSet[refChunk, myPageRun.firstPage, myPageRun.count, fpmFileHandle.fileDataSize]; SELECT TRUE FROM new => IF (refChunk.chunkType = leader) OR ((readReadAheadOrUse = use) AND (wholeChunk)) THEN BEGIN MakeAllPagesInChunkClean[refChunk]; refChunk.state _ valid; END ELSE BEGIN refChunk.state _ readInProgress; nFilePagesToRead _ chunkTrueSize; END; NOT new => SELECT readReadAheadOrUse FROM read, use => DO IF refChunk.state = readInProgress THEN WAIT ReadDone ELSE EXIT; ENDLOOP; ENDCASE; ENDCASE; END; -- returns NIL if the requested chunk doesn't exist, otherwise, if necessary, takes it off the lru list. GetMappedChunk: INTERNAL PROCEDURE[fpmFileHandle: FPMFileHandle, myPageRun: MyPageRun, readReadAheadOrUse: ReadReadAheadOrUse] RETURNS [errors: ERROR, refChunk: RefChunk] = -- values of errors are {NoSuchFile, NoSuchVolume, PageRunExtendsPastEof, VolumeWentOffline}. BEGIN -- non system fatal errors: none. IF fpmFileHandle.volumeState # online THEN RETURN[VolumeError[fpmFileHandle.volumeState], NIL]; IF (NOT fpmFileHandle.exists) THEN RETURN[NoSuchFile, NIL]; IF myPageRun.firstPage + myPageRun.count > fpmFileHandle.fileDataSize THEN RETURN[PageRunExtendsPastEof, NIL]; IF ((refChunk _ OST.Lookup[fpmFileHandle.chunkTable, myPageRun.chunkStartFilePage]) # NIL) AND (refChunk.useCount = 0) THEN FpmL.GetOurChunkFromLruList[refChunk, FALSE]; errors _ Okay; END; -- on entry state and pages are already set to undefined; inserts into chunkTable, sets fileHandle and startFilePageNumber. MapChunk: INTERNAL PROCEDURE[fpmFileHandle: FPMFileHandle, fileHandle: AI.FileHandle, startChunkFilePage: AE.PageNumber, refChunk: RefChunk] = BEGIN -- non system fatal errors: none. OST.Insert[fpmFileHandle.chunkTable, refChunk, startChunkFilePage]; refChunk.fileHandle _ fileHandle; refChunk.startFilePageNumber _ startChunkFilePage; fpmFileHandle.nMappedChunks _ fpmFileHandle.nMappedChunks + 1; END; -- io is not in progress on entry; removes from chunkTable, clears fileHandle, handles defWritePending and nDefWriteChunks. UnmapChunk: INTERNAL PROCEDURE[fpmFileHandle: FPMFileHandle, refChunk: RefChunk] = BEGIN -- non system fatal errors: none. IF OST.Delete[fpmFileHandle.chunkTable, refChunk.startFilePageNumber] = NIL THEN ERROR InternalFilePageMgrLogicError; IF refChunk.defWritePending THEN BEGIN refChunk.defWritePending _ FALSE; fpmFileHandle.nDefWriteChunks _ fpmFileHandle.nDefWriteChunks - 1; END; refChunk.fileHandle _ NIL; fpmFileHandle.nMappedChunks _ fpmFileHandle.nMappedChunks - 1; refChunk.state _ undefined; END; SetUpVmPageSet: INTERNAL PROCEDURE[refChunk: RefChunk, clientStartFilePage: AE.PageNumber, clientFilePageCount: NAT, fileDataSize: AE.PageCount] RETURNS [vMPageSet: Fpm.VMPageSet, wholeChunk: BOOLEAN, chunkTrueSize: AE.PageCount] = BEGIN -- non system fatal errors: none. chunkEndFilePagePlus1: AE.PageNumber; clientEndFilePagePlus1: AE.PageNumber; IF refChunk.chunkType = leader THEN RETURN[ vMPageSet: [pages: VMAddressForPageNumber[refChunk.startVMPageNumber], pageRun: [0, FpmPC.NLeaderFilePages], refChunk: refChunk], wholeChunk: TRUE, chunkTrueSize: FpmPC.NLeaderFilePages ]; chunkEndFilePagePlus1 _ MIN[refChunk.startFilePageNumber + FpmPC.ChunkFilePageCount[refChunk.chunkType], fileDataSize]; clientEndFilePagePlus1 _ MIN[clientStartFilePage + clientFilePageCount, chunkEndFilePagePlus1]; vMPageSet.pages _ VMAddressForPageNumber[GetVMIntervalFromFileInterval[ refChunk.startFilePageNumber, clientStartFilePage, 0, refChunk.startVMPageNumber].vmPageNumber]; vMPageSet.pageRun.firstPage _ clientStartFilePage; vMPageSet.pageRun.count _ clientEndFilePagePlus1 - clientStartFilePage; vMPageSet.refChunk _ refChunk; chunkTrueSize _ chunkEndFilePagePlus1 - refChunk.startFilePageNumber; wholeChunk _ ((clientStartFilePage = refChunk.startFilePageNumber) AND (vMPageSet.pageRun.count = chunkTrueSize)); END; -- this procedure isn't monitored, but it calls one that is. -- gets a chunk from the lru list. MonitoredOtherGetNewChunk: PROCEDURE[chunkType: FpmPC.ClientChunkType] RETURNS [otherRefChunk: RefChunk] = BEGIN -- non system fatal errors: none. DO otherFileHandle: FileMap.Handle; otherStartFilePageNumber: AE.PageNumber; mapped: BOOLEAN; [mapped, otherRefChunk, otherFileHandle, otherStartFilePageNumber] _ FpmL.GetOtherChunkFromLruList[chunkType]; IF mapped -- we didn't get it yet, try to free it. THEN BEGIN ErrorProc: PROCEDURE RETURNS[fpmFileHandle: FPMFileHandle] = BEGIN ERROR ConsistencyError; END; -- non system fatal errors: none. IF (NOT MonitoredOtherFreeChunkFromFile[ FileMap.VerifyFilePageMgrHandle[otherFileHandle, ErrorProc], otherFileHandle, otherRefChunk, otherStartFilePageNumber]) THEN LOOP; END; EXIT; ENDLOOP; END; MonitoredOtherFreeChunkFromFile: ENTRY PROCEDURE[fpmFileHandle: FPMFileHandle, fileHandle: AI.FileHandle, refChunk: RefChunk, startFilePageNumber: AE.PageNumber] RETURNS [okay: BOOLEAN] = BEGIN -- non system fatal errors: none. hurryUp: BOOLEAN _ FALSE; dirty: BOOLEAN _ FALSE; DO okay _ ((OST.Lookup[fpmFileHandle.chunkTable, startFilePageNumber] = refChunk) AND (refChunk.useCount = 0)); IF NOT okay THEN RETURN; IF refChunk.state = readInProgress THEN BEGIN WAIT ReadDone; LOOP; END; IF refChunk.state = writeInProgress THEN BEGIN hurryUp _ TRUE; WAIT WriteDone; LOOP; END; EXIT; ENDLOOP; dirty _ ChunkIsDirty[refChunk]; FpmL.GetOurChunkFromLruList[refChunk, (hurryUp OR dirty)]; IF dirty THEN BEGIN refChunk.state _ writeInProgress; CleanAndWriteChunk[fpmFileHandle, refChunk]; END; UnmapChunk[fpmFileHandle, refChunk]; END; -- Notifies the file page manager that the indicated pages are likely to be read soon. ReadAheadPages: PUBLIC PROCEDURE[fileHandle: AI.FileHandle, pageRun: AE.PageRun] = BEGIN -- non system fatal errors: NoSuchFile, NoSuchVolume, PageRunArgIllegal, VolumeWentOffline. BasicReadAhead[fileHandle, pageRun, normal]; END; ReadAheadLogPages: PUBLIC PROCEDURE[fileHandle: AI.FileHandle, pageRun: AE.PageRun] = BEGIN -- non system fatal errors: NoSuchFile, NoSuchVolume, PageRunArgIllegal, VolumeWentOffline. BasicReadAhead[fileHandle, pageRun, log]; END; BasicReadAhead: PROCEDURE[fileHandle: AI.FileHandle, pageRun: AE.PageRun, chunkType: FpmPC.ClientChunkType[normal..log]] = BEGIN -- non system fatal errors: NoSuchFile, NoSuchVolume, PageRunArgIllegal, VolumeWentOffline. vMPageSet: Fpm.VMPageSet; lastValidNFilePagesToRead: AE.PageCount _ 0; lastIndex: NAT _ 0; listOfRefChunk: LIST OF RefChunk _ NIL; listOfVMPageSet: LIST OF Fpm.VMPageSet _ NIL; DO nFilePagesToRead: AE.PageCount; [vMPageSet, nFilePagesToRead] _ BasicGetPages[fileHandle, pageRun, readAhead, chunkType ! PageRunExtendsPastEof => GOTO done]; IF pageRun.count < vMPageSet.pageRun.count THEN ERROR InternalFilePageMgrLogicError; pageRun.firstPage _ pageRun.firstPage + vMPageSet.pageRun.count; pageRun.count _ pageRun.count - vMPageSet.pageRun.count; IF nFilePagesToRead # 0 THEN BEGIN lastIndex _ lastIndex + 1; lastValidNFilePagesToRead _ nFilePagesToRead; listOfRefChunk _ CONS[vMPageSet.refChunk, listOfRefChunk]; listOfVMPageSet _ CONS[vMPageSet, listOfVMPageSet]; END ELSE ReleaseVMPageSet[vMPageSet, clean, TRUE]; IF ((pageRun.count = 0) OR (lastIndex = MaxReadAheadSets)) THEN EXIT; REPEAT done => NULL; ENDLOOP; IF listOfRefChunk # NIL THEN TRUSTED BEGIN Process.Detach[FORK ForkedBasicReader[fileHandle, listOfRefChunk, listOfVMPageSet, lastValidNFilePagesToRead]]; END; END; ForkedBasicReader: PROCEDURE[fileHandle: AI.FileHandle, listOfRefChunk: LIST OF RefChunk, listOfVMPageSet: LIST OF Fpm.VMPageSet, nFilePagesToRead: AE.PageCount] = BEGIN -- non system fatal errors: none. fpmFileHandle: FPMFileHandle _ GetFilePageMgrHandle[fileHandle, TRUE]; DoSequentialIO[fpmFileHandle, read, listOfRefChunk, nFilePagesToRead]; FOR listOfVMPageSet _ listOfVMPageSet, listOfVMPageSet.rest UNTIL listOfVMPageSet = NIL DO ReleaseVMPageSet[listOfVMPageSet.first, clean, TRUE]; ENDLOOP; MonitoredSetListOfChunksValidAfterIO[fpmFileHandle, listOfRefChunk, readCompleted]; END; -- Bumps the share count of the Chunk in the VMPageSet. ShareVMPageSet: PUBLIC PROCEDURE[vMPageSet: Fpm.VMPageSet] = BEGIN -- non system fatal errors: none. refChunk: RefChunk _ vMPageSet.refChunk; -- keep the compiler happy. -- chunk can't change mapping since useCount >= 1. MonitoredShareVMPageSet[GetFilePageMgrHandle[refChunk.fileHandle, TRUE], refChunk]; END; MonitoredShareVMPageSet: ENTRY PROCEDURE[fpmFileHandle: FPMFileHandle, refChunk: RefChunk] = BEGIN -- non system fatal errors: none. IF (refChunk.useCount _ refChunk.useCount + 1) = 1 THEN ERROR ConsistencyError; END; ReleaseVMPageSet: PUBLIC PROCEDURE[vMPageSet: Fpm.VMPageSet, releaseState: Fpm.ReleaseState, keep: BOOLEAN] = BEGIN -- non system fatal errors: none. refChunk: RefChunk _ vMPageSet.refChunk; fpmFileHandle: FPMFileHandle _ GetFilePageMgrHandle[refChunk.fileHandle, TRUE]; writeNeeded: BOOLEAN _ FALSE; IF releaseState IN Fpm.DirtyWaitReleaseState THEN BEGIN IF (writeNeeded _ MonitoredWaitForWriteToCompleteThenMaybeSetWIP[ fpmFileHandle, refChunk]) THEN CleanAndWriteChunk[fpmFileHandle, refChunk]; END; MonitoredMainReleaseVMPageSet[fpmFileHandle, refChunk, releaseState, keep, writeNeeded]; END; MonitoredMainReleaseVMPageSet: ENTRY PROCEDURE[fpmFileHandle: FPMFileHandle, refChunk: RefChunk, releaseState: Fpm.ReleaseState, keep: BOOLEAN, setValid: BOOLEAN] = BEGIN -- non system fatal errors: none. IF setValid THEN BEGIN refChunk.state _ valid; BROADCAST WriteDone; END; IF refChunk.chunkType = normal AND FpmL.UsingTooMuchOfCache[fpmFileHandle] AND refChunk.state # readInProgress THEN BEGIN -- we catch after the fact. keep _ FALSE; IF releaseState = writeIndividualNoWait THEN releaseState _ writeBatchedNoWait; END; IF (refChunk.useCount _ refChunk.useCount - 1) = 0 THEN BEGIN FpmL.PutMappedChunkOnLruList[refChunk, IF ((releaseState IN Fpm.DirtyNoWaitReleaseState) OR (keep)) THEN mru ELSE lru]; IF ((releaseState = writeBatchedNoWait) AND (NOT refChunk.defWritePending)) THEN BEGIN refChunk.defWritePending _ TRUE; IF (fpmFileHandle.nDefWriteChunks _ fpmFileHandle.nDefWriteChunks + 1) >= LimitDefWriteChunks THEN StartSomeDeferredWrites[fpmFileHandle, refChunk, keep]; END; END; END; -- Spins off lists of chunks, in ascending or descending order. ChunkAndPage: TYPE = RECORD[refChunk: RefChunk, startFilePageNumber: AE.PageNumber]; StartSomeDeferredWrites: INTERNAL PROCEDURE[fpmFileHandle: FPMFileHandle, startChunk: RefChunk, keep: BOOLEAN] = BEGIN -- non system fatal errors: none. nextChunk: RefChunk _ startChunk; searchProc: OST.LookupProc _ OST.LookupNextSmaller; listOfChunksAndPages: LIST OF ChunkAndPage _ NIL; AddChunk: PROCEDURE = BEGIN -- non system fatal errors: none. listOfChunksAndPages _ CONS[[nextChunk, nextChunk.startFilePageNumber], listOfChunksAndPages]; END; ForkDemon: PROCEDURE = BEGIN -- non system fatal errors: none. TRUSTED BEGIN Process.Detach[FORK DeferredWriteDemon[fpmFileHandle, listOfChunksAndPages, keep]]; END; listOfChunksAndPages _ NIL; END; DO IF nextChunk.defWritePending THEN BEGIN nextChunk.defWritePending _ FALSE; fpmFileHandle.nDefWriteChunks _ fpmFileHandle.nDefWriteChunks - 1; AddChunk[]; IF fpmFileHandle.nDefWriteChunks = 0 THEN EXIT; END; IF (nextChunk _ searchProc[fpmFileHandle.chunkTable, nextChunk.startFilePageNumber]) = NIL THEN BEGIN IF searchProc = OST.LookupNextLarger THEN ERROR InternalFilePageMgrLogicError; ForkDemon[]; -- keep all the chunks going one way, for performance. searchProc _ OST.LookupNextLarger; IF (nextChunk _ searchProc[fpmFileHandle.chunkTable, startChunk.startFilePageNumber]) = NIL THEN ERROR InternalFilePageMgrLogicError; END; ENDLOOP; ForkDemon[]; END; -- forces the chunks out and then, if requested, moves them to the lru end of the lru list. DeferredWriteDemon: PROCEDURE[fpmFileHandle: FPMFileHandle, listOfChunksAndPages: LIST OF ChunkAndPage, keep: BOOLEAN] = BEGIN -- non system fatal errors: none. listOfValidAndDirty: LIST OF RefChunk; listOfWIPAndClean: LIST OF ChunkAndPage; [listOfValidAndDirty, listOfWIPAndClean] _ MonitoredDefWriteSortChunks[fpmFileHandle, listOfChunksAndPages, keep]; DoSequentialIO[fpmFileHandle, write, listOfValidAndDirty, 0]; MonitoredDefWriteSetValidAndWait[fpmFileHandle, listOfValidAndDirty, listOfWIPAndClean]; END; -- This routine is not interested in chunks that have undergone various state transitions such as being remapped. Essentially, it has three functions: -- It wants to return a list of the valid and dirty chunks so that the caller can write them out outside of the monitor and then possibly reorder them. -- If keep is TRUE: -- it wants to return a list of the writeInProgress and clean chunks so that the caller can possibly reorder them after it's waited for them to complete their writes. -- it reorders any valid and clean chunks that are still on the lru list. MonitoredDefWriteSortChunks: ENTRY PROCEDURE[fpmFileHandle: FPMFileHandle, listOfChunksAndPages: LIST OF ChunkAndPage, keep: BOOLEAN] RETURNS[listOfValidAndDirty: LIST OF RefChunk, listOfWIPAndClean: LIST OF ChunkAndPage] = BEGIN -- non system fatal errors: none. refChunk: RefChunk; listOfValidAndDirty _ NIL; listOfWIPAndClean _ NIL; FOR listOfChunksAndPages _ listOfChunksAndPages, listOfChunksAndPages.rest UNTIL listOfChunksAndPages = NIL DO dirty: BOOLEAN; refChunk _ OST.Lookup[fpmFileHandle.chunkTable, listOfChunksAndPages.first.startFilePageNumber]; IF ((refChunk # listOfChunksAndPages.first.refChunk) OR (refChunk.defWritePending)) THEN LOOP; dirty _ ChunkIsDirty[refChunk]; SELECT TRUE FROM ((refChunk.state = valid) AND (dirty)) => BEGIN listOfValidAndDirty _ CONS[listOfChunksAndPages.first.refChunk, listOfValidAndDirty]; refChunk.state _ writeInProgress; END; (keep) => SELECT TRUE FROM ((refChunk.state = valid) AND (refChunk.useCount = 0)) => FpmL.RelinkChunkAsLruOnLruList[refChunk]; ((refChunk.state = writeInProgress) AND (NOT dirty)) => listOfWIPAndClean _ CONS[listOfChunksAndPages.first, listOfWIPAndClean]; ENDCASE; ENDCASE => NULL; ENDLOOP; END; -- This routine is not interested in chunks that have undergone various state transitions such as being remapped. Essentially, it has two functions: -- It has a list of chunks that were written to be set to valid and possibly reordered. -- It has a list of chunks that were wIP and clean. It wants to wait for them to become valid and clean, at which point it will possibly reorder them. MonitoredDefWriteSetValidAndWait: ENTRY PROCEDURE[fpmFileHandle: FPMFileHandle, listOfValidAndDirty: LIST OF RefChunk, listOfWIPAndClean: LIST OF ChunkAndPage] = BEGIN -- non system fatal errors: none. refChunk: RefChunk; InternalSetListOfChunksValidAfterIOThenMaybeReorder[fpmFileHandle, listOfValidAndDirty, writeCompleted, TRUE]; DO IF listOfWIPAndClean = NIL THEN RETURN; refChunk _ OST.Lookup[fpmFileHandle.chunkTable, listOfWIPAndClean.first.startFilePageNumber]; IF ((refChunk = listOfWIPAndClean.first.refChunk) AND (NOT refChunk.defWritePending) AND (NOT ChunkIsDirty[refChunk])) THEN SELECT TRUE FROM (refChunk.state = valid) => IF refChunk.useCount = 0 THEN FpmL.RelinkChunkAsLruOnLruList[refChunk]; (refChunk.state = writeInProgress) => BEGIN WAIT WriteDone; LOOP; END; ENDCASE => NULL; listOfWIPAndClean _ listOfWIPAndClean.rest; ENDLOOP; END; ForceOutVMPageSet: PUBLIC PROCEDURE[vMPageSet: Fpm.VMPageSet] = BEGIN -- non system fatal errors: none. refChunk: RefChunk _ vMPageSet.refChunk; -- keep compiler happy. fpmFileHandle: FPMFileHandle _ GetFilePageMgrHandle[refChunk.fileHandle, TRUE]; IF MonitoredWaitForWriteToCompleteThenMaybeSetWIP[fpmFileHandle, refChunk] THEN BEGIN CleanAndWriteChunk[fpmFileHandle, refChunk]; MonitoredSetChunkValidAfterIO[fpmFileHandle, refChunk, writeCompleted]; END; END; ForceOutFile: PUBLIC PROCEDURE[fileHandle: AI.FileHandle] = BEGIN -- non system fatal errors: NoSuchFile, NoSuchVolume, VolumeWentOffline. fpmFileHandle: FPMFileHandle _ GetFilePageMgrHandle[fileHandle, FALSE]; errors: ERROR _ Okay; listOfValidAndDirty: LIST OF RefChunk; listOfWIPAndClean: LIST OF ChunkAndPage; [errors, listOfValidAndDirty, listOfWIPAndClean] _ MonitoredForceOutFileSortChunks[fpmFileHandle]; IF errors # Okay THEN ERROR errors; DoSequentialIO[fpmFileHandle, write, listOfValidAndDirty, 0]; MonitoredForceOutFileSetValidAndWait[fpmFileHandle, listOfValidAndDirty, listOfWIPAndClean]; END; MonitoredForceOutFileSortChunks: ENTRY PROCEDURE[fpmFileHandle: FPMFileHandle] RETURNS [errors: ERROR, listOfValidAndDirty: LIST OF RefChunk, listOfWIPAndClean: LIST OF ChunkAndPage] = -- values of errors are {NoSuchFile, NoSuchVolume, VolumeWentOffline}. BEGIN -- non system fatal errors: none. refChunk: RefChunk; dirty: BOOLEAN; currentFilePageNumber: AE.PageNumber; listOfValidAndDirty _ NIL; listOfWIPAndClean _ NIL; IF fpmFileHandle.volumeState # online THEN BEGIN errors _ VolumeError[fpmFileHandle.volumeState]; RETURN; END; IF (NOT fpmFileHandle.exists) THEN BEGIN errors _ NoSuchFile; RETURN; END; errors _ Okay; refChunk _ OST.LookupSmallest[fpmFileHandle.chunkTable]; DO IF refChunk = NIL THEN RETURN; currentFilePageNumber _ refChunk.startFilePageNumber; BEGIN DO dirty _ ChunkIsDirty[refChunk]; IF ((refChunk.state # writeInProgress) OR (NOT dirty)) THEN EXIT; WAIT WriteDone; IF (OST.Lookup[fpmFileHandle.chunkTable, currentFilePageNumber] # refChunk) THEN GOTO doneWithThisChunk; ENDLOOP; SELECT TRUE FROM ((refChunk.state = valid) AND (dirty)) => BEGIN listOfValidAndDirty _ CONS[refChunk, listOfValidAndDirty]; refChunk.state _ writeInProgress; END; (refChunk.state = writeInProgress) => listOfWIPAndClean _ CONS[[refChunk, refChunk.startFilePageNumber], listOfWIPAndClean]; ENDCASE; EXITS doneWithThisChunk => NULL; END; refChunk _ OST.LookupNextLarger[fpmFileHandle.chunkTable, currentFilePageNumber]; ENDLOOP; END; MonitoredForceOutFileSetValidAndWait: ENTRY PROCEDURE[fpmFileHandle: FPMFileHandle, listOfValidAndDirty: LIST OF RefChunk, listOfWIPAndClean: LIST OF ChunkAndPage] = BEGIN -- non system fatal errors: none. refChunk: RefChunk; InternalSetListOfChunksValidAfterIOThenMaybeReorder[fpmFileHandle, listOfValidAndDirty, writeCompleted, FALSE]; DO IF listOfWIPAndClean = NIL THEN RETURN; refChunk _ OST.Lookup[fpmFileHandle.chunkTable, listOfWIPAndClean.first.startFilePageNumber]; IF ((refChunk = listOfWIPAndClean.first.refChunk) AND (listOfWIPAndClean.first.refChunk.state = writeInProgress)) THEN WAIT WriteDone ELSE listOfWIPAndClean _ listOfWIPAndClean.rest; ENDLOOP; END; Sweeper: PUBLIC PROCEDURE[chunkType: FpmPC.ChunkType] = BEGIN -- non system fatal errors: none. needToHurry: BOOLEAN _ FALSE; sweepList: LIST OF FpmL.SweepItem; DO [needToHurry, sweepList] _ FpmL.WaitToSweep[needToHurry, chunkType]; UNTIL sweepList = NIL DO fpmFileHandle: FPMFileHandle _ GetFilePageMgrHandle[sweepList.first.fileHandle, FALSE]; listOfValidAndDirty: LIST OF RefChunk; [listOfValidAndDirty, sweepList] _ MonitoredSweeperSortChunks[fpmFileHandle, sweepList.first.fileHandle, sweepList]; DoSequentialIO[fpmFileHandle, write, listOfValidAndDirty, 0]; MonitoredSetListOfChunksValidAfterIO[fpmFileHandle, listOfValidAndDirty, writeCompleted]; ENDLOOP; ENDLOOP; END; MonitoredSweeperSortChunks: ENTRY PROCEDURE[fpmFileHandle: FPMFileHandle, fileHandle: FileMap.Handle, sweepList: LIST OF FpmL.SweepItem] RETURNS[listOfValidAndDirty: LIST OF RefChunk, newSweepList: LIST OF FpmL.SweepItem] = BEGIN -- non system fatal errors: none. refChunk: RefChunk; listOfValidAndDirty _ NIL; DO refChunk _ OST.Lookup[fpmFileHandle.chunkTable, sweepList.first.startFilePageNumber]; IF ((refChunk # NIL) AND (refChunk.useCount = 0) AND (refChunk.state = valid) AND (ChunkIsDirty[refChunk])) THEN BEGIN listOfValidAndDirty _ CONS[refChunk, listOfValidAndDirty]; refChunk.state _ writeInProgress; END; sweepList _ sweepList.rest; IF ((sweepList = NIL) OR (sweepList.first.fileHandle # fileHandle)) THEN EXIT; ENDLOOP; newSweepList _ sweepList; END; ForceOutEverything: PUBLIC PROCEDURE = BEGIN -- non system fatal errors: NoSuchVolume, VolumeWentOffline. FOR fileHandle: AI.FileHandle _ FileMap.GetNext[NIL], FileMap.GetNext[fileHandle] UNTIL fileHandle = NIL DO ForceOutFile[fileHandle ! NoSuchFile => CONTINUE]; ENDLOOP; END; -- this is a routine used for debugging. On its return, it only guarantees that the cache was clean at some point in its processing. The caller must not be doing things like concurrent maps, etc. Call ForceOutEverything first so no chunks have writeinProgress up. RestoreCacheToCleanState: PUBLIC PROCEDURE = BEGIN -- non system fatal errors: none. FOR fileHandle: AI.FileHandle _ FileMap.GetNext[NIL], FileMap.GetNext[fileHandle] UNTIL fileHandle = NIL DO errors: ERROR _ MonitoredUnmapFile[GetFilePageMgrHandle[fileHandle, FALSE]]; IF ((errors # Okay) AND (errors # NoSuchFile)) THEN ERROR errors; ENDLOOP; IF (NOT FpmL.CheckCacheInCleanState[]) THEN ERROR InternalFilePageMgrLogicError; -- maybe not, maybe the client did a number on us. END; MonitoredUnmapFile: ENTRY PROCEDURE[fpmFileHandle: FPMFileHandle] RETURNS[errors: ERROR] = -- values of errors are {NoSuchFile, NoSuchVolume, VolumeWentOffline}. BEGIN -- non system fatal errors: none. IF fpmFileHandle.volumeState # online THEN RETURN[VolumeError[fpmFileHandle.volumeState]]; IF (NOT fpmFileHandle.exists) THEN RETURN[NoSuchFile]; DumpInconvenientlyMappedChunks[fpmFileHandle, TRUE, 0]; RETURN[Okay]; END; Create: PUBLIC PROCEDURE[volumeID: AE.VolumeID, initialSize: AE.PageCount, proc: PROCEDURE[AE.FileID]] = BEGIN -- non system fatal errors: InsufficientSpaceOnVolume, NoSuchVolume, SizeArgIllegal, VolumeTooFragmented, VolumeWentOffline plus any raised by proc. LocalProc: PROCEDURE[fp: File.FP, propertyStorage: File.PropertyStorage] = BEGIN proc[fp]; END;-- non system fatal errors: any raised by proc. volume: File.Volume; volOrVolGroupID: VolOrVolGroupID _ volumeID; IF initialSize < 0 THEN ERROR SizeArgIllegal; IF (volume _ File.FindVolumeFromID[volOrVolGroupID]) = NIL THEN ERROR NoSuchVolume; [] _ File.Create[volume: volume, size: initialSize, report: LocalProc ! File.Error => SELECT why FROM wentOffline => GOTO wentOffline; volumeFull => GOTO volumeFull; fragmented => GOTO fragmented; ENDCASE;]; EXITS fragmented => ERROR VolumeTooFragmented; volumeFull => ERROR InsufficientSpaceOnVolume; wentOffline => ERROR VolumeWentOffline; END; Delete: PUBLIC PROCEDURE[fileHandle: AI.FileHandle] = BEGIN -- non system fatal errors: NoSuchFile, NoSuchVolume, VolumeWentOffline. errors: ERROR; IF (errors _ MonitoredDelete[GetFilePageMgrHandle[fileHandle, FALSE]]) # Okay THEN ERROR errors; END; MonitoredDelete: ENTRY PROCEDURE[fpmFileHandle: FPMFileHandle] RETURNS[errors: ERROR] = -- values of errors are {NoSuchFile, NoSuchVolume, VolumeWentOffline}. BEGIN -- non system fatal errors: none. IF fpmFileHandle.volumeState # online THEN RETURN[VolumeError[fpmFileHandle.volumeState]]; IF (NOT fpmFileHandle.exists) THEN RETURN[NoSuchFile]; DumpInconvenientlyMappedChunks[fpmFileHandle, TRUE, 0]; File.Delete[fpmFileHandle.lowerHandle ! File.Error => SELECT why FROM wentOffline => GOTO wentOffline; unknownFile => GOTO horrible; ENDCASE;]; fpmFileHandle.fileDataSize _ 0; fpmFileHandle.exists _ FALSE; RETURN[Okay]; EXITS horrible => ERROR InternalFilePageMgrLogicError; wentOffline => RETURN[VolumeWentOffline]; END; SetSize: PUBLIC PROCEDURE[fileHandle: AI.FileHandle, size: AE.PageCount] = BEGIN -- non system fatal errors: InsufficientSpaceOnVolume, NoSuchFile, NoSuchVolume, SizeArgIllegal, VolumeTooFragmented, VolumeWentOffline. errors: ERROR; IF size < 0 THEN ERROR SizeArgIllegal; IF (errors _ MonitoredSetSize[GetFilePageMgrHandle[fileHandle, FALSE], size]) # Okay THEN ERROR errors; END; MonitoredSetSize: ENTRY PROCEDURE[fpmFileHandle: FPMFileHandle, newSize: AE.PageCount] RETURNS[errors: ERROR] = -- values of errors are {InsufficientSpaceOnVolume, NoSuchFile, NoSuchVolume, VolumeTooFragmented, VolumeWentOffline}. BEGIN -- non system fatal errors: none. IF fpmFileHandle.volumeState # online THEN BEGIN errors _ VolumeError[fpmFileHandle.volumeState]; RETURN; END; IF (NOT fpmFileHandle.exists) THEN RETURN[NoSuchFile]; IF fpmFileHandle.fileDataSize = newSize THEN RETURN[Okay]; IF newSize < fpmFileHandle.fileDataSize THEN -- truncating. DumpInconvenientlyMappedChunks[fpmFileHandle, FALSE, newSize]; File.SetSize[fpmFileHandle.lowerHandle, newSize ! File.Error => SELECT why FROM fragmented => GOTO fragmented; unknownFile => GOTO horrible; volumeFull => GOTO volumeFull; wentOffline => GOTO wentOffline; ENDCASE;]; fpmFileHandle.fileDataSize _ newSize; RETURN[Okay]; EXITS fragmented => RETURN[VolumeTooFragmented]; horrible => ERROR InternalFilePageMgrLogicError; volumeFull => RETURN[InsufficientSpaceOnVolume]; wentOffline => RETURN[VolumeWentOffline]; END; -- Called from unmap file, delete file, and setsize truncate. Wants to unmap chunks mapped to areas of the file that will no longer exist. None of those chunks should have useCounts > 0 except ones left around in ReadAhead, which we wait for. Some consistency checking is done. DumpInconvenientlyMappedChunks: INTERNAL PROCEDURE[fpmFileHandle: FPMFileHandle, dumpingWholeFile: BOOLEAN, size: AE.PageCount] = BEGIN -- non system fatal errors: none. IF fpmFileHandle.nMappedChunks # 0 THEN BEGIN refChunk: RefChunk; firstChunkIsPartial: BOOLEAN; [refChunk, firstChunkIsPartial] _ FirstChunkToCheck[fpmFileHandle, dumpingWholeFile, size]; IF refChunk # NIL THEN CheckTheChunks[fpmFileHandle, refChunk, firstChunkIsPartial]; -- bug catchers: [refChunk, firstChunkIsPartial] _ FirstChunkToCheck[fpmFileHandle, dumpingWholeFile, size]; IF refChunk # NIL THEN BEGIN IF ((dumpingWholeFile) OR (NOT firstChunkIsPartial) OR (OST.LookupNextLarger[fpmFileHandle.chunkTable, refChunk.startFilePageNumber] # NIL)) THEN ERROR; END; END; END; -- If unmap file or delete file, start at lowest chunk in file, including leader. If setsize truncate, start at chunk containing new eof. FirstChunkToCheck: INTERNAL SAFE PROCEDURE[fpmFileHandle: FPMFileHandle, dumpingWholeFile: BOOLEAN, newSize: AE.PageCount] RETURNS [refChunk: RefChunk, firstChunkIsPartial: BOOLEAN] = CHECKED BEGIN -- non system fatal errors: none. firstPageToClip: AE.PageNumber; fileChunkType: FpmPC.ClientChunkType; IF dumpingWholeFile THEN RETURN [OST.LookupSmallest[fpmFileHandle.chunkTable], FALSE]; IF OST.LookupLargest[fpmFileHandle.chunkTable] = NIL THEN RETURN[NIL, FALSE]; IF (fileChunkType _ OST.LookupLargest[fpmFileHandle.chunkTable].chunkType) = leader THEN RETURN[NIL, FALSE]; --setlen can't ask to trunc leader. firstPageToClip _ ChunkStartFilePage[newSize, fileChunkType]; IF (refChunk _ OST.Lookup[fpmFileHandle.chunkTable, firstPageToClip]) = NIL THEN refChunk _ OST.LookupNextLarger[fpmFileHandle.chunkTable, firstPageToClip]; firstChunkIsPartial _ ((refChunk # NIL) AND (refChunk.startFilePageNumber # newSize)); END; CheckTheChunks: INTERNAL SAFE PROCEDURE[fpmFileHandle: FPMFileHandle, refChunk: RefChunk, firstChunkIsPartial: BOOLEAN] = CHECKED BEGIN -- non system fatal errors: none. startPageOfChunk: AE.PageNumber; DO startPageOfChunk _ refChunk.startFilePageNumber; BEGIN DO IF (refChunk _ OST.Lookup[fpmFileHandle.chunkTable, startPageOfChunk]) = NIL THEN GOTO doneWithThisChunk; IF refChunk.state = readInProgress THEN BEGIN WAIT ReadDone; LOOP; END; IF refChunk.useCount # 0 THEN ERROR; IF refChunk.state = writeInProgress THEN BEGIN WAIT WriteDone; LOOP; END; EXIT; ENDLOOP; IF ((refChunk.state = valid) AND (ChunkIsDirty[refChunk])) THEN BEGIN refChunk.state _ writeInProgress; CleanAndWriteChunk[fpmFileHandle, refChunk]; END; IF NOT firstChunkIsPartial THEN BEGIN FpmL.GetOurChunkFromLruList[refChunk, FALSE]; UnmapChunk[fpmFileHandle, refChunk]; FpmL.PutUnmappedChunkOnLruList[refChunk]; END ELSE refChunk.state _ valid; EXITS doneWithThisChunk => NULL; END; firstChunkIsPartial _ FALSE; IF (refChunk _ OST.LookupNextLarger[fpmFileHandle.chunkTable, startPageOfChunk]) = NIL THEN EXIT; ENDLOOP; END; FileExists: PUBLIC PROCEDURE[fileHandle: AI.FileHandle] RETURNS [fileExists: BOOLEAN] = BEGIN -- non system fatal errors: NoSuchVolume, VolumeWentOffline. errors: ERROR; [errors, fileExists] _ MonitoredFileExists[GetFilePageMgrHandle[fileHandle, FALSE]]; IF errors # Okay THEN ERROR errors; END; MonitoredFileExists: ENTRY PROCEDURE[fpmFileHandle: FPMFileHandle] RETURNS [errors: ERROR, fileExists: BOOLEAN] = -- values of errors are {NoSuchVolume, VolumeWentOffline}. BEGIN -- non system fatal errors: none. IF fpmFileHandle.volumeState # online THEN BEGIN errors _ VolumeError[fpmFileHandle.volumeState]; RETURN; END; RETURN[Okay, fpmFileHandle.exists]; END; GetSize: PUBLIC PROCEDURE[fileHandle: AI.FileHandle] RETURNS [size: AE.PageCount] = BEGIN -- non system fatal errors: NoSuchFile, NoSuchVolume, VolumeWentOffline. errors: ERROR; [errors, size] _ MonitoredGetDataSize[GetFilePageMgrHandle[fileHandle, FALSE]]; IF errors # Okay THEN ERROR errors; END; MonitoredGetDataSize: ENTRY PROCEDURE[fpmFileHandle: FPMFileHandle] RETURNS [errors: ERROR, size: AE.PageCount] = -- values of errors are {NoSuchFile, NoSuchVolume, VolumeWentOffline}. BEGIN -- non system fatal errors: none. IF fpmFileHandle.volumeState # online THEN BEGIN errors _ VolumeError[fpmFileHandle.volumeState]; RETURN; END; IF (NOT fpmFileHandle.exists) THEN RETURN[NoSuchFile, 0]; RETURN[Okay, fpmFileHandle.fileDataSize]; END; -- utility routines: GetFilePageMgrHandle: PROCEDURE[fileHandle: FileMap.Handle, handleMustExist: BOOLEAN] RETURNS [fpmFileHandle: FPMFileHandle] = BEGIN -- non system fatal errors: none. InitFPMFileHandle: PROCEDURE RETURNS[fpmFileHandle: FPMFileHandle] = BEGIN -- non system fatal errors: none. volumeID: AE.VolumeID; volOrVolGroupID: VolOrVolGroupID; fileID: AE.FileID; volume: File.Volume; volumeState: FpmPF.VolumeState _ online; fileExists: BOOLEAN _ TRUE; lowerHandle: File.Handle _ NIL; fileDataSize: AE.PageCount; IF handleMustExist THEN ERROR ConsistencyError; [volumeID, fileID] _ FileMap.GetVolumeIDAndFileID[fileHandle]; volOrVolGroupID _ volumeID; IF (volume _ File.FindVolumeFromID[volOrVolGroupID]) = NIL THEN BEGIN volumeState _ nonExist; fileExists _ FALSE; END ELSE BEGIN ENABLE File.Error => SELECT why FROM unknownFile, wentOffline => BEGIN fileExists _ FALSE; IF why = wentOffline THEN volumeState _ wentOffline; CONTINUE; END; ENDCASE; lowerHandle _ File.Open[volume: volume, fp: fileID]; fileDataSize _ File.Info[lowerHandle].size; END; fpmFileHandle _ NEW[FPMFileObject _ [ chunkTable: OST.CreateTable[header: ChunkAllocator[treeHeader, FALSE]], nMappedChunks: 0, fileDataSize: fileDataSize, exists: fileExists, volumeState: volumeState, nDefWriteChunks: 0, nLruListChunks: 0, lowerHandle: lowerHandle]]; END; fpmFileHandle _ FileMap.VerifyFilePageMgrHandle[fileHandle, InitFPMFileHandle]; END; ChunkAllocator: PUBLIC PROCEDURE[chunkType: FpmPC.ChunkType, permanent: BOOLEAN] RETURNS [refChunk: RefChunk] = BEGIN -- non system fatal errors: running out of vm is system fatal. refChunk _ IF permanent THEN AZ.static.NEW[Chunk] ELSE NEW[Chunk]; refChunk^ _ [chunkType: chunkType, defWritePending: FALSE, state: undefined, useCount: 0, fileHandle: NIL, startFilePageNumber: 0, startVMPageNumber: 0, prev: NIL, next: NIL, rbColor: , rbLLink: NIL, rbRLink: NIL]; IF ((chunkType IN FpmPC.ClientChunkType) AND (chunkType # leader)) THEN BEGIN refChunk.startVMPageNumber _ VM.Allocate[count: FpmPC.ChunkVMPageCount[chunkType], partition: normalVM, subRange: [0, 0],start: 0, alignment: 0, in64K: FALSE].page; VM.Pin[[refChunk.startVMPageNumber, FpmPC.ChunkVMPageCount[chunkType]]]; END; END; -- xx needs work when sizes differ. GetVMIntervalFromFileInterval: PROCEDURE[startFilePageNumber, filePageNumber: AE.PageNumber, filePageCount: AE.PageCount, startVMPageNumber: VM.PageNumber] RETURNS[vmPageNumber: VM.PageNumber, vmPageCount: VM.PageCount] = BEGIN -- non system fatal errors: none. vmPageNumber _ startVMPageNumber + (filePageNumber - startFilePageNumber); vmPageCount _ filePageCount; END; -- xx needs work when sizes differ. GetFileIntervalFromVMInterval: PROCEDURE[startVMPageNumber, vmPageNumber: VM.PageNumber, vmPageCount: VM.PageCount, startFilePageNumber: AE.PageNumber] RETURNS[filePageNumber: AE.PageNumber, filePageCount: AE.PageCount] = BEGIN -- non system fatal errors: none. filePageNumber _ startFilePageNumber + (vmPageNumber - startVMPageNumber); filePageCount _ vmPageCount; END; ChunkStartFilePage: PROCEDURE[clientFilePage: AE.PageNumber, chunkType: FpmPC.ClientChunkType] RETURNS[chunkStartFilePage: AE.PageNumber] = INLINE BEGIN -- non system fatal errors: none. RETURN[IF chunkType = leader THEN FpmPF.LeaderFilePageNumber ELSE (clientFilePage - Basics.LongDivMod[clientFilePage, FpmPC.ChunkFilePageCount[chunkType]].remainder)]; END; VMAddressForPageNumber: PROCEDURE[page: VM.PageNumber] RETURNS [address: LONG POINTER] = -- non system fatal errors: none. TRUSTED BEGIN RETURN[VM.AddressForPageNumber[page]]; END; MakePagesClean: PROCEDURE[interval: VM.Interval] = INLINE BEGIN -- non system fatal errors: none. VM.MakeUnchanged[interval]; END; MakeAllPagesInChunkClean: PROCEDURE[refChunk: RefChunk] = INLINE BEGIN -- non system fatal errors: none. VM.MakeUnchanged[[page: refChunk.startVMPageNumber, count: FpmPC.ChunkVMPageCount[refChunk.chunkType]]]; END; ChunkIsDirty: PUBLIC PROCEDURE[refChunk: RefChunk] RETURNS [dirty: BOOLEAN] = BEGIN -- non system fatal errors: none. FOR vMPage: VM.PageNumber IN [refChunk.startVMPageNumber..refChunk.startVMPageNumber + FpmPC.ChunkVMPageCount[refChunk.chunkType]) DO IF VM.State[vMPage].dataState = changed THEN RETURN[TRUE]; ENDLOOP; RETURN[FALSE]; END; -- chunks are presented in ascending or descending order. Sets pages clean before the writes and after the reads. DoSequentialIO: PROCEDURE[fpmFileHandle: FPMFileHandle, io: FpmIO.IOType, listOfRefChunk: LIST OF RefChunk, nFilePagesHighestChunkForRead: AE.PageCount] = BEGIN -- non system fatal errors: none. controllingProcess: PROCESS _ LOOPHOLE[Process.GetCurrent[]]; iORequest: FpmIO.IORequest; workToDo: BOOLEAN; listOfIOReq: LIST OF FpmIO.IORequest _ NIL; error: File.RC; errorIORequest: FpmIO.IORequest; first: BOOLEAN _ TRUE; IF listOfRefChunk = NIL THEN RETURN; IF ((listOfRefChunk.rest # NIL) AND (listOfRefChunk.first.startFilePageNumber < listOfRefChunk.rest.first.startFilePageNumber)) THEN BEGIN tempList: LIST OF RefChunk _ NIL; FOR listOfRefChunk _ listOfRefChunk, listOfRefChunk.rest UNTIL listOfRefChunk = NIL DO tempList _ CONS[listOfRefChunk.first, tempList]; ENDLOOP; listOfRefChunk _ tempList; END; FOR listOfRefChunk _ listOfRefChunk, listOfRefChunk.rest UNTIL listOfRefChunk = NIL DO SELECT io FROM read => BEGIN listOfIOReq _ CONS[[filePageNumber: [listOfRefChunk.first.startFilePageNumber], nPages: (IF first THEN nFilePagesHighestChunkForRead ELSE FpmPC.ChunkFilePageCount[listOfRefChunk.first.chunkType]), vM: VMAddressForPageNumber[listOfRefChunk.first.startVMPageNumber]], listOfIOReq]; first _ FALSE; END; write => BEGIN AddToCmdList: PROCEDURE[filePageNumber: AE.PageNumber, filePageCount: AE.PageCount, vmPageNumber: VM.PageNumber] RETURNS[stop: BOOLEAN] = BEGIN-- non system fatal errors: none. listOfIOReq _ CONS[[filePageNumber: [filePageNumber], nPages: filePageCount, vM: VMAddressForPageNumber[vmPageNumber]], listOfIOReq]; stop _ FALSE; END; GetAndCleanNextSeqForWrite[listOfRefChunk.first, descending, AddToCmdList]; END; ENDCASE; ENDLOOP; iORequest _ FpmIO.RegisterRequest[controllingProcess: controllingProcess, io: io, file: fpmFileHandle.lowerHandle, list: listOfIOReq]; DO FpmIO.DoIO[io, fpmFileHandle.lowerHandle, iORequest ! File.Error => BEGIN error _ why; errorIORequest _ iORequest; FpmIO.LogError[controllingProcess, controller, why, iORequest]; GOTO errorSeen; END]; [error, errorIORequest, workToDo, iORequest] _ FpmIO.GetNext[controllingProcess: controllingProcess, who: controller]; IF error # ok THEN GOTO errorSeen; IF workToDo = FALSE THEN EXIT; REPEAT errorSeen => ERROR; -- someday we'll do the right thing. ENDLOOP; IF io = read THEN FOR tempList: LIST OF RefChunk _ listOfRefChunk, listOfRefChunk.rest UNTIL listOfRefChunk = NIL DO MakeAllPagesInChunkClean[tempList.first]; ENDLOOP; END; -- sets the dirty pages to clean and does the write. CleanAndWriteChunk: PROCEDURE[fpmFileHandle: FPMFileHandle, refChunk: RefChunk] = BEGIN -- non system fatal errors: none. WriterProc: PROCEDURE[filePageNumber: AE.PageNumber, filePageCount: AE.PageCount, vmPageNumber: VM.PageNumber] RETURNS[stop: BOOLEAN] = BEGIN -- non system fatal errors: none. FpmIO.DoIO[write, fpmFileHandle.lowerHandle, [[filePageNumber], filePageCount, VMAddressForPageNumber[vmPageNumber]]]; stop _ FALSE; END; GetAndCleanNextSeqForWrite[refChunk, ascending, WriterProc]; END; GetAndCleanNextSeqForWrite: PROCEDURE[refChunk: RefChunk, whichWay: {ascending, descending}, proc: PROCEDURE[filePageNumber: AE.PageNumber, filePageCount: AE.PageCount, vmPageNumber: VM.PageNumber] RETURNS[stop: BOOLEAN]] = BEGIN -- non system fatal errors: any non system fatal errors returned by "proc". filePageNumber: AE.PageNumber; filePageCount: AE.PageCount; vmPageNumber: VM.PageNumber; vmPageCount: VM.PageCount; startVMPage: VM.PageNumber _ refChunk.startVMPageNumber; endVMPage: VM.PageNumber _ refChunk.startVMPageNumber + FpmPC.ChunkVMPageCount[refChunk.chunkType] - 1; IF refChunk.chunkType = leader THEN BEGIN MakeAllPagesInChunkClean[refChunk]; [] _ proc[FpmPF.LeaderFilePageNumber, FpmPC.NLeaderFilePages, refChunk.startVMPageNumber]; RETURN; END; DO BEGIN IF whichWay = ascending THEN BEGIN IF VM.State[startVMPage].dataState = changed THEN BEGIN vmPageNumber _ startVMPage; DO startVMPage _ startVMPage + 1; IF ((startVMPage > endVMPage) OR (VM.State[startVMPage].dataState # changed)) THEN EXIT; ENDLOOP; vmPageCount _ startVMPage - vmPageNumber; GOTO doInterval; END; END ELSE BEGIN IF VM.State[endVMPage].dataState = changed THEN BEGIN saveEndVMPage: VM.PageNumber _ endVMPage; DO endVMPage _ endVMPage - 1; IF ((startVMPage > endVMPage) OR (VM.State[endVMPage].dataState # changed)) THEN EXIT; ENDLOOP; vmPageNumber _ endVMPage + 1; vmPageCount _ saveEndVMPage - endVMPage; GOTO doInterval; END; END; EXITS doInterval => BEGIN [filePageNumber, filePageCount] _ GetFileIntervalFromVMInterval[refChunk.startVMPageNumber, vmPageNumber, vmPageCount, refChunk.startFilePageNumber]; MakePagesClean[[vmPageNumber, vmPageCount]]; IF proc[filePageNumber, filePageCount, vmPageNumber] THEN RETURN; END; END; IF whichWay = ascending THEN startVMPage _ startVMPage + 1 ELSE endVMPage _ endVMPage - 1; IF startVMPage > endVMPage THEN EXIT; ENDLOOP; END; MonitoredWaitForWriteToCompleteThenMaybeSetWIP: ENTRY PROCEDURE[fpmFileHandle: FPMFileHandle, refChunk: RefChunk] RETURNS[writeNeeded: BOOLEAN]= BEGIN -- non system fatal errors: none. DO IF refChunk.state # writeInProgress THEN EXIT; WAIT WriteDone; ENDLOOP; IF (writeNeeded _ ChunkIsDirty[refChunk]) THEN refChunk.state _ writeInProgress; END; MonitoredSetChunkValidAfterIO: ENTRY PROCEDURE[fpmFileHandle: FPMFileHandle, refChunk: RefChunk, what: {readCompleted, writeCompleted}] = BEGIN -- non system fatal errors: none. refChunk.state _ valid; IF what = readCompleted -- weird form to keep compiler happy. THEN BROADCAST ReadDone ELSE BROADCAST WriteDone; END; MonitoredSetListOfChunksValidAfterIO: ENTRY PROCEDURE[fpmFileHandle: FPMFileHandle, listOfRefChunk: LIST OF RefChunk, what: {readCompleted, writeCompleted}] = BEGIN -- non system fatal errors: none. FOR list: LIST OF RefChunk _ listOfRefChunk, list.rest UNTIL list = NIL DO list.first.state _ valid; ENDLOOP; IF what = readCompleted -- weird form to keep compiler happy. THEN BROADCAST ReadDone ELSE BROADCAST WriteDone; END; InternalSetListOfChunksValidAfterIOThenMaybeReorder: INTERNAL PROCEDURE[fpmFileHandle: FPMFileHandle, listOfRefChunk: LIST OF RefChunk, what: {readCompleted, writeCompleted}, reorder: BOOLEAN] = BEGIN -- non system fatal errors: none. FOR list: LIST OF RefChunk _ listOfRefChunk, list.rest UNTIL list = NIL DO list.first.state _ valid; IF ((reorder) AND (NOT ChunkIsDirty[list.first]) AND (list.first.useCount = 0)) THEN FpmL.RelinkChunkAsLruOnLruList[list.first]; ENDLOOP; IF what = readCompleted -- weird form to keep compiler happy. THEN BROADCAST ReadDone ELSE BROADCAST WriteDone; END; InitializeFilePageMgr: PUBLIC PROCEDURE[nNormalChunksInCache: NAT, nLogChunksInCache: NAT, nLeaderChunksInCache: NAT] = BEGIN -- non system fatal errors: none. IF moduleInitialized THEN ERROR; OST.Initialize[ChunkAllocator[ostGeneral, TRUE], ChunkAllocator[ostGeneral, TRUE]]; FpmL.InitializeLruLists[[normal: nNormalChunksInCache, leader: nLeaderChunksInCache, log: nLogChunksInCache]]; -- operates inside the lru list monitor. moduleInitialized _ TRUE; END; -- fatal errors: ConsistencyError: -- CALLING or FPM -- ERROR = CODE; -- caller has asserted that a FPMFileHandle exists or a vmpageset has its usecount positive, but it doesn't. -- main line code: MaxReadAheadSets: NAT = 10; -- to avoid swamping the cache, we won't read ahead more -- sets than these on one request. -- The remainder of the request is ignored. LimitDefWriteChunks: NAT = 10; -- once this many chunks from a given file are -- waiting for a demon, we incarnate one or more. moduleInitialized: BOOLEAN _ FALSE; END. Edit Log Initial: Kolling: 23-Feb-82 14:49:56: main impl module for FilePageManager. Êï˜JšÍœÏcœÏk œ#žœcžœ)žœ žœ"žœ*žœBžœ4žœ]žœtžœ]žœµžœ™žœVžœ¹žœžœ žœ‚Ðblœžœžœžœžœ žœžœWžœžœžœ9žœžœžœžœžœ{žœžœžœžœ Ïn œžœžœ  œžœžœ œžœžœ œžœžœ.œwœ>œ œžœœžœžœ  œžœœžœžœ  œžœœžœžœ œžœœžœžœ œžœœžœžœ œžœœžœžœ œžœœžœžœ œžœœžœžœ  œžœžœžœ* œ8 œžœœžœžœ œžœžœœ œž œ  œž œ  œžœž œ2žœ!žœsœNžœ  œžœž œ2žœ!žœsœKžœ œžœžœž œžœ"žœIœMžœ œžœž œ2žœ!žœsœMžœ  œžœž œ2žœ!žœsœJžœ œžœž œžœ"žœIœLžœ  œžœžœ"žœ& œžœYœíœÁœ  œž œ}žœ>9œžœsœCžœ9žœ žœ žœžœžœžœžœžœ¨žœµžœžœžœ žœž$œžœ"žœžœ?žœ—ž5œžœžœžœžœ<žœžœ%žœžœoœ œžœž œµžœ žœžœ?\œžœ"œ+žœ{žœžœžœžœžœžœ9žœžœžœžœž œžœžœžœžœžœžœžœžœ žœžœžœ°ž0œž4œž?œžœ%ž=œž9œ!ž<œžœ žIœž;œ+žDœž œžBœžœ$ž9œžœžœž<œž0œžœžœžœ"žœ3žœ žœCžœ“ž œžœžœžœžœ-žœžœžœVž<œžœKžœž!œžœžœžœ ž0œHžœžœ1ž œ$žœžœžœžœ Ðck[œ œžœž œ^žœ žœ]œžœ"œžœ#ž œžœ)žœžœžœžœžœ žœžœJžœžœžœžœZžœžœ"žœ'žœžœ|œ œžœž œwžœ"œçžœ}œ  œžœž œ9žœ"œžœFžœžœžœ#žœ žœžœžœažœžœžœ\žœ œ œžœž œOžœžœ(žœ$žœ"œVžœ#žœžœÍžœVžœžœìžœCžœ6žœ`œ œž œ$žœ žœ"œžœ\žœ}žœ)œžœžœ  œž œžœ0žœžœžœ!œžœžœ¼žœžœžœžœžœžœ œžœž œtžœžœžœ"œ žœžœ žœž œFžœžœ žœ$žœžœžœžœ ž œžœžœ žœžœžœ!ž œžœžœžœžœ žœžœžœ žœLžœ žœ žœžœožœ-žœWœ œžœž œ6žœ\œ3žœ œžœž œ7žœ\œ0žœ œž œfžœ\œœ>žœžœžœžœž œžœžœ žœžœ žœ žœ)žœžœ£žœžœžœBžœžœžœ%žœžœžœžœ#žœžœžœ!žœžœžœ žœž œžœžœžœžœžœžœmžœžœ œž œ,žœžœžœžœ4žœ!žœ@žœœMžœ9ž œžœž œ8žœžœYžœ8œ œžœž œ žœ"œ,œ3œEžœžœ œžœž œ9žœ"œžœ1žœžœžœ œžœž œBžœžœ"œxžœžœž œžœ!žœžœžœjžœMžœažœ œžœž œZžœ žœžœ"œžœž œž œ ž œžœ)žœ(žœžœœžœ žœ&žœ/žœžœ7žœžœ(žœžœ,žœ žœžœžœ&žœžœ&žœžœžœžœ…žœKžœ žœžœ?œ  œžœžœ; œžœž œ<žœžœ"œužœžœžœ  œž œ žœ"œžœLž œ  œž œ žœ"œž œžœJžœžœžœžœžœ!žœžœžœ{žœ#žœžœžœžœZžœžœžœžœ4žœžœ;7œ?žœgžœžœžœ.žœžœžœ\œ œž œ6žœžœžœžœ"œžœžœ žœžœžœ žœzžœ žœ¿¡„ œžœž œ6žœžœžœžœžœžœžœ"œ0žœžœžœžœžœž œž œžœ7ž œ/žœžœ2žœžœžœ'žœžœžœ žœžœNžœ0žœž'œžœ^ž œžœ$žœžœž$œ@žœžœžœžœ‡œ  œžœž œ5žœžœžœ"œˆžœžœžœžœž œ4žœ-žœžœ/ž œžœž œžœžœž%œ žœ1žœ*žœ*ž œ žœž"œž œžœž œžœ œžœž œ žœ"œ,œLžœžœžœPžœ,žœGžœžœ  œžœž œ!žœIœCžœžœ!žœžœ!žœžœ~žœžœžœ¯ž œ œžœž œ žœ žœžœžœžœžœFœžœ"œžœ ž œAžœžœžœžœ#žœ1žœžœžœžœžœžœžœžœžœNžœžœžœ žœžœžœ9žœžœž œ#žœžœ žœžœ žœOž œž œžœ!žœ žœ)žœ8žœžœ"žœBžœPžœžœžœžœZžœžœ &œžœž œ5žœž œžœ"žœ…žœžœžœžœž œ4žœ,žœ0žœGžœžœ žœžœžœž œžœžœ œžœ"žœ!œžœžœžœžœžœIž œ žœžœ[ž œžœ]žœ¦žœ3ž œžœžœ œžœGžœžœžœžœžœ!œ1žœžœWžœžœžœžœžœ%žœžœžœPžœ$žœžœžœ,žœžœžœžœžœ  œžœž œžœ=œžœ-žœ"žœžœžœ)žœžœžœ‹œ œžœž œžœ"œžœ-žœ"žœžœžœžœ7žœ žœžœžœžœžœžœžœ&žœžœ 3œžœ œžœž œ žœ žœGœžœ!¡žœ#ž œžœ)žœžœžœžœ?žœ žœ žœ œžœž œ:ž œžœ•œ  œž œ=žœ žœ/œLžœžœžœžœ;žœžœžœnžœžœžœ(žœ'žœžœž œ ž œžœ ž œžœ ž œžœ œžœž œ!žœIœ žœžœ<žœžœžœ žœ œžœž œžœ žœDœžœ"œžœ#ž œžœ)žœžœžœžœ?žœEžœžœžœ)žœžœAžœžœ žœžœžœžœžœžœžœ œžœž œ5žœ‰œ žœžœ žœžœžœ=žœžœžœ žœ œžœž œ7žœ žœwœžœ"œžœ#žœ1žœžœžœžœžœžœžœ&žœžœ žœ&žœœ5žœUžœžœžœ(žœ%žœ(žœžœ0žœ žœžœžœžœ žœžœ0žœžœžœ žœžœžœ™œ œžœž œ2žœžœ"œžœ ž œžœ6žœ|ž œ žœžœBž œ{žœ ž>œž"œžœž"œkžœž œžœžœžœ‹œ œžœžœž œ2žœžœ,žœžœžœ"œLžœžœžœ6žœžœ+žœž œžœžœžœžœXžœžœžœžœ$œ0žœžœFžœžœsžœžœ,žœ œžœžœž œIžœžœžœ"œ'žœžœžœžœžœžœ žœ$žœžœžœžœžœžœžœž œ žœžœžœžœžœžœ"žœž œ žœžœžœžœžœžœ žœžœ0žœ,žœžœžœžœžœ'žœqžœžœžœžœžœžœžœQžœžœžœžœžœ  œžœž œžœžœžœ=œ žœQžœžœžœžœ žœ œžœž œžœ žœžœ:œžœ"œžœ#žœ1žœžœž œžœ œžœž œžœžœIœ žœLžœžœžœžœ žœ œžœž œžœ žœGœžœ"œžœ#žœ1žœžœžœžœžœžœžœ'žœœ œž œ/žœžœ'žœ" œž œžœ(žœ"œÆžœžœ%žœ&ž œžœžœ}ž œ5žœ žœžœLžœžœ žœžœžœžœžœ3žœžœ&žœžœ@žœ&žœžœœžœžœ]žœøžœZžœ œžœž œ(žœžœžœ?œžœ žœ žœžœžœ@žœ3žœ<žœžœžœ žœžœ žœžœžœžœÉžœ_žœžœ$œ œž œvžœ>ž$œ7žœžœ#žœ$œ œž œržœBž$œ9žœžœ#žœ œž œCžœ&žœžœ"œžœžœžœ žœwžœ œž œžœ žœžœ!œž œžœ$žœ œž œžœžœ!œ#žœ œž œžœžœ!œwžœ  œžœžœ žœžœ"œžœžœož œ&žœžœžœ žœžœžœžœsœ œž œBžœžœ=žœ"œžœžœDžœžœžœžœ8žœžœžœžœžœžœžœžœž œ[ž#œ žœžœ žœžœ9žœžœžœžœ(žœžœ žœžœ9žœžœžœž œžœžœ"žœ]žœžœ4žœÈžœž"œžœ  œž œnžœžœž"œ%žœÀž(œ`žœžœžœ•žœžœKžœ…žœ(žœ#žœ&žœ_žœ ž œžœ žœžœ žœ$œžœžœžœžœ žœžœ3žœžœžœ1žœžœ4œ œž œ8žœ"œžœ  œž œ_žœžœžœ!œžœžœžœ@žœ œ œž œ>ž œ[žœžœžœKœžžœ7žœWžœžœ žœž!œ#žœož1œžœžœžœ žœžœ.ž œž œ$žœž œ žœ!ž œžœKžœžœžœCž œ ž!œžœž œžœ,ž œž œ4žœ#žœ žœ(žœžœHžœžœžœ%žœOž œ ž œžœž œžœ¡žœ3žœžœžœžœžœžœžœ žœžœž œžœž œžœžœ œžœ .œžœž œ4žœžœžœ"œžœ!žœžœžœžœ ž œžœ žœžœ"žœžœ œžœ`žœ"œ žœžœ%œžœž œžœž œžœ $œžœ0žœ7žœ"œžœžœ žœž œž œ žœžœžœ%œžœž œžœž œžœ 3œžœ/žœ<žœžœ"œžœžœ žœž œž œ žœž œ žœžœžœ&žœ+žœžœ%œžœž œžœž œžœ œžœž œžœžœžœžœ"œžœžœžœ/žœžœ|)œžœžœœ œœžœžœmœœœ œžœ9œ#œ, œžœ/œ2œ œžœžœžœ™œ˜ÒË—…—åÔÿÉ