DIRECTORY Disk USING[ DoIO, Channel, DriveAttributes, InspectDiskShape, Label, labelCheck, NextChannel, ok, PageCount, PageNumber, Request, Status, Valid ], DiskFace USING[ wordsPerPage ], File USING[ VolumeID ], FileInternal USING[ ReadRootPage, TranslateStatus, VolumeObject ], VolumeFormat, PhysicalVolume USING[ Credentials, PhysicalRC, SubVolumeDetails, SubVolumeDetailsObject ], VM USING[ AddressToPageNumber, Allocate, Free, PageNumberToAddress, Pin, Unpin, wordsPerPage ], Rope USING[ ROPE, FromRefText ]; PhysicalVolumeImpl: CEDAR MONITOR IMPORTS Disk, FileInternal, Rope, VM EXPORTS DiskFace--AbsID--, File, PhysicalVolume = BEGIN PageData: TYPE = LONG POINTER; pagePages: INT = (DiskFace.wordsPerPage+VM.wordsPerPage-1)/VM.wordsPerPage; AccessPage: UNSAFE PROC[channel: Disk.Channel, page: Disk.PageNumber, label: LONG POINTER TO Disk.Label, data: PageData, direction: { read, write }, verifyLabel: BOOL --else read label--] RETURNS[ status: Disk.Status ] = TRUSTED BEGIN ENABLE UNWIND => VM.Unpin[[page: VM.AddressToPageNumber[data], count: pagePages]]; countDone: Disk.PageCount; req: Disk.Request _ [ diskPage: page, data: data, command: [header: verify, label: IF verifyLabel THEN verify ELSE read, data: IF direction=read THEN read ELSE write], count: 1 ]; VM.Pin[[page: VM.AddressToPageNumber[data], count: pagePages]]; [status, countDone] _ Disk.DoIO[channel, label, @req]; VM.Unpin[[page: VM.AddressToPageNumber[data], count: pagePages]]; END; FreePage: UNSAFE PROC[data: PageData] = TRUSTED { VM.Free[[page: VM.AddressToPageNumber[data], count: pagePages]] }; ReadPage: PROC[channel: Disk.Channel, page: Disk.PageNumber, label: LONG POINTER TO Disk.Label, verifyLabel: BOOL] RETURNS[ status: Disk.Status, data: PageData ] = TRUSTED BEGIN data _ VM.PageNumberToAddress[VM.Allocate[pagePages].page]; status _ AccessPage[channel, page, label, data, read, verifyLabel]; IF status # Disk.ok THEN { FreePage[data]; data _ NIL }; END; PhysRoot: TYPE = LONG POINTER TO VolumeFormat.PhysicalRoot; --DiskFace.--AbsID: PUBLIC TYPE = VolumeFormat.AbsID; --DiskFace.--Attributes: PUBLIC TYPE = VolumeFormat.Attributes; PhysRootLabel: PROC[id: File.VolumeID] RETURNS[Disk.Label] = TRUSTED BEGIN RETURN[ [ fileID: [abs[AbsID[id]]], filePage: 0, attributes: Attributes[physicalRoot], dontCare: LOOPHOLE[LONG[0]] ] ] END; CredentialsLabel: PROC[id: File.VolumeID] RETURNS[Disk.Label] = TRUSTED BEGIN cannedID: MACHINE DEPENDENT RECORD[a,b,c,d,e: CARDINAL] = [a: 076543B, b: 021076B, c: 054321B, d: 076543B, e: 021076B]; -- ask Roy! RETURN[ [ fileID: [abs[AbsID[LOOPHOLE[cannedID]]]], filePage: 0, attributes: Attributes[LOOPHOLE[0]], dontCare: LOOPHOLE[LONG[0]] ] ] END; ReadPhysicalRoot: PROC[channel: Disk.Channel] RETURNS[ status: Disk.Status, root: PhysRoot ] = TRUSTED BEGIN label: Disk.Label; data: PageData; [status, data] _ ReadPage[channel, VolumeFormat.rootPageNumber, @label, FALSE]; root _ LOOPHOLE[data]; IF status = Disk.ok AND label # PhysRootLabel[root.pvID] THEN { status _ Disk.labelCheck; FreePage[data]; root _ NIL }; END; --PhysicalVolume.--PhysicalObject: PUBLIC TYPE = RECORD[ channel: Disk.Channel, rootStatus: PhysicalVolume.PhysicalRC _ ok, root: PhysRoot _ NIL, name: Rope.ROPE _ NIL, rest: REF PhysicalObject _ NIL ]; Physical: TYPE = REF PhysicalObject; physicalList: REF PhysicalObject _ NIL; -- known physical volumes -- NextPhysical: PUBLIC PROC[prev: Physical, wait: BOOL] RETURNS[next: Physical] = TRUSTED BEGIN DO lastCh: Disk.Channel; [next, lastCh] _ EntryNextPhysical[prev]; IF next # NIL OR NOT wait THEN EXIT; [] _ Disk.NextChannel[lastCh, TRUE]; ENDLOOP; END; lastChannel: Disk.Channel _ NIL; EntryNextPhysical: ENTRY PROC[prev: Physical] RETURNS[next: Physical, lastCh: Disk.Channel] = { [next, lastCh] _ InnerNextPhysical[prev] }; InnerNextPhysical: INTERNAL PROC[prev: Physical] RETURNS[next: Physical, lastCh: Disk.Channel] = TRUSTED BEGIN next _ IF prev = NIL THEN physicalList ELSE prev.rest; -- check whether found volume has changed state -- WHILE next # NIL AND NOT Disk.Valid[next.channel] DO AssignPhysical[prev, next.rest]; next _ next.rest; ENDLOOP; IF next = NIL THEN BEGIN -- Check for a new channel -- new: Disk.Channel = Disk.NextChannel[lastChannel]; IF new # NIL THEN { AssignPhysical[prev, NewPhysical[new]]; lastChannel _ new }; next _ IF prev = NIL THEN physicalList ELSE prev.rest; END; lastCh _ lastChannel; END; AssignPhysical: INTERNAL PROC[tail: Physical, new: Physical] = { IF tail = NIL THEN physicalList _ new ELSE tail.rest _ new }; NewPhysical: INTERNAL PROC[channel: Disk.Channel] RETURNS[ new: Physical ] = TRUSTED BEGIN status: Disk.Status; new _ NEW[PhysicalObject _ [channel: channel]]; IF NOT Disk.InspectDiskShape[channel, quickReadOnly] THEN ERROR; [status, new.root] _ ReadPhysicalRoot[channel]; new.rootStatus _ FileInternal.TranslateStatus[status]; IF new.rootStatus # ok THEN RETURN; IF new.root.seal # VolumeFormat.PRSeal OR new.root.version # VolumeFormat.PRCurrentVersion THEN { new.rootStatus _ inconsistent; RETURN }; TRUSTED{ new.name _ GetName[@new.root.label, new.root.labelLength] }; FOR s: INT IN [0..new.root.subVolumeCount) DO svd: LONG POINTER TO VolumeFormat.SubVolumeDesc = @new.root.subVolumes[s]; l: REF LogicalObject _ NIL; FOR l _ logicalList, l.rest UNTIL l = NIL DO IF l.id = svd.lvID THEN EXIT REPEAT FINISHED => BEGIN l _ NEW[LogicalObject _ [id: svd.lvID, size: svd.lvSize, rest: NIL]]; IF lastLogical = NIL THEN logicalList _ l ELSE lastLogical.rest _ l; lastLogical _ l; END; ENDLOOP; -- Now, "l" is appropriate LogicalObject -- BEGIN this: PhysicalVolume.SubVolumeDetails = NEW[PhysicalVolume.SubVolumeDetailsObject _ [ id: svd.lvID, start: svd.lvPage, size: svd.nPages, physical: new, channel: channel, address: svd.pvPage] ]; prev: LIST OF PhysicalVolume.SubVolumeDetails _ NIL; FOR sv: LIST OF PhysicalVolume.SubVolumeDetails _ l.subVols, sv.rest UNTIL sv = NIL DO IF NOT Disk.Valid[sv.first.channel] THEN BEGIN -- remove from chain -- IF prev = NIL THEN l.subVols _ sv.rest ELSE prev.rest _ sv.rest; LOOP END; IF sv.first.start > this.start THEN BEGIN -- insert before "sv" -- IF this.start + this.size > sv.first.start THEN { new.rootStatus _ inconsistent; RETURN }; IF prev = NIL THEN l.subVols _ CONS[first: this, rest: sv] ELSE prev.rest _ CONS[first: this, rest: sv]; EXIT END; IF sv.first.start + sv.first.size > this.start THEN { new.rootStatus _ inconsistent; RETURN }; prev _ sv; REPEAT FINISHED => IF prev = NIL THEN l.subVols _ CONS[first: this, rest: NIL] ELSE prev.rest _ CONS[first: this, rest: NIL] ENDLOOP; END; ENDLOOP; 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; PhysicalInfo: PUBLIC PROC[physical: Physical] RETURNS[ channel: Disk.Channel, rootStatus: PhysicalVolume.PhysicalRC, id: File.VolumeID, -- unique id of this physical volume -- name: Rope.ROPE, size: Disk.PageCount, free: Disk.PageCount, timeValid: BOOL, time: VolumeFormat.TimeParameters] = TRUSTED BEGIN size _ Disk.DriveAttributes[physical.channel].nPages; channel _ physical.channel; rootStatus _ physical.rootStatus; IF rootStatus = ok THEN BEGIN lastSV: VolumeFormat.SubVolumeDesc = physical.root.subVolumes[physical.root.subVolumeCount-1]; id _ physical.root.pvID; name _ physical.name; free _ size - (lastSV.pvPage + lastSV.nPages); timeValid _ physical.root.timeParametersValid; time _ physical.root.timeParameters; END; END; WriteTimeParameters: PUBLIC PROC[physical: Physical, timeValid: BOOL, time: VolumeFormat.TimeParameters] RETURNS[ PhysicalVolume.PhysicalRC ] = TRUSTED BEGIN label: Disk.Label _ PhysRootLabel[physical.root.pvID]; status: Disk.Status; physical.root.timeParametersValid _ timeValid; IF timeValid THEN physical.root.timeParameters _ time; status _ AccessPage[physical.channel, VolumeFormat.rootPageNumber, @label, physical.root, write, TRUE]; RETURN[ FileInternal.TranslateStatus[status] ] END; ReadCredentials: PUBLIC UNSAFE PROC[physical: Physical, credentials: PhysicalVolume.Credentials] RETURNS[ PhysicalVolume.PhysicalRC ] = UNCHECKED BEGIN label: Disk.Label _ CredentialsLabel[physical.root.pvID]; status: Disk.Status = AccessPage[physical.channel, VolumeFormat.credentialsPageNumber, @label, credentials, read, TRUE]; RETURN[ FileInternal.TranslateStatus[status] ] END; WriteCredentials: PUBLIC PROC[physical: Physical, credentials: PhysicalVolume.Credentials] RETURNS[ PhysicalVolume.PhysicalRC ] = TRUSTED BEGIN label: Disk.Label _ CredentialsLabel[physical.root.pvID]; status: Disk.Status = AccessPage[physical.channel, VolumeFormat.credentialsPageNumber, @label, credentials, write, TRUE]; RETURN[ FileInternal.TranslateStatus[status] ] END; LogicalObject: TYPE = RECORD[ id: File.VolumeID, size: VolumeFormat.LogicalPageCount, subVols: LIST OF PhysicalVolume.SubVolumeDetails _ NIL, -- ordered list of fragments -- rest: REF LogicalObject ]; Logical: TYPE = REF LogicalObject; logicalList: REF LogicalObject _ NIL; -- known logical volumes -- NextLogical: PROC[prev: Logical, wait: BOOL] RETURNS[next: Logical] = BEGIN DO lastP: Physical; [next, lastP] _ EntryNextLogical[prev]; IF next # NIL OR NOT wait THEN EXIT; [] _ NextPhysical[lastP, TRUE]; ENDLOOP; END; lastPhysical: REF PhysicalObject _ NIL; EntryNextLogical: ENTRY PROC[prev: Logical] RETURNS[next: Logical, lastP: Physical] = { [next, lastP] _ InnerNextLogical[prev] }; InnerNextLogical: INTERNAL PROC[prev: Logical] RETURNS[next: Logical, lastP: Physical] = BEGIN -- check for any more physical volumes -- FOR p: Physical _ InnerNextPhysical[lastPhysical].next, InnerNextPhysical[p].next UNTIL p = NIL DO lastPhysical _ p ENDLOOP; next _ prev; DO wanted: INT _ 0; next _ IF next = NIL THEN logicalList ELSE next.rest; IF next = NIL THEN RETURN[NIL, lastPhysical]; -- check entire volume is known -- FOR sv: LIST OF PhysicalVolume.SubVolumeDetails _ next.subVols, sv.rest UNTIL sv = NIL DO IF wanted # sv.first.start OR NOT Disk.Valid[sv.first.channel] THEN EXIT; wanted _ wanted + sv.first.size; ENDLOOP; IF wanted = next.size THEN EXIT; ENDLOOP; RETURN[next, lastPhysical] END; Valid: INTERNAL PROC[list: LIST OF PhysicalVolume.SubVolumeDetails] RETURNS[BOOL] = TRUSTED BEGIN FOR sv: LIST OF PhysicalVolume.SubVolumeDetails _ list, sv.rest UNTIL sv = NIL DO IF NOT Disk.Valid[sv.first.channel] THEN RETURN[FALSE] ENDLOOP; RETURN[TRUE] END; Volume: TYPE = REF VolumeObject; --File.--VolumeObject: PUBLIC TYPE = FileInternal.VolumeObject; volumeList: Volume _ NIL; -- known volumes -- --File.--NextVolume: PUBLIC PROC[volume: Volume, wait: BOOL _ FALSE] RETURNS[next: Volume] = BEGIN DO lastLog: Logical; [next, lastLog] _ EntryNext[volume]; IF next # NIL OR NOT wait THEN EXIT; [] _ NextLogical[lastLog, TRUE]; ENDLOOP; IF next # NIL AND NOT next.initialised THEN { FileInternal.ReadRootPage[next]; NoteRootRead[next] }; END; lastLogical: Logical _ NIL; rootRead: CONDITION; EntryNext: ENTRY PROC[prev: Volume] RETURNS[next: Volume, lastLog: Logical] = BEGIN next _ IF prev = NIL THEN volumeList ELSE prev.rest; -- check whether next volume has gone offline -- WHILE next # NIL AND NOT Valid[next.subVolumes] DO Assign[prev, next.rest]; next _ next.rest; ENDLOOP; IF next = NIL THEN BEGIN -- Check for a new volume -- new: Logical = InnerNextLogical[lastLogical].next; IF new # NIL THEN { Assign[prev, NewVolume[new]]; lastLogical _ new }; next _ IF prev = NIL THEN volumeList ELSE prev.rest; END ELSE UNTIL next.initialised DO WAIT rootRead ENDLOOP; lastLog _ lastLogical; END; Assign: INTERNAL PROC[tail: Volume, new: Volume] = { IF tail = NIL THEN volumeList _ new ELSE tail.rest _ new }; NoteRootRead: ENTRY PROC[volume: Volume] = { volume.initialised _ TRUE; BROADCAST rootRead }; NewVolume: INTERNAL PROC[logical: Logical] RETURNS[volume: Volume] = BEGIN volume _ NEW[VolumeObject _ [id: logical.id, subVolumes: logical.subVols, size: logical.size]]; END; --PhysicalVolume.--GetPhysical: PUBLIC PROC[volume: Volume, page: VolumeFormat.LogicalPage _ [0]] RETURNS[Physical] = BEGIN FOR sv: LIST OF PhysicalVolume.SubVolumeDetails _ volume.subVolumes, sv.rest UNTIL sv = NIL DO IF sv.first.start <= page AND sv.first.start + sv.first.size > page THEN RETURN[sv.first.physical]; ENDLOOP; RETURN[NIL] END; END. ¢Cedar Nucleus (Files): access to physical and logical volumes PhysicalVolumeImpl.mesa Andrew Birrell July 8, 1983 2:55 pm This implementation currently doesn't really do the right things, particularly about volumes going offline. It will get rewritten sometime. (ADB May 2, 1983 11:48 am). ******** List of known physical volumes ******** I'd prefer using the pvID and a separate file type. For now, compatibility wins (ADB) now enumerate its sub-volumes to see what logical volume they belong to. ******** Accumulation of sub-volumes into logical volumes ******** ******** Top-level: list of File.Volume objects ******** If "next" is newly created, we're responsible for reading its root page. Note that if next.initialised = FALSE, we have exclusive access to next.root (others wait inside EntryNext until we're ready). If next.root # NIL, then it's immutable. So we can read next.root outside the monitor lock. �ʈ˜Jšœ>™>Jšœ™Jšœ$™$J™šÏk ˜ Jšœ�œˆ˜’Jšœ �œ˜Jšœ�œ ˜Jšœ �œ0˜BJšœ ˜ Jšœ�œF˜ZJš�œ�œW˜_Jšœ�œ�œ˜ —J˜šœ�œ�˜!Jš�œ�˜$Jš�œ Ïc œ˜1—J˜Jš�˜J˜Jšœ©™©J˜J˜Jšœ0™0˜Jšœ �œ�œ�œ˜Jšœ �œ�œ�œ˜KJ˜šÏn œ�œ�œ˜.J˜Jšœ�œ�œ�œ ˜"J˜J˜Jšœ �œžœ˜&Jš�œ�˜(Jš�˜Jš�œ�œ�œ�œ/˜RJ˜˜J˜J˜ ˜Jšœ�œ �œ�œ˜,Jšœ�œ�œ�œ˜.—J˜ —Jš�œ �œ/˜?J˜6Jš�œ�œ/˜AJš�œ˜—J˜šŸœ�œ�œ�˜/Jšœ�œ �œ1˜D—J˜šŸœ�œ˜%J˜Jšœ�œ�œ�œ ˜"Jšœ �œ˜Jš�œ*�˜8Jš�˜Jšœ�œ�œ˜;J˜CJš�œ�œ�œ˜8Jš�œ˜—J˜Jš œ �œ�œ�œ�œ˜;J˜Jšž œ�œ�œ˜5J˜Jšž œ �œ�œ˜?J˜šŸ œ�œ�œ�˜DJš�˜š�œ˜ J˜J˜ J˜%Jšœ �œ�œ˜J˜—Jš�œ˜—J˜šŸœ�œ�œ�˜GJš�˜JšœV™Vš œ �œ� œ�œ �œ˜9Jšœ>ž ˜I—š�œ˜ Jšœ�œ˜)J˜ Jšœ�œ˜$Jšœ �œ�œ˜J˜—Jš�œ˜—J˜šŸœ�œ˜-Jš�œ*�˜8Jš�˜Jšœ˜J˜JšœH�œ˜OJšœ�œ˜Jš�œ�œ!˜8Jš�œ4�œ˜>Jš�œ˜—J˜šžœ�œ�œ�œ˜8J˜Jšœ+˜+Jšœ�œ˜Jšœ �œ�œ˜Jšœ�œ�œ˜!—J˜Jšœ �œ�œ˜$J˜Jšœ�œ�œž˜DJ˜šŸ œ�œ�œ�œ˜5Jš�œ�˜!Jš�˜š�œ˜J˜)Jš �œ�œ�œ�œ�œ�œ˜$Jšœ�œ˜$—Jš�œ˜Jš�œ˜—J˜Jšœ�œ˜ J˜šŸœ�œ�œ˜-Jš�œ(˜/J˜-—J˜šŸœ�œ�œ˜0Jš�œ)�˜7Jš�˜Jš œ�œ�œ�œ�œ ˜6Jšž2˜2Jš�œ�œ�œ�œ˜1Jš�œ4�œ˜>Jš�œ�˜ š�œ�˜ Jšž˜J˜2Jš�œ�œ�œ?˜PJš œ�œ�œ�œ�œ ˜6Jš�œ˜—J˜Jš�œ˜—J˜šŸœ�œ�œ!˜>Jš œ�œ�œ�œ�œ˜?—J˜šŸ œ�œ�œ˜1Jš�œ�˜"Jš�˜J˜Jšœ�œ&˜/Jš�œ�œ/�œ�œ˜@J˜/Jšœ6˜6Jš�œ�œ�œ˜#Jš�œ$˜&Jš�œ1˜3Jš�œ"�œ˜/Jš�œ>˜EJšœH™HJš�œ�œ�œ˜*š�œ�œ�œ�œ6˜MJšœ�œ�œ˜Jš�œ�œ�˜)Jš�œ�œ�œ�˜š�œ�œ˜Jš�˜Jšœ�œ8�œ˜EJš�œ�œ�œ�œ˜DJ˜Jš�œ˜—Jš�œ˜Jšž+˜+š�˜šœ(�œ*˜UJ˜ J˜J˜J˜J˜J˜—Jšœ�œ�œ#�œ˜4Jš �œ�œ�œ6�œ�˜Sš�œ�œ�œ˜&š�œ�œž˜"Jš�œ�˜ Jš�œ˜Jš�œ˜Jš�˜Jš�œ˜—Jš�œ˜š�œ�œž˜#Jš�œ(˜*Jš�œ"�œ˜/Jš�œ�˜ Jš�œ �œ˜,Jš�œ �œ˜-Jš�˜Jš�œ˜—Jš�œ,˜.Jš�œ"�œ˜/J˜ —š�œ�œ˜Jš�œ�˜ Jš�œ �œ�œ˜-Jš�œ �œ�œ˜-—Jš�œ˜—Jš�œ˜—Jš�œ˜Jš�œ˜—J˜šŸœ�œ˜Jšœ�œ�œ�œ�œ�œ%�œ�œ˜PJšœ�œ˜ Jš�œ�œ�˜Jš�˜Jš œ�œ�œ�œ�œ ˜#Jš �œ�œ�œ �œ�œ˜8J˜Jš�œ˜Jš�œ˜—J˜šŸ œ�œ�œ�œ˜6J˜Jšœ&˜&Jšœž'˜:Jšœ �œ˜J˜J˜Jšœ �œ˜Jšœ%�˜,Jš�˜J˜5J˜J˜!Jš�œ˜š�œ�˜ šœ$˜$Jšœ9˜9—J˜Jšœ˜Jšœ.˜.Jšœ.˜.Jšœ$˜$Jš�œ˜—Jš�œ˜—J˜š Ÿœ�œ�œ �œ%�œ �˜—Jš�˜Jšœ6˜6Jšœ˜Jšœ.˜.Jš�œ �œ%˜6šœB˜BJšœ�œ˜$—Jš�œ(˜.Jš�œ˜—J˜š Ÿœ�œ�œ�œ>�œ � ˜‘Jš�˜Jšœ9˜9šœV˜VJšœ�œ˜!—Jš�œ(˜.Jš�œ˜—J˜š Ÿœ�œ�œ>�œ �˜‰Jš�˜Jšœ9˜9šœV˜VJšœ�œ˜"—Jš�œ(˜.Jš�œ˜—J˜—J˜JšœB™B˜šœ�œ�œ˜J˜J˜$Jšœ �œ�œ#�œž˜WJšœ�œ˜—J˜Jšœ �œ�œ˜"J˜Jšœ �œ�œž˜AJ˜šŸ œ�œ�œ˜,Jš�œ˜Jš�˜š�œ˜J˜'Jš �œ�œ�œ�œ�œ�œ˜$Jšœ�œ˜—Jš�œ˜Jš�œ˜—J˜Jšœ�œ�œ˜'J˜šŸœ�œ�œ�œ"˜UJšœ+˜+—J˜šŸœ�œ�œ�œ"˜XJš�˜Jšž)˜)Jš�œN˜QJš�œ�˜ Jš�œ�œ˜J˜ š�œ �œ˜šœ�œ�˜Jš�œ ˜Jš�œ ˜—Jš �œ�œ�œ�œ�œ˜-Jšž"˜"Jš �œ�œ�œ9�œ�˜Vš �œ�œ�œ�œ�œ�œ˜LJ˜ —Jš�œ˜Jš�œ�œ�œ˜ —Jš�œ˜Jš�œ˜Jš�œ˜—J˜šŸœ�œ�œ�œ�œ"�œ�œ�˜[Jš�˜Jš �œ�œ�œ1�œ�˜NJš�œ�œ�œ�œ�œ�œ�œ˜BJš�œ�œ˜ Jš�œ˜—J˜—J˜Jšž8™8˜Jšœ�œ�œ˜ J˜Jšž œ�œ�œ˜?J˜Jšœ�œž˜-J˜š ž Ÿ œ�œ�œ�œ�œ˜DJš�œ˜Jš�˜š�œ˜J˜$Jš �œ�œ�œ�œ�œ�œ˜$Jšœ�œ˜ —Jš�œ˜Jšœ¦™¦Jš�œ�œ�œ�œ˜&Jš�œ9˜=Jš�œ˜—J˜Jšœ�œ˜J˜Jšœ � œ˜J˜šŸ œ�œ�œ˜#Jš�œ"˜)Jš�˜Jš œ�œ�œ�œ �œ ˜4Jšž0˜0Jš�œ�œ�œ�œ˜/Jš�œ,�œ˜6Jš�œ�˜ š�œ�˜ Jšž˜Jšœ2˜2Jš�œ�œ�œ5˜FJš œ�œ�œ�œ �œ ˜4Jš�˜—Jš �œ�œ�œ�œ �œ˜5J˜Jš�œ˜—J˜šŸœ�œ�œ˜2Jš œ�œ�œ�œ�œ˜>—J˜šŸ œ�œ�œ˜*Jšœ�œ� œ ˜2—J˜šŸ œ�œ�œ˜*Jš�œ˜Jš�˜šœ˜Jš�œS˜V—Jš�œ˜—J˜š žŸ œ�œ�œ7�œ ˜uJš�˜Jš�œ�œ�œ=˜LJš�œ�˜š�œ�œ�œ&˜FJš�œ�œ˜—Jš�œ˜Jš�œ�œ˜ Jš�œ˜—J˜—Jš�œ˜J˜J˜—…—0’D¼