Saturday, November 15, 2008

PIPE(LINUX)

"UNIX Network Programming" by W.Richard Stevens.

Create a directory [pipe] and bring [pipe.txt] in that directory
$ mkdir $HOME/pipe
$ cd pipe
$ ftp IP-address-of-laboratory-FTP-server

[pipes] can be used as IPC ( Inter Process Communication ) channel and
are provided by all Unix systems. They are half duplex, data flow only
in one direction. They can be used only between processes that have
common ancestor. ( APIUE by W.R.Stevens. Chapter-14. )

The system call [pipe] returns two file descriptors. Descriptor[0]
is open for reading and descriptor[1] is open for writing. The pipe
resides in kernel. Read manual of [pipe(2)].
$ man 2 pipe

-----------------------------------------------------------
// file-name f1.c

#include
#include


int main( void )
{
int x[2]; // to be used as file descriptors
int n;

// a pipe is tried to be created
n = pipe(x);
if( n == -1 ) { perror("pipe-call"); exit(1); }

printf("PID = %u\n", getpid() );

printf("descriptor of read end of pipe is x[0] = %u \n",
x[0] );
printf("descriptor of write end of pipe is x[1] = %u \n",
x[1] );

while(1) { sleep(20); } // endless loop

return 0;
} // end of main
-----------------------------------------------------------
Compile and run the executable in background
$ gcc -Wall ./f1.c -o ./one
$ ./one &
PID = ______

Check the file descriptors of the process
$ ls -l /proc/PID-of-one/fd

Were two file descriptors for the [pipe] created ? ( Y / n )

Bring the process in foreground and terminate with CTRL-C
$ fg
CRTL=C

The next program shows writing in a pipe and reading from a pipe.
-----------------------------------------------------------------
// file-name f2.c

#include
#include
#include
#include

#define BUFFERSIZE 4096

int main( void )
{
int x[2];
int n;
ssize_t i,j;
char buffer[BUFFERSIZE];

printf("PIPE_BUF is bytes in atomic write to a pipe\n");
printf("PIPE_BUF = %u\n", PIPE_BUF);

n = pipe( x );
if( n == -1 ) { perror("pipe-call"); exit(1); }

printf("descriptor of read end of pipe = %u\n", x[0] );
printf("descriptor of write end of pipe = %u\n", x[1] );

// file [/etc/services] is opened for reading
n = open("/etc/services", O_RDONLY);
if( n == -1 ) { perror("open"); exit(1); }
printf("descriptor of [/etc/services] file = %u\n", n );

// 4096 bytes are to be read from the file
i = read( n, buffer, 4096 );
if( i == -1 ) { perror("file-read"); exit(1); }

// As [/etc/services] file is no longer
// needed, it's descriptor is closed
close(n);
printf("%u bytes were read from descriptor %u\n", i, n );

/*
The following figure shows that data is written on write-end
of the pipe and data is read from the read-end of the pipe

___________________
| |
write --->---| x |--->--- read
x[1] |___________________| x[0]

*/

// write contents of the buffer to write end x[1] of pipe
j = write( x[1], buffer, i );
if( j == -1 ) { perror("pipe-write"); exit(1); }
printf("%u bytes were written to descriptor %u\n", j, x[1] );

// buffer is cleared
memset(buffer, '\0', BUFFERSIZE);

// data is read from the read end x[0] of pipe
i = read( x[0], buffer, BUFFERSIZE );
if( i == -1 ) { perror("pipe-read"); exit(1); }

printf("%u bytes were read from descriptor %u\n", i, x[0] );

// the buffer may be printed on standard output
/*
j = write( STDOUT_FILENO, buffer, i );
if( j == -1 ) { perror("write"); exit(1); }
*/

// the two ends of pipe are closed
close( x[0] );
close( x[1] );

return 0;
} // end of main
-----------------------------------------------------------

Compile, run and record result
$ gcc -Wall ./f2.c -o ./two
$ ./two

