DIRECTORY Atom, BasicTime USING[FromNSTime, GMT, nullGMT, OutOfRange, ToNSTime], CCTypes USING[CCError, CCErrorCase], CirioDeltaFace USING[DebuggeeNameToDebuggerName, TargetVersionMapName], Commander, Convert, MobDefs, MorePfsNames, IO, PFS, PFSNames, Rope, RopeParts, RopeSequence, SimpleFeedback USING[PutF], SourceFileOpsExtras USING [FullOpenSource, Position], SymTab, SystemInterface USING[CirioFileInfo], VersionMap, VersionMap2, VersionMap2Binding; SystemInterfaceImpl: CEDAR MONITOR LOCKS set USING set: FileSet IMPORTS Atom, BasicTime, CCTypes, CirioDeltaFace, Commander, Convert, IO, MorePfsNames, PFS, PFSNames, Rope, RopeParts, RopeSequence, SimpleFeedback, SourceFileOpsExtras, SymTab, VersionMap2, VersionMap2Binding EXPORTS SystemInterface = BEGIN OPEN IO, MPN:MorePfsNames, Rope, RP:RopeParts, RS:RopeSequence, VM2:VersionMap2, VM2Binding:VersionMap2Binding; CCE: ERROR[case: CCTypes.CCErrorCase, msg: Rope.ROPE _ NIL] _ CCTypes.CCError; PATH: TYPE ~ PFSNames.PATH; PathList: TYPE ~ LIST OF PATH; Component: TYPE ~ PFSNames.Component; ROPE: TYPE ~ Rope.ROPE; RopeList: TYPE ~ LIST OF ROPE; RopePart: TYPE ~ RP.RopePart; RopeSeq: TYPE ~ RS.RopeSeq; ShowReport: PUBLIC SIGNAL[msgText: ROPE, priority: ATOM] = CODE; ShowSource: PUBLIC PROC [desc: ROPE, pos: SourceFileOpsExtras.Position, feedBack: STREAM _ NIL] = BEGIN SourceFileOpsExtras.FullOpenSource[desc, pos, feedBack]; END; DirectorySeparatorChar: CHAR = '>; DirectorySeparatorString: ROPE = ">"; CExtensionsList: LIST OF ROPE _ LIST[".c2c.c", ".c"]; FileSet: TYPE = REF FileSetBody; FileSetBody: PUBLIC TYPE = MONITORED RECORD[ files: LIST OF CirioFile, nOpenStreams: CARD, openInactiveStreams: LIST OF REF FileStreamInfo, lastOpenInactiveStream: LIST OF REF FileStreamInfo]; CirioFile: TYPE = REF CirioFileBody; CirioFileBody: PUBLIC TYPE = RECORD[ fileSet: FileSet, name: PATH, infoValid: BOOLEAN, info: SystemInterface.CirioFileInfo, streams: LIST OF REF FileStreamInfo]; FileStreamInfo: TYPE = RECORD[ file: CirioFile, active: BOOLEAN, stream: IO.STREAM, openFile: PFS.OpenFile, fileInfo: SystemInterface.CirioFileInfo]; NominalMaxOpenStreams: CARD _ 10; CreateFileSet: PUBLIC PROC RETURNS[FileSet] = {RETURN[NEW[FileSetBody_[ , NIL, 0, NIL, NIL]]]}; CloseFileSet: PUBLIC ENTRY PROC[set: FileSet] = BEGIN ENABLE UNWIND => NULL; FOR fl: LIST OF CirioFile _ set.files, fl.rest WHILE fl # NIL DO FOR sl: LIST OF REF FileStreamInfo _ fl.first.streams, sl.rest WHILE sl # NIL DO IF sl.first.stream # NIL THEN {IO.Close[sl.first.stream]; sl.first.stream _ NIL}; ENDLOOP; ENDLOOP; END; GetCirioFileFromDebuggee: PUBLIC PROC[set: FileSet, name: PATH, debuggee: Rope.ROPE, uniqueID: PFS.UniqueID _ PFS.nullUniqueID] RETURNS[cf: CirioFile] ~ { localName: PATH ~ DebuggeeUnixNameToDebuggerCedarName[name, debuggee]; cf _ GetCirioFile[set, localName, uniqueID]; IF cf=NIL THEN ShowReport[IO.PutFR["Local name (%g) for remote name (%g, on %g, created %g) doesn't work", [rope[PFS.RopeFromPath[localName]]], [rope[PFS.RopeFromPath[name]]], [rope[IF debuggee#NIL THEN debuggee ELSE "self"]], [rope[FmtUniqueID[uniqueID]]] ], $urgent]; RETURN}; GetCirioFile: PUBLIC ENTRY PROC[set: FileSet, name: PATH, uniqueID: PFS.UniqueID _ PFS.nullUniqueID] RETURNS[CirioFile] = { ENABLE UNWIND => NULL; RETURN InnerGetCirioFile[set, name, uniqueID]}; InnerGetCirioFile: INTERNAL PROC[set: FileSet, name: PATH, uniqueID: PFS.UniqueID] RETURNS[CirioFile] = { noDate: BOOL ~ uniqueID=PFS.nullUniqueID; FOR fl: LIST OF CirioFile _ set.files, fl.rest WHILE fl # NIL DO IF name.Equal[fl.first.name] AND (noDate OR uniqueID=fl.first.info.uniqueID) THEN RETURN[fl.first]; ENDLOOP; { fileToOpen: PATH _ name; file: CirioFile _ NEW[CirioFileBody_[set, fileToOpen, TRUE, [uniqueID, 0, fileToOpen], NIL]]; [bytes: file.info.bytes, uniqueID: file.info.uniqueID] _ PFS.FileInfo[file.name, uniqueID !PFS.Error => { asRope: ROPE ~ PFS.RopeFromPath[file.name]; GOTO DontExist}]; set.files _ CONS[file, set.files]; RETURN[file]; EXITS DontExist => RETURN [NIL]; }; }; GenCirioFilesForInfo: PUBLIC PROC[set: FileSet, pattern: PATH, for: PROC[SystemInterface.CirioFileInfo] RETURNS[thisIsIt: BOOLEAN]] RETURNS[found: CirioFile] = BEGIN ENABLE UNWIND => NULL; result: CirioFile _ NIL; local: PROC [ fullFName, attachedTo: PATH, uniqueID: PFS.UniqueID, bytes: INT, mutability: PFS.Mutability, fileType: PFS.FileType ] RETURNS [continue: BOOL] = BEGIN stop: BOOLEAN _ for[[uniqueID, bytes, fullFName]]; IF stop THEN result _ GetCirioFile[set, fullFName, uniqueID]; RETURN[NOT stop]; END; PFS.EnumerateForInfo[pattern, local ! PFS.Error => CONTINUE]; RETURN[result]; END; warnOnVmPfsErr: BOOL _ FALSE; verboseGenVm: BOOL _ FALSE; GenVersionMapFilesForInfo: PUBLIC PROC [set: FileSet, map: ATOM, versionStamp: MobDefs.VersionStamp, stems: PathList, suffixes: RopeList, testName: BOOL, for: PROC[SystemInterface.CirioFileInfo] RETURNS[thisIsIt: BOOLEAN], wantedUniqueID: PFS.UniqueID _ PFS.nullUniqueID] RETURNS[found: CirioFile _ NIL] = { targetMap: ATOM ~ CirioDeltaFace.TargetVersionMapName[map]; vmap: VM2.Map ~ VM2Binding.GetMap[Atom.GetPName[targetMap]]; noDate: BOOL _ wantedUniqueID=PFS.nullUniqueID; GenerateNames: PROC [Test: PROC [ROPE] RETURNS [BOOL]] RETURNS [BOOL] ~ { FOR steml: PathList _ stems, steml.rest WHILE steml#NIL DO FOR sufl: RopeList _ suffixes, sufl.rest WHILE sufl#NIL DO IF Test[ Rope.Concat[ PFS.RopeFromPath[steml.first], sufl.first] ] THEN RETURN [TRUE]; ENDLOOP; ENDLOOP; RETURN [FALSE]}; TryName: PROC [short: ROPE] RETURNS [BOOL] ~ { triedDirs: SymTab.Ref ~ SymTab.Create[case: TRUE]; TryTuple: PROC [t: VM2.VersionTuple] RETURNS [BOOL] ~ { fullRope: ROPE ~ PFS.RopeFromPath[t.name]; foundStamp: VersionMap.VersionStamp ~ t.stamp; uniqueID: PFS.UniqueID _ t.created; full: PFSNames.PATH _ t.name; directory, verless: PFSNames.PATH; IF verboseGenVm THEN ShowReport[IO.PutFR["\tvm: %g\n", [rope[fullRope]] ], $urgent]; directory _ full.Directory; verless _ full.StripVersionNumber; IF noDate THEN { {bytes: INT; [fullFName: full, bytes: bytes, uniqueID: uniqueID] _ PFS.FileInfo[full, wantedUniqueID !PFS.Error => { IF warnOnVmPfsErr THEN ShowReport[IO.PutFR["PFS.FileInfo[%g (from version map)] => PFS.Error[%g, %g]", [rope[fullRope]], [atom[error.code]], [rope[error.explanation]] ], $urgent]; uniqueID _ PFS.nullUniqueID; GOTO NotMe}]; IF for[[uniqueID, bytes, full]] THEN found _ GetCirioFile[set, full, uniqueID]; EXITS NotMe => found _ NIL}; IF found=NIL AND triedDirs.Insert[PFS.RopeFromPath[directory], $T] THEN { highestFull: PATH; highestCreated: PFS.UniqueID; FilteringFor: PROC[cfi: SystemInterface.CirioFileInfo] RETURNS[thisIsIt: BOOLEAN] ~ { IF cfi.uniqueID=highestCreated AND cfi.fullName.Equal[highestFull, FALSE] THEN RETURN [FALSE]; RETURN for[cfi]}; {bytes: INT; [fullFName: highestFull, bytes: bytes, uniqueID: highestCreated] _ PFS.FileInfo[verless !PFS.Error => { IF warnOnVmPfsErr THEN ShowReport[IO.PutFR["PFS.FileInfo[%g (from version map)] => PFS.Error[%g, %g]", [rope[PFS.RopeFromPath[verless]]], [atom[error.code]], [rope[error.explanation]] ], $urgent]; GOTO NotHere}]; IF highestCreated#uniqueID AND for[[highestCreated, bytes, highestFull]] THEN found _ GetCirioFile[set, highestFull, highestCreated]; EXITS NotHere => short _ short}; IF found=NIL THEN found _ GenCirioFilesForInfo[set, verless.SetVersionNumber[[PFSNames.VersionKind.all, 0]], FilteringFor]}; } ELSE {bytes: INT; [fullFName: full, bytes: bytes, uniqueID: uniqueID] _ PFS.FileInfo[verless, wantedUniqueID !PFS.Error => { IF warnOnVmPfsErr THEN ShowReport[IO.PutFR["PFS.FileInfo[%g (from version map), %g] => PFS.Error[%g, %g]", [rope[PFS.RopeFromPath[verless]]], IF wantedUniqueID.egmt.time=BasicTime.nullGMT THEN [rope["nullGMT"]] ELSE [time[wantedUniqueID.egmt.time]], [atom[error.code]], [rope[error.explanation]] ], $urgent]; GOTO None}]; IF for[[uniqueID, bytes, full]] THEN found _ GetCirioFile[set, full, uniqueID]; EXITS None => NULL}; RETURN [found#NIL]}; pat: VM2.VersionTuple ~ [VM2.ShortNameToPattern[short].name, wantedUniqueID, versionStamp]; RETURN [VM2.ScanMatches[vmap, TryTuple, FALSE, pat].found]}; IF testName THEN [] _ GenerateNames[TryName] ELSE [] _ TryName["*"]; RETURN}; GetNameOfFile: PUBLIC PROC[file: CirioFile] RETURNS[PATH] = BEGIN inner: ENTRY PROC[set: FileSet] RETURNS[PATH] = {ENABLE UNWIND => NULL; RETURN[file.name]}; IF file = NIL THEN RETURN[NIL] ELSE RETURN[inner[file.fileSet]]; END; GetFileInfo: PUBLIC PROC[file: CirioFile] RETURNS[SystemInterface.CirioFileInfo] = BEGIN inner: ENTRY PROC[set: FileSet] RETURNS[SystemInterface.CirioFileInfo] = BEGIN ENABLE UNWIND => NULL; IF NOT file.infoValid THEN ERROR<>; RETURN[file.info]; END; RETURN[inner[file.fileSet]] END; GetStreamForFile: PUBLIC PROC[file: CirioFile] RETURNS[IO.STREAM] = { IF file=NIL THEN RETURN[NIL] ELSE RETURN EnterGetStreamForFile[file.fileSet, file]}; EnterGetStreamForFile: ENTRY PROC[set: FileSet, file: CirioFile] RETURNS[IO.STREAM] = { ENABLE UNWIND => NULL; RETURN InnerGetStreamForFile[set, file]}; InnerGetStreamForFile: INTERNAL PROC[set: FileSet, file: CirioFile] RETURNS[IO.STREAM] = { IF NOT file.infoValid THEN ERROR<>; FOR sl: LIST OF REF FileStreamInfo _ file.streams, sl.rest WHILE sl # NIL DO IF NOT sl.first.active THEN BEGIN sl.first.active _ TRUE; IF sl.first.stream # NIL THEN BEGIN -- remove from the open but inactive list, and reset position set: FileSet _ file.fileSet; stream: REF FileStreamInfo _ sl.first; removed: BOOLEAN _ FALSE; info: SystemInterface.CirioFileInfo; IF set.openInactiveStreams = NIL THEN ERROR; IF set.openInactiveStreams.first = stream THEN {set.openInactiveStreams _ set.openInactiveStreams.rest; removed _ TRUE} ELSE FOR sl: LIST OF REF FileStreamInfo _ set.openInactiveStreams, sl.rest WHILE sl.rest # NIL DO IF sl.rest.first = stream THEN {sl.rest _ sl.rest.rest; removed _ TRUE; EXIT}; ENDLOOP; IF NOT removed THEN ERROR; IF set.lastOpenInactiveStream.first = stream THEN BEGIN -- ugh ... fixup IF set.openInactiveStreams = NIL THEN set.lastOpenInactiveStream _ NIL ELSE FOR sl: LIST OF REF FileStreamInfo _ set.openInactiveStreams, sl.rest DO IF sl.rest = NIL THEN {set.lastOpenInactiveStream _ sl; EXIT}; ENDLOOP; END; [bytes: info.bytes, uniqueID: info.uniqueID] _ PFS.GetInfo[stream.openFile]; IF info.bytes # stream.fileInfo.bytes OR info.uniqueID # stream.fileInfo.uniqueID THEN -- file has changed ERROR PFS.Error[[environment, $ImmutableFileChanged, IO.PutFR["The CirioFile for %g of %g len %g has changed", [rope[PFS.RopeFromPath[file.name]]], [rope[FmtUniqueID[file.info.uniqueID]]], [integer[file.info.bytes]] ]]] <>; IO.SetIndex[stream.stream, 0]; RETURN[stream.stream]; END ELSE -- we have to open the stream BEGIN sl.first.stream _ PFS.StreamOpen[file.name, read, file.info.uniqueID]; sl.first.openFile _ PFS.OpenFileFromStream[sl.first.stream]; sl.first.fileInfo _ file.info; <<[bytes: sl.first.fileInfo.bytes, uniqueID: sl.first.fileInfo.uniqueID] _ PFS.GetInfo[sl.first.openFile]; file.info _ sl.first.fileInfo; -- update the file info>> file.fileSet.nOpenStreams _ file.fileSet.nOpenStreams+1; CheckNOpenStreamsP[file.fileSet]; RETURN[sl.first.stream]; END; END; ENDLOOP; BEGIN stream: IO.STREAM _ PFS.StreamOpen[file.name, read, file.info.uniqueID]; openFile: PFS.OpenFile _ PFS.OpenFileFromStream[stream]; streamInfo: REF FileStreamInfo _ NEW[FileStreamInfo_[ file, TRUE, stream, openFile, file.info]]; <<[bytes: streamInfo.fileInfo.bytes, uniqueID: streamInfo.fileInfo.uniqueID] _ PFS.GetInfo[openFile]; file.info _ streamInfo.fileInfo; -- update the file info>> file.streams _ CONS[streamInfo, file.streams]; file.fileSet.nOpenStreams _ file.fileSet.nOpenStreams+1; CheckNOpenStreamsP[file.fileSet]; RETURN[file.streams.first.stream]; END; }; ReleaseStreamForFile: PUBLIC PROC[file: CirioFile, stream: IO.STREAM] = BEGIN inner: ENTRY PROC[set: FileSet] = BEGIN ENABLE UNWIND => NULL; FOR sl: LIST OF REF FileStreamInfo _ file.streams, sl.rest WHILE sl # NIL DO IF sl.first.stream = stream THEN BEGIN cell: LIST OF REF FileStreamInfo _ LIST[sl.first]; cell.first.active _ FALSE; IF file.fileSet.openInactiveStreams = NIL THEN file.fileSet.openInactiveStreams _ cell ELSE file.fileSet.lastOpenInactiveStream.rest _ cell; file.fileSet.lastOpenInactiveStream _ cell; CheckNOpenStreamsP[file.fileSet]; RETURN; END; ENDLOOP; ERROR; END; inner[file.fileSet]; END; CheckNOpenStreamsP: PROC[set: FileSet] = BEGIN IF set.nOpenStreams > NominalMaxOpenStreams AND set.openInactiveStreams # NIL THEN BEGIN toClose: REF FileStreamInfo _ set.openInactiveStreams.first; set.openInactiveStreams _ set.openInactiveStreams.rest; IO.Close[toClose.stream]; toClose.stream _ NIL; IF toClose.active THEN ERROR; set.nOpenStreams _ set.nOpenStreams - 1; END; END; BadDotONameSyntax: ERROR = CODE; < GOTO fails; cirioPrefix: PATH _ PFSNames.EmptyPath; cirioRemainingFileName: PATH _ PFSNames.EmptyPath; volume: PATH _ PFSNames.EmptyPath; name1: PATH _ PFSNames.EmptyPath; remainingPath: PATH _ PFSNames.EmptyPath; machineDependentSubdir: PATH _ PFSNames.EmptyPath; stem: RopePart; secondaryExtension: RopePart; extension: RopePart; version: PFSNames.Version; fileNameSeq: RopeSeq _ NIL; tentativeRemainingPath: LIST OF Component _ NIL; exit: BOOLEAN _ FALSE; curPart: Component _ unixFileName.Fetch[1]; curPartRope: ROPE _ curPart.ComponentRope; nextPart: INT _ 1; IF Rope.Equal[curPartRope, "tmp_mnt", FALSE] THEN { curPart _ unixFileName.Fetch[2]; curPartRope _ curPart.ComponentRope; IF Rope.Equal[curPartRope, "net", FALSE] THEN { nextPart _ 3; volume _ PFSNames.ConstructName[LIST[unixFileName.Fetch[0], unixFileName.Fetch[1], curPart], FALSE, TRUE]; }; } ELSE IF curPartRope.Equal["volume", FALSE] OR curPartRope.Equal["net", FALSE] OR curPartRope.Equal["pseudo", FALSE] THEN { nextPart _ 2; volume _ PFSNames.ConstructName[LIST[unixFileName.Fetch[0], curPart], FALSE, TRUE]; }; IF volume = PFSNames.EmptyPath THEN volume _ PFSNames.ConstructName[LIST[unixFileName.Fetch[0]], FALSE, TRUE]; IF PFS.RopeFromPath[volume].Substr[0,3].Equal["-ux"] THEN volume _ PFS.PathFromRope[PFS.RopeFromPath[volume].Replace[0, 3, ""]]; IF unixFileName.ComponentCount # 3 OR volume.ComponentCount = 1 THEN { curPart _ unixFileName.Fetch[nextPart]; name1 _ PFSNames.ConstructName[LIST[curPart], FALSE, TRUE]} ELSE nextPart _ nextPart - 1; nextPart _ nextPart + 1; FOR part: INT _ nextPart, part+1 UNTIL part = unixFileName.ComponentCount-1 OR exit = TRUE DO nextPart _ part+1; curPart _ unixFileName.Fetch[part]; IF CirioDeltaFace.IsMachineDependentSubdirectory[curPart] THEN { exit _ TRUE; machineDependentSubdir _ PFSNames.ConstructName[LIST[curPart], FALSE, TRUE]} ELSE tentativeRemainingPath _ CONS[curPart, tentativeRemainingPath]; ENDLOOP; remainingPath _ PFSNames.ConstructName[tentativeRemainingPath, FALSE, TRUE, TRUE]; curPart _ unixFileName.Fetch[nextPart]; version _ curPart.version; curPartRope _ curPart.ComponentRope; { fnPart: RopePart _ RS.PartFromRope[curPartRope]; fileNameSeq _ RS.ParsePartToSeq[fnPart, '.]; SELECT fileNameSeq.ComponentCount FROM 1 => stem _ fileNameSeq.Fetch[0]; 2 => { stem _ fileNameSeq.Fetch[0]; extension _ fileNameSeq.Fetch[1]; }; 3 => { stem _ fileNameSeq.Fetch[0]; secondaryExtension _ fileNameSeq.Fetch[1]; extension _ fileNameSeq.Fetch[2]; }; ENDCASE => { stem _ fileNameSeq.Fetch[0]; secondaryExtension _ fileNameSeq.Fetch[1]; extension _ fileNameSeq.Fetch[2]; }; }; cirioPrefix _ volume.Cat[name1]; cirioRemainingFileName _ PFSNames.EmptyPath; cirioRemainingFileName _ remainingPath.Cat[machineDependentSubdir]; cirioRemainingFileName _ cirioRemainingFileName.Cat[ PFS.PathFromRope[RS.ConstructSeq[LIST[stem,secondaryExtension,extension]].UnparseSeqToPart['.].RopeFromPart]]; curPartRope _ PFS.RopeFromPath[cirioRemainingFileName]; IF curPartRope.Fetch[curPartRope.Length-1] = ': THEN cirioRemainingFileName _ PFS.PathFromRope[curPartRope.Substr[0, curPartRope.Length - 1]]; cirioRemainingFileName _ cirioRemainingFileName.SetVersionNumber[version]; RETURN[[ cirioPrefix: cirioPrefix, cirioRemainingFileName: cirioRemainingFileName, volume: volume, name1: name1, remainingPath: remainingPath, machineDependentSubdir: machineDependentSubdir, stem: stem, secondaryExtension: secondaryExtension, extension: extension, version: version ]]; EXITS fails => BadDotONameSyntax[]; END; GetLocalFileName: PROC [fileName: PFSNames.PATH, serverName: ROPE _ NIL] RETURNS [PFSNames.PATH] ~ { unixName: FactoredUnixFileName _ []; ourPrefix: PATH; IF serverName = NIL OR serverName.Equal["sameWorld"] THEN RETURN [fileName]; unixName _ FactorUnixFileName[fileName ! BadDotONameSyntax => BEGIN ShowReport[Rope.Cat["Unable to factor unix name ", PFS.RopeFromPath[fileName]], $urgent]; unixName.cirioPrefix _ NIL; CONTINUE; END;]; IF debugNaming THEN SimpleFeedback.PutF[$CirioSystemInterface, oneLiner, $Naming, "Factor[%g, %g] => %g", [rope[PFS.RopeFromPath[fileName]]], [rope[IF serverName=NIL THEN "NIL" ELSE Convert.RopeFromRope[serverName]]], [rope[FmtFufn[unixName]]] ]; IF unixName.cirioPrefix = NIL THEN RETURN[fileName]; ourPrefix _ CirioDeltaFace.GetOurPrefixForUnixPrefix[unixName.volume, unixName.name1, serverName, "ux"]; IF debugNaming THEN SimpleFeedback.PutF[$CirioSystemInterface, oneLiner, $Naming, "GetOur... => %g", [rope[PFS.RopeFromPath[ourPrefix]]] ]; RETURN[ourPrefix.Cat[unixName.cirioRemainingFileName]]; }; FmtFufn: PROC [fufn: FactoredUnixFileName] RETURNS [ROPE] ~ { buf: IO.STREAM ~ IO.ROS[]; buf.PutF["[cp:%g, crfn:%g; vol:%g, n1:%g, ", [rope[PFS.RopeFromPath[fufn.cirioPrefix]]], [rope[PFS.RopeFromPath[fufn.cirioRemainingFileName]]], [rope[PFS.RopeFromPath[fufn.volume]]], [rope[PFS.RopeFromPath[fufn.name1]]] ]; buf.PutF["rp:%g, mds:%g, stem:%g, se:%g, e:%g, v:", [rope[PFS.RopeFromPath[fufn.remainingPath]]], [rope[PFS.RopeFromPath[fufn.machineDependentSubdir]]], [rope[RS.RopeFromPart[fufn.stem]]], [rope[RS.RopeFromPart[fufn.secondaryExtension]]], [rope[RS.RopeFromPart[fufn.extension]]] ]; SELECT fufn.version.versionKind FROM numeric => buf.Put[ [cardinal[fufn.version.version]] ]; none => buf.PutRope["none"]; lowest => buf.PutRope["lowest"]; highest => buf.PutRope["highest"]; next => buf.PutRope["next"]; all => buf.PutRope["all"]; ENDCASE => buf.PutRope["?"]; buf.PutChar[']]; RETURN buf.RopeFromROS[]}; >> DebuggeeUnixNameToDebuggerCedarName: PUBLIC PROC [unixName: PATH, debuggee: ROPE] RETURNS [cedar: PATH] ~ { lc: Component; c0: ROPE _ "-ux"; cedar _ unixName; lc _ cedar.ShortName[]; IF NOT cedar.Fetch[0].ComponentRope.IsEmpty THEN ShowReport[IO.PutFR["Given unix name (%g) on debuggee (%g) with non-empty 0'th component", [rope[PFS.RopeFromPath[unixName]]], [rope[debuggee]] ], $normal] ELSE IF lc.version.versionKind # none THEN ShowReport[IO.PutFR["Given unix name (%g) on debuggee (%g) with non-empty version", [rope[PFS.RopeFromPath[unixName]]], [rope[debuggee]] ], $normal] ELSE { rs: RopeSeq ~ RS.ParsePartToSeq[MPN.ComponentName[lc], '.]; n: NAT ~ RS.Length[rs]; le: RopePart ~ rs.Fetch[n-1]; lel: INT ~ le.Length[]; IF lel>2 AND le.Fetch[0]='~ AND le.Fetch[lel-1]='~ THEN { ver: CARD ~ Convert.CardFromRope[le.Substr[start: 1, len: lel-2].ToRope !Convert.Error => GOTO NotNum]; IF ver > CARDINAL.LAST THEN ShowReport[IO.PutFR["Can't naturalize huge version of unix name (%g) from debuggee (%g) - left in -ux: syntax", [rope[PFS.RopeFromPath[unixName]]], [rope[debuggee]] ], $urgent] ELSE { lc.name.len _ lc.name.len - (lel+1); lc.version _ [numeric, ver]; cedar _ cedar.ReplaceShortName[lc]; c0 _ "-vux"}; EXITS NotNum => c0 _ c0}; cedar _ cedar.ReplaceComponent[0, [name: [c0, 0, c0.Length]]]; }; IF debuggee#NIL AND NOT debuggee.Equal["SameWorld", FALSE] THEN { Warn: PROC [r: ROPE] ~ {ShowReport[r, $urgent]}; local: PATH ~ CirioDeltaFace.DebuggeeNameToDebuggerName[cedar, debuggee, Warn]; IF debugNaming THEN SimpleFeedback.PutF[$SystemInterfaceImpl, oneLiner, $Naming, "Localize[%g, %g] => %g", [rope[debuggee]], [rope[PFS.RopeFromPath[cedar]]], [rope[PFS.RopeFromPath[local]]] ]; cedar _ local}; RETURN}; debugNaming: BOOL _ FALSE; FmtUniqueID: PUBLIC PROC [uid: PFS.UniqueID] RETURNS [ROPE] ~ { time: ROPE _ "error formatting time"; IF uid.egmt.time = BasicTime.nullGMT THEN time _ "unspecified" ELSE time _ Convert.RopeFromTime[uid.egmt.time, years, seconds, FALSE, FALSE !Convert.Error => CONTINUE]; IF uid.egmt.usecs#0 THEN time _ IO.PutFR["%g plus %gus", [rope[time]], [cardinal[uid.egmt.usecs]] ]; IF uid.host # [0, 0] THEN time _ IO.PutFR["%g host [%xH, %xH]", [rope[time]], [cardinal[uid.host.a]], [cardinal[uid.host.b]] ]; RETURN [time]}; VersionStampToUniqueID: PUBLIC PROC [vs: MobDefs.VersionStamp] RETURNS [uid: PFS.UniqueID] ~ { IF vs[1]#0 THEN CCE[cirioError, IO.PutFR["MobDefs.VersionStamp[%xH, %xH] can't be converted to a create date", [cardinal[vs[0]]], [cardinal[vs[1]]] ]]; uid _ [egmt: [time: BasicTime.FromNSTime[vs[0] ! BasicTime.OutOfRange => CCE[cirioError, IO.PutFR["MobDefs.VersionStamp[%xH, 0] doesn't encode a valid create date", [cardinal[vs[0]]] ]] ]]]; RETURN}; UniqueIDToVersionStamp: PUBLIC PROC [uid: PFS.UniqueID] RETURNS [vs: MobDefs.VersionStamp] ~ { IF uid.egmt.usecs # 0 OR uid.host # [0, 0] THEN ERROR CCE[cirioError, IO.PutFR["PFS.UniqueID %g can't be converted to a MobDefs.VersionStamp", [rope[FmtUniqueID[uid]]] ]]; vs _ [BasicTime.ToNSTime[uid.egmt.time], 0]; RETURN}; Verbosify: Commander.CommandProc = {warnOnVmPfsErr _ verboseGenVm _ TRUE}; Tersify: Commander.CommandProc = {warnOnVmPfsErr _ verboseGenVm _ FALSE}; Commander.Register["Cirio.verboseScanVersionMaps", Verbosify]; Commander.Register["Cirio.terseScanVersionMaps", Tersify]; END. :h SystemInterfaceImpl.mesa Copyright Ó 1990 by Xerox Corporation. All rights reserved. Theimer, October 14, 1989 10:34:24 pm PDT Last changed by Theimer on October 31, 1989 8:17:54 pm PST Last changed by Sturgis: February 20, 1990 1:15 pm PST Last tweaked by Mike Spreitzer on December 23, 1991 9:55 am PST Last changed by Coolidge, July 27, 1990 4:40 pm PDT Laurie Horton, October 11, 1990 3:02 pm PDT Philip James, December 26, 1991 11:51 am PST Useful shorthand Useful, since many packages wish to deliver reports priority is one of the following: $urgent, $normal, $debug Routines that abstract the FileViewerOps interface. Routines that hide the details of file naming conventions. All routines that know about the differences between D-machine and PCedar file names are (supposedly) here. Currently we assume the old Cedar environment name format: [machine]sub-dir>...>rootName. MesaFileNameToCFileName: PUBLIC PROC [mesaFileName: ROPE, reports: IO.STREAM] RETURNS [ROPE] = BEGIN first we pick apart the given mesa file name mns: MesaNameStuff _ GetMesaNameStuff[mesaFileName]; server: ROPE _ Rope.Substr[mns.fullMesaName, mns.positionsInMesaName.server.start, mns.positionsInMesaName.server.length]; dir: ROPE _ Rope.Substr[mns.fullMesaName, mns.positionsInMesaName.dir.start, mns.positionsInMesaName.dir.length]; subDirs: ROPE _ Rope.Substr[mns.fullMesaName, mns.positionsInMesaName.subDirs.start, mns.positionsInMesaName.subDirs.length]; base: ROPE _ Rope.Substr[mns.fullMesaName, mns.positionsInMesaName.base.start, mns.positionsInMesaName.base.length]; ext: ROPE _ Rope.Substr[mns.fullMesaName, mns.positionsInMesaName.ext.start, mns.positionsInMesaName.ext.length]; ver: ROPE _ Rope.Substr[mns.fullMesaName, mns.positionsInMesaName.ver.start, mns.positionsInMesaName.ver.length]; IF NOT Rope.Equal[ext, "mesa"] THEN {IO.PutF[reports, "selection not in a mesa file, aborting\N"]; RETURN[NIL]}; see if we can find a corresponding C file (THIS should really be in symbol finding) (we don't check any version stamps, we should) BEGIN preamble1: ROPE _ Rope.Cat[ Rope.Cat["[", server, "]<", dir, DirectorySeparatorString]]; preamble: ROPE _ IF Rope.IsEmpty[subDirs] THEN preamble1 ELSE Rope.Cat[preamble1, subDirs, DirectorySeparatorString]; cFileName: ROPE _ Rope.Cat[preamble, base, ".c"]; cFileStream: IO.STREAM; cFileStream _ FS.StreamOpen[cFileName ! FS.Error => CONTINUE]; IF cFileStream = NIL THEN { cFileName _ Rope.Cat[preamble, base, ".c2c.c"]; cFileStream _ FS.StreamOpen[cFileName ! FS.Error => CONTINUE]}; IF cFileStream = NIL THEN {IO.PutF[reports, "coresponding c file must be in same directory as selected mesa file, aborting\N"]; RETURN[NIL]}; IO.Close[cFileStream]; now, lets give the client what he wants IO.PutF[reports, "corresponding C file is %g\N", IO.rope[cFileName]]; IO.PutF[reports, "\Twarning: consistency unchecked!!!\N"]; RETURN[cFileName]; END; END; MesaNameStuff: TYPE = RECORD[ fullMesaName: ROPE, positionsInMesaName: FS.ComponentPositions, dirOmittedInMesaName: BOOLEAN]; GetMesaNameStuff: PROC[mesaFileName: ROPE] RETURNS[MesaNameStuff] = BEGIN fullMesaName: ROPE; positionsInMesaName: FS.ComponentPositions; dirOmittedInMesaName: BOOLEAN; [fullMesaName, positionsInMesaName, dirOmittedInMesaName] _ FS.ExpandName[mesaFileName]; RETURN[[fullMesaName, positionsInMesaName, dirOmittedInMesaName]]; END; CFileNameToDotOFileName: PUBLIC PROC [cFileName: ROPE, reports: IO.STREAM] RETURNS [ROPE] = BEGIN dotOFileName, dotOFileName1: ROPE; loc: INT; Change the .c file extension to a .o file extension. Note: We can't assume the .c is at the end of the string. We DO assume that the last ".c" in the name is indeed the .c file extension. loc _ Rope.FindBackward[cFileName, ".c"]; dotOFileName _ Rope.Replace[cFileName, loc+1, 1, "o"]; Try finding the constructed file name in a machine-dependent subdirectory first, since that is the common case. dotOFileName1 _ AddMachineDependentSubdirectory[dotOFileName, MachineDependentSubdirectoryName[]]; IF FileExists[dotOFileName1, reports] # NIL THEN RETURN [dotOFileName1]; Check if the original constructed file name exists. IF FileExists[dotOFileName, reports] # NIL THEN RETURN [dotOFileName]; We couldn't find the desired dotO file. Report an error. CCE[cirioError]; END; DotOFileNameToCFileName: PUBLIC PROC [dotOFileName: ROPE, searchDirectories: LIST OF ROPE, reports: IO.STREAM] RETURNS [dotCFileName: ROPE] = BEGIN dirPath: ROPE; rootName: ROPE; ext: ROPE; Disassemble the file name into useful (and understandable) components. [dirPath, rootName, ext] _ DisassembleUnixFileName[dotOFileName]; If dirPath represents a machine-dependent subdirectory then strip that subdirectory from it. dirPath _ StripMachineDependentSubdirectory[dirPath, MachineDependentSubdirectoryName[]]; Check each legal C file name extension (using the appropriate view on the file system) to see which might have been used. dotCFileName _ SearchThroughPossibleCExtensions[dirPath, rootName, reports]; IF dotCFileName # NIL THEN RETURN [dotCFileName]; Check if the file is in one of the connections' search directories. FOR paths: LIST OF ROPE _ searchDirectories, paths.rest WHILE paths # NIL DO dotCFileName _ SearchThroughPossibleCExtensions[paths.first, rootName, reports]; IF dotCFileName # NIL THEN RETURN [dotCFileName]; ENDLOOP; We couldn't find a C file for this .o file. Report and error and return NIL. IO.PutF[reports, "ERROR: Couldn't find a .c file corresponding to %g\n", IO.rope[dotOFileName]]; RETURN [NIL]; END; DisassembleUnixFileName: PROC [unixFileName: ROPE] RETURNS [dirPath, rootName, ext: ROPE] = BEGIN extStartLoc, rootNameStartLoc: INT; unixFileName _ ConvertFileNameToLocalFormat[unixFileName]; Search backwards for the end of the directory pathname. We assume that the first directory separator encountered, looking backwards, denotes the end of the directory pathname and the beginning of the root name. rootNameStartLoc _ Rope.FindBackward[unixFileName, DirectorySeparatorString] + 1; Search forwards from the start of the rootName for the beginning of the extension. We assume that the first "." encountered represents the separator for the extension. extStartLoc _ Rope.Find[unixFileName, ".", rootNameStartLoc]; IF extStartLoc = -1 THEN RETURN [NIL, NIL, NIL] ELSE extStartLoc _ extStartLoc + 1; Return the relevant substrings. dirPath _ Rope.Substr[unixFileName, 0, rootNameStartLoc]; rootName _ Rope.Substr[unixFileName, rootNameStartLoc, extStartLoc-rootNameStartLoc-1]; ext _ Rope.Substr[unixFileName, extStartLoc, Rope.Length[unixFileName]-extStartLoc]; RETURN [dirPath, rootName, ext]; END; DetermineFileSystemView: PUBLIC PROC [fileName: ROPE, reports: IO.STREAM] RETURNS [ROPE] = BEGIN Note: We assume that dirName is already in the form [name-*]<... Also, we assume that if the file name ends in ~*~ then it is an -nfs view file and we strip off the ~*~. nfsFileName, uxFileName: ROPE; locStart, locEnd, locTwiddle: INT; lenFileName: INT; locStart _ Rope.Find[fileName, "-"] + 1; locEnd _ Rope.Find[fileName, "]"]; Try the -nfs file view first because that's the most common case. Check for a trailing ~*~ and strip it off. lenFileName _ Length[fileName]; IF Rope.Fetch[fileName, lenFileName-1] = '~ THEN BEGIN locTwiddle _ Rope.FindBackward[fileName, "~", lenFileName-2] - 1; nfsFileName _ Rope.Replace[fileName, locTwiddle, lenFileName-locTwiddle, NIL]; END ELSE nfsFileName _ fileName; nfsFileName _ Rope.Replace[nfsFileName, locStart, locEnd-locStart, "nfs"]; IF FileExists[nfsFileName, reports] # NIL THEN RETURN [nfsFileName]; Try the -ux file view if we failed to find the file in the -nfs view. locEnd _ Rope.Find[fileName, "]"]; uxFileName _ Rope.Replace[fileName, locStart, locEnd-locStart, "ux"]; IF FileExists[uxFileName, reports] # NIL THEN RETURN [uxFileName]; RETURN [NIL]; END; SearchThroughPossibleCExtensions: PROC [candidateDirPath, rootName: ROPE, reports: IO.STREAM] RETURNS [dotCFileName: ROPE] = BEGIN FOR cExtension: LIST OF ROPE _ CExtensionsList, cExtension.rest DO IF cExtension = NIL THEN RETURN [NIL]; dotCFileName _ Rope.Cat[candidateDirPath, rootName, cExtension.first]; IF FileExists[dotCFileName, reports] # NIL THEN RETURN [dotCFileName]; ENDLOOP; END; FileExists: PROC [candidateFileName: ROPE, reports: IO.STREAM] RETURNS [fullFName: ROPE] = BEGIN IO.PutF[reports, "FileExists[%g]?\n", IO.rope[candidateFileName]]; [fullFName] _ FS.FileInfo[candidateFileName! FS.Error => IF error.group = user THEN BEGIN IO.PutF[reports, "\tERROR: FS.FileInfo[%g] reported: %g\n", IO.rope[candidateFileName], IO.rope[error.explanation]]; fullFName _ NIL; CONTINUE; END]; RETURN [fullFName]; END; CFileNameToMesaFileName: PUBLIC PROC [dotCFileName: ROPE, reports: IO.STREAM] RETURNS [ROPE] = BEGIN dotMesaFileName: ROPE; loc: INT; fullFName: ROPE _ NIL; Note: We assume that the C file name is already in proper local format. Change the .c file extension to a .mesa file extension. Note: We can't assume the .c is at the end of the string. We DO assume that the last ".c" in the name is indeed the .c file extension. loc _ Rope.FindBackward[dotCFileName, ".c"]; dotMesaFileName _ Rope.Replace[dotCFileName, loc+1, 1, "mesa"]; Check for a c2c extension and remove it. loc _ Rope.Find[dotMesaFileName, ".c2c.mesa"]; IF loc # -1 THEN dotMesaFileName _ Rope.Replace[dotMesaFileName, loc, 4]; Check to see if the file name we have constructed actually exists. fullFName _ FileExists[dotMesaFileName, reports]; IF fullFName = NIL THEN IO.PutF[reports, "ERROR: Couldn't find a .mesa file corresponding to %g\n", IO.rope[dotCFileName]]; RETURN [dotMesaFileName]; END; ConvertFileNameToLocalFormat: PUBLIC PROC [fileName: ROPE, defaultDirectory: ROPE _ NIL] RETURNS [ROPE] = BEGIN loc: INT; fileViewChange: BOOLEAN _ TRUE; factoredFileName: FactoredUnixFileName; If the filename is already in old cedar format then simply return it. IF Rope.Fetch[fileName, 0] = '[ THEN RETURN [fileName]; If the filename is not a fullName then return it as is. IF Rope.Fetch[fileName, 0] # '/ THEN RETURN [fileName]; To convert the filename: Make sure the fileserver name part is of the form [name-ux]<. factoredFileName _ FactorUnixFileName[fileName]; fileName _ Rope.Cat[factoredFileName.cirioPrefix, factoredFileName.cirioRemainingFileName]; Change /'s to [,],>'s. loc _ Rope.Find[fileName, "/"]; fileName _ Rope.Replace[fileName, loc, 1, "["]; loc _ Rope.Find[fileName, "/"]; fileName _ Rope.Replace[fileName, loc, 1, "]<"]; loc _ Rope.Find[fileName, "/"]; WHILE loc # -1 DO fileName _ Rope.Replace[fileName, loc, 1, DirectorySeparatorString]; loc _ Rope.Find[fileName, "/"]; ENDLOOP; RETURN [fileName]; END; ConvertToDirectoryFormat: PUBLIC PROC [dirName: ROPE] RETURNS [ROPE] = BEGIN loc: INT _ Rope.Length[dirName]; c: CHAR _ Rope.Fetch[dirName, loc-1]; dirName _ CirioDeltaFace.ConvertFileNameToLocalFormat[dirName]; IF c # DirectorySeparatorChar THEN BEGIN User didn't terminate the directory name with a pathname separator. Add it on. dirName _ Rope.Concat[dirName, DirectorySeparatorString]; END; RETURN [dirName]; END; ReplaceDirectory: PUBLIC PROC [fileName: ROPE, newDirectoryName: ROPE _ NIL] RETURNS [ROPE] = BEGIN loc: INT _ Rope.FindBackward[fileName, DirectorySeparatorString]; fileName _ Rope.Replace[fileName, 0, loc+1, newDirectoryName]; RETURN [fileName]; END; MachineDependentSubdirectoryName: PUBLIC PROC [] RETURNS [ROPE] = BEGIN Eventually we will want to put code in here that check what machine the target world is running on and return the appropriate value as a function of that information. RETURN ["sun4"]; END; StripMachineDependentSubdirectory: PROC [fileName: ROPE, subDirectoryName: ROPE] RETURNS [ROPE] = BEGIN lenFileName, lenSubDirectoryName: INT; lastChar: CHAR; subDirStartLoc: INT; endOfFileName: ROPE; Check for the subdirectory name at the end of the filename and strip it off if found. lenFileName _ Rope.Length[fileName]; lenSubDirectoryName _ Rope.Length[subDirectoryName]; IF (lenSubDirectoryName+1) > lenFileName THEN RETURN [fileName]; lastChar _ Rope.Fetch[fileName, lenFileName-1]; IF lastChar = DirectorySeparatorChar THEN subDirStartLoc _ lenFileName - lenSubDirectoryName - 2 ELSE subDirStartLoc _ lenFileName - lenSubDirectoryName - 1; endOfFileName _ Rope.Substr[fileName, subDirStartLoc, lenSubDirectoryName+1]; IF Rope.Equal[endOfFileName, Rope.Cat[DirectorySeparatorString, subDirectoryName]] THEN RETURN [Rope.Replace[fileName, subDirStartLoc+1]] ELSE RETURN [fileName]; END; AddMachineDependentSubdirectory: PROC [fileName: ROPE, subDirectoryName: ROPE] RETURNS [ROPE] = BEGIN loc: INT _ Rope.FindBackward[fileName, DirectorySeparatorString]; separatedSubDirectoryName: ROPE _ Rope.Cat[DirectorySeparatorString, subDirectoryName, DirectorySeparatorString]; RETURN [Rope.Replace[fileName, loc, 1, separatedSubDirectoryName]]; END; File sets Removed asAtom 7/17/90 --JLC. The stream field will be NIL if the stream has been closed. This field may be subsequently used if a new stream is opened on the same file. fileInfo represents the state of the file when the stream was opened. This may differ from the current state of the file. This implementation uses linear search. We assume the number of streams etc that we have to search is relatively small. we treat capitalization as significant didn't find it, so we have to make one need not be an entry procedure because it makes no direct ref to the internals of a set. can not be an entry procedure because it calls GetCirioFile, as do the implementors of for. (a PFS.InfoProc) need not be an entry procedure because it makes no direct ref to the internals of a set. can not be an entry procedure because it calls GetCirioFile, as do the implementors of for. lets check to be sure file hasn't changed we didn't find an inactive slot, so we have to make one factored file names A UnixFileName is: volume name1 "/" remainingPath machineDependentSubdir stem secondaryExtension extension version where volume is either "/", "/volume/", or "/net/" or "/tmp_mnt/net/". name1 contains no "/" and represents the pathname element that is relative to the volume, if there is one. (Thus name1 may be "".) machineDependentSubdir is either "/" or "/target-machine-subdir/". stem is the "root name" of the file and contains no "/". secondaryExtension is either empty or something like ".c2c"; that is, it includes a ".". extension is empty or something like ".o"; that is, it includes a ".". version is empty or "~" someNumber "~" or "!" someNumber. First, figure out volume OK, that's done. Now for name1. That was easy. Now to tackle remainingPath We're still going. Time to disassemble the filename. Deal with PFS' occasional trailing : If we're supposed to be local, act local. Parse the given filename into bits Get the "right" volume prefix to get to the relative path this computation of vs is designed to be equivalent to MobAccessImpl.ComputeSourceVersionStamp. Ê#ë•NewlineDelimiter ™codešœ™K™™>Kšœ ™Kšœ™—K™šž œœœœ ™BKš™K™¦Kšœ ™Kšœ™—K˜šž!œœ*œ ™aKš™Kšœ"œ™&Kšœ œ™Kšœœ™Kšœ™K™™UKšœ$™$Kšœ4™4Kšœ'œœ ™@K™/šœ#œ™*Kšœ6™6—šœ™Kšœ7™7—KšœM™MKšœQœœ+™‰Kšœœ ™—Kšœ™—K˜šžœœ*œ ™_Kš™Kšœœ9™AKšœq™qKšœ=™CKšœ™—K˜—™ K™Kšœ œœ ˜ š œ œœ œœ˜,Kšœœœ ˜Kšœœ˜Kšœœœœ˜0šœœœœ˜4K˜——K˜Kšœ œœ˜$K™šœœœœ˜$Kšœ˜Kšœœ˜ Kšœ œ˜K˜$Kšœ œœœ˜%—K˜šœœœ˜K˜Kšœœ˜Kšœœœ˜Kšœ œ ˜šœ)˜)K™ŒKšœz™z——K˜K˜KšŸœœ˜!˜K™x—K˜šŸ œœœœ ˜-Kš œœœœœœ˜1—K˜šŸ œœœœ˜/Kš˜Kšœœœ˜š œœœ œœ˜@š œœœœ-œœ˜Qšœœ˜Kšœœ+œ˜3—Kšœ˜—Kšœ˜—Kšœ˜—K˜šŸœœœœœ œ œœ˜šKšœ œ7˜FKšœ,˜,Kšœœœ œUœ"œœ œœ œ5˜Kšœ˜—˜K™&—šŸ œœœœœ œ œœ˜{Kšœœœ˜Kšœ)˜/—K˜š Ÿœœœœ œ œ˜iKšœœ œ˜)š œœœ œœ˜@Kš œœ œ"œœ ˜cKšœ˜—™&Kšœ˜Kšœ œ˜Kšœœ!œœ˜]šœ9œœ ˜iKšœœœ˜+Kšœ ˜—Kšœ œ˜"Kšœ˜ Kšœœœ˜ Kšœ˜—Kšœ˜K™K™XKšœ[™[—šŸœœœœœ œ œœ˜ŸKš˜Kšœœœ˜Kšœœ˜šœœœ œœœœ œ œ˜ K™Kš˜Kšœœ%˜2Kšœœ1˜=Kšœœ˜Kšœ˜—šœ ˜#Kšœœ œ˜—Kšœ ˜Kšœ˜—K™Kšœœœ˜Kšœœœ˜˜K™XKšœ[™[—šŸœœœœUœœ œ œœ œœœ˜³Kšœ œ,˜;Kšœœ3˜œc˜ÛKšœ˜Kšœ˜Kšœœ˜*Kšœ˜Kšœ¢˜.Kšœ˜Kšœ˜—Kšœ˜Kšœ˜Kš˜—šœœ¢˜#Kš˜Kšœœ1˜FKšœœ%˜œ4œ;œœœ˜ÊKšœ œ]œ!œ œœœA˜öKšœœœœ ˜4K˜K™9K˜hKšœ œXœ˜‹Kšœ1˜7K˜—K˜šŸœœœœ˜=Kš œœœœœ˜Kš œ3œ)œ4œ$œ˜ÞKš œ:œ+œ4œ"œ0œ"˜™šœ˜$Kšœ7˜7K˜Kšœ ˜ Kšœ"˜"K˜K˜Kšœ˜—K˜Kšœ˜—Kšœ˜K˜šŸ#œœœ œ œœ œ˜kK˜Kšœœ ˜Kšœ˜Kšœ˜Kš œœ&œ œTœ7˜ÌKš œœœ œMœ7˜¿šœ˜Kšœœœ˜;Kšœœœ ˜Kšœ˜Kšœœ˜šœœœœ˜9KšœœQœ ˜gKš œœœœ œiœ7˜Ìšœ˜K˜$Kšœ˜Kšœ#˜#Kšœ ˜ —Kšœ˜—Kšœ>˜>K˜—š œ œœœœœ˜AKšŸœœœ˜0KšœœD˜OKšœ œpœœ˜ÀKšœ˜—Kšœ˜—K˜Kšœ œœ˜K˜š Ÿ œœœœ œœ˜?Kšœœ˜%Kš œ#œœ<œœœ˜¨KšœœœB˜dKšœœœ\˜Kšœ ˜—K˜š Ÿœœœœœ˜^Kšœ œœu˜—šœ0˜0Kšœ(œc˜—Kšœ˜—K˜š Ÿœœœœ œ˜^Kš œœœœœc˜«Kšœ,˜,Kšœ˜K™_—K˜KšŸ œ;œ˜JKšŸœ;œ˜IK˜K˜>K˜:K˜—Kšœ˜—…—Xt¶Ç