Here i try to implement linux shell script with piping in c, and i try to do it by passing the output of 1st child process to the 2nd child, and then do "grep a", then it should return sth like this
a 1
a 4
,and it should end the program.
But what i encounter is that, the output of 2nd child process is correct,output of "grep a" did come out, but the child process get stuck there and does not terminate itself, can anyone explain to me why this is happening? My parent process is keep waiting for the 2nd child process to end. But it just stuck there foreverfor some reason.
/* pipe4.c */
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include<sys/wait.h>
#include<stdio.h>
#include<stdlib.h>
#include <unistd.h>
int main(int agrc, char* agrv[])
{
int pipefds[2];
pid_t pid;
pid_t pid2;
int status;
if(pipe(pipefds) == -1){
perror("pipe");
exit(EXIT_FAILURE);
}
pid = fork();
if(pid == -1){
perror("fork");
exit(EXIT_FAILURE);
}
if(pid == 0){
//replace stdout with the write end of the pipe
dup2(pipefds[1],STDOUT_FILENO);
//close read to pipe, in child
close(pipefds[0]);
execlp("cat","cat","try.txt",NULL);
}else{
waitpid(pid, &status, 0);
printf("first child done\n");
pid2 = fork();
if(pid2 == 0){
printf("second child start\n");
dup2(pipefds[0],STDIN_FILENO);
close(pipefds[1]);
execlp("grep","grep","a",NULL);
}
else{
waitpid(pid2, &status, 0);
printf("second child end\n");
close(pipefds[0]);
close(pipefds[1]);
exit(EXIT_SUCCESS);
printf("end\n");
}
}
}
The grep is waiting for all processes to close the write side of the pipe. The parent is waiting for grep to finish before it closes the write side of the pipe. That's a deadlock. The parent needs to close the pipe ends before it calls waitpid
Note that the boiler plate for dup2 is:
dup2(pipefds[1],STDOUT_FILENO);
close(pipefds[0]);
close(pipefds[1]);
as you need to close both ends of the pipe. I believe this is not causing an issue in your current setup, but it's not worth thinking too hard about. Just close both ends of the pipe.
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...
In original program kill(pid, SIGUSR1); is called first and the pause(); in parent process and In child process pause(); is called first and then kill(getppid(), SIGUSR1); its output is given below
In changed program if I replace the kill(getppid(), SIGUSR1); with pause(); in child process output is totally different I have pasted the output below the code.
Can someone explain me the why the output is changed
**********************ORIGINAL PROGRAM***********************************
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void action(int dummy){
sleep(1);
printf("Switching\n");
}
int main(int argc, char *argv[]){
pid_t pid;
if((pid=fork())>0){//parent
sleep(1);
while(1){
printf("Parent is running\n");
kill(pid, SIGUSR1);
signal(SIGUSR1, action);
pause();
}
}
else //child code
while(1){//child
signal(SIGUSR1, action);
pause();
printf("Child is running\n");
kill(getppid(), SIGUSR1);
}
}
//OUTPUT OF THIS PROGRAM
Parent is running
Switching
Child is running
Switching
Parent is running
Switching
Child is running
Switching
Parent is running
Switching
Child is running
Switching
Parent is running
Switching
Child is running
Switching
Parent is running
Switching
Child is running
*********************CHANGED PROGRAM************************
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void action(int dummy){
sleep(1);
printf("Switching\n");
}
int main(int argc, char *argv[]){
pid_t pid;
if((pid=fork())>0){//parent
sleep(1);
while(1){
printf("Parent is running\n");
kill(pid, SIGUSR1);
signal(SIGUSR1, action);
pause();
}
}
else //child code
while(1){//child
signal(SIGUSR1, action);
kill(getppid(), SIGUSR1);
printf("Child is running\n");
pause();
}
}
//OUTPUT OF THIS PROGRAM
//Child is running
//User defined signal 1
If a process receives SIGUSR1 before it has set up a signal handler for it (and is not ignoring or holding it), the process will be terminated. (For details. see the signal man page).
Your code (both versions) has several race conditions.
In the first version:
if((pid=fork())>0){//parent
sleep(1);
while(1){
printf("Parent is running\n");
kill(pid, SIGUSR1);
signal(SIGUSR1, action);
pause();
}
}
else //child code
while(1){//child
signal(SIGUSR1, action);
pause();
printf("Child is running\n");
kill(getppid(), SIGUSR1);
}
The child will wait for SIGUSR1.
At about the same time, the parent will sleep, then send SIGUSR1 to the child.
The child, after the signal has been received, will do a couple printfs, then send SIGUSR1 to the parent.
At about the same time, the parent will set up a signal handler for SIGUSR1.
It's likely (but not necessarily always the case) that the child will set up the signal handler while the parent is doing the sleep(1); the signal sent from the parent to the child will be caught rather than causing the child to be terminated.
It's likely (but not necessarily always the case) that by the time the child has done its two printfs, the parent has set up the signal handler; the signal sent from the child to the parent will be caught rather than causing the parent to be terminated.
But, since there are race conditions, slight changes in timing can cause things to break.
In the second version:
if((pid=fork())>0){//parent
sleep(1);
while(1){
printf("Parent is running\n");
kill(pid, SIGUSR1);
signal(SIGUSR1, action);
pause();
}
}
else //child code
while(1){//child
signal(SIGUSR1, action);
kill(getppid(), SIGUSR1);
printf("Child is running\n");
pause();
}
The child sends SIGUSR1 to the parent just after it forks, and this will almost certainly happen while the parent is in the middle of the sleep(1). Since the parent hasn't yet set up a handler for SIGUSR1, the signal will terminate it. The shell then prints out User defined signal 1, which is the long name of the SIGUSR1 signal.
Things will work better if you set up a signal handler for SIGUSR1 before the fork. That way, both parent and child will be ready to handle the signal.
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.)
I am trying to write a C program in which the parent process suspends the child process and after a few seconds continues executing it.
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
void sigstop();
void sigcont();
void sigquit();
int main()
{
int pid;
if ((pid = fork()) < 0) {
perror("fork");
exit(1);
}
if (pid == 0)
{ /* child */
signal(SIGSTOP,sigstop);
signal(SIGCONT,sigcont);
signal(SIGQUIT, sigquit);
for(;;); /* loop for ever */
}
else {
printf("\nPARENT: sending SIGSTOP to suspend the process\n\n");
kill(pid, SIGSTOP);
sleep(5);
printf("\nPARENT: sending SIGCONT to continue the suspended process\n\n");
kill(pid, SIGCONT);
sleep(5);
printf("killing child");
kill(pid,SIGQUIT);
sleep(5);
}
}
void sigstop()
{
printf("CHILD: I've been suspended\n");
}
void sigcont()
{
printf("CHILD: I'm back\n");
}
void sigquit()
{
printf("My DADDY has Killed me!!!\n");
exit(0);
}
printf inside sigstop() never gets executed and sigquit() gets called before printf("killing child");. How does this happen and how can I get output in proper order ?
If you read the signal(7) manual page you will see that
The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.
You simply can not catch that signal.
The last bit, about "Killing child" get printed in the wrong order is very easy to fix: Add a trailing newline to the string you print.
This is because output to stdout (which is what printf writes to) is by default line-buffered. This means that the internal buffer of stdout is flushed (i.e. actually written to the terminal) on a newline. If you don't have the newline, then the buffer will be flushed when the process exits, which is after you exit the child process.