SIGCHLD and fork + waitpid() in a library - c

I am writing a library that uses fork() and exec() to run external programs.
The code in my library runs as a separate thread.
The library thread needs to waitpid() on the forked process to know the exit code of the child process.
Unfortunately if the application using my library register a signal handler for SIGCHLD, the waitpid() calls returns with an error ECHILD.
How do I deal with this as a library with the application having minimal impact?
I essentially want the child to remain a zombie and have control over when it's reaped.
Can I insulate myself from what the application decides to do?
Can I hijack the signal handler in some way and put it back after my waitpid is done?

Freeing resources allocated by a library is an application bug. It's the same in principle as if the appliction did something like free(yourlib_create_context(...)), although of course the consequences are less severe. There's no reason to try to support this kind of nonsensical application behavior; just document that it's invalid.
If you want to "shield" against this sort of programming error, mask signals an then call abort if waitpid fails with ECHLD. This will unconditionally terminate the process without the applicating having any chance to catch it.

You can do just as system does:
system() executes a command specified in command by calling /bin/sh -c command, and returns after the command has been completed. During execution of the command, SIGCHLD will be blocked, and SIGINT and SIGQUIT will be ignored.
An outline could be
pid_t pid;
sigset_t block, backup;
sigemptyset(&block);
sigaddset(&block, SIGCHLD);
sigprocmask(SIG_BLOCK, &block, &backup);
if ((pid = fork()) < 0) {
// error handle
} else if (pid == 0) { // child
sigprocmask(SIG_SETMASK, &backup, NULL);
// ...
} else {
// waitpid
sigprocmask(SIG_SETMASK, &backup, NULL);
}
// ...

Related

Systemcall multiprocess child not resuming in parent (fork) Linux

