An [out] pipe is implemented in a similar way to an input pipe, except that the client and server make use of the push routine instead of the pull routine. The following samples show an [out] pipe used to read the output from a shell command executed by the server.
The declarations in the interface definition are as follows:
typedef pipe char test_pipe_t;
void pipe_test2(
[in] handle_t handle,
[in, string] char cmd[],
[out] test_pipe_t *test_pipe,
[out] error_status_t *status
);
The server manager routines demonstrate a couple of possible implementations. In each case, the manager makes a cycle of calls to the server stub's push routine, ending by pushing a zero-length chunk:
#include <dirent.h>
#define SBUFFSIZE 256
void
pipe_test2(
handle_t binding_h,
idl_char *cmd,
test_pipe_t *test_pipe,
error_status_t *status
)
{
DIR *dir_ptr;
struct dirent *directory;
char buffer[SBUFFSIZE];
FILE *str_ptr;
int n;
/* An elementary mechanism to execute a command and get
* the output back. Note that popen() and fread() are
* thread-safe, so the whole process won't block while
* the call thread waits for them to return.
*
* This is potentially a dangerous operation!
* Here we'll only allow a couple of "safe" commands.
*/
if (!strcmp(cmd, "ps") || !strcmp(cmd, "ls"))
{
if ((str_ptr = popen(cmd, "r")) == NULL)
return;
while ((n = fread(buffer, sizeof(char), \
SBUFFSIZE, str_ptr)) > 0)
{
(*(test_pipe->push))(test_pipe->state, buffer, n);
}
(*(test_pipe->push))(test_pipe->state, buffer, 0);
fclose(str_ptr);
}
/* Here's another method: list an arbitrary directory
* This time, we buffer the directory names as null-
* terminated strings of various lengths. The client
* will need to provide formatting of the output stream,
* for example, by substituting a CR for each NULL byte.
*/
/*
if ((dir_ptr = opendir(cmd)) == NULL)
{
printf("Can't open directory %s\n", cmd);
return;
}
while ((directory = readdir(dir_ptr)) != NULL)
{
if (directory->d_ino == 0)
continue;
(*(test_pipe->push))(test_pipe->state, \
directory->d_name,
strlen(directory->d_name)+1);
}
(*(test_pipe->push))(test_pipe->state, \
directory->d_name, 0);
closedir(dir_ptr);
*/
*status = error_status_ok;
}
The stub enforces well-behaved pipe filling by the manager by raising exceptions as necessary. After all in pipes have been drained completely, the out pipes must be completely filled, in order.
The client code uses the same declarations as in the input pipe example, except that instead of using a client_pull routine it uses a test_push routine that prints out the contents of each received buffer:
/*
* Our push routine prints each received buffer-full.
*/
void test_push(
rpc_ss_pipe_state_t *state,
idl_char *buf,
unsigned32 count
)
{
unsigned_char_t *cptr;
for (cptr = buf; cptr < buf + count; cptr++)
{
/* For the second, directory reading example,
uncomment the following:
if (*cptr == 0)
*cptr = '\n';
*/
putchar(*cptr);
}
}
For an out pipe, the client code must do the following:
1. Allocate the test_pipe_t structure.
2. Initialize the test_pipe_t.push and test_pipe_t.state fields.
3. Pass the structure as the pipe parameter, either by value or by reference.
test_pipe_t test_pipe;
test_pipe.alloc = (void (*)())client_alloc;
test_pipe.push = (void (*)())test_push;
test_pipe.state = (rpc_ss_pipe_state_t)&out_test_pipe_state;
pipe_test2(binding_h, cmd, &test_pipe, &status);
The client stub unmarshals chunks of the pipe into a buffer and calls back to the application, passing a reference to the buffer. To allow the application code to manage its memory usage, and possibly avoid unnecessary copying, the client stub first calls back to the application's test_pipe.alloc routine to get a buffer. In some cases, this may result in the test_pipe.push routine's not having any work to do.
The client stub may go through more than one (test_pipe.alloc, test_pipe.push) cycle in order to unmarshal data that the server marshalled as a single chunk. Note that there is no guarantee that chunks seen by the client stub will match the chunks supplied by the server's push routine.