DIRECTORY Atom USING [GetPName], BasicTime USING [earliestGMT, GMT, Now, Period], Booting USING [RegisterProcs, RollbackProc], FS USING [Error, ErrorDesc, FileType, InfoProc], FSBackdoor USING [ErrorCode, ProduceError, Version], FSPseudoServers USING [AvoidRemoteCheck, TranslateForRead, TranslateForWrite], FSRemoteFile USING [ConfirmProc], FSRemoteFileBackdoor USING [ConfirmProc, ConfirmRetrieveProc, GetServerProc, InfoProc, NameProc, ServerHandle, ServerObject, ServerProcs, ServerProcsObject, SweepProc, ValidateProc], IO USING [STREAM], Process USING [Abort, CheckForAbort, Detach, EnableAborts, MsecToTicks, SetTimeout], Rope USING [Cat, Concat, EqualSubstrs, Find, FindBackward, Flatten, InlineFetch, InlineLength, IsPrefix, Length, ROPE, Substr], SymTab USING [Create, Delete, EachPairAction, Erase, Fetch, Pairs, Ref, Store], UserCredentials USING [CredentialsChangeProc, RegisterForChange] ; FSRemoteFileImpl: CEDAR MONITOR IMPORTS Atom, BasicTime, Booting, FS, FSBackdoor, FSPseudoServers, Process, Rope, SymTab, UserCredentials EXPORTS FSRemoteFile, FSRemoteFileBackdoor ~{ ConfirmProc: TYPE ~ FSRemoteFile.ConfirmProc; ConfirmRetrieveProc: TYPE ~ FSRemoteFileBackdoor.ConfirmRetrieveProc; FileType: TYPE ~ FS.FileType; GetServerProc: TYPE ~ FSRemoteFileBackdoor.GetServerProc; GMT: TYPE ~ BasicTime.GMT; InfoProc: TYPE ~ FSRemoteFileBackdoor.InfoProc; NameProc: TYPE ~ FSRemoteFileBackdoor.NameProc; ROPE: TYPE ~ Rope.ROPE; ServerHandle: TYPE ~ FSRemoteFileBackdoor.ServerHandle; Version: TYPE ~ FSBackdoor.Version; secsBetweenSweeps: INT _ 6; nonexistentServerTTL: CARD _ 60; -- seconds waitForServerTimeout: INT _ 13; -- seconds Delete: PUBLIC PROC [server, file: ROPE, wantedCreatedTime: GMT, proc: FSRemoteFile.ConfirmProc] ~ { pServer, psTranslation, serverPart, pathPart: ROPE; h: ServerHandle; pServer _ UnbracketServer[server]; psTranslation _ FSPseudoServers.TranslateForWrite[pServer]; [serverPart, pathPart] _ ParsePSTranslation[psTranslation]; h _ GetServer[serverPart]; file _ PrependPath[pathPart, InsertDefaultVersion[file, "!l", FALSE]]; h.procs.delete[h, file, wantedCreatedTime, FALSE, proc]; }; EnumerateForInfo: PUBLIC PROC [server, pattern: ROPE, proc: FS.InfoProc] ~ { pServer: ROPE _ UnbracketServer[server]; pServerB: ROPE _ Rope.Cat["[", pServer, "]"]; replicated: BOOL _ FSPseudoServers.AvoidRemoteCheck[pServer]; psTranslations: LIST OF ROPE; gotOne: BOOL _ FALSE; savedErrorDesc: FS.ErrorDesc; h: ServerHandle; serverPart, pathPart: ROPE; ProcInner: FSRemoteFileBackdoor.InfoProc -- [file, bytes, created, type] RETURNS [continue: BOOL] -- ~ { fullFName: ROPE; gotOne _ TRUE; Process.CheckForAbort[]; fullFName _ Rope.Concat[pServerB, StripPath[pathPart, file]]; RETURN [proc[fullFName~fullFName, attachedTo~NIL, created~created, bytes~bytes, keep~0, fileType~type]]; }; psTranslations _ FSPseudoServers.TranslateForRead[pServer]; pattern _ InsertDefaultVersion[pattern, "!*", FALSE]; FOR each: LIST OF ROPE _ psTranslations, each.rest WHILE (each # NIL) DO ENABLE FS.Error => { IF gotOne OR ((error.code = $unknownFile) AND replicated) THEN REJECT; IF each = psTranslations THEN savedErrorDesc _ error; LOOP; }; [serverPart, pathPart] _ ParsePSTranslation[each.first]; h _ GetServer[serverPart]; h.procs.enumerateForInfo[h, PrependPath[pathPart, pattern], FALSE, ProcInner]; IF gotOne OR replicated THEN RETURN; ENDLOOP; IF savedErrorDesc.code # NIL THEN ERROR FS.Error[savedErrorDesc]; }; EnumerateForNames: PUBLIC PROC [server, pattern: ROPE, proc: NameProc] ~ { pServer: ROPE _ UnbracketServer[server]; pServerB: ROPE _ Rope.Cat["[", pServer, "]"]; replicated: BOOL _ FSPseudoServers.AvoidRemoteCheck[pServer]; psTranslations: LIST OF ROPE; gotOne: BOOL _ FALSE; savedErrorDesc: FS.ErrorDesc; h: ServerHandle; serverPart, pathPart: ROPE; ProcInner: FSRemoteFileBackdoor.NameProc -- [file] RETURNS [continue: BOOL] -- ~ { fullFName: ROPE; gotOne _ TRUE; Process.CheckForAbort[]; fullFName _ Rope.Concat[pServerB, StripPath[pathPart, file]]; RETURN [proc[fullFName]]; }; psTranslations _ FSPseudoServers.TranslateForRead[pServer]; pattern _ InsertDefaultVersion[pattern, "!*", FALSE]; FOR each: LIST OF ROPE _ psTranslations, each.rest WHILE (each # NIL) DO ENABLE FS.Error => { IF gotOne OR ((error.code = $unknownFile) AND replicated) THEN REJECT; IF each = psTranslations THEN savedErrorDesc _ error; LOOP; }; [serverPart, pathPart] _ ParsePSTranslation[each.first]; h _ GetServer[serverPart]; h.procs.enumerateForNames[h, PrependPath[pathPart, pattern], FALSE, ProcInner]; IF gotOne OR replicated THEN RETURN; ENDLOOP; IF savedErrorDesc.code # NIL THEN ERROR FS.Error[savedErrorDesc]; }; Info: PUBLIC PROC [server, file: ROPE, wantedCreatedTime: GMT] RETURNS [version: Version, bytes: INT, created: GMT, fileType: FileType] ~ { pServer: ROPE _ UnbracketServer[server]; pServerB: ROPE _ Rope.Cat["[", pServer, "]"]; replicated: BOOL _ FSPseudoServers.AvoidRemoteCheck[pServer]; psTranslations: LIST OF ROPE; savedErrorDesc: FS.ErrorDesc; h: ServerHandle; serverPart, pathPart: ROPE; psTranslations _ FSPseudoServers.TranslateForRead[pServer]; file _ InsertDefaultVersion[file, "!h", FALSE]; FOR each: LIST OF ROPE _ psTranslations, each.rest WHILE (each # NIL) DO ENABLE FS.Error => { IF (error.code = $unknownFile) AND replicated THEN REJECT; IF each = psTranslations THEN savedErrorDesc _ error; LOOP; }; [serverPart, pathPart] _ ParsePSTranslation[each.first]; h _ GetServer[serverPart]; [version, bytes, created, fileType] _ h.procs.getInfo[h, PrependPath[pathPart, file], wantedCreatedTime, FALSE]; RETURN; ENDLOOP; IF savedErrorDesc.code = NIL THEN ERROR; -- can't happen ERROR FS.Error[savedErrorDesc]; }; Rename: PUBLIC PROC [server, fromFile: ROPE, fromCreated: GMT, toFile: ROPE, proc: ConfirmProc] ~ { pServer, psTranslation, serverPart, pathPart: ROPE; h: ServerHandle; pServer _ UnbracketServer[server]; psTranslation _ FSPseudoServers.TranslateForWrite[pServer]; [serverPart, pathPart] _ ParsePSTranslation[psTranslation]; h _ GetServer[serverPart]; fromFile _ PrependPath[pathPart, InsertDefaultVersion[fromFile, "!h", FALSE]]; toFile _ PrependPath[pathPart, InsertDefaultVersion[toFile, "!", TRUE]]; h.procs.rename[h, fromFile, fromCreated, toFile, FALSE, proc]; }; Retrieve: PUBLIC PROC [ server, file: ROPE, wantedCreatedTime: GMT, proc: PROC[fullGName: ROPE, bytes: INT, created: GMT] RETURNS [IO.STREAM], checkFileType: BOOL, fileType: FileType ] ~ { pServer: ROPE _ UnbracketServer[server]; pServerB: ROPE _ Rope.Cat["[", pServer, "]"]; replicated: BOOL _ FSPseudoServers.AvoidRemoteCheck[pServer]; psTranslations: LIST OF ROPE; savedErrorDesc: FS.ErrorDesc; h: ServerHandle; serverPart, pathPart: ROPE; ProcInner: FSRemoteFileBackdoor.ConfirmRetrieveProc -- [file, bytes, created, type] RETURNS [IO.STREAM] -- ~ { fullGName: ROPE; IF checkFileType AND (type # fileType) THEN FSBackdoor.ProduceError[$fileTypeMismatch, "file on server has the wrong file type"]; fullGName _ Rope.Concat[pServerB, StripPath[pathPart, file]]; RETURN[proc[fullGName, bytes, created]]; }; psTranslations _ FSPseudoServers.TranslateForRead[pServer]; file _ InsertDefaultVersion[file, "!h", FALSE]; FOR each: LIST OF ROPE _ psTranslations, each.rest WHILE (each # NIL) DO ENABLE FS.Error => { IF (error.code = $unknownFile) AND replicated THEN REJECT; IF each = psTranslations THEN savedErrorDesc _ error; LOOP; }; [serverPart, pathPart] _ ParsePSTranslation[each.first]; h _ GetServer[serverPart]; h.procs.retrieve[h, PrependPath[pathPart, file], wantedCreatedTime, FALSE, ProcInner]; RETURN; ENDLOOP; IF savedErrorDesc.code = NIL THEN ERROR; -- can't happen ERROR FS.Error[savedErrorDesc]; }; Store: PUBLIC PROC [server, file: ROPE, str: IO.STREAM, created: GMT, proc: FSRemoteFile.ConfirmProc] ~ { pServer, psTranslation, serverPart, pathPart: ROPE; h: ServerHandle; pServer _ UnbracketServer[server]; psTranslation _ FSPseudoServers.TranslateForWrite[pServer]; [serverPart, pathPart] _ ParsePSTranslation[psTranslation]; h _ GetServer[serverPart]; file _ PrependPath[pathPart, InsertDefaultVersion[file, "!", TRUE]]; h.procs.store[h, file, FALSE, str, created, proc]; }; Registration: TYPE ~ REF RegistrationObject; RegistrationObject: TYPE ~ RECORD [ next: Registration, flavor: ATOM, nameSuffix: ROPE, -- constructed from flavor getServer: GetServerProc ]; registrations: Registration _ NIL; Register: PUBLIC ENTRY PROC [flavor: ATOM, getServer: GetServerProc] ~ { p, prev: Registration; nameSuffix: ROPE _ Rope.Concat["-", Atom.GetPName[flavor]]; FOR p _ registrations, p.next WHILE p # NIL DO IF p.flavor = flavor THEN { IF prev = NIL THEN registrations _ p.next ELSE prev.next _ p.next; EXIT; }; prev _ p; ENDLOOP; IF getServer # NIL THEN registrations _ NEW[RegistrationObject _ [registrations, flavor, nameSuffix, getServer]]; }; serverTab: SymTab.Ref _ SymTab.Create[case~FALSE]; lastSweepTime: BasicTime.GMT _ BasicTime.earliestGMT; readyToSweep: CONDITION; Daemon: PROC ~ { seconds: INT; CallSweep: SymTab.EachPairAction -- [key, val] RETURNS [quit] -- ~ { h: ServerHandle ~ NARROW[val]; h.procs.sweep[h, CARD[seconds]]; RETURN [FALSE]; }; WaitReadyToSweep: ENTRY PROC ~ INLINE { DO thisSweepTime: BasicTime.GMT ~ BasicTime.Now[]; seconds _ BasicTime.Period[from~lastSweepTime, to~thisSweepTime]; IF seconds >= secsBetweenSweeps THEN { lastSweepTime _ thisSweepTime; EXIT; }; WAIT readyToSweep; ENDLOOP; }; TRUSTED { Process.EnableAborts[@readyToSweep]; Process.SetTimeout[@readyToSweep, Process.MsecToTicks[INT[1000]*secsBetweenSweeps]]; }; DO WaitReadyToSweep[]; [] _ SymTab.Pairs[serverTab, CallSweep]; ENDLOOP; }; Rollback: ENTRY Booting.RollbackProc ~ { lastSweepTime _ BasicTime.Now[]; SymTab.Erase[serverTab]; }; CredentialsChange: ENTRY UserCredentials.CredentialsChangeProc ~ { lastSweepTime _ BasicTime.Now[]; SymTab.Erase[serverTab]; }; GetServerResponse: TYPE ~ REF GetServerResponseObject; GetServerResponseObject: TYPE ~ RECORD [ nRunning: CARDINAL _ 0, wakeup: CONDITION, handle: ServerHandle _ NIL, downMsg: ROPE _ NIL ]; GetServer: PUBLIC PROC [server: ROPE] RETURNS [h: ServerHandle] ~ { obsolete: BOOL; downMsg: ROPE; kids: LIST OF PROCESS _ NIL; response: GetServerResponse; theRegistration: Registration; started: GMT; MakeChild: ENTRY PROC [r: Registration] RETURNS [p: PROCESS] ~ --INLINE-- { response.nRunning _ response.nRunning + 1; p _ FORK DoGetServer[r, server, response]; }; WaitForResponse: ENTRY PROC ~ --INLINE-- { WHILE (response.nRunning > 0) AND (response.handle = NIL) AND (response.downMsg = NIL) DO sinceStarted: INT ~ BasicTime.Period[from~started, to~BasicTime.Now[]]; IF sinceStarted >= waitForServerTimeout THEN EXIT; TRUSTED { Process.SetTimeout[@response.wakeup, Process.MsecToTicks[1000*(waitForServerTimeout-sinceStarted)+500]] }; WAIT response.wakeup; ENDLOOP; h _ response.handle; downMsg _ response.downMsg; }; h _ NARROW[SymTab.Fetch[serverTab, server].val]; IF h # NIL THEN { [obsolete, downMsg] _ h.procs.validate[h]; IF NOT obsolete THEN { IF downMsg # NIL THEN ErrorDownServer[h, downMsg]; RETURN; }; }; response _ NEW [GetServerResponseObject]; FOR theRegistration _ registrations, theRegistration.next WHILE theRegistration # NIL DO suffixLen, pos: INT; suffixLen _ Rope.Length[theRegistration.nameSuffix]; pos _ Rope.Length[server] - suffixLen; IF (pos >= 0) AND Rope.EqualSubstrs[server, pos, suffixLen, theRegistration.nameSuffix, 0, suffixLen, FALSE] THEN { server _ Rope.Substr[server, 0, pos]; EXIT }; ENDLOOP; IF theRegistration # NIL THEN { DoGetServer[theRegistration, server, response]; h _ response.handle; IF h # NIL THEN h.name _ Rope.Concat[h.name, theRegistration.nameSuffix]; downMsg _ response.downMsg; } ELSE { started _ BasicTime.Now[]; TRUSTED { Process.EnableAborts[@response.wakeup] }; FOR each: Registration _ registrations, each.next WHILE each # NIL DO kids _ CONS [MakeChild[each], kids]; ENDLOOP; WaitForResponse[]; FOR kid: LIST OF PROCESS _ kids, kid.rest WHILE kid # NIL DO TRUSTED { Process.Abort[kid.first] }; TRUSTED { Process.Detach[kid.first] }; ENDLOOP; }; IF h = NIL THEN { IF downMsg = NIL THEN downMsg _ "unknown server"; h _ MakeNonexistentServer[server, downMsg]; }; [] _ SymTab.Store[serverTab, h.name, h]; IF downMsg # NIL THEN ErrorDownServer[h, downMsg]; }; DoGetServer: PROC [r: Registration, server: ROPE, response: GetServerResponse] ~ { h: ServerHandle; downMsg: ROPE; NotifyDone: ENTRY PROC ~ --INLINE-- { response.nRunning _ response.nRunning - 1; IF (response.handle # NIL) OR (response.downMsg # NIL) THEN RETURN; SELECT TRUE FROM (h # NIL) OR (downMsg # NIL) => -- found it -- { response.handle _ h; response.downMsg _ downMsg; NOTIFY response.wakeup; }; (response.nRunning = 0) => -- nobody will find it -- { NOTIFY response.wakeup; }; ENDCASE; }; [h, downMsg] _ r.getServer[server ! FS.Error => CONTINUE]; NotifyDone[]; }; ErrorDownServer: PROC [h: ServerHandle, downMsg: ROPE] ~ { FSBackdoor.ProduceError[FSBackdoor.ErrorCode.unknownServer, Rope.Cat["Server \"[", h.name, "]\" unavailable - ", downMsg]]; }; SetCachedServer: PUBLIC PROC [server: ROPE, flavor: ATOM] ~ { r: Registration; h: ServerHandle; ClearCachedServer[server]; FOR r _ registrations, r.next WHILE (r # NIL) AND (r.flavor # flavor) DO NULL ENDLOOP; IF r = NIL THEN RETURN; [h~h] _ r.getServer[server]; IF h = NIL THEN h _ MakeNonexistentServer[server, "unknown server"]; [] _ SymTab.Store[serverTab, server, h]; }; ClearCachedServer: PUBLIC PROC [server: ROPE] ~ { [] _ SymTab.Delete[serverTab, server]; }; NonexistentServerData: TYPE ~ REF NonexistentServerDataObject; NonexistentServerDataObject: TYPE ~ RECORD [ ttl: CARD _ nonexistentServerTTL, downMsg: ROPE ]; nonexistentServerProcs: FSRemoteFileBackdoor.ServerProcs _ NEW[FSRemoteFileBackdoor.ServerProcsObject _ [ sweep~SweepNonexistentServer, validate~ValidateNonexistentServer, delete~NIL, enumerateForInfo~NIL, enumerateForNames~NIL, getInfo~NIL, rename~NIL, retrieve~NIL, store~NIL, do~NIL ] ]; MakeNonexistentServer: PROC [name, downMsg: ROPE] RETURNS [h: ServerHandle] ~ { d: NonexistentServerData _ NEW[NonexistentServerDataObject _ [downMsg~downMsg]]; h _ NEW[FSRemoteFileBackdoor.ServerObject _ [$nonexistentServer, name, nonexistentServerProcs, d]]; }; SweepNonexistentServer: FSRemoteFileBackdoor.SweepProc -- [h: ServerHandle, seconds: CARD] -- ~ { d: NonexistentServerData _ NARROW[h.data]; IF d.ttl > seconds THEN d.ttl _ d.ttl - seconds ELSE d.ttl _ 0; }; ValidateNonexistentServer: FSRemoteFileBackdoor.ValidateProc -- [h: ServerHandle] RETURNS [obsolete: BOOL, down: BOOL] -- ~ { d: NonexistentServerData _ NARROW[h.data]; RETURN[(d.ttl = 0), d.downMsg]; }; ErrorNotImplemented: PUBLIC PROC [h: ServerHandle, file, msg: ROPE] ~ { r: ROPE _ NIL; IF h # NIL THEN r _ Rope.Cat["[", h.name, "]"]; IF file # NIL THEN r _ Rope.Concat[r, file]; IF (msg # NIL) AND (r # NIL) THEN r _ Rope.Concat[r, ": "]; r _ Rope.Concat[r, msg]; FSBackdoor.ProduceError[FSBackdoor.ErrorCode.notImplemented, r]; }; InsertDefaultVersion: PROC [file, versionPart: ROPE, smash: BOOL _ FALSE] RETURNS [ROPE] ~ { pos: INT _ Rope.FindBackward[file, "!"]; IF pos < 0 THEN RETURN [Rope.Concat[file, versionPart]]; IF smash THEN RETURN [Rope.Concat[Rope.Substr[file, 0, pos], versionPart]]; RETURN [file]; }; ParsePSTranslation: PROC [psTranslation: ROPE] RETURNS [server, path: ROPE] ~ { len, pos: INT; len _ Rope.InlineLength[psTranslation]; IF (len = 0) OR (Rope.InlineFetch[psTranslation, 0] # '[) THEN RETURN [psTranslation, NIL]; pos _ Rope.Find[psTranslation, "]"]; IF pos < 0 THEN RETURN [Rope.Substr[psTranslation, 1], NIL]; server _ Rope.Substr[psTranslation, 1, pos-1]; IF pos < (len-1) THEN path _ Rope.Substr[psTranslation, pos+1]; }; PrependPath: PROC [path, file: ROPE] RETURNS [ROPE] ~ { pathLen: INT _ Rope.InlineLength[path]; fileLen: INT _ Rope.InlineLength[file]; IF pathLen = 0 THEN RETURN [file]; IF fileLen = 0 THEN RETURN [path]; IF Rope.InlineFetch[path, pathLen-1] # '> THEN path _ Rope.Concat[path, ">"]; SELECT TRUE FROM Rope.IsPrefix["<>", file] => RETURN [Rope.Concat[path, Rope.Substr[file, 2]]]; Rope.IsPrefix["<", file] => RETURN [Rope.Concat[path, Rope.Substr[file, 1]]]; ENDCASE => RETURN [Rope.Concat[path, file]]; }; StripPath: PROC [path, file: ROPE] RETURNS [ROPE] ~ { pathLen: INT _ Rope.InlineLength[path]; IF pathLen = 0 THEN RETURN [file]; IF Rope.InlineFetch[path, pathLen-1] # '> THEN { path _ Rope.Concat[path, ">"]; pathLen _ pathLen + 1; }; IF NOT Rope.IsPrefix[path, file, FALSE] THEN ERROR; -- make this less drastic later! IF Rope.Find[file, ">", pathLen] >= 0 THEN RETURN [Rope.Concat["<", Rope.Substr[file, pathLen]]] ELSE RETURN [Rope.Concat["<>", Rope.Substr[file, pathLen]]]; }; UnbracketServer: PROC[server: ROPE] RETURNS [ROPE] ~ INLINE { IF (Rope.InlineLength[server] = 0) OR (Rope.InlineFetch[server, 0] # '[) THEN RETURN [server]; RETURN [ Rope.Flatten[server, 1, Rope.Length[server]-2]] }; Booting.RegisterProcs[r~Rollback]; UserCredentials.RegisterForChange[proc~CredentialsChange]; TRUSTED { Process.Detach[ FORK Daemon[] ] }; }... |FSRemoteFileImpl.mesa Copyright Σ 1987 by Xerox Corporation. All rights reserved. Demers, October 28, 1987 5:02:20 pm PST Types Parameters Exported to FSRemoteFile Should I raise FS.Error if NOT gotOne? Should I raise FS.Error if NOT gotOne? Registration (Exported to FSRemoteFileBackdoor) Server Handle Cache Finding a server (exported to FSRemoteFileBackdoor) Raises FS.Error if server is down. set [h, downMsg] ... Controlling the flavor of a cached server (Exported to FSRemoteFileBackdoor) Nonexistent Server Implementation Unimplemented operations Name manipulation Caller asserts path is a prefix of file. Initialization Κa˜codešœ™K™œ˜FKšœ+œ˜8K˜K˜—šžœœœœ˜LKšœ œ˜(Kšœ œ˜-Icode2šœ œ-˜=Kšœœœœ˜Kšœœœ˜Kšœœ ˜J˜Kšœœ˜šž œ Ÿ;œ˜hKšœ œ˜Kšœ œ˜Kšœ˜Kšœ=˜=Kšœ&œ8˜hK˜—Kšœ;˜;Kšœ.œ˜5š œœœœœ œ˜Hšœœ ˜Kš œœœ œœ˜FKšœœ˜5Kšœ˜Kšœ˜—Jšœ8˜8Jšœ˜Jšœ<œ ˜NJšœœ œœ˜$Kšœ˜—Kš œœœœœ˜AK™&K˜K˜—šžœœœœ˜JKšœ œ˜(Kšœ œ˜-Mšœ œ-˜=Kšœœœœ˜Kšœœœ˜Kšœœ ˜J˜Kšœœ˜šž œ Ÿ%œ˜RKšœ œ˜Kšœ œ˜Kšœ˜Kšœ=˜=Kšœ˜K˜—Kšœ;˜;Kšœ.œ˜5š œœœœœ œ˜Hšœœ ˜Kš œœœ œœ˜FKšœœ˜5Kšœ˜Kšœ˜—Jšœ8˜8Jšœ˜Jšœ=œ ˜OJšœœ œœ˜$Kšœ˜—Kš œœœœœ˜AK™&K˜K˜—š žœœœœœ˜>Kšœœ œ˜LKšœ œ˜(Kšœ œ˜-Mšœ œ-˜=Kšœœœœ˜Kšœœ ˜J˜Kšœœ˜Kšœ;˜;Kšœ(œ˜/š œœœœœ œ˜Hšœœ ˜Kšœœ œœ˜:Kšœœ˜5Kšœ˜Kšœ˜—Jšœ8˜8Jšœ˜Kšœiœ˜pKšœ˜Kšœ˜—Kš œœœœŸ˜8Kšœœ˜K˜K˜—š žœœœœœœ˜cKšœ.œ˜3Kšœ˜Kšœ"˜"Kšœ;˜;Kšœ;˜;Kšœ˜KšœFœ˜NKšœAœ˜HKšœ1œ˜>K˜K˜—šžœœœ˜Kšœœœ˜+Kšœœ œ œ œœœœ˜JKšœœ˜'Kšœ˜Kšœ œ˜(Kšœ œ˜-Mšœ œ-˜=Kšœœœœ˜Kšœœ ˜J˜Kšœœ˜šž œ+Ÿ6œ˜nKšœ œ˜KšœœœV˜Kšœ=˜=Kšœ"˜(K˜—Kšœ;˜;Kšœ(œ˜/š œœœœœ œ˜Hšœœ ˜Kšœœ œœ˜:Kšœœ˜5Kšœ˜Kšœ˜—Jšœ8˜8Jšœ˜KšœDœ ˜VKšœ˜Kšœ˜—Kš œœœœŸ˜8Kšœœ˜K˜K˜—šžœœœœœœ œ%˜iKšœ.œ˜3Kšœ˜Kšœ"˜"Kšœ;˜;Kšœ;˜;Kšœ˜Kšœ=œ˜DKšœœ˜2K˜——šœ/™/Kšœœœ˜,šœœœ˜#Kšœ˜Kšœœ˜ Kšœ œŸ˜,Kšœ˜K˜K˜—Kšœœ˜"K˜š žœœœœ œ˜HKšœ˜Kšœ œ+˜;šœœœ˜.šœœ˜Kšœœœœ˜BKšœ˜K˜—Kšœ ˜ Kšœ˜—šœ ˜KšœœF˜^—K˜——™Kšœ+œ˜2K˜Kšœœ˜5Kšœ œ˜K˜šžœœ˜Kšœ œ˜ šž œŸœ˜DKšœœ˜Kšœœ ˜ Kšœœ˜K˜—šžœœœœ˜'š˜Kšœœ˜/KšœA˜Ašœœ˜&Kšœ˜Kšœ˜K˜—Kšœ˜Kšœ˜—Kšœ˜—K˜šœ˜ Kšœ$˜$Kšœ6œ˜TKšœ˜—š˜K˜Kšœ(˜(Kšœ˜—K˜K˜—šžœœ˜(Kšœ ˜ Kšœ˜K˜K˜—šžœœ*˜BKšœ ˜ Kšœ˜K˜——™3Kšœœœ˜6šœœœ˜(Kšœ œ˜Kšœ œ˜Kšœœ˜Kšœ œ˜K˜K˜—š ž œœœ œœ˜CK™"Kšœ œ˜Kšœ œ˜Kš œœœœœ˜Kšœ˜K˜Kšœ œ˜ K˜šž œœœœœŸŸœ˜KKšœ*˜*Kšœœ"˜*K˜K˜—šžœœœŸ œ˜*K™š œœœœœ˜YKšœœ6˜GJšœ&œœ˜2Kšœm˜tKšœ˜Kšœ˜—K˜Kšœ˜K˜K˜—Kšœœ&˜0šœœœ˜Kšœ*˜*šœœ œ˜Kšœ œœ˜2Kšœ˜K˜—K˜—K˜Kšœ œ˜)šœ7œœ˜XKšœœ˜Kšœ4˜4Kšœ&˜&šœ œUœ˜lKšœ)œ˜4—Kšœ˜—šœ˜šœ˜Kšœ/˜/K˜Kšœœœ:˜IKšœ˜K˜—šœ˜K˜Kšœ,˜3šœ/œœ˜EJšœœ˜$Jšœ˜—K˜š œœœœœœ˜šœœœ˜,Kšœœ˜!Kšœ ˜ K˜K˜—šœ:˜:šœ+˜.Kšœ˜Kšœ#˜#Kšœœ˜ Kšœœ˜Kšœœ˜Kšœœ˜ Kšœœ˜ Kšœ œ˜ Kšœ˜ Kšœ˜Kšœ˜—Kšœ˜K˜—šžœœœœ˜OKšœœ2˜PKšœœ\˜cK˜K˜—šžœ!Ÿ&œ˜aKšœœ ˜*Kšœœœ ˜?K˜K˜—šžœ$Ÿ<œ˜}Kšœœ ˜*Kšœ˜K˜——™šžœœœœ˜GKšœœœ˜Kšœœœ ˜/Kšœœœ˜,Kš œœœœœ˜;K˜Kšœ@˜@K˜——™šžœœœ œœœœ˜\Mšœœ ˜(Mšœ œœ"˜8Mšœœœ7˜KMšœ˜M˜M˜—š žœœœœœ˜OMšœ œ˜Mšœ'˜'Mš œ œ+œœœ˜[Kšœ$˜$Kšœ œœ!œ˜