Pipe guarantee to close after the child has exited - c

In the code below, is it safe to rely on read() failure to detect termination of child?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void)
{
int pipefd[2];
pipefd[0] = 0;
pipefd[1] = 0;
pipe(pipefd);
pid_t pid = fork();
if (pid == 0)
{
// child
close(pipefd[0]); // close unused read end
while ((dup2(pipefd[1], STDOUT_FILENO) == -1) && (errno == EINTR)) {} // send stdout to the pipe
while ((dup2(pipefd[1], STDERR_FILENO) == -1) && (errno == EINTR)) {} // send stderr to the pipe
close(pipefd[1]); // close unused write end
char *argv[3];
argv[0] = "worker-app";
argv[1] = NULL;
argv[2] = NULL;
execvp("./worker-app", argv);
printf("failed to execvp, errno %d\n", errno);
exit(EXIT_FAILURE);
}
else if (pid == -1)
{
}
else
{
// parent
close(pipefd[1]); // close the write end of the pipe in the parent
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
while (1) // <= here is it safe to rely on read below to break from this loop ?
{
ssize_t count = read(pipefd[0], buffer, sizeof(buffer)-1);
printf("pipe read return %d\n", (int)count);
if (count > 0)
{
printf("child: %s\n", buffer);
}
else if (count == 0)
{
printf("end read child pipe\n", buffer);
break;
}
else if (count == -1)
{
if (errno == EINTR)
{ continue;
}
printf("error read child pipe\n", buffer);
break;
}
}
close(pipefd[0]); // close read end, prevent descriptor leak
int waitStatus = 0;
waitpid(pid, &waitStatus, 0);
}
fprintf(stdout, "All work completed :-)\n");
return EXIT_SUCCESS;
}
Should I add something in the while(1) loop to detect child termination? What specific scenario could happen and break this app ?
Some thoughts of improvements below. However would I just waste CPU cycles?
Use kill with special argument 0 that won't kill the process but just check if it is responsive:
if (kill(pid, 0)) { break; /* child exited */ };
/* If sig is 0, then no signal is sent, but error checking is still performed; this can be used to check for the existence of a process ID or process group ID. https://linux.die.net/man/2/kill */
Use waitpid non-blocking in the while(1) loop to check if child has exited.
Use select() to check for pipe readability to prevent read() from possibly hanging?
Thanks!

Regarding your ideas:
If the child spawns children of its own, the read() won't return 0 until all of its descendants either die or close stdout and stderr. If it doesn't, or if the child always outlives all of its descendants, then just waiting for read() to return 0 is good enough and won't ever cause a problem.
If the child dies but the parent hasn't yet wait(2)ed on it, then kill(pid, 0) will succeed as if the child were still alive (at least on Linux), so this isn't an effective check from within your parent program.
A non-blocking waitpid() on its own would appear to fix the problem with the child having children of its own, but would actually introduce a subtle race condition. If the child exited right after the waitpid() but before the read(), then the read() would block until the rest of the descendants exited.
On its own, if you used select() in a blocking way, it's no better than just calling read(). If you used select() in a non-blocking way, you'd just end up burning CPU time in a loop.
What I'd do:
Add a no-op signal handler function for SIGCHLD, just so that it causes EINTR when it occurs.
Block SIGCHLD in the parent before you start looping.
Use non-blocking reads, and use pselect(2) to block to avoid spinning the CPU forever.
During the pselect, pass in a sigset_t that doesn't have SIGCHLD blocked, so that it's guaranteed to cause an EINTR for it when it eventually gets sent.
Somewhere in the loop, do a non-blocking waitpid(2), and handle its return appropriately. (Make sure you do this at least once after blocking SIGCHLD but before calling select for the first time, or you'll have a race condition.)

Related

forked process doesn't exit when parent invoke waitpid()

In this program 2 children process are forked, then one send string to another through pipe. When communication finished, the parent get stuck in waiting the exit of the child that reads from the pipe (waitpid(read_child, NULL, 0);). It works fine without any waitpid (both children processes exit) or just wait for the write_child. Why is that?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
int pipe_fd[2];
if (pipe(pipe_fd) == -1)
{
// fail to build pipe
perror("pipe");
exit(EXIT_FAILURE);
}
int read_child = fork();
if (read_child == 0)
{
sleep(0.5);
close(pipe_fd[1]);
printf("child %d read from pipe:\n", (int)getpid());
char buffer;
while (read(pipe_fd[0], &buffer, 1) > 0)
{
write(STDOUT_FILENO, &buffer, 1);
}
write(STDOUT_FILENO, "\n", 1);
close(pipe_fd[0]);
printf("read child exits\n");
exit(0);
}
int write_child = fork();
if (write_child == 0)
{
sleep(0.5);
close(pipe_fd[0]);
printf("child %d writes to pipe\n", (int)getpid());
char message[100];
sprintf(message, "greeting from brother %d", (int)getpid());
write(pipe_fd[1], message, strlen(message));
close(pipe_fd[1]);
printf("write child exits\n");
exit(0);
}
waitpid(read_child, NULL, 0);
// waitpid(write_child, NULL, 0);
return 0;
}
TL;DR
add close(pipe_fd[1]); just before parent process's waitpid(read_child, NULL, 0); will solve the problem.
The problem here is that, parent process also holds a reference to the two pipe fds.
The read blocks until some data available or, when the pipe identified by the fd is closed and the read returns immediately with 0 byte.
Initially the refcount of the write pipe fd is 2, from the writer child and the parent. The parent block waiting for writer, and then the writer exits, refcount decreases to 1. As the writer has exited, the parent's blocking wait returns, and the parent also exit. Refcount decreases to 0, so the write side of pipe is closed, so the reader's blocking read returns with 0 byte, then the reader exits.
However if the parent wait for the reader without first releasing its reference to write side of the pipe fd, the pipe will not be closed even if the writer has exited, due to the final reference from the parent. This means, the read of the reader child shall block forever...

