Named Pipes
UNIX provides for a second type of pipe called a named pipe, or FIFO (we will use the terms interchangeably). Named pipes are similar in spirit to unnamed pipes but have additional benefits. When created, named pipes have a directory entry. With the directory entry are file access permissions and the capability for unrelated processes to use the pipe file. Although the FIFO has a directory entry, keep in mind the data written to the FIFO is passed to and stored by the kernel and is not directly written to the file system.
Named pipes can be created at the shell level (on the command line) or within a program. It is instructive to look at the generation of a named pipe at the shell level before addressing its use in a program. At the shell level the command used to make a named pipe is mknod . Officially, mknod is a utility command designed to generate special files. It is most commonly used by the superuser to generate special device files (e.g., the block, character device files found in the /dev directory). For nonprivileged users, mknod can only be used to generate a named pipe. The syntax for the mknod command to make a named pipe is
linux$ mknod PIPE p
The first argument to the mknod command is the file name for the FIFO (this can be any valid UNIX file name; however, it is common to use an uppercase file name to alert the user to the special nature of the file). The second argument is a lowercase p , which notifies mknod that a FIFO file is to be created. If we issue the command shown above and check the directory entry for the file that it has created, we will find a listing similar to that shown below:
linux$ ls -l PIPE prw-r--r-- 1 gray faculty 0 Feb 26 07:18 PIPE
The lowercase letter p at the start of the permission string indicates the file called PIPE is a FIFO. The default file permissions for a FIFO are assigned using the standard umask arrangement discussed previously. The number of bytes in the FIFO is listed as 0. As soon as all the processes that are using a named pipe are done with it, any remaining data in the pipe is released by the system and the byte count for the file reverts to 0. If we wish to, we can, on the command line, redirect the output from a shell command to a named pipe. If we do this, we should place the command sequence in the background to prevent it from hanging. We could then redirect the output of the same FIFO to be the input of another command.
For example, the command
linux$ cat test_file > PIPE & [1] 27742
will cause the display of the contents of file test_file to be redirected to the named pipe PIPE . If this command is followed by
linux$ cat < PIPE This is test file to use with our pipe. [1] + Done cat test_file > PIPE
the second cat command will read its input from the named pipe called PIPE and display its output to the screen.
While the previous discussion is instructive, it is of limited practical use. Under most circumstances, FIFOs are created in a programming environment, not on the command line. The system call to generate a FIFO in a program has the same name as the system command equivalent: mknod (Table 5.12).
Table 5.12. Summary of the mknod System Call.
Include File(s) |
|
Manual Section |
2 |
Summary |
int mknod(const char *pathname, mode_t mode, dev_t dev); |
||
Return |
Success |
Failure |
Sets errno |
-1 |
Yes |
The mknod system call creates the file referenced by pathname . The type of the file created (FIFO, character or block special, directory [3] or plain) and its access permissions are determined by the mode value. Most often the mode for the file is created by OR ing a symbolic constant indicating the file type with the file access permissions (see the section on umask for a more detailed discussion). Permissible file types are listed in Table 5.13.
[3] While most versions of mknod can also be used to generate a directory (if you are the superuser), the version found in Linux cannot (use the mkdir system call instead).
Table 5.13. File Type Specification Constants for mknod .
Symbolic Constant |
File Type |
---|---|
S_IFIFO |
FIFO special |
S_IFCHR |
character special |
S_IFDIR |
directory |
S_IFBLK |
block special |
S_IFREG |
ordinary file |
The dev argument for mknod is used only when a character or block special file is specified. For character and block special files, the dev argument is used to assign the major and minor number of the device. For nonprivileged users, the mknod system call can only be used to generate a FIFO. When generating a FIFO, the dev argument should be left as 0. If mknod is successful, it returns a value of 0. Otherwise, errno is set to indicate the error, and a value of 1 is returned.
Table 5.14. mknod Error Messages.
# |
Constant |
perror Message |
Explanation |
---|---|---|---|
1 |
EPERM |
Operation not permitted |
The effective ID of the calling process is not that of the superuser. |
4 |
EINTR |
Interrupted system call |
Signal was caught during the system call. |
12 |
ENOMEM |
Cannot allocate memory |
Insufficient kernel memory was available. |
13 |
EACCES |
Permission denied |
Parent directory (or one of the directories in pathname ) lacks write permission. |
14 |
EFAULT |
Bad address |
pathname references an illegal address. |
17 |
EEXIST |
File exists |
pathname already exists. |
20 |
ENOTDIR |
Not a directory |
Part of the specified pathname is not a directory. |
22 |
EINVAL |
Invalid argument |
Invalid dev specified. |
28 |
ENOSPC |
No space left on device |
File system has no inodes left for new file generation. |
30 |
EROFS |
Read-only file system |
Referenced file is (or would be) on a read-only file system. |
67 |
ENOLINK |
The link has been severed |
The pathname value references a remote system that is no longer active. |
72 |
EMULTIHOP |
Multihop attempted |
The pathname value requires multiple hops to remote systems, but file system does not allow it. |
36 |
ENAMETOOLONG |
File name too long |
The pathname value exceeds system path /file name length. |
40 |
ELOOP |
Too many levels of symbolic links |
The perror message says it all. |
In many versions of UNIX, a C library function called mkfifo simplifies the generation of a FIFO. The mkfifo library function (Table 5.15) uses the mknod system call to generate the FIFO. Most often, unlike mknod , mkfifo does not require the user have superuser privileges.
Table 5.15. Summary of the mkfifo Library Function.
Include File(s) |
|
Manual Section |
3 |
Summary |
int mkfifo (const char *pathname, mode_t mode) |
||
Return |
Success |
Failure |
Sets errno |
-1 |
Yes |
If mkfifo is used in place of mknod , the mode argument for mkfifo refers only to the file access permission for the FIFO, because the file type, by default, is set to S_IFIFO. If the mkfifo call fails, it returns a 1 and sets the value in errno . When generating a FIFO, the errors that may be encountered with mkfifo are similar to those previously listed for the mknod system call (Table 5.14). In our examples, we use the more universal mknod system call when generating a FIFO.
Our next example is somewhat more grand in scope than some of the past examples. We combine the use of unnamed and named pipes to produce a clientserver relationship. Both the client and server processes will run on the same platform. The single-server process is run first and placed in the background. Client processes, run subsequently, are in the foreground. The client processes accept a shell command from the user. The command is sent to the server via a public FIFO (known to all clients and the server) for processing. Once the command is received, the server executes it using the popenpclose sequence (which generates an unnamed pipe in the process). The server process returns the output of the command to the client over a private FIFO where the client, upon receipt, displays it to the screen. Figure 5.7 shows the process and pipe relationships.
Figure 5.7. Clientserver process relationships.
More succinctly, the steps taken by the processes involved are as follows :
- Server generates the public FIFO (available to all participating client processes).
- Client process generates its own private FIFO.
- Client prompts for, and receives, a shell command.
- Client writes the name of its private FIFO and the shell command to the public FIFO.
- Server reads the public FIFO and obtains the private FIFO name and the shell command.
- Server uses a popen pclose sequence to execute the shell command. The output of the shell command is sent back to the client via the private FIFO.
- Client displays the output of the command.
To ensure that both server and client processes will use the same public FIFO name and have the same message format, a local header file is used. This header file is shown in Figure 5.8.
Figure 5.8 Header file for clientserver example.
File : local.h /* local header file for pipe client-server */ #include + #include #include #include #include #include 10 #include #include using namespace std; const char *PUBLIC = "/tmp/PUBLIC"; <-- 1 const int B_SIZ = (PIPE_BUF / 2); + struct message { char fifo_name[B_SIZ]; char cmd_line[B_SIZ]; };
(1) Establish the name of the common public FIFO.
In this file, a constant is used to establish the name for the public FIFO as /tmp/PUBLIC . The format of the message that will be sent over the public FIFO is declared with the struct statement. The message structure consists of two character array members . The first member, called fifo_name , stores the name of the private FIFO. The second structure member, cmd_line , stores the command to be executed by the server.
Program 5.4 shows the code for the client process.
Program 5.4 The client process.
File : client.cxx /* The client process */ #define _GNU_SOURCE #include "local.h" + int main( ){ int n, privatefifo, publicfifo; static char buffer[PIPE_BUF]; struct message msg; 10 sprintf(msg.fifo_name, "/tmp/fifo%d", getpid( )); <-- 1 if (mknod(msg.fifo_name, S_IFIFO 0666, 0) < 0) { <-- 2 perror(msg.fifo_name); + return 1; } if ((publicfifo = open(PUBLIC, O_WRONLY)) == -1) { <-- 3 perror(PUBLIC); return 2; 20 } while ( 1 ) { write(fileno(stdout), " cmd>", 6); <-- 4 memset(msg.cmd_line, 0x0, B_SIZ); n = read(fileno(stdin), msg.cmd_line, B_SIZ); + if (!strncmp("quit", msg.cmd_line, n1)) break; write(publicfifo, (char *) &msg, sizeof(msg)); <-- 5 if ((privatefifo = open(msg.fifo_name, O_RDONLY)) == -1) { perror(msg.fifo_name); 30 return 3; } while ((n = read(privatefifo, buffer, PIPE_BUF)) > 0) { <-- 6 write(fileno(stderr), buffer, n); } + close(privatefifo); } close(publicfifo); unlink(msg.fifo_name); return 0; 40 }
(1) Build a unique name for the private FIFO for this process.
(2) Generate the private FIFO.
(3) Open the public FIFO for writing.
(4) Prompt for command; clear space to hold command.
(5) Write command to public pipe for server to process.
(6) Open private FIFO; read what is returned.
Using the sprintf function, the client creates a unique name for its private FIFO by incorporating the value returned by the getpid system call. The mknod system call is used next to create the private FIFO with read and write permissions for all. The following open statement opens the public FIFO for writing. If for some reason the public FIFO has not been previously generated by the server, the open will fail. In this case the perror call produces an error message and the client process exits. If the open is successful, the client process then enters an endless loop. The client first prompts the user for a command. [4] Prior to obtaining the command, the structure member where the command will be stored is set to all NULLs using the C library function memset . This action assures that no extraneous characters will be left at this storage location. Note that using memset is preferable to using the deprecated bzero library function for clearing a string. The read statement in line 24 obtains the user's input from standard input and stores it in msg.cmd_line . The input is checked to determine if the user would like to quit the program. The check is accomplished by comparing the input to the character string quit . We use n-1 as the number of characters for comparison to avoid including the found at the end of the user's input. If quit was entered, the while loop is exited via the break statement, the private FIFO is removed, and the client process terminates. If the user does not want to quit, the entire message structure, consisting of the private FIFO name and the command the user entered, is written to the public FIFO (thus sending the information on to the server). The client process then attempts to read its private FIFO to obtain the output that will be sent to it from the server. At this juncture, if the server has not finished with its execution of the client's command, the client process will block (which is the default for read ). Once data is available from the private FIFO, the while loop in the client will read and write its contents to standard error. The code for the server process is shown in Program 5.5.
[4] Notice that all the I/O in the program is done with read/write to avoid buffer flushing problems associated with standard I/O library calls.
Program 5.5 The server process.
File : server.cxx /* The server process */ #define _GNU_SOURCE #include "local.h" + int main( ){ int n, done, dummyfifo, publicfifo, privatefifo; struct message msg; FILE *fin; 10 static char buffer[PIPE_BUF]; mknod(PUBLIC, S_IFIFO 0666, 0); <-- 1 if ((publicfifo = open(PUBLIC, O_RDONLY)) == -1 + (dummyfifo = open(PUBLIC, O_WRONLY O_NDELAY)) == -1 ) { perror(PUBLIC); return 1; } 20 while (read(publicfifo, (char *) &msg, sizeof(msg)) > 0) { <-- 2 n = done = 0; do { if ((privatefifo=open(msg.fifo_name, <-- 3 O_WRONLYO_NDELAY)) == -1) + sleep(3); else { fin = popen(msg.cmd_line, "r"); <-- 4 write(privatefifo, " ", 1); while ((n = read(fileno(fin), buffer, PIPE_BUF)) > 0) { 30 write(privatefifo, buffer, n); <-- 5 memset(buffer, 0x0, PIPE_BUF); } pclose(fin); close(privatefifo); + done = 1; } } while (++n < 5 && !done); if (!done) { write(fileno(stderr), 40 " NOTE: SERVER ** NEVER ** accessed private FIFO ", 48); return 2; } } return 0; + }
(1) Generate public FIFO and open for reading and writing.
(2) Read message (command) from public FIFO.
(3) Open the child's private FIFO.
(4) Server executes the command using popen .
(5) Command output is read and sent to the child.
The server process is responsible for creating the public FIFO. Once created, the public FIFO is opened for both reading and writing. This may appear to be a little odd, as the server process only needs to read from the public FIFO. By opening the public FIFO for writing as well, the public FIFO always has at least one writing process associated with it. Therefore, the server process will never receive an end-of-file on the public FIFO. The server process will block on an empty public FIFO waiting for additional messages to be written. This technique saves us from having to close and reopen the public FIFO every time a client process finishes its activities.
Once the public FIFO is established, the server attempts to read a message from the public FIFO. When a message is read (consisting of a private FIFO name and a command to execute), the server tries to open the indicated private FIFO for writing. The attempt to open the private FIFO is done within a do-while loop. The O_NDELAY flag is used to keep the open from generating a deadlock situation. Should the client, for some reason, not open its end of the private FIFO for reading, the server would, without the O_NDELAY flag specification, block at the open of the private FIFO for writing. If the attempt to open the private FIFO fails, the server sleeps three seconds and tries again. After five unsuccessful attempts, the server displays an informational message to standard error and continues with its processing. If the private FIFO is successfully opened, a popen is used to execute the command that was passed in the message structure. The output of the command (which is obtained from the unnamed pipe) is written to the private FIFO using a while loop. When all of the output of the command has been written to the unnamed pipe, the unnamed pipe and private FIFO are closed. A sample run of the client-server programs is shown in Figure 5.9.
Figure 5.9 Typical client-server output.
linux$ server & <-- 1 [1] 27107 $ client <-- 2 cmd>ps PID TTY TIME CMD 14736 pts/3 00:00:00 csh 27107 pts/3 00:00:00 server 27108 pts/3 00:00:00 client 27109 pts/3 00:00:00 6 cmd>who gray pts/3 Feb 27 11:28 cmd>quit <-- 3 linux$ kill -9 27107 [1] Killed server $
(1) Place the server in the background.
(2) Run a client process in the foreground.
(3) The server process must be removed by sending it a kill signal.
The server process is placed in the background. The client process is then run, and shell commands ( ps and who ) are entered in response to the cmd> prompt. The output of each command (after it is executed by the server process and its output sent back to the client) is shown. The client process is terminated by entering the word quit . The server process, which remains in the background even after the client has been removed, is terminated by using the kill command.