OBS-RPC-1
Issue 2.3; 2nd July 1998
Royal Greenwich Observatory,
Madingley Road,
Cambridge CB3 0HJ
Telephone (01223) 374000
Fax (01223) 374700
Internet G.Rixon@ast.cam.ac.uk
The DRAMA-RPC system is the recommended structure for writing programs to implement user commands in ING's DRAMA-based observing system. This document describes the current implementation of the RPC system and suggests ways of using RPCs in client programs.
1.2 Scope of the software
The discussion below covers describes client programs for an observing system; a client is defined as a program run to execute a single user-command which contacts one or more server programs to get the work done.
The client programs are considered to be part of the central intelligence of an observing system: that part which co-ordinates actions in instrument sub-systems.
The subroutine library, libcia, is crucial to the clients and is the main subject of this document. This library presents DRAMA's messaging and tasking facilities as an RPC model.
1.3 References
[1] Distributed instrument tasking system
AAO document DITS_SPEC/5 by T. J. Farrell.
[2] Guide to writing DRAMA tasks
AAO document DRAMA_GUIDE/3 by T. J. Farrell.
[3] Inter-process message passing system
AAO document IMP_MANUAL/8 by Keith Shortridge.
[4] Dialogues with the user
RGO/ING document
OBS-TALK-1 by Guy Rixon.
[5] Interlocking for observing systems
RGO/ING document
OBS-LOCK-1 by Guy Rixon.
[6] Controls for INT prime-focus camera: configuration management
RGO/ING document
INT-PF-3 by Guy Rixon.
[7] Self-defining Data System (SDS) Version 1.4
AAO document by Jeremy Bailey.
1.4 Overview
Section 2 introduces the client-server concept and discusses the benefits of using such a model. Section 4 explores the concept in an example of a client program. The important details of libcia are laid out in section 6 and linking instructions are in section 7. The ultimate reference for libcia is the prologue comments of each component: these are presented in Annex B.
Client-server software requires message-passing between processes and (usually) between processors. Any work done by the server on behalf of the client becomes the subject of a `transaction', sometimes expressed in terms of an ADAM or DITS [1] action on a D-task. Where transactions are run in parallel, as is common in observing systems, the client ceases to be a simple sequential program and takes on real-time responsibilities. Since there may be many clients in a system, and since the pattern of parallel transactions may be complex, it is easy for the client software to become unmanageable.
Remote Procedure Calls (RPCs) offer a chance to hide the complexity of client programs in subroutine libraries and to regain the simplicity and intelligibility of localized, sequential programs. Simply put, each transaction appears in the client as a library-function call which returns when the transaction is complete. In our parallel, distributed systems, the basic RPC-call has to execute a variable number of transactions in parallel, and must allow the calling program to react to each transaction as it completes or fails with the others still in progress. In pseudo-code, the transactions appear in the client as
Set up several transactions.
Repeat:
Process all transactions.
Until all transactions finished or a transaction reports an error.
In many RPC systems, a separate function is compiled and called for each separate RPC. Due to limitations of the underlying message system, this function takes a single argument (normally the address or a structure) to be sent to the server. The function `knows' how to locate and contact the server. The ING system uses a more capable message system (the IMP [3] sub-system of DRAMA) and so can use a generic function, ciaExecuteRpc(), to execute all RPCs. The single argument used by most RPC systems is expanded to describe all aspects of the transaction and is an RPC-control block (a structure of type ciaRpc_t). Flags in the RPC-control structure allow the client to determine which RPC has completed on each return from ciaExecuteRpc(). The scheme is explored with an example in the next section.
DRAMA [2], as conceived at AAO, does not use RPCs. Instead, it relies on chains of call-backs invoked by a message-reception loop. A task is expected to exit from the loop only when it dies. This structure is appropriate for servers but makes the writing of parallelized clients very hard. The ING RPC structure is layered on top of DRAMA to regain simplicity in the clients. The message-reception loop is only active while the client is running RPCs and the end of each RPC causes an exit from the loop.
#include "ciaClient.h"
int main( int argc, char *argv[] )
{
ciaRpc_t rpc;
StatusType status = STATUS__OK;
ciaLogin_t us = { 1, "Fred", 10000, 2000, CIA_NO_FLAGS };
(void)ciaClientBegin( &us, &argc, argv, CIA_NO_FLAGS, &status );
ciaObeyRpc( "BOGUS", "NOP", &rpc, &status );
(void)ciaExecuteRpc( &status, 1, &rpc );
return( ciaClientEnd( CIA_NO_FLAGS, status ) );
}
That's it: just nine lines of code. Let's examine it in some detail.
The #include statement is crucial for programming clients: it supplies definitions of the essential types and pre-processor symbols. Most clients need only include this file and the system-supplied include-files such as stdio.h. There are other include-files associated with libcia but ciaClient.h itself includes those. Note also that there are no #include statements for DRAMA files. The necessary DRAMA files have also been included by ciaClient.h.
The program's main() function is defined to return an int. This is the usual status return to the calling program or shell and is necessary to let the client work properly in a script. main() is definedwith the standard arguments even though the visible client-code doesn't expect any. The command-line arguments are passed to one of the libcia functions which will deal automatically with certain standard option-flags.
The variable us, of type ciaLogin_t, defines how the client logs into the message net. The initializer for this structure sets the following data:
z The version number of the structure. This is used in a consistency check and should be set to 1 when linking against the current version of libcia.
z The name under which the client logs into the message net. Case is significant here, as in all DRAMA programs, and the name should include no more than 15 characters.
z The size in bytes of the buffers that the client establishes for incoming messages. 10,000 is a reasonable minimum; making the buffers too small may cause the client to fail if several messages arrive at once.
z A long-word of flag-bits controlling options in the underlying DRAMA code. This longword is passed into the flags argument of DitsAppInit() and should be used as defined in [1]. Most clients do not needs to invoke special features and can pass the symbol CIA_NO_FLAGS.
z The size, in bytes, of the buffer that the client uses for internal messaging. 2,000 bytes is the lowest safe value here. In general, clients don't do much internal messaging so 2,000 will be enough for almost all cases.
The variable rpc, of type ciaRpc_t, is the control block for the single RPC that the client needs to execute. It is not initialized at the point where it is declared; in fact, the format of this structured type is too large and too unstable for clients to code explict initializers. The RPC block is declared as an automatic variable in main(): it's a non-trivial structure but small enough to sit on the stack. In any case, it is important that the RPC block remain in scope while the RPC is being executed.
The call to ciaClientBegin() logs the client into the message net (i.e. establishes it as a DRAMA task) and enables standard features that are expected of all ING clients. Please see the entry for this function in appendix B for details of the standard features. At the opposite end of the program, the call to ciaClientEnd() logs the client out of the message net and does certain standard operations for task-closedown. After returning from ciaClientEnd(), the client is no longer running as a DRAMA task but can continue to do `ordinary' processing. It is normal to return immediately from main() the final status returned by ciaClientEnd().
The call to ciaObeyRpc() loads up the RPC-control block with the details of the required Obey
transaction. The call to ciaExecuteRpc() executes the transaction and returns when the transaction
is complete. While ciaExecuteRpc() is
running,
the client is sensitive to incoming messages of all types: commands from other tasks to execute actions
are obeyed and any textual messages from the server BOGUS are passed on to the message-log or talker.
The client needs to get a communication path to the program BOGUS to execute the RPC and does this
transparently as the first step in ciaExecuteRpc().
For simplicity, I've assumed that the action NOP/BOGUS needs no arguments.
If argument were required, another library call would be needed to load them into the RPC block; this is shown in
the next example.
The entry into the client is similar to `fred' in the previous section. It logs in as `acquire':
#include "ciaClient.h"
int main( int argc, char *argv[] )
{
StatusType status = STATUS__OK;
ciaLogin_t us = { 1, "acquire", 10000, 2000, CIA_NO_FLAGS };
long int nStars;
There is one RPC block for each RPC. This is unavoidable where the RPCs run in parallel. Where the RPCs run sequentially the blocks could be reused but that would make the code unclear.
ciaRpc_t slewRpc;
ciaRpc_t probeRpc;
ciaRpc_t fieldRpc;
ciaRpc_t starsRpc;
(void)ciaClientBegin( &us, &argc, argv, CIA_NO_FLAGS, &status );
The slew RPC is set up as a DITS Obey transaction for action SLEW on task TEL; TEL is the telescope server running on the same computer as ACQUIRE. This call gives a valid RPC, with its flags set ready for execution. The call does not set up the argument list to tell TEL where to slew, nor does it contact TEL to start the transaction.
ciaObeyRpc( "TEL", "SLEW", &slewRpc, &status );
The name of the target is taken from the first command-line argument and pushed into the argument list that goes into the server. This argument list uses SDS encoding (see [7]) in order to pass between computer architectures: the element slewRpc.argsIn is a variable of type SdsIdType. Once this section is complete, the RPC is ready to go, but the client has not yet made contact with TEL.
ciaRpcArgs( &status, &slewRpc, "$", argv[1] );
ciaRpcArgs() can set up any number of RPC arguments: it takes a variable number of arguments in the call somewhat like printf().
The RPC for the probe move is set up in the same way as for the slew. The autoguider server is stated to be running at the IP node autoint.ing.iac.es. As engineers, we know that this is a VME crate running DRAMA on VxWorks, but the client and RPC library don't need to know this. The underlying message-system handles the link between the processors and SDS handles any difference in representation of data. The probe is being sent to a default position (0,0).
ciaObeyRpc( "AUTO@autoint.ing.iac.es", "PROBE",
&probeRpc, &status );
ciaRpcArgs( &status, &probeRpc, "ll", 0, 0 );
The field action is again set up as an Obey transaction. Since we don't want to run FIELD until after the slew and probe move, we set its flag fieldRpc.ready to false to inhibit execution. ciaObeyRpc() defaults this flag to true.
ciaObeyRpc( "AUTO@autoint.ing.iac.es", "FIELD",
&fieldRpc, &status );
fieldRpc.ready = FALSE;
We can now execute our three RPCs. We can pass them to ciaExecuteRpc() in one call, since that function takes a variable number of RPC-blocks as arguments. ciaExecuteRpc() returns once for each RPC that completes or fails, so we expect three returns; we use a while loop to call the function until all three RPCs are done. On the first two passes through the loop, the FIELD action is inhibited by its ready flag and doesn't execute. When the SLEW and PROBE transactions are finished, we can allow FIELD to start, so we set its ready flag to true. The finished flags on the RPCs are set to false by ciaObeyRpc() and to true by ciaExecuteRpc() when the RPC completes or fails. If something goes wrong we want to give up, so we have a status trap in the loop. When an RPC returns bad status, ciaExecuteRpc() returns that condition-code in its inherited-status variable and also in the status field of the RPC control-block.
while( !slewRpc.finished &&
!probeRpc.finished &&
!fieldRpc.finished &&
StatusOk(status) )
{
ciaExecuteRpc( &status, 3, &slewRpc, &probeRpc, &fieldRpc );
fieldRpc.ready = slewRpc.finished && probeRpc.finished;
}
The fourth RPC can now be set up. This is a Get transaction for the parameter named NSTARS in the autoguider server. No argument-list is needed.
ciaGetRpc( "AUTO@autoint.ing.iac.es", "NSTARS",
&starsRpc, &status );
We execute the Get RPC and retrieve the result. Since there's only one RPC active at this point, we don't need the while loop. The returned argument-list appears in starsRpc.argsOut, a variable of type SdsIdType, and we extract the parameter value by name.
(void)ciaExecuteRpc( &status, 1, &starsRpc );
ArgGeti( starsRpc.argsOut, "NSTARS", &nStars, &status );
printf( "Number of stars found: %ld\n", nStars );
The client is now finished. It logs out of the message net and exits. The call to ciaLastWords() produces a `command completed' message if status is STATUS__OK and flushes any error messages otherwise; this call is standard but is not strictly necessary for the working of the RPC system.
return( ciaClientEnd( CIA_NO_FLAGS, &status ) );
Some general points are worth noting.
z This program is less than 30 statements long, but it sequences operations on three astronomical devices and two computers. The complexity of the controls problem has been isolated in the servers and the distributed nature of the system is largely hidden by the RPC formalism.
z The client is portable: there are no OS-specific features in the client code. To be ported, the client needs DRAMA and libcia on the target platform.
z The client runs like a shell command in Unix: it is loaded, gets its orders from its command line, executes one operation and exits. The RPC formalism may not be so well suited to clients that execute several user commands, but it is certainly not excluded.
z This client makes no attempt to recover from errors in the RPCs. However, it would be simple to add tests for specific conditions to the while loop around ciaExecuteRpc().
z The IP name of the autoguider computer obtrudes. If the autoguider moves to a different computer, the client must be edited and re-compiled; this is a general weakness of DRAMA's messaging. Section 6.2 introduces a neater way of handling the name-space.
In some cases, a server may want to invoke its own actions. In this case, use of the RPC calls is not recommended, since a server should not normally exit from its message-reception loop and the loop should not be used recursively. DRAMA provides the DUI facility [1] for invoking actions by a call-back mechanism.
ADAM tasks may also be servers for DRAMA clients. DRAMA provides a translator task, ADITS, which allows DRAMA and ADAM tasks to communicate without altering the semantics of either client or server. ADAM tasks should not attempt to use libcia. ADAM servers do not support monitoring of parameters.
Each RPC-control block includes the following elements:
ciaBool_t ready;
ciaBool_t active;
ciaBool_t finished;
StatusType status;
SdsIdType argsIn;
SdsIdType argsOut;
The elements ready, active and finished are Boolean flags (ciaBool_t is defined in ciaBoolean.h).
The ready flag is an instruction to ciaExecuteRpc() that the RPC should be executed: any RPC-control blocks with ready set to FALSE on entry are ignored. ciaObeyRpc(), ciaKickRpc(), ciaSetRpc(), and ciaGetRpc() set ready to FALSE; no other functions in libcia alter this field.
The active and finished flags are set to FALSE by ciaObeyRpc(), ciaKickRpc(), ciaSetRpc(), and ciaGetRpc(). Active is set to TRUE by ciaExecuteRpc() as soon as the RPC is activated and to FALSE as soon as it completes or fails. Finished is set to TRUE by ciaExecuteRpc() as soon as the RPC is completes or fails.
Status is a normal DRAMA condition-code. It is set to STATUS__OK by ciaObeyRpc(), ciaKickRpc(), ciaSetRpc(), and ciaGetRpc() and to some other condition-code if the RPC fails.
ArgsIn and argsOut are argument lists in SDS encoding. ArgsIn is sent to the target server with the RPC and argsOut is filled in by libcia with any arguments that the server sends back. Either element may take the integer value zero indicating the absence of an argument list (this is not the same thing as an empty argument list; see [7] for an explanation)..
Client programs are at liberty to manipulate ready and argsIn. Other fields from the list above may be read but should not be changed.
6.2 Names of tasks, actions and parameters
To set up any RPC a client must state the name of the target task and either the name of an action or the name of a parameter. There are restrictions on the forms of these names and the names are validated by the functions ciaObeyRpc(), ciaKickRpc(), ciaSetRpc() and ciaGetRpc(); if the validation fails, the RPC is not set up and the function returns bad status.
Action and parameter names must be 20 characters or less in length and may contain only alphanumeric characters, hyphens and underscores; in particular, leading, trailing and embedded spaces are not allowed in the names passed to libcia. If the target server is an ADAM task, the action name should not be longer than 15 characters and should not contain lower-case letters. The restrictions for ADAM naming are not enforced by libcia.
Task names have the same restrictions as action and parameter names. Optionally, a task name may be qualified by the address of its host computer in the form task@address. In this case, the address part should be an internet address in either alphabetic (e.g. Trigan.ast.cam.ac.uk) or numeric (e.g. 131.111.69.70) form. In the address part only, full-stop characters are allowed. It is an error to append `@' to the task name without giving an address.
In practice, it is both undesirable and unnecessary to code machine addresses in clients: all normal clients should use unqualified task-names. The DRAMA message system caches the names of all remote tasks that have been contacted from a given system in such a way that those tasks appear in the name-space for local tasks. The best way to locate remote tasks is then:
z Clear away all tasks and empty the name space (the cleanup command).
z Use a special configuration client to contact all remote tasks required for the current configuration.
z Address the remote tasks from clients as if they were local.
When this scheme is used, the names of tasks must unique across all machines used in a system. It is reasonable to have tasks of the same name at separate telescopes.
6.3 Communications paths and message buffers
The DRAMA message system is based on the idea of a communications path between two tasks. The tasks establish the path at the first transaction and use the same path for all subsequent transactions. The path remains in use until one or both tasks exit; DRAMA allows the tasks to discard a path but libcia does not give access to this feature.
In the RPC system, a path from a client to a server is established either when the first RPC to that server is activated or by an explicit call to ciaGetPath(). In the general case, and particularly when contacting remote servers, the path is not got immediately and the client must run its message loop to receive notification of the completed path. This looping is done in either ciaGetPath() or ciaExecuteRpc() as appropriate. The client may process other messages while the path is pending.
In the current version, v0.5, of DRAMA, errors occur if two RPCs are passed to ciaExecuteRpc() without paths. This is thought to be and may be fixed in later versions. Until it is fixed, it is safer to get paths using ciaGetPath().
The path is associated with a message buffer in each task of a fixed and finite size. Overflowing the message buffer causes one or both of the tasks to fail. The buffer size is set either in the call to ciaGetPath() or in the calls to ciaExecuteRpc(). The function ciaRpcArgs() adjusts the buffer-size request for the outgoing message to accomodate one Obey message with the given argument list. To set the size of the buffers for return messages, ciaResizeRpc() can be used:
ciaResizeRpc( 512, 3000, 2, &rpc, &status );
Here, the message-buffer size in the server, is reset to the normal default of 512 bytes and the size of the return buffer in the client is set for two messages each of 3000 bytes.
ciaResizeRpc() is really only suitable for a client which executes one RPC on each server. If there will be multiple RPCs on a server use ciaGetPath() instead and call it before the first RPC to the server in question. (Remember that ciaResizeRpc() cannot change the buffer sizes on an established path.)
ciaGetPath() is used thus:
ciaGetPath( "SERVER", timeout, msgBytes, nMsg,
replyBytes, nReply, &status );
where timeout is the longest time (in seconds) to wait for connection, nMsg is the number of RPCs to server that will be active at once, msgBytes is the size of the argument list in the messages starting the RPCs, nReply is the number of outstanding reply messages from the server and replyBytes is the total size of each reply.
The use of the buffer-size parameters in ciaResizeRpc() and ciaGetPath() is subtle. The size of the message allows for the size of the argument list only; libcia adds to this the size of the rest of the message. If a negative value is given, libcia examines rpc.argsIn and sets the buffer size accordingly.
The return buffer in the client must allow space for all unread messages from the server summed over all active RPCs; this includes textual error messages. It seems that DRAMA allocates a single buffer of size replyBytes nReply and allows this to be filled with any combination of message lengths. Messages from a server are not read immediately (and therefore remain in the buffer) except when the client is executing ciaGetPath(), ciaExecuteRpc() or some other message-loop function such as DitsMainLoop().
6.4 Managing argument lists
The input and output argument lists for an RPC are held as SDS structures to make them portable between computers. The root of each structure is indicated by a handle, rpc.argsIn and rpc.argsOut respectively. An argument structure can be built onto rpc.argsIn (a) using ciaRpcArgs(); (b) using the arg library; by using low-level SDS functions.
The use of ciaRpcArgs() was introduced in section 4. ciaRpcArgs() takes a format string each character of which indicates the type of one argument. Here is ciaRpcArgs() formatting a character string, an unsigned short integer, a floating-point number and a pre-defined SDS structure:
unsigned short fNum = 2;
SdsIdType fDetails;
...
/* Hang an SDS object on fDetails here.*/
...
ciaRpcArgs( &status, &rpc, "$hf@", "FILTER_NUM",
fNum, 42.3, fDetails );
The arguments are named Argument1, Argument2...etc. in the order presented.
Here is code that does the same thing using the arg library:
unsigned short fNum = 2;
SdsIdType fDetails;
SdsIdType tempId;
...
/* Hang an SDS object on fDetails here.*/
...
ArgNew( &rpc.argsIn, &status );
ArgPutString( rpc.argsIn, "Argument1", "FILTER_NUM", &status );
ArgPutus( rpc.argsIn, "Argument2", fNum, &status );
ArgPutf( rpc.ArgsIn, "Argument3", 42.3, &status );
SdsCopy( fDetails, &tempID, &status );
SdsRename( tempId, "Argument4", &status );
SdsInsert( rpc.argsIn, tempId, &status );
Arguments returned by the server can be accessed through the various ArgGetx() functions. Here, a client tries to read the telescope focus from an RPC processed by the telescope task:
float focus;
...
ciaGetRpc( "TEL", "FOCUS", &rpc, &status );
(void)ciaExecuteRpc( &status, 1, &rpc );
ArgGetf( rpc.argsOut, "FOCUS", &focus, &status );
When a Get transaction completes, the parameter value is supplied in the return argument list: the argument name is the same as the parameter name. If an Obey transaction returns arguments, there is no established convention for their naming: they may be Argument1 etc. in order or they may have descriptive names.
Since argument lists are SDS objects it is sometimes useful to pass them to SdsList() to get a listing to the terminal.
6.5 Setting parameter values in other tasks
The Set transaction, to set a parameter value in a server, is rarely used; parameters are normally used by servers for output only. If such a transaction is needed, ciaSetRpc() will do the trick.
ciaSetRpc() sets the value of one named parameter; the parameter value is passed in via a void pointer. Here, a short integer is being set:
signed short focusLimit = 124;
...
ciaSetRpc( "TEL", "FOCUS_LIMIT", 's', &focusLimit );
The third argument is a single character which identifies the type of the following argument using the same coding-convention as ciaRpcArgs().
6.6 Call-back functions
The DRAMA environment is designed mainly to support server programs and is built around the principle of call-backs handling events. libcia is a conscious attempt to subvert this structure and to hide all details of the call-backs. Occasionally, however, a client needs to deal with call-backs explicitly.
All the call-backs are registered by writing the address of a handler function to an element of rpc.dui. The recognized call-backs are currently
rpc.dui.SuccessHandler
rpc.dui.ErrorHandler
rpc.dui.TriggerHandler
rpc.dui.InfoHandler
rpc.dui.Completehandler
One of the first two will be triggered at the end of each RPC: SuccessHandler if the server considered the action to have succeeded and ErrorHandler otherwise. The functions in libcia that initialize RPC-control blocks supply a stub for these call-backs that keeps the RPC system working smoothly. A client may replace either or both of these stubs with its own function using the rules in [1].
CompleteHandler is reserved for the use of ciaExecuteRpc() (it is used to force an exit from the message loop when an RPC completes or fails). Any call-back you put here will be ignored.
InfoHandler is called if the server passes back textual messages to be forwarded to the user: this can happen during the course of an RPC. No call-back is supplied by libcia by default, which allows DRAMA to write the message out to the terminal. A client can install a call-back here to re-route the message. The most common call-back is ciaErsTrap(), a part of libcia, which adds the text-messages to the client's ERS Stack.
TriggerHandler is normally used with monitor transactions: see below.
6.7 Monitoring parameters
ciaMonitorRpc() allows monitoring of a parameter in another task. It sets up a monitor transaction which runs until the client exits or until the transaction is cancelled. The monitor RPC does not normally return from ciaExecuteRpc() (it will do so if the monitor fails) but the trigger handler is invoked each time a new parameter value is notified to the client. ciaMonitorRpc() supplies a minimal trigger-handler but for serious work the client should set rpc.dui.TriggerHandler to the address of a suitable function.
Here is ciaMonitorRpc() settings a monitor on the telescope focus:
ciaMonitorRpc( "TEL", "START", 0, "FOCUS",
NULL, NULL, &startRpc, &status );
startRpc.dui.TriggerHandler = FocusChanged;
(void)ciaExecuteRpc( &status, 1, &startRpc );
Note the use of "START" in the action argument to set the monitor going. FocusChanged() is the trigger handler for this monitor. The monitor is not enabled until ciaExecuteRpc() is called. Note that this is in fact an infinite loop: the single RPC will not return from ciaExecuteRpc() so long as the monitor persists but will execute FocusChanged() in the background. It is more normal to run the monitor RPC in parallel with another RPC that can return from ciaExecuteRpc().
In all monitor transactions, the first trigger-message received shows the ID-number of the monitor instead of the parameter value. If it is necessary to alter the operation of the monitor (e.g. to shut it down) the ID must be caught and recorded by the trigger handler (using a global variable) and then fed back to ciaMonitorRpc():
static int monitorID;
...
ciaMonitorRpc( "TEL", "CANCEL", monitorID,
NULL, NULL, NULL, &cancelRpc, &status );
while( StatusOkP(status)
&& !startRpc.finished
&& !cancelRpc.finished
(void)ciaExecuteRpc( &status, 2, &startRpc, &cancelRpc );
It is important to remember that the start and cancel operations for the monitor are separate RPCs. Both complete as soon as the monitor is cancelled.
ciaMonitorRpc() is a particularly ugly and awkward function. It is likely to be replaced in the future by something better under a difference name. Due warning of this will be given.
6.8 Which RPC has matured?
When ciaExecuteRpc() is called with a list of RPCs it returns once when each RPC completes or fails. Some clients may need to find out which RPC has matured and take action. ciaExecuteRpc() returns the address of the control block for the RPC that causes it to return, if and only if status is good on entry. ciaExecuteRpc() returns NULL if it is fed a bad status.
The implementation of ciaExecuteRpc() maintains an implicit list of all active RPCs that is independent of the set of control blocks passed in the call. Consider the following:
ciaObeyRpc( "SERVER", "ACTION1", &rpc1, &status );
ciaObeyRpc( "SERVER", "ACTION2", &rpc2, &status );
ciaObeyRpc( "SERVER", "ACTION3", &rpc3, &status );
(void)ciaExecuteRpc( &status, 2, &rpc1, &rpc2 );
/* Do other processing */
qRpc = ciaExecuteRpc( &status, 1, &rpc3 );
In this case, the first call to ciaExecuteRpc() returns when one of rpc1 or rpc2 matures and the other is still running. The second call to ciaExecuteRpc() can return for either rpc3 or one of the first two RPCs. In this case, the return value from ciaExecuteRpc() will still be correct.
It is bad style to leave an RPC unfinished when a client exits (although it will probably not have a long-term effect on the system). It is easier to account for all RPCs if a client has only one loop calling ciaExecuteRpc() which is left only when all the RPCs have matured.
6.9 Re-using RPC-control blocks
Sometimes it is necessary to do an RPC twice, perhaps to recover from an error:
ciaObeyRpc( "AUTO", "PROBE", &rpc, &status );
CiaRpcArgs( &status, &rpc, "ll", 0, 0 );
ciaExecuteRpc( &status, 1, &rpc );
if( !StatusOk(rpc.status) )
{
/* Clear error condition somehow... */
ciaReuseRpc( &rpc, &status );
ciaExecuteRpc( &status, 1, &rpc );
}
Once an RPC has been executed once by ciaExecuteRpc(), it will not run again without intervention. ciaReuseRpc() resets the state flags and status field of the RPC-control block but leaves the other information intact. This allows ciaExecuteRpc() to run the RPC again.
6.10 Talking with the user
For reasons too arcane to discuss here, dialogues on standard input and output are not favoured in clients. Instead, the RPC mechanism can be used enter dialogues with a `Talker', a standard user-interface task. The parts of the CIA library dealing with this (and the reasons why one should do something so involved) are described in OBS-TALK-1.
6.11 Condition codes and error messages
Condition-codes specifically to do with libcia are defined in ciaErr.h, which is an include-file following the message-facility conventions of DRAMA. This file is included when ciaClient.h is included. The table by which the codes are translated to error messages is held in ciaErr_msgt.h. This file is also included with ciaClient.h and is added to the translation facility by ciaClientBegin().
6.12 Addresses of remote clients
It is bad practice to hard-code IP addresses into client programs: it make the clients useless if their servers move to different machines. Equally, it is awkward for a client to have to read the address of a server from some configuration database each time the client runs. Luckily, DRAMA has a feature that avoids this problem.
When a path is got by any task on machine A to a task on a remote machine B, the DRAMA infrastructure remembers the details of that path and arranges that the remote task be made to appear as a local task on A. Any tasks on A that subsequently try to talk to the same task on B are absolved of the need to specify an IP address. This arrangement holds until the task on B explictly logs out of the message net.
It is intended that the start-up sequence for ING systems include commands that access all the remote servers from the computer on which the clients are to run. Hence, the clients may treat all servers as local.
Clients must be linked with the library libcia and the linking may be static or dynamic. For dynamic linking use
cc -o client client.o \
-L $$OBSSYS/lib -R $$OBSSYS/lib -l cia \
-L $$DUL_LIB -l dul `$$DITS_LIB/dits_link`
in which the -R option writes the name of the directory holding the library into the executable file. If you don't use -R you will have to include the library directory in LD_LIBRARY_PATH at run-time
Dynamic linking is the default; to get static linking use
cc -o client client.o
-B static -L $$OBSSYS/lib -l cia \
-B dynamic -L $$DUL_LIB -l dul `$$DITS_LIB/dits_link`
in which the -B dynamic switch is needed to pick up libraries referenced in dits_link. Alternatively, refer directly to the object library:
cc -o client client.o
$$OBSSYS/lib/libcia.a \
-L $$DUL_LIB -l dul `$DITS_LIB/dits_link`
Annex A. Document history
Issue 1.1 27/06/95 Original document.
Issue 2.1 29/02/96 The section on `Details of the system' was greatly expanded. The prologues were moved to annex B and updated from libcia v5.2.
Issue 2.2 1996-07-11 Updated to suit libcia v6. The descriptions of the functions have been updated and the set of functions described has changed. Thesection describing `fred' is new; the `acquire' example has been upgraded to the modern form. A section on network names was added. The linking instructions are extended to include DUL.
Issue 2.3 1998-07-02 An error in the description of ciaLogin_t was corrected. In previous issues, the flags member had been put bewteen the main buffer-size and the self-buffer size in the examples of constructors.
Annex B. Prologue comments from selected library functions
ciaClient.c v1.1
TYPE:
C source-code
PURPOSE:
To perform the initialization and tidy-up functions of a transient client-program.
FUNCTION:
This component implements a standard start-up and shut-down for an ING transient client-program using the
CIA library.
The functions `ciaClientBegin()' and `ciaClientEnd()' should be called as a matched pair, the former at the start of the program and the latter at the end. The return value from `ciaClientEnd()' should be returned from `main()' or passed to `exit()'.
A program structured in this way gains the following benefits:
- The RPC features of libcia are enabled.
- The interface to the Talker is enabled.
- The interface to the log manager is enabled.
- Standard command-line options for the above interfaces are processed, stored and eliminated from the argumnent lists.
- The starting and ending of the command are logged to the talker, showing the command-line arguments.
- Any messages in the ERS stack are flushed to the talker when `ciaClientExit()' is called.
- If the program runs for more than 30 seconds, a bell is rung when the program completes.
SUBORDINATES:
ciaClient.h standard declarations for clients
libcia client-support library
DEPENDENCIES:
ciaClientEnd() should not be called unless ciaClientBegin() has been called.
The functions concerned with RPCs and with the talker should not be called until ciaClientBegin() has completed.
This component replaces ciaRpcInit.c, ciaTalkInit.c and cialastWords.c, and hence makes obsolete the functions ciaRpcInit(), ciaTalkInit() and ciaLastWords(). Those functions should not be used in a program that uses ciaClientBegin() and ciaClientEnd(). (ciaTalkInit() is used internally by libcia.)
INTERFACES:
Call from C:
char *ciaClientBegin
( ciaLogin_t *us, Define the message-net login.
int *argc, Number of cmd-line args.
char **argv, Command-line arguments.
long flags, Flags for libcia.
StatusType *status ) DRAMA status.
This function returns the name by which the client has logged into the message net. `argc' and `argv' are the command-line arguments of the program.
Call from C:
int ciaClientEnd
( long flags, Flags for libcia.
StatusType status ) DRAMA status (NB: by value!).
This function returns 0 if the final status was good and 1 otherwise.
PROCESSING:
All processing is sequential.
ciaClientBegin():
Record the start time in startTime_l.
Record the command string in argString_l.
Start DITS with a unique name.
Enable the standard action PING and EXIT.
Call ciaTalkInit() to connect to the Talker.
Report the command string.
ciaClientEnd():
Call ciaLastWords() for a final report.
Ring the bell if StartTime_l was 30+ seconds ago.
Shut down DITS.
DATA:
`netName_l' is the name by which the client logs into the the message net. It is set in `ciaClientBegin()' and used
in `ciaClientEnd()'. `startTime_l' is the time (in whole seconds since 1970) at which the command started. It is
set in `ciaClientBegin()' and used in `ciaClientEnd()'. Both these data are static and private to this component.
ciaClient.h v1.1
TYPE:
C source-code (include-file).
PURPOSE:
To make the declarations needed by standard ING client-programs.
FUNCTION:
All transient client-programs using libcia should include this file. The various other include-files that clients need
are all included by the current component and need not be refered to directly by the client.
SUBORDINATES:
ciaActions.h
ciaRpc.h
ciaTalk.h
ciaErr.h
ciaErr_msgt.h
ciaBoolean.h
DEPENDENCIES:
None.
INTERFACES:
Call from C:
# include "ciaClient.h"
PROCESSING:
The contents of this file are only compiled if the symbol CIA_CLIENT_H is undefined on entry. If so, that symbol
is defined on exit.
DATA:
The following DRAMA files are included:
- DitsTypes.h for general types (also includes status.h and sds.h)
- Ers.h for ERS_M_XXX.
- mess.h for the message table facilities.
ciaErsFlush.c v1.1
TYPE:
C source-code: one public function.
PURPOSE:
To flush the ERS stack as a dialogue with a talker.
FUNCTION:
ciaErsFlush() replaces and augments ErsFlush() for use in client programs using ING RPCs. Given that there are
some messages in the stack, ciaErsFlush() outputs them as a dialogue with a talker task. The dialogue can be of
any type that the talker supports; by default it is a COMMENT or ALARM dialogue according to the flags of the
messages in the stack. If a dialogue is specified that returns araguments, the SDS id of these arguments is returned
by ciaErsFlush().
SUBORDINATES:
Included: ciaRpc.h, ciaTalk.h
Called: ciaReuseRpc(), ciaExecuteRpc()
DEPENDENCIES:
DitsInit(), ciaRpcInit() and ciaTalkInit() should have completed successfully before ciaErsFlush is called. This
function calls (indirectly) the DITS event loop and so should not be called when the event loop is already active.
INTERFACES:
Call from C:
SdsIdType args = ciaExecuteRpc( const char *dialogueType )
where args are the arguments returned from the dialogue and dialogueType is the name of the dialogue action on the talker. If a null pointer is given for dialogueType, a default type is assumed.
PROCESSING:
The dialogue is built up in ciaErsRpc_g. ErsFlush() is called to flush the ERS stack into the argument list of the
RPC. The action name is set to dialogueType if given; otherwise, the action name is COMMENT or ALARM if
the alarm flag is set. ciaExecuteRpc() is called to execute the RPC.
The global counters ciaErsCharsFlushed_g and ciaErsFlags_g are reset to zero to show that all messages flushed from the stack have been sent.
DATA:
The dialogue RPC is set up in ciaErsRpc_g, a global variable provided for this purpose in ciaTalkInit.c.
ciaErsTrap.c v1.2
TYPE:
C source-code.
PURPOSE:
To trap informational/error messages from subsidiary servers and to add them to the client's ERS stack.
FUNCTION:
ciaErsTrap() is intended to be installed as the `info handler' in an RPC. It is then called whenever the server in the
RPC transmits informational or error messages. ciaErsTrap() traps these messages, which would otherwise be
written to standard error, and adds them to the client's ERS stack. In this scheme, if the client can recover from the
error, the stack may be wiped and the error messages need not be output to the user.
SUBORDINATES:
Included: ciaRpc.h, ciaTalk.h
DEPENDENCIES:
DitsInit(), ciaRpcInit() and ciaTalkInit() must have completed successfully before ciaErsTrap() is called. This
function should only be called as the `info handler' of a transaction started by DUI (ING RPCs qualify in this
respect).
INTERFACES:
Declare this handler by setting dui.InfoHandler (or rpc.dui.InfoHandler) to its address, e.g.
#include "Dui.h"
#include "ciaRpc.h"
#include "ciaTalk.h"
...
DuiDetailsType dui
dui.InfoHandler = ciaErsTrap;
PROCESSING:
For each line of text in the messages sent by the server, the text, flags and status are extracted from the argument
list and one call to ErsRep() is made. Since the arguments are arrays (see below), they cannot be handled by the arg
package and SdsFind()/SdsFreeId() calls are used to get the identifiers.
A new error-context is created (ErsPush()) before any processing is done. This context holds the messsage(s) sent by the server and any error text generated by failures in ciaErsTrap(). If the message-processing succeeds, this context is merged into the one above; otherwise, the context is annulled.
This component always returns good status (bad status tends to hang up the event loop). If the trapping is a success, true is returned; otherwise, false is returned which may persuade DITS to output the message itself.
DATA:
The message(s) sent by the server are obtained as an an argument-list from DitsGetArgument(). There are three
significant arguments, each of which is a primitive array with one member per line of message:
FLAGS[] contains the ERS flags for each line;
STATUS[] contains the status associated with each line;
MESSAGE[200][] contains the lines of text.
The message array is assumed to contain lines no created than 200 characters.
ciaExecuteRpc.c v2.4
TYPE:
C source-code.
PURPOSE:
To execute a set of remote procedure calls (RPCs) in parallel.
FUNCTION:
ciaExecuteRpc() executes a set of DRAMA action-requests as if they were RPCs. Each transaction is described to
ciaExecuteRpc() by a structure of type ciaRpc_t. Any number of RPCs can be passed: ciaExecuteRpc() takes a
variable-length argument-list. All the transactions passed in a call to ciaExecuteRpc() are protentially run in
parallel on the various server tasks. When one of the RPCs completes or fails, ciaExecuteRpc() returns the address
of its control-structure. If status is bad on entry, the call returns NULL.
Arguments for the controlled actions are entered through the member .argsIn of ciaRpc_t. Returned arguments can be picked up from the member .argsOut.
Not all RPCs in the list are executed in each call to ciaExecuteRpc(): the set activated is controlled by the boolean flags .ready, .active and .finished in the ciaRpc_t structures. A call to ciaExecuteRpc() activates a particular RPC if and only if the .ready flag is true and both .active and finished are false. The flags .finished and .active are set by ciaExecuteRpc() itself as the work progresses. By calling ciaExecuteRpc() in a repeat loop and cross-connecting the flags, complex sequences of actions can be controlled.
SUBORDINATES:
ciaRpc.h declares ciaRpc_t.
ciaBoolean.h declares ciaBool_t, TRUE, FALSE
DEPENDENCIES:
The RPC-control structures must each have been set up before being passed to ciaExecuteRpc(). The elements
.dui.Action, .dui.TaskName, .dui.MessageBytes and .dui.ReplyBytes must be appropriately set. Success and error
callbacks should have been provided (if they are not, the RPC system will work but may generate unwanted
messsages to the terminal). Completion-callbacks should not be provided - ciaExecuteRpc() provides its own.
NB: this set-up is usually provided by the functions ciaXxxRpc() and calling programs should not be setting the
fields directly in most cases.
While the RPCs are active - between calls to ciaExecuteRpc() when RPCs are running in parallel - only the flag .ready should be changed in the RPC control-structures. Changing any other component may cause an infinite loop in ciaExecuteRpc().
ciaExecuteRpc() may only be used in one thread at a time and should never be called from signal handlers.
The parent program must be logged into the DRAMA message-net before any call to ciaExecuteRpc(). ciaRpcInit() should have been called.
INTERFACES:
Call from C:
ciaRpc_t *ciaExecuteRpc
( StatusType *status, Inherited status.
int nRpc, Number of RPCs to consider.
... ) nRpc items of type ciaRpc_t*.
PROCESSING:
The RPCs are executed as DRAMA transactions in user-interface context. DuiExecuteCmd() is called to invoke
the actions and DitsMainLoop() is called to wait for the responses. Any RPC for which DuiExecuteCmd() is
called is marked as active. If DuiExecuteRpc() returns bad status, the RPC is also marked as finished.
The variable-length list of RPC-structures is addressed through the standard library for variable argument-lists. The list is traversed twice, once to start the RPCs and once to find the one that has returned. On the second pass, any RPC that is marked as both active and finished is assumed to have recently completed; the address of the last of these in the list is returned as the result of ciaExecuteRpc(). These RPCs are now marked as not active.
ciaRecoverRpc() is established as the completion-callback for each of the RPCs. This function sets the finished flag, extracts the returned arguments and records the final status of the RPC. It then forces an exit from DitsMainLoop() and a return to ciaExecuteRpc().
DitsMainLoop() is called if and only if (a) at least one RPC in the list is active; (b) no RPC in the list is both active and finished. The first condition avoids a hang if ciaExecuteRpc() is called with a dead list. The second condition allows the calling program to detect aborted RPCs immediately. Note that if one RPC in the list aborts, the others are still active, but their completion or failure will not be detected until the next call to ciaExecuteRpc().
DATA:
In order for the callback ciaRecoverRpc() to find its RPC-control structure, a pointer to that structure is kept in the
.UserData field of the .dui sub-structure.
ciaExit.c v1.2
TYPE:
C source-code: one public and one private function.
PURPOSE:
To terminate a DRAMA task in response to the EXIT action.
FUNCTION:
ciaExitInit() enables the EXIT action by adding the action to the calling task's map of actions. In this call, the task
can specify whether the EXIT action causes the task to die or to return from its main event loop, and can specify
a function to be called before termination or loop exit. When ciaExitInit() has completed successfully, any
invocation of EXIT calls ciaExitObey().
ciaExitObey() calls the function, if any, that was specified to ciaExitInit(). If the call to ciaExitInit() specified that the task is to die, ciaExitObey() suts down DITS and calls exit() with good status Otherwise, ciaExitObey() makes a request for the DITS event-loop to be terminated and ends the action with good status.
SUBORDINATES:
Included: ciaActions.h
DEPENDENCIES:
DitsInit() should have completed successfully before ciaExitInit() is called.
INTERFACES:
Call from C:
void = ciaExitInit( int die,
char *taskName,
ciaTermH_t handler,
StatusType *status )
handler is the address of an completion-handler function taking no arguments and returning void. The argument die is a boolean integer: if it is non-zero on entry, EXIT kills the task without exiting the event loop. The symbolic constants CIA_EXIT_DIE and CIA_EXIT_RETURN can be used to set die.
Invoke the action as EXIT. No arguments are required or returned. This action will normally return good status.
PROCESSING:
The call to ciaExitInit() sets up persistent variables for use in ciaExitObey(). Successive calls to ciaExitObey()
would over-write these variables.
If an completion-handler is provided in ciaExitInit(), ciaExitObey() executes it directly. The handler is not registered with the atexit() call and will not be called if exit() is called elsewhere in the program or if the program is killed by a signal. If functions are registered with atexit(), they are called after the completion-handler specified to ciaExitInit().
DATA:
- termOption_l records the die/return option.
- termH_l records the address of the exit handler.
- taskName_l records the task's name.
ciaGetPath.c v2.1
TYPE:
C source-code.
PURPOSE:
To set up an IMP path for RPCs.
FUNCTION:
ciaGetPath() gets a path to one DRAMA program, which may either be on the same processor as the calling
program or on a remote processor. The total sizes of IMP resources for the path are specified in the call; these
should be enough for the largest set of RPCs to the target program by the calling program that need to execute in
parallel. The call returns when the path has been found or is deemed unobtainable. A timeout is applied.
ciaGetPath() returns the path ID to its caller. The path ID is also cached by DITS and is applied by DITS to all subsequent transactions between the calling and target programs. This means that its is not normally necessary for the calling program to store the path ID itself.
DITS allows only one path to exist at a time between a pair of tasks. That is, a second call to ciaGetPath() for the same target task will return immediately with good status but will not change the path or its buffer sizes.
ciaGetPath() does not validate task names itself. Invalid task names are passed to DITS and cause a `task not found' error. Passing a null pointer for the task name or zero for any of the message-resources aborts the calling program. If a negative number is passed for the timeout, ciaGetPath() applies a default (positive) timeout.
ciaGetPath() receives and processes messages from other tasks (messages are exchanged as part of setting a path). If the calling client has actions enabled, these actions may execute while ciaGetPath() is active.
SUBORDINATES:
None.
DEPENDENCIES:
ciaGetPath() should never be called when RPCs or other DITS transactions are active in user-interface context. It
should not be called from event-handler functions either. In general, a client should call ciaGetPath() once for
each server it intends to use before any transactions are started.
INTERFACES:
Call from C:
DitsPathType ciaGetPath
( const char *taskName, Name of target task.
int timeout, Timeout in seconds.
int msgBytes, Size of outgoing message.
int maxMsgs, Number of outgoing messages.
int replyBytes, Size of returned messages.
int maxReplies, Number of returned messages.
StatusType *status ) DRAMA status.
RESOURCES:
This call will attempt to allocate in the target program message a ring buffer of size msgBytes*maxMsgs.
Completion of the return path will allocate in the calling task a ring buffer of size replyBytes*maxReplies.
PROCESSING:
All the path-getting details, including the wait for the appropriate messages, are handled by `DulGetPathW()'.
The transaction is undertaken in `Uface' context and a dummy event-handler `ignore' is supplied to enable this.
DATA:
There are no global or static data in this component.
ciaGetRpc.c v1.2
TYPE:
C source-code: one public function.
PURPOSE:
To initialize an RPC-control structure for a (parameter) Get Transaction.
FUNCTION:
ciaGetRpc() takes a structure of type ciaRpc_t and sets its members for a Get transaction. Further adjustments to
the structure may be made after ciaGetRpc() completes and before the RPC is initated by ciaExecuteRpc(). The
name of the parameter is taken from the arguments to the call to ciaGetRpc().
SUBORDINATES:
Included: ciaRpc.h
Called: ciaObeyRpc()
INTERFACES:
Call from C:
void = ciaGetRpc( const char *task,
const char *parameter,
ciaRpc_t *rpc,
StatusType *status )
PROCESSING:
This function calls ciaObeyRpc() and changes the message type in .dui.MsgType to DITS_MSG_GET. The name
of the parameter is written into rpc.dui.Action. No argument list is used.
ciaKickRpc.c v1.1
TYPE:
C source-code: one public function.
PURPOSE:
To initialize an RPC-control structure for a Kick Transaction.
FUNCTION:
ciaRpc() takes a structure of type ciaRpc_t and sets its members for a Kick transaction. Further adjustments to the
structure may be made after ciaKickRpc() completes and before the RPC is initated by ciaExecuteRpc().
SUBORDINATES:
Included: ciaRpc.h
Called: ciaObeyRpc()
INTERFACES:
Call from C:
(void) = ciaKickRpc( const char *task,
const char *action,
ciaRpc_t *rpc,
StatusType *status );
PROCESSING:
This function calls ciaObeyRpc() and changes the message type in .dui.MsgType to DITS_MSG_KICK.
ciaLockReq.c V1.1
TYPE:
C source-code: one public function.
PURPOSE:
To make lock requests on behalf of clients.
FUNCTION:
This component is a stub. ciaLockReq() returns without action.
SUBORDINATES:
None.
INTERFACES:
Call from C:
void = ciaLockReq( const char *lockName, StatusType *status )
ciaMonitorRpc.c v2.1
TYPE:
C source-code: one public function.
PURPOSE:
To initialize an RPC-control structure for a Monitor Transaction.
FUNCTION:
ciaMonitorRpc() takes a structure of type ciaRpc_t and sets its members for a Monitor transaction. Further
adjustments to the structure may be made after ciaMonitorRpc() completes and before the RPC is initated by
ciaExecuteRpc().
The action argument to ciaMonitorRpc() must be one of START, FORWARD, ADD, DELETE or CANCEL. START and FORWARD start a new monitor transaction; FORWARD causes the parameter values to be forwarded to the task named in the argument *task2 withthe action named in *action2. ADD, DELETE and CANCEL operate on an existing monitor identied by the argument monitorId. In all cases, the argument `parameter' is the name of a single parameter. Any value, including NULL, may be given for arguments not needed in a particular call.
The results of the monitor are trapped and copied, in SDS encoding, into rpc.argsOut. Each response from the monitor cases ciaExecuteRpc() to return. This behaviour can be changed by imposing a different handler for trigger messages.
SUBORDINATES:
Included: ciaRpc.h
Called: ciaObeyRpc()
INTERFACES:
Call from C:
void = ciaMonitorRpc( const char *task,
const char *action,
int monitorId,
const char parameter,
const char *task2,
const char *action2,
ciaRpc_t *rpc,
StatusType *status )
PROCESSING:
This function calls ciaObeyRpc() and changes the message type in .dui.MsgType to DITS_MSG_MONITOR.
The argument list (rpc->argsOut) for the RPC is assembled as follows.
START: Argument1 = name of parameter.
FORWARD: Argument1 = name of task forwared to.
Argument2 = name of action invoked by FORWARD.
Argument3 = name of parameter.
ADD: Argument1 = monitor ID.
Argument2 = name of parameter.
DELETE: Argument1 = monitor ID.
Argument2 = name of parameter.
CANCEL: Argument1 = monitor ID.
If an invalid string is given for `action', status is set to CIA__BADMONOPT and a message is stacked in ERS.
Trigger messages are handled by ciaMonitorTrigger(). This function locates the RPC control structure using the pointer in the .UserData field of the DUI transaction-control structure and copies the arguments to rpc.argsOut.
ciaObeyRpc.c v2.4
TYPE:
C source-code.
PURPOSE:
To initialize an RPC-control structure for an Obey Transaction.
FUNCTION:
ciaObeyRpc() takes a structure of type ciaRpc_t and sets its members for the most general type of DRAMA RPC:
an Obey transaction. Further adjustments to the structure may be made after ciaObeyRpc() completes and before
the RPC is initated by ciaExecuteRpc().
On successful return from ciaObeyRpc(), the RPC-control flags are set as follows: ready true; active false; finished false. The status field in the RPC-control block is set to good status. The input and output argument lists are cleared. The .dui sub-structure is set up for an Obey transaction with the given task and action names.
Validity checks are made on the task and action names. Names that are syntactically invalid cause ciaObeyRpc() to return a bad status and to stack an ERS message.
DEPENDENCIES:
ciaRpcInit() must have completed successfully before ciaObeyRpc() is called.
The RPC-control block passed to ciaObeyRpc() must not belong to an active RPC (i.e. must not be in use by ciaExecuteRpc()) at the time of the call.
Passing null pointers for any of the arguments to ciaObeyRpc() will abort the calling program.
SUBORDINATES:
Included: ciaRpc.h
INTERFACES:
Call from C:
(void) = ciaObeyRpc( const char *task,
const char *action,
ciaRpc_t *rpc,
StatusType *status );
PROCESSING:
ciaObeyRpc() does the following:
- validates the task and action name and aborts if they are invalid;
- initializes rpc->dui with DITS' defaults;
- loads the task and action name into rpc->dui;
- sets the get-path timeout in rpc->dui to 30 seconds;
- sets the sizes for messages in rpc->dui to 512 bytes;
- establishes ciaFinishRpc() as the success and status handler;
- sets the RPC-control flags:
(ready, active, finished, status) = (TRUE, FALSE, FALSE, STATUS__OK)
- sets the SDS IDs for the input and output argument lists to zero.
The private function ciaFinishRpc() is specified as a callback on success or failure of the transaction. This function does nothing but return true; this stops DITS from writing messages to the terminal.
Most of the code in this component is to do with validating the inputs to ciaObeyRpc(). TaskBadSyntax() takes a task name (with optional address in the format task@address) and checks it for the following faults:
- overall length > 80 characters or zero;
- task part > 20 characters;
- address part has no characters when `@' is present';
- `.' appears in the task part;
- `@' appears more than once;
- characters other than "@-_." and alphneumrics are present.
ActionBadSyntax() takes an action (or parameter) name and checks for the following faults:
- overall length > 20 characters or zero;
- characters other than `-', `_' and alpha numerics are present.
The first fault found causes a call to ReportBadName() which generates a single-line message in ERS and returns the condition code CIA__BADNAME.
Both TaskBadSyntax() and ActionBadSyntax() return true if they detect an error.
ciaObssysFind.c v1.1
TYPE:
C source-code: one public function.
PURPOSE:
To find a file in an ING observing-system.
FUNCTION:
ciaObssysFind() searches for a file in a set of directories defining an ING observing system. The list of directories
is taken from the environment variables OBSSYSPATH (first choice) or OBSSYS (used if OBSSYSPATH is not
set); if neither variable is set, the current working directory is searched.
If the file is found, the address of its absolute path-name is returned; otherwise, NULL is returned. The returned address refers to a buffer within pathfind(3G), called by ciaObssysFind(), which will survive until the next call to pathfind() in the current thread. The name in the buffer is zero-terminated.
In normal use, OBSSYS is expected to name a single observing system, e.g. /ing/s1.1, and OBSSYSPATH is expected to name a set of observing systems and/or patches, e.g. /ing/p22:/ing/p31:/ing/s1.1. In both cases, the named directories are the roots of sub-trees. Hence, one would normally call ciaObssysFind() with a sub-directory name prepended to the name of the target file, e.g.
ccd_binary = ciaObssysFind( "bin/ccd0" );
config_file = ciaObssysFind( "etc/config.dat" );
SUBORDINATES:
None.
DEPENDENCIES:
If set, OBSSYSPATH and OBSSYS must contain a colon-separated list of directories or the name of a single
directory.
INTERFACES:
Call from C:
char = ciaObssysFind( const char *filename )
ciaResizeRpc.c v1.2
TYPE:
C source-code: one public function.
PURPOSE:
To adjust the message-buffer sizes for an RPC.
FUNCTION:
The size of the message buffers - bytes for outgoing messages, bytes for returned messages and number of
returned messages - are altered for the specified RPC. The sizes of the messages as given in the call are the size
of the argument list of the message; ciaResizeRpc() adds to the given value the size of the IMP and DITS headers
that the message needs. If the size given for the outgoing message is negative, ciaResizeRpc() works out the
required size from the argument list rpc.argsIn.
SUBORDINATES:
Included: ciaRpc.h
DEPENDENCIES:
The RPC control structure should have been set up by, e.g., ciaRpcObey(). If nBytesIn is negative on entry,
rpc->argsIn must either be a valid SDS ID or must be zero.
INTERFACES:
Call from C:
void = ciaResizeRpc( int nBytesIn,
int nBytesOut,
int nMsgOut,
ciaRpc_t *rpc,
StatusType *status )
PROCESSING:
When nBytesIn is negative on entry, the size of the message is determined by DitsGetMsgLength(). Otherwise,
the size of the message into the RPC is DITS_C_MINSIZE + nBytesIn. The return message is always of size
DITS_C_MINSIZE + nBytesOut.
DATA:
The buffer-size parameters are written into the .dui sub-structure of the RPC at dui.MessageBytes,
dui.ReplyBytes and dui.MaxReplies.
ciaReuseRpc.c v1.2
TYPE:
C source-code.
PURPOSE:
To allow an RPC-control structure to be re-used in a second RPC.
FUNCTION:
ciaReuseRpc() takes a structure of type ciaRpc_t that has previously been used in an RPC and resets its control
flags for another activation. Any arguments returned by the previous RPC are discarded. The new activation uses
by default the same action name, task name, buffer sizes and argument list as the previous activation; these
defaults can be changed by the calling program before the structure is passed to ciaExecuteRpc().
SUBORDINATES:
Included: ciaRpc.h
DEPENDENCIES:
An RPC structure must not be re-used while its RPC is active.
INTERFACES:
Call from C:
(void) = ciaReuseRpc( const char *task,
const char *action,
ciaRpc_t *rpc,
StatusType *status );
ciaRpcArgs.c v1.4
TYPE:
C source-code.
PURPOSE:
To load arguments into an RPC-control block.
FUNCTION:
An RPC-control block (C type ciaRpc_t; see [1] and ciaRpc.h) contains the element .argsOut which is the SDS ID
of the argument structure to be sent to the server that executes the RPC. ciaRpcArgs() creates this argument list
using a given set of scalar variables.
The `format' argument to ciaRpcArgs() is a string in which each character describes one item to be read from the list of trailing arguments and added to the SDS structure. The valid codes are:
$ = character string;
c = single character;
s = signed short integer;
h = unsigned short integer;
l = signed long integer;
u = unsigned long integer;
f = single-precision floating-point;
d = double-precision floating-point;
@ = SDS structure.
Other codes are ignored.
Character strings should be passed to ciaRpcArgs() by reference and all other items by value; for pre-built SDS sub-structures, pass the ID of the sub-structure by value. SDS sub-structures are cloned for inclusion in the RPC-control block; the original SDS ID remains valid and independent of the effects of the RPC.
Items are added to the SDS argument-structure in the order in which they appear in the call to ciaRpcArgs(). Each item is named Argumentn where n is the number in the list, counted from 1. If the format list is a zero-length string, no SDS argument-structure is created.
The message-buffer size-request for outgoing messages is set in the RPC block to be big enough for the given argument list. This setting has no effect if the calling task already has a path to the server in question.
SUBORDINATES:
ciaRpc.h RPC-related declarations
ciaBoolean.h defines TRUE, FALSE, ciaBool_t
libcia (rest of the) client-support library
DEPENDENCIES:
The RPC-control block should be set up by, e.g., ciaObeyRpc() before calling ciaRpcArgs(). If this order is
reversed, the general set-up will destroy the information inserted by ciaRpcArgs().
INTERFACES:
Call from C:
void ciaRpcArgs
( StatusType *status, DRAMA status.
ciaRpc_t *rpc, RPC-control block, to be updated.
const char *format, Describes the list of arguments.
... ) Arguments to be formatted.
REFERENCES:
[1] "Remote Procedure Calls fro DRAMA clients"
ING/RGO document OBS-RPC-1.
PROCESSING:
ciaRpcArgs() will call abort() if the following conditions are true on entry:
- `status' is a null pointer;
- *status is not STATUS__OK and `rpc' is a null pointer;
- *status is not STATUS__OK and `format' is a null pointer.
ciaSetRpc.c v2.1
TYPE:
C source-code.
PURPOSE:
To initialize an RPC-control block for a (parameter) Set Transaction.
FUNCTION:
ciaSetRpc() takes a structure of type ciaRpc_t and sets its members for a Set transaction. Further adjustments to
the structure may be made after ciaMonitorRpc() completes and before the RPC is initated by ciaExecuteRpc().
The name of the parameter is taken from the arguments to the call to ciaSetRpc().
ciaSetRpc() takes thevalue to be set in the parameter from a void pointer:
this allows any of a number of parameter types to be set. The pointer is interpreted according to the `type'
argument:
'$' = character string (char*)
'c' = single character (char*)
's' = signed short integer (short*)
'h' = unsigned short integer (ushort*)
'l' = signed long integer (long*)
'u' = unsigned long integer (ulong*)
'f' = single-precision floating-point (float*)
'd' = double-precision floating-point (double*)
'@' = SDS sub-structure (SdsIdType*)
For all types except '$', the void pointer is cast to the type shown above and dereferenced to get a scalar value. For '$', the void pointer is cast to char* and used directly. It is the callers responsibility to ensure that the pointer points to appropriate data: ciaSetRpc() cannot check this.
SUBORDINATES:
Included: ciaRpc.h
Called: ciaObeyRpc()
INTERFACES:
Call from C:
void = ciaSetRpc( const char *task,
const char *parameter,
char type,
void *value,
ciaRpc_t *rpc,
StatusType *status )
where `type' denotes the nature of the data pointed to by `value' according to the rules above.
PROCESSING:
This function calls ciaObeyRpc() and changes the message type in .dui.MsgType to DITS_MSG_SETPARAM.
The input validation is delegated to ciaObeyRpc().
The parameter value is encoded in rpc.argsOut by a call to ciaRpcArgs(). There is a selection on the value of `type' and a separate call for each data type; this allows proper casting of the void pointer `value'. Any unrecognized types are ignored and leave `rpc' with no argument list. Note that character strings are passed to ciaRpcArgs() by reference and all other types are dereferenced and passed by value.
ciaTalkRpc.c v2.2
TYPE:
C source-code: one public and two private functions.
PURPOSE:
To set up RPCs for use in dialogues.
FUNCTION:
In the ING/DRAMA talk facility, described in OBS-TALK-1, dialogues with the user are conducted using the
RPC facility. This component sets up suitable RPC control-structures that may be passed to ciaExecuteRpc() for
activation.
ciaTalkRpc() initializes an RPC structure passed to it as an argument. The talker task (selected in ciaTalkInit()) is made the target task, and the action name is taken from the argument list. The arguments to the RPC are set up as required in OBS-TALK-1, with the message text taken from the arguments of ciaTalkRpc().
ciaTalkSuccess() and ciaTalkError() are the success and failure handlers for the transaction: their addresses are written by ciaTalkRpc() into the RPC-control structure. ciaTalkSuccess() does nothing but return true to dissuade DITS from talking about the transaction. ciaTalkError() executes the dialogue on standard output and standard input.
SUBORDINATES:
Included: ciaRpc.h, ciaTalk.h
DEPENDENCIES:
DitsInit(), ciaTalkInit() and ciaRpcInit() must have completed successfully.
INTERFACES:
Call from C:
void = ciaTalkRpc( StatusType *status, ciaRpc_t *rpc,
const char *action, const char *text, ... )
The variable list of arguments has no effect at present. In future, ciaTalkRpc may allow a `printf()' style of formatting text.
REFERENCES:
Dialogues with the user: general princples"
RGO/ING document OBS-TALK-1.