lkml.org 
[lkml]   [2007]   [Sep]   [22]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
/
From
SubjectRe: Should parent's WIFSIGNALED(siginfo->si_status) be true EVEN IF the SIGNAL was caught by the child?
Date
On Saturday 22 September 2007 11:49:09 Michael Kerrisk wrote:
> John,
>

...snip...

>
> If the child terminated by calling exit(), regardless of whether it
> was done from inside a signal handler, then WIFEXITED() should test
> true, but WIFSIGNALED() will not. If you are seeing otherwise, then
> show a *short* program that demonstrates the behavior. (But it seems
> unlikely that there would be a kernel bug on this point, so do check
> your program carefully!)

Attached is a (somewhat) short program that demonstates the behavior. I
simply compile it with 'make sigtest'.

My observed behavior is:

$ ./sigtest
sigtest started
child1 started
child2 started
selecting...
sigCaught: 3366 receieved signal 15
sigtest 3366 exiting
sigChld: 3365 receieved signal 17
sigChld: 3365 child 3366 WIFEXITED with childStat 15
sigChld: 3365 child 3366 WIFSIGNALED with si_status 15
select error: Interrupted system call
selecting...
sigCaught: 3367 receieved signal 15
sigtest 3367 exiting
sigChld: 3365 receieved signal 17
sigChld: 3365 child 3367 WIFEXITED with childStat 15
sigChld: 3365 child 3367 WIFSIGNALED with si_status 15
select error: Interrupted system call
selecting...
sigCaught: 3365 receieved signal 15
sigtest 3365 exiting
$

To get this output, I ran, from another shell, the following sequence:

$ ps -eaf | grep sigtest
zoltan 3365 2307 0 13:04 pts/0 00:00:00 ./sigtest
zoltan 3366 3365 98 13:04 pts/0 00:00:06 ./sigtest
zoltan 3367 3365 98 13:04 pts/0 00:00:06 ./sigtest
$ kill -SIGTERM 3366
$ kill -SIGTERM 3367
$ kill -SIGTERM 3365

That's it. What I find odd is that the wait_pid() status and the
si_status are the same for both a WIFEXITED and a WIFSIGNALED, which
should be impossible, if I read the documentation right.

Thanks for your responses,
John




#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <dirent.h>
#include <errno.h>
#include <time.h>
#include <pwd.h>
#include <signal.h>
#include <syslog.h>

#include <netinet/in.h>

#include <sys/types.h>
#include <sys/inotify.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/wait.h>

#define SER_PORT_ID 1234 /* The server's port */

static int child; /* child = 0, parent != 0 */

static void child1(void);
static void child2(void);
static void sigChld(int signum, siginfo_t * siginfo, void * ucontext);
static void sigCaught(int signum, siginfo_t * siginfo, void * ucontext);
static void setupEnv(void);
static void serverLoop(void);
static void processConnect(int csfd);
static void sigtestExit();

/**
* main:
* @argc: The count of command line data
* @argv: The array of command line strings
*
* The entry point of the executable.
*/
int
main(int argc, char * argv[])
{
setupEnv();

/*
* spawn two child processes so they (the children) can be sent SIGNAL's
* and the behaviour can be observed...
*/

if ((child = fork()) < 0) /* fork error */
{
perror("Child1 could not be created");
exit(1);
}
else
if (child == 0) /* In child */
{
child1(); /* never returns... */
}

if ((child = fork()) < 0) /* fork error */
{
perror("Monitor child could not be created");
exit(1);
}
else
if (child == 0) /* In child */
{
child2(); /* never returns... */
}

serverLoop(); /* Never returns */

return 0;

}

/**
* child1:
*
* This is the first child.
*/
static void
child1(void)
{
printf("child1 started\n");

for ( ; ; );
}

/**
* child2:
*
* This is the second child.
*/
static void
child2(void)
{
printf("child2 started\n");

for ( ; ; );
}

/**
* sigChild:
* @signum: The signal that got us here.
* @siginfo: Additional data associated with the signal.
* @ucontext: Pointer to ucontext_t struct. Unused here.
*
* Signal handler for catching SIGCHLD. Is re-entrant...
*/
static void
sigChld(int signum, siginfo_t * siginfo, void * ucontext)
{
int childStat;

printf("sigChld: %d receieved signal %d\n", getpid(), signum);

if (signum != SIGCHLD) /* technically can't happen here... */
return; /* but ignore it if it does */

waitpid(siginfo->si_pid, &childStat, 0); /* orderly child shutdown... */

/*
* These next 4 if blocks are where it gets interesting...this I believe
* demonstrates the inconsistency.
*/

if (WIFEXITED(childStat))
printf("sigChld: %d child %d WIFEXITED with childStat %d\n",
getpid(), siginfo->si_pid, WEXITSTATUS(childStat));

if (WIFEXITED(siginfo->si_status))
printf("sigChld: %d child %d WIFEXITED with si_status %d\n",
getpid(), siginfo->si_pid, WEXITSTATUS(siginfo->si_status));

if (WIFSIGNALED(childStat))
{
printf("sigChld: %d child %d WIFSIGNALED with childStat %d\n",
getpid(), siginfo->si_pid, WTERMSIG(childStat));
}

if (WIFSIGNALED(siginfo->si_status))
{
printf("sigChld: %d child %d WIFSIGNALED with si_status %d\n",
getpid(), siginfo->si_pid, WTERMSIG(siginfo->si_status));
}

return;
}

