SIGINT only received on child process if explicitly caught - c

I have the following test C program with UNIX system calls:
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void abide(int sig_num) {
printf("I, sleeper, will abide to this signal %d!\n", sig_num);
fflush(stdout);
exit(0);
}
void refuse(int sig_num) {
signal(SIGINT, refuse);
printf("I, sleeper, REFUSE this signal %d!\n", sig_num);
fflush(stdout);
}
int main(int argc, char *argv[]) {
if (argc > 1 && strcmp(argv[1], "refuse") == 0) {
signal(SIGINT, refuse);
} else if (argc > 1 && strcmp(argv[1], "deaf") == 0) {
printf("I, sleeper, have been made deaf...\n");
} else {
signal(SIGINT, abide);
}
printf("I, sleeper, am now sleeping for 10s...\n");
sleep(10);
printf("I, sleeper, has terminated normally.\n");
return 0;
}
Then I have another program which acts as a little shell. At my testing point, it forks and makes the child program execute the program above (with appropriate arguments). This shell is also ignoring Ctrl+C commands by use of
signal(SIGINT, SIG_IGN);
The results are the following:
MyShell> ./sleeper
I, sleeper, am now sleeping for 10s...
^CI, sleeper, will abide to this signal!
MyShell> ./sleeper refuse
I, sleeper, am now sleeping for 10s...
^CI, sleeper, REFUSE this signal!
I, sleeper, has terminated normally.
MyShell> ./sleeper deaf
I, sleeper, have been made deaf...
I, sleeper, am now sleeping for 10s...
^C^C^C^C <---- not terminating
The first run seems correct. The second one is a bit strange as we are effectively ignoring the signal, but the program terminates anyway. Maybe it's because we're calling sleep() which is interrupted.
But it's the third result that confuses me. In a regular shell the program terminates, but in my custom shell nothing happens. It keeps running. Shouldn't the sleeper program's default signal handler (which terminates it also) execute just as abide() does?
Thanks for any clarification!

Solved it. The problem was a bit subtle. After using fork(), child processes apparently inherit their parents signal handlers, even if you use the exec() system calls afterwards. So the child process for sleeper was using the ignore handler. The solution was simply to add the default handler
signal(SIGINT, SIG_DFL)
between the calls to fork() and exec().

Related

parent shell not getting SIGINT when child has a handler

