UNIX Pipes

A pipe between two processes is similar to a tubular piece of plumbing. When a UNIX pipe is created, a data pipeline is formed between a writing process and a reading process. The UNIX pipe can become plugged if the reading process does not continue to receive the piped data. Unlike a physical pipe, however, some versions of UNIX insist that the data must flow in one direction: from its source to its destination.

In Chapter 2, "UNIX File System Objects," you read about FIFOs, which are also known as named pipes. This chapter, however, will be concerned with nameless pipes. Unlike FIFOs, nameless pipes are created in the open state and only exist between processes.

Creating UNIX Pipes

The system call that is responsible for creating nameless pipes is the function pipe(2). Its function synopsis is as follows:

#include <unistd.h>

int pipe(int fildes[2]);

The pipe(2) call returns one pair of file descriptors that represent both ends of the pipe. When the function is successful, the array fildes[] is populated with two open file descriptors, and the value 0 is returned. Otherwise -1 is returned, and an error code is left in the external variable errno.

Note

The close-on-exec flag is not set on the two file descriptors that are returned by pipe(2).


Systems that only support unidirectional pipes will provide fildes[0] as a file descriptor capable of reading only. The descriptor fildes[1] will be capable of writing only. Data written to fildes[1] can be read at the opposite end of the pipe with file descriptor fildes[0].

Systems that support STREAMS-based pipes allow reading and writing to both ends. Data written to fildes[0] is read via descriptor fildes[1]. Data written to fildes[1] is read via descriptor fildes[0]. In this respect, the STREAMS-based pipe is similar to a connected socket (the curious may read about socketpair(2)).

The following example shows how a pipe is created:

int z;                         /* General status code */
int fildes[2];                 /* Pair of file descriptors */