/**
* sigCaught:
* @signum: The signal that got us here.
* @siginfo: Additional data associated with the signal.
* @ucontext: Pointer to ucontext_t struct. Unused here.
*
* Signal handler for catching all but SIGCHLD. Is re-entrant...
*/
static void
sigCaught(int signum, siginfo_t * siginfo, void * ucontext)
{
int childStat;

printf("sigCaught: %d receieved signal %d\n", getpid(), signum);

if (signum == SIGCHLD) /* technically can't happen here... */
return; /* but ignore it if it does */

exit(signum);
}

/**
* setupEnv:
*
* Setup the environment, i.e, signal handlers, etc.
*/
static void
setupEnv(void)
{
struct sigaction action;

printf("sigtest started\n");

atexit(sigtestExit);

action.sa_handler = NULL;
action.sa_sigaction = sigChld;
sigemptyset(&action.sa_mask);

/*
* Since sigChld is re-entrant, its okay to have SA_NODEFER, and necessary,
* too, since I want to waitpid() on ALL child processes, and since
* waitpid() won't block if called after the child croaked, its just simply
* a way to affect an orderly shutdown of child processes, avoiding the
* zombie state...of course, I could be wrong, and without SA_NODEFER, the
* SIGCHLD is only _temporarily_ blocked, then I'll get subsequent SIGCHLDs
* after this handler completes...either way, in this case its irrelevant.
*/

action.sa_flags = SA_SIGINFO | SA_NOCLDSTOP | SA_RESTART | SA_NODEFER;

sigaction(SIGCHLD, &action, NULL);

/*
* Now catch the rest of the signals...
*/

action.sa_handler = NULL;
action.sa_sigaction = sigCaught;
sigfillset(&action.sa_mask);
action.sa_flags = SA_SIGINFO;

sigaction(SIGINT, &action, NULL);
sigaction(SIGHUP, &action, NULL);
sigaction(SIGTERM, &action, NULL);
sigaction(SIGQUIT, &action, NULL);
sigaction(SIGTSTP, &action, NULL);
sigaction(SIGSTOP, &action, NULL);

return;
}

/**
* serverLoop:
*
* The main loop for listening for and handling connections.
*/
static void
serverLoop(void)
{
fd_set read_fs;
int sfd, csfd, n, from_len, saved_errno;
struct sockaddr_in sin;



FD_ZERO(&read_fs); /* Clear the file descriptors */

if ((sfd = socket(AF_INET,SOCK_STREAM,0)) < 0) /* Create the socket */
{
perror("Socket creation failed");
exit(1);
}

n = 1;

if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)) < 0)
{
perror("setsockopt failed");
exit(1);
}

bzero((caddr_t)&sin,sizeof(struct sockaddr_in));/* Zero out the sock_addr */

sin.sin_family = AF_INET; /* Internet protocol */
sin.sin_addr.s_addr = INADDR_ANY; /* bind all local interfaces */
sin.sin_port = htons(SER_PORT_ID); /* Receive on the server port */

if (bind(sfd, (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) < 0) /* Bind socket to port */
{
perror("Socket binding failed");
exit(1);
}

if (listen(sfd, 10) < 0) /* max. 10 q'd connections */
{
perror("Listen failed");
exit(1);
}

for ( ; ; ) /* main server loop */
{
FD_SET(sfd,&read_fs);

do
{
printf("selecting...\n");

n = select(sfd+1,&read_fs,NULL,NULL,NULL);

if (n < 0)
{
saved_errno = errno;
perror("select error");

if (saved_errno != EINTR)
{
perror("Server select error");
exit(1);
}
}
else
break; /* no error, so handle it... */
} while (saved_errno == EINTR); /* restart select on EINTR */

if (FD_ISSET(sfd, &read_fs))
{
from_len = sizeof(struct sockaddr_in);

printf("accepting...\n");

if ((csfd = accept(sfd, (struct sockaddr *)&sin,
(socklen_t *)&from_len)) < 0)
{
perror("Accept error");
exit(1);
}

if ((child = fork()) < 0) /* fork error */
{
perror("Child could not be created");
exit(1);
}
else
if (child == 0) /* In child */
{
close(sfd); /* Child doesn't need this */
processConnect(csfd); /* never returns... */
}

close(csfd); /* Parent doesn't need this one */
} /* if (FD_ISSET(sfd,&read_fs)) */
} /* for ( ; ; ) */

exit(99); /* Never gets here... */
}

/**
* processConnect:
* @csfd: The socket for this connection
*
* This is the child process dedicated to this connection.
*/
static void
processConnect(int csfd)
{
uint8_t * buf;
int n = 0;

if (!(buf = calloc((size_t)1024, sizeof(uint8_t))))
{
perror("calloc failed");
exit(1);
}

/*
* This means we're in the child, and have an active connection...
*/

printf("\nReading csfd %d...\n", csfd);

if ((n = read(csfd, buf, sizeof(buf))) < 0)
{
perror("Server read error");
exit(1);
}

if (n == 0)
{
printf("Connection closed...exiting\n");
exit(2);
}

printf("Read 0x%x bytes...\n", n);

buf[n] = 0;
printf("%s", buf);

// would do something with the data here, but this is just for testing...

close(csfd);
exit(0); /* child exits... */
}

/**
* sigtestExit:
*
* Since master server is exiting, everyone must be killed...
*/
static void
sigtestExit()
{
sigset_t sigset;

printf("sigtest %d exiting\n", getpid());

if (child) /* if the main server is exiting... */
{
sigfillset(&sigset);
sigprocmask(SIG_BLOCK,&sigset,NULL); /* block all sigs... */
kill(0,SIGTERM); /* everybody in this proc. group */
}
}
\
 
 \ /
  Last update: 2007-09-22 22:21    [W:0.571 / U:0.212 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site