DIRECTORY Ascii USING [BS, ControlA, ControlQ, ControlW, ControlX, CR, DEL, Digit, Letter, Lower, SP], BasicTime USING[ Now, ToNSTime ], Booting USING[ Switches, switches ], Convert USING [ RopeFromInt ], Disk USING [Channel, DriveAttributes, NextChannel, PageCount, PageNumber], File USING [Error, FindVolumeFromName, GetVolumeName, NextVolume, RC, SystemVolume, Volume, VolumeFile, VolumeID], FileExtra USING [ReserveNoPages, ReservePages], FormatDisk --USING everything--, FS USING[ Error, FileInfo ], GermSwap USING[ Switch ], IagoOps USING[ Command ], IO USING[ EraseChar, GetChar, PutChar, PutF, PutFR, PutRope, STREAM ], PhysicalVolume USING[ NextPhysical, Physical, PhysicalInfo ], ProcessorFace USING[ processorID ], Rope USING [ Cat, Fetch, Find, FromChar, Length, ROPE, Run, Substr ], SystemVersion USING[ machineType ]; IagoOpsImpl: CEDAR PROGRAM IMPORTS Ascii, BasicTime, Booting, Convert, Disk, File, FileExtra, FormatDisk, FS, IO, PhysicalVolume, ProcessorFace, Rope, SystemVersion EXPORTS File--VolumeID--, IagoOps = BEGIN Rubout: PUBLIC ERROR = CODE; GetID: PUBLIC PROC [in, out: IO.STREAM, default: Rope.ROPE, init: Rope.ROPE _ NIL, echo: BOOL _ TRUE] RETURNS [id: Rope.ROPE, c: CHAR] = BEGIN OPEN Ascii; firstTime: BOOLEAN _ init = NIL; EraseAll: PROC = { IF echo THEN FOR i: INT DECREASING IN [0..id.Length[]) DO out.EraseChar[id.Fetch[i]]; ENDLOOP; id _ NIL; }; Done: PROC [c: CHAR] RETURNS [BOOL] = INLINE { IF firstTime THEN { SELECT c FROM ControlA, BS, ControlQ, ControlW, ControlX, CR, SP, DEL => NULL; ENDCASE => EraseAll[]; firstTime _ FALSE; }; RETURN[c = SP OR c = CR OR c = '?] }; id _ Rope.Cat[init, default]; IF echo THEN out.PutRope[default]; c _ in.GetChar[]; UNTIL Done[c] DO SELECT c FROM DEL => ERROR Rubout; ControlA, BS => { len: INT _ id.Length[]; IF len > 0 THEN { len _ len - 1; IF echo THEN out.EraseChar[id.Fetch[len]]; id _ id.Substr[len: len]; }; }; ControlW, ControlQ => { alpha: BOOL _ FALSE; FOR i: INT DECREASING IN [0..id.Length[]) DO ch: CHAR = id.Fetch[i]; IF Ascii.Letter[ch] OR Ascii.Digit[ch] THEN alpha _ TRUE ELSE IF alpha THEN {id _ id.Substr[len: i + 1]; EXIT}; IF echo THEN out.EraseChar[ch]; REPEAT FINISHED => id _ NIL; ENDLOOP; }; ControlX => EraseAll[]; ENDCASE => {id _ id.Cat[Rope.FromChar[c]]; IF echo THEN out.PutChar[c]}; c _ in.GetChar[]; ENDLOOP; END; GetCommand: PUBLIC PROC[in, out: IO.STREAM, diskReadable: BOOL] RETURNS[found: IagoOps.Command] = -- May raise Rubout BEGIN prompt: Rope.ROPE = "\n\n> "; commands: ARRAY IagoOps.Command OF Rope.ROPE _ [ attach: "Attach File", bootLogical: "Boot Logical Volume", checkDrive: "Check Drive", copy: "Copy File", createLogical: "Create Logical Volume", createPhysical: "Create Physical Volume", createUserWorld: "Create User World", createVM: "Create VM Backing File", delete: "Delete Files", describeDrives: "Describe Drives", describeLV: "Describe Logical Volumes", describeMachine: "Describe Machine", describePV: "Describe Physical Volumes", eraseLogical: "Erase Logical Volume", flushCache: "Flush Cache", format: "Format Disk", installBoot: "Install Boot File", installCredentials: "Install Credentials", installGerm: "Install Germ", installInitial: "Install Initial Microcode", installMicrocode: "Install Cedar Microcode", listCache: "List Cache", listFileInfo: "List File Info", listNames: "List Names", login: "Login", quit: "Quit", rename: "Rename", rollbackLogical: "Rollback Logical Volume", scavenge: "Scavenge", setKeep: "Set Keep", setPhysicalBoot: "Set Physical Boot File", setPhysicalGerm: "Set Physical Germ", setPhysicalMicrocode: "Set Physical Microcode", setWDir: "Set Working Directory" ]; BoolFalse: TYPE = BOOL _ FALSE; allowed: PACKED ARRAY IagoOps.Command OF BoolFalse = [ -- if command is allowed with N switch checkDrive: TRUE, createPhysical: TRUE, describeDrives: TRUE, describeMachine: TRUE, describePV: TRUE, format: TRUE, login: TRUE ]; prefix: Rope.ROPE _ NIL; out.PutRope[prompt]; DO end: CHAR; matches: { none, one, many } _ none; uniqueLength: INT; [id: prefix, c: end] _ GetID[in: in, out: out, default: NIL, init: prefix]; uniqueLength _ prefix.Length[]; IF end = '? THEN out.PutChar['?]; FOR c: IagoOps.Command IN IagoOps.Command DO IF (diskReadable OR allowed[c]) AND Rope.Run[s1: commands[c], s2: prefix, case: FALSE] = prefix.Length[] THEN BEGIN IF matches = none THEN BEGIN matches _ one; uniqueLength _ (commands[c]).Length[]; found _ c; IF end = '? THEN out.PutRope[" ... one of:\n"]; END ELSE BEGIN stillMatches: INT = Rope.Run[s1: commands[c], s2: commands[found], case: FALSE]; uniqueLength _ MIN[uniqueLength, stillMatches]; matches _ many; IF end = '? THEN out.PutRope[", "]; END; IF end = '? THEN out.PutRope[commands[c]]; END; ENDLOOP; IF end = '? AND matches # none THEN { out.PutRope[prompt]; out.PutRope[prefix] } ELSE BEGIN IF uniqueLength = prefix.Length[] THEN BEGIN SELECT matches FROM none => out.PutRope[" ... command not found"]; one => EXIT; many => IF uniqueLength # 0 THEN out.PutRope[" ... ambiguous"]; ENDCASE => ERROR; out.PutRope[prompt]; out.PutRope[prefix]; END ELSE BEGIN extra: Rope.ROPE = (commands[found]).Substr[start: prefix.Length[], len: uniqueLength-prefix.Length[]]; out.PutRope[extra]; SELECT matches FROM one => EXIT; many => prefix _ prefix.Cat[extra]; ENDCASE => ERROR; END; END; ENDLOOP; END; GetArg: PUBLIC PROC[in, out: IO.STREAM, prompt, default: Rope.ROPE, help: PROC] RETURNS[value: Rope.ROPE _ NIL] = BEGIN DO end: CHAR; out.PutRope[prompt]; out.PutRope[value]; [id: value, c: end] _ GetID[in: in, out: out, default: default, init: value]; default _ NIL; IF end = '? THEN help[] ELSE EXIT; ENDLOOP; END; Confirm: PUBLIC PROC[in, out: IO.STREAM] RETURNS[BOOL] = BEGIN DO c: CHAR = in.GetChar[]; SELECT c FROM 'y, 'Y, Ascii.CR => { out.PutRope["yes"]; RETURN[TRUE] }; 'n, 'N => { out.PutRope["no"]; RETURN[FALSE] }; Ascii.DEL => ERROR Rubout[]; ENDCASE => out.PutRope["?\nConfirm by typing \"Y\" or CR, deny by typing \"N\", abort by typing DEL: "]; ENDLOOP; END; ConfirmDestruction: PUBLIC PROC[in, out: IO.STREAM, which: Rope.ROPE] RETURNS[BOOL] = BEGIN out.PutF["\nThis operation will destroy all files on %g.\nAre you sure you want to do this? ", [rope[which]] ]; RETURN[Confirm[in, out]] END; GetNumber: PUBLIC PROC[in, out: IO.STREAM, default: INT, max: INT _ LAST[INT], prompt, help: Rope.ROPE] RETURNS[size: INT _ 0] = BEGIN Help: PROC = { out.PutRope[help] }; sizeRope: Rope.ROPE _ Convert.RopeFromInt[default]; DO BEGIN sizeRope _ GetArg[ in: in, out: out, prompt: prompt, default: sizeRope, help: Help]; size _ 0; FOR i: CARDINAL IN CARDINAL[0..sizeRope.Length[]) DO c: CHAR = sizeRope.Fetch[i]; IF c IN ['0..'9] THEN size _ size * 10 + (c-'0) ELSE { out.PutRope[" ... not a number"]; EXIT }; REPEAT FINISHED => IF size <= max THEN EXIT--from outer loop-- ELSE out.PutF[" ... number should be less than %g", [integer[max]] ]; ENDLOOP; END; ENDLOOP; END; GetSize: PUBLIC PROC[in, out: IO.STREAM, default: INT, max: INT _ LAST[INT]] RETURNS[size: INT _ 0] = { RETURN[GetNumber[in, out, default, max, "\nSize in pages: ", "? type the desired number of pages (in decimal)"]] }; lastFile: Rope.ROPE _ NIL; lastPattern: Rope.ROPE _ NIL; lastDest: Rope.ROPE _ NIL; GetFile: PUBLIC PROC[ in, out: IO.STREAM, prompt: Rope.ROPE _ NIL, extension: Rope.ROPE _ NIL, default: Rope.ROPE _ NIL, wDir: Rope.ROPE _ NIL, check: BOOL _ FALSE, pattern: BOOL _ FALSE] RETURNS[name: Rope.ROPE] = BEGIN Help: PROC = BEGIN out.PutRope["? type an FS file name"]; IF pattern THEN out.PutRope[" (or pattern)"]; out.PutRope[" such as \"[]a.b\" or \"[server]c.d\""]; END; name _ SELECT TRUE FROM default # NIL => default, pattern => lastPattern, check => lastFile, ENDCASE => lastDest; DO BEGIN name _ GetArg[ in: in, out: out, prompt: Rope.Cat["\n", prompt, "file name", IF pattern THEN " or pattern: " ELSE ": "], default: name, help: Help]; IF name.Length[] = 0 THEN { Help[]; LOOP }; IF name.Find["."] < 0 AND extension.Length[] > 0 THEN { name _ name.Cat[extension]; out.PutRope[extension] }; IF check OR pattern THEN BEGIN lastPattern _ name; IF lastPattern.Find["*"] < 0 THEN lastFile _ lastPattern; END ELSE lastDest _ name; IF check THEN BEGIN name _ FS.FileInfo[name: name, wDir: wDir ! FS.Error => SELECT error.code FROM $unknownServer, $unknownVolume, $unknownFile, $illegalName, $patternNotAllowed => { out.PutRope[" ... "]; out.PutRope[error.explanation]; LOOP }; ENDCASE => NULL ].fullFName; END; EXIT; END; ENDLOOP; END; GetSwitches: PUBLIC PROC[in, out: IO.STREAM] RETURNS[switches: Booting.Switches] = BEGIN RopeDefaultNil: TYPE = Rope.ROPE _ NIL; switchTable: ARRAY GermSwap.Switch OF RopeDefaultNil _ [ zero: "worry-call debugger as early as possible", one: "call debugger after MesaRuntime initialization", two: "call debugger after VM initialization", three: "call debugger after File initialization", four: "call debugger after finding VM file and swapping in non-initial parts of boot file", five: "call debugger in LoaderDriver after loading but before starting loadees", a: "don't start Ethernet1 drivers", b: "don't start Ethernet2 drivers", c: "don't start Communication package", d: "development version: don't try the Cedar directory in LoaderDriver/Installer", f: "do full booting sequence, even if defaults would imply a rollback", h: "hang in CPU loop with MP code instead of swapping to debugger", i: "enable control-swat as early as possible (instead of waiting until after login)", l: "long (and interactive) installation dialogue", n: "don't touch the disk during initialization (forces entry to Iago)", p: "don't read user profile from disk", r: "rollback if the system volume has a valid checkpoint", s: "use software safe storage ops instead of microcode", t: "force teledebugging instead of disk world-swap", v: "perform VM copying for checkpoint (internal use only!)", w: "for world-swap debugger, assume debuggee outload file is valid/interesting" ]; value: Rope.ROPE _ NIL; DO end: CHAR; errors: BOOL _ FALSE; FromChar: PROC[c: CHAR] = BEGIN SELECT TRUE FROM Ascii.Letter[c] => switches[VAL[GermSwap.Switch.a.ORD+(Ascii.Lower[c]-'a)]] _ TRUE; Ascii.Digit[c] => switches[VAL[GermSwap.Switch.zero.ORD+(c-'0)]] _ TRUE; ENDCASE => BEGIN out.PutRope[IF errors THEN ", " ELSE "\nUnrecognized switches: "]; out.PutChar[c]; errors _ TRUE; END; END; ToChar: PROC [sw: GermSwap.Switch] RETURNS [CHAR] = BEGIN IF sw IN [zero..nine] THEN RETURN ['0+sw.ORD] ELSE RETURN['A + (sw.ORD - GermSwap.Switch.a.ORD)] END; out.PutRope["\nSwitches: "]; out.PutRope[value]; [id: value, c: end] _ GetID[in: in, out: out, default: NIL, init: value]; IF end = '? THEN out.PutChar[end]; switches _ ALL[FALSE]; FOR i: INT IN [0..value.Length[]) DO FromChar[value.Fetch[i]] ENDLOOP; IF end = '? THEN BEGIN all: BOOL = value.Length[] = 0; IF all THEN out.PutRope[" Type one or more boot switches."]; out.PutRope["\nThe boot switches have the following meanings:"]; FOR sw: GermSwap.Switch IN GermSwap.Switch DO IF (all AND switchTable[sw] # NIL) OR switches[sw] THEN BEGIN out.PutRope["\n "]; out.PutChar[ToChar[sw]]; out.PutRope[": "]; out.PutRope[IF switchTable[sw] = NIL THEN "I don't know any meaning for this switch" ELSE switchTable[sw]]; END; ENDLOOP; END ELSE { IF NOT errors THEN EXIT }; ENDLOOP; END; clientVolName: PUBLIC Rope.ROPE _ IF File.SystemVolume[] = NIL THEN "Cedar" ELSE File.GetVolumeName[File.SystemVolume[]]; GetLogical: PUBLIC PROC[in, out: IO.STREAM, direction: Rope.ROPE _ NIL] RETURNS[v: File.Volume] = BEGIN Help: PROC = BEGIN first: BOOL _ TRUE; out.PutRope["? one of:\n"]; FOR v: File.Volume _ File.NextVolume[NIL], File.NextVolume[v] UNTIL v = NIL DO IF first THEN first _ FALSE ELSE out.PutRope[", "]; out.PutRope[File.GetVolumeName[v ! File.Error => CONTINUE]]; ENDLOOP; END; name: Rope.ROPE _ clientVolName; DO name _ GetArg[ in: in, out: out, prompt: Rope.Cat["\n", direction, "logical volume name: "], default: name, help: Help]; v _ File.FindVolumeFromName[name]; IF v # NIL THEN EXIT; out.PutRope[" ... that volume doesn't exist"]; ENDLOOP; clientVolName _ name; END; GetPhysical: PUBLIC PROC[in, out: IO.STREAM] RETURNS[p: PhysicalVolume.Physical] = BEGIN p _ PhysicalVolume.NextPhysical[NIL]; IF p # NIL THEN out.PutF["\nOn physical volume \"%g\"", [rope[PhysicalVolume.PhysicalInfo[p].name]] ]; END; GetDrive: PUBLIC PROC[in, out: IO.STREAM] RETURNS[d: Disk.Channel] = BEGIN d _ Disk.NextChannel[NIL]; IF d # NIL THEN out.PutF["\nOn drive RD%g", [integer[Disk.DriveAttributes[d].ordinal]] ]; END; FileError: PUBLIC PROC[why: File.RC] RETURNS[Rope.ROPE] = BEGIN RETURN[SELECT why FROM wentOffline => "volume is offline", nonCedarVolume => "can't do that to a non-Cedar volume", inconsistent => "the volume root page seems to be inconsistent", software => "label-check: consult an expert", hardware => "hard disk error: consult an expert", unknownFile => "the file has been deleted", unknownPage => "accessing beyond the end of the file", volumeFull => "volume full", fragmented => "the volume is too fragmented", mixedDevices => "the file (or part of it) is on the wrong disk", ENDCASE => "unknown File.Error: consult an expert"] END; reserved: PhysicalVolume.Physical _ NIL; interacted: BOOL _ FALSE; -- whether we've asked the user to pronounce on Alto regions altoRegions: INT; ReservePages: PUBLIC PROC[in, out: IO.STREAM, p: PhysicalVolume.Physical] = BEGIN IF Disk.DriveAttributes[PhysicalVolume.PhysicalInfo[p].channel].ordinal # 0 OR p = reserved THEN RETURN; [] _ CheckAltoRegions[in, out]; FileExtra.ReserveNoPages[p]; FileExtra.ReservePages[ physical: p, start: FormatDisk.hardUCodeStart, size: FormatDisk.hardUCodeSize]; FileExtra.ReservePages[ physical: p, start: IF FormatDisk.altoRegionsBottomUp THEN FormatDisk.altoRegionsStart ELSE [FormatDisk.altoRegionsStart + FormatDisk.altoRegionsSize * (FormatDisk.altoRegionsMax - altoRegions)], size: FormatDisk.altoRegionsSize * altoRegions]; reserved _ p; END; CheckAltoRegions: PUBLIC PROC[in, out: IO.STREAM] RETURNS[BOOL] = BEGIN IF NOT interacted THEN ReserveAltoRegions[in, out]; RETURN[ altoRegions#0 ] END; ReserveAltoRegions: PUBLIC PROC[in, out: IO.STREAM] = BEGIN reserved _ NIL; interacted _ FALSE; altoRegions _ GetNumber[ in: in, out: out, default: 0, max: FormatDisk.altoRegionsMax, prompt: Rope.Cat["\nHow many Alto ", FormatDisk.altoRegionJargon, " do you want to have on drive RD0? "], help: IO.PutFR["? type the desired number of %g, in the range [ 0 .. %g )", [rope[FormatDisk.altoRegionJargon]], [integer[FormatDisk.altoRegionsMax]] ] ]; interacted _ TRUE; END; NextRun: PUBLIC PROC[d: Disk.Channel, origin: Disk.PageNumber] RETURNS[firstPage: Disk.PageNumber, pageCount: Disk.PageCount-- -1 at end of disk --] = BEGIN altoCount: Disk.PageCount = FormatDisk.altoRegionsSize * altoRegions; altoStart: Disk.PageNumber = IF FormatDisk.altoRegionsBottomUp THEN FormatDisk.altoRegionsStart ELSE [FormatDisk.altoRegionsStart + FormatDisk.altoRegionsSize * (FormatDisk.altoRegionsMax - altoRegions)]; diskCount: Disk.PageCount = Disk.DriveAttributes[d].nPages; IF origin >= diskCount THEN RETURN[origin, -1]; IF Disk.DriveAttributes[d].ordinal # 0 THEN RETURN[origin, diskCount-origin]; firstPage _ origin; IF firstPage >= altoStart AND firstPage <= altoStart + altoCount THEN firstPage _ [altoStart + altoCount]; pageCount _ diskCount-firstPage; IF firstPage < altoStart AND firstPage+pageCount > altoStart THEN pageCount _ altoStart-firstPage; END; --File.--VolumeID: PUBLIC TYPE = MACHINE DEPENDENT RECORD[ a,b,c: CARDINAL, t: LONG CARDINAL ]; NewID: PUBLIC PROC RETURNS[new: VolumeID] = BEGIN id: RECORD[a,b,c: CARDINAL] = LOOPHOLE[ProcessorFace.processorID]; new.a _ id.a; new.b _ id.b; new.c _ id.c; new.t _ BasicTime.ToNSTime[BasicTime.Now[]]; END; PutID: PUBLIC PROC[out: IO.STREAM, id: VolumeID] = BEGIN out.PutF["[%b,%b,%b,%b]", [cardinal[id.a]], [cardinal[id.b]], [cardinal[id.c]], [cardinal[id.t]] ]; END; InitialMicrocodeFileName: PUBLIC PROC RETURNS[Rope.ROPE] = BEGIN RETURN[ Rope.Cat["[Indigo]<", IF Booting.switches[d] THEN "PreCedar" ELSE "Cedar", ">Top>", SELECT SystemVersion.machineType FROM dolphin => "InitialPilot", dorado => "InitialEtherCedarDorado", dicentra => "InitialEtherCedarDicentra", ENDCASE => "SomethingButIDontKnowWhat", ".eb"] ] END; RemoteRootFileName: PUBLIC PROC[which: File.VolumeFile[checkpoint..bootFile]] RETURNS[Rope.ROPE] = BEGIN machine: Rope.ROPE = SELECT SystemVersion.machineType FROM dolphin => "D0", dorado => "Dorado", dandelion => "DLion", dicentra => "Dicentra", ENDCASE => NIL; dir: Rope.ROPE = IF Booting.switches[d] THEN "[Indigo]Top>" ELSE "[Indigo]Top>"; RETURN[SELECT which FROM checkpoint => NIL, microcode => Rope.Cat[dir, "Cedar", machine, ext[which]], germ => Rope.Cat[dir, machine, ext[which]], bootFile => Rope.Cat[dir, IF Booting.switches[d] THEN "BasicCedar" ELSE "Cedar", machine, ext[which]], ENDCASE => ERROR] END; LocalRootFileName: PUBLIC PROC[which: File.VolumeFile] RETURNS[Rope.ROPE] = BEGIN RETURN[Rope.Cat[ SELECT which FROM checkpoint => "Checkpoint", microcode => "Microcode", germ => "Germ", bootFile => "BootFile", debugger => "Debugger", debuggee => "Debuggee", VM => "VM", VAM => "VAM", client => "FS", alpine => "Alpine", ENDCASE => ERROR, ".DontDeleteMe"] ] END; ext: PUBLIC ARRAY File.VolumeFile[checkpoint..bootFile] OF Rope.ROPE _ [ checkpoint: ".outload", microcode: ".eb", germ: ".germ", bootFile: ".boot"]; TRUSTED{ FormatDisk.Start[] }; END.  Cedar: Iago user interface subroutines IagoOpsImpl.mesa Andrew Birrell December 7, 1983 10:51 am Last Edited by: Levin, September 22, 1983 1:32 pm ******** Input subroutines ******** text to be backed up is of the form ..., the and following are to be removed. Defaults for "GetFile": First find a starting point outside reserved areas IF firstPage >= FormatDisk.hardUCodeStart AND firstPage <= FormatDisk.hardUCodeStart + FormatDisk.hardUCodeSize THEN firstPage _ [FormatDisk.hardUCodeStart + FormatDisk.hardUCodeSize]; Then find how many pages we can manage IF firstPage < FormatDisk.hardUCodeStart AND firstPage+pageCount > FormatDisk.hardUCodeStart THEN pageCount _ FormatDisk.hardUCodeStart-firstPage; ʼ˜Jšœ'™'Jšœ™Jšœ)™)J™1J™šÏk ˜ Jš œœœ*œœœ˜\Jšœ œ˜!Jšœœ˜$Jšœœ˜Jšœœ@˜JJšœœ8œ.˜rJšœ œ ˜/Jšœ Ïcœ˜ Jšœœ˜Jšœ œ ˜Jšœœ ˜Jšœœ5œ˜FJšœœ)˜=Jšœœ˜#Jšœœ'œ˜EJšœœ˜#—J˜šœ œ˜JšœFœœ4˜‰Jšœž œ ˜#—J˜Jš˜˜J˜—Jšœ1™1˜Jšœ œœ˜J˜šÏnœœœ œœœ œœœœœ œœ˜ˆJš˜Jšœ˜ Jšœ œ œ˜ šŸœœ˜šœ˜ Jš œœ œœœœ˜Q—Jšœœ˜ J˜—š Ÿœœœœœœ˜.šœ œ˜šœ˜ Jš œ œ œœœœ˜@Jšœ˜—Jšœ œ˜Jšœ˜—Jš œœœœœ˜"J˜—J˜Jšœœ˜"Jšœ˜šœ ˜šœ˜ Jšœœ˜šœ œ˜Jšœœ˜šœ œ˜J˜Jšœœ˜*Jšœ˜Jšœ˜—J˜—šœ˜Jšœ~™~Jšœœœ˜š œœ œœ˜,Jšœœ˜Jšœœœ ˜8Jšœœœœ˜6Jšœœ˜š˜Jšœ œ˜—Jšœ˜—J˜—Jšœ˜Jšœ$œœ˜H—J˜Jšœ˜—Jšœ˜—J˜šŸ œœœ œœœœž˜uJš˜Jšœ œ ˜šœ œœœ˜0J˜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˜ J˜J˜+J˜J˜J˜*J˜%J˜/J˜ J˜—Jšœ œœœ˜šœ œœœž&˜]Jšœ œ˜Jšœœ˜Jšœœ˜Jšœœ˜Jšœ œ˜Jšœœ˜ Jšœ˜ J˜—Jšœ œœ˜J˜šœœ˜ J˜$Jšœœ˜Jšœ8œ˜KJšœ˜Jšœ œ˜!Jšœœ˜)šœœœ ˜"Jšœ-œ˜Hšœ˜ Jšœ˜šœ˜ Jšœ@˜@Jšœ œ˜/Jš˜—šœ˜ Jšœœ8œ˜PJšœœ˜/Jšœ˜Jšœ œ˜#Jšœ˜—Jšœ ˜ Jšœ˜Jšœ˜——Jšœ˜Jšœ œ˜Jšœ-˜1šœ˜ Jšœ˜!šœ˜ šœ ˜J˜.Jšœœ˜ Jšœœœ˜?—Jšœœ˜J˜)Jš˜—šœ˜ šœ œ˜JšœT˜T—J˜šœ ˜Jšœœ˜ Jšœ#˜#—Jšœœ˜Jšœ˜—Jšœ˜——Jšœ˜Jšœ˜—J˜šŸœœœ œœœœœ œœ˜qJš˜šœœ˜ J˜J˜J˜MJšœ œ˜Jšœ œœœ˜"—Jšœ˜Jšœ˜—J˜šŸœœœ œœœœ˜8Jš˜šœœ˜šœ˜ Jšœœœœ˜9Jšœœœ˜/Jšœœœ ˜—šœ˜ Jšœ]˜]——Jšœ˜Jšœ˜—J˜šŸœœœ œœœœœ˜UJš˜šœ^˜^Jšœ˜—Jšœ˜Jšœ˜—J˜šŸ œœœ œœ œœœœœœœ˜€Jš˜šŸœœ˜ Jšœ˜—Jšœœ ˜3šœ˜šœ˜J˜J˜ Jšœ˜Jšœ˜J˜ —Jšœ ˜ Jšœœœœ˜1šœœ˜Jšœœ ˜Jšœ˜Jšœ%œ˜0—šœœ˜Jšœ ˜Jšœž˜JšœA˜E—Jšœ˜Jšœ˜—Jšœ˜Jšœ˜—J˜šŸœœœ œœ œœœœœœ˜eJšœœm˜u—J˜šœ™Jšœœœ˜Jšœœœ˜Jšœœœ˜—J˜šŸœœœ˜Jšœ œœ˜Jšœ œœ˜Jšœœœ˜Jšœœœ˜Jšœ œœ˜Jšœœœ˜Jšœ œœ˜Jšœ œ˜Jš˜šŸœœ˜ Jš˜Jšœ&˜&Jšœ œ˜-JšœH˜HJšœ˜—šœœœ˜Jšœ œ ˜Jšœ˜Jšœ˜Jšœ ˜—šœ˜šœ˜J˜J˜ Jšœ,œ œœ˜WJšœ˜J˜ —Jšœœ œ˜+Jšœœ˜0Jšœ8˜Jšœ˜ Jšœh˜l—Jšœ;˜;Jšœœœ ˜/Jšœ%œœ˜MJšœ2™2Jšœ˜Jšœ˜Jšœ#˜&Jšœ%˜)Jšœ)™)JšœE™EJšœH™HJšœ&™&Jšœ ˜ Jšœ˜Jšœ ˜#Jšœ!˜%Jšœ(™(Jšœ3™3Jšœ5™5Jšœ˜—J˜š ž œ œœœ œœ˜:Jšœœ˜Jšœœ˜J˜—J˜šŸœœœœ˜+Jš˜Jšœœœœ˜BJšœ ˜ Jšœ ˜ Jšœ ˜ J˜,Jšœ˜—J˜š Ÿœœœœœ˜2Jš˜˜J˜J˜J˜J˜J˜—Jšœ˜—J˜š Ÿœœœœœ˜:Jš˜šœ˜šœ˜Jšœœ œ ˜4Jšœ˜šœ˜%J˜J˜$J˜(—Jšœ ˜'J˜—J˜—Jšœ˜—J˜š Ÿœœœ/œœ˜bJš˜šœœœ˜:J˜J˜J˜J˜Jšœœ˜—šœ œ˜Jšœœœ˜P—šœœ˜Jšœœ˜Jšœ9˜9Jšœ+˜+Jšœœœœ˜fJšœœ˜—Jšœ˜—J˜š Ÿœœœœœ˜KJš˜šœ ˜šœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ˜J˜J˜Jšœ ˜ Jšœ ˜ J˜J˜—Jšœœ˜$—Jšœ˜—J˜š œœœ'œœ˜HJšœ˜Jšœ˜Jšœ˜Jšœ˜—J˜Jšœ˜J˜—Jšœ˜—…—D]Þ