DIRECTORY AMModel, Basics, BasicTime, BcdDefs, Commander, CommandTool, ComputeServerClient, ComputeServer, ComputeServerCallbacksRpcControl, ComputeServerControl, ComputeServerControllerRpcControl, ComputeServerDebugger, ComputeServerInternal, ComputeServerServer, ComputeServerStatistics, ComputeUtils, Convert, DFOperations, DFUtilities, FS, GVBasics, GVSend, InterpreterToolPrivate, IO, List, LoadState, PrincOps, Process, ProcessProps, PupDefs, PupErrors, PupStream, PupTypes, SummonerControllerControl, SymTab, Rope, RPC, UserCredentials, UserProfile, VM, WatchStats, WorldVM; ComputeServerImpl: CEDAR MONITOR IMPORTS AMModel, BasicTime, CommandTool, ComputeServerCallbacksRpcControl, ComputeServerControllerRpcControl, ComputeServerInternal, ComputeServerServer, ComputeServerStatistics, ComputeUtils, Convert, DFOperations, DFUtilities, FS, GVSend, IO, List, LoadState, Process, ProcessProps, PupDefs, PupErrors, PupStream, SummonerControllerControl, Rope, RPC, SymTab, UserCredentials, UserProfile, VM, WatchStats, WorldVM EXPORTS ComputeServer, ComputeServerControl, ComputeServerInternal, ComputeServerStatistics SHARES ComputeServerServer = BEGIN STREAM: TYPE = IO.STREAM; ROPE: TYPE = Rope.ROPE; ControllerGVName: PUBLIC ROPE _ NIL; ControllerInterface: PUBLIC ComputeServerControllerRpcControl.InterfaceRecord _ NIL; ActiveServicesItem: TYPE = ComputeServerInternal.ActiveServicesItem; ActiveServicesListBase: PUBLIC ComputeServerInternal.ActiveServicesItem _ NIL; ActiveServicesItemObject: TYPE = ComputeServerInternal.ActiveServicesItemObject; PupListener: TYPE = REF ComputeServerInternal.PupListenerObject; ActiveServices: PUBLIC INT _ 0 ; -- count of currently active services BufStreamData: TYPE = ComputeServerInternal.BufStreamData; bufStreamState: TYPE = ComputeServerInternal.bufStreamState; BufStreamDataObject: TYPE = ComputeServerInternal.BufStreamDataObject; CommandTable: PUBLIC SymTab.Ref; -- knowing a command, find the package/version PackageTable: PUBLIC SymTab.Ref; -- knowing a package, find its commands and maintainer PackageEntryObject: TYPE = ComputeServerInternal.PackageEntryObject; PackageEntry: TYPE = ComputeServerInternal.PackageEntry; CmdEntryObject: TYPE = ComputeServerInternal.CmdEntryObject; CmdEntry: TYPE = ComputeServerInternal.CmdEntry; ConfigTable: PUBLIC SymTab.Ref; -- knowing a config, find the package that ran it ConfigEntryObject: TYPE = ComputeServerInternal.ConfigEntryObject; ConfigEntry: TYPE = ComputeServerInternal.ConfigEntry; NotSummonerProcess: PUBLIC ERROR = CODE; RemoteCommandDir: PUBLIC ROPE _ NIL; LocalCommandDir: PUBLIC ROPE _ NIL; ShortPackageList: ROPE _ "PackageList"; clientInterfaceItem: TYPE = RECORD [ clientInstance: ROPE _ NIL, interface: ComputeServerCallbacksRpcControl.InterfaceRecord _ NIL, lastUsed: BasicTime.GMT _ BasicTime.earliestGMT ]; clientInterfaceCacheSize: INT = 20; clientInterfaceArray: TYPE = ARRAY [0..clientInterfaceCacheSize) OF clientInterfaceItem; clientInterfaceCache: REF clientInterfaceArray; OKToRunBCDs: BOOL _ TRUE; PackagesOKToRun: LIST OF ROPE _ NIL; PackagesNotOKToRun: LIST OF ROPE _ NIL; TryForFreeGFIs: INT _ 10; OKToUseLocalDisk: PUBLIC BOOL _ FALSE; anotherServerEvent: CONDITION; serverEventListTail: REF ComputeServerStatistics.ServerEvent _ NIL; watchingServer: BOOLEAN _ FALSE; RunListItem: TYPE = RECORD [ package: ROPE, gfi: INT _ 0 ]; RunList: TYPE = LIST OF REF RunListItem; InitCommands: PUBLIC PROC [remoteCommandDirectory, localCommandDirectory: Rope.ROPE] RETURNS [msg: ROPE _ NIL] = { ShortPackageList: ROPE _ "PackageList"; cp: FS.ComponentPositions; fullFName: ROPE; packageListLocalName: ROPE; packageListStream: IO.STREAM; remotePackageListDate: BasicTime.GMT; localPackageListDate: BasicTime.GMT _ BasicTime.nullGMT; packages: LIST OF ROPE _ NIL; packagesList: LIST OF ROPE _ NIL; parseError: BOOL _ FALSE; mapWedgeToSlash: Rope.TranslatorType = {IF old = '> THEN RETURN['/] ELSE RETURN[old]}; constructFName: PROC [cr: FS.ComponentRopes, omitDir: BOOL] RETURNS [fName: ROPE] = { fName _ Rope.Cat[ "/", cr.server, "/" ]; IF NOT omitDir THEN fName _ Rope.Cat[ fName, cr.dir, "/" ]; IF Rope.Length[cr.subDirs] > 0 THEN fName _ Rope.Cat[ fName, cr.subDirs, "/" ]; fName _ Rope.Cat[ fName, cr.base ]; IF Rope.Length[cr.ext] > 0 THEN fName _ Rope.Cat[ fName, ".", cr.ext ]; IF Rope.Length[cr.ver] > 0 THEN fName _ Rope.Cat[ fName, "!", cr.ver ]; }; GetProfileConstants[]; IF (RemoteCommandDir _ (IF Rope.IsEmpty[remoteCommandDirectory] THEN UserProfile.Token[key: "Summoner.RemoteCommandDirectory"] ELSE remoteCommandDirectory)) = NIL THEN { RemoteCommandDir _ "[Summoner]Packages>"; }; [] _ FS.ExpandName[name: RemoteCommandDir ! FS.Error => { msg _ Rope.Cat["Bad Remote Command Directory (", RemoteCommandDir, ")"]; GOTO returnMsg; }; ]; IF (LocalCommandDir _ (IF Rope.IsEmpty[localCommandDirectory] THEN UserProfile.Token[key: "Summoner.LocalCommandDirectory"] ELSE localCommandDirectory)) = NIL THEN { LocalCommandDir _ "///Summoner/Packages/"; }; [cp: cp, fullFName: fullFName] _ FS.ExpandName[name: LocalCommandDir.Concat["foo"] ! FS.Error => { msg _ Rope.Cat["Bad Local Command Directory (", LocalCommandDir, ")"]; GOTO returnMsg; }; ]; LocalCommandDir _ constructFName[ cr: [fullFName.Substr[cp.server.start, cp.server.length], fullFName.Substr[cp.dir.start, cp.dir.length], Rope.Translate[base: fullFName.Substr[cp.subDirs.start, cp.subDirs.length], translator: mapWedgeToSlash], NIL, NIL, NIL], omitDir: FALSE ]; [created: remotePackageListDate] _ FS.FileInfo[name: Rope.Concat[RemoteCommandDir, ShortPackageList] ! FS.Error => { msg _ Rope.Cat["Cannot do an info on the package list because FS says ", error.explanation]; GOTO cantGetPackageList; };]; packageListLocalName _ Rope.Concat[LocalCommandDir, ShortPackageList] ; [created: localPackageListDate] _ FS.FileInfo[name: packageListLocalName, remoteCheck: FALSE ! FS.Error => CONTINUE]; IF localPackageListDate = BasicTime.nullGMT OR localPackageListDate # remotePackageListDate THEN { [] _ FS.Copy[from: Rope.Concat[RemoteCommandDir, ShortPackageList], to: packageListLocalName, setKeep: TRUE, keep: 2, wantedCreatedTime: remotePackageListDate, remoteCheck: FALSE, attach: TRUE ! FS.Error => { msg _ Rope.Cat["Cannot open package list because FS says ", error.explanation]; GOTO cantGetPackageList; }; ]; }; packageListStream _ FS.StreamOpen[packageListLocalName ! FS.Error => { msg _ Rope.Cat["Cannot open package list stream because FS says ", error.explanation]; GOTO cantOpenPackageList; }; ]; [packages, parseError] _ FindPackagesAndDoBringOver[packageListStream, FALSE]; IF parseError THEN GOTO syntaxErrorInPackageList; CommandTable _ SymTab.Create[mod: 59, case: FALSE]; PackageTable _ SymTab.Create[mod: 59, case: FALSE]; ConfigTable _ SymTab.Create[mod: 59, case: FALSE]; FOR packagesList _ packages, packagesList.rest UNTIL packagesList = NIL DO addCommandsFromFile[packagesList.first]; ENDLOOP; EXITS cantGetPackageList => {}; cantOpenPackageList => {}; syntaxErrorInPackageList => {RETURN["Syntax error in Package List"]}; returnMsg => {}; }; FindPackagesAndDoBringOver: PROC [packageListStream: IO.STREAM, deltasOnly: BOOL] RETURNS [packages: LIST OF ROPE _ NIL, parseError: BOOL _ FALSE] = { currentDir: ROPE _ RemoteCommandDir; DoOneItem: DFUtilities.ProcessItemProc = { errors, warnings, filesActedUpon: INT _ 0; remoteName: ROPE; innerBringOver: PROC = { [errors, warnings, filesActedUpon] _ DFOperations.BringOver[dfFile: remoteName, filter: [all, public, all], action: enter]; }; WITH item SELECT FROM dir: REF DFUtilities.DirectoryItem => { currentDir _ dir.path1; }; file: REF DFUtilities.FileItem => { shortName: ROPE = Rope.Substr[file.name, 0, Rope.Index[s1: file.name, s2: "!"]]; package: ROPE = shortName.Substr[ 0, Rope.Index[s1: shortName, s2: "."]]; packageDFDate: BasicTime.GMT _ BasicTime.nullGMT; currentDFDate: BasicTime.GMT _ BasicTime.nullGMT; remoteName _ Rope.Concat[currentDir, shortName]; [created: currentDFDate] _ FS.FileInfo[name: Rope.Cat[LocalCommandDir, package, "/", shortName], remoteCheck: FALSE ! FS.Error => CONTINUE]; IF file.date.format # explicit OR file.date.gmt # currentDFDate OR currentDFDate = BasicTime.nullGMT THEN { propList: List.AList; propList _ List.PutAssoc[key: $WorkingDirectory , val: Rope.Cat[LocalCommandDir, package, "/"], aList: NIL]; ProcessProps.AddPropList[propList: propList, inner: innerBringOver]; IF errors = 0 THEN packages _ CONS[shortName.Substr[ 0, Rope.Index[s1: shortName, s2: "."]], packages]; } ELSE IF ~deltasOnly THEN packages _ CONS[package, packages]; }; ENDCASE; }; DFUtilities.ParseFromStream[packageListStream, DoOneItem ! DFUtilities.SyntaxError => GOTO syntaxErrorInPackageList]; EXITS syntaxErrorInPackageList => {parseError _ TRUE}; }; getNewPackages: PUBLIC PROC = { remotePackageListDate: BasicTime.GMT; packageListLocalName: ROPE _ NIL; localPackageListDate: BasicTime.GMT _ BasicTime.nullGMT; packageListStream: IO.STREAM; parseError: BOOL _ FALSE; packages: LIST OF ROPE _ NIL; packagesList: LIST OF ROPE _ NIL; [created: remotePackageListDate] _ FS.FileInfo[name: Rope.Concat[RemoteCommandDir, ShortPackageList] ! FS.Error => CONTINUE]; packageListLocalName _ Rope.Concat[LocalCommandDir, ShortPackageList] ; [created: localPackageListDate] _ FS.FileInfo[name: packageListLocalName, remoteCheck: FALSE ! FS.Error => CONTINUE]; IF localPackageListDate = BasicTime.nullGMT OR localPackageListDate # remotePackageListDate THEN { [] _ FS.Copy[from: Rope.Concat[RemoteCommandDir, ShortPackageList], to: packageListLocalName, setKeep: TRUE, keep: 2, wantedCreatedTime: remotePackageListDate, remoteCheck: FALSE, attach: TRUE ! FS.Error => GOTO cantGetPackageList]; packageListStream _ FS.StreamOpen[packageListLocalName ! FS.Error => GOTO cantOpenPackageList]; [packages, parseError] _ FindPackagesAndDoBringOver[packageListStream, TRUE]; IF parseError THEN GOTO syntaxErrorInPackageList; FOR packagesList _ packages, packagesList.rest UNTIL packagesList = NIL DO addCommandsFromFile[packagesList.first]; ENDLOOP; }; EXITS cantGetPackageList => {}; cantOpenPackageList => {}; syntaxErrorInPackageList => {}; }; addCommandsFromFile: PROC [package: ROPE] = { packageListStream: IO.STREAM; packageRemoteCommands: ROPE = Rope.Cat[LocalCommandDir, package, "/", package, ".remoteCommands"]; packageDF: ROPE = Rope.Cat[LocalCommandDir, package, "/", package, ".df"]; version: LIST OF ROPE _ NIL; ver: ROPE _ NIL; maintainer: LIST OF ROPE _ NIL; commands: LIST OF ROPE _ NIL; commandsList: LIST OF ROPE _ NIL; exclusive: BOOL _ FALSE; countActive: INT _ 10000; packageEntry: PackageEntry; packageListFile: FS.OpenFile; { -- one of those dumb extra blocks to allow the EXIT clause to see the variables dfCreate: BasicTime.GMT _ BasicTime.nullGMT; [created: dfCreate] _ FS.FileInfo[name: packageDF ! FS.Error => CONTINUE;]; packageListFile _ FS.Open[name: packageRemoteCommands, remoteCheck: FALSE ! FS.Error => GOTO cantOpen]; packageListStream _ FS.StreamFromOpenFile[openFile: packageListFile ! FS.Error => GOTO cantOpen]; DO token: ROPE _ NIL; tokens, tail: LIST OF ROPE _ NIL; key: ROPE _ NIL; token _ ComputeUtils.LocalToken[packageListStream, TRUE]; IF (key _ token) = NIL THEN EXIT; SELECT ComputeUtils.SkipWhite[packageListStream] FROM ': => [] _ packageListStream.GetChar[]; -- flush the ': ENDCASE => { DO IF packageListStream.GetChar[ ! IO.EndOfStream => EXIT] = '\n THEN EXIT; ENDLOOP; LOOP; }; DO list: LIST OF ROPE _ NIL; token _ ComputeUtils.LocalToken[packageListStream]; IF token = NIL THEN EXIT; list _ LIST[token]; IF tail = NIL THEN {tail _ tokens _ list} ELSE {tail.rest _ list; tail _ list}; ENDLOOP; SELECT TRUE FROM Rope.Equal[key, "version", FALSE] => { IF tail = NIL THEN {version _ tokens} ELSE {tail.rest _ version; version _ tokens}; }; Rope.Equal[key, "maintainer", FALSE] => { IF tail = NIL THEN {maintainer _ tokens} ELSE {tail.rest _ maintainer; maintainer _ tokens}; }; Rope.Equal[key, "commands", FALSE] => { IF tail = NIL THEN {commands _ tokens} ELSE {tail.rest _ commands; commands _ tokens}; }; Rope.Equal[key, "exclusive", FALSE] => { IF tail # NIL AND tail = tokens THEN { SELECT ComputeUtils.trueOrFalse[tail.first] FROM true => exclusive _ TRUE; false => exclusive _ FALSE; ENDCASE ; }; }; Rope.Equal[key, "countActive", FALSE] => { IF tail # NIL AND tail = tokens THEN { count : INT; bad: BOOL _ FALSE; count _ Convert.IntFromRope[tail.first ! Convert.Error => {bad _ TRUE; CONTINUE}]; IF ~bad AND count > 0 THEN countActive _ count; }; }; ENDCASE; ENDLOOP; -- for that DO way back up there IF version # NIL THEN ver _ NARROW[version.first]; packageEntry _ NEW [PackageEntryObject _ [package, dfCreate, commands, maintainer, ver]]; packageEntry.exclusive _ exclusive; packageEntry.maxCountActive _ countActive; [] _ SymTab.Store[x: PackageTable, key: package, val: packageEntry]; FOR commandsList _ commands, commandsList.rest UNTIL commandsList = NIL DO cmd: ROPE = commandsList.first; cmdEntry: CmdEntry; cmdEntry _ NEW [CmdEntryObject _ [cmd, package, ver]]; [] _ SymTab.Store[x: CommandTable, key: cmd, val: cmdEntry]; ENDLOOP; EXITS cantOpen => {IF packageListFile # NIL THEN FS.Close[packageListFile ! FS.Error => CONTINUE;];}; }; }; AskForService: PUBLIC PROC [service: ROPE, version: RPC.ShortROPE, clientMachineName: RPC.ShortROPE, streamPupAddress: PupDefs.PupAddress, needListener: BOOL] RETURNS [found: ComputeServer.AskResponce _ foundOK, serverPupAddress: PupDefs.PupAddress, errMsg: Rope.ROPE _ NIL] = { listener: PupListener _ NIL; newItem: ActiveServicesItem; foundInCmdTable: BOOL; cmdVal: REF ANY; cmdEntry: CmdEntry; valPack: REF ANY; foundPack: BOOL; packageEntry: PackageEntry; package: ROPE _ NIL; procHandle: ComputeServerInternal.RegisteredProcHandle _ NIL; IF ~ComputeServerInternal.ServiceEnabled THEN RETURN [foundButTooBusy, PupTypes.fillInPupAddress, "Server not up"]; procHandle _ findCommand[service, version]; [found: foundInCmdTable, val: cmdVal] _ SymTab.Fetch[x: CommandTable, key: service]; cmdEntry _ NARROW[cmdVal]; IF cmdEntry # NIL THEN package _ cmdEntry.package; IF procHandle = NIL OR (version.IsEmpty[] AND foundInCmdTable AND cmdEntry.firstTime) THEN { IF foundInCmdTable THEN { IF Rope.InlineIsEmpty[version] OR Rope.Equal[cmdEntry.version, version, FALSE] THEN { tryRun: BOOL _ OKToRunBCDs; IF ~Rope.InlineIsEmpty[version] THEN procHandle _ NIL; -- use old version if there is a problem with the new one (out of GFI's) IF OKToRunBCDs THEN { FOR loopList: LIST OF ROPE _ PackagesNotOKToRun, loopList.rest UNTIL loopList = NIL DO IF Rope.Equal[loopList.first, package, FALSE] THEN { tryRun _ FALSE; EXIT; }; ENDLOOP; IF tryRun AND PackagesOKToRun # NIL THEN { tryRun _ FALSE; -- got to find it on the list for it to be OK FOR loopList: LIST OF ROPE _ PackagesOKToRun, loopList.rest UNTIL loopList = NIL DO IF Rope.Equal[loopList.first, package, FALSE] THEN { tryRun _ TRUE; EXIT; }; ENDLOOP; }; }; IF tryRun THEN { ok: BOOL; [ok, errMsg] _ loadPackage[package, version]; IF ok THEN procHandle _ findCommand[service, version]; } ELSE { controllerInterface: ComputeServerControllerRpcControl.InterfaceRecord; controllerInterface _ ComputeServerInternal.ControllerInterface; errMsg _ "Command not now running on Server, and running not enabled"; found _ foundButTooBusy; serverPupAddress _ PupTypes.fillInPupAddress; IF controllerInterface # NIL THEN controllerInterface.CommandUnavailable[serverMachineName: ComputeServerInternal.MyNetAddressRope, commandName: service, version: version ! RPC.CallFailed => { SummonerControllerControl.ControllerCallFailed[why]; ControllerInterface _ NIL ; CONTINUE; }; ]; RETURN; }; }; } ELSE errMsg _ "Command not known on Server"; }; IF procHandle = NIL THEN { controllerInterface: ComputeServerControllerRpcControl.InterfaceRecord; controllerInterface _ ComputeServerInternal.ControllerInterface; IF errMsg.IsEmpty[] THEN errMsg _ Rope.Cat["Processed summonerLoad file for ", package, " but even then could not find the command ", service, " registered via a call to ComputeServerServer.Register on Server"]; IF foundInCmdTable THEN found _ foundButTooBusy ELSE found _ notFound; serverPupAddress _ PupTypes.fillInPupAddress; IF controllerInterface # NIL THEN controllerInterface.CommandUnavailable[serverMachineName: ComputeServerInternal.MyNetAddressRope, commandName: service, version: version ! RPC.CallFailed => { SummonerControllerControl.ControllerCallFailed[why]; ControllerInterface _ NIL ; CONTINUE; }; ]; RETURN; }; IF cmdEntry # NIL THEN { cmdEntry.firstTime _ FALSE; [found: foundPack, val: valPack] _ SymTab.Fetch[x: PackageTable, key: cmdEntry.package]; IF ~foundPack THEN RETURN[notFound, PupTypes.fillInPupAddress, "Package missing from Package table on Server"]; packageEntry _ NARROW[valPack, PackageEntry]; IF packageEntry.exclusive AND ActiveServices > 0 THEN RETURN[foundButTooBusy, PupTypes.fillInPupAddress, "Cannot get exclusive package access to server"]; IF packageEntry.nowActive >= packageEntry.maxCountActive THEN RETURN[foundButTooBusy, PupTypes.fillInPupAddress, "Too many commands running from this package"]; } ELSE { cmdEntry _ NEW[CmdEntryObject _ [service: service, package: Rope.Concat["Package for ", service], firstTime: FALSE, doQueueing: FALSE]]; packageEntry _ NEW[PackageEntryObject _ []]; }; serverPupAddress _ PupDefs.AnyLocalPupAddress[PupDefs.UniqueLocalPupSocketID[]]; IF needListener THEN listener _ ComputeServerInternal.CreatePupByteStreamListener[local: serverPupAddress.socket, proc: ComputeServerInternal.ListenerProcess, ticks: PupStream.SecondsToTocks[1], filter: ComputeServerInternal.DontReject]; newItem _ ComputeServerInternal.AddPupAddress[serverPupAddress, procHandle, listener]; newItem.clientMachineName _ clientMachineName; newItem.packageEntry _ packageEntry; newItem.commandEntry _ cmdEntry; }; findCommand: PROC[service: ROPE, version: RPC.ShortROPE] RETURNS [procHandle: ComputeServerInternal.RegisteredProcHandle _ NIL]= { found: BOOL; procRef: REF ANY; listOfRegisteredProc: LIST OF ComputeServerInternal.RegisteredProcHandle; [found: found, val: procRef] _ SymTab.Fetch[x: ComputeServerServer.Registry, key: service]; IF found AND procRef # NIL THEN { loopList: LIST OF ComputeServerInternal.RegisteredProcHandle; listOfRegisteredProc _ NARROW[procRef]; FOR loopList _ listOfRegisteredProc, loopList.rest UNTIL loopList = NIL DO IF Rope.InlineIsEmpty[loopList.first.version] OR Rope.Equal[loopList.first.version, version, FALSE] THEN RETURN[loopList.first]; ENDLOOP; }; }; loadPackage: PROC [package: ROPE, version: ROPE] RETURNS [ok: BOOL _ TRUE, errMsg: Rope.ROPE _ NIL] = { parseStateType: TYPE = {sawName, sawLeft, sawGFI, sawRight, eof}; parseState: parseStateType _ sawRight; packageLoad: ROPE _ Rope.Cat[LocalCommandDir, package, "/", package, ".summonerLoad"]; valPack: REF ANY; foundPack: BOOL; packageEntry: PackageEntry; packageLoadStream: IO.STREAM; runList: RunList _ NIL; bcdName: ROPE _ NIL; rememberBcdName: ROPE _ NIL; runListTail: RunList _ NIL; gfisNeeded: INT _ 0; runs: RunList _ NIL; ok: BOOL _ TRUE; [found: foundPack, val: valPack] _ SymTab.Fetch[x: PackageTable, key: package]; packageEntry _ NARROW[valPack, PackageEntry]; IF ~foundPack OR (~Rope.InlineIsEmpty[version] AND ~Rope.Equal[packageEntry.latestVersion, version, FALSE]) THEN RETURN [FALSE, "package not found, or version requested not newest know to server"]; packageEntry.runVersion _ packageEntry.latestVersion; packageLoadStream _ FS.StreamOpen[packageLoad ! FS.Error => GOTO fail]; WHILE parseState # eof DO token: ROPE _ NIL; [token: token] _ packageLoadStream.GetTokenRope[IO.TokenProc ! IO.EndOfStream => {parseState _ eof; CONTINUE};]; IF parseState # eof THEN { IF token.Equal["-"] AND packageLoadStream.PeekChar[] = '- THEN { [] _ packageLoadStream.GetLineRope[ ! IO.EndOfStream => {parseState _ eof; CONTINUE};]; LOOP; }; }; SELECT parseState FROM sawName, sawRight => { IF token.Equal["("] THEN { parseState _ sawLeft; LOOP;}; rememberBcdName _ bcdName; bcdName _ token; parseState _ sawName; }; sawLeft => { gfisNeeded _ Convert.IntFromRope[token ! Convert.Error => CONTINUE ]; parseState _ sawGFI; LOOP; }; sawGFI => { IF token.Equal[")"] THEN { parseState _ sawRight; LOOP;}; parseState _ sawName; }; eof => rememberBcdName _ bcdName; ENDCASE; IF parseState = eof OR parseState = sawName THEN { IF rememberBcdName # NIL THEN { shortName: ROPE = Rope.Substr[rememberBcdName, 0, Rope.Index[s1: rememberBcdName, s2: ".bcd"]]; IF gfisNeeded = 0 THEN { IF Rope.Equal[shortName, "CompilerServer", FALSE] THEN gfisNeeded _ 109; IF Rope.Equal[shortName, "TeX", FALSE] THEN gfisNeeded _ 53; IF Rope.Equal[shortName, "RemoteTSetter", FALSE] THEN gfisNeeded _ 26; }; IF runListTail = NIL THEN runList _ runListTail _ CONS[NEW[RunListItem _ [rememberBcdName, gfisNeeded]], NIL] ELSE runListTail _ (runListTail.rest _ CONS[NEW[RunListItem _ [rememberBcdName, gfisNeeded]], NIL]); rememberBcdName _ NIL; gfisNeeded _ 0; }; }; ENDLOOP; packageLoadStream.Close[! FS.Error => CONTINUE;]; [ok, errMsg] _ checkRunList[package, runList]; IF ok THEN { errMsg _ NIL; FOR runs _ runList, runs.rest UNTIL runs = NIL OR ~ok DO runItem: ROPE = runs.first.package; noGFIs: INT = runs.first.gfi; runError: BOOL _ FALSE; tooManyGFIs: BOOL _ FALSE; alreadyRun: BOOL; configEntry: ConfigEntry _ NIL; itemErrMsg: ROPE _ NIL; localRunName: ROPE = Rope.Cat[LocalCommandDir, package, "/", runItem]; [alreadyRun: alreadyRun, tooManyGFIs: tooManyGFIs, msg: itemErrMsg] _ lookAtBcdAndLoadState[package: package, noGFIs: noGFIs, packageDFDate: packageEntry.dfCreate, runItem: runItem, fileName: localRunName]; IF tooManyGFIs THEN { errMsg _ Rope.Concat[errMsg, "Package needed too many GFIs.\n"]; ok _ FALSE; EXIT; }; IF ~alreadyRun AND itemErrMsg = NIL THEN { innerRun: PROC [] = { [errMsg: itemErrMsg, error: runError] _ CommandTool.Run[bcdName: localRunName, runEvenIfAlreadyRun: FALSE, runEvenIfUnbound: TRUE]; }; newPropertys: List.AList _ NIL; newPropertys _ List.PutAssoc[key: $WorkingDirectory , val: Rope.Cat[LocalCommandDir, package, "/"], aList: NIL]; ProcessProps.AddPropList[propList: newPropertys, inner: innerRun]; IF runError THEN { -- ignore Unbound imports errors IF Rope.Find[itemErrMsg, "Unbound imports {"] > 0 THEN { runError _ FALSE; errMsg _ Rope.Cat[errMsg, runItem," Got an unbound error: ", itemErrMsg, "\n"]; }; }; }; IF runError THEN { errMsg _ Rope.Cat[Rope.Cat[errMsg, "Got the following error trying to run ", runItem,": ", Rope.Cat[itemErrMsg, "\n"]]]; ok _ FALSE; EXIT; }; configEntry _ NEW [ConfigEntryObject _ [package, packageEntry.dfCreate, packageEntry.maintainer]]; [] _ SymTab.Store[x: ConfigTable, key: runItem, val: configEntry]; ENDLOOP; }; EXITS fail => {}; }; checkRunList: PROC [package: ROPE, runList: RunList] RETURNS [ok: BOOL _ TRUE, errMsg: ROPE] = { loopList: RunList; FOR loopList _ runList, loopList.rest UNTIL loopList = NIL OR ~ok OR errMsg # NIL DO runItem: ROPE = loopList.first.package; shortBcdName: ROPE _ Rope.Concat[runItem.Substr[0, runItem.Index[s2: "."]] ,".bcd"]; bcdName: ROPE _ Rope.Cat[LocalCommandDir, package, "/", shortBcdName]; fullFName: ROPE; desiredTime: BasicTime.GMT _ BasicTime.nullGMT ; serverTime: BasicTime.GMT _ BasicTime.nullGMT ; runOK: BOOL _ FALSE; differentInStd: BOOL _ FALSE; [fullFName: fullFName, created: desiredTime] _ FS.FileInfo[name: bcdName, remoteCheck: FALSE ! FS.Error => CONTINUE]; IF desiredTime = BasicTime.nullGMT THEN { ok _ FALSE; errMsg _ Rope.Cat["BCD for ", runItem, " in ", package, " not in the df file."]; EXIT; }; [created: serverTime] _ FS.FileInfo[name: fullFName, wantedCreatedTime: desiredTime, remoteCheck: TRUE ! FS.Error => CONTINUE]; IF serverTime = BasicTime.nullGMT THEN { ok _ FALSE; errMsg _ Rope.Cat["No BCD for ", runItem, " in ", package, " not on the server."]; EXIT; }; IF serverTime # desiredTime THEN { ok _ FALSE; errMsg _ Rope.Cat["Wrong date for BCD for ", runItem, " in ", package, " missing on the server."]; EXIT; }; ENDLOOP; }; lookAtBcdAndLoadState: PROC[package: ROPE, noGFIs: INT, packageDFDate: BasicTime.GMT, runItem: ROPE, fileName: ROPE] RETURNS [alreadyRun: BOOL _ FALSE, tooManyGFIs: BOOL _ FALSE, msg: ROPE _ NIL] = { file: FS.OpenFile _ FS.nullOpenFile; length: INT; IF (noGFIs + TryForFreeGFIs) > WatchStats.GetWatchStats[].gfiFree THEN {tooManyGFIs _ TRUE; RETURN}; length _ Rope.Length[fileName]; IF length < 5 OR (Rope.Compare[Rope.Substr[fileName, length - 4, 4], ".bcd", FALSE] # equal AND Rope.Find[fileName, "!", MAX[0, length-6]] = -1) THEN fileName _ Rope.Concat[fileName, ".bcd"]; file _ FS.Open[fileName ! FS.Error => { msg _ error.explanation; GOTO cantOpenBCD; } ]; TRUSTED { LoadState.local.Acquire[]; { ENABLE UNWIND => LoadState.local.Release[]; BcdVersion: SAFE PROC[file: FS.OpenFile] RETURNS [version: BcdDefs.VersionStamp _ BcdDefs.NullVersion] = TRUSTED { bcdSpace: VM.Interval = VM.Allocate[count: 1]; bcd: BcdDefs.BcdBase _ LOOPHOLE[VM.AddressForPageNumber[bcdSpace.page]]; FS.Read[file: file, from: 0, nPages: 1, to: LOOPHOLE[bcd]]; IF bcd.versionIdent = BcdDefs.VersionID AND NOT bcd.definitions AND bcd.spare1 THEN version _ bcd.version; -- else error, which will be reported later VM.Free[bcdSpace]; }; lookAtConfig: SAFE PROC [config: LoadState.ConfigID] RETURNS [stop: BOOL _ FALSE] = TRUSTED { IF bcdVersion = LoadState.local.ConfigInfo[config].bcd.version THEN RETURN[TRUE]; }; bcdVersion: BcdDefs.VersionStamp = BcdVersion[file]; IF bcdVersion # BcdDefs.NullVersion AND LoadState.local.EnumerateConfigs[newestFirst, lookAtConfig] # LoadState.nullConfig THEN alreadyRun _ TRUE; }; -- end ENABLE UNWIND => LoadState.local.Release[]; LoadState.local.Release[]; IF ~alreadyRun THEN { context: AMModel.Context; contextNames: LIST OF ROPE _ NIL; enumContextChildren: PROC[c: AMModel.Context] RETURNS[stop: BOOL _ FALSE] = TRUSTED { contextName: ROPE = AMModel.ContextName[c]; contextNames _ CONS[contextName, contextNames]; IF Rope.Equal[contextName.Substr[0, contextName.Index[0, ":"]], runItem, FALSE] THEN { sendGVMailAboutDupPackage[package, packageDFDate, runItem]; stop _ TRUE; }; }; context _ AMModel.ContextChildren[AMModel.RootContext[WorldVM.LocalWorld[]], enumContextChildren]; contextNames _ NIL; }; }; EXITS cantOpenBCD => {}; }; sendGVMailAboutDupPackage: PROC [package: ROPE, packageDFDate: BasicTime.GMT, runItem: ROPE] = { foundConfig: BOOL; valConfig: REF ANY; [found: foundConfig, val: valConfig] _ SymTab.Fetch[x: ConfigTable, key: runItem]; IF foundConfig THEN { foundPack: BOOL; valPack: REF ANY; packageEntry: PackageEntry; configEntry: ConfigEntry _ NARROW[valConfig]; [found: foundPack, val: valPack] _ SymTab.Fetch[x: PackageTable, key: configEntry.package]; IF ~foundPack THEN sendGVMailAboutBug[Rope.Concat["missing package table entry for ", package]] ELSE { FOR try: INT IN [0..3) DO gvHandle: GVSend.Handle; startSend: GVSend.StartSendInfo; name, password: Rope.ROPE; toRope: Rope.ROPE _ NIL; recipients: INT _ 0; packageEntry _ NARROW[valPack, PackageEntry]; gvHandle _ GVSend.Create[]; [name: name, password: password] _ UserCredentials.Get[]; startSend _ gvHandle.StartSend[senderPwd: password, sender: name, validate: TRUE ]; IF startSend = ok THEN { ENABLE GVSend.SendFailed => { gvHandle.Abort[]; LOOP; }; FOR toList: LIST OF ROPE _ packageEntry.maintainer, toList.rest WHILE toList # NIL DO gvHandle.AddRecipient[toList.first]; IF toRope = NIL THEN toRope _ toList.first ELSE toRope _ Rope.Cat[toRope, ", ", toList.first]; ENDLOOP; FOR toList: LIST OF ROPE _ configEntry.maintainer, toList.rest WHILE toList # NIL DO gvHandle.AddRecipient[toList.first]; IF toRope = NIL THEN toRope _ toList.first ELSE toRope _ Rope.Cat[toRope, ", ", toList.first]; ENDLOOP; recipients _ gvHandle.CheckValidity[notify: NIL]; IF recipients > 0 THEN { rope0, rope1, rope1a, rope2, rope3, rope4: ROPE _ NIL; gvHandle.StartItem[Text]; rope0 _ Rope.Cat["Date: ", Convert.RopeFromTime[from: BasicTime.Now[], start:years, end: seconds, includeDayOfWeek: TRUE, useAMPM: TRUE, includeZone: TRUE],"\n"]; rope1 _ Rope.Cat["From: ", ComputeServerInternal.myHostName, " Compute Server\nSubject: Multiple versions on ", ComputeServerInternal.myHostName, "\n"]; rope1a _ Rope.Cat["To: ", toRope, "\n\n"]; rope2 _ Rope.Cat["Compute Server had different versions of ", runItem, " run\n"]; rope3 _ Rope.Cat[" First package loaded was ", configEntry.package, " with df file date of ", Convert.RopeFromTime[from: configEntry.dfCreate, start:years, end: seconds, includeDayOfWeek: FALSE, useAMPM: TRUE, includeZone: TRUE], "\n"]; rope4 _ Rope.Cat[" Second package is ", package, " with df file date of ", Convert.RopeFromTime[from: packageDFDate, start: years, end: seconds, includeDayOfWeek: FALSE, useAMPM: TRUE, includeZone: TRUE], "\n"]; gvHandle.AddToItem[Rope.Cat[Rope.Cat[rope0, rope1, rope1a], rope2, rope3, rope4]]; gvHandle.StartItem[LastItem]; gvHandle.Send[]; EXIT; } ELSE { gvHandle.Abort[]; EXIT; }; }; ENDLOOP; }; } ELSE sendGVMailAboutBug[Rope.Concat["missing config table entry for ", package]]; }; sendGVMailAboutBug: PROC [msg: ROPE] = { }; checkActives: ENTRY PROC[matchedItem: ActiveServicesItem] RETURNS [ok: BOOL ] = { IF matchedItem.packageEntry.exclusive AND ActiveServices > 0 THEN RETURN [FALSE]; IF matchedItem.packageEntry.nowActive >= matchedItem.packageEntry.maxCountActive THEN RETURN [FALSE]; matchedItem.packageEntry.nowActive _ matchedItem.packageEntry.nowActive + 1; ActiveServices _ ActiveServices + 1; RETURN [TRUE]; }; inActive: ENTRY PROC[matchedItem: ActiveServicesItem] = { matchedItem.packageEntry.nowActive _ matchedItem.packageEntry.nowActive - 1; ActiveServices _ ActiveServices - 1; }; DoService: PUBLIC PROC [serverPupAddress: PupDefs.PupAddress, clientNetAddressRope: RPC.ShortROPE, commandLine: ROPE, WorkingDirectory: Rope.ROPE, needRemoteInStream: BOOL, needRemoteOutStream: BOOL] RETURNS [success: ComputeServerClient.RemoteSuccess _ true, msg: ROPE _ NIL] = { matchedItem: ActiveServicesItem ; interface: ComputeServerCallbacksRpcControl.InterfaceRecord ; lastLoop: BOOL _ FALSE; ok: BOOL; in, out, err: STREAM _ NIL; inData, outData: BufStreamData ; serviceProcess: PROCESS; matchOK, deleteOK: BOOL _ FALSE; IF ~ComputeServerInternal.ServiceEnabled THEN RETURN [false, "Server is down"]; [found: matchOK, item: matchedItem] _ ComputeServerInternal.MatchPupAddress[serverPupAddress, TRUE]; IF ~matchOK THEN RETURN [false, "DoService call not matched by AskForService"]; ok _ checkActives[matchedItem]; IF ~ok THEN RETURN [serverTooBusy, "Server now to busy"]; ReportServerEvent[type: startService, command: matchedItem.commandEntry.service, startTime: matchedItem.startListenGMT, endTime: BasicTime.nullGMT, remoteMachineName: matchedItem.clientMachineName]; matchedItem.commandEntry.okToQueuePosted _ FALSE; IF matchedItem.commandEntry.doQueueing AND ~matchedItem.packageEntry.exclusive AND matchedItem.packageEntry.nowActive < matchedItem.packageEntry.maxCountActive THEN { controllerInterface: ComputeServerControllerRpcControl.InterfaceRecord; controllerInterface _ ComputeServerInternal.ControllerInterface; IF controllerInterface # NIL THEN { controllerInterface.MightAcceptQueuedCommand[serverMachineAddress: ComputeServerInternal.MyNetAddressRope, commandName: matchedItem.commandEntry.service ! RPC.CallFailed => CONTINUE;]; matchedItem.commandEntry.okToQueuePosted _ TRUE; }; }; IF needRemoteInStream OR needRemoteOutStream THEN { WHILE matchedItem.remoteStream = NIL DO Process.Pause[5]; ENDLOOP; }; IF needRemoteInStream THEN { in _ IO.CreateStream[streamProcs: ComputeServerInternal.inStreamProcs, streamData: inData _ NEW[BufStreamDataObject _ [listenerItem: matchedItem]]]; } ELSE { in _ IO.noInputStream; matchedItem.inEOF _ TRUE; }; IF needRemoteOutStream THEN { out _ IO.CreateStream[streamProcs: ComputeServerInternal.outStreamProcs, streamData: outData _ NEW[BufStreamDataObject _ [listenerItem: matchedItem]]]; } ELSE { out _ IO.noWhereStream; matchedItem.outEOF _ true; }; interface _ GetClientInterfaceFromCache[clientNetAddressRope]; IF interface = NIL THEN {inActive[matchedItem]; RETURN[false, "Cannot import Client Callbacks"]}; matchedItem.callbacksInterface _ interface; matchedItem.clientNetAddressRope _ clientNetAddressRope; BumpRequests[]; serviceProcess _ FORK ServiceProcess[in, out, err, commandLine, matchedItem, WorkingDirectory]; WHILE ~matchedItem.inEOF OR matchedItem.outEOF ~= true OR ~matchedItem.callOver DO doneSomething: BOOL _ FALSE; IF matchedItem.pleaseAbort THEN TRUSTED { Process.Abort[serviceProcess]; matchedItem.pleaseAbort _ FALSE; }; IF matchedItem.outEOF ~= true AND ComputeServerInternal.inCharsAvail[out, FALSE] > 0 THEN { WHILE ComputeServerInternal.inCharsAvail[out, FALSE] > 0 AND matchedItem.outEOF = false DO matchedItem.remoteStream.PutChar[ComputeServerInternal.inBufGetChar[out ! IO.EndOfStream => {matchedItem.outEOF _ pending; CONTINUE;}]]; ENDLOOP; matchedItem.remoteStream.Flush[! PupErrors.StreamClosing => { matchedItem.pleaseAbort _ TRUE; matchedItem.outEOF _ true; matchedItem.inEOF _ TRUE; matchedItem.success _ communicationFailure; CONTINUE; };]; IF matchedItem.outEOF = pending THEN { PupStream.SendMark[matchedItem.remoteStream, 27B ! PupErrors.StreamClosing => {CONTINUE;};]; matchedItem.outEOF _ true; }; doneSomething _ TRUE; }; WHILE ~matchedItem.inEOF AND matchedItem.remoteStream.CharsAvail[] > 0 AND (inData.inPointer - (inData.outPointer + 1)) MOD ComputeServerInternal.BufStreamBufferSize # 0 DO doneSomething _ TRUE; ComputeServerInternal.outBufPutChar[in, matchedItem.remoteStream.GetChar[! IO.EndOfStream => { inData.EOF _ pending; matchedItem.inEOF _ TRUE; CONTINUE;}; PupErrors.StreamClosing => { matchedItem.pleaseAbort _ TRUE; matchedItem.outEOF _ true; matchedItem.inEOF _ TRUE; matchedItem.success _ communicationFailure; CONTINUE; }; ]]; ENDLOOP; IF lastLoop THEN EXIT; IF matchedItem.callOver THEN { lastLoop _ TRUE; LOOP;}; IF ~doneSomething THEN Process.Pause[5]; ENDLOOP; IF matchedItem.remoteStream # NIL THEN matchedItem.remoteStream.Close[! IO.Error => CONTINUE;] ; msg _ matchedItem.msg; success _ matchedItem.success; ReportServerEvent[type: doneService, command: matchedItem.commandEntry.service, startTime: matchedItem.startListenGMT, endTime: BasicTime.Now[], remoteMachineName: matchedItem.clientMachineName]; inActive[matchedItem]; deleteOK _ ComputeServerInternal.DeletePupAddress[serverPupAddress]; TRUSTED {JOIN serviceProcess;}; IF matchedItem.commandEntry.doQueueing AND matchedItem.packageEntry.nowActive < matchedItem.packageEntry.maxCountActive AND ~matchedItem.commandEntry.okToQueuePosted THEN { controllerInterface: ComputeServerControllerRpcControl.InterfaceRecord; controllerInterface _ ComputeServerInternal.ControllerInterface; IF controllerInterface # NIL THEN { controllerInterface.MightAcceptQueuedCommand[serverMachineAddress: ComputeServerInternal.MyNetAddressRope, commandName: matchedItem.commandEntry.service ! RPC.CallFailed => CONTINUE;]; matchedItem.commandEntry.okToQueuePosted _ TRUE; }; }; }; ServiceProcess: PROC [in, out, err: STREAM, commandLine: ROPE, matchedItem: ActiveServicesItem, WorkingDirectory: ROPE] = { newPropertys: List.AList _ NIL; ServiceProcessInner: PROC [] = { ENABLE { UNWIND => {}; FS.Error => { IF ~((error.group = environment) AND (error.code = $serverInaccessible OR error.code = $serverUnmatchedComputation)) THEN REJECT; matchedItem.success _ communicationFailure; GOTO clientInaccessible; }; RPC.CallFailed => { matchedItem.success _ communicationFailure; GOTO clientInaccessible; }; ABORTED => { process: PROCESS = LOOPHOLE[Process.GetCurrent[]]; ComputeServerInternal.RemoveMarkProcessNotGuest[process]; GOTO aborted; }; }; startPriority: Process.Priority _ Process.priorityNormal; result: REF _ NIL; msg: Rope.ROPE _ NIL; cmd: Commander.Handle; cmd _ NEW[Commander.CommandObject _ [in, out, err, commandLine, matchedItem.procHandle.service, ProcessProps.GetPropList[], matchedItem.procHandle.commanderProcHandle]]; startPriority _ Process.GetPriority[]; Process.SetPriority[Process.priorityBackground]; [msg: matchedItem.msg] _ matchedItem.procHandle.commanderProcHandle.proc[cmd]; Process.SetPriority[startPriority]; in.Close[]; out.Close[]; EXITS aborted => { in.Close[]; out.Close[]; IF matchedItem.success # communicationFailure THEN { matchedItem.msg _ "call ABORTED"; matchedItem.success _ aborted; }; }; clientInaccessible => { in.Close[]; out.Close[]; matchedItem.msg _ "call ABORTED due to client becomming inaccessible"; matchedItem.success _ communicationFailure; }; }; IF Rope.Length[WorkingDirectory] > 0 THEN newPropertys _ List.PutAssoc[key: $WorkingDirectory , val: WorkingDirectory, aList: NIL]; [] _ ComputeServerInternal.MarkGuestProcess[LOOPHOLE[Process.GetCurrent[]], LOOPHOLE[matchedItem]]; ProcessProps.AddPropList[propList: newPropertys, inner: ServiceProcessInner]; [] _ ComputeServerInternal.MarkGuestProcess[LOOPHOLE[Process.GetCurrent[]], NIL]; matchedItem.callOver _ TRUE; }; AskForAbort: PUBLIC PROC [serverPupAddress: PupDefs.PupAddress] = { matchOK: BOOL _ FALSE; matchedItem: ActiveServicesItem ; [found: matchOK, item: matchedItem] _ ComputeServerInternal.MatchPupAddress[serverPupAddress, TRUE]; IF matchOK THEN matchedItem.pleaseAbort _ TRUE; }; GetClientInterfaceFromCache: PUBLIC ENTRY PROC [clientInstance: RPC.ShortROPE, forceNewInterface: BOOL _ FALSE] RETURNS [clientInterface: ComputeServerCallbacksRpcControl.InterfaceRecord _ NIL] = { newClientInterface: ComputeServerCallbacksRpcControl.InterfaceRecord _ NIL; bestIndex: INT _ 0; bestTime: BasicTime.GMT _ BasicTime.latestGMT; now: BasicTime.GMT _ BasicTime.Now[]; FOR index: INT IN [0..clientInterfaceCacheSize) DO IF Rope.Equal[clientInstance, clientInterfaceCache[index].clientInstance] THEN { IF forceNewInterface THEN { clientInterfaceCache[index].lastUsed _ BasicTime.earliestGMT; clientInterfaceCache[index].clientInstance _ NIL; clientInterfaceCache[index].interface _ NIL; bestIndex _ index; } ELSE { clientInterfaceCache[index].lastUsed _ now; RETURN[clientInterfaceCache[index].interface]; }; }; IF BasicTime.Period[bestTime, clientInterfaceCache[index].lastUsed] < 0 THEN { bestTime _ clientInterfaceCache[index].lastUsed; bestIndex _ index; }; ENDLOOP; newClientInterface _ ComputeServerCallbacksRpcControl.ImportNewInterface[ interfaceName: [ type: "ComputeServerCallbacks.summoner", instance: clientInstance, version: [1,1]] ! RPC.ImportFailed => { CONTINUE; }; ]; IF newClientInterface # NIL THEN { clientInterfaceCache[bestIndex].lastUsed _ now; clientInterfaceCache[bestIndex].clientInstance _ clientInstance; clientInterfaceCache[bestIndex].interface _ newClientInterface; }; RETURN[newClientInterface]; }; DeleteClientInterfaceFromCache: PUBLIC ENTRY PROC [clientInterface: ComputeServerCallbacksRpcControl.InterfaceRecord] RETURNS [gotIt: BOOL _ FALSE] = { IF clientInterface = NIL THEN RETURN[FALSE]; FOR index: INT IN [0..clientInterfaceCacheSize) DO IF clientInterfaceCache[index].interface = clientInterface THEN { clientInterfaceCache[index].clientInstance _ NIL; clientInterfaceCache[index].interface _ NIL; clientInterfaceCache[index].lastUsed _ BasicTime.earliestGMT; gotIt _ TRUE; EXIT; }; ENDLOOP; }; Register: PUBLIC PROC [key: ROPE, version: ROPE _ NIL, proc: Commander.CommandProc, doc: ROPE _ NIL, clientData: REF ANY _ NIL] = { found: BOOL; val: REF ANY; foundCmdTab: BOOL; tempControllerInterface: ComputeServerControllerRpcControl.InterfaceRecord _ NIL; IF key.IsEmpty[] THEN RETURN; IF proc = NIL THEN [] _ SymTab.Delete[x: ComputeServerServer.Registry, key: key] ELSE { regList: LIST OF ComputeServerInternal.RegisteredProcHandle; regProc: ComputeServerInternal.RegisteredProcHandle _ NEW[ComputeServerInternal.RegisteredProcObject _ [version: version, service: key, commanderProcHandle: NEW[Commander.CommandProcObject _ [proc: proc, doc: doc, clientData: clientData]]]]; [found: found, val: val] _ SymTab.Fetch[x: ComputeServerServer.Registry, key: key]; regList _ NARROW[val]; regList _ CONS[regProc, regList]; [] _ SymTab.Store[ x: ComputeServerServer.Registry, key: key, val: regList ]; [found: foundCmdTab] _ SymTab.Fetch[x: CommandTable, key: key]; IF ~foundCmdTab AND (tempControllerInterface _ ControllerInterface) # NIL THEN { tempControllerInterface.ExtraCommandAvailable[serverMachineName: ComputeServerInternal.MyNetAddressRope, commandName: key, version: version ! RPC.CallFailed => { SummonerControllerControl.ControllerCallFailed[why]; ControllerInterface _ NIL ; CONTINUE; }; ]; }; }; }; BumpRequests: ENTRY PROC = { ComputeServerStatistics.RequestesThisIncarnation _ ComputeServerStatistics.RequestesThisIncarnation + 1; ComputeServerStatistics.TotalRequestes _ ComputeServerStatistics.TotalRequestes + 1; }; NextServerEvent: PUBLIC ENTRY PROC [last: REF READONLY ComputeServerStatistics.ServerEvent _ NIL] RETURNS [REF ComputeServerStatistics.ServerEvent] = { IF last = NIL THEN { IF serverEventListTail = NIL THEN { watchingServer _ TRUE; UNTIL serverEventListTail # NIL DO WAIT anotherServerEvent ENDLOOP; RETURN [serverEventListTail]; } ELSE last _ serverEventListTail; }; UNTIL last.chain # NIL DO WAIT anotherServerEvent ENDLOOP; -- wait for next event RETURN [last.chain]; }; ReportServerEvent: PUBLIC ENTRY PROC [type: ComputeServerStatistics.ServerEventType, command: Rope.ROPE, startTime: BasicTime.GMT, endTime: BasicTime.GMT, remoteMachineName: Rope.ROPE] = { IF watchingServer THEN { eventREF: REF ComputeServerStatistics.ServerEvent = NEW [ ComputeServerStatistics.ServerEvent _ [type, command, startTime, endTime, remoteMachineName, NIL] ]; IF serverEventListTail # NIL THEN serverEventListTail.chain _ eventREF; serverEventListTail _ eventREF; BROADCAST anotherServerEvent; }; }; ProfileChanged: UserProfile.ProfileChangedProc = { SELECT reason FROM firstTime => { }; rollBack => { }; edit => { GetProfileConstants[]; }; ENDCASE; }; GetProfileConstants: PROC = { OKToRunBCDs _ UserProfile.Boolean[key: "Summoner.OKToRunBCDs", default: TRUE]; PackagesOKToRun _ UserProfile.ListOfTokens[key: "Summoner.PackagesOKToRun", default: NIL]; PackagesNotOKToRun _ UserProfile.ListOfTokens[key: "Summoner.PackagesNotOKToRun", default: NIL]; TryForFreeGFIs _ UserProfile.Number[key: "Summoner.FreeGFIs", default: 10]; OKToUseLocalDisk _ UserProfile.Boolean[key: "Summoner.OKToUseLocalDisk", default: FALSE]; }; AddPupAddress: PUBLIC ENTRY PROC [serverPupAddress: PupDefs.PupAddress, procHandle: ComputeServerInternal.RegisteredProcHandle, listener: PupListener] RETURNS [newItem: ActiveServicesItem] = { newItem _ NEW[ActiveServicesItemObject _ [next: ActiveServicesListBase, listenerPupAddress: serverPupAddress, listener: listener, startListenGMT: BasicTime.Now[], procHandle: procHandle, success: true]]; ActiveServicesListBase _ newItem; }; MatchPupAddress: PUBLIC ENTRY PROC [serverPupAddress: PupDefs.PupAddress, flagStarted: BOOL] RETURNS [found: BOOL, item: ActiveServicesItem] = { nowItem: ActiveServicesItem _ ActiveServicesListBase ; WHILE nowItem # NIL DO IF serverPupAddress = nowItem.listenerPupAddress THEN { IF flagStarted THEN nowItem.heardDoService _ TRUE; RETURN [TRUE, nowItem]; }; nowItem _ nowItem.next; ENDLOOP; RETURN[FALSE, NIL]; }; findDebuggerItemFromInterpreterHandle: PUBLIC ENTRY PROC [h: InterpreterToolPrivate.Handle] RETURNS [found: BOOL, item: ActiveServicesItem _ NIL] = { nowItem: ActiveServicesItem _ ActiveServicesListBase ; WHILE nowItem # NIL DO IF h = nowItem.h THEN RETURN [TRUE, nowItem]; nowItem _ nowItem.next; ENDLOOP; RETURN[FALSE, NIL]; }; KillOldUnstartedServices: PUBLIC PROC = { nowItem: ActiveServicesItem _ ActiveServicesListBase ; lastItem: ActiveServicesItem _ NIL; now: BasicTime.GMT _ BasicTime.Now[]; WHILE nowItem # NIL DO IF ~nowItem.heardDoService AND BasicTime.Period[nowItem.startListenGMT, now] > 77 THEN { IF nowItem.listener # NIL THEN ComputeServerInternal.DestroyPupListener[nowItem.listener]; nowItem.listener _ NIL; IF lastItem = NIL THEN ActiveServicesListBase _ nowItem.next ELSE lastItem.next _ nowItem.next; }; lastItem _ nowItem; nowItem _ nowItem.next; ENDLOOP; }; DeletePupAddress: PUBLIC ENTRY PROC [serverPupAddress: PupDefs.PupAddress] RETURNS [found: BOOL] = { nowItem: ActiveServicesItem _ ActiveServicesListBase ; lastItem: ActiveServicesItem _ NIL; WHILE nowItem # NIL DO IF serverPupAddress = nowItem.listenerPupAddress THEN { IF nowItem.listener # NIL THEN ComputeServerInternal.DestroyPupListener[nowItem.listener]; nowItem.listener _ NIL; IF lastItem = NIL THEN ActiveServicesListBase _ nowItem.next ELSE lastItem.next _ nowItem.next; RETURN[TRUE]; }; lastItem _ nowItem; nowItem _ nowItem.next; ENDLOOP; RETURN[FALSE]; }; Init: PROC = { clientInterfaceCache _ NEW[clientInterfaceArray]; CommandTable _ SymTab.Create[mod: 59, case: FALSE]; ComputeServerServer.RegisterRealRegistration[Register]; UserProfile.CallWhenProfileChanges[proc: ProfileChanged]; }; Init[]; END. pComputeServerImpl.mesa The Compute Server side of the Summoner. Last Edited by: Bob Hagmann, February 10, 1986 3:10:55 pm PST Copyright c 1984, 1985, 1986 by Xerox Corporation. All rights reserved. Variable Declarations Command Directory check to see if local top level .df file matches that stored on the server need a new packagelist process the .df file a line at a time in DoOneItem. Result is a LIST OF ROPE of packages. PROC [item: REF ANY] RETURNS [stop: BOOL _ FALSE] local .df file does not exist, or is not of the proper create date => bring it over need a new .df file open the file manually so that we can avoid the remoteCheck key was NOT followed by ':, so flush to the end of line and report the error ReportInternal[msg: IO.PutFR["missing : at [%d]", IO.int[position]]]; now the key is key, and the list of tokens is in tokens insert commands into tables Main Processing Routines do I really have to run it? If so, then run it with "runEvenIfAlreadyRun" as TRUE since we know it has to be run IF ~alreadyRun AND errMsg = NIL THEN [errMsg: errMsg, error: runError] _ CommandTool.Run[bcdName: localRunName, runEvenIfAlreadyRun: TRUE, runEvenIfUnbound: FALSE]; This does not do the "Run" in the sub-directory, so LFBoundingBox blows up packageEntry.maintainer is a LIST OF ROPE of the maintainters of the existing package, and configEntry.maintainer for package to be run interface _ ComputeServerCallbacksRpcControl.ImportNewInterface[ interfaceName: [ instance: clientNetAddressRope] ! RPC.ImportFailed => { CONTINUE; }; ]; copy from the internal stream to the remote stream copy from the remote stream to the internal stream RuntimeError.UNCAUGHT => { MyCatcher[msg: parameters, signal: signal, frame: PrincOps.MyLocalFrame[]]; }; Command Registry Statistics first call for this client no events in list yet Profile/Rollback Restart PROC [reason: ProfileChangeReason]; Active Services List Maintence Initialization Bob Hagmann February 12, 1985 2:09:54 pm PST changes to: DoService Bob Hagmann January 8, 1986 3:48:30 pm PST Added run item to error message during failure to load changes to: loadPackage Bob Hagmann January 17, 1986 8:25:25 am PST Changed message about running all the bcd's and still not finding command to be more explicit changes to: AskForService Bob Hagmann January 29, 1986 9:55:52 am PST ignore "Unbound imports" errors during package load changes to: loadPackage Ê0¼– "Cedar" style˜head™Ibodyšœ(™(L™=Jšœ Ïmœ=™Hcode2šÏk ˜ M˜Mšœ˜M˜ M˜M˜ M˜ Mšœ˜Mšœ˜Mšœ!˜!M˜Mšœ"˜"Mšœ˜Mšœ˜M˜Mšœ˜Mšœ ˜ M˜Mšœ ˜ Mšœ ˜ Mšžœ˜M˜ M˜Mšœ˜Mšžœ˜M˜M˜ M˜ M˜M˜ Mšœ˜M˜ M˜ M˜ M˜Mšœ˜M˜Mšžœ˜Mšœ˜Mšœ ˜ Mšžœ˜Mšœ ˜ Mšœ˜——šœžœž˜ Mš žœÞžœ žœjžœ(žœ˜ŸMšžœT˜[Mšžœ˜Mšœž˜—™Mšžœžœžœžœ˜Icodešžœžœžœ˜N˜N˜Nšœžœžœžœ˜$Nšœžœ5žœ˜TIcode1˜Ošœžœ,˜DOšœžœ,žœ˜NNšœžœ2˜POšœ žœžœ)˜@J˜JšœžœžœÏc%˜GJ˜J˜Jšœžœ'˜:Jšœžœ(˜žœ˜BJšœžœ˜/J˜J˜—Jšœžœ˜#J˜Jšœžœžœžœ˜XJšœžœ˜/J˜Jšœ žœžœ˜Jš œžœžœžœžœ˜$Jš œžœžœžœžœ˜'Jšœžœ˜Jšœžœžœžœ˜&J˜Jšœž œ˜Jšœžœ'žœ˜CJšœžœžœ˜ J˜šœ žœžœ˜Jšœ žœ˜Jšœžœ˜ J˜—Jš œ žœžœžœžœ ˜(—™defaultšÏn œžœžœ6žœžœžœžœ˜rPšœžœ˜'Pšœžœ˜Pšœ žœ˜Pšœžœ˜Pšœžœžœ˜Pšœ!žœ˜%Pšœ žœ˜8Pš œ žœžœžœžœ˜Pš œžœžœžœžœ˜!Pšœ žœžœ˜šÏbœ˜&Mš œžœ žœžœžœžœ˜/—š œžœžœžœžœ žœ˜UM˜(Mšžœžœ žœ(˜;Mšžœžœ,˜OM˜#Mšžœžœ(˜GMšžœžœ(˜GMšœ˜—P˜˜P™J—P˜Pšœ˜š žœžœ&žœ;žœžœžœ˜©Pšœ3˜3P˜—šœžœ%žœ ˜9PšœH˜HPšžœ ˜Pšœ˜—Pšœ˜š žœžœ%žœ:žœžœžœ˜¥Pšœ*˜*P˜—šœ!žœ2žœ ˜bPšœF˜FPšžœ ˜Pšœ˜—Pšœ˜šœ!˜!PšœÓžœžœžœ˜ãPšœ ž˜—Pšœ˜šœ#žœBžœ ˜tPšœ\˜\Pšžœ˜Pšœ˜—PšœG˜GPš œ"žœ3žœžœ žœ˜ušžœ*žœ.žœ˜bP™š œžœ`žœBžœ žœžœ ˜ÑPšœO˜OPšžœ˜Pšœ˜—Pšœ˜P˜—šœžœ#žœ ˜FPšœV˜VPšžœ˜P˜—šœ˜JšœZ™Z—JšœGžœ˜NJšžœ žœžœ˜1Jšœ,žœ˜3Jšœ,žœ˜3Jšœ+žœ˜2šžœ,žœžœž˜JJšœ(˜(Jšžœ˜—šž˜Pšœ˜Pšœ˜Pšœžœ"˜EPšœ˜—P˜—P˜š œžœžœžœžœžœ žœžœžœžœžœžœ˜–Pšœ žœ˜$š¡ œ!˜*Pš žœžœžœžœžœžœ™1Pšœ"žœ˜*Pšœ žœ˜š¡œžœ˜Jšœ{˜{J˜—šžœžœž˜šœžœ˜'Pšœ˜P˜—šœžœ˜#Pšœ žœB˜QPšœ žœ<˜IPšœžœ˜1Pšœžœ˜1Pšœ0˜0Pš œžœQžœžœ žœ˜Œšžœžœžœ#žœ˜kP™SPšœ˜Pšœgžœ˜lPšœD˜DPšžœ žœ žœE˜gP˜—Pš œžœžœ žœ žœ˜=P˜—Pšžœ˜—P˜—PšœVžœ˜ušž˜Pšœ*žœ˜0—Pšœ˜—P˜š¡œžœžœ˜Pšœ!žœ˜%Pšœžœžœ˜!Pšœ žœ˜8Pšœžœžœ˜Pšœ žœžœ˜Pš œ žœžœžœžœ˜Pš œžœžœžœžœ˜!P˜Pšœ#žœBžœ žœ˜}PšœG˜GPš œ"žœ3žœžœ žœ˜ušžœ*žœ.žœ˜bP™Pš œžœ`žœBžœ žœžœ žœ˜éPšœžœ#žœ žœ˜_PšœGžœ˜MPšžœ žœžœ˜1šžœ,žœžœž˜JJšœ(˜(Jšžœ˜—J˜—šž˜Pšœ˜Pšœ˜Pšœ˜—P˜—P˜š¡œžœ žœ˜-Pšœžœžœ˜PšœžœG˜bPšœ žœ;˜JPš œ žœžœžœžœ˜Pšœžœžœ˜Pš œ žœžœžœžœ˜Pš œ žœžœžœžœ˜Pš œžœžœžœžœ˜!Pšœ žœžœ˜Pšœ žœ ˜Pšœ˜Pšœžœ ˜P˜šœŸ/ÐcsŸ˜QPšœ;™;Pšœžœ˜,Pšœžœžœ žœ˜KPš œžœ0žœžœ žœ ˜gPšœžœ0žœ žœ ˜ašž˜Jšœžœžœ˜Jš œžœžœžœžœ˜!Jšœžœžœ˜Jšœ3žœ˜9Jšžœžœžœžœ˜!šžœ+ž˜5Jšœ)Ÿ˜8šžœ˜ JšœL™Lšž˜Jš žœžœžœžœžœ˜HJšžœ˜—Jšœžœžœ™EJšžœ˜Jšœ˜——šž˜Jš œžœžœžœžœ˜Jšœ3˜3Jšžœ žœžœžœ˜Jšœžœ˜šžœž˜ Jšžœ˜Jšžœ!˜%—Jšžœ˜J˜Jšœ7™7—šžœžœž˜šœžœ˜&šžœž˜ Jšžœ˜Jšžœ)˜-—J˜—šœžœ˜)šžœž˜ Jšžœ˜Jšžœ/˜3—J˜—šœžœ˜'šžœž˜ Jšžœ˜Jšžœ+˜/—J˜—šœžœ˜(šžœžœžœžœ˜&šžœ&ž˜0Jšœžœ˜Jšœžœ˜Jšžœ˜ Jšœ˜——J˜—šœžœ˜*šžœžœžœžœ˜&Jšœžœ˜ Jšœžœžœ˜JšœAžœžœ˜RJšžœžœ žœ˜/Jšœ˜—J˜—J˜Jšžœ˜—J˜J˜JšžœŸ ˜*—™J™—Jšžœ žœžœžœ˜2JšœžœG˜YJšœ#˜#Jšœ*˜*JšœD˜Dšžœ,žœžœž˜JJšœžœ˜Jšœ˜Jšœ žœ(˜6Jšœ<˜š žœ žœžœžœ"žœ žœž˜Sšžœ%žœžœ˜4Jšœ žœ˜Jšžœ˜Jšœ˜—Jšžœ˜—J˜—J˜—šžœžœ˜Jšœžœ˜ Jšœ-˜-Mšžœžœ,˜6M˜—šœžœ˜JšœG˜GMšœ@˜@MšœF˜FMšœ˜Mšœ-˜-šžœžœžœŒžœ˜ÀJšœ4˜4Jšœžœ˜Jšžœ˜ Jšœ˜—J˜Mšžœ˜M˜—J˜—J˜—Jšœžœ(˜-J˜—šžœžœžœ˜JšœG˜GMšœ@˜@Jšžœžœ»˜ÓJšžœžœžœ˜FJšœ-˜-šžœžœžœŒžœ˜ÀJšœ4˜4Jšœžœ˜Jšžœ˜ Jšœ˜—Jšœ˜Jšžœ˜J˜—šžœ žœžœ˜Jšœžœ˜JšœX˜XJšžœ žœžœV˜oJšœžœ˜-Jšžœžœžœžœ^˜šJšžœ7žœžœ\˜ J˜—šœžœ˜Jšœ žœ_žœžœ˜ˆJšœžœ˜,J˜—MšœP˜PMšžœžœÙ˜íMšœV˜VMšœ.˜.Mšœ$˜$Mšœ ˜ Mšœ˜—š ¡ œžœ žœ žœ žœ;žœ˜‚Mšœžœ˜ Mšœ žœžœ˜Mšœžœžœ,˜IMšœ[˜[šžœžœ žœžœ˜!Jšœ žœžœ,˜=Jšœžœ ˜'šžœ0žœ žœž˜JJš žœ,žœ-žœžœžœ˜Jšžœ˜—M˜—M˜—š¡ œžœ žœ žœžœžœžœžœžœ˜gMšœžœ-˜AMšœ&˜&Mšœ žœE˜VMšœ žœžœ˜Mšœ žœ˜Mšœ˜Mšœžœžœ˜Mšœžœ˜Mšœ žœžœ˜Mšœžœžœ˜Mšœžœ˜Mšœ žœ˜Mšœžœ˜Mšœžœžœ˜MšœO˜OMšœžœ˜-Mšžœ žœžœ2žœžœžœžœG˜ÅMšœ5˜5Mšœžœžœ žœ˜Gšžœž˜Mšœžœžœ˜Mš œ0žœ ÐkrÏrœ¥¤¥œ˜qšžœžœ˜šžœžœ#žœ˜@Mšœ&žœ!¥¤¥œ˜WMšžœ˜M˜—M˜—šžœ ž˜šœ˜Nšžœžœžœ˜8Nšœ˜Nšœ˜Nšœ˜N˜—šœ ˜ Nšœ:¤œ˜ENšœ˜Nšžœ˜Nšœ˜—šœ ˜ Nšžœžœžœ˜9Nšœ˜N˜—Nšœ!˜!Nšžœ˜—šžœžœžœ˜2šžœžœžœ˜Mšœ žœP˜_šžœžœ˜Mšžœ)žœžœ˜HMšžœžœžœ˜žœžœžœžœžœžœ:žœžœ˜˜Mšœ!˜!Mšœ=˜=Mšœ žœžœ˜Mšœžœ˜ Mšœžœžœ˜Mšœ!˜!Mšœžœ˜Mšœžœžœ˜ Mšžœ'žœžœ˜OMšœ^žœ˜dMšžœ žœžœ8˜OMšœ˜Mšžœžœžœ'˜9MšœÆ˜ÆMšœ+žœ˜1šžœ%žœ%žœNžœ˜¦MšœG˜GMšœ@˜@šžœžœžœ˜#Mšœ›žœžœ˜¸Jšœ+žœ˜0J˜—M˜—šžœžœžœ˜3šžœžœž˜'M˜Mšžœ˜—M˜—šžœžœ˜MšœžœUžœ5˜”M˜—šœžœ˜Mšœžœ˜Mšœžœ˜M˜—šžœžœ˜MšœžœWžœ5˜—M˜—šœžœ˜Mšœžœ˜Mšœ˜M˜—Mšœ>˜>šœ@™@Mšœ0™0šœžœ™Mšžœ™ Mšœ™—Mšœ™—Mšžœ žœžœžœ+˜aMšœ+˜+Mšœ8˜8Mšœ˜MšœžœJ˜_šžœžœžœž˜RMšœžœžœ˜šžœžœžœ˜)Mšœ˜Mšœžœ˜ M˜M™2—šžœžœ)žœžœ˜\šžœ)žœžœž˜ZMšœJžœ/žœ˜ˆMšžœ˜—šœ=˜=Mšœžœ˜Mšœ˜Mšœžœ˜Mšœ+˜+Mšžœ˜ Mšœ˜—šžœžœ˜&MšœOžœ˜\Mšœ˜M˜—Mšœžœ˜Mšœ˜M™2—š žœžœ+žœ.žœ/ž˜¬Mšœžœ˜šœJ˜Jšžœ˜Mšœžœ ˜Mšœžœ˜Mšžœ˜ —šœ˜Mšœžœ˜Mšœ˜Mšœžœ˜Mšœ+˜+Mšžœ˜ Mšœ˜—Mšœ˜——Mšžœ˜Mšžœ žœžœ˜Mšžœžœžœžœ˜7Mšžœžœ˜(Mšžœ˜—Mš žœžœžœ"žœ žœ˜aMšœ˜Mšœ˜MšœÃ˜ÃMšœ˜MšœD˜DMšžœžœ˜šžœ%žœOžœ+žœ˜­MšœG˜GMšœ@˜@šžœžœžœ˜#Mšœ›žœžœ˜¸Jšœ+žœ˜0J˜—M˜—M˜—š  œžœžœžœ5žœ˜|Mšœžœ˜š œžœ˜ šžœ˜Mšžœ˜ šžœ ˜ Mš žœžœ#žœ,žœžœ˜Mšœ+˜+Mšžœ˜M˜—šžœ˜Mšœ+˜+Mšžœ˜M˜—šœ žœ™MšœK™KMšœ™—šžœ˜ Mšœ žœžœ˜2Mšœ9˜9Mšžœ ˜ Mšœ˜—Mšœ˜—Mšœ9˜9Mšœžœžœ˜Mšœ žœžœ˜Mšœ˜Mšœžœ ˜©M˜Mšœ&˜&Mšœ0˜0MšœN˜NMšœ#˜#M˜ M˜ M˜šž˜šœ ˜ M˜ M˜ šžœ,žœ˜4Mšœ!˜!Mšœ˜M˜—M˜—šœ˜M˜ M˜ MšœF˜FMšœ+˜+M˜——Mšœ˜—Mšžœ#žœUžœ˜ƒMšœ,žœžœ˜cMšœM˜MMšœ,žœžœ˜QMšœžœ˜M˜—š  œžœžœ+˜CMšœ žœžœ˜Mšœ!˜!Mšœ^žœ˜dMšžœ žœžœ˜/Mšœ˜—N˜š£œžœžœžœžœžœžœžœFžœ˜ÅMšœGžœ˜KMšœ žœ˜Mšœžœ˜.Mšœžœ˜%šžœžœžœž˜2šžœHžœ˜Pšžœžœ˜Mšœ>˜>Mšœ-žœ˜1Mšœ(žœ˜,Mšœ˜Mšœ˜—šœžœ˜Mšœ+˜+Mšžœ(˜.M˜—M˜—šžœFžœ˜NMšœ0˜0Mšœ˜M˜—Mšžœ˜—šœI˜IMšœc˜cšœžœ˜Mšžœ˜ Mšœ˜—M˜—šžœžœžœ˜"Mšœ0˜0Mšœ@˜@Mšœ?˜?M˜—Mšžœ˜M˜—š£œžœžœžœEžœ žœžœ˜—Mš žœžœžœžœžœ˜,šžœžœžœž˜2šžœ9žœ˜AMšœ-žœ˜1Mšœ(žœ˜,Mšœ=˜=Mšœžœ˜ Mšžœ˜M˜—Mšžœ˜—M˜—N˜—™š£œžœž¡œžœ žœžœ$žœžœžœžœžœ˜ƒNšœžœ˜ Nšœžœžœ˜ Nšœ žœ˜NšœMžœ˜QNšžœžœžœ˜šžœžœžœ@žœ˜XJšœ žœžœ,˜