Closing a pipe does not send EOF to other end - c

I would like to run an external command from a C program. Let's say, as minimal working example, that I want to run the 'cat' command. I use use fork() and execl() to spawn the new process, and I communicate with it via pipes.
Now that's where my problem is. In a terminal I would tell 'cat' that I am done with my input by pressing CTRL-D. Here I am trying to do so by closing the file descriptor -- see the line with close(outpipefd[1]) in the code below -- but this does not seem to work. My code stalls as 'cat' is waiting for more input.
My code is as follows... What am I doing wrong? Thanks in advance!
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
int main(void)
{
pid_t pid=0;
int inpipefd[2];
int outpipefd[2];
/*
We create the pipes for communicating with the child process
*/
pipe(inpipefd);
pipe(outpipefd);
if((pid=fork())==0)
{
/*
Child
*/
dup2(outpipefd[0],STDIN_FILENO);
dup2(inpipefd[1],STDOUT_FILENO);
dup2(inpipefd[1],STDERR_FILENO);
/*
We spawn the process
*/
execl("/bin/cat","cat",(char *)(NULL));
/*
Nothing below this line should be executed by child process.
If so, it means that the execl function wasn't successfull, so lets exit!
*/
exit(1);
}
/*
Parent.
Close unused pipe ends.
*/
close(outpipefd[0]);
close(inpipefd[1]);
/*
Now we can write to outpipefd[1] and read from inpipefd[0]
*/
char *greeting="Hello world!\n";
write(outpipefd[1],greeting,strlen(greeting));
/*
Here I believe that closing the pipe should be equivalent to
pressing CTRL-D in a terminal, therefore terminating the cat command...
This is unfortunately not the case!
*/
close(outpipefd[1]);
while(1)
{
char buf[256];
for(int c=0;c<256;c++)
buf[c]=0;
if(read(inpipefd[0], buf, 256)<=0)
break;
printf("OUTPUT: %s\n", buf);
}
/*
Send SIGKILL signal to the child process
*/
int status;
kill(pid, SIGKILL);
waitpid(pid, &status, 0);
return 0;
}

The child still has both ends of both pipes opened, because you never closed any of your FDs in it. Until every FD referring to the write end of a pipe is closed, it won't return EOF.

You have also to close the unused pipe ends in the child, or there will be still things open that block the other end. close what you don't use in parent and child, and you will get the EOFs.

Related

C Linux programming - Pipe makes child process exit

I am having a hard time understanding the behavior of the following code. Closing the file descriptor p[0] makes the program exit (since otherwise the parent process just waits on the child processes in all eternity). It doesn't make sense to me because the child processes are running an infinite while loop, why would they just exit because the read end of the pipe is closed? Sure, you won't be able to write to the pipe but the while loop is not dependent on whether the read end of the parent process is open or not. I tried removing the exit() functions in the child processes and the program exits anyways, so why is the child process killing itself as soon as it notices that the read end is closed?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
main()
{
int run=10, fd[2]; pipe(fd); srand(time(0)); char ch, x='x', o='o';
if(!fork())
{
close(fd[0]);
while(run)
{
sleep(1+rand()%6); write(fd[1],&x,1);
}
exit(0); //This exit doesn't happen
}
if(!fork())
{
close(fd[0]);
while(run)
{
sleep(1+rand()%3); write(fd[1],&o,1);
}
exit(0); //This exit doesn't happen
}
close(fd[1]);
while(run--)
{
read(fd[0],&ch,1);
printf("%d %c\n",run,ch);
sleep(1);
}
close(fd[0]); //closing this file descriptor results in that the program can exit
wait(0);
wait(0);
exit(0);
}
That's standard behavior. You close the read-end of the pipe, so there is nowhere to write. That results in a SIGPIPE signal being sent to the writing process.
The default behavior of SIGPIPE is to terminate the process receiving the signal.
If you want your process to survive the signal, you have to catch or ignore it. Then you must check the result of write as it will return with an error when the pipes read-end is closed.
Your problem is not with pipe. Two header files missing in your codes and for which it is crashing when calling wait() function. Add below two header files in your program and it will fix your problem. Also your declaration of main function is old c type declaration make it like void main().
<sys/types.h>
<sys/wait.h>

Feeding stdout to a child process which will execv() sort

