DIRECTORY Atom USING [ GetPName, GetProp, PutProp ], Commander USING [ CommandProc, Register ], Convert USING [ RopeFromInt, IntFromRope ], BasicTime USING [ Update, Now, GMT ], GVNames USING [ AddForward, AuthenticateKey, CreateIndividual, Expand, ExpandInfo, GetConnect, Outcome, RemoveForward, RListHandle, SetConnect ], IO, Log USING [ CLog, CloseCLog, MakeCLog, RedoCLog, Report, WriteCLog ], MBQueue USING [ Action, Create, DequeueAction, Flush, Queue, QueueClientAction ], Names, NamesGV, Process USING [ Detach --, Pause, SecondsToTicks-- ], RefTab USING [ Create, EachPairAction, Fetch, GetSize, Pairs, Ref, Store ], SymTab USING [ Create, EachPairAction, Fetch, Pairs, Ref, Store ], Rope USING [ Cat, Concat, Find, Index, Length, ROPE, Substr ], RPC USING [ EncryptionKey ] ; NamesGVImpl: CEDAR MONITOR IMPORTS Atom, BasicTime, Commander, Convert, GVNames, IO, Log, MBQueue, Names, Process, RefTab, SymTab, Rope EXPORTS NamesGV = { OPEN IO; ROPE: TYPE= Names.ROPE; larkRegistry: ROPE=".Lark"; Results: TYPE = {ok, notFound, error}; ModeNotFound: TYPE = { ok, create, error }; -- create not available CacheBehavior: TYPE = { lookupAfter, lookupFirst, lookInCacheOnly }; Authenticity: TYPE = NamesGV.Authenticity; -- { unknown, authentic, bogus }; AttributeSeq: TYPE = NamesGV.AttributeSeq; AttributeSeqRec: TYPE = NamesGV.AttributeSeqRec; GVDetails: TYPE=REF GVDetailsR; GVDetailsR: TYPE=RECORD [ rName: ROPE, attributes: DetailsAttributes_NIL, authenticity: Authenticity_unknown, key: RPC.EncryptionKey_NULL, valid: BOOL_FALSE, canCreate: BOOL_FALSE, -- GetDetails can make one if it isn't there. mustAuthenticate: BOOL_FALSE, lastTimeValid: BasicTime.GMT, recording: BOOL_FALSE, -- Attribute update is illegal while this is TRUE dirty: BOOL_FALSE, -- Some attribute has been changed by the client refreshMode: BOOL_FALSE, -- See Note, below done: CONDITION ]; DetailsAttribute: TYPE = REF DetailsAttributeR; DetailsAttributeR: TYPE = RECORD [ attributeValue: ROPE, -- NIL only when this attribute is being deleted. formerValue: ROPE_NIL -- Non-NIL only when this attribute has changed. ]; DetailsCache: TYPE = SymTab.Ref; DetailsAttributes: TYPE = RefTab.Ref; gvCacheLog: Log.CLog _ NIL; cacheMod: CARDINAL _ 37; squawking: BOOL _ FALSE; queuesEmpty: BOOL_TRUE; queuesEmptyCond: CONDITION; gvCacheVersion: ATOM _ $V1; cache: DetailsCache_NIL; cacheQueue: MBQueue.Queue; refreshCacheQueue: MBQueue.Queue; useCurrentValue: ROPE = "Use Current Value, but replace former value, in StoreAttribute"; assumedValidInterval: INT _ 60; -- GV updates done elsewhere may take a minute, and two calls, to be noticed. assumedValidIntervalAfterChange: INT _ 600; -- GV updates done elsewhere may take a minute, and two calls, to be noticed. GV updates done elsewhere after a change here may take up to ten minutes to get noticed unless you explicitly flush the local cache. realOldTime: BasicTime.GMT _ ValidUntil[-1]; GVGetAttribute: PUBLIC ENTRY PROC[rName: ROPE, attribute: ATOM, default: ROPE_NIL] RETURNS [value: ROPE] = { ENABLE UNWIND => NULL; details: GVDetails _ GetGVDetails[rName].details; IF ~details.valid THEN RETURN[default]; IF attribute=$dirty THEN RETURN[IF details.dirty THEN "TRUE" ELSE NIL]; value _ FetchAttribute[details, attribute]; IF value=NIL THEN value_default; }; GVSetAttribute: PUBLIC ENTRY PROC[rName: ROPE, attribute: ATOM, value: ROPE] = { ENABLE UNWIND => NULL; details: GVDetails _ GetGVDetails[rName: rName, mode: create].details; detailsAttribute: DetailsAttribute; IF ~details.valid THEN RETURN; IF (~details.dirty) OR details.recording THEN UNTIL queuesEmpty DO WAIT queuesEmptyCond; ENDLOOP; -- GVWait IF details.recording THEN ERROR; -- Sanity check details.dirty _ TRUE; IF details.attributes # NIL THEN detailsAttribute _ NARROW[details.attributes.Fetch[key: attribute].val]; IF value#NIL AND value.Length[]=0 THEN value_NIL; IF detailsAttribute = NIL THEN StoreAttribute[details, attribute, value, NIL] ELSE detailsAttribute.attributeValue _ value; }; GVIsAuthenticated: PUBLIC ENTRY PROC[rName: ROPE] RETURNS [authenticity: Authenticity] = { ENABLE UNWIND => NULL; details: GVDetails _ GetGVDetails[rName].details; RETURN[details.authenticity]; }; GVAuthenticate: PUBLIC ENTRY PROC[rName: ROPE, key: RPC.EncryptionKey] RETURNS [authenticity: Authenticity] = { ENABLE UNWIND => NULL; details: GVDetails _ GetGVDetails[rName: rName, mode: error, key: key, authenticate: TRUE].details; RETURN[details.authenticity]; }; GVGetAttributeSeq: PUBLIC PROC [rName: ROPE, attribute: ATOM] RETURNS [value: AttributeSeq] = { attributeValue: ROPE _ GVGetAttribute[rName, attribute]; index: INT_0; IF attributeValue = NIL THEN RETURN[NIL]; value _ NEW[AttributeSeqRec[10]]; WHILE index < attributeValue.Length[] DO newIndex: INT _ attributeValue.Index[pos1: index, s2: ", "]; value[value.length].attributeValue _ attributeValue.Substr[index, newIndex-index]; value.length _ value.length+1; index _ newIndex+2; ENDLOOP; }; GVSetAttributeSeq: PUBLIC PROC[rName: ROPE, attribute: ATOM, value: AttributeSeq] = { attVal: ROPE_NIL; FOR i: INT IN [0..value.length) DO attVal _ Rope.Cat[attVal, value[i].attributeValue, ", "]; ENDLOOP; IF attVal#NIL THEN attVal _ attVal.Substr[len: attVal.Length[]-2]; GVSetAttribute[rName, attribute, attVal]; }; GVGetAttributes: PUBLIC ENTRY PROC[rName: ROPE] RETURNS [value: AttributeSeq_NIL] = { ENABLE UNWIND => NULL; details: GVDetails _ GetGVDetails[rName: rName, mode: create].details; len: CARDINAL; i: CARDINAL_0; GetEachAttribute: RefTab.EachPairAction = { quit_FALSE; value[i] _ [NARROW[key], NARROW[val, DetailsAttribute].attributeValue]; i_i+1; }; IF ~details.valid THEN RETURN; IF details.attributes#NIL THEN len _ details.attributes.GetSize[]; IF details.dirty THEN len_len+1; IF len=0 THEN RETURN; value _ NEW[AttributeSeqRec[len]]; value.length _ len; []_details.attributes.Pairs[GetEachAttribute]; IF details.dirty THEN value[len-1] _ [$dirty, "TRUE"]; }; GVWait: PUBLIC ENTRY PROC = { ENABLE UNWIND => NULL; UNTIL queuesEmpty DO WAIT queuesEmptyCond; ENDLOOP; }; GVUpdate: PUBLIC ENTRY PROC[rName: ROPE] = { ENABLE UNWIND => NULL; SetGVDetails[GetGVDetails[rName, error].details]; }; GVUpdateAll: PUBLIC ENTRY PROC = { ENABLE UNWIND => NULL; UpdateEachRName: INTERNAL SymTab.EachPairAction = { details: GVDetails _ NARROW[val]; IF details.dirty AND ~details.recording THEN QueueAction[refreshCacheQueue, SetNewDetails, details]; quit_FALSE; }; []_SymTab.Pairs[cache, UpdateEachRName]; }; GVFlushCache: PUBLIC ENTRY PROC = { ENABLE UNWIND => NULL; GetGVCache[TRUE]; -- Old one fades into oblivion }; GVSaveCache: PUBLIC ENTRY PROC = { ENABLE UNWIND => NULL; ros: IO.STREAM; SaveEachRName: INTERNAL SymTab.EachPairAction = { details: GVDetails _ NARROW[val]; SaveEachAttribute: RefTab.EachPairAction = { attributeName: ATOM _ NARROW[key]; detailsAttribute: DetailsAttribute _ NARROW[val]; quit_FALSE; IF detailsAttribute.attributeValue=NIL OR detailsAttribute.attributeValue.Length[]=0 THEN RETURN; ros.PutF["%s: %s\n", rope[Atom.GetPName[attributeName]], rope[detailsAttribute.attributeValue]]; }; quit_FALSE; ros _ IO.ROS[ros]; IF details.recording THEN UNTIL queuesEmpty DO WAIT queuesEmptyCond; ENDLOOP; -- GVWait IF ~details.valid OR details.authenticity=bogus THEN RETURN; ros.PutF["rname: %s\n", rope[details.rName]]; IF details.authenticity = authentic THEN TRUSTED { key: RPC.EncryptionKey _ details.key; cardKey: LONG POINTER TO ARRAY[0..2) OF LONG CARDINAL = LOOPHOLE[LONG[@key]]; ros.PutF["key: %bB %bB\n", card[cardKey[0]], card[cardKey[1]]]; }; IF details.attributes#NIL THEN []_details.attributes.Pairs[SaveEachAttribute]; IF details.dirty THEN ros.PutRope["dirty: TRUE\n"]; ros.PutChar['\n]; Log.WriteCLog[gvCacheLog, IO.RopeFromROS[ros, FALSE]]; }; GetGVCache[FALSE]; gvCacheLog _ Log.MakeCLog["GVCacheLog.txt", RestoreEntry, TRUE, 3]; IF gvCacheLog=NIL THEN { Log.Report["GV:** Couldn't create GVCacheLog.txt", $System]; RETURN; }; ros _ IO.ROS[]; []_cache.Pairs[SaveEachRName]; gvCacheLog_Log.CloseCLog[gvCacheLog]; ros.Close[]; }; GVRestoreCache: PUBLIC ENTRY PROC = { ENABLE UNWIND => NULL; GetGVCache[FALSE]; gvCacheLog _ Log.MakeCLog["GVCacheLog.txt", RestoreEntry, FALSE, 3]; IF gvCacheLog=NIL THEN { Log.Report["GV:** Couldn't find GVCacheLog.txt", $System]; RETURN; }; GetGVCache[TRUE]; -- Old one fades into oblivion Log.RedoCLog[gvCacheLog]; gvCacheLog_Log.CloseCLog[gvCacheLog]; }; GetGVDetails: INTERNAL PROC[rName: ROPE, mode: ModeNotFound_ok, behavior: CacheBehavior_lookupAfter, authenticate: BOOL_FALSE, key: RPC.EncryptionKey_NULL] RETURNS [results: Results_ok, details: GVDetails_NIL] = TRUSTED { GetGVCache[FALSE]; details _ NARROW[cache.Fetch[rName].val]; IF details=NIL THEN IF behavior=lookInCacheOnly THEN RETURN[notFound, NIL] ELSE []_cache.Store[rName, details _ NEW[GVDetailsR_[rName: rName, lastTimeValid: realOldTime]]]; details.mustAuthenticate _ details.mustAuthenticate OR authenticate; IF authenticate THEN details.key _ key; details.canCreate _ ~details.valid AND mode=create; SELECT behavior FROM lookupFirst => { QueueAction[cacheQueue, LookupDetails, details]; WAIT details.done; }; lookupAfter => { dontKnow: BOOL = ~details.valid OR (details.mustAuthenticate AND (SELECT details.authenticity FROM unknown, perhaps=>TRUE, ENDCASE=>FALSE)); IF (details.mustAuthenticate AND dontKnow) OR Names.GMTComp[BasicTime.Now[], details.lastTimeValid]=greater THEN { details.lastTimeValid _ ValidUntil[assumedValidInterval]; QueueAction[ (IF dontKnow THEN cacheQueue ELSE refreshCacheQueue), LookupDetails, details]; IF dontKnow THEN WAIT details.done; }; }; ENDCASE; details.canCreate _ FALSE; IF ~details.valid THEN RETURN[ IF mode=ok THEN notFound ELSE Report[notFound, notFound, details], details]; IF squawking THEN Log.Report[IO.PutFR["GV: Found %g", rope[rName]], $System]; }; SetGVDetails: INTERNAL PROC[details: GVDetails] = { GetGVCache[FALSE]; IF details=NIL OR details.dirty = FALSE THEN RETURN; details.lastTimeValid _ ValidUntil[assumedValidIntervalAfterChange]; details.recording _ TRUE; QueueAction[refreshCacheQueue, SetNewDetails, details]; IF squawking THEN Log.Report[IO.PutFR["GV: Modified %g", rope[details.rName]], $System]; }; RestoreEntry: SAFE PROC[cLog: Log.CLog] = { -- DoCLog command procedure logStream: IO.STREAM = cLog.logStream; details: GVDetails; DO line: ROPE_ReadLine[logStream]; key: ATOM; val: ROPE; IF line=NIL OR line.Length[]=0 THEN RETURN; [key, val] _ ParseAttribute[line]; SELECT key FROM $rname => { IF details#NIL THEN ERROR; details _ NEW[GVDetailsR_[ rName: val, lastTimeValid: realOldTime, valid: TRUE, authenticity: perhaps]]; []_cache.Store[key: val, val: details]; }; $key => TRUSTED { keyStream: IO.STREAM _ IO.RIS[val]; key: RPC.EncryptionKey; cardKey:LONG POINTER TO ARRAY[0..2) OF LONG CARDINAL = LOOPHOLE[LONG[@key]]; cardKey[0] _ IO.GetCard[keyStream]; cardKey[1] _ IO.GetCard[keyStream]; IF details=NIL THEN ERROR; details.key _ key; IO.Close[keyStream]; details.mustAuthenticate _ TRUE; details.authenticity _ authentic; }; $dirty => TRUSTED { IF details=NIL THEN ERROR; details.dirty _ TRUE; -- inhibits GV refreshes!!! }; NIL => ERROR; -- Didn't find a key! ENDCASE => { IF details=NIL THEN ERROR; StoreAttribute[details, key, val, NIL]; }; ENDLOOP; }; LookupDetails: SAFE PROC[reallyDetails: REF ANY] = TRUSTED { details: GVDetails _ NARROW[reallyDetails]; authenticity: Authenticity_details.authenticity; rName: ROPE = details.rName; connect: ROPE; expandInfo: GVNames.ExpandInfo _ [notFound []]; info, aInfo: GVNames.Outcome_notFound; entries: GVNames.RListHandle_NIL; valid: BOOL_FALSE; RecordDetails: ENTRY PROC = CHECKED INLINE { details.rName _ rName; details.valid _ valid; details.lastTimeValid _ ValidUntil[ IF details.canCreate AND details.valid THEN assumedValidIntervalAfterChange ELSE assumedValidInterval]; details.authenticity _ authenticity; details.canCreate _ FALSE; ParseDetails[details, entries]; details.refreshMode _ FALSE; NOTIFY details.done; }; IF details.dirty AND ~details.refreshMode THEN { IF squawking THEN Log.Report[IO.PutFR["GV:---Not seeking %g (dirty)", rope[rName]], $System]; RETURN; }; IF squawking THEN Log.Report[IO.PutFR["GV:---Seeking %g", rope[rName]], $System]; [info, connect] _ GVNames.GetConnect[rName]; valid _ info=individual; IF ~valid THEN IF info=notFound THEN { info _ DoCreate[details, "MFLFLX"]; valid _ info=individual; } ELSE []_Report[info, error, details] ELSE { expandInfo _ GVNames.Expand[rName]; WITH expandInfo SELECT FROM group => entries_members; noChange, individual => NULL; ENDCASE => []_Report[type, error, details]; }; IF connect#NIL THEN { connect _ Rope.Concat["connect: ", connect]; IF entries=NIL THEN entries _ LIST[ connect ] ELSE entries _ CONS[connect, entries]; }; IF valid AND (SELECT details.authenticity FROM bogus, authentic, nonexistent => FALSE, unknown, perhaps =>TRUE, ENDCASE=>ERROR) THEN IF details.mustAuthenticate THEN { IF squawking THEN Log.Report[IO.PutFR["GV:---Authenticating %g", rope[rName]], $System]; SELECT (aInfo_GVNames.AuthenticateKey[rName, details.key]) FROM badPwd => authenticity_bogus; individual => authenticity_authentic; ENDCASE => []_Report[aInfo, error, details]; } ELSE authenticity _ perhaps; IF info=notFound THEN authenticity _ nonexistent; RecordDetails[]; IF squawking THEN Log.Report[IO.PutFR["GV:---End %g", rope[rName]], $System]; }; SetNewDetails: SAFE PROC[reallyDetails: REF ANY] = TRUSTED { details: GVDetails _ NARROW[reallyDetails]; outcome: GVNames.Outcome; somethingStillDirty: BOOL_FALSE; EachAttribute: RefTab.EachPairAction = TRUSTED { attribute: DetailsAttribute _ NARROW[val]; attributeName: ATOM _ NARROW[key]; quit_FALSE; IF attribute.attributeValue=attribute.formerValue THEN RETURN; IF attributeName=$connect THEN { outcome _ GVNames.SetConnect[ user: Names.CurrentRName[], password: Names.CurrentPasskey[], individual: details.rName, connect: attribute.attributeValue]; SELECT outcome FROM noChange, individual => attribute.formerValue _ attribute.attributeValue; ENDCASE=> { []_Report[outcome, error, details, TRUE]; somethingStillDirty _ TRUE; RETURN; }; }; IF attribute.formerValue # NIL AND attribute.formerValue.Length[]#0 THEN { oldFwd: ROPE_ Atom.GetPName[attributeName].Cat[": ", attribute.formerValue]; outcome _ GVNames.RemoveForward[ user: Names.CurrentRName[], password: Names.CurrentPasskey[], individual: details.rName, dest: oldFwd]; SELECT outcome FROM noChange, individual => attribute.formerValue _ NIL; ENDCASE=> { []_Report[outcome, error, details, TRUE]; somethingStillDirty _ TRUE; RETURN; }; }; IF attribute.attributeValue # NIL AND attribute.attributeValue.Length[]#0 THEN { newFwd: ROPE_ Atom.GetPName[NARROW[key]].Cat[": ", attribute.attributeValue]; outcome _ GVNames.AddForward[ user: Names.CurrentRName[], password: Names.CurrentPasskey[], individual: details.rName, dest: newFwd]; SELECT outcome FROM noChange, individual => attribute.formerValue _ attribute.attributeValue; ENDCASE=> { []_Report[outcome, error, details, TRUE]; somethingStillDirty _ TRUE; RETURN; }; }; }; { ENABLE UNWIND => details.recording _ FALSE; IF squawking THEN Log.Report[IO.PutFR["GV:---Change attributes for %g", rope[details.rName]], $System]; IF details.attributes # NIL THEN []_RefTab.Pairs[details.attributes, EachAttribute]; details.recording _ FALSE; details.dirty _ somethingStillDirty; IF squawking THEN Log.Report["GV:---End update", $System]; }; }; DoCreate: PROC[details: GVDetails, password: ROPE] RETURNS [info: GVNames.Outcome] = TRUSTED { IF ~details.canCreate THEN RETURN[notFound]; IF squawking THEN Log.Report[IO.PutFR["GV:---Creating %g", rope[details.rName]], $System]; info _ GVNames.CreateIndividual[ user: Names.CurrentRName[], password: Names.CurrentPasskey[], individual: details.rName, newPwd: Names.CurrentPasskey[password]]; SELECT info FROM individual => NULL; ENDCASE => []_Report[info, error]; }; ParseDetails: INTERNAL PROC[details: GVDetails, entries: GVNames.RListHandle] = { attributeTable: RefTab.Ref_NIL; IF ~details.refreshMode THEN details.attributes_NIL; IF ~details.valid THEN RETURN; FOR e: GVNames.RListHandle _ entries, e.rest WHILE e#NIL DO key: ATOM; val: ROPE; [key, val] _ ParseAttribute[e.first]; IF key=NIL THEN LOOP; -- Not the right syntax for an entry StoreAttribute[details, key, IF details.refreshMode THEN useCurrentValue ELSE val, val]; ENDLOOP; }; ParseAttribute: PROC[attributeSpec: ROPE] RETURNS [key: ATOM_NIL, val: ROPE_NIL] = { index: INT _ attributeSpec.Find[": "]; IF index<0 THEN RETURN; -- Not the right syntax for an entry key _ Names.MakeAtom[rName: attributeSpec.Substr[start: 0, len: index], case: FALSE]; val _ attributeSpec.Substr[start: index+2]; }; StoreAttribute: PROC[details: GVDetails, key: ATOM, val: ROPE, oldVal: ROPE] = { detailsAttribute: DetailsAttribute; IF val=useCurrentValue THEN val _ FetchAttribute[details, key]; detailsAttribute _ NEW[DetailsAttributeR _ [val, oldVal]]; IF details.attributes = NIL THEN details.attributes _ RefTab.Create[]; [] _ details.attributes.Store[key: key, val: detailsAttribute]; }; FetchAttribute: PROC[details: GVDetails, key: ATOM] RETURNS [attributeValue: ROPE] = { detailsAttribute: DetailsAttribute; IF details.attributes=NIL THEN RETURN[NIL]; detailsAttribute _ NARROW[details.attributes.Fetch[key: key].val]; RETURN[IF detailsAttribute # NIL THEN detailsAttribute.attributeValue ELSE NIL]; }; QueueAction: INTERNAL PROC [q: MBQueue.Queue, proc: PROC [REF ANY], data: REF ANY] = { queuesEmpty_FALSE; MBQueue.QueueClientAction[q, proc, data]; IF q=cacheQueue THEN MBQueue.QueueClientAction[refreshCacheQueue, Arise, NIL]; }; Arise: SAFE PROC [whatever: REF] = { NULL }; MBQueueEmpty: ENTRY PROC[q: MBQueue.Queue, notifyIfEmpty: BOOL_FALSE] RETURNS [empty: BOOL] = TRUSTED { SomethingThatLooksLikeAnMBQueue: TYPE = RECORD [ lock: CARDINAL, -- something that looks like a lock. firstEvent: LIST OF MBQueue.Action, otherStuff: CARDINAL -- and so on, but who cares? ]; qp: LONG POINTER TO SomethingThatLooksLikeAnMBQueue = LOOPHOLE[q]; empty _ qp.firstEvent=NIL; IF ~empty OR ~notifyIfEmpty THEN RETURN; queuesEmpty_TRUE; NOTIFY queuesEmptyCond; }; Action: TYPE = MBQueue.Action; Notifier: PROC [] = { DO ENABLE ABORTED => { MBQueue.Flush[cacheQueue]; MBQueue.Flush[refreshCacheQueue]; LOOP; }; UNTIL MBQueueEmpty[cacheQueue] DO WITH MBQueue.DequeueAction[cacheQueue] SELECT FROM e2: Action.client => { queuesEmpty_FALSE; e2.proc[e2.data]; }; ENDCASE => ERROR; ENDLOOP; [] _ MBQueueEmpty[refreshCacheQueue, TRUE]; -- if TRUE, about to wait WITH MBQueue.DequeueAction[refreshCacheQueue] SELECT FROM e2: Action.client => { queuesEmpty_FALSE; e2.proc[e2.data]; }; ENDCASE => ERROR; ENDLOOP; }; SemiTok: IO.BreakProc = TRUSTED {RETURN[IF char='; THEN break ELSE other]; }; CommaTok: IO.BreakProc = TRUSTED {RETURN[IF char=', THEN break ELSE other]; }; Report: PROC[outcome: GVNames.Outcome, r: Results, details: GVDetails_NIL, timeout: BOOL_FALSE] RETURNS[rr: Results] = { rName: ROPE_NIL; rr_r; IF details#NIL THEN { details.valid_FALSE; rName_details.rName; IF timeout THEN details.lastTimeValid _ realOldTime; -- next query will go to GV fer sherr }; Log.Report[IO.PutFR["GV: **%s %s\n", rope[rName], rope[SELECT outcome FROM noChange => "no change", group => "group", individual => "individual", notFound => "not found", protocolError => "protocol error", wrongServer => "wrong server", allDown => "all servers down", badPwd => "bad password", outOfDate => "out of date", notAllowed => "not allowed", ENDCASE => "??"]], $System]; }; ValidUntil: PROC[interval: INT] RETURNS [BasicTime.GMT] = { RETURN[BasicTime.Update[BasicTime.Now[], interval]]; }; GetGVCache: INTERNAL PROC[new: BOOL_FALSE] = { cRef: REF DetailsCache _ NIL; cache_NIL; DO ref: REF _ Atom.GetProp[$GVCache, gvCacheVersion]; IF ref=NIL THEN EXIT; WITH ref SELECT FROM cRef1: REF DetailsCache => { cRef_cRef1; EXIT; }; ENDCASE; gvCacheVersion _ Names.MakeAtom[ rName: Rope.Cat["V", Convert.RopeFromInt[ Convert.IntFromRope[ Rope.Substr[ Atom.GetPName[gvCacheVersion], 1] ]+1 ] ], case: TRUE ]; ENDLOOP; IF ~new AND cRef#NIL THEN { cache _ cRef^; RETURN}; cache _ SymTab.Create[cacheMod, FALSE]; Atom.PutProp[$GVCache, gvCacheVersion, NEW[DetailsCache _ cache]]; }; CmdSaveGVCache: Commander.CommandProc = { GVSaveCache[]; }; CmdRestoreGVCache: Commander.CommandProc = { GVRestoreCache[]; }; CmdRefreshGVCache: ENTRY Commander.CommandProc = { RefreshEachRName: INTERNAL SymTab.EachPairAction = { details: GVDetails _ NARROW[val]; quit_FALSE; IF details.dirty THEN details.refreshMode _ TRUE; QueueAction[refreshCacheQueue, LookupDetails, details]; }; []_SymTab.Pairs[cache, RefreshEachRName]; }; CmdUpdateGVCache: Commander.CommandProc = { GVUpdateAll[]; }; CmdFlushGVCache: Commander.CommandProc = { GVFlushCache[]; }; CmdWaitForGV: Commander.CommandProc = { GVWait[]; }; CmdGVSquawk: Commander.CommandProc = { squawking _ ~squawking; Log.Report[IO.PutFR["Squawking[%g]", bool[squawking]], $System]; }; ReadLine: PROC[s: IO.STREAM] RETURNS [ line: ROPE] = { RETURN[s.GetLineRope[]]; }; cacheQueue _ MBQueue.Create[pushModel: FALSE]; refreshCacheQueue _ MBQueue.Create[pushModel: FALSE]; TRUSTED { Process.Detach[FORK Notifier[]]; }; Commander.Register["GVFlushCache", CmdFlushGVCache, "Flush GV Cache"]; Commander.Register["GVSaveCache", CmdSaveGVCache, "Save GV Cache"]; Commander.Register["GVRestoreCache", CmdRestoreGVCache, "Restore GV Cache"]; Commander.Register["GVRefreshCache", CmdRefreshGVCache, "Refresh all GV Cache entries from GV"]; Commander.Register["GVUpdateCache", CmdUpdateGVCache, "Write all dirty cache entries to GV"]; Commander.Register["GVWait", CmdWaitForGV, "Wait for GV communications to complete"]; Commander.Register["GVSquawk", CmdGVSquawk, "Inform user of GV activity"]; }. NamesGVImpl.Mesa Last modified by Swinehart, January 9, 1985 4:20:17 pm PST Last Edited by: Pier, May 20, 1984 7:08:08 pm PDT Data Note: There are two ways to store GV entries: in GV and in a local cache file. If the entry is not dirty, a cache-file version will look a lot like the GV version, and anyway, the GV version is preferred. But for cached dirty values the idea is that they should be believed first. Unfortunately, one eventually needs to know what GV thinks before one can properly update the GV entries. RefreshAllEntries sets the refreshMode bit. When the bit is set and the entry's dirty, the current GV entry is read, but only into the "formerValue" fields. Now an update operation can clear dirty and write the proper values. After fetching the former values, the refreshMode condition is cleared. NB: When you're counting on this kind of thing, you're playing with fire. There are no interlocks to be sure all the reads are done before you do the write, and that sort of thing. Be sure to use Squawk mode, or do GVWaits, or something, to be sure things are adequately synchronized. DCS 10/7/84. GV Procedures exported to Names The following nonsense truncates the log file before beginning to write. GV Utilities DontKnow: cached copy doesn't exist, is incorrect, or is incomplete. At most one refresh try per interval Queued GV Utilities Special case: connect attribute is really RName's connect field. val=useCurrentValue: update only oldVal field, unless there's no current attribute. GV Utilities Utilities Derived from MBQueueImpl.Notifier; runs all the time. Exhaust cacheQueue before considering entries on refreshCacheQueue. When cacheQueue operations are queued, dummy refresh operations are, too, to wake us up. timeout=TRUE means that next attempt to fetch info about this RName consult Grapevine right away All copies of the same version of Names on same machine get the same cache!! Different versions jockey for position among the gvCacheVersion entries. A cache is a SymTab.Ref, which is opaque, which means that a REF ANY containing one can't be narrowed, so it's stored inside another REF. Sigh. GV user commands Initialization Ê͘Jšœ™Jšœ:™:J™1J˜šÏk ˜ Jšœœ ˜*Jšœ œ˜+J˜+Jšœ œœ˜%Jšœœ„˜‘Jšœ˜Jšœœ<˜EJšœœD˜QJ˜J˜J˜5Jšœœ?˜KJšœœ6˜BJšœœ%œ ˜>J˜J˜J˜—šœ œ˜Jšœ/œ4˜lšœ ˜Jšœœ˜J˜——™J™Jšœœœ˜Jšœœ ˜J˜&JšœœÏc˜CJšœœ1˜DJšœœž!˜LJšœœ˜*Jšœœ˜0J˜Jšœ œœ ˜šœ œœ˜Jšœœ˜ Jšœœ˜"Jšœ#˜#J˜Jšœœœ˜Jšœœ˜Jšœ œœž-˜DJšœœœ˜J˜Jšœœ˜Jšœ œœž1˜HJšœœœž0˜CJšœ œœž˜+Jšœ ˜J˜—Jšœ#œ œfœœ–œ+œoœÆ™´J™¬J˜Jšœœœ˜/šœœœ˜"Jšœœž1˜GJšœ œœž0˜FJ˜J˜—Jšœœ˜ Jšœœ˜%J˜Jšœœ˜Jšœ œ˜Jšœ œœ˜Jšœ œœ˜Jšœ œ˜Jšœœ˜Jšœœ˜Jšœ˜Jšœ!˜!JšœœD˜YJ˜JšœœžM˜mJšœ!œžÓ˜ÿJšœ,˜,J˜—J™˜šÏnœœœœœ œ œœ˜RJšœ œ˜Jšœœœ˜J˜1Jšœœœ ˜'Jšœœœœœœœ˜GJ˜+Jšœœœ˜ J˜J˜—šŸœœœœœ œ œ˜PJšœœœ˜J˜FJ˜#Jšœœœ˜šœœ˜-Jš œ œœœž ˜=—Jšœœœž˜0Jšœœ˜šœœ˜ Jšœœ/˜H—Jš œœœœœ˜1Jšœœœ+œ˜MJšœ)˜-J˜J˜—š Ÿœœœœœ˜1Jšœ!˜(Jšœœœ˜J˜1Jšœ˜J˜J˜—š Ÿœœœœœœ˜FJšœ!˜(Jšœœœ˜JšœUœ ˜cJšœ˜J˜J˜—š Ÿœœœ œ œ˜=Jšœ˜!Jšœœ$˜8Jšœœ˜ Jš œœœœœ˜)Jšœœ˜!šœ!˜(Jšœ œ/˜œ˜^Jšœ œž˜0J˜Jšœ%˜%J˜J˜—J˜—J™J™ ˜šŸ œœœœLœœœœœ*œœ˜ÝJšœ œ˜Jšœ œ˜)šœ œ˜Jšœœœ œ˜6šœ ˜$Jšœ9˜<——Jšœ4œ˜DJšœœ˜'Jšœ#œ ˜3šœ ˜šœ˜Jšœ0˜0Jšœ˜J˜—šœ˜šœ œœœ˜AJš œœœœœœ˜K—šœœ ˜-šœ>œ˜DJ™D—Jšœ9˜9Jšœž#™$šœ ˜ Jšœœ œ œ-˜N—Jšœ œœ˜#J˜—J˜—Jšœ˜—Jšœœ˜šœœœ˜Jšœ œ œ/˜L—Jšœ œ œ.˜MJ˜——˜šŸ œ œ˜3Jšœ œ˜Jš œ œœœœœ˜4J˜DJšœœ˜J˜7Jšœ œ œ9˜XJ˜J˜—šŸ œœœž˜GJšœ œœ˜&J˜š˜Jšœœ˜Jšœœ˜ Jšœœ˜ Jš œœœœœ˜+J˜"šœ˜˜ Jšœ œœœ˜šœ œ ˜Jšœ/œ˜M—J˜'J˜—šœœ˜Jš œ œœœœ˜#Jšœœ˜Jšœœœœœœœœœœ˜LJšœ œ˜#Jšœ œ˜#Jšœ œœœ˜J˜Jšœ˜Jšœœ#˜BJ˜—šœ œ˜Jšœ œœœ˜Jšœœ˜1J˜—Jšœœž˜#šœ˜ Jšœ œœœ˜Jšœ"œ˜'J˜——Jšœ˜—J˜——J˜J™™š Ÿ œœœœœœ˜˜]Jšœ˜J˜—Jšœ œ œ2˜QJ˜,Jšœ˜šœœ˜JšœœA˜VJšœ ˜$—šœ˜Jšœ#˜#šœ ˜Jšœ˜Jšœœ˜Jšœ$˜+—J˜—šœ œœ˜J˜,Jšœ œœ œ ˜-Jšœ œ˜&J˜—šœœœ˜.Jš œ!œœœœ˜PJš˜šœœ˜"šœ ˜Jšœ œ9˜F—šœ5˜?Jšœ˜Jšœ%˜%Jšœ%˜,—J˜—Jšœ˜—Jšœœ˜1Jšœ˜Jšœ œ œ.˜MJ˜——˜š Ÿ œœœœœœ˜˜>——šœ ˜JšœI˜Išœ˜ Jšœ#œ˜)Jšœœ˜Jšœ˜ ——J˜—šœœœ"œ˜JJšœœ@˜Lšœ ˜ ˜=Jšœ)˜)——šœ ˜Jšœ0œ˜4šœ˜ Jšœ#œ˜)Jšœœ˜Jšœ˜ ——J˜—šœœœ%œ˜PJšœœœ+˜Mšœ˜˜=Jšœ)˜)——šœ ˜JšœI˜Išœ˜ Jšœ#œ˜*Jšœœ˜Jšœ˜ ——J˜—J˜—šœ˜Jšœœœ˜+šœ ˜Jšœ œH˜U—Jšœœœ4˜TJšœœ˜Jšœ$˜$Jšœ œ)˜:J˜—J˜J˜—š Ÿœœœœœ˜^Jšœœœ ˜,šœ ˜Jšœ œ;˜H—šœ ˜ Jšœ=˜=JšœC˜C—šœ˜Jšœœ˜Jšœ˜"—Jšœ˜——˜šŸ œœœ6˜QJšœœ˜Jšœœœ˜4Jšœœœ˜šœ*œœ˜;Jšœœ˜ Jšœœ˜ J˜%Jš œœœœž$˜:Jšœœœœ ˜XJšœ˜—J˜J˜—šŸœœœœœœœœ˜TJšœœ˜&Jšœ œœž$˜Jšœœ˜—Jšœ˜—Jšœ%œž˜Ešœ*œ˜9Jšœ#œ˜>Jšœœ˜—Jšœ˜—Jšœ˜—J™Jš œ œ œœœ œœ ˜MJš œ œ œœœ œœ ˜NJ˜š Ÿœœ:œ œœ˜_JšœœT™`Jšœ˜Jšœœœ˜J˜šœ œœ˜Jšœœ˜*Jšœ œ&ž%˜ZJšœ˜—šœ7œ ˜JJ˜J˜J˜J˜J˜"J˜J˜J˜J˜J˜Jšœ˜——J™šŸ œœ œœ˜