Tracing exec process from the parent to count the number of forks. In the parent I resume the execve and verify it stopped. I resume it (with ptrace(cont..)or SIGCONT). However its not resuming.
I read I should set PTRACE_O_TRACEEXEC option failing to do so results in SIGTRAP being sent to tracee upon a call to execve. Ive added into the child & parent, it doesnt seem to make a difference.
Im using Linux MX, it shouldnt be a problem as this should work in all recent Linux versions.
argument: /bin/bash -c "echo 'first test' | wc -c"
int main(int argc, char *argv[]) {
int status;
int counter = 0;
pid_t pid = fork();
if (pid < 0)
exit(1);
else if (pid == 0) {
ptrace(PTRACE_TRACEME, pid, NULL, NULL);
ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACEEXEC);
raise(SIGSTOP);
execve(argv[1], &argv[1], NULL);
} else {
wait(&status);
if (WIFSTOPPED(status))
printf("child is stopped");
if (status == 0)
printf("The child process terminated normally.");
if (status == 1)
printf("The child process terminated with an error!.");
ptrace(PTRACE_CONT, pid, NULL, NULL); //<< Child should restart here, not sure if pid = childs or parents.
raise(SIGCONT); // if ptrace(cont) doesnt work then this should make the child start.
if (WIFSTOPPED(status))
printf("child is stopped"); // << this shouldnt print bc Ive continued the child process.
while (1) {
ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_EXITKILL | PTRACE_O_TRACEFORK);
if (status >> 8 == (SIGTRAP | (PTRACE_O_TRACEFORK << 8)) {
printf("it works");
break;
}
}
}
return 0;
}
The question and some of the code comments indicate some uncertainty about the behavior of ptrace, that in turn suggests unfamiliarity with its documentation. Manual pages are not always easy to understand, but you should definitely start there.
There are several problems with the program presented. Among the more significant are:
PTRACE_TRACEME is the only ptrace command recognized from the tracee. All other ptrace commands must be executed by the tracer, which is the parent in this case. In particular, any PTRACE_SETOPTIONS commands must be executed by the tracer.
The tracer (parent) uses raise() to attempt to send a SIGCONT to the child. This is wrong for two reasons:
raise() sends the specified signal to the calling process, which is not the one that is intended to receive the signal. The kill() function should be used instead to send a signal (of your choice) to another process. But,
A traced process is stopped whenever it receives a signal, but this ptrace-stop is different from "stopped by a signal". The effect of SIGCONT to resume execution applies only to the latter kind of stoppage. Therefore, sending a ptrace-stopped process a SIGCONT is counterproductive if the objective is to make the tracee resume execution. Not only will it not resume the process from ptrace-stop, but when it ultimately is delivered, it will cause the process to reenter ptrace-stop.
The program seems to assume that the second WIFSTOPPED(status) might evaluate to a different value than the first, and in particular, to a value that reflects the child's status at the time of the evaluation of WIFSTOPPED. That is a faulty assumption. WIFSTOPPED and its brethren simply interpret the status integer provided by wait(), which is a static representation of the status of the waited-for process as of the time the wait returned. It will not reflect any status changes since that time.
The program pervasively assumes that all its function calls succeed normally. Generally speaking, this is not a safe assumption. The program should check the return values of its function calls to verify successful completion, and take appropriate action (error message, possibly program termination) in the event of unexpected failure.
Overall, the program does not reflect a good understanding about the usage paradigm of ptrace(), and especially about the role of signals. For example, I take this comment:
I read I should set PTRACE_O_TRACEEXEC option failing to do so results
in SIGTRAP being sent to tracee upon a call to execve.
Yes, if the PTRACE_O_TRACEEXEC is not enabled, then the traced process will be sent a SIGTRAP when it calls execve, but the whole point of that is so that the tracer can catch that and have the option to trace the tracee at that point. As with any signal (other than SIGKILL) delivered to the tracee, the tracer has complete control over whether the signal is actually acted upon by the tracee. Furthermore, if PTRACE_O_TRACEEXEC is in effect then the tracee still stops for tracing when it performs an execve -- the difference is that the status returned to the tracer can distinguish between this case and the one where a SIGTRAP is delivered to the tracee for some other reason (read the docs for details).
So here's a general outline:
The original process forks.
The child executes a PTRACE_TRACEME command to attach itself as tracee to its parent process. (It is not useful for your purposes for the child to execute any other ptrace commands.)
The child proceeds normally to whichever exec-family function is appropriate (all will eventually result in a call to execve). It's not particularly useful for the child to send itself a signal prior to the exec if the only point is to get the attention of the tracer.
The parent loops, repeatedly waiting for the child. It doesn't have to use the wait() function specifically; other functions from that family, such as waitpid(), may also be used.
Upon each successful return from a wait, the parent handles a ptrace or other event. The first of these is likely to be the SIGTRAP generated by the child's initial execve (though it cannot be ruled out that another event, for a different signal, is received first). The status code filled in by wait conveys information about the nature of the event, and in particular, which signal or traced event triggered it.
The parent sets whatever ptrace options it wants when the first ptrace event is received. This can include PTRACE_O_TRACEEXEC if you like, though again, exec events are traced regardless. If you want to count not only the forks of the initial child, but also any forks of that child's descendants, then you probably want to set PTRACE_O_TRACEFORK.
After every event handled, the parent performs a PTRACE_CONT command targeting the tracee. If you are tracing all the forked descendants, then the applicable tracee is not necessarily the initial child.
The parent exits the loop after it has handled a termination event for every traced descendant.

Kill child process spawned with execl without making it zombie

