File and device input/output

Stream construction example

The following example extends the previous communications device echoing example (see ``Basic STREAMS operations'') by inserting a module in the Stream. The (hypothetical) module in this example can convert (change case, delete, and/or duplicate) selected alphabetic characters.

Inserting modules

An advantage of STREAMS over the traditional character I/O mechanism stems from the ability to insert various modules into a Stream to process and manipulate data that pass between a user process and the driver. In the example, the character conversion module is passed a command and a corresponding string of characters by the user. All data passing through the module are inspected for instances of characters in this string; the operation identified by the command is performed on all matching characters. The necessary declarations for this program are shown below:

   #include <string.h>
   #include <fcntl.h>
   #include <stropts.h>

#define BUFLEN 1024

/* * These defines would typically be * found in a header file for the module */ #define XCASE 1 /* change alphabetic case of char */ #define DELETE 2 /* delete char */ #define DUPLICATE 3 /* duplicate char */

main() { char buf[BUFLEN]; int fd, count; struct strioctl strioctl;

The first step is to establish a Stream to the communications driver and insert the character conversion module. The following sequence of system calls accomplishes the following display:

   	if ((fd = open("/dev/comm/01", O_RDWR)) < 0) {
   		perror("open failed");

if (ioctl(fd, I_PUSH, "chconv") < 0) { perror("ioctl I_PUSH failed"); exit(2); }

The I_PUSH ioctl call directs the Stream head to insert the character conversion module between the driver and the Stream head, creating the Stream shown in ``Case converter module''. As with drivers, this module resides in the kernel and must have been configured into the system before it was booted, unless the system has an autoload capability.

Case converter module

An important difference between STREAMS drivers and modules is illustrated here. Drivers are accessed through a node or nodes in the file system and may be opened just like any other device. Modules, on the other hand, do not occupy a file system node. Instead, they are identified through a separate naming convention, and are inserted into a Stream using I_PUSH. The name of a module is defined by the module developer.

Modules are pushed onto a Stream and removed from a Stream in Last-In-First-Out (LIFO) order. Therefore, if a second module was pushed onto this Stream, it would be inserted between the Stream head and the character conversion module.

Module and driver control

The next step in this example is to pass the commands and corresponding strings to the character conversion module. This can be done by issuing ioctl calls to the character conversion module as follows:

   	/* change all upper case vowels to lower case */
   	strioctl.ic_cmd = XCASE;
   	strioctl.ic_timout = 0;		/* default timeout (15 sec) */
   	strioctl.ic_dp = "AEIOU";
   	strioctl.ic_len = strlen(strioctl.ic_dp);

if (ioctl(fd, I_STR, &strioctl) < 0) { perror("ioctl I_STR failed"); exit(3); }

/* delete all instances of the chars 'x' and 'X' */ strioctl.ic_cmd = DELETE; strioctl.ic_dp = "xX"; strioctl.ic_len = strlen(strioctl.ic_dp);

if (ioctl(fd, I_STR, &strioctl) < 0) { perror("ioctl I_STR failed"); exit(4); }

ioctl requests are issued to STREAMS drivers and modules indirectly, using the I_STR ioctl call (see streamio(7)). The argument to I_STR must be a pointer to a strioctl structure, which specifies the request to be made to a module or driver. This structure is defined in <stropts.h> and has the following format:

   struct strioctl {
   	int	ic_cmd;		/* ioctl request */
   	int	ic_timout;	/* ACK/NAK timeout */
   	int	ic_len;		/* length of data argument */
   	char	*ic_dp;		/* ptr to data argument */

where ic_cmd identifies the command intended for a module or driver, ic_timout specifies the number of seconds an I_STR request should wait for an acknowledgement before timing out, ic_len is the number of bytes of data to accompany the request, and ic_dp points to that data.

In the example, two separate commands are sent to the character conversion module. The first sets ic_cmd to the command XCASE and sends as data the string ``AEIOU''; it converts all upper case vowels in data passing through the module to lower case. The second sets ic_cmd to the command DELETE and sends as data the string ``xX''; it deletes all occurrences of the characters `x' and `X' from data passing through the module. For each command, the value of ic_timout is set to zero, which specifies the system default timeout value of 15 seconds. The ic_dp field points to the beginning of the data for each command; ic_len is set to the length of the data.

I_STR is intercepted by the Stream head, which packages it into a message, using information contained in the strioctl structure, and sends the message downstream. Any module that does not understand the command in ic_cmd passes the message further downstream. The request will be processed by the module or driver closest to the Stream head that understands the command specified by ic_cmd. The ioctl call will block up to ic_timout seconds, waiting for the target module or driver to respond with either a positive or negative acknowledgement message. If an acknowledgement is not received in ic_timout seconds, the ioctl call will fail.

NOTE: Only one I_STR request can be active on a Stream at one time. Further requests will block until the active I_STR request is acknowledged and the system call completes.

The strioctl structure is also used to retrieve the results, if any, of an I_STR request. If data is returned by the target module or driver, ic_dp must point to a buffer large enough to hold that data, and ic_len will be set on return to show the amount of data returned:

   	while ((count = read(fd, buf, BUFLEN)) > 0) {
   		if (write(fd, buf, count) != count) {
   			perror("write failed");

Note that the character conversion processing was realized with no change to the communications driver.

The exit system call dismantles the Stream before terminating the process. The character conversion module is removed from the Stream automatically when it is closed. Alternatively, modules may be removed from a Stream using the I_POP ioctl call described in streamio(7). This call removes the topmost module on the Stream, and enables a user process to alter the configuration of a Stream dynamically, by popping modules as needed.

A few of the important ioctl requests supported by STREAMS have been discussed. Several other requests are available to support operations such as determining if a given module exists on the Stream, or flushing the data on a Stream. These requests are described fully in streamio(7).

Previous topic: Closing the stream

© 2004 The SCO Group, Inc. All rights reserved.
UnixWare 7 Release 7.1.4 - 27 April 2004