C - How to detect EOF in pipe when reading only single char?

As explained in this answer, I'd be expecting the reader process to catch the EOF right after the writer process closes all related file descriptors.
But that doesn't happen and this program ends up stuck in an endless loop.
Parent waits for it's child to finish & child waits for EOF signalizing closed pipe.
Why the reader process doesn't receive EOF?
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <wait.h>
#define STRING_TO_SEND "Hello, world!\n"
int main() {
int fd[2], i = 0;
__pid_t pid;
char _char;
ssize_t nbytes;
pipe(fd);
pid = fork();
if (pid == -1) {
// Error
perror("Error forking!");
return EXIT_FAILURE;
} else if (pid == 0) {
// Child
close(fd[1]);
while ((nbytes = read(fd[0], &_char, 1)) != EOF) {
if (nbytes == 0)
continue;
putchar(_char);
}
close(fd[0]);
} else {
// Parent
close(fd[0]);
for(;;) {
_char = STRING_TO_SEND[i++];
write(fd[1], &_char, 1);
if (_char == '\0')
break;
}
close(fd[1]);
close(STDOUT_FILENO);
while (wait(NULL)>0) {}
}
return 0;
}
You simply misunderstood the "end of file" indication of read() which simply means nothing more to read for read() (read() returns 0 in that case). But read() doesn't actually return the value EOF. So your condition should be:
while ((nbytes = read(fd[0], &_char, 1)) > 0) {
Also __pid_t is an internal type of your C library. You shouldn't use that; just use pid_t.
See read(2)'s man page for details.
EOF is a constant usually defined to -1 that stdio (the C library buffering layer around the raw system calls) uses to signal end of file in functions like getchar() which conflate the returned character with an end-of-file signal.
read signals end-of-file by simply returning 0. Note that it can also return -1 if there's an error (e.g., you can get EINTR if the read is interrupted by a signal handler before it read anything).
Consequently, what you want is something like:
while ((nbytes = read(fd[0], &_char, 1)) > 0){ /*...*/ }
if (0>nread) { /*report error (or maybe repeat if it's EINTR)*/ }
Manpages (read(2)) or the POSIX spec for read document all this.

prctl(PR_SET_PDEATHSIG) race condition

As I understand, the best way to achieve terminating a child process when its parent dies is via prctl(PR_SET_PDEATHSIG) (at least on Linux): How to make child process die after parent exits?
There is one caveat to this mentioned in man prctl:
This value is cleared for the child of a fork(2) and (since Linux 2.4.36 / 2.6.23) when executing a set-user-ID or set-group-ID binary, or a binary that has associated capabilities (see capabilities(7)). This value is preserved across execve(2).
So, the following code has a race condition:
parent.c:
#include <unistd.h>
int main(int argc, char **argv) {
int f = fork();
if (fork() == 0) {
execl("./child", "child", NULL, NULL);
}
return 0;
}
child.c:
#include <sys/prctl.h>
#include <signal.h>
int main(int argc, char **argv) {
prctl(PR_SET_PDEATHSIG, SIGKILL); // ignore error checking for now
// ...
return 0;
}
Namely, the parent count die before prctl() is executed in the child (and thus the child will not receive the SIGKILL). The proper way to address this is to prctl() in the parent before the exec():
parent.c:
#include <unistd.h>
#include <sys/prctl.h>
#include <signal.h>
int main(int argc, char **argv) {
int f = fork();
if (fork() == 0) {
prctl(PR_SET_PDEATHSIG, SIGKILL); // ignore error checking for now
execl("./child", "child", NULL, NULL);
}
return 0;
}
child.c:
int main(int argc, char **argv) {
// ...
return 0;
}
However, if ./child is a setuid/setgid binary, then this trick to avoid the race condition doesn't work (exec()ing the setuid/setgid binary causes the PDEATHSIG to be lost as per the man page quoted above), and it seems like you are forced to employ the first (racy) solution.
Is there any way if child is a setuid/setgid binary to prctl(PR_SET_PDEATH_SIG) in a non-racy way?
It is much more common to have the parent process set up a pipe. Parent process keeps the write end open (pipefd[1]), closing the read end (pipefd[0]). Child process closes the write end (pipefd[1]), and sets the read end (pipefd[1]) nonblocking.
This way, the child process can use read(pipefd[0], buffer, 1) to check if the parent process is still alive. If the parent is still running, it will return -1 with errno == EAGAIN (or errno == EINTR).
Now, in Linux, the child process can also set the read end async, in which case it will be sent a signal (SIGIO by default) when the parent process exits:
fcntl(pipefd[0], F_SETSIG, desired_signal);
fcntl(pipefd[0], F_SETOWN, getpid());
fcntl(pipefd[0], F_SETFL, O_NONBLOCK | O_ASYNC);
Use a siginfo handler for desired_signal. If info->si_code == POLL_IN && info->si_fd == pipefd[0], the parent process either exited or wrote something to the pipe. Because read() is async-signal safe, and the pipe is nonblocking, you can use read(pipefd[0], &buffer, sizeof buffer) in the signal handler whether the parent wrote something, or if parent exited (closed the pipe). In the latter case, the read() will return 0.
As far as I can see, this approach has no race conditions (if you use a realtime signal, so that the signal is not lost because an user-sent one is already pending), although it is very Linux-specific. After setting the signal handler, and at any point during the lifetime of the child process, the child can always explicitly check if the parent is still alive, without affecting the signal generation.
So, to recap, in pseudocode:
Construct pipe
Fork child process
Child process:
Close write end of pipe
Install pipe signal handler (say, SIGRTMIN+0)
Set read end of pipe to generate pipe signal (F_SETSIG)
Set own PID as read end owner (F_SETOWN)
Set read end of pipe nonblocking and async (F_SETFL, O_NONBLOCK | O_ASYNC)
If read(pipefd[0], buffer, sizeof buffer) == 0,
the parent process has already exited.
Continue with normal work.
Child process pipe signal handler:
If siginfo->si_code == POLL_IN and siginfo->si_fd == pipefd[0],
parent process has exited.
To immediately die, use e.g. raise(SIGKILL).
Parent process:
Close read end of pipe
Continue with normal work.
I do not expect you to believe my word.
Below is a crude example program you can use to check this behaviour yourself. It is long, but only because I wanted it to be easy to see what is happening at runtime. To implement this in a normal program, you only need a couple of dozen lines of code. example.c:
#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
static volatile sig_atomic_t done = 0;
static void handle_done(int signum)
{
if (!done)
done = signum;
}
static int install_done(const int signum)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_handler = handle_done;
act.sa_flags = 0;
if (sigaction(signum, &act, NULL) == -1)
return errno;
return 0;
}
static int deathfd = -1;
static void death(int signum, siginfo_t *info, void *context)
{
if (info->si_code == POLL_IN && info->si_fd == deathfd)
raise(SIGTERM);
}
static int install_death(const int signum)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_sigaction = death;
act.sa_flags = SA_SIGINFO;
if (sigaction(signum, &act, NULL) == -1)
return errno;
return 0;
}
int main(void)
{
pid_t child, p;
int pipefd[2], status;
char buffer[8];
if (install_done(SIGINT)) {
fprintf(stderr, "Cannot set SIGINT handler: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
if (pipe(pipefd) == -1) {
fprintf(stderr, "Cannot create control pipe: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
child = fork();
if (child == (pid_t)-1) {
fprintf(stderr, "Cannot fork child process: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
if (!child) {
/*
* Child process.
*/
/* Close write end of pipe. */
deathfd = pipefd[0];
close(pipefd[1]);
/* Set a SIGHUP signal handler. */
if (install_death(SIGHUP)) {
fprintf(stderr, "Child process: cannot set SIGHUP handler: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
/* Set SIGTERM signal handler. */
if (install_done(SIGTERM)) {
fprintf(stderr, "Child process: cannot set SIGTERM handler: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
/* We want a SIGHUP instead of SIGIO. */
fcntl(deathfd, F_SETSIG, SIGHUP);
/* We want the SIGHUP delivered when deathfd closes. */
fcntl(deathfd, F_SETOWN, getpid());
/* Make the deathfd (read end of pipe) nonblocking and async. */
fcntl(deathfd, F_SETFL, O_NONBLOCK | O_ASYNC);
/* Check if the parent process is dead. */
if (read(deathfd, buffer, sizeof buffer) == 0) {
printf("Child process (%ld): Parent process is already dead.\n", (long)getpid());
return EXIT_FAILURE;
}
while (1) {
status = __atomic_fetch_and(&done, 0, __ATOMIC_SEQ_CST);
if (status == SIGINT)
printf("Child process (%ld): SIGINT caught and ignored.\n", (long)getpid());
else
if (status)
break;
printf("Child process (%ld): Tick.\n", (long)getpid());
fflush(stdout);
sleep(1);
status = __atomic_fetch_and(&done, 0, __ATOMIC_SEQ_CST);
if (status == SIGINT)
printf("Child process (%ld): SIGINT caught and ignored.\n", (long)getpid());
else
if (status)
break;
printf("Child process (%ld): Tock.\n", (long)getpid());
fflush(stdout);
sleep(1);
}
printf("Child process (%ld): Exited due to %s.\n", (long)getpid(),
(status == SIGINT) ? "SIGINT" :
(status == SIGHUP) ? "SIGHUP" :
(status == SIGTERM) ? "SIGTERM" : "Unknown signal.\n");
fflush(stdout);
return EXIT_SUCCESS;
}
/*
* Parent process.
*/
/* Close read end of pipe. */
close(pipefd[0]);
while (!done) {
fprintf(stderr, "Parent process (%ld): Tick.\n", (long)getpid());
fflush(stderr);
sleep(1);
fprintf(stderr, "Parent process (%ld): Tock.\n", (long)getpid());
fflush(stderr);
sleep(1);
/* Try reaping the child process. */
p = waitpid(child, &status, WNOHANG);
if (p == child || (p == (pid_t)-1 && errno == ECHILD)) {
if (p == child && WIFSIGNALED(status))
fprintf(stderr, "Child process died from %s. Parent will now exit, too.\n",
(WTERMSIG(status) == SIGINT) ? "SIGINT" :
(WTERMSIG(status) == SIGHUP) ? "SIGHUP" :
(WTERMSIG(status) == SIGTERM) ? "SIGTERM" : "an unknown signal");
else
fprintf(stderr, "Child process has exited, so the parent will too.\n");
fflush(stderr);
break;
}
}
if (done) {
fprintf(stderr, "Parent process (%ld): Exited due to %s.\n", (long)getpid(),
(done == SIGINT) ? "SIGINT" :
(done == SIGHUP) ? "SIGHUP" : "Unknown signal.\n");
fflush(stderr);
}
/* Never reached! */
return EXIT_SUCCESS;
}
Compile and run the above using e.g.
gcc -Wall -O2 example.c -o example
./example
The parent process will print to standard output, and the child process to standard error. The parent process will exit if you press Ctrl+C; the child process will ignore that signal. The child process uses SIGHUP instead of SIGIO (although a realtime signal, say SIGRTMIN+0, would be safer); if generated by the parent process exiting, the SIGHUP signal handler will raise SIGTERM in the child.
To make the termination causes easy to see, the child catches SIGTERM, and exits the next iteration (a second later). If so desired, the handler can use e.g. raise(SIGKILL) to terminate itself immediately.
Both parent and child processes show their process IDs, so you can easily send a SIGINT/SIGHUP/SIGTERM signal from another terminal window. (The child process ignores SIGINT and SIGHUP sent from outside the process.)
Your last code snippet still contains a race condition:
int main(int argc, char **argv) {
int f = fork();
if (fork() == 0) {
// <- !!!race time!!!
prctl(PR_SET_PDEATHSIG, SIGKILL); // ignore error checking for now
execl("./child", "child", NULL, NULL);
}
return 0;
}
Meaning that in the child, after the fork, until the prctl() has visible effects (think: returns), there is a time-window where the parent may exit.
To fix this race you have to save the PID of the parent before the fork and check it after the prctl() call, e.g.:
pid_t ppid_before_fork = getpid();
pid_t pid = fork();
if (pid == -1) { perror(0); exit(1); }
if (pid) {
; // continue parent execution
} else {
int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
if (r == -1) { perror(0); exit(1); }
// test in case the original parent exited just
// before the prctl() call
if (getppid() != ppid_before_fork)
exit(1);
// continue child execution ...
(see also)
Regarding executing a setuid/setgid program: You can then pass the ppid_before_fork by other means (e.g. in the argument or environment vector) and execute the prctl() (including the comparison) after the exec, i.e. inside the execed binary.
I don't know this for sure, but clearing the parent death signal on execve when invoking a set-id binary looks like an intentional restriction for security reasons. I'm not sure why, considering that you can use kill to send signals to setuid programs that share your real user ID, but they wouldn't have bothered making that change in 2.6.23 if there wasn't a reason to disallow it.
Since you control the code of the set-id child, here is a kludge: make the call to prctl, then immediately afterward, call getppid and see if it returns 1. If it does, then either the process was started directly by init (which is not as rare as it used to be) or the process was reparented to init before it had a chance to call prctl, which means its original parent is dead and it should exit.
(This is a kludge because I know of no way to rule out the possibility that the process was started directly by init. init never exits, so you have one case where it should exit and one case where it shouldn't and no way to tell which. But if you know from the larger design that the process will not be started directly by init, it should be reliable.)
(You must call getppid after prctl, or you have only narrowed the race window, not eliminated it.)

Capturing exit status code of child process

I have a function that forks a process, duplicates file descriptors for input and output buffers, and then runs execl on a command passed in via a string called cmd:
static pid_t
c2b_popen4(const char* cmd, int pin[2], int pout[2], int perr[2], int flags)
{
pid_t ret = fork();
if (ret < 0) {
fprintf(stderr, "fork() failed!\n");
return ret;
}
else if (ret == 0) {
/*
Assign file descriptors to child pipes (not shown)...
*/
execl("/bin/sh", "/bin/sh", "-c", cmd, NULL);
fprintf(stderr, "execl() failed!\n");
exit(EXIT_FAILURE);
}
else {
/*
Close parent read and write pipes (not shown)...
*/
return ret;
}
return ret;
}
Each of the cmd instances process my data correctly, so long as my test inputs are correct.
When bad data is passed to a child process, my parent program will run to completion and exit with a non-error status code of 0.
If I deliberately put in bad input — to purposefully try to get one of the cmd instances to fail in an expected way — I'd like to know how to capture the exit status of that cmd so that I can issue the correct error status code from the parent program, before termination.
How is this generally done?
You can get the exit status of the child via the first argument of wait(), or the second argument of waitpid(), and then using the macros WIFEXITED and WEXITSTATUS with it.
For instance:
pid_t ret = c2b_popen4("myprog", pin, pout, perr, 0);
if ( ret > 0 ) {
int status;
if ( waitpid(ret, &status, 0) == -1 ) {
perror("waitpid() failed");
exit(EXIT_FAILURE);
}
if ( WIFEXITED(status) ) {
int es = WEXITSTATUS(status);
printf("Exit status was %d\n", es);
}
}
A simplified working example:
failprog.c:
int main(void) {
return 53;
}
shellex.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void)
{
pid_t p = fork();
if ( p == -1 ) {
perror("fork failed");
return EXIT_FAILURE;
}
else if ( p == 0 ) {
execl("/bin/sh", "bin/sh", "-c", "./failprog", "NULL");
return EXIT_FAILURE;
}
int status;
if ( waitpid(p, &status, 0) == -1 ) {
perror("waitpid failed");
return EXIT_FAILURE;
}
if ( WIFEXITED(status) ) {
const int es = WEXITSTATUS(status);
printf("exit status was %d\n", es);
}
return EXIT_SUCCESS;
}
Output:
paul#thoth:~/src/sandbox$ ./shellex
exit status was 53
paul#thoth:~/src/sandbox$
waitpid() will block until the process with the supplied process ID exits. Since you're calling your function with a popen() name and passing pipes to it, presumably your child process doesn't terminate quickly, so that probably wouldn't be the right place to check it, if the call succeeded. You can pass WNOHANG as the third parameter to waitpid() to check if the process has terminated, and to return 0 if the child has not yet exited, but you have to be careful about when you do this, since you get no guarantees about which process will run when. If you call waitpid() with WNOHANG immediately after returning from c2b_popen4(), it may return 0 before your child process has had a chance to execute and terminate with an error code, and make it look as if the execution was successful when it's just about to not be successful.
If the process does die immediately, you'll have problems reading from and writing to your pipes, so one option would be to check waitpid() if you get an error from the first attempt to do that, to check if the read() or write() is failing because your child process died. If that turns out to be true, you can retrieve the exit status and exit your overall program then.
There are other possible strategies, including catching the SIGCHLD signal, since that'll be raised whenever one of your child processes dies. It would be OK, for instance, to call _exit() right from your signal handler, after waiting for the child process (calling waitpid() in a signal handler is also safe) and getting its exit status.

Data transmitted in a pipe are garbage

I am using these two programs of this answer. This answer uses named-pipes and not pipes, am I correct?
I have written main.c, which is actually the code of my actual project, minimized to this specific question (that's why I have a for loop for example).
#include <unistd.h>
#include <sys/wait.h>
#include <stddef.h>
#include <limits.h>
#include <stdio.h>
int main(void) {
pid_t pid;
int i;
for(i = 0; i < 2; ++i) {
pid = fork();
if (pid == -1) {
// error, failed to fork()
perror("failed to fork()");
return 1;
} else if (pid == 0) {
// child code
if(i < 1) {
// the writer.c
char* arg_list[] = { "w", NULL };
execv( "w", arg_list );
printf("exec FAILED\n");
} else {
// the reader.c
char* arg_list[] = { "r", NULL };
execv( "r", arg_list );
printf("exec FAILED\n");
}
}
}
// parent code
int status;
// wait for all children to terminate
while ((pid = wait(&status)) > 0) {
if (status == 1) {
printf("The child process terminated with an error!\n");
return -1;
}
}
printf("All children are done\n");
return 0;
}
The problem is that sometimes, the reader receives garbage (or most likely nothing) and it hangs up.
Sample output:
Received: Hi
All children are done
samaras#samaras-A15:~/test$ ./m
Received: Hi
All children are done
samaras#samaras-A15:~/test$ ./m
Received: <----------------- This is garbage, that is not reproducible
^C
So, what am I missing?
No need to read below that point.
My guesses are (not checked, so if I am correct, I still need clarification):
The reader runs before writer, that's why it has garbage, but then why it hangs?
or
I need to write a wrapper function read_all() (and one for the write case as well?) that collects all the data that the pipe spits, but then why if I replace "Hi" with "H", I have the same behaviour?
EDIT:
In case my first guess is the case, I put a loop for reading, but it will execute forever in the case that reader starts first.
In the case that I see garbage, after running with strace -f I got this:
...
[pid 3326] read(-1, 0xbfddd80c, 1024) = -1 EBADF (Bad file descriptor)^C
Process 3324 resumed
Process 3325 detached
Process 3326 detached
Your loops (or lack of) have nothing to do with it. When your reader opens (open()) the pipe for reading before writer creates the pipe, then the file descriptor your readers waits on is invalid (-1). So even when writer writes something later on, reader just waits on an invalid fd (-1) and is never going to read anything. Trivially, you could solve it with:
while( (fd = open(myfifo, O_RDONLY)) == -1);
in reader so that it waits until pipe is available. I am actually wondering if there can be a better approach than this. One other way I can think of is a loop over access(), but it's not massively different to this...

Resources