I need to spawn a long-running child process and then kill it from the parent code. At the moment I do like this:
int PID = fork();
if (PID == 0) {
execl("myexec", "myexec", nullptr);
perror("ERROR");
exit(1);
}
// do something in between and then:
kill(PID, SIGKILL);
This does the job in the sense that the child process is stopped, but then it remains as a zombie. I tried to completely remove it by adding:
kill(getpid(), SIGCHLD);
to no avail. I must be doing something wrong but I can't figure out what, so any help would be greatly appreciated. Thanks.
signal(SIGCHLD, SIG_IGN);
kill(getpid(), SIGCHLD);
Presto. No zombie.
By ignoring SIGCHLD we tell the kernel we don't care about exit codes so the zombies just go away immediately.
You have been answered with:
signal(SIGCHLD, SIG_IGN);
to ignore the signal sent to the parent when a child dies. This is an old mechanism to avoid zombies, but zombies are your friends, as my answer will explain.
The zombies are not a bug, but a feature of the system. They are there to complete the fork(2), wait(2), exit(2), kill(2) group of system calls.
When you wait(2) for a child to die, the kernel tests if there's a child running with the characteristics you state in the wait(2). If it exists, the wait(2) will block, because the wait(2) system call is the one used in unix to give the parent the exit status of the waited child. If you use wait() and you have done no fork() a new child previously, wait() should give you an error, because you are calling wait with no fork (i'll stop boldening the system calls in this discussion from here on) but what happens if the parent did a fork but the child died before the parent was capable of making a wait. Should this be taken as an error? No. The system maintains the process table entry for the child proces, until one of two things happen: The parent dies (then all children processess get orphaned, being adopted by process id 1 ---init/systemd--- which is continously blocked in wait calls; or the parent does a wait, in which case the status of one (or the one requested) of the children is reported.
So in a proper usage of the system, it is possible (or necessary) to make a wait call for each fork you make. if you do more waits than forks, you get errors... if you make more forks than waits, you get zombies. In order to compensate this, your code should be changed to make the appropiate wait call.
kill(PID, SIGINT); /* don't use SIGKILL in the first time, give time to your process to arrange its last will before dying */
res = waitpid(PID, &status, 0);
And this will allow the child to die normally. The child is going to die, because you killed it (except if the child has decided to ignore the signal you send to it)
The reason for no race condition here (the child could die before is is wait()ed for) is the zombie process. Zombie processes are not proper processes, they don't accept signals, it is impossible to kill them, because they are dead already (no pun here ;)). They only occupy the process table slot, but no resource is allocated to them. When a parent does a wait call, if there's a zombie, it will be freed and the accounting info will be transferred to the parent (this is how the accounting is done), including the exit status, and if there isn't (because it died prematurely and you had invoked the above behaviour) you will get an error from wait, and the accounting info will be tranferred to init/systemd, which will cope for this. If you decide to ignore the SIGCHLD signal, you are cancelling the production of zombies, but the accounting is being feed in the wron way to init/systemd, and not accounted in the parent. (no such process can be waited for) you cannot distinguish if the wait fails because the child process died or because you didn't spawn it correctly. More is to come.
Let's say that the child cannot exec a new program and it dies (calling exit()). When you kill it, nothing happens, as there's no target process (well, you should receive an error from kill call, but I assume you are not interested in the kill result, as it is irrelevant for this case, you are interested in the status of the child, or how did the child died. This means you need to wait for it. if you get a normal exit, with a status of 1, (as you did an exit in case exec() fails) you will know that the child was not able to exec (you still need to distinguish if the 1 exit code was produced by the child or by the program later run by the child). If you successfully killed the child, you should get a status telling you that the child was killed with signal (the one you sent) and you will know that your code is behaving properly.
In case you don't want to block your parent process in the wait system call (well, your child program could have decided to ignore signals and the kill had no effect), then you can substitute the above by this:
kill(PID, SIGINT);
res = waitpid(PID, &status, WNOHANG);
that will not block the parent, in the case the child program has decided to ignore the signal you send to it. In this case, if wait returns -1 and errno value EINTR, then you know that your child has decided to ignore the signal you sent to it, and you need help from the operator (or be more drastic, e.g. killing it with SIGKILL).
A good approach should be
void alarm_handler()
{
}
...
kill(PID, SIGINT); /* kill it softly (it's your child, man!!) */
void *saved = signal(SIGALRM, alarm_handler);
alarm(3); /* put an awakener, you will be interrupted in 3s. */
res = waitpid(PID, &status, 0);
signal(SIGALRM, saaved); /* restore the previous signal handler */
if (res == -1 && errno == EINTR) {
/* we where interrupted by the alarm, and child didn't die. */
kill(PID, SIGKILL); /* be more rude */
}

process catch SIGINT signal using sigint_handler

I encountered this problem when doing shell lab from the book CSAPP, the lab ask you to implement your own version of shell with some specification,one of them is
Typing ctrl-c (ctrl-z) should cause a SIGINT (SIGTSTP) signal to be
sent to the current foreground job, as well as any descendents of that
job (e.g., any child processes that it forked). If there is no
foreground job, then the signal should have no effect.
so you should complete one of the given functions called sigint_handler which supposed to catch SIGINT signal and send it along to the foreground job. below is a piece of code I find online(the code passed the correctness check)
void sigint_handler(int sig)
{
int olderrno = errno;
pid_t pid = fgpid(jobs);
if (pid != 0)
kill(-pid, sig);
errno = olderrno;
return;
}
what I don't understand is if SIGINT is sent using kill,then the descendents of foreground job will also use this handler to catch SIGINT signal right? so it's kind of a recursive call to me.so how does this actually work? thanks for helping me.
handler is installed in the main function
signal(SIGINT, sigint_handler); /* ctrl-c */
and fgpid return PID of current foreground job, 0 if no such job
Once a child process calls execve(), the child's (usually short-lived) initial address space is freed/released and replaced with space for the specified executable image; now the child no longer has a copy or access to the parent's data or text, like signal handlers.
Now consider a process-group associated with the control-terminal (tty). When a user types a CTRL-C (or CTRL-\ or CTRL-Z), the tty driver posts a signal to 1+ processes as members of the associated process-group. The result of delivering a signal would be the system default action unless a process established a different signal disposition (signal(), sigaction(), or related).
The posted code excerpt indicates a relayed event: user types a CTRL-C, tty driver posts a SIGINT to the shell, shell's handler looks for a foreground job, calls kill() with a negative pid to post a signal to members of that process-group.
For related info see these man pages:
man setpgrp
man tty_ioctl (symbols: TIOCSCTTY, TIOCGPGRP, TIOCSPGRP)
alternates:
man tcgetpgrp tcsetpgrp

What does "wait and waitpid are always interrupted when a signal is caught" mean?

From APUE:
To prevent applications from having to handle interrupted system
calls, 4.2BSD introduced the automatic restarting of certain
interrupted system calls. The system calls that were automatically
restarted are ioctl, read, readv, write, writev, wait, and waitpid. As
we’ve mentioned, the first five of these functions are interrupted by
a signal only if they are operating on a slow device; wait and waitpid
are always interrupted when a signal is caught. Since this caused a
problem for some applications that didn’t want the operation restarted
if it was interrupted, 4.3BSD allowed the process to disable this
feature on a per-signal basis.
Does it mean that before automatic restarting was introduced, if a process catch a signal, wait and waitpid would immediately stop waiting and execute the subsequent code?
For example:
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
void handler(int sig){}
void handler2(int sig){}
int main(){
pid_t pid;
int status;
signal(SIGUSR1, handler);
signal(SIGUSR2, handler2);
if((pid == fork()) < 0){
printf("fork error\n");
}else{
if(pid){
//child
//do something, needs several hours.
}else{
//parent
waitpid(pid, &status, 0);
printf("Hello world\n");
}
}
return 0;
}
If not providing automatic restarting, when I run this program in the background gcc test.c && ./a.out &, then I send a signal kill -SIGUSR1 pid or kill -SIGUSR2 pid, waitpid would return and the code after waitpid(pid, &status, 0); would execute.
And if providing automatic restarting, waitpid would execute again and the parent will remain waiting.
Is my understanding correct?
The original behaviour of signal() (System-V semantics) was, to interrupt any system call if the process currently slept, execute the signal handler and the system call returns with -EINTR. Then, BSD4.3 invented the restart mechanism, which would restart any system call automatically after it was interrupted. This avoids having to write a loop for each syscall if signal handlers are involved.
Linux did not change the semantics of the signal() syscall. However, the signal() glibc wrapper function nowadays calls the syscall sigaction() with SA_RESTART flag by default. So, if you do not need the restart behaviour, you have to call sigaction() and omit that flag.
So, your code indeed makes use of the restart mechanism on both BSD and linux
wait and waitpid like any other blocking function can be interrupted with errno set to EINTR - this is exactly because a signal handler can do very little - mostly set some flags. Now if a blocking function would not return with EINTR, how could you react to an signal in any way?!
But this also means that you need to have a complicated loop over every function - you might have some signals for which you know you don't want the system calls be interrupted, so you can set this signal to have automatic restart.

waitpid blocking when it shouldn't

I am writing a mini-shell(no, not for school :P; for my own enjoyment) and most of the basic functionality is now done but I am stuck when trying to handle SIGTSTP.
Supposedly, when a user presses Ctrl+Z, SIGTSTP should be sent to the Foreground process of the shell if it exists, and Shell should continue normally.
After creating each process(if it's a Foreground process), the following code waits:
if(waitpid(pid, &processReturnStatus, WUNTRACED)>0){//wait stopped too
if(WIFEXITED(processReturnStatus) || WIFSIGNALED(processReturnStatus))
removeFromJobList(pid);
}
And I am handling the signal as follows:
void sigtstpHandler(int signum)
{
signum++;//Just to remove gcc's warning
pid_t pid = findForegroundProcessID();
if(pid > -1){
kill(-pid, SIGTSTP);//Sending to the whole group
}
}
What happens is that when I press Ctrl+Z, the child process does get suspended indeed(using ps -all to view the state of the processes) but my shell hangs at waitpid it never returns even though I passed WUNTRACED flag which as far as I understood is supposed to make waitpid return when the process is stopped too.
So what could I have possible done wrong? or did I understand waitpid's behavior incorrectly?
Notes:
-findForegroundProcessID() returns the right pid; I double checked that.
-I am changing each process's group when right after I fork
-Handling Ctrl+C is working just fine
-If I use another terminal to send SIGCONT after my shell hangs, the child process resumes its work and the shell reaps it eventually.
-I am catching SIGTSTP which as far as I read(and tested) can be caught.
-I tried using waitid instead of waitpid just in case, problem persisted.
EDIT:
void sigchldHandler(int signum)
{
signum++;//Just to remove the warning
pid_t pid;
while((pid = waitpid(-1, &processReturnStatus, 0)) > 0){
removeFromJobList(pid);
}
if(errno != ECHILD)
unixError("kill error");
}
My SIGCHLD handler.
SIGCHLD is delivered for stopped children. The waitpid() call in the signal handler - which doesn't specify WUNTRACED - blocks forever.
You should probably not have the removeFromJobList() processing in two different places. If I had to guess, it sounds like it touches global data structures, and doesn't belong in a signal handler.
Waitpid is not returning because you are not not setting a sigchld handler (which I sent you earlier). You have child processess that are not getting reaped. Furthermore, waitpid needs to be in a while loop, not an if (also sent you that).
The only signal you are supposed to catch is SIGCHLD. The reason being is that if your processes are forked properly, the kernel will send that signal to the foreground process and it will terminate it or stop it or do whatever the signal is properly.
When process groups are not set correctly, signals will get sent to the wrong process. One way to test that is by running a foreground process and hitting Ctrl-Z. If your entire shell exists, then Ctrl-Z signal is getting sent to the entire shell. This means you did not set the new process in a new process group and gave it a terminal.
Now here's what you need to do if your Ctrl-Z signal is stopping your entire shell. Once you fork a process, in the child process:
- Set the process in its own group using setpgid.
- Give it a sane terminal by blocking SIGTTOU and then giving it the terminal using tcsetpgrp.
In the parent:
- Also set its child process using setpgid. This is because you have no idea if the child or the parent will execute first, so this avoids a race condition. It doesn't hurt to set it twice.

Resources