When you press Ctrl-C, foreground processes receive SIGINT:
$ bash -c 'sleep 100; echo program died'
^C
$ echo $?
130
However, if a program installs a SIGINT handler, parent program doesn't receive the signal. Why?
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
void sig_int(int sig_num)
{
exit(1);
}
static struct sigaction sigact = { .sa_handler=sig_int };
int main(int argc, char *argv[])
{
sigaction(SIGINT,&sigact,NULL);
sleep(100);
return 0;
}
bash didn't die:
$ bash -c './a.out; echo program died'
^Cprogram died
Related to Bash not trapping interrupts during rsync/subshell exec statements , but all answers there are workarounds.
The shell ignore SIGINT if not sent directly from the terminal
This long post explain what is happening in details. Here I'll try to summarise the most important concepts and propose a working solution.
It turns out that the shell is programmed to ignore the SIGINT if it is not directly sent from the terminal (by hitting CTRL-C). If a subprocess intercepts it then it must exit by explicitly killing itself with SIGINT, quoting the post:
"If you don't catch SIGINT, the system automatically does the right
thing for you: Your program exits and the calling program gets the
right "I-exited-on-SIGINT" status after waiting for your exit.
But once you catch SIGINT, you have to take care of the proper way to
exit after whatever cleanup you do in your SIGINT handler.
Decide whether the SIGINT is used for exit/abort purposes and hence a
shellscript calling this program should discontinue. This is hopefully
obvious. If you just need to do some cleanup on SIGINT, but then exit
immediately, the answer is "yes".
If so, you have to tell the calling program about it by exiting with
the "I-exited-on-SIGINT" status.
There is no other way of doing this than to kill yourself with a
SIGINT signal. Do it by resetting the SIGINT handler to SIG_DFL, then
send yourself the signal.
void sigint_handler(int sig)
{
[do some cleanup]
signal(SIGINT, SIG_DFL);
kill(getpid(), SIGINT);
}
SIGINT Handler
Here is a working version of the handler that intercepts the signal and correctly kill itself (and thus it doesn't print 'program died').
OTOH, If you send a different signal the handler run the exit function and you will see again 'program died' printed on the screen.
void sig_int(int sig_num)
{
if (sig_num == SIGINT) {
printf("received SIGINT\n");
signal(SIGINT, SIG_DFL);
kill(getpid(), SIGINT);
} else {
exit(1);
}
}
static struct sigaction sigact = { .sa_handler=sig_int };
int main(int argc, char *argv[])
{
sigaction(SIGINT,&sigact,NULL);
printf("go to sleep\n");
sleep(3);
printf("awaken\n");
return 0;
}

Why signal handling is malfunctioning?

I have a signal handling snippet but it is somehow malfunctioning on my Mac and virtual Linux box at koding.com but on my office Linux PC it is working..Can someone please tell me why..
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void my_isr(int n){
printf("Hello World");
signal(SIGINT, SIG_DFL);
}
int main(){
signal(SIGINT, my_isr);
printf("pid = %d\n", getpid());
while(1);
return 0;
}
When I am pressing Ctrl+C it is not printing Hello World on the first time but it is re-modifying the SIGINT signal action & hence it is exiting the program when I press Ctrl+C second time. Can someone explain me why?
You are not allowed to call every function in a signal handler.
Read signal(7). Only async signal safe functions can be called (directly or indirectly) from a signal handler, and printf is not such a function. If you really want to reliably "print" something from inside a signal handler (which I don't recommend), you can only use the low-level write(2) syscall (it is async signal safe).
So you've got undefined behavior. This explains why it is so bad.
The recommended way is to set a volatile sigatomic_t flag in your signal handler, and to test it outside of it (e.g. in your while loop...).
And you forgot to call fflush(3). You might be more lucky by ending your printf format string with \n since stdout is line-buffered!
Of course, changing your printf inside your signal handler is still UB, even with a \n, but very often it would appear to work.
Here is a conforming version of your program....
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
volatile sig_atomic_t got_signal;
void my_sigint_handler (int signum) {
if (signum == SIGINT) // this is always true!
got_signal = 1;
#define INTERRUPT_MESSAGE "Interrupted!\n"
write(STDOUT_FILENO, INTERRUPT_MESSAGE, strlen(INTERRUPT_MESSAGE));
};
int main(int argc, char**argv) {
struct sigaction act_int;
memset (&act_int, 0, sizeof(act_int));
act_int.sa_handler = my_sigint_handler;
if (sigaction(SIGINT, &act_int, NULL)) {
perror("sigaction"); exit(EXIT_FAILURE);
};
printf ("start %s pid %d\n", argv[0], (int)getpid());
while (!got_signal) {
};
printf ("ended %s after signal\n", argv[0]);
return 0;
}
A useful (and permissible) trick could be to write(2) a single byte -inside your signal handler- on a pipe(7) to self (you set up that pipe using pipe(2) early at program initialization), and in your event loop poll(2) the read end of that pipe.
printf is the culprit just use counter in handler and print outside handler its value it will work.
use sigaction instead of signal

How to catch SIGINT and ignore it in the child process?

I have a main that runs program from the command line arguments. The command line program is forked and run in the child process. When SIGINT is sent, I want to catch it and ask the user to confirm that he/she want to quit. If yes, both parent and child end, else child keeps running.
My problem is that I can't get the child to start running back up, when user says no.
I have tried SIGSTOP & SIGCONT but these actually just cause the processes to stop.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
extern char **environ;
void sigint_handler(int sig);
void sigint_chldhandler(int sig);
int main( int argc, char** argv)
{
int pid;
signal(SIGINT,sigint_handler);
if((pid=fork())==0)
{
printf("%d\n",pid);
execve(argv[1],argv,environ);
}
int status;
waitpid(pid,&status,0);
}
void sigint_handler(int sig)
{
printf("Do you want to quit?Yes/No:\n");
char buf[4];
fgets(buf, sizeof(char)*4, stdin);
printf("child pid:%d\n",getpid());
printf("parent pid:%d\n",getppid());
if(strcmp(buf,"Yes")==0)
{
kill(-getpid(),SIGKILL);
printf("Exiting!\n");
exit(0);
}
}
Unless you rig the child's signal handling, it will be terminated by the interrupt when the signal is sent, regardless of what happens in the parent. Therefore, you will need to be rather more sophisticated. I think you will need something along the lines of:
Parent process sets its SIGINT signal handler.
Parent forks.
Child process sets its SIGINT handling to SIG_IGN.
Child executes specified command.
Parent waits for SIGINT to arrive, probably while running waitpid().
When it arrives, it sends SIGSTOP to the child.
It asks the question and gets the response.
If the response is to continue, then it sends SIGCONT to the child and returns to its waiting mode.
If the response is to stop, then it sends first SIGCONT and then SIGTERM (or another signal other than SIGINT) to the child to kill it. (Using SIGKILL is not sensible; the child should be given a chance to exit in response to SIGTERM or SIGHUP. If the child doesn't take the death threat seriously, then you can send it SIGKILL.)
When the parent has established that the child has exited, it can exit in its own turn.
Note that if the child process is running something like vim, which alters the terminal settings dramatically, then sending it SIGKILL will leave the terminal in a cockeyed state. It is fiddly setting it back to a sane state; it is better to give the program a chance to reset the terminal settings in its own right.
SIGINT comes to parent process and to child process (to process group).
Parent process calls your handler.
Child processes this signal by default.
You can use this, for example:
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
int main()
{
pid_t pid;
char c;
switch(pid = fork())
{
case -1:
printf("!!!");
return -1;
break;
case 0:
printf("child started\n");
while(1) { };
break;
default:
while(1)
{
c = getchar();
if(c == 'q')
{
//your conditions
kill(pid, SIGKILL);
return 0;
}
}
break;
}
return 0;
}

calling ptrace inside a ptraced Linux process

Someone added to the Wikipedia "ptrace" article claiming that, on Linux, a ptraced process couldn't itself ptrace another process. I'm trying to determine if (and if so why) that's the case. Below is a simple program I contrived to test this. My program fails (the sub sub process doesn't run properly) but I'm pretty convinced it's my error and not something fundamental.
In essence the initial process A forks process B which in turn forks C. A ptraces its child B, B ptraces its child C. Once they're set up, all three processes are written to just print A,B, or C to stdout once every second.
In practice what happens is that A and B work fine, but C prints only once and then gets stuck. Checking with ps -eo pid,cmd,wchan shows C stuck in kernel function ptrace_stop while the rest are in hrtimer_nanosleep where I'd expect all three to be.
Very occasionally all three do work (so the program prints Cs as well as As and Bs), which leads me to believe there's some race condition in the initial setup.
My guesses as to what might be wrong are:
something to do with A seeing a SIGCHLD related to B seeing a SIGCHLD to do with a signal to C, and wait(2) reporting both as coming from B (but a hacky call of PTRACE_CONT to both pids doesn't fix things)?
C should be ptraced by B - has C inherited the ptrace by A instead (and B's call to ptrace neither errored nor overwrote this)?
Can anyone figure out what I'm doing wrong? Thanks.
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
static void a(){
while(1){
printf ("A\n");
fflush(stdout);
sleep(1);
}
}
static void b(){
while(1){
printf ("B\n");
fflush(stdout);
sleep(1);
}
}
static void c(){
while(1){
printf ("C\n");
fflush(stdout);
sleep(1);
}
}
static void sigchld_handler(int sig){
int result;
pid_t child_pid = wait(NULL); // find who send us this SIGCHLD
printf("SIGCHLD on %d\n", child_pid);
result=ptrace(PTRACE_CONT, child_pid, sig, NULL);
if(result) {
perror("continuing after SIGCHLD");
}
}
int main(int argc,
char **argv){
pid_t mychild_pid;
int result;
printf("pidA = %d\n", getpid());
signal(SIGCHLD, sigchld_handler);
mychild_pid = fork();
if (mychild_pid) {
printf("pidB = %d\n", mychild_pid);
result = ptrace(PTRACE_ATTACH, mychild_pid, NULL, NULL);
if(result==-1){
perror("outer ptrace");
}
a();
}
else {
mychild_pid = fork();
if (mychild_pid) {
printf("pidC = %d\n", mychild_pid);
result = ptrace(PTRACE_ATTACH, mychild_pid, NULL, NULL);
if(result==-1){
perror("inner ptrace");
}
b();
}
else {
c();
}
}
return 0;
}
You are indeed seeing a race condition. You can cause it to happen repeatably by putting sleep(1); immediately before the second fork() call.
The race condition is caused because process A is not correctly passing signals on to process B. That means that if process B starts tracing process C after process A has started tracing process B, process B never gets the SIGCHLD signal indicating that process C has stopped, so it can never continue it.
To fix the problem, you just need to fix your SIGCHLD handler:
static void sigchld_handler(int sig){
int result, status;
pid_t child_pid = wait(&status); // find who send us this SIGCHLD
printf("%d received SIGCHLD on %d\n", getpid(), child_pid);
if (WIFSTOPPED(status))
{
result=ptrace(PTRACE_CONT, child_pid, 0, WSTOPSIG(status));
if(result) {
perror("continuing after SIGCHLD");
}
}
}
It is "possible" to perform some ptrace functionalities on a child process that invokes ptrace itself. The real difficulty is that a tracer process becomes the parent of the tracee when attached to the latter. And if your tracer process wants to trace all behaviors from all (direct and indirect) child processes (i.e. like when a debugger program needs to debug a multi-threaded program), it naturally breaks the original process hierarchy, and all inter-process/inter-thread communications (i.e. thread synchronization, signal sending / receiving, ...) among all child processes needs to be emulated / multiplexed by the tracer process. It is still "possible", but much more difficult and inefficient.

Test cases in C for WIFSIGNALED, WIFSTOPPED, WIFCONTINUED

I'm playing with waitpid() and signal() and I'm looking for reliable test cases for returning WIFSIGNALED(status) = WIFSTOPPED(status) = WIFCONTINUED (status) = true but can't find any...
Care to tell me how can I make sure those return true so I can debug my code?
Also, a few hints about what signals should I catch with signal() to test those macros would be helpful...
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#define NELEMS(x) (sizeof (x) / sizeof (x)[0])
static void testsignaled(void) {
kill(getpid(), SIGINT);
}
static void teststopped(void) {
kill(getpid(), SIGSTOP);
}
static void testcontinued(void) {
kill(getpid(), SIGSTOP);
/* Busy-work to keep us from exiting before the parent waits.
* This is a race.
*/
alarm(1);
while(1) {}
}
int main(void) {
void (*test[])(void) = {testsignaled, teststopped, testcontinued};
pid_t pid[NELEMS(test)];
int i, status;
for(i = 0; i < sizeof test / sizeof test[0]; ++i) {
pid[i] = fork();
if(0 == pid[i]) {
test[i]();
return 0;
}
}
/* Pause to let the child processes to do their thing.
* This is a race.
*/
sleep(1);
/* Observe the stoppage of the third process and continue it. */
wait4(pid[2], &status, WUNTRACED, 0);
kill(pid[2], SIGCONT);
/* Wait for the child processes. */
for(i = 0; i < NELEMS(test); ++i) {
wait4(pid[i], &status, WCONTINUED | WUNTRACED, 0);
printf("%d%s%s%s\n", i, WIFCONTINUED(status) ? " CONTINUED" : "", WIFSIGNALED(status) ? " SIGNALED" : "", WIFSTOPPED(status) ? " STOPPED" : "");
}
return 0;
}
Handling WIFSIGNALED is easy. The child process can commit suicide with the kill() system call. You can also check for core dumps - some signals create them (SIGQUIT, IIRC); some signals do not (SIGINT).
Handling WIFSTOPPED may be harder. The simple step to try is for the child to send itself SIGSTOP with the kill() system call again. Actually, I think that should work. Note that you may want to check on SIGTTIN and SIGTTOU and SIGTSTOP - I believe they count for WIFSTOPPED. (There's also a chance that SIGSTOP only works sanely when sent by a debugger to a process it is running via the non-POSIX system call, ptrace().)
Handling WIFCONTINUED is something that I think the parent has to do; after you detect a process has been stopped, your calling code should make it continue by sending it a SIGCONT signal (kill() again). The child can't deliver this itself; it has been stopped. Again, I'm not sure whether there are extra wrinkles to worry about - probably.
A framework something like the below will allow you check the results of the wait() and waitpid() calls.
pid_t pid = fork();
if (pid == 0) {
/* child */
sleep(200);
}
else {
/* parent */
kill(pid, SIGSTOP);
/* do wait(), waitpid() stuff */
}
You do not actually have to catch the signals (using signal() or related function) that are sent. signal() installs a handler that overrides the default behavior for the specific signal - so if you want to check for a signal terminating your process, pick one that has that default behavior - "man -s7 signal" will give you details a signal's default behavior.
For the macros you have mentioned use SIGSTOP for WIFSTOPPED(status), SIGCONT for WIFCONTINUED (status) and SIGINT for WIFSIGNALED(status)
If you want more flexibility for testing, you could use kill (see "man kill") to send signals to your process. kill -l will list all the signals that can be sent.
in your tests you can fork() and send specific signal to your child processes? In this scenario your child processes are test cases?
EDIT
my answer is about coding a C test. you fork, get the pid of your child process (the process
with signal handlers installed), then you can send signal to it by using kill(2).
In this way you can test the exit status

Resources