Client/server using the global namespace

If the client/server relationship that you're porting depended on the global namespace, then the client used qnx_name_locate(), and the server would “register” its name via qnx_name_attach().

In this case, you have two choices. You can try to retain the global namespace idiom, or you can modify your client and server to act like a standard resource manager. If you wish to retain the global namespace, then you should look at the name_attach() and name_detach() functions for your server, and name_open() and name_close() for your clients.

However, I'd recommend that you do the latter; it's “the QNX Neutrino way” to do everything with resource managers, rather than try to bolt a resource manager “kludge” onto the side of a global namespace server.

The modification is actually reasonably simple. Chances are that the client side calls a function that returns either the process ID of the server or uses the “VC” (Virtual Circuit) approach to create a VC from the client's node to a remote server's node. In both cases, the process ID or the VC to the remote process ID was found based on calling qnx_name_locate().

Here, the “magic cookie” that binds the client to the server is some form of process ID (we're considering the VC to be a process ID, because VCs are taken from the same number space, and for all intents and purposes, they look just like process IDs).

If you were to return a connection ID instead of a process ID, you'd have conquered the major difference. Since the QNX 4 client probably doesn't examine the process ID in any way (what meaning would it have, anyway?—it's just a number), you can probably trick the QNX 4 client into performing an open() on the “global name.”

In this case, however, the global name would be the pathname that the resource manager attached as its “id.” For example, the following is typical QNX 4 client code, stolen from my caller ID (CLID) server library:

/*
 *  CLID_Attach (serverName)
 *
 *  This routine is responsible for establishing a connection to 
 *  the CLID server.
 *
 *  Returns the process ID or VC to the CLID server.
*/

// a place to store the name, for other library calls
static char CLID_serverName [MAX_CLID_SERVER_NAME + 1];

// a place to store the clid server id
static int clid_pid = -1;

int
CLID_Attach (char *serverName)
{
    if (serverName == NULL) {
        sprintf (CLID_serverName, "/PARSE/CLID");
    } else {
        strcpy (CLID_serverName, serverName);
    }
    clid_pid = qnx_name_locate (0, CLID_serverName, 
                                sizeof (CLID_ServerIPC), NULL);
    if (clid_pid != -1) {
        CLID_IPC (CLID_MsgAttach);  // send it an ATTACH message
        return (clid_pid);
    }
    return (-1);
}

You could change this to be:

/*
 *  CLID_Attach (serverName) QNX Neutrino version
*/

int
CLID_Attach (char *serverName)
{
    if (serverName == NULL) {
        sprintf (CLID_serverName, "/PARSE/CLID");
    } else {
        strcpy (CLID_serverName, serverName);
    }
    return (clid_pid = open (CLID_serverName, O_RDWR));
}

and the client wouldn't even notice the difference.

Note: Two implementation notes: I've simply left the default name /PARSE/CLID as the registered name of the resource manager. Most likely a better name would be /dev/clid—it's up to you how “POSIX-like” you want to make things. In any event, it's a one-line change and is only marginally related to the discussion here.

The second note is that I've still called the file descriptor clid_pid, even though now it should really be called clid_fd. Again, this is a style issue and relates to just how much change you want to perform between your QNX 4 version and the QNX Neutrino one.

In any event, to be totally portable to both, you'll want to abstract the client binding portion of the code into a function call—as I did above with the CLID_Attach().

At some point, the client would actually perform the message pass operation. This is where things get a little trickier. Since the client/server relationship is not based on an I/O manager relationship, the client generally creates “customized” messages. Again from the CLID library (CLID_AddSingleNPANXX() is the client's exposed API call; I've also included checkAttach() and CLID_IPC() to show the actual message passing and checking logic):

/*
 *  CLID_AddSingleNPANXX (npa, nxx)
*/

int
CLID_AddSingleNPANXX (int npa, int nxx)
{
    checkAttach ();
    CLID_IPCData.npa = npa;
    CLID_IPCData.nxx = nxx;
    CLID_IPC (CLID_MsgAddSingleNPANXX);
    return (CLID_IPCData.returnValue);
}

/*
 *  CLID_IPC (IPC message number)
 *
 *  This routine will call the server with the global buffer 
 *  CLID_IPCData, and will stuff in the message number passed 
 *  as the argument.
 *
 *  Should the server not exist, this routine will stuff the 
 *  .returnValue field with CLID_NoServer.  Otherwise, no 
 *  fields are affected.
*/

void
CLID_IPC (IPCMessage)
int     IPCMessage;
{
    if (clid_pid == -1) {
        CLID_IPCData.returnValue = CLID_NoServer;
        return;
    }
    CLID_IPCData.serverFunction = IPCMessage;
    CLID_IPCData.type = 0x8001;
    CLID_IPCData.subtype = 0;
    if (Send (clid_pid, &CLID_IPCData, &CLID_IPCData, 
              sizeof (CLID_IPCData),
              sizeof (CLID_IPCData))) {
        CLID_IPCData.returnValue = CLID_IPCError;
        return;
    }
}

void
checkAttach ()
{
    if (clid_pid == -1) {
        CLID_Attach (NULL);
    }
}

As you can see, the checkAttach() function is used to ensure that a connection exists to the CLID server. If you didn't have a connection, it would be like calling read() with an invalid file descriptor. In my case here, the checkAttach() automagically creates the connection. It would be like having the read() function determine that there is no valid file descriptor and just create one out of the blue. Another style issue.

The customized messaging occurs in the CLID_IPC() function. It takes the global variable CLID_IPCData and tries to send it to the server using the QNX 4 Send() function.

The customized messages can be handled in one of two ways:

  1. Functionally translate them into standard, file-descriptor-based POSIX calls.

    Or:

  2. Encapsulate them into either a devctl() or a customized message wrapper using the _IO_MSG message type.

In both cases, you've effectively converted the client to communicating using standard resource manager mechanisms for communications. What? You don't have a file descriptor? You have only a connection ID? Or vice versa? This isn't a problem! Under QNX Neutrino, a file descriptor is a connection ID!