DIRECTORY AMModel, Basics, BasicTime, BcdDefs, CedarProcess, Commander, CommandTool, ComputeServerClient, ComputeServer, ComputeServerCallbacksRpcControl, ComputeServerControl, ComputeServerControllerRpcControl, ComputeServerInternal, ComputeServerServer, ComputeServerStatistics, ComputeUtils, Convert, DFOperations, DFUtilities, FS, GVBasics, GVSend, InterpreterToolPrivate, IO, List, LoadState, PrincOps, Process, ProcessProps, Pup USING [Address, nullAddress], PupName USING [MyName, MyRope], PupStream USING [AllocateSocket, LocalAddress, Sockets, SendMark, StreamClosing, waitForever, WaitForRendezvous], SummonerControllerControl, SymTab, RefText, Rope, RPC, UserCredentials, UserProfile, VM, WorldVM; ComputeServerImpl: CEDAR MONITOR IMPORTS AMModel, BasicTime, CedarProcess, CommandTool, ComputeServerCallbacksRpcControl, ComputeServerControllerRpcControl, ComputeServerInternal, ComputeServerServer, ComputeServerStatistics, ComputeUtils, Convert, DFOperations, DFUtilities, FS, GVSend, IO, List, LoadState, Process, ProcessProps, PupName, PupStream, RefText, Rope, RPC, SummonerControllerControl, SymTab, UserCredentials, UserProfile, VM, 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; myHostName: PUBLIC ROPE _ PupName.MyName[]; MyNetAddressRope: PUBLIC ROPE _ PupName.MyRope[]; ActiveServicesItem: TYPE = ComputeServerInternal.ActiveServicesItem; ActiveServicesListBase: PUBLIC ComputeServerInternal.ActiveServicesItem _ NIL; ActiveServicesItemObject: TYPE = ComputeServerInternal.ActiveServicesItemObject; ActiveServices: PUBLIC INT _ 0 ; -- count of currently active services LastActiveTime: PUBLIC BasicTime.GMT _ BasicTime.Now[]; CurrentRequests: PUBLIC LIST OF ComputeServer.Request _ NIL; 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; DisableIFIdle: PUBLIC BOOL _ TRUE; DisableIFIdleAfter: PUBLIC INT _ 1900; DisableIFIdleBefore: PUBLIC INT _ 700; anotherServerEvent: CONDITION; serverEventListTail: REF ComputeServerStatistics.ServerEvent _ NIL; watchingServer: BOOLEAN _ FALSE; RunListItem: TYPE = RECORD [ package: ROPE, gfi: INT _ 0 ]; RunList: TYPE = LIST OF REF RunListItem; inStreamProcs: PUBLIC REF IO.StreamProcs _ IO.CreateStreamProcs[ variety: $input, class: $ROPE, getChar: inBufGetChar, endOf: inBufEndOf, charsAvail: inCharsAvail, backup: inBackup, getIndex: inBufGetIndex, close: inBufClose ]; outStreamProcs: PUBLIC REF IO.StreamProcs _ IO.CreateStreamProcs[ variety: $output, class: $ROPE, putChar: outBufPutChar, unsafePutBlock: outBufUnsafePutBlock, flush: outBufFlush, getIndex: outBufGetIndex, close: outBufClose ]; 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]Packages1>"; }; [] _ 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, userName: RPC.ShortROPE] RETURNS [found: ATOM _ $foundOK, serverPupAddress: Pup.Address, errMsg: Rope.ROPE _ NIL] = { sockets: PupStream.Sockets _ NIL; newItem: ActiveServicesItem; foundInCmdTable: BOOL; versionEmpty: 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, Pup.nullAddress, "Server not up"]; ComputeServerInternal.LastActiveTime _ BasicTime.Now[]; procHandle _ findCommand[service, version]; [found: foundInCmdTable, val: cmdVal] _ SymTab.Fetch[x: CommandTable, key: service]; cmdEntry _ NARROW[cmdVal]; IF cmdEntry # NIL THEN package _ cmdEntry.package; versionEmpty _ Rope.InlineIsEmpty[version]; IF procHandle = NIL OR (versionEmpty AND foundInCmdTable AND cmdEntry.firstTime) THEN { IF foundInCmdTable THEN { IF versionEmpty OR Rope.Equal[cmdEntry.version, version, FALSE] THEN { tryRun: BOOL _ OKToRunBCDs; IF ~versionEmpty 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 { IF procHandle = NIL THEN { controllerInterface: ComputeServerControllerRpcControl.InterfaceRecord; controllerInterface _ ComputeServerInternal.ControllerInterface; errMsg _ "Command not now running on Server, and running not enabled"; found _ $foundButTooBusy; serverPupAddress _ Pup.nullAddress; 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 _ Pup.nullAddress; 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, Pup.nullAddress, "Package missing from Package table on Server"]; packageEntry _ NARROW[valPack, PackageEntry]; IF packageEntry.exclusive AND ActiveServices > 0 THEN RETURN[$foundButTooBusy, Pup.nullAddress, "Cannot get exclusive package access to server"]; IF packageEntry.nowActive >= packageEntry.maxCountActive THEN RETURN[$foundButTooBusy, Pup.nullAddress, "Too many commands running from this package"]; } ELSE { cmdEntry _ NEW[CmdEntryObject _ [service: service, package: Rope.Concat["Package for ", service], firstTime: FALSE, doQueueing: TRUE]]; packageEntry _ NEW[PackageEntryObject _ []]; }; sockets _ PupStream.AllocateSocket[Pup.nullAddress]; serverPupAddress _ PupStream.LocalAddress[sockets]; newItem _ AddPupAddress[serverPupAddress, procHandle, sockets]; newItem.clientMachineName _ clientMachineName; newItem.packageEntry _ packageEntry; newItem.commandEntry _ cmdEntry; newItem.userName _ userName; AddRequest[newItem, service, serverPupAddress, userName]; }; 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] = { countGFI: PROC RETURNS [free: NAT] = TRUSTED { free _ 0; FOR i: CARDINAL DECREASING IN [1..PrincOps.SD[PrincOps.sGFTLength]) DO item: PrincOps.GFTItem = PrincOps.GFT[i]; IF item.data = 0 THEN free _ free + 1; ENDLOOP; }; file: FS.OpenFile _ FS.nullOpenFile; length: INT; gfiFree: NAT; gfiFree _ countGFI[]; IF (noGFIs + TryForFreeGFIs) > 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: Pup.Address, 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] _ 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, userName: matchedItem.userName]; 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 { matchedItem.remoteStream _ PupStream.WaitForRendezvous[sockets: matchedItem.sockets, getTimeout: 1000, putTimeout: PupStream.waitForever, waitTimeout: PupStream.waitForever ! PupStream.StreamClosing => { ok _ FALSE; msg _ Rope.Concat["Failed to make a remote stream because ", text]; CONTINUE; }; ]; }; IF ~ok THEN RETURN [communicationFailure, msg]; 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 { DO IF acquireOutStream[matchedItem] THEN EXIT; Process.Pause[1]; ENDLOOP; WHILE ComputeServerInternal.inCharsAvail[out, FALSE] > 0 AND matchedItem.outEOF = false DO matchedItem.remoteStream.PutChar[ComputeServerInternal.inBufGetChar[out ! IO.EndOfStream => {matchedItem.outEOF _ pending; CONTINUE;}] ! PupStream.StreamClosing => { matchedItem.pleaseAbort _ TRUE; matchedItem.outEOF _ true; matchedItem.inEOF _ TRUE; matchedItem.success _ communicationFailure; CONTINUE; }; ]; ENDLOOP; matchedItem.remoteStream.Flush[! PupStream.StreamClosing => { matchedItem.pleaseAbort _ TRUE; matchedItem.outEOF _ true; matchedItem.inEOF _ TRUE; matchedItem.success _ communicationFailure; CONTINUE; }; ]; IF matchedItem.outEOF = pending THEN { PupStream.SendMark[matchedItem.remoteStream, 27B ! PupStream.StreamClosing => {CONTINUE;};]; matchedItem.outEOF _ true; }; doneSomething _ TRUE; freeOutStream[matchedItem]; }; IF matchedItem.flushCounter > 0 THEN { matchedItem.flushCounter _ matchedItem.flushCounter - 1; IF matchedItem.flushCounter <= 0 THEN { DO IF acquireOutStream[matchedItem] THEN EXIT; Process.Pause[1]; ENDLOOP; matchedItem.remoteStream.Flush[! PupStream.StreamClosing => { matchedItem.pleaseAbort _ TRUE; matchedItem.outEOF _ true; matchedItem.inEOF _ TRUE; matchedItem.success _ communicationFailure; CONTINUE; }; ]; IF matchedItem.outEOF = pending THEN { PupStream.SendMark[matchedItem.remoteStream, 27B ! PupStream.StreamClosing => {CONTINUE;};]; matchedItem.outEOF _ true; }; doneSomething _ TRUE; freeOutStream[matchedItem]; }; }; 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;}; PupStream.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[3]; ENDLOOP; IF matchedItem.remoteStream # NIL THEN matchedItem.remoteStream.Close[TRUE ! 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, userName: matchedItem.userName]; inActive[matchedItem]; ComputeServerInternal.LastActiveTime _ BasicTime.Now[]; deleteOK _ 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: CedarProcess.Priority _ normal; 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 _ CedarProcess.GetPriority[]; CedarProcess.SetPriority[background]; [msg: matchedItem.msg] _ matchedItem.procHandle.commanderProcHandle.proc[cmd]; CedarProcess.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: Pup.Address] = { matchOK: BOOL _ FALSE; matchedItem: ActiveServicesItem ; [found: matchOK, item: matchedItem] _ MatchPupAddress[serverPupAddress, TRUE]; IF matchOK THEN matchedItem.pleaseAbort _ TRUE; }; GenericClientToServer: PUBLIC PROC [requestCode: ATOM, requestString: Rope.ROPE] RETURNS [resultCode: ATOM, resultString: Rope.ROPE] = { RETURN [$notImplemented, NIL]; }; 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; }; ]; }; }; }; inBufGetChar: PUBLIC PROC [self: STREAM] RETURNS [ch: CHAR] = { data: BufStreamData = NARROW[self.streamData]; DO IF data.EOF = true THEN ERROR IO.EndOfStream[self]; IF data.EOF = pending AND data.inPointer = data.outPointer THEN { data.EOF _ true; ERROR IO.EndOfStream[self]; }; IF data.backUpChars # NIL THEN { ch _ data.backUpChars.first; data.backUpChars _ data.backUpChars.rest; RETURN; }; IF data.inPointer # data.outPointer THEN { ch _ data.buffer[LOOPHOLE[Basics.DoubleAnd[LOOPHOLE[data.inPointer], LOOPHOLE[ComputeServerInternal.BufStreamBufferSizeMask]], INT]]; data.inPointer _ data.inPointer + 1; RETURN; } ELSE IF data.listenerItem.success = communicationFailure THEN { Process.CheckForAbort[]; ERROR IO.Error[ec: Failure, stream: self]; }; Process.Pause[5]; ENDLOOP; }; inBackup: PROC [self: STREAM, char: CHAR] = { data: BufStreamData = NARROW[self.streamData]; data.backUpChars _ CONS[char, data.backUpChars]; }; inBufEndOf: PROC [self: STREAM] RETURNS [BOOL] = { data: BufStreamData = NARROW[self.streamData]; WHILE data.EOF = false AND data.inPointer = data.outPointer DO Process.Pause[5]; ENDLOOP; IF data.EOF = true OR data.inPointer = data.outPointer THEN RETURN[TRUE] ELSE RETURN[FALSE]; }; inCharsAvail: PUBLIC PROC [self: STREAM, wait: BOOL] RETURNS [INT] = { data: BufStreamData = NARROW[self.streamData]; RETURN[(IF data.EOF # false THEN 1 ELSE 0) + data.outPointer - data.inPointer]; }; inBufGetIndex: PROC [self: STREAM] RETURNS [INT] = { data: BufStreamData = NARROW[self.streamData]; RETURN[data.inPointer]; }; inBufClose: PROC [self: STREAM, abort: BOOL] = { data: BufStreamData = NARROW[self.streamData]; data.EOF _ true; }; acquireOutStream: ENTRY PROC [asi: ActiveServicesItem] RETURNS [gotIt: BOOL] = { IF asi.outStreamBusy THEN RETURN [FALSE] ELSE { asi.outStreamBusy _ TRUE; RETURN [TRUE]; }; }; freeOutStream: ENTRY PROC [asi: ActiveServicesItem] = { asi.outStreamBusy _ FALSE; }; outBufPutChar: PUBLIC PROC [self: STREAM, char: CHAR] = { data: BufStreamData = NARROW[self.streamData]; WHILE (data.inPointer - (data.outPointer + 1)) MOD ComputeServerInternal.BufStreamBufferSize = 0 DO IF data.listenerItem.success = communicationFailure THEN RETURN; -- ignore output once communications fails Process.Pause[2]; ENDLOOP; data.buffer[LOOPHOLE[Basics.DoubleAnd[LOOPHOLE[data.outPointer], LOOPHOLE[ComputeServerInternal.BufStreamBufferSizeMask]], INT]] _ char; data.outPointer _ data.outPointer + 1; }; outBufUnsafePutBlock: PUBLIC PROC [self: STREAM, block: IO.UnsafeBlock] = { data: BufStreamData = NARROW[self.streamData]; doPutChars: BOOL _ TRUE; IF data.inPointer = data.outPointer THEN { asi: ActiveServicesItem = data.listenerItem; stream: IO.STREAM = asi.remoteStream; buffer: REF TEXT; DO IF acquireOutStream[asi] THEN EXIT; Process.Pause[1]; ENDLOOP; doPutChars _ FALSE; buffer _ RefText.ObtainScratch[block.count]; TRUSTED { [] _ PrincOpsUtils.ByteBlt[ to: [blockPointer: LOOPHOLE[buffer, LONG POINTER]+TEXT[0].SIZE, startIndex: 0, stopIndexPlusOne: block.count], from: [blockPointer: block.base, startIndex: block.startIndex, stopIndexPlusOne: block.startIndex + block.count]]; }; buffer.length _ block.count; stream.PutBlock[buffer, 0 , block.count ! PupStream.StreamClosing => { doPutChars _ TRUE; CONTINUE}; ]; RefText.ReleaseScratch[buffer]; IF ~doPutChars THEN data.listenerItem.flushCounter _ 5; freeOutStream[asi]; }; IF doPutChars THEN { FOR i: INT IN [block.startIndex .. block.startIndex + block.count) DO TRUSTED {self.PutChar[LOOPHOLE[block.base[i]]];}; ENDLOOP; }; }; outBufFlush: PROC [self: STREAM] = { data: BufStreamData = NARROW[self.streamData]; asi: ActiveServicesItem = data.listenerItem; stream: IO.STREAM = asi.remoteStream; DO IF acquireOutStream[asi] THEN EXIT; Process.Pause[1]; ENDLOOP; stream.Flush[]; freeOutStream[asi]; }; outBufGetIndex: PROC [self: STREAM] RETURNS [INT] = { data: BufStreamData = NARROW[self.streamData]; RETURN[data.outPointer]; }; outBufClose: PROC [self: STREAM, abort: BOOL] = { data: BufStreamData = NARROW[self.streamData]; IF data.EOF = false THEN data.EOF _ pending; }; 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, userName: Rope.ROPE] = { IF watchingServer THEN { eventREF: REF ComputeServerStatistics.ServerEvent = NEW [ ComputeServerStatistics.ServerEvent _ [type, command, startTime, endTime, remoteMachineName, userName, 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]; DisableIFIdle _ UserProfile.Boolean[key: "Summoner.ServerOffIfIdle", default: FALSE] AND ~UserProfile.Boolean["Watch.powerOffInhibit", FALSE]; DisableIFIdleAfter _ UserProfile.Number["Watch.powerOffAfter", 1900]; SELECT DisableIFIdleAfter FROM < 0 => DisableIFIdleAfter _ 0; > 2400 => DisableIFIdleAfter _ 2400; ENDCASE; DisableIFIdleBefore _ UserProfile.Number["Watch.powerOffBefore", 700]; SELECT DisableIFIdleBefore FROM < 0 => DisableIFIdleBefore _ 0; > 2400 => DisableIFIdleBefore _ 2400; ENDCASE; }; AddPupAddress: PUBLIC ENTRY PROC [serverPupAddress: Pup.Address, procHandle: ComputeServerInternal.RegisteredProcHandle, sockets: PupStream.Sockets] RETURNS [newItem: ActiveServicesItem] = { newItem _ NEW[ActiveServicesItemObject _ [next: ActiveServicesListBase, listenerPupAddress: serverPupAddress, sockets: sockets, startListenGMT: BasicTime.Now[], procHandle: procHandle, success: true]]; ActiveServicesListBase _ newItem; }; AddRequest: PUBLIC ENTRY PROC [item: ActiveServicesItem, service: ROPE, serverPupAddress: Pup.Address, userName: ROPE] = { CurrentRequests _ CONS[[service, serverPupAddress, userName], CurrentRequests]; item.request _ CurrentRequests; }; MatchPupAddress: PUBLIC ENTRY PROC [serverPupAddress: Pup.Address, 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 = { findAnOldService: ENTRY PROC = { nowItem: ActiveServicesItem _ ActiveServicesListBase ; now: BasicTime.GMT _ BasicTime.Now[]; WHILE nowItem # NIL DO IF ~nowItem.heardDoService AND BasicTime.Period[nowItem.startListenGMT, now] > 77 THEN { nowItem.sockets _ NIL; oldServerPupAddress _ nowItem.listenerPupAddress; RETURN; }; nowItem _ nowItem.next; ENDLOOP; }; oldServerPupAddress: Pup.Address; DO oldServerPupAddress _ Pup.nullAddress; findAnOldService[]; IF oldServerPupAddress = Pup.nullAddress THEN EXIT; IF ~DeletePupAddress[oldServerPupAddress] THEN EXIT; ENDLOOP; }; DeletePupAddress: PUBLIC ENTRY PROC [serverPupAddress: Pup.Address] RETURNS [found: BOOL] = { nowItem: ActiveServicesItem _ ActiveServicesListBase ; lastItem: ActiveServicesItem _ NIL; WHILE nowItem # NIL DO IF serverPupAddress = nowItem.listenerPupAddress THEN { IF lastItem = NIL THEN ActiveServicesListBase _ nowItem.next ELSE lastItem.next _ nowItem.next; IF nowItem.request # NIL THEN { request: LIST OF ComputeServer.Request _ nowItem.request; IF CurrentRequests = request THEN CurrentRequests _ request.rest ELSE { FOR cr: LIST OF ComputeServer.Request _ CurrentRequests, cr.rest UNTIL cr = NIL DO IF cr.rest = request THEN { cr.rest _ cr.rest.rest; EXIT; }; ENDLOOP; }; }; 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, August 6, 1986 7:13:26 am PDT Hal Murray, March 23, 1986 0:26:04 am 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 code stolen from Watch This procedure counts the free GFIs via a linear scan of the table. 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[]]; }; generic call to allow for expansion without RPC interface recompilation Command Registry Internal Streams input procedures output procedures We should just be able to do an UnsafePutBlock, but the Pup Stream implementation makes this real slow. Convert it to a PutBlock. stream.UnsafePutBlock[block ! PupStream.StreamClosing => { doPutChars _ TRUE; CONTINUE}; ]; 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 Bob Hagmann April 18, 1986 10:47:36 am PST added GenericClientToServer, added userName arg changes to: AskForService, GenericClientToServer, GetClientInterfaceFromCache Bob Hagmann July 14, 1986 9:47:49 am PDT added PutBlock for output streams , DoService Ê;.– "Cedar" style˜head™Ibodyšœ(™(L™:Icode™)Jšœ Ïmœ=™Hcode2šÏk ˜ N˜Nšœ˜N˜ N˜Nšœ ˜ N˜ N˜ Nšœ˜Nšœ˜Nšœ!˜!N˜Nšœ"˜"Nšœ˜N˜Nšœ˜Nšœ ˜ N˜Nšœ ˜ Nšœ ˜ Nšžœ˜N˜ N˜Nšœ˜Nšžœ˜N˜N˜ N˜ N˜N˜ Nšœžœ˜!Nšœžœ˜Nšœ žœb˜qN˜Nšœ˜N˜N˜Nšžœ˜Nšœ˜Nšœ ˜ Nšžœ˜Nšœ˜——šœžœž˜ Nš žœìžœ žœMžœDžœ ˜ NšžœT˜[Nšžœ˜Nšœž˜—™Nšžœžœžœžœ˜Mšžœžœžœ˜M˜M˜Mšœžœžœžœ˜$Mšœžœ5žœ˜TMšœ žœžœ˜+Mšœžœžœ˜1Icode1˜Ošœžœ,˜DOšœžœ,žœ˜NMšœžœ2˜PJ˜JšœžœžœÏc%˜GJšœžœ žœ˜7Jš œžœžœžœžœ˜žœ˜BJšœžœ˜/J˜J˜—Jšœžœ˜#J˜Jšœžœžœžœ˜XJšœžœ˜/J˜Jšœ žœžœ˜Jš œžœžœžœžœ˜$Jš œžœžœžœžœ˜'Jšœžœ˜Jšœžœžœžœ˜&Jšœžœžœžœ˜"Jšœž œ˜'Jšœž œ˜'J˜Jšœž œ˜Jšœžœ'žœ˜CJšœžœžœ˜ J˜šœ žœžœ˜Jšœ žœ˜Jšœžœ˜ J˜—Jš œ žœžœžœžœ ˜(J˜š œžœžœžœžœ˜@Jšœ˜Jšœ ˜ Jšœ˜J˜Jšœ˜Jšœ˜J˜Jšœ˜J˜—š œžœžœžœžœ˜AJšœ˜Jšœ ˜ Jšœ˜Jšœ%˜%Jšœ˜Jšœ˜Jšœ˜J˜—J˜J˜—™defaultšÏn œžœžœ6žœžœžœžœ˜rPšœžœ˜'Pšœžœ˜Pšœ žœ˜Pšœžœ˜Pšœžœžœ˜Pšœ!žœ˜%Pšœ žœ˜8Pš œ žœžœžœžœ˜Pš œžœžœžœžœ˜!Pšœ žœžœ˜šÏbœ˜&Nš œžœ žœžœžœžœ˜/—š œžœžœžœžœ žœ˜UN˜(Nšžœžœ žœ(˜;Nšžœžœ,˜ON˜#Nšžœžœ(˜GNšžœžœ(˜GNšœ˜—P˜˜P™J—P˜Pšœ˜š žœžœ&žœ;žœžœžœ˜©Pšœ4˜4P˜—šœžœ%žœ ˜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šœ-˜-Nšžœžœ,˜6N˜—šœžœ˜šžœžœžœ˜JšœG˜GNšœ@˜@NšœF˜FNšœ˜Nšœ#˜#šžœžœžœŒžœ˜ÀJšœ4˜4Jšœžœ˜Jšžœ˜ Jšœ˜—J˜Nšžœ˜N˜—N˜—J˜—J˜—Jšœžœ(˜-J˜—šžœžœžœ˜JšœG˜GNšœ@˜@Jšžœžœ»˜ÓJšžœžœžœ˜HJšœ#˜#šžœžœžœŒžœ˜ÀJšœ4˜4Jšœžœ˜Jšžœ˜ Jšœ˜—Jšœ˜Jšžœ˜J˜—šžœ žœžœ˜Jšœžœ˜JšœX˜XJšžœ žœžœM˜fJšœžœ˜-JšžœžœžœžœU˜‘Jšžœ7žœžœS˜—J˜—šœžœ˜Jšœ žœ_žœžœ˜‡Jšœžœ˜,J˜—Nšœ4˜4Nšœ3˜3Nšœ?˜?Nšœ.˜.Nšœ$˜$Nšœ ˜ Nšœ˜Nšœ9˜9Nšœ˜—š ¡ œžœ žœ žœ žœ;žœ˜‚Nšœžœ˜ Nšœ žœžœ˜Nšœžœžœ,˜INšœ[˜[šžœžœ žœžœ˜!Jšœ žœžœ,˜=Jšœžœ ˜'šžœ0žœ žœž˜JJš žœ,žœ-žœžœžœ˜Jšžœ˜—N˜—N˜—š¡ œžœ žœ žœžœžœžœžœžœ˜gNšœžœ-˜ANšœ&˜&Nšœ žœE˜VNšœ žœžœ˜Nšœ žœ˜Nšœ˜Nšœžœžœ˜Nšœžœ˜Nšœ žœžœ˜Nšœžœžœ˜Nšœžœ˜Nšœ žœ˜Nšœžœ˜Nšœžœžœ˜NšœO˜ONšœžœ˜-Nšžœ žœžœ2žœžœžœžœG˜ÅNšœ5˜5Nšœžœžœ žœ˜Gšžœž˜Nšœžœžœ˜Nš œ0žœ ÐkrÏrœ¥¤¥œ˜qšžœžœ˜šžœžœ#žœ˜@Nšœ&žœ!¥¤¥œ˜WNšžœ˜N˜—N˜—šžœ ž˜šœ˜Mšžœžœžœ˜8Mšœ˜Mšœ˜Mšœ˜M˜—šœ ˜ Mšœ:¤œ˜EMšœ˜Mšžœ˜Mšœ˜—šœ ˜ Mšžœžœžœ˜9Mšœ˜M˜—Mšœ!˜!Mšžœ˜—šžœžœžœ˜2šžœžœžœ˜Nšœ žœP˜_šžœžœ˜Nšžœ)žœžœ˜HNšžœžœžœ˜˜>šœ@™@Nšœ0™0šœžœ™Nšžœ™ Nšœ™—Nšœ™—Nšžœ žœžœžœ+˜aNšœ+˜+Nšœ8˜8Nšœ˜NšœžœJ˜_šžœžœžœž˜RNšœžœžœ˜šžœžœžœ˜)Nšœ˜Nšœžœ˜ N˜N™2—šžœžœ)žœžœ˜\šž˜Nšžœžœžœ˜+N˜Nšžœ˜—šžœ)žœžœž˜ZšœJžœ/žœ"˜¥Nšœžœ˜Nšœ˜Nšœžœ˜Nšœ+˜+Nšžœ˜ Nšœ˜—Nšœ˜Nšžœ˜—šœ=˜=Nšœžœ˜Nšœ˜Nšœžœ˜Nšœ+˜+Nšžœ˜ Nšœ˜—Nšœ˜šžœžœ˜&NšœOžœ˜\Nšœ˜N˜—Nšœžœ˜Nšœ˜Nšœ˜N™2—šžœžœ˜&Nšœ8˜8šžœžœ˜'šž˜Nšžœžœžœ˜+N˜Nšžœ˜—šœ=˜=Nšœžœ˜Nšœ˜Nšœžœ˜Nšœ+˜+Nšžœ˜ Nšœ˜—Nšœ˜šžœžœ˜&NšœOžœ˜\Nšœ˜N˜—Nšœžœ˜Nšœ˜N˜—N˜—š žœžœ+žœ.žœ/ž˜¬Nšœžœ˜šœJ˜Jšžœ˜Nšœžœ ˜Nšœžœ˜Nšžœ˜ —šœ˜Nšœžœ˜Nšœ˜Nšœžœ˜Nšœ+˜+Nšžœ˜ Nšœ˜—Nšœ˜——Nšžœ˜Nšžœ žœžœ˜Nšžœžœžœžœ˜7Nšžœžœ˜(Nšžœ˜—Nš žœžœžœ žœžœ žœ˜fNšœ˜Nšœ˜NšœØžœ ˜ãNšœ˜Nšœ7˜7Nšœ.˜.Nšžœžœ˜šžœ%žœOžœ+žœ˜­NšœG˜GNšœ@˜@šžœžœžœ˜#Nšœ›žœžœ˜¸Jšœ+žœ˜0J˜—N˜—N˜—š  œžœžœžœ5žœ˜|Nšœžœ˜š œžœ˜ šžœ˜Nšžœ˜ šžœ ˜ Nš žœžœ#žœ,žœžœ˜Nšœ+˜+Nšžœ˜N˜—šžœ˜Nšœ+˜+Nšžœ˜N˜—šœ žœ™NšœK™KNšœ™—šžœ˜ Nšœ žœžœ˜2Nšœ9˜9Nšžœ ˜ Nšœ˜—Nšœ˜—Nšœ.˜.Nšœžœžœ˜Nšœ žœžœ˜Nšœ˜Nšœžœ ˜©N˜Nšœ+˜+Nšœ%˜%NšœN˜NNšœ(˜(N˜ N˜ N˜šž˜šœ ˜ N˜ N˜ šžœ,žœ˜4Nšœ!˜!Nšœ˜N˜—N˜—šœ˜N˜ N˜ NšœF˜FNšœ+˜+N˜——Nšœ˜—Nšžœ#žœUžœ˜ƒNšœ,žœžœ˜cNšœM˜MNšœ,žœžœ˜QNšœžœ˜N˜—š  œžœžœ$˜˜>Nšœ-žœ˜1Nšœ(žœ˜,Nšœ˜Nšœ˜—šœžœ˜Nšœ+˜+Nšžœ(˜.N˜—N˜—šžœFžœ˜NNšœ0˜0Nšœ˜N˜—Nšžœ˜—šœI˜INšœc˜cšœžœ˜Nšžœ˜ Nšœ˜—N˜—šžœžœžœ˜"Nšœ0˜0Nšœ@˜@Nšœ?˜?N˜—Nšžœ˜N˜—š£œžœžœžœEžœ žœžœ˜—Nš žœžœžœžœžœ˜,šžœžœžœž˜2šžœ9žœ˜ANšœ-žœ˜1Nšœ(žœ˜,Nšœ=˜=Nšœžœ˜ Nšžœ˜N˜—Nšžœ˜—N˜—M˜—™š£œžœž¡œžœ žœžœ$žœžœžœžœžœ˜ƒMšœžœ˜ Mšœžœžœ˜ Mšœ žœ˜MšœMžœ˜QMšžœžœžœ˜šžœžœžœ@žœ˜XJšœ žœžœ,˜J˜Jšžœ˜—Jšžœžœžœ"žœžœžœžœžœžœ˜\Jšœ˜—š¡ œžœžœžœžœžœžœ˜FNšœžœ˜.Nš žœžœžœ žœžœ(˜ON˜—š ¡ œžœžœžœžœ˜4Nšœžœ˜.Nšžœ˜N˜—š¡ œžœžœ žœ˜0Nšœžœ˜.Nšœžœ˜N˜—N™š ¡œžœžœžœ žœ˜PNšžœžœžœžœ˜(šœžœ˜Nšœžœ˜Nšžœžœ˜N˜—N˜—š¡ œžœžœ˜7Nšœžœ˜N˜—š ¡ œžœžœžœžœ˜9Nšœžœ˜.šžœ*žœ/ž˜cNšžœ2žœžœŸ*˜lN˜Nšžœ˜—Nš œ žœžœžœ2žœ ˜ˆNšœ&˜&N˜—š ¡œžœžœžœ žœ˜KNšœžœ˜.Nšœ žœžœ˜šžœ"žœ˜*Nšœ,˜,Nšœžœžœ˜%Nšœžœžœ˜šž˜Nšžœžœžœ˜#N˜Nšžœ˜—šœ žœ˜Nšœ‚™‚—Nšœ,˜,Nš žœ2žœ žœžœžœžœ¦˜ŠNšœ˜šœF˜FNšœ žœ˜Nšžœ˜ —N˜šœ:™:Nšœ žœ™Nšžœ™ —N™Nšœ˜Nšžœ žœ$˜7Nšœ˜N˜—šžœ žœ˜šžœžœžœ6ž˜EMšžœžœ˜1Mšžœ˜—N˜—N˜—š¡ žœžœ¡œ˜$Nšœžœ˜.Nšœ,˜,Nšœžœžœ˜%šž˜Nšžœžœžœ˜#N˜Nšžœ˜—Nšœ˜Nšœ˜N˜—š ¡œžœžœžœžœ˜5Nšœžœ˜.Nšžœ˜N˜—š¡ œžœžœ žœ˜1Nšœžœ˜.Nšžœžœ žœžœ ˜,N˜—M˜—™ š£ œžœžœ˜Nšœh˜hNšœT˜TM˜M˜—š œžœžœžœžœžœ'žœžœžœ)˜˜šžœžœžœ˜Nšœ™šžœž˜šžœ˜Nšœ™Nšœžœ˜Nš žœžœžœžœžœ˜CNšžœ˜Nšœ˜—Nšžœ˜ —Nšœ˜—Nš žœžœžœžœžœŸ˜QNšžœ˜Mšœ˜—š œžœžœžœ?žœžœžœžœžœ˜Ñšžœžœ˜šœ žœ&˜3Nšžœhžœ˜t—Nšžœžœžœ&˜GNšœ˜Nšž œ˜Nšœ˜—Nšœ˜—M˜—™š¡œ$˜2Mšžœ™#šžœž˜šœ˜M˜—šœ ˜ M˜—šœ ˜ M˜M˜—Mšžœ˜—M˜M˜—š œžœ˜PšœHžœ˜NP•StartOfExpansion,[key: ROPE, default: LIST OF ROPE _ NIL]šœUžœ˜ZPšœ[žœ˜`P–![key: ROPE, default: INT _ 0]šœK˜KPšœRžœ˜YPšœNžœžœ/žœ˜ŽP˜Ešžœž˜Mšœ˜Mšœ$˜$Mšžœ˜—MšœF˜Fšžœž˜Mšœ˜Mšœ%˜%Mšžœ˜—M˜——šœ™š £ œžœžœžœužœ"˜¾Nšœ žœ¼˜ÉNšœ!˜!Nšœ˜—š £ œžœžœžœ%žœ+žœ˜zNšœžœ9˜ONšœ˜Nšœ˜—š£œžœžœžœ.žœžœ žœ˜‰Nšœ6˜6šžœ žœž˜šžœ/žœž˜7Nšžœ žœžœ˜2Nšžœžœ ˜N˜—Nšœ˜Nšžœ˜—Nšžœžœžœ˜Nšœ˜—š¡%œžœžœžœ$žœ žœžœ˜•Nšœ6˜6šžœ žœž˜Nšžœžœžœžœ ˜-Nšœ˜Nšžœ˜—Nšžœžœžœ˜Nšœ˜—š£¡žœž¡œ˜)šœžœžœ˜ Nšœ6˜6Nšœžœ˜%šžœ žœž˜šžœžœ4žœ˜XNšœžœ˜Nšœ1˜1Nšžœ˜N˜—Nšœ˜Nšžœ˜—N˜Nšœ˜—Nšœ!˜!šž˜Nšœ&˜&N˜Nšžœ'žœžœ˜3Nšœ*žœžœ˜4Nšžœ˜—Nšœ˜—N˜š £œžœžœžœ!žœ žœ˜]Nšœ6˜6Nšœžœ˜#šžœ žœž˜šžœ/žœ˜7Nšžœ žœžœ'žœ˜_šžœžœžœ˜Nšœ žœžœ)˜9Nšžœžœ˜@šœžœ˜š žœžœžœ2žœžœž˜Ršžœžœ˜Nšœ˜Nšžœ˜N˜—Nšžœ˜—N˜—N˜—Nšžœžœ˜ Nšœ˜—Nšœ˜Nšœ˜Nšžœ˜—Nšžœžœ˜Nšœ˜—J˜—™š œžœ˜Mšœžœ˜1Mšœ,žœ˜3Mšœ7˜7Mšœ9˜9M˜—M˜M˜M˜Nšžœ˜—™,Mšœ ¥ ™—™*M™6Mšœ ¥ ™—™+M™]Mšœ ¥ ™—™+M™3Mšœ ¥ ™—™*Mšœ¥œ™/Mšœ ¥A™M—™(M™!Mš¥ ™ —M™M™—…—Ìrð