// GrapevineEnquire.bcpl
// Copyright Xerox Corporation 1981, 1983
// Last modified September 19, 1983  9:41 AM by Taft

get "Grapevine.decl"
get "GrapevineProtocol.decl"
get "GrapevineInternal.decl"
get "Streams.d"

external
[
// outgoing procedures
Enquire; EnquireWithStamp

// incoming procedures
SendWord; SendRName; ReceiveWord; FindServer; GVCreateStream
GVClaimStream; GVReleaseStream; BSPWriteBlock; BSPForceOutput
ExtractSubstring; ConcatenateStrings
DefaultArgs; Block; MyFrame; Free

// incoming statics
@gus; offsetBSPStr
]

//----------------------------------------------------------------------------
let EnquireWithStamp(stream, op, name, stamp; numargs na) = valof
//----------------------------------------------------------------------------
// Sends an enquiry of the form [op, name, stamp] and returns the
// resulting returnCode.  The default stamp used is null.
[
let defaultStamp = table [ 0; 0; 0 ]
DefaultArgs(lv na, -3, defaultStamp)
SendWord(stream, op)  // op
SendRName(stream, name)  // name
BSPWriteBlock(stream, stamp, 0, 2*lenTimeStamp)  // stamp
BSPForceOutput(stream-offsetBSPStr, true)
resultis ReceiveWord(stream)  // returnCode
]

//----------------------------------------------------------------------------
and Enquire(name, proc, arg) = valof
//----------------------------------------------------------------------------
// Opens a BSP stream to a registration server that has a copy of the registry
// for name (an RName), and then calls Proc(stream, name, arg).
// Proc should return a ReturnCode describing the outcome; any additional
// results may be communicated via storage pointed to by arg.
// Note that proc may be called more than once, since the first registration
// server we talk to may not have the required information, or the connection
// may fail in the middle.  Also, any stream errors on stream cause control
// to be ripped away from proc, so proc must not acquire any resources not
// known to the caller of Enquire.
// Returns the ReturnCode returned by proc if proc was called successfully,
// or a locally-manufactured ReturnCode otherwise.
[
let dot = 0
for i = 1 to name>>RName.length do
   if name>>RName.char↑i eq $. then dot = i
if dot eq 0 resultis rcBadRName lshift 8  // missing "." in RName

// for registry NA, the group named NA.GV enumerates its R-Servers
let zone = gus>>GUS.zone
let registry = ConcatenateStrings(ExtractSubstring(name, dot+1, 0, zone),
 ".GV", true, false, zone)

// If we already have a stream open, try it first.  That is, ask the R-Server
// we are currently connected to to perform the enquiry.  If that succeeds,
// we are done; if not, we must go through the R-Server location algorithm.
let returnCode = nil
GVClaimStream()
if gus>>GUS.stream ne 0 then
   [
   gus>>GUS.stream>>ST.par1 = MyFrame()
   returnCode = proc(gus>>GUS.stream, name, arg)
   ]
GVReleaseStream()

// Enquire (cont'd)

// The enquiry process consists of two main stages:
// stage 0: Find ANY R-Server and ask it to look up name; if this succeeds
// 	    then we are done.
// stage 1: find the R-Server for NA.GV and ask it to look up name.
// FindServer would do the complete registry location protocol if we
// simply passed it NA.GV.  However, we employ the following optimization:
// if we don't currently have an open stream, we first ask
// FindServer to open a stream to ANY R-Server, and we present name
// to that server in the hope that the server has a copy of name's
// registry.  Only if this fails do we ask FindServer to go through
// the complete protocol for locating registry -- but by this time we
// have already established a stream to SOME R-Server, so the first part
// of the R-Server location protocol will not need to be repeated.

// Note: if there was a cached stream, and the stream is still alive, and
// proc did NOT return WrongServer, then we are already connected to the
// correct server and received the response we deserved; firstStage=2
// bypasses all the R-Server location protocol in this case.
let firstStage = gus>>GUS.stream eq 0? 0,
 returnCode<<ReturnCode.code eq rcWrongServer? 1, 2
for stage = firstStage to 1 do
   [
   // Stage 0: try to find ANY R-Server, and open a stream to it.
   // Stage 1: try to find the correct R-Server, and open a stream to it.
   // In stage 0, the call of FindServer doesn't call us back
   // recursively, because FindServer locates registry GV by
   // consulting a Name Lookup Server or by broadcasting.
   // In stage 1, FindServer may call us back recursively while
   // expanding NA.GV and obtaining connect sites.
   let ec = FindServer((stage eq 0? "GrapevineRServer", registry),
    socRegServerPolling, GVCreateStream)
   test ec ne 0
      ifso
         [
	 // unable to contact any registration server (stage 0)
	 // or any server for registry (stage 1).  In both cases this is fatal.
         returnCode<<ReturnCode.code = ec eq ecBadRName? rcBadRName, rcAllDown
	 break
	 ]
      ifnot
         [
	 // successfully opened a stream to some R-Server (stage 0) or to some
         // R-Server for registry (stage 1).  Now attempt to perform the enquiry.
         GVClaimStream()
	 gus>>GUS.stream>>ST.par1 = MyFrame()
	 returnCode = proc(gus>>GUS.stream, name, arg)
         GVReleaseStream()
	 
	 // If the server does not know about name and this is stage 0,
	 // go around again.  In all other cases we are finished.
	 // A WrongServer error in stage 1 indicates an inconsistency
	 // in the data base; convert this to BadName.
	 test returnCode<<ReturnCode.code eq rcWrongServer
	    ifso returnCode<<ReturnCode.code = rcBadRName
	    ifnot break
	 ]
   ]

Free(zone, registry)
resultis returnCode
]