PIPE_BUF is bytes in atomic write to a pipe
PIPE_BUF = _______
descriptor of read end of pipe = __
descriptor of write end of pipe = __
descriptor of [/etc/services] file =__
_____ bytes were read from descriptor __
_____ bytes were written to descriptor __
_____ bytes were read from descriptor __


Normally the process that calls [pipe], then calls [fork], creating a
pipe from parent to child or vice verse. Communication between parent
and child is possible using pipe/pipes.

Example:
Using pipe try to communicate between two processes.
(WBUT2003-oslab)

Write a C code to implement how a process communicates with
other processes using the concept of piping.
(WBUT2003-oslab)

Write a program to communicate between two process using PIPE.
(WBUT2004-oslab)

Write a code in C to implement how a process communicates with
other processes using the concept of piping.
(WBUT2004-oslab)

The following program illustrates child to parent communication.
------------------------------------------------------------------
// file-name f3.c
// child to parent communication
// child is writer, parent is reader

#include
#include
#include
#include
#include

#define BUFFERSIZE 4096

int main (void)
{
char buffer[BUFFERSIZE];
int p[2],n;
ssize_t i,j;
pid_t pid;


// main creating a pipe
n = pipe(p);
if( n == -1 ) { perror("pipe"); exit(1); }

printf("read end of pipe = %u\n", p[0] );
printf("write end of pipe = %u\n", p[1] );

// main creating a new process
pid = fork();
if( pid == -1 ){ perror("fork"); exit(1); };

if( pid == 0 ) // child process; the writer
{
/*
The child inherits the following( among other things ),
from the parent

p[0] read-end of pipe; may be closed
p[1] write-end of pipe; to be used for writing
STDOUT_FILENO may be closed; but used for debugging in
this example
STDIN_FILENO may be closed
STDERR_FILENO may be closed; but needed for error output
*/
close(p[0]);
close(STDIN_FILENO);

// child writes a string in pipe. Enter your-name
// and modify 3rd argument suitably
j = write( p[1], "I am your-name\n", 15 );
if( j == -1 ) { perror("c-write"); exit(1); }
close( p[1] );
printf("child: %u bytes were written in fd %u\n", j, p[1] );
exit(0);
}

// rest is parent process. As parent is the reader, the write
// end of the pipe is useless to parent. So the write-end of
// the pipe is closed by the reader
close(p[1]);

// parent waits for the child to exit
wait(NULL);

// buffer is cleared before reading
memset( buffer, '\0', BUFFERSIZE );

// parent reads the pipe
i = read(p[0],buffer,BUFFERSIZE );
if( i == -1 ) { perror("p-read"); exit(1); }

close( p[0] );

printf( "parent: %u bytes were read from fd %u\n", i,p[0] );

j = write(STDOUT_FILENO,"parent: received from pipe-->",29);
if( j == -1 ) { perror("p-write-1"); exit(1); }

// parent writing contents of buffer in standard output
j = write ( STDOUT_FILENO, buffer, i );
if( j == -1 ) { perror("p-write-2"); exit(1); }

printf("parent: written %u bytes on screen\n",j);

return 0;
}
------------------------------------------------------------------
Discussion of the above program:
A simple one way communication from
child to parent is implemented. The main program ( parent ) creates a
pipe and gets two file descriptors, for the two ends of the pipe. Now
the parent would have five file descriptors. Then the main creates a
new process using [ fork() ] system call. The child process inherits
these five file descriptors. The read end of the pipe is useless to
the child as the writer; so p[0] is closed by the child. child writes
a string in the write end of pipe. The parent reads the reply from
read-end of the pipe and prints in standard output.

The following figure shows that child to parent communication in the
program [f3.c]

__________________
child | | parent
write --->---| p |--->--- read
p[1] |__________________ | p[0]


Compile and run the program
$ gcc -Wall ./f3.c -o ./three
$ ./three

Record the output

read end of pipe = __
write end of pipe = __
child: ___ bytes were written in fd __
parent: ___ bytes were read from fd __
parent: received from pipe-->I am ___________
parent: written ___ bytes on screen


The following example shows both-way ( duplex ) communication between
parent and child, using two pipes. This is a so called "open" sever.
The client reads a file name from keyboard and writes the file name to
write-end of one pipe. In the following example, the filename is
written by the client without the newline. The server reads the file
name from the read-end of the above pipe. The server tries to open the
file for reading. If [ open ] fails, an error message is written to
write-end of a second pipe. If [open] is successful, server reads the
contents of the file and writes it to write-end of the second pipe.
The client reads the read-end of the second pipe and prints it on the
standard output.
--------------------------------------------------------------------
// file-name f4a.c

#include
#include
#include
#include
#include
#include

#define BUFFERSIZE 4098
//#define DEBUG

int main(void)
{
int A[2], B[2];
pid_t pid;
int fd, n;
ssize_t i,j;
char buffer[BUFFERSIZE];
char filename[255];

// main creates A pipe
n = pipe(A);
if( n == -1 ) { perror("pipe-A"); exit(1); }

// main creates B pipe
n = pipe(B);
if( n == -1 ) { perror("pipe-B"); exit(1); }

// main creates a new process
pid = fork();
if( pid == -1 ) { perror("fork"); exit(1); }

if( pid != 0 ) // parent process block, the client
{
#ifdef DEBUG
write( STDOUT_FILENO, "client: starting\n", 17 );
printf("client: pipe descriptors are %u %u %u %u\n",
A[0], A[1], B[0], B[1] );
#endif
// client closes the non-essential pipe descriptors
close( A[0] ); // parent closes read end of pipe-A
close( B[1] ); // parent closes write end of pipe-B

/*
The client (parent) reads filename from keyboard and writes
the filename, in write end of A pipe. The client reads the
reply from server from the read end of B pipe. The reply is
written on standard output by the client.
____________ ____________
| | | |
| |--> filename -->--| A |
| | A[1] |____________ |
| |
Keyboard-->filename--> | CLIENT | _____________
| | | |
| |--<- reply<------- | B |
CRT screen ---<------ | | B[0] |_____________|
|____________|
*/

// client clears buffer
memset( buffer, '\0', BUFFERSIZE );
// client reads filename from keyboard
write( STDOUT_FILENO,"client: Filename please ? ", 26);
i = read( STDIN_FILENO, buffer, BUFFERSIZE );
if( i == -1 ) { perror("client-read-keyboard"); exit(1); }

#ifdef DEBUG
printf("client: received %u bytes from %u\n", i,STDIN_FILENO );
printf("client: file-name = <%s>", buffer );
// examine the output for newline character in file-name.
// the newline is to be stripped from file-name
#endif

// filename has (i-1) bytes. client writes the filename on the
// write-end of A pipe, without the newline
j = write( A[1], buffer, (i-1) );
if( j == -1 ) { perror("client-write-pipe"); exit(1); }

#ifdef DEBUG
printf( "client: written %u bytes on A[1] \n",j );
#endif

memset( buffer, '\0', BUFFERSIZE);
//client reads reply from server from read-end of pipe-B
i = read( B[0], buffer, BUFFERSIZE );
if( i == -1 ) { perror("client-read-pipe"); exit(1); }

#ifdef DEBUG
printf("client: read %u bytes from B[0] \n", i );
#endif

write(STDOUT_FILENO, "client: server's reply-->", 25);
j = write( STDOUT_FILENO, buffer, i );
if( j == -1 ) { perror("client-write-screen"); exit(1); }

#ifdef DEBUG
printf("client: written %u bytes on STDOUT_FILENO \n", i );
write(STDOUT_FILENO,"client: exiting\n", 16 );
#endif

wait(NULL) ;
exit(0);
} // end of parent process block


if( pid == 0 ) // child process block, the server
{
// child inherits seven descriptors.
// uncomment the following line to verify
// while(1) { sleep(1); }

// server closes non-essential descriptors
close( A[1] ); //child closes write end of A
close( B[0] ); //child closes read end of B

/*

server reads the filename from read end of A pipe and tries
to open the file. If server fails to open the requested file,
a suitable error message is written in write end of B pipe.
If the file is opened successfully, the contents of the file
are written in write end of B pipe.

_____________________ __________
| | | |
| A |--->---file-name ----->--| |
|_____________________ | A[0] | |->---- file
| | |
_____________________ | SERVER |--<-----|
| | file contents | |
| B |-<- or -<-- | |
|_____________________ | B[1] error message |__________ |


*/

// server reads filename from read end of A pipe
i = read( A[0], buffer, BUFFERSIZE );
if( i == -1 ) { perror("server-read-pipe"); exit(1); }

strncpy( filename, buffer, i );

#ifdef DEBUG
printf("server: read %u bytes from A[0]\n", i );
printf("server: filename read from A[0] = <%s> \n", buffer);
#endif

// server tries to open the file for reading
fd = open( filename, O_RDONLY );
if( fd == -1 ) // server fails to open file
{
perror("server-open"); // note absence of exit() call
memset( buffer, '\0', BUFFERSIZE );
// buffer is prepared with error message
i = sprintf( buffer,"file [%s] could not be opened\n",
filename );
// error message is written in B pipe
j = write( B[1], buffer, i );
if( j == -1 ) { perror("server-write-error"); exit(1); }
#ifdef DEBUG
printf( "server: written %u byte err-mes. on B[1] \n",
j );
#endif
}
else //file could be opened.
{
#ifdef DEBUG
printf("server: fd of <%s> = %u \n", filename,fd );
#endif
// read contents of file
i = read( fd, buffer, BUFFERSIZE );
if( i == -1 ) { perror("server-read-file"); exit(1); }
close(fd);

#ifdef DEBUG
printf("server: read %u bytes from descriptor %u\n",i, fd);
#endif

j = write( B[1], buffer, i );
if( j == -1 ) { perror("server-write-pipe"); exit(1); }

#ifdef DEBUG
printf("server: written %u bytes on B[1]\n",j);
#endif
} //else block ends.

close( A[0] );
close( B[1] );
#ifdef DEBUG
write( STDOUT_FILENO, "server: exiting\n", 16 );
#endif
exit(0);
} // end of child process block

return 0;
} // end of main
--------------------------------------------------------------------

Compile
$ gcc -Wall ./f4a.c -o ./foura

Test the program with....

A small file, such as /etc/papersize
A larger file, such as /etc/passwd
A non-existent file, such as /etc/bogus

You may use [ strace ] on the program to verify that SIGCHLD signal is
delivered to the parent when child exists.

Define DEBUG, compile and run with a small file such as /etc/papersize

The next program shows a simple two way communication between child
and parent.
------------------------------------------------------
// file-name f4b.c
// both way communication between child and
// parent using two pipes

#include
#include
#include
#include

#define BUFFERSIZE 4096

int main ( void )
{

int x[2];
int y[2];
int n;
pid_t pid;
ssize_t i;
char buffer[BUFFERSIZE];

n = pipe(x);
if( n == -1 ) { perror("pipe-x"); exit(1); }

n = pipe(y);
if( n == -1 ) { perror("pipe-y"); exit(1); }

pid = fork();
if( pid == -1 ) { perror("fork"); exit(1); }

/*

0 ------------ 1
<-----------| pipe x |<----------
------------
CHILD PARENT
------------
----------->| pipe y |----->
1 ----------- 0

*/

if( pid == 0 ) // child process
{
// child closing useless descriptors
close( x[1] ); close( y[0] );

i = read( x[0], buffer, BUFFERSIZE );
if( i == -1 ) { perror("read-child"); exit(1); }
printf("child: read %u bytes from pipe-x\n", i);
write( STDOUT_FILENO,"child: received->", 17 );
write( STDOUT_FILENO, buffer, i );

n = write( y[1],"Hello! this is child\n", 22 );
if( n == -1 ) { perror("write-child"); exit(1); }
close(x[0]); close(y[1]);
exit(0);
}

// rest is parent process
// parent closing useless descriptors
close(x[0]); close(y[1]);

i = write( x[1],"Hi! I am parent\n", 16 );
if( i == -1 ) { perror("write-parent"); exit(1); }

i = read( y[0], buffer, BUFFERSIZE );
if( i == -1 ) { perror("read-parent"); exit(1); }
printf("parent: read %u bytes from pipe-y\n", i );
write( STDOUT_FILENO,"parent: received->", 18 );
write( STDOUT_FILENO, buffer, i );

close(x[1]); close(y[0]);

wait(NULL);

return 0;
} //end of main
------------------------------------------------------
Compile and run the program
$ gcc -Wall ./f4b.c -o ./fourb
$ ./fourb


popen() and pclose() functions:

popen() library function creates a pipe and starts a new process. The
new process either reads from or writes to the pipe but not both. You
may read the manual of the function.
$ man 3 popen

The synopsis of the call is

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

The first argument is a shell command line. The second argument must
be either `r' for reading or `w' for writing. A pipe is created
between the calling process and the [command]. If the second argument
[ type ] is `r', the calling process reads the standard output of the
[ command ]. If it is 'w', the calling process writes to the standard
input of the [command].


In the following program [cat] shell command is used from C program.
------------------------------------------------------
// file-name p1.c

#include
#include
#include

#define BUFFERSIZE 512

int main(int argc, char *argv[] )
{
char buffer[BUFFERSIZE];
char command[100];
ssize_t i;
FILE *fp;

// check number of arguments
if( argc != 2 )
{
printf("usage: %s file-name \n", argv[0] );
exit(1);
}

memset( command, '\0', 100 );
strcpy( command, "/bin/cat " );
strcat( command, argv[1] );

// uncomment for debugging
//printf( "command = <%s>\n", command );

fp = popen( command, "r" );
if( fp == NULL ) { perror("popen"); exit(1); }

while( fgets( buffer, BUFFERSIZE, fp) != NULL )
{
i = write(STDOUT_FILENO, buffer, strlen(buffer) );
if(i == -1 ) { perror("write"); exit(1); }
}

pclose(fp);

return 0;
}
------------------------------------------------------
Compile the program
$ gcc -Wall ./p1.c -o ./cat-command

Run the program with different files such as /etc/bogus, /etc/passwd,
/etc/hosts, /etc/services, ./pipe.txt etc etc.


The following program obtains the current working directory, using
[ pwd ] shell command.
( From UNIX Network Programming by W.Richard Stevens )
------------------------------------------------
// file-name p2.c

#include

#define BUFFERSIZE 512

int main(void)
{
char buffer[BUFFERSIZE];
FILE *fp;


fp = popen( "/bin/pwd", "r" );
if( fp == NULL ) { perror("popen"); exit(1); }

if ( fgets( buffer, BUFFERSIZE, fp) == NULL )
{ perror("fgets"); exit(1); }

printf("%s", buffer );

pclose(fp);

return 0;
}
------------------------------------------------
Compile and run
$ gcc -Wall ./p2.c -o ./pwd-command
$ ./pwd-command

output-->


Optional Assignments-1.
Write a program to implement IPC using a single
pipe. The child should be the reader and parent the writer.

Optional Assignments-2.
A property of pipe is illustrated in [ f5.c ].
When a process reads an empty pipe and no process has the pipe open
for writing, [ read(2) ] call returns zero and it is not an error.
-------------------------------------------------------
// file-name f5.c

#include
#include

#define BUFFERSIZE 4096

int extern errno;

int main( void )
{
int xx[2];
int n;
ssize_t k;
char buffer[BUFFERSIZE];

n = pipe( xx );
if( n == -1 ) { perror("pipe-call"); exit(1); }

// closing write-end of pipe
n = close( xx[1]);
if( n == -1 ) { perror("close-1"); exit(1); }
// the [pipe] has no writer now

errno = 0; // errno is set to zero before [read] call

printf("before read() call: errno = %u\n", errno );

// reading an empty pipe which has no writer
k = read( xx[0], buffer, BUFFERSIZE );
if( k == -1 )
{
perror("read");
printf("errno = %u \n", errno );
exit(1);
}

printf("after read() call: errno = %u\n", errno );
printf("Read %u bytes from pipe \n", k );

n = close( xx[0] );
if( n == -1 ) { perror("close-2"); exit(1); }

return 0;
} // end of main
-------------------------------------------------------
Compile and run
$ gcc -Wall ./f5.c -o ./five

Was there an error in [read()] call ? ( y / N )

Optional Assignments-3.
When a process reads an empty pipe and at least
one process has it open for writing, the [ read ] call blocks, waiting
for data. The above is true if O_NONBLOCK is not used by [fcntl] call
on the descriptor of pipe. This is another property of pipe.
-------------------------------------------------
// file-name f6.c

#include
#include

#define BUFFERSIZE 4096

int extern errno;

int main( void )
{
int xx[2];
int n;
ssize_t k;
char buffer[BUFFERSIZE];


n = pipe( xx );
if( n == -1 ) { perror("pipe-call"); exit(1); }

printf("Read-end of the pipe = %u\n", xx[0] );

// reading an empty pipe which has a writer
k = read( xx[0], buffer, BUFFERSIZE );
if( k == -1 ) { perror("read"); exit(1); }

close( xx[0] );
close( xx[1] );

return 0;
} // end of main
-------------------------------------------------
Compile and run the executable
$ gcc -Wall ./f6.c -o ./six
$ ./six

Read-end of the pipe = ____

Terminate process with CTRL-C

Run [strace] on the program
$ strace ./six

Did the call block while reading read-end of the pipe? ( Y / n )

Terminate with CTRL-C

Optional Assignments-4.
If a process writes to a [ pipe ], but there is
no process which has this [ pipe ] open for reading, SIGPIPE signal is
delivered to the calling process. The [ write ] call returns -1, with
[ errno ] set to EPIPE. This is another property of pipe.
-----------------------------------------------------------
// file-name f7.c

#include
#include
#include
#include
#include

// function prototype
void sighandler( int signum );

int extern errno;

int main( void )
{
int xx[2];
int n;
ssize_t k;
struct sigaction action;

printf("numerical value of EPIPE = %u \n", EPIPE );

// a signal handler for SIGPIPE is installed
action.sa_handler = sighandler;
n = sigaction( SIGPIPE, &action, NULL );
if( n == -1 ) { perror("sigaction"); exit(1); }

n = pipe( xx );
if( n == -1 ) { perror("pipe-call"); exit(1); }

// read-end of the pipe is closed
n = close( xx[0] );
if( n == -1 ) { perror("close-1"); exit(1); }
// Now the process has no reader

printf("write end of pipe = %u\n", xx[1] );

// errno is set to zero
errno = 0;
printf("before write() call: errno = %u\n", errno );

// writing to a pipe which has no reader
k = write( xx[1], "Hello World\n", 12 );
if( k == -1 )
{
printf("write call failed: errno = %u \n", errno );
exit(1);
}

printf("after write() call: errno = %u\n", errno );

return 0;
} // end of main


void sighandler( int signum )
{
printf("signal handler: received signal %u \n", signum );
return;
}
-----------------------------------------------------------
Compile, run and record output
$ gcc -Wall ./f7.c -o ./seven
$ ./seven

numerical value of EPIPE = ___
write end of pipe = __
before write() call: errno = __
signal handler: received signal _____
write call failed: errno = ___

Read manual of [signal(7)] and record
Name of the signal _____________

Run the executable with [strace]
$ strace ./seven

Examine the output. What was the symbolic constant of the error, when
writing in the pipe ( which had no reader ) was tried ? ______________