DIRECTORY Disk USING[ Channel, DoIO, invalid, Label, labelCheck, ok, PageNumber, PageCount, Request, SameDrive, Status ], DiskFace USING[ DontCare, RelID, wordsPerPage ], NewFile USING[ Create, FileID, FP, nullFP, Handle, NextVolume, Open, PageCount, PageNumber, PagesForWords, RC, Read, Reason, SetRoot, VolumeFile, VolumeID, Write ], FileInternal, Process USING[ Pause, SecondsToTicks, Ticks ], Rope USING[ Equal, FromRefText, ROPE ], VolumeFormat USING[ AbsID, Attributes, LogicalPage, LogicalPageCount, LogicalRoot, LogicalRun, LRCurrentVersion, LRSeal, nullDiskFileID, RunPageCount, VAMObject, VAMChunk, vamChunkPos, volumeLabelLength ], Volumes USING[ SubVolumeDetails ], VM USING[ AddressToPageNumber, Allocate, Interval, MakeUnchanged, PageCount, PageNumber, PageNumberToAddress, State, SwapIn, Unpin, WordsToPages], VMSideDoor USING[ Run ]; VolumeRootImpl: CEDAR MONITOR LOCKS volume USING volume: Volume IMPORTS Disk, NewFile, Process, FileInternal, Rope, VM EXPORTS DiskFace--RelID,AbsID,Attributes--, NewFile, FileInternal SHARES NewFile = BEGIN OPEN File: NewFile; -- ******** Data Types and minor subroutines ******** -- --DiskFace.--Attributes: PUBLIC TYPE = VolumeFormat.Attributes; --DiskFace.--AbsID: PUBLIC TYPE = VolumeFormat.AbsID; --File.--FileID: PUBLIC TYPE = FileInternal.FileID; Volume: TYPE = REF VolumeObject; --File.--VolumeObject: PUBLIC TYPE = FileInternal.VolumeObject; PhysicalRun: TYPE = VMSideDoor.Run; DoPinnedIO: UNSAFE PROC[channel: Disk.Channel, label: REF Disk.Label, req: POINTER TO Disk.Request] RETURNS[ status: Disk.Status, countDone: Disk.PageCount] = TRUSTED BEGIN interval: VM.Interval = [ page: VM.AddressToPageNumber[req.data], count: VM.WordsToPages[ (IF req.incrementDataPtr THEN req.count ELSE 1)*DiskFace.wordsPerPage] ]; VM.SwapIn[interval: interval, kill: req.command.data=read, pin: TRUE]; [status, countDone] _ Disk.DoIO[channel, label, req ! UNWIND => VM.Unpin[interval] ]; VM.Unpin[interval]; END; --File.--Error: PUBLIC ERROR[why: File.Reason] = CODE; -- ******** Access to volume root page and volume's list of sub-volumes ******** -- volRoot: VolumeFormat.LogicalPage = [0]; -- logical page number of volume root page -- --File.--GetVolumeID: PUBLIC PROC[volume: Volume] RETURNS[id: File.VolumeID] = { RETURN[volume.id] }; LogRootLabel: PROC[id: File.VolumeID] RETURNS[REF Disk.Label] = BEGIN RETURN[NEW[Disk.Label _ [ fileID: [abs[AbsID[id]]], filePage: 0, attributes: Attributes[logicalRoot], dontCare: LOOPHOLE[LONG[0]] ] ] ] END; RootTransfer: INTERNAL PROC[volume: Volume, direction: {read, write} ] RETURNS[why: File.RC] = TRUSTED BEGIN status: Disk.Status; countDone: Disk.PageCount; req: Disk.Request _ [ diskPage: volume.subVolumes.first.address, data: volume.root, incrementDataPtr: TRUE, command: [header: verify, label: verify, data: IF direction = read THEN read ELSE write], count: 1 ]; [status, countDone] _ DoPinnedIO[volume.subVolumes.first.channel, LogRootLabel[volume.id], @req]; RETURN[ FileInternal.TranslateStatus[status] ] END; --FileInternal.--ReadRootPage: PUBLIC ENTRY PROC[volume: Volume] = TRUSTED BEGIN ENABLE UNWIND => NULL; volume.root _ VM.PageNumberToAddress[VM.Allocate[ VM.WordsToPages[DiskFace.wordsPerPage]].page]; volume.rootStatus _ RootTransfer[volume, read]; IF volume.rootStatus = ok AND (volume.root.seal # VolumeFormat.LRSeal OR volume.root.version # VolumeFormat.LRCurrentVersion) THEN volume.rootStatus _ inconsistent; IF volume.rootStatus = ok THEN BEGIN ENABLE Error => BEGIN volume.vamStatus _ SELECT why FROM unknownFile, unknownPage => inconsistent, ENDCASE => why; CONTINUE END; volume.lastFileID _ volume.root.lastFileID; TRUSTED{ volume.name _ GetName[@volume.root.label, volume.root.labelLength] }; ReadVAM[volume]; volume.vamStatus _ ok; END; END; GetName: PROC[ chars: LONG POINTER TO PACKED ARRAY [0..VolumeFormat.volumeLabelLength) OF CHAR, length: INT] RETURNS[Rope.ROPE] = TRUSTED BEGIN text: REF TEXT = NEW[TEXT[length]]; FOR i: INT IN [0..length) DO text[i] _ chars[i] ENDLOOP; text.length _ length; RETURN[Rope.FromRefText[text]] END; --File.--GetVolumeName: PUBLIC ENTRY PROC[volume: Volume] RETURNS[Rope.ROPE] = BEGIN ENABLE UNWIND => NULL; IF volume.rootStatus # ok THEN RETURN WITH ERROR Error[volume.rootStatus]; RETURN[volume.name] END; --File.--FindVolumeFromID: PUBLIC PROC[id: File.VolumeID] RETURNS[volume: Volume] = BEGIN volume _ NIL; DO volume _ File.NextVolume[volume]; IF volume = NIL THEN EXIT; IF GetVolumeID[volume] = id THEN EXIT; ENDLOOP; END; --File.--FindVolumeFromName: PUBLIC PROC[name: Rope.ROPE] RETURNS[volume: Volume] = BEGIN volume _ NIL; DO volume _ File.NextVolume[volume]; IF volume = NIL THEN EXIT; IF Rope.Equal[GetVolumeName[volume ! Error => CONTINUE], name, FALSE] THEN EXIT; ENDLOOP; END; --FileInternal.--TranslateLogicalRun: PUBLIC --EXTERNAL-- PROC[logicalRun: VolumeFormat.LogicalRun, volume: Volume, page: File.PageNumber] RETURNS[run: PhysicalRun] = BEGIN FOR sv: LIST OF Volumes.SubVolumeDetails _ volume.subVolumes, sv.rest UNTIL sv = NIL DO IF sv.first.start <= logicalRun.first AND sv.first.start + sv.first.size >= logicalRun.first + logicalRun.size THEN RETURN[ [filePage: page, channel: sv.first.channel, diskPage: [sv.first.address + (logicalRun.first - sv.first.start)] ] ]; REPEAT FINISHED => RETURN WITH ERROR Error[inconsistent] ENDLOOP; END; --FileInternal.--RecordRootFile: PUBLIC ENTRY PROC[volume: Volume, root: File.VolumeFile, fp: File.FP, id: DiskFace.RelID, link: DiskFace.DontCare, channel: Disk.Channel] = TRUSTED BEGIN ENABLE UNWIND => NULL; why: File.RC; IF volume.rootStatus # ok THEN RETURN WITH ERROR Error[volume.rootStatus]; IF NOT Disk.SameDrive[channel, volume.subVolumes.first.channel] THEN RETURN WITH ERROR Error[mixedDevices]; volume.root.rootFile[root] _ [fp: fp]; why _ RootTransfer[volume, write]; IF why # ok THEN RETURN WITH ERROR Error[why]; END; --File.--GetRoot: PUBLIC ENTRY PROC[volume: Volume, root: File.VolumeFile] RETURNS[fp: File.FP, page: File.PageNumber _ [0]] = TRUSTED BEGIN ENABLE UNWIND => NULL; IF volume.rootStatus # ok THEN RETURN WITH ERROR Error[volume.rootStatus]; fp _ volume.root.rootFile[root].fp; END; EraseRoot: INTERNAL PROC[volume: Volume] = TRUSTED BEGIN volume.root.bootingInfo _ ALL[VolumeFormat.nullDiskFileID]; volume.root.rootFile _ ALL[]; END; idGroupSize: INT = 100; -- number if id's to allocate at a time. --FileInternal.--NewID: PUBLIC ENTRY PROC[volume: Volume] RETURNS [FileID] = TRUSTED BEGIN ENABLE UNWIND => NULL; IF volume.rootStatus # ok THEN RETURN WITH ERROR Error[volume.rootStatus]; DO lastRecorded: FileID = volume.root.lastFileID; why: File.RC; IF volume.lastFileID < lastRecorded THEN EXIT; volume.root.lastFileID _ FileID[lastRecorded + idGroupSize]; why _ RootTransfer[volume, write]; IF why # ok THEN RETURN WITH ERROR Error[why]; ENDLOOP -- This is a loop only to recover from garbage ID's --; RETURN[ volume.lastFileID _ [volume.lastFileID + 1] ] END; -- ******** Volume Allocation Map ******** -- VAM: TYPE = FileInternal.VAM; PageBits: TYPE = MACHINE DEPENDENT RECORD[ls(0): CARDINAL, ms(1): CARDINAL]; VAMChunks: PROC[pages: VolumeFormat.LogicalPageCount] RETURNS[CARDINAL] = TRUSTED { OPEN p: LOOPHOLE[pages+LAST[CARDINAL]-1, PageBits]; RETURN[p.ms] }; VAMWords: PROC[pages: VolumeFormat.LogicalPageCount] RETURNS[INT] = -- SIZE[VAMObject[VAMChunks[pages]]], but compiler can't handle it -- { RETURN[VolumeFormat.vamChunkPos + VAMChunks[pages]*4096] }; VAMFilePages: PROC[pages: VolumeFormat.LogicalPageCount] RETURNS[File.PageCount] = INLINE { RETURN[File.PagesForWords[VAMWords[pages]]] }; VAMVMPages: PROC[pages: VolumeFormat.LogicalPageCount] RETURNS[VM.PageCount] = INLINE { RETURN[VM.WordsToPages[VAMWords[pages]]] }; Used: PROC[vam: VAM, page: VolumeFormat.LogicalPage] RETURNS[BOOL] = TRUSTED INLINE -- vam.used[p.ms][p.ls]], but the compiler can't handle it! -- { OPEN p: LOOPHOLE[page, PageBits]; RETURN[LOOPHOLE[@vam.used + p.ms*SIZE[VolumeFormat.VAMChunk], LONG POINTER TO VolumeFormat.VAMChunk][p.ls]] }; SetUsed: PROC[vam: VAM, page: VolumeFormat.LogicalPage, inUse: BOOL] = TRUSTED INLINE -- vam.used[p.ms][p.ls]], but the compiler can't handle it! -- { OPEN p: LOOPHOLE[page, PageBits]; LOOPHOLE[@vam.used + p.ms*SIZE[VolumeFormat.VAMChunk], LONG POINTER TO VolumeFormat.VAMChunk][p.ls] _ inUse }; AllocVAM: INTERNAL PROC[volume: Volume] = TRUSTED BEGIN IF volume.vam = NIL THEN BEGIN volume.vam _ VM.PageNumberToAddress[VM.Allocate[VAMVMPages[volume.size]].page]; volume.vam.size _ volume.size; END; SetUsed[volume.vam, volRoot, TRUE]; volume.vam.rover _ [0]; volume.free _ 0; END; ReadVAM: INTERNAL PROC[volume: Volume] = TRUSTED BEGIN this: VolumeFormat.LogicalPage _ [0]; IF volume.rootStatus # ok THEN ERROR; -- checked by our caller volume.vamFile _ File.Open[volume, volume.root.rootFile[VAM].fp --File.Error is caught by our caller--]; AllocVAM[volume]; File.Read[volume.vamFile, [0], VAMFilePages[volume.size], volume.vam]; volume.free _ 0; DO IF this = volume.vam.size THEN EXIT; IF NOT Used[volume.vam, this] THEN volume.free _ volume.free+1; this _ [this+1]; ENDLOOP; END; --File.--GetVolumePages: PUBLIC ENTRY PROC[volume: Volume] RETURNS[size, free: File.PageCount] = BEGIN ENABLE UNWIND => NULL; IF volume.rootStatus # ok THEN RETURN WITH ERROR Error[volume.rootStatus]; RETURN[size: volume.size, free: volume.free] END; --FileInternal.--Alloc: PUBLIC ENTRY PROC[volume: Volume, first: VolumeFormat.LogicalPage, size: VolumeFormat.LogicalPageCount] RETURNS[given: VolumeFormat.LogicalRun] = TRUSTED BEGIN ENABLE UNWIND => NULL; vam: VAM = volume.vam; IF volume.vamStatus # ok THEN RETURN WITH ERROR Error[volume.vamStatus]; IF volume.free < size THEN RETURN WITH ERROR Error[volumeFull]; volume.vamChanged _ TRUE; -- Restrict run length to LAST[CARDINAL], to maximize runs per run-table page -- IF size > LAST[VolumeFormat.RunPageCount] THEN size _ LAST[VolumeFormat.RunPageCount]; -- TEMP! Returns first free run found -- WHILE Used[vam, vam.rover] DO vam.rover _ [vam.rover+1]; IF vam.rover = vam.size THEN vam.rover _ [0] ENDLOOP; given _ [first: vam.rover, size: 0]; UNTIL Used[vam, vam.rover] OR vam.rover = vam.size OR given.size = size DO given.size _ given.size+1; vam.rover _ [vam.rover+1] ENDLOOP; FOR i: INT IN [0..given.size) DO SetUsed[vam, [given.first+i], TRUE] ENDLOOP; volume.free _ volume.free - given.size; END; --FileInternal.--Free: PUBLIC ENTRY PROC[volume: Volume, logicalRun: VolumeFormat.LogicalRun] = BEGIN ENABLE UNWIND => NULL; IF volume.vamStatus # ok THEN RETURN WITH ERROR Error[volume.vamStatus]; volume.vamChanged _ TRUE; FOR i: VolumeFormat.RunPageCount IN [0..logicalRun.size) DO SetUsed[volume.vam, [logicalRun.first + i], FALSE] ENDLOOP; volume.free _ volume.free + logicalRun.size; END; vamFileRelease: CONDITION; GrabVAMFile: ENTRY PROC[volume: Volume] RETURNS [file: File.Handle] = TRUSTED BEGIN ENABLE UNWIND => NULL; DO IF volume.vamStatus # ok THEN RETURN WITH ERROR Error[volume.vamStatus]; IF (file _ volume.vamFile) # NIL THEN { volume.vamFile _ NIL; EXIT }; WAIT vamFileRelease; -- because we must be in the process of erasing the volume ENDLOOP; volume.vamChanged _ FALSE; -- because our caller is about to write it to disk END; ReleaseVAMFile: ENTRY PROC[volume: Volume, file: File.Handle, changed: BOOL] = TRUSTED BEGIN ENABLE UNWIND => NULL; volume.vamChanged _ volume.vamChanged OR changed--indicates if commit failed--; IF (volume.vamFile _ file) = NIL THEN volume.vamStatus _ inconsistent; BROADCAST vamFileRelease; END; --FileInternal.--Commit: PUBLIC PROC[volume: Volume] = TRUSTED BEGIN -- Ensure VAM is up to date on disk -- -- Assumes volume.vam is vm-page-aligned. -- vamFile: File.Handle _ GrabVAMFile[volume]; -- raises File.Error if vamFile isn't ok. InnerCommit[volume, vamFile ! Error => { ReleaseVAMFile[volume, vamFile, TRUE]; vamFile _ NIL }; -- release our lock UNWIND => IF vamFile # NIL THEN ReleaseVAMFile[volume, vamFile, TRUE] ]; ReleaseVAMFile[volume, vamFile, FALSE]; END; InnerCommit: PROC[volume: Volume, vamFile: File.Handle] = TRUSTED BEGIN filePageCount: File.PageCount = VAMFilePages[volume.size]; vmPagesPerFilePage: INT = VM.WordsToPages[DiskFace.wordsPerPage]; FOR filePage: File.PageNumber _ [0], [filePage+1] UNTIL filePage = filePageCount DO fileBaseAddr: LONG POINTER = LOOPHOLE[volume.vam + filePage * DiskFace.wordsPerPage]; baseVMPage: VM.PageNumber = VM.AddressToPageNumber[fileBaseAddr]; FOR i: INT IN [0..vmPagesPerFilePage) DO IF VM.State[baseVMPage+i].dataState = changed THEN BEGIN VM.MakeUnchanged[[page: baseVMPage, count: vmPagesPerFilePage]]; File.Write[vamFile, filePage, 1, fileBaseAddr]; EXIT END; ENDLOOP; ENDLOOP; END; --File.--EraseVolume: PUBLIC PROC[volume: Volume] = TRUSTED BEGIN EntryErase[volume]; BEGIN ENABLE UNWIND => ReleaseVAMFile[volume, NIL, TRUE];-- vamState _ inconsistent vamFile: File.Handle _ NIL; FOR sv: LIST OF Volumes.SubVolumeDetails _ volume.subVolumes, sv.rest UNTIL sv = NIL DO IF volRoot >= sv.first.start AND volRoot < sv.first.start + sv.first.size THEN BEGIN FileInternal.FreeRun[[first: sv.first.start, size: volRoot-sv.first.start], volume]; FileInternal.FreeRun[[first: [volRoot+1], size: sv.first.size-(volRoot-sv.first.start)-1], volume]; END ELSE FileInternal.FreeRun[[first: sv.first.start, size: sv.first.size], volume]; ENDLOOP; vamFile _ File.Create[volume, VAMFilePages[volume.size]]; InnerCommit[volume, vamFile]; File.SetRoot[VAM, vamFile]; ReleaseVAMFile[volume, vamFile, FALSE]; END; END; EntryErase: ENTRY PROC[volume: Volume] = TRUSTED BEGIN ENABLE UNWIND => NULL; WHILE volume.vamStatus = ok AND volume.vamFile = NIL DO WAIT vamFileRelease ENDLOOP; IF volume.rootStatus # ok THEN RETURN WITH ERROR Error[volume.rootStatus]; volume.vamStatus _ inconsistent; volume.vamFile _ NIL; EraseRoot[volume]; AllocVAM[volume]; volume.vamStatus _ ok; -- but we still own the (not-yet-created) volume.vamFile END; CommitVAMFiles: PROC = BEGIN commitPause: Process.Ticks _ Process.SecondsToTicks[1]; DO FOR this: Volume _ File.NextVolume[NIL,FALSE], File.NextVolume[this,FALSE] UNTIL this = NIL DO IF this.vamChanged--in monitor!-- THEN Commit[this ! Error => CONTINUE] ENDLOOP; Process.Pause[commitPause]; ENDLOOP; END; vamCommiter: PROCESS = FORK CommitVAMFiles[]; END. ÖCedar Nucleus(Files): Access to per-volume data structures: root page and VAM VolumeRootImpl.mesa Andrew Birrell May 2, 1983 10:02 am Last Edited by: Levin, May 20, 1983 5:21 pm Accesses only immutable fields of the volume If root = VAM then I hope you know what you're doing! -- ******** Creation and writing of the VAM file ******** -- This is messy, because there is a recursion through the top-level file operations, particularly File.Create. So we must tread very carefully to avoid recursing onto our own monitor lock. Note that File.Create does not call Commit. We now own the (not-yet-created) volume.vamFile, but we're outside our monitor so File.Create won't deadlock! Avoid overwriting volume root page! ʘJšœN™NJšœ™Jšœ$™$J™+šÏk ˜ Jšœœe˜oJšœ œ"˜0JšœœœJœ7˜¤J˜ Jšœœ!˜.Jšœœœ˜'Jšœ œ»˜ÍJšœœ˜"JšœœŠ˜’Jšœ œ˜—J˜š œœœœœ˜?Jšœ-˜6Jšœ Ïcœ˜AJšœ ˜—J˜Jš˜J˜šœ˜J˜—J˜Jšž8˜8˜Jšž œ œœ˜?J˜Jšž œœœ˜5J˜Jšž œœœ˜3J˜Jšœœœ˜ J˜Jšž œœœ˜?J˜Jšœ œ˜#J˜š Ïn œœœœœœ˜cJšœ4˜BJš˜šœ œ ˜Jšœœ˜'šœœ˜Jšœœœ œ˜I——Jšœ>œ˜FJšœ6œœ˜UJšœ˜Jšœ˜—J˜šž œœœœ˜6J˜J˜—J˜J˜—JšžS˜S˜Jšœ)ž-˜VJ˜š ž Ÿ œœœœ˜NJšœœ˜—J˜šŸ œœœœ˜?Jš˜šœœ˜J˜J˜ J˜$Jšœ œœ˜J˜—Jšœ˜—J˜šŸ œ œ+˜FJšœ œ˜Jš˜J˜J˜˜Jšœ*˜*J˜Jšœœ˜Jšœ/œœœ˜YJ˜ —˜JšœK˜K—Jšœ(˜.Jšœ˜—J˜š žŸ œœœœ˜JJš˜Jšœœœ˜šœœœ ˜1Jšœ,˜.—Jšœ/˜/Jšœ˜šœ(˜+Jšœ5˜7—Jšœ"˜&Jšœ˜šœ˜ šœ ˜Jš˜šœœ˜"J˜)Jšœ˜—Jš˜Jšœ˜—J˜+JšœG˜NJ˜J˜Jšœ˜—Jšœ˜—J˜šŸœœ˜Jšœœœœœœ%œœ˜PJšœœ˜ Jšœœ˜Jš˜Jš œœœœœ ˜#Jš œœœ œœ˜8J˜Jšœ˜Jšœ˜—J˜š ž Ÿ œœ œœœ˜NJš˜Jšœœœ˜Jš œœœœœ˜JJšœ ˜Jšœ˜—J˜š ž Ÿœœœœ˜SJš˜Jšœ œ˜ šœ"˜$Jšœ œœœ˜Jšœœœ˜&—Jšœ˜Jšœ˜—J˜š ž Ÿœœœ œœ˜SJš˜Jšœ œ˜ šœ"˜$Jšœ œœœ˜Jšœ,œ œ˜EJšœœ˜ —Jšœ˜Jšœ˜—J˜š žŸœœžžœœL˜ŠJšœ˜Jšœ,™,Jš˜Jš œœœ7œ˜Tšœœ#˜(JšœE˜Hšœœ˜J˜J˜BJ˜——Jš œœœœœ˜8Jšœ˜Jšœ˜—J˜š žŸœœœœ{˜´Jš˜Jšœœœ˜Jšœ œ˜ Jš œœœœœ˜JJšœœ9˜?Jšœœœœ˜+Jšœ5™5Jšœ&˜&Jšœ"˜"Jš œ œœœœ ˜.Jšœ˜—J˜šž Ÿœœ œ'˜JJšœ œ!˜;Jš˜Jšœœœ˜Jš œœœœœ˜JJ˜#Jšœ˜—J˜šŸ œœœ˜2Jš˜Jšœœ˜;Jšœœ˜Jšœ˜—J˜Jšœ œž(˜@J˜š žŸœœœœœ ˜TJš˜Jšœœœ˜Jš œœœœœ˜Jšœ˜Jšœ.˜.Jšœ œ˜ Jšœ"œœ˜.Jšœ<˜˜>šœœœ˜#šœœœ˜=Jšœœœ!˜0———J˜š Ÿœœœ)œœ˜UJšž>˜>šœœœ˜#šœœ˜6Jšœœœ(˜7———J˜šŸœœœ˜1Jš˜Jšœ˜šœ˜ Jšœ œœ)˜OJ˜Jšœ˜—Jšœœ˜#J˜J˜Jšœ˜—J˜šŸœœœ˜0Jš˜J˜%Jšœœœž˜>šœ˜Jšœ'œž&œ˜W—J˜J˜FJ˜šœœœœ˜'Jšœœœ˜?J˜—Jšœ˜Jšœ˜—J˜š ž Ÿœœ œœ˜`Jš˜Jšœœœ˜Jš œœœœœ˜JJšœ&˜,Jšœ˜—J˜š žŸœœœœ1˜ZJ˜$Jšœ#˜1Jš˜Jšœœœ˜Jšœœ˜Jš œœœœœ˜HJš œœœœœ˜?Jšœœ˜JšžP˜PJšœœœœ˜VJšž)˜)Jšœ˜Jšœœœœ˜SJ˜$Jšœœœ˜GJšœ6œ˜@Jš œœœœœœ˜MJ˜'Jšœ˜—J˜š žŸœœœœ7˜_Jš˜Jšœœœ˜Jš œœœœœ˜HJšœœ˜Jšœœ˜8Jšœ-œœ˜>J˜,Jšœ˜J˜J˜—J˜J˜—Jšž œ$ž ™<™Jšœè™èJ˜Jšœ œ˜J˜š Ÿ œœœœ˜MJš˜Jšœœœ˜š˜Jš œœœœœ˜HJš œœœœœ˜EJšœž:˜O—Jšœ˜Jšœœž2˜MJšœ˜—J˜š Ÿœœœ-œ˜VJš˜Jšœœœ˜Jšœ&œžœ˜OJšœœœ!˜FJš œ˜Jšœ˜—J˜šžŸœœœ˜>Jš˜Jšž&˜&Jšž,˜,Jšœ,ž)˜U˜Jšœ+œ œž˜VJš œœ œœ!œ˜EJ˜—Jšœ œ˜'Jšœ˜—J˜šŸ œœ)˜AJš˜J˜:Jšœœœ%˜AJšœ/œ˜Pšœœœœ0˜XJšœ œœ#˜AJšœœœ˜%šœœœ(˜0šœ˜ Jšœ>˜@J˜/Jš˜Jšœ˜——Jšœ˜—Jšœ˜Jšœ˜—J˜šž Ÿ œœœ˜;Jš˜Jšœ˜Jšœm™mš˜Jš œœœœž˜MJšœœ˜Jš œœœ7œ˜Tšœœœ)˜Lšœ˜ Jšœ#™#J˜TJ˜cJš˜—JšœL˜P—Jšœ˜Jšœ9˜9Jšœ˜Jšœ œ ˜Jšœ œ˜'—Jšœ˜Jšœ˜—J˜šŸ œœœ˜0Jš˜Jšœœœ˜Jš œœœœœœ˜TJš œœœœœ˜JJ˜ Jšœœ˜J˜Jšœ˜Jšœž8˜OJšœ˜—J˜šŸœœ˜Jš˜Jšœ7˜7š˜Jšœ œœœ˜JJšœ˜Jš œœžœœœœ˜SJšœ˜—Jšœ˜Jšœ˜—J˜Jšœ œœ˜-J˜—Jšœ˜J˜J˜—…—7ÎL¨