if ( (z = pipe(&fildes[0])) == -1 ) {
    perror("pipe(2)");         /* Report the error */
    exit(13);
)
printf("fildes[0] = %d, for reading
",fildes[0]);
printf("fildes[1] = %d, for writing
",fildes[1]);

This example shows how a pipe is created and how its file descriptors are reported (a unidirectional pipe is assumed in this example).

Note

The value st_size returned by fstat(2) is the number of bytes available for reading. For systems that support only unidirectional pipes, the same value st_size is returned for either file descriptor fildes[0] or fildes[1].

For STREAMS-based pipes, the st_size value returned by fstat(2) is the number of bytes available for reading at the specified end of the pipe. Descriptor fildes[0] or fildes[1] specifies which end of the pipe to query.


The creation of a pipe within one process may not appear to be useful. However, when you couple this functionality with the fork(2) system call, which is covered in Chapter 19, "Forked Processes," this becomes a powerful tool.

Because fork(2) is covered in the next chapter, this discussion will now turn to the popen(3) call. The pipe(2) function was introduced here because the popen(3) function calls upon it internally.

Note

FreeBSD release 3.4, UnixWare 7, and Solaris 8 support STREAMS-based pipes (bi-directional).

SGI IRIX 6.5 and HPUX 10.0 and later can be configured to use STREAMS-based (bi-directional) or unidirectional pipes. SGI also permits STREAMS-based pipe support to be chosen at program link time.

Only the unidirectional pipe is supported by Linux and IBM's AIX 4.3.


Opening Pipes to Other Processes

The C standard I/O library popen(3) makes it easy for the application programmer to open a pipe to an external process. It makes the necessary call to pipe(2) and then calls upon fork(2) to start a new process, which is attached to a pipe. The function synopsis for popen(3) is as follows:

#include <stdio.h>

FILE *popen(const char *command, const char *mode);

int pclose(FILE *stream);

The popen(3) function arguments are similar to the fopen(3) function except that the first argument is a command rather than a pathname. The argument command must be a command that is acceptable to the UNIX shell.

The second argument mode must be the C string "r" for reading or "w" for writing. No other combination, such as "w+", is acceptable. A popen(3) pipe must be opened for reading from a process or writing to a process, but never for both. When popen(3) is successful, a valid FILE pointer is returned. Otherwise, a null pointer is returned and the error is posted to errno.

Successfully opened pipes must be later closed by a call to pclose(3). The return value for pclose(3) is the termination status of the shell process.

Warning

Calling popen(3) from a set-user-ID program is dangerous. The popen(3) function uses fork(2) and exec(2) to invoke the new shell, and consequently it is possible for a security leak to occur (the current effective user and group ID values are saved by exec(2)). The shell and the commands invoked are subject to environment variable settings such as PATH and SHELL.


The C string given as argument command to popen(3) must be acceptable to the shell. This is because the popen(3) function invokes a shell process first. The entire pipe creation process can be described as follows:

  1. The popen(3) function creates a nameless pipe with a call to pipe(2).

  2. The popen(3) function calls functions fork(2) and execve(2) to start the shell.

  3. The shell interprets your command string that was provided in the call to popen(3).

  4. The shell starts your command if it is able to. If not, the shell returns an error to the popen(3) call.

The command process started by popen(3) is referred to as the child process of your current process. The current process that has called popen(3) is known as the parent process. This terminology helps to identify the process relationships involved.

Because the command string is passed to the shell, you have considerable flexibility in the features at your disposal. This includes the ability to use command lines that use wildcard filenames and shell input and output redirection operators. Additionally, you may use the pipe symbol to create additional pipes to other processes.

Warning

If you write programs that use the popen(3) function and that must be portable to other UNIX operating systems, keep in mind the limitations of the shell. Different shell programs are used on some UNIX platforms, with varying capabilities.


The current process environment is important to the shell that is invoked by the popen(3) call to start your command. This means that any commands that you expect it to invoke are subject to the usual PATH directory searches.

Reading from Pipes

The short program in Listing 18.1 shows a simple program that opens a pipe to the ps(1) command. After the pipe is opened, the program reads from the pipe until end-of-file is reached. All read data is displayed on standard output.

Code Listing 18.1. popen.c—Demonstration of popen(3) and Reading ps(1) Output
1:   /* popen.c */
2:
3:   #include <stdio.h>
4:   #include <stdlib.h>
5:
6:   int
7:   main(int argc,char **argv) {
8:       char buf[256];                      /* Input buffer */
9:       FILE *p;                            /* Input pipe */
10:
11:      /*
12:       * Open pipe to ps(1) command for reading :
13:       */
14:      p = popen("ps -l","r");
15:
16:      if ( !p ) {
17:          perror("popen(3)");
18:          return 13;
19:      }
20:
21:      /*
22:       * Read the output of the pipe:
23:       */
24:      while ( fgets(buf,sizeof buf,p) != 0 )
25:          fputs(buf,stdout);
26:
27:      if ( pclose(p) ) {
28:          perror("pclose(3)");
29:          return 13;
30:      }
31:
32:      return 0;
33:  }

The program begins by opening a read pipe to the command ps -l in line 14. Once the pipe has been opened successfully, the program reads each text line in the loop in lines 24 and 25, until end-of-file is reached. Then the pclose(3) function is called to properly close the pipe p (line 27).

The following FreeBSD compile and run session is provided as follows:

$ make popen
cc -c  -Wall popen.c
cc -o popen popen.o
$ ./popen
  UID   PID  PPID CPU PRI NI   VSZ  RSS WCHAN  STAT  TT       TIME COMMAND
 1001  7590     1   0  10  0   596  344 wait   Is    p0    0:00.05 -sh (sh)
 1001  7593  7592   1  10  0   596  344 wait   Ss    p1    0:00.22 -sh (sh)
 1001  7813  7593   1  -6  0   780  408 piperd S+    p1    0:00.01 ./popen
 1001  7814  7813   1  10  0   496  332 wait   S+    p1    0:00.00 sh -c ps -l
 1001  7815  7814   1  28  0   376  244 -      R+    p1    0:00.00 ps -l
$

The last three lines of output show the processes involved (the preceding ones are for the xterm(1) session that was being used). Process 7813 is the process used to execute the program ./popen. However, note how the popen(3) call has created two new processes:

  • Process 7814 is the shell that has been started to execute the command.

  • Process 7815 is the command process itself (the ps(1) command).

Although you cannot see the single quotes that were used, you can see how the popen(3) process created the command process using the shell process 7814. If you could see the single quotes, you would see:

sh -c 'ps -l'

This demonstrates the work that the popen(3) function has performed for you by calling upon pipe(2), fork(2), and execve(2). The functions fork(2) and execve(2) are discussed in Chapter 19.

Note

The command-line options for the ps(1) command differ for different UNIX platforms. The examples presented in this chapter assume FreeBSD release 3.4.


Writing to Pipes

When a pipe is being written to by the current process, another process at the other end of the pipe is reading that data from its standard input. To illustrate that procedure, look at the example program provided in Listing 18.2.

Code Listing 18.2. pmail.c—A Program That Writes to a popen(3) Pipe
1:   /* pmail.c */
2:
3:   #include <stdio.h>
4:   #include <stdlib.h>
5:   #include <unistd.h>
6:   #include <pwd.h>
7:   #include <sys/types.h>
8:
9:   int
10:  main(int argc,char **argv) {
11:      struct passwd *pw = 0;      /* Password info */
12:      char cmd[256];              /* Command buffer */
13:      FILE *p = 0;                /* mailx pipe */
14:
15:      /*
16:       * Lookup our userid:
17:       */
18:      if ( !(pw = getpwuid(geteuid())) ) {
19:          perror("getpwuid()");
20:          return 13;
21:      }
22:
23:      /*
24:       * Format command :
25:       */
26:      sprintf(cmd,"mail -s 'A message from process ID %ld'%s",
27:          (long)getpid(),         /* Process ID */
28:          pw->pw_name);           /* User name */
29:
30:      /*
31:       * Open a pipe to mailx:
32:       */
33:      if ( !(p = popen(cmd,"w")) ) {
34:          perror("popen(3)");
35:          return 13;
36:      }
37:
38:      /*
39:       * Now write our message:
40:       */
41:      fprintf(p,"This is command %s speaking.
",argv[0]);
42:      fprintf(p,"I am operating in the account for %s
",pw->pw_gecos);
43:
44:      if ( getuid() != 0 ) {
45:          fprintf(p,"I'd like to operate in root instead.
");
46:          fprintf(p,"I could do more damage there. :)

");
47:      } else {
48:          fprintf(p,"I'd like to operate in a non-root ID instead.
");
49:          fprintf(p,"I would be safer there.
");
50:      }
51:
52:      fprintf(p,"Sincerely,
  Process ID %ld
",(long)getpid());
53:
54:      if ( pclose(p) == -1 ) {
55:          perror("pclose(3)");
56:          return 13;
57:      }
58:
59:      printf("Message sent to %s
",pw->pw_name);
60:      return 0;
61:  }

This program looks up your user ID in lines 18–21. Then it forms a command to start an email to your current account in lines 26–28. A write pipe is created by calling popen(3) in line 33. The message text lines are written to lines 41–52. The message text is completed by sending end-of-file to the mail(1) command by calling pclose(3) in line 54.

Compiling and running this command under FreeBSD yields the following result:

$ make pmail
cc -c  -Wall pmail.c
cc -o pmail pmail.o
$ ./pmail
Message sent to ehg
$

Checking the mailbox yields results similar to this:

$ mail
Mail version 8.1 6/6/93.  Type ? for help.
"/var/mail/ehg": 1 message 1 new
>N  1 ehg      Mon Jun 19 23:20  20/588  "A message from process ID 7943"
& 1
Message 1:
From ehg Mon Jun 19 23:20:24 2000
Date: Mon, 19 Jun 2000 23:20:24 -0400 (EDT)
From: "Earl Grey" <ehg>
To: ehg
Subject: A message from process ID 7943

This is command ./pmail speaking.
I am operating in the account for Earl Grey
I'd like to operate in root instead.
I could do more damage there. :)

Sincerely,
  Process ID 7943

& d 1
& q
$

This demonstrated how your C program could write data to another external process through a pipe.

Closing a Pipe

After a pipe is opened for reading or writing, the pipe must be closed by a call to pclose(3). This allows a number of important concluding operations to take place:

  • The wait(2) function (or equivalent) must be called to pause the execution of the current process until the child process terminates.

  • Obtain success or failure information from wait(2) about the child process that has terminated.

  • Destroy the FILE control block.

The wait(2) call is necessary to obtain termination status about the child process. This is fully discussed in the next chapter.

Because popen(3) returns a pointer to FILE, there is a strong urge by programmers to close the open pipe with a call to fclose(3). However, close popen(3) pipes with pclose(3) only. On some platforms, using fclose(3) on a popen(3) FILE stream will cause the program to abort.

Warning

Always use function pclose(3) to close a pipe opened with popen(3). Failure to obey this rule will result in undetected process errors, possible memory leaks and, on some UNIX platforms, aborts.

Furthermore, this practice can result in zombie processes while your program continues to run. For more about zombie processes, see Chapter 19.


Handling a Broken Pipe

When a program has opened a pipe to another process for writing, and that other process has aborted, the read end of the pipe becomes closed. At that point, the pipe is half closed and there is no hope for it to be emptied of data—there is no process reading from it. This causes the UNIX kernel to raise the signal SIGPIPE in the process that is attempting to write to the pipe. This indicates to the writer that the pipe is broken.

The signal SIGPIPE is not always desirable for this purpose. You can elect to ignore the signal SIGPIPE and simply allow the write(2) function to return an error when this condition arises (error code EPIPE).

For example, you could alter Listing 18.2 as follows:

1:   /* pmail.c */
2:
3:   #include <stdio.h>
4:   #include <stdlib.h>
5:   #include <unistd.h>
6:   #include <pwd.h>
7:   #include <sys/types.h>
8:
9:   int
10:  main(int argc,char **argv) {
11:      struct passwd *pw = 0;      /* Password info */
12:      char cmd[256];              /* Command buffer */
13:      FILE *p = 0;                /* mailx pipe */
14:
15:      signal(SIGPIPE,SIG_IGN);    /* Ignore SIGPIPE */

Line 15 adds a call to signal(3) that requests the action SIG_IGN for signal SIGPIPE. The default action for signal SIGPIPE is to terminate the process. Consequently, you must be prepared for this signal in programs that work with pipes.

Note

EPIPEBroken pipe This error indicates that the calling process is not able to perform a write(2) (or equivalent) operation on a file descriptor because it is writing to a pipe with no reading processes.


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.89.24