Client

The client API implementation is contained in fserv-client.c. It abstracts away the details of creating connections and exchanging messages in order to perform Fserv operations.

Connecting

The client creates an LWMsgPeer structure and connects it to the local IPC server. After this is done, any number of concurrent calls can safely be dispatched to the server through this structure. Therefore, a single global instance is lazily initialized in fserv_construct() using the pthread_once() mechanism.

Before establishing the connection, we must first create a protocol structure which describes the protocol the peer node will speak. A protocol structure allows one or more protocol specifications to be combined together. First, the structure is created:

/* Create protocol structure */
once_status = lwmsg_protocol_new(NULL, &protocol);

The protocol specification is then added:

/* Add protocol spec to protocol structure */
once_status = lwmsg_protocol_add_protocol_spec(protocol, fserv_get_protocol());

We create the peer structure, passing in our protocol:

/* Create peer */
once_status = lwmsg_peer_new(NULL, protocol, &client);

Now we can add an endpoint to connect to:

/* Add connect endpoint */
client,
FSERV_SOCKET_PATH);

Finally, we connect the peer node to the server:

/* Connect */
once_status = lwmsg_peer_connect(client, &session);

Connecting to the server gives us a session which can be used for handle management later.

Disconnecting

To disconnect from the server, the client simply disconnects the peer structure and then deletes it:

/* Disconnect and delete */

Opening a file

Opening a file involves making a call to the server. First, we acquire a call handle from the peer structure:

/* Acquire call */
status = lwmsg_peer_acquire_call(client, &call);

The input parameters to the call are initialized:

/* Set up request parameters */
request.mode = mode;
request.path = (char*) path;
in.tag = FSERV_OPEN_REQ;
in.data = &request;

The call can now be dispatched, sending the input parameters to the server and receiving back the output parameters in a single operation:

/* Make call */
status = lwmsg_call_dispatch(call, &in, &out, NULL, NULL);

If the call succeeds, out.tag is set to the type of the call result and out.data is set to the result data. The function then checks what kind of result it received. If it received a normal open response, it returns the file handle to the caller. The data is nulled out of the parameters structure so that it is not freed later on. If it received a failure response, it extracts the error code and returns the error to the caller.

switch (out.tag)
{
case FSERV_OPEN_RES:
*file = out.data;
out.data = NULL;
break;
case FSERV_ERROR_RES:
/* Open failed -- extract the error code */
ret = ((StatusReply*) out.data)->err;
goto error;
default:
ret = EINVAL;
goto error;
}

Before the function returns, it needs to clean up all resources allocated during the call. Any output parameters from the call that we have not saved are destroyed, and then the call handle is released.

/* Clean up */