DIRECTORY Disk USING[ Channel, invalid, Label, ok, PageCount, PageNumber, Request, Status ], DiskFace USING[ AbsID, DontCare, RelID, Tries, wordsPerPage ], File USING[ Error, FP, GetVolumeID, PageCount, PageNumber, RC, Volume, VolumeID ], FileBackdoor USING [], FileInternal, VolumeFormat USING[ AbsID, Attributes, lastLogicalRun, LogicalPage, LogicalPageCount, LogicalRun, LogicalRunObject, RelID, RunPageCount ], VM USING[AddressForPageNumber, Allocate, Free, Interval, PageCount, PageNumber, PageNumberForAddress, PagesForWords, SwapIn], VMBacking USING[RunTableIndex, RunTableObject, RunTablePageNumber]; FilePagesImpl: CEDAR MONITOR LOCKS FileInternal.FileImplMonitorLock IMPORTS File, FileInternal, VM EXPORTS DiskFace, File, FileBackdoor, FileInternal SHARES File = { --DiskFace.--Attributes: PUBLIC TYPE = VolumeFormat.Attributes; --DiskFace.--AbsID: PUBLIC TYPE = VolumeFormat.AbsID; --DiskFace.--RelID: PUBLIC TYPE = VolumeFormat.RelID; Handle: TYPE = REF Object; --File.--Object: PUBLIC TYPE = FileInternal.Object; RunTable: TYPE = FileInternal.RunTable; RunTableObject: TYPE = VMBacking.RunTableObject; RunTableIndex: TYPE = VMBacking.RunTableIndex; PhysicalRun: TYPE = FileInternal.PhysicalRun; lastRun: VMBacking.RunTablePageNumber = LAST[INT]; -- end marker in runTable -- MaxTransferRun: PUBLIC Disk.PageCount _ 200; scratchWriter: PUBLIC LONG POINTER; -- scratch buffer for writing (all 0) scratchReader: PUBLIC LONG POINTER; -- scratch buffer for reading badData: Disk.Status = [unchanged[dataCRCError]]; -- indicates data is bad but label is ok labelTries: DiskFace.Tries = 5; notReallyFree: PUBLIC INT _ 0; HeaderLabel: PUBLIC PROC[fp: File.FP] RETURNS[Disk.Label] = { RETURN[ [ fileID: [rel[RelID[fp]]], filePage: 0, attributes: Attributes[header], dontCare: LOOPHOLE[LONG[0]] ] ] }; DataLabel: PUBLIC PROC[fp: File.FP] RETURNS[Disk.Label] = { RETURN[ [ fileID: [rel[RelID[fp]]], filePage: 0, attributes: Attributes[data], dontCare: LOOPHOLE[LONG[0]] ] ] }; FreeLabel: PUBLIC PROC[volume: File.Volume] RETURNS[Disk.Label] = { RETURN[ [ fileID: [abs[AbsID[File.GetVolumeID[volume]]]], filePage: 0, attributes: Attributes[freePage], dontCare: LOOPHOLE[LONG[0]] ] ] }; WriteLabels: PUBLIC PROC[channel: Disk.Channel, diskPage: Disk.PageNumber, count: Disk.PageCount, data: LONG POINTER, label: POINTER TO Disk.Label] RETURNS[ status: Disk.Status, countDone: Disk.PageCount] = TRUSTED { req: Disk.Request _ [ diskPage: diskPage, data: IF data = NIL THEN scratchWriter ELSE data, incrementDataPtr: data # NIL, command: [header: verify, label: write, data: write], count: count ]; [status, countDone] _ FileInternal.DoPinnedIO[channel, label, @req]; }; VerifyLabels: PUBLIC PROC[channel: Disk.Channel, diskPage: Disk.PageNumber, count: Disk.PageCount, label: POINTER TO Disk.Label] RETURNS[ status: Disk.Status, countDone: Disk.PageCount] = TRUSTED { req: Disk.Request _ [ diskPage: diskPage, data: FileInternal.scratchReader, incrementDataPtr: FALSE, command: [header: verify, label: verify, data: read], count: count, tries: labelTries ]; [status, countDone] _ FileInternal.DoPinnedIO[channel, label, @req]; IF status = badData THEN { status _ Disk.ok; countDone _ countDone+1; label.filePage _ label.filePage+1 }; }; GetScratchPage: PUBLIC PROC RETURNS [data: LONG POINTER] = TRUSTED { temp: LONG POINTER TO ARRAY [0..DiskFace.wordsPerPage) OF WORD; interval: VM.Interval _ VM.Allocate[VM.PagesForWords[DiskFace.wordsPerPage]]; VM.SwapIn[interval]; data _ VM.AddressForPageNumber[interval.page]; temp _ LOOPHOLE[data]; temp^ _ ALL[0]; }; FreeScratchPage: PUBLIC PROC[data: LONG POINTER] = TRUSTED { VM.Free[ [ page: VM.PageNumberForAddress[data], count: VM.PagesForWords[DiskFace.wordsPerPage] ] ]; }; AddRun: PUBLIC PROC[file: Handle, run: POINTER TO PhysicalRun, logicalPage: VolumeFormat.LogicalPage, okPages: VolumeFormat.RunPageCount] = TRUSTED { logical: LONG POINTER TO VolumeFormat.LogicalRunObject _ file.logicalRunTable; physical: RunTable = file.runTable; oldNRuns: CARDINAL _ physical.nRuns; physical.nDataPages _ file.size + okPages; IF oldNRuns > 0 AND logical[oldNRuns-1].first + logical[oldNRuns-1].size = logicalPage AND logical[oldNRuns-1].size <= LAST[VolumeFormat.RunPageCount] - okPages AND run.channel = physical[oldNRuns-1].channel THEN logical[oldNRuns-1].size _ logical[oldNRuns-1].size + okPages ELSE { IF physical.nRuns+1 = logical.maxRuns THEN { ExtendPhysicalRunTable[file]; logical _ file.logicalRunTable; -- recompute since it is changed by ExtendFileHeader in ExtendPhysicalRunTable oldNRuns _ physical.nRuns; }; physical[oldNRuns] _ run^; logical[oldNRuns].first _ logicalPage; logical[oldNRuns].size _ okPages; physical.nRuns _ oldNRuns + 1; IF physical.nRuns = physical.length THEN { file.runTable _ NEW[RunTableObject[physical.length*2]]; file.runTable.nDataPages _ physical.nDataPages; file.runTable.nRuns _ physical.nRuns; FOR i: CARDINAL IN [0..physical.length) DO file.runTable[i] _ physical[i] ENDLOOP; }; file.runTable[oldNRuns + 1].filePage _ lastRun; logical[oldNRuns + 1].first _ VolumeFormat.lastLogicalRun; }; }; ExtendPhysicalRunTable: PROC [file: Handle] = TRUSTED { newRunPages: VolumeFormat.LogicalPageCount = FileInternal.RunsToHeaderPages[file.logicalRunTable.maxRuns] + 1; diskPage1: Disk.PageNumber; restOfHeaderSize: Disk.PageCount; oldChannel: Disk.Channel; [diskPage: diskPage1, size: restOfHeaderSize, channel: oldChannel] _ FileInternal.FindRun[ start: [-file.logicalRunTable.headerPages+1], nPages: file.diskRunPages + file.diskPropertyPages - 1, runTable: file.runTable]; IF restOfHeaderSize # file.diskRunPages + file.diskPropertyPages - 1 THEN ERROR File.Error[fragmented]; FileInternal.ExtendFileHeader[file: file, newRunPages: newRunPages, newPropertyPages: file.diskPropertyPages]; -- recomputes logical.maxRuns }; RemoveFromRunTable: PUBLIC PROC[file: Handle, remove: INT] = TRUSTED { logical: LONG POINTER TO VolumeFormat.LogicalRunObject = file.logicalRunTable; physical: RunTable = file.runTable; physical.nDataPages _ file.size; WHILE remove > 0 DO IF physical.nRuns = 0 THEN ERROR File.Error[inconsistent]; { runSize: VolumeFormat.RunPageCount = logical[physical.nRuns-1].size; amount: VolumeFormat.RunPageCount = MIN[remove, runSize]; logical[physical.nRuns-1].size _ runSize - amount; IF runSize - amount = 0 THEN { physical.nRuns _ physical.nRuns - 1; physical[physical.nRuns].filePage _ lastRun; logical[physical.nRuns].first _ VolumeFormat.lastLogicalRun; }; remove _ remove - amount; }; ENDLOOP; }; SpliceOutDataPage: PUBLIC PROC[file: Handle, filePage: File.PageCount] RETURNS [oldPage: Disk.PageNumber _ [0], oldChannel: Disk.Channel _ NIL] = TRUSTED { runNumber: CARDINAL; nearTo: VolumeFormat.LogicalPage; newRun: FileInternal.PhysicalRun; newLogicalRun: VolumeFormat.LogicalRun ; labelsThisTime: Disk.PageCount _ 0; IF filePage >= file.runTable.nDataPages THEN ERROR File.Error[unknownPage]; [diskPage: oldPage, channel: oldChannel] _ FindRun[start: [filePage], nPages: 1, runTable: file.runTable]; IF file.runTable.nRuns+3 >= file.logicalRunTable.maxRuns THEN { -- may split too soon ExtendPhysicalRunTable[file]; -- make sure there is enough room FileInternal.WriteRunTable[file]; -- write it out }; [runNumber] _ SplitPhysicalRunTable[file: file, page: [filePage+file.runPages+file.propertyPages]]; IF filePage+1 < file.runTable.nDataPages THEN [] _ SplitPhysicalRunTable[file: file, page: [filePage+file.runPages+file.propertyPages+1]]; IF file.logicalRunTable.runs[runNumber].size # 1 THEN ERROR; nearTo _ file.logicalRunTable.runs[runNumber].first; WHILE labelsThisTime # 1 DO status: Disk.Status; labelsOK: Disk.PageCount; freeLabel: Disk.Label _ FileInternal.FreeLabel[file.volume]; newLogicalRun _ FileInternal.Alloc[volume: file.volume, first: nearTo, size: 1, min: 1 ]; freeLabel.filePage _ newLogicalRun.first; [newRun.channel, newRun.diskPage] _ FileInternal.TranslateLogicalRun[newLogicalRun, file.volume]; nearTo _ [newLogicalRun.first + 1]; -- hint for next call of Alloc, if needed TRUSTED{ [status, labelsOK] _ FileInternal.VerifyLabels[newRun.channel, newRun.diskPage, newLogicalRun.size, @freeLabel] }; IF status # Disk.ok THEN notReallyFree _ notReallyFree+1; -- statistics IF labelsOK = 1 THEN { dataLabel: Disk.Label _ FileInternal.DataLabel[file.fp]; dataLabel.filePage _ filePage; TRUSTED{[status, labelsThisTime] _ FileInternal.WriteLabels[newRun.channel, newRun.diskPage, 1, file.headerVM+DiskFace.wordsPerPage, @dataLabel]}; }; ENDLOOP; -- end of WHILE labelsThisTime # 1 DO file.logicalRunTable.runs[runNumber].first _ newLogicalRun.first; FileInternal.WriteRunTable[file]; [] _ FileInternal.TranslateLogicalRunTable[file: file]; }; SplitPhysicalRunTable: PUBLIC PROC[file: Handle, page: VolumeFormat.LogicalPage] RETURNS [runNumber: CARDINAL] = TRUSTED { logicalRunTable: LONG POINTER TO VolumeFormat.LogicalRunObject = file.logicalRunTable; filePage: VolumeFormat.LogicalPage _ [0]; FOR runNumber IN [0..logicalRunTable.maxRuns) DO IF logicalRunTable.runs[runNumber].first = VolumeFormat.lastLogicalRun THEN ERROR File.Error[inconsistent]; IF page = filePage THEN EXIT; -- split already in place IF page >= filePage AND page < filePage+logicalRunTable.runs[runNumber].size THEN { scratchLogicalRun: VolumeFormat.LogicalRun _ logicalRunTable.runs[runNumber]; FOR j: CARDINAL IN [runNumber+1..logicalRunTable.maxRuns) DO -- asumes enough room in the run table sLogicalRun: VolumeFormat.LogicalRun = logicalRunTable.runs[j]; logicalRunTable.runs[j] _ scratchLogicalRun; IF scratchLogicalRun.first = VolumeFormat.lastLogicalRun THEN EXIT; scratchLogicalRun _ sLogicalRun; ENDLOOP; logicalRunTable.runs[runNumber].size _ page - filePage; runNumber _ runNumber+1; logicalRunTable.runs[runNumber].first _ [logicalRunTable.runs[runNumber].first + page - filePage]; logicalRunTable.runs[runNumber].size _ logicalRunTable.runs[runNumber].size - page + filePage; RETURN; }; filePage _ [filePage+logicalRunTable.runs[runNumber].size]; ENDLOOP; }; FreeRun: PUBLIC PROC[logicalRun: VolumeFormat.LogicalRun, volume: File.Volume, verifyLabel: POINTER TO Disk.Label _ NIL] = { label: Disk.Label _ FreeLabel[volume]; WHILE logicalRun.size > 0 DO channel: Disk.Channel; diskPage: Disk.PageNumber; status: Disk.Status; verifyStatus: Disk.Status _ Disk.ok; thisTime: Disk.PageCount _ logicalRun.size; countDone: Disk.PageCount; Consume: PROC = { logicalRun.first _ [logicalRun.first + countDone]; logicalRun.size _ logicalRun.size - countDone; IF verifyLabel # NIL THEN TRUSTED { verifyLabel.filePage _ verifyLabel.filePage + countDone; }; countDone _ 0; }; [channel, diskPage] _ FileInternal.TranslateLogicalRun[logicalRun, volume]; IF verifyLabel # NIL THEN TRUSTED { temp: Disk.Label _ verifyLabel^; [verifyStatus, thisTime] _ VerifyLabels[channel, diskPage, thisTime, @temp]; }; label.filePage _ logicalRun.first; FileInternal.Free[volume, [first: logicalRun.first, size: thisTime]]; TRUSTED{[status, countDone] _ WriteLabels[channel, diskPage, thisTime, NIL, @label]}; SELECT status FROM Disk.ok => NULL; Disk.invalid => ERROR File.Error[wentOffline, diskPage+countDone]; ENDCASE => countDone _ countDone + 1; -- Page is in our file, but not writeable Consume[]; IF verifyLabel # NIL AND status = Disk.ok AND verifyStatus # Disk.ok THEN TRUSTED { free: Disk.Label _ FreeLabel[volume]; free.filePage _ logicalRun.first; [channel, diskPage] _ FileInternal.TranslateLogicalRun[logicalRun, volume]; [verifyStatus, countDone] _ VerifyLabels[channel, diskPage, logicalRun.size, @free]; IF verifyStatus # Disk.ok AND countDone = 0 THEN countDone _ 1 -- label isn't in our file, but isn't free, so ignore it ELSE FileInternal.Free[volume, [first: logicalRun.first, size: countDone]]; Consume[]; } ENDLOOP; }; FindRun: PUBLIC PROC[start: File.PageNumber, nPages: File.PageCount, runTable: RunTable] RETURNS [diskPage: Disk.PageNumber, size: Disk.PageCount, channel: Disk.Channel] = { probe: RunTableIndex _ runTable.nRuns / 2; -- NB: round down -- increment: CARDINAL _ probe; IF runTable.nRuns = 0 THEN ERROR File.Error[inconsistent]; IF start + nPages > runTable.nDataPages THEN ERROR File.Error[unknownPage, start]; IF start < runTable[0].filePage THEN ERROR File.Error[unknownPage, start]; DO increment _ (increment+1)/2; SELECT TRUE FROM runTable[probe].filePage > start => probe _ IF probe < increment THEN 0 ELSE probe-increment; runTable[probe+1].filePage <= start => probe _ MIN[probe + increment, runTable.nRuns-1]; ENDCASE => RETURN[ diskPage: [runTable[probe].diskPage + (start-runTable[probe].filePage)], size: IF start + nPages <= runTable[probe+1].filePage THEN nPages ELSE runTable[probe+1].filePage - start, channel: runTable[probe].channel ] ENDLOOP; }; maxTransferRun: Disk.PageCount _ 200; Transfer: PUBLIC PROC[file: Handle, data: LONG POINTER, filePage: File.PageNumber, nPages: File.PageCount, action: FileInternal.ActionType, where: FileInternal.WhereLocation ] = { label: Disk.Label _ IF where = header THEN FileInternal.HeaderLabel[file.fp] ELSE FileInternal.DataLabel[file.fp]; label.filePage _ filePage; WHILE nPages > 0 DO status: Disk.Status; countDone: Disk.PageCount; channel: Disk.Channel; req: Disk.Request; firstDiskPage: Disk.PageNumber; req.data _ data; req.incrementDataPtr _ TRUE; req.command _ IF action = read THEN [header: verify, label: verify, data: read] ELSE [header: verify, label: verify, data: write]; TRUSTED{ [diskPage: req.diskPage, size: req.count, channel: channel] _ FileInternal.FindRun[ -- NB: first call of FindRun checks entire transfer is within file start: IF where = header THEN [-file.logicalRunTable.headerPages+filePage] ELSE filePage, nPages: nPages, runTable: file.runTable] }; firstDiskPage _ req.diskPage; IF req.count > maxTransferRun THEN req.count _ maxTransferRun; TRUSTED{[status, countDone] _ FileInternal.DoPinnedIO[channel, @label, @req]}; FileInternal.CheckStatus[status, firstDiskPage+countDone]; -- use firstDiskPage instead of req.diskPage since DiskImpl modifies req.diskPage TRUSTED{data _ data + countDone * DiskFace.wordsPerPage}; nPages _ nPages - countDone; filePage _ [filePage + countDone]; ENDLOOP; }; WriteRunTable: PUBLIC PROC[file: Handle] = TRUSTED { IF file.diskRunPages = file.runPages AND file.diskPropertyPages = file.propertyPages THEN { FileInternal.Transfer[file: file, data: file.headerVM, filePage: [0], nPages: file.runPages, action: write, where: header]; } ELSE { labelsThisTime: Disk.PageCount _ 0; size: VolumeFormat.LogicalPageCount = file.runPages + file.propertyPages - 1 ; nearTo: VolumeFormat.LogicalPage _ file.logicalRunTable.runs[0].first; IF file.logicalRunTable.runs[0].size # 1 THEN { [] _ FileInternal.SplitPhysicalRunTable[file: file, page: [1]]; }; IF file.logicalRunTable.runs[1].size # file.diskRunPages+file.diskPropertyPages-1 THEN { [] _ FileInternal.SplitPhysicalRunTable[file: file, page: [file.diskRunPages+file.diskPropertyPages]]; }; WHILE labelsThisTime # size DO logicalRun: VolumeFormat.LogicalRun; status: Disk.Status; labelsOK: Disk.PageCount; run: FileInternal.PhysicalRun; freeLabel: Disk.Label _ FileInternal.FreeLabel[file.volume]; logicalRun _ FileInternal.Alloc[volume: file.volume, first: nearTo, size: size, min: size]; freeLabel.filePage _ logicalRun.first; [run.channel, run.diskPage] _ FileInternal.TranslateLogicalRun[logicalRun, file.volume]; nearTo _ [logicalRun.first + size]; -- hint for next call of Alloc, if needed TRUSTED { [status, labelsOK] _ FileInternal.VerifyLabels[run.channel, run.diskPage, logicalRun.size, @freeLabel] }; IF status # Disk.ok THEN notReallyFree _ notReallyFree+1; -- statistics IF labelsOK = size THEN { headerLabel: Disk.Label _ FileInternal.HeaderLabel[file.fp]; headerLabel.filePage _ 1; TRUSTED { [status, labelsThisTime] _ FileInternal.WriteLabels[run.channel, run.diskPage, size, file.headerVM+DiskFace.wordsPerPage, @headerLabel]; }; IF labelsThisTime = size THEN { destroyLogicalRun: VolumeFormat.LogicalRun _ file.logicalRunTable.runs[1]; file.logicalRunTable.runs[1] _ logicalRun; file.diskRunPages _ file.runPages ; file.diskPropertyPages _ file.propertyPages ; file.logicalRunTable.headerPages _ file.diskRunPages + file.diskPropertyPages ; [] _ FileInternal.TranslateLogicalRunTable[file]; FileInternal.Transfer[file: file, data: file.headerVM, filePage: [0], nPages: 1, action: write, where: header]; headerLabel.filePage _ 1; FileInternal.FreeRun[logicalRun: destroyLogicalRun, volume: file.volume, verifyLabel: @headerLabel]; } ELSE { IF labelsThisTime > 0 THEN { headerLabel.filePage _ 1; FileInternal.FreeRun[ [first: [logicalRun.first], size: labelsOK], file.volume, @headerLabel]; }; IF size - labelsThisTime - 1 > 0 THEN { FileInternal.Free[volume: file.volume, logicalRun: [first: [logicalRun.first + labelsOK + 1], size: size - labelsThisTime - 1]]; }; }; } ELSE { IF labelsOK > 0 THEN FileInternal.Free[volume: file.volume, logicalRun: [first: [logicalRun.first], size: labelsOK]]; IF size - labelsOK - 1 > 0 THEN FileInternal.Free[volume: file.volume, logicalRun: [first: [logicalRun.first + labelsOK + 1], size: size - labelsOK - 1]]; }; ENDLOOP; }; }; scratchWriter _ GetScratchPage[]; scratchReader _ GetScratchPage[]; }.  FilePagesImpl.mesa - per-file operations, locking file header data structure, file page ops Copyright c 1985 by Xerox Corporation. All rights reserved. Andrew Birrell December 8, 1983 9:51 am Levin, August 8, 1983 5:57 pm Schroeder, June 10, 1983 5:20 pm Bob Hagmann, January 9, 1986 4:01:58 pm PST Russ Atkinson (RRA) May 15, 1985 8:29:35 pm PDT Four different Impls all export some of these three data types. All exports are the same. Largest number of disk pages to transfer in single request. This much VM will be pinned during the transfer. Variable, not constant, to allow patching. FileInternal ******** Subroutines for access to file pages ******** -- FileInternal FileInternal FileInternal FileInternal FileInternal FileInternal FileInternal FileInternal Either extend last run, or add new run Add another run Extend logical run table to ensure there is space to record an extension. extend physical run table object file.runTable^ _ physical^ . . . . but the compiler can't copy sequences Extend logical run table to ensure there is space to record an extension. Do this by allocating a single run that has enough pages for all header pages except page 0. Then write the labels and header data for the new header pages 1 on up. Write header page 0. If the write succeeds, we have committed to the new header pages. If we crash, we loose the new header pages (should be free but are not), but we have a consistent view of the header: the old one. Free old header pages. insist that headers 1 on up all are in the same run FileInternal Remove the new run table entry entirely! -- FileBackdoor need to loop because we may find pages free in VAM that are not really free Suspected free page was free; write labels and data for header area retranslate since we have extended and changed everything FileInternal == page is logical page in file with header page 0 as page 0. copy forward all entries at and after the point of insertion FileInternal Write page labels as "free" and mark as unused in VAM. Iff verifyLabel # NIL, do so only to pages whose labels verify correctly. verify which pages are part of our file We've freed everything up to but excluding a label mis-match. Now, look for free pages which should be notified to the VAM in case the VAM thinks they're in use. FileInternal Largest number of disk pages to transfer in single request. This much VM will be pinned during the transfer. Variable, not constant, to allow patching. FileInternal Exported to FileInternal Caller guarantees header will not need to be expanded. Normal case: one page run table and single property page, or header size on disk is correct. Page level atomicity guarantees file invariants. Abnormal case: header is being extended. Be careful to build a new header pages 1 on up before writing page 0. It is possible to guarantee file invariants, but still loose some pages (headers written, not in disk run table) if we crash at the wrong time. Get run table in acceptable format: run 0 is one page long, run 1 has the rest of the header(s), the rest of the runs are for data pages. need to loop because we demand run number two contain all the rest of the header Suspected free pages were free; write labels and data for header area re-translate since we have changed everything Go for commit on the new header pages. Free old header pages Some pages would not write. Rewrite labels of pages written OK, and fix up VAM. deallocate pages where labels not changed Some labels were not free (verify failed) even though the VAM said they were free. Free pages with the labels changed, and fix up VAM for pages not changed. Bob Hagmann January 14, 1985 9:13:04 am PST Split from FileImpl due to storage overflow in compiler Bob Hagmann January 9, 1986 3:58:46 pm PST Fixes to bad status reporting changes to: Transfer Κ˜codešΟc œN™[Kšœ Οmœ1™KšœŸœ Ÿœ&Ÿœ˜RKšœ Ÿœ˜K˜ Kšœ Ÿœx˜ŠKšŸœŸœu˜}Kšœ Ÿœ4˜C—K˜šœŸœŸœŸœ!˜CKšŸœŸ˜KšŸœ+˜2KšŸœ ˜K˜—™ZK™Kš œ ŸœŸœ˜?K˜Kš œŸœŸœ˜5K˜Kš œŸœŸœ˜5K˜KšœŸœŸœ˜K˜Kš œŸœŸœ˜3K˜Kšœ Ÿœ˜'K˜KšœŸœ˜0K˜KšœŸœ˜.K˜Kšœ Ÿœ˜-K˜Kšœ(ŸœŸœ˜OK˜K˜šœŸœ˜,Kšœ˜™˜—K˜K˜KšœŸœŸœŸœ%˜JKšœŸœŸœŸœ˜CK˜Kšœ2(˜ZK˜Kšœ˜šœŸœŸœ˜Kš ™ —K˜—Kšœ9™9˜š Οn œŸœŸœ ŸœŸœ˜=Kš œ™ šŸœ˜ Kšœ˜K˜ Kšœ˜Kšœ ŸœŸœ˜K˜—Kšœ˜—K˜š   œŸœŸœ ŸœŸœ˜;Kš œ™ šŸœ˜ Kšœ˜K˜ Kšœ˜Kšœ ŸœŸœ˜K˜—Kšœ˜—K˜š  œŸœŸœŸœ˜CKš œ™ šŸœ˜ Kšœ/˜/K˜ Kšœ!˜!Kšœ ŸœŸœ˜K˜—Kšœ˜—K˜š  œŸœŸœPŸœŸœ ŸœŸœ Ÿœ4Ÿœ˜ΨKš ™ ˜Kšœ˜Kš œŸœŸœŸœŸœ˜1KšœŸœ˜K˜5Kšœ˜—KšœD˜DKšœ˜—K˜š  œŸœŸœQŸœŸœ Ÿœ4Ÿœ˜ΕKš ™ ˜Kšœ˜Kšœ!˜!KšœŸœ˜Kšœ5˜5Kšœ ˜ Kšœ˜—KšœD˜DKšŸœ˜KšŸœR˜VKšœ˜—K˜š œŸœŸœŸœŸœŸœŸœ˜DKš ™ Kš œŸœŸœŸœŸœŸœŸœ˜?Kšœ Ÿœ Ÿœ Ÿœ'˜MKšŸœ˜KšœŸœ%˜.KšœŸœ˜KšœŸœ˜Kšœ˜—K˜š  œŸœŸœŸœŸœŸœ˜˜BšŸœ˜Kšœ™šŸœ$Ÿœ˜,KšI™IKšœ˜Kšœ!8œ˜oKšœ˜Kšœ˜—Kšœ˜Kšœ&˜&Kšœ!˜!Kšœ˜šŸœ"Ÿœ˜*Kšœ ™ KšœŸœ$˜7KšœH™HKšœ/˜/Kšœ%˜%Kš ŸœŸœŸœŸœ Ÿœ˜RKšœ˜—Kšœ/˜/Kšœ:˜:Kšœ˜——Kšœ˜—K˜š œŸœŸœ˜7šI™IK™›—Kšœn˜nKšœ˜Kšœ!˜!Kšœ˜KšœΪ˜ΪšŸœCŸ˜IKšœ3™3KšŸœ˜—Kšœp˜K˜K˜—š  œŸœŸœŸœŸœ˜FKšœ ™ Kšœ ŸœŸœŸœ6˜NKšœ#˜#Kšœ ˜ šŸœ Ÿ˜KšŸœŸœŸœ˜:šœ˜KšœD˜DKšœ$Ÿœ˜9Kšœ2˜2šŸœŸœ˜Kšœ+™+Kšœ$˜$Kšœ,˜,Kšœ<˜KšŸœG˜NKšœ<Q˜KšŸœ2˜9K˜K˜"KšŸœ˜—Kšœ˜K˜—š  œŸœŸœŸœ˜4K™K™6šŸœ#Ÿœ,˜TšŸœ˜K™ŽKšœ{˜{K˜—šŸœ˜K™€Kšœ#˜#KšœN˜NšœF˜FK™‰—šŸœ'Ÿœ˜/Kšœ?˜?K˜—šŸœPŸœ˜XKšœf˜fK˜—K˜šŸœŸ˜KšœP™PKšœ$˜$K˜Kšœ˜Kšœ˜Kšœ<˜