I am trying to find out how I can send output of one process into a child process. I have gone down a journey learning of file descriptors and pipes. I think I am almost there but am missing a key component.
This is what I have so far:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int fd[2];
pid_t sort_pid;
/* Create the pipe */
if(pipe(fd) == -1) {
fprintf(stderr, "Pipe failed\n");
exit(EXIT_FAILURE);
}
/* create child process that will sort */
sort_pid = fork();
if(sort_pid < 0) { // failed to fork
fprintf(stderr, "Child Fork failed\n");
exit(EXIT_FAILURE);
}
else if(sort_pid == 0) { // child process
close(0); // close stdin
dup2(fd[0], 0); // make stdin same as fd[0]
close(fd[1]); // don't need this end of the pipe
execlp("D:/Cygwin/bin/sort", "sort", NULL);
}
else { // parent process
close(1); // close stdout
dup2(fd[1], 1); // make stdout same as fd[1]
close(fd[0]); // don't need this end of the pipe
printf("Hello\n");
printf("Bye\n");
printf("Hi\n");
printf("G'day\n");
printf("It Works!\n");
wait(NULL);
}
return EXIT_SUCCESS;
}
This doesn't work, as it seems to go into an endless loop or something. I tried combinations of the wait() but that doesnt help either.
I am doing this to learn how to apply this idea in my actual program. In my actual program I read files, parse them line by line and save the processed data to a static array of structs. I want to be able to then generate output based on these results and use the fork() and execv() syscalls to sort the output.
This is ultimately for a project in uni.
These are similar examples which I dissected to get to the stage I am at so far:
pipe() and fork() in c
How to call UNIX sort command on data in pipe
Using dup,pipe,fifo to communicate with the child process
Furthermore I read the manual pages on the relevant syscalls to try and understand them. I will admit my knowledge of pipes and using them is still basically nothing, as this is my first every try with them.
Any help is appreciated, even further sources of information I could look into myself. I seem to have exhausted most of the useful stuff a google search give me.
sort will read until it encounters end-of-file. You therefore have to close the write-end of the pipe if you want it to complete. Because of the dup2, you have two copies of the open file description, so you need
close(fd[1]); anytime after the call to dup2
close(1); after you're done writing to (the new) stdout
Make sure to fflush(stdout) before the second of these to ensure that all your data actually made it into the pipe.
(This is a simple example of a deadlock: sort is waiting on the pipe to close, which will happen when the parent exits. But the parent won't exit until it finishes waiting on the child to exit…)

C reading all data from pipe

My program creates child process and sets pipes to communicate with it. The problem occurs when i try to read data from the pipe. Since child process has ended (i use wait to ensure that) EOF should be on the end of the data stream thus ending the read (As in the man page for pipe). But instead read just freezes and waits for more data to come.
What am i missing here?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
void setfd(int *in, int *out) {
dup2(out[1], 1);
dup2(in[0], 0);
}
int main(int argc, char *argv[]) {
int status;
int pipe2ch[2], pipe2pr[2];
char *newargv[] = {NULL, NULL};
newargv[0] = argv[1];
pipe(pipe2ch);
pipe(pipe2pr);
setfd(pipe2pr, pipe2ch);
int a;
if (!(a = fork())) {
setfd(pipe2ch, pipe2pr);
execve(newargv[0], newargv, NULL);
exit(1);
} else {
printf("hello!\n");
fflush(stdout);
char str;
wait(&status);
while (read(pipe2pr[0], &str, 1) > 0) {
fprintf(stderr, "%c", str);
}
exit(0);
}
}
Since child process has ended (i use wait to ensure that) EOF should be on the end of the data stream thus ending the read (As in the man page for pipe).
I'm not sure what you've read to suggest that. Or maybe it's your wording that I don't understand. EOF is not a character on the stream.
But instead read just freezes and waits for more data to come. What am i missing here?
Several things. The most important one is probably that when a process forks, the child's copies of the parent's open file descriptors refer to the same entries in the kernel's underlying table of open files as the parent's do, each of which remains open until all handles on it are closed. The child closes all its file descriptors when it exits, but both ends of both pipes remain open in the parent, so end-of-file will not be signaled to readers. Each process must close the pipe ends it doesn't use or is finished using.
Additionally, you should read() first, then wait(), for if the child process writes enough data to the pipe then it may block, and if the parent does not read until after the child exits then you'll have a deadlock.
Furthermore, I don't see any reason to dupe either pipe end onto the parent's standard streams (resulting in closing the original ones). Just manipulate the pipes via their file descriptors, as you already half do. If you want a stream interface to those, then use fdopen() to get one.

regarding the relation of close() and read()

int main()
{
char *msg="hello";
char buff[MAX];
int p[2];
pipe(p);
int i,pid=fork();
if(pid>0){
//close(p[1]);
read(p[0],buff, MAX);
}
else
{
printf("child exiting\n");
}
}
Why does the above code end up blocking ? But then if we remove the comment and place
close(p[1])
then why does the code end immediately ?
Once you create a pipe, it gets four ends:
A reading end p[0] in the parent process
A writing end p[1] in the parent process
A reading end p[0] in the child process
A writing end p[1] in the child process
UNIX will not deliver EOF to the reader unless both writing ends have been closed, because it knows that the pipe is still writeable.
When the child process exits, it closes both ends of the pipe on its side. However, the parent still has one writeable end open, so reading from the pipe blocks instead of delivering an EOF to the parent. That is why UNIX manual instructs to close the unused ends of the pipe right away:
An application that uses pipe(2) and fork(2) should use suitable close(2) calls to close unnecessary duplicate file descriptors; this ensures that end-of-file and SIGPIPE/EPIPE are delivered when appropriate.
Here is an example of how to make your program not block without closing p[1] on the parent side:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void* write_pipe(void* pp) {
int* p = (int*)pp;
char msg[] = "Hello from another thread!";
write(p[1], msg, sizeof(msg));
return NULL;
}
int main()
{
char buff[100];
int p[2];
pipe(p);
int pid=fork();
if(pid>0){
pthread_t thread1;
pthread_create (&thread1, NULL, &write_pipe, (void *)p);
read(p[0],buff, 100);
printf("%s\n", buff);
printf("parent exiting\n");
}
else
{
printf("child exiting\n");
}
return 0;
}
The code above writes to the writing end of the pipe from a thread within the parent process, instead of writing to it from the child process. This is a legitimate use of a pipe, too, illustrating why UNIX cannot deliver EOF unless the parent's writing end of the pipe is closed.
Read is a blocking call and it returns only when it receives EOF . If you wont close the write end of the pipe, read end wont get the EOF and hence,program will remain blocked

Declaring char array causes execv() to not work

I wrote the following code in order to use pipes in c unix:
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
int main ()
{
int fds[2];
pid_t pid;
/* Create a pipe. File descriptors for the two ends of the pipe are
placed in fds. */
pipe (fds);
/* Fork a child process. */
pid = fork ();
if (pid == (pid_t) 0) {
//char abc[10]; - **Uncommenting this cause the program not to work.**
/* This is the child process. Close our copy of the write end of
the file descriptor. */
close (fds[1]);
// Read params
FILE * stream;
stream = fdopen (fds[0], "r");
char* args[4]={"avg3.out","4","3","5"};
/* Replace the child process with the “avg3” program. */
execv("avg3.out", args);
} else {
/* This is the parent process. */
FILE* stream;
/* Close our copy of the read end of the file descriptor. */
close (fds[0]);
/* Convert the write file descriptor to a FILE object, and write
to it. */
dup2(fds[0], STDOUT_FILENO);
stream = fdopen (fds[1], "w");
fprintf (stream, "5 4 3");
fflush (stream);
close (fds[1]);
/* Wait for the child process to finish. */
waitpid (pid, NULL, 0);
}
return 0;
}
avg3.out is a file I compiled before. It simply calculate the average of the 3 params sent to it.
The output was 4, but when I tried to actually read from the stream, I added a declaration for char buffer[10] The code stopped working. That is, no output provided. I tried to rename it, to move the decleration to the start of the if statement. but nothing worked.
So, why does the program stop working when adding just an array declaration?
The parameter-array to go with calls to exec*() needs to be (char*)NULL-terminated.
This line
char* args[4]={"avg3.out","4","3","5"};
should be
char* args[] = {"avg3.out", "4", "3", "5", NULL};
As it isn't in your code, exec() might get lost searching for it.
By bad luck the stack might have been clean (0 filled) for the version of your code not declaring a and execv() found a NULL right after the pointer pointing to "5". Having a created on the stack then changed the content of the stack which made execv() getting lost searching for the NULL.
Additionally its worth mentioning that the OP's code misses error checking on most of the relevant system call.
Having done so together with a detailed examation of the errors' causes, probably by using calls to perror() might have led to solving this issue by providing relevant information.
In particular placing it after the call to execv() it soon would have been obvious what's wrong:
execv("avg3.out", args);
perror("execv() failed");

Resources