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.041 / U:0.952 seconds]
    ©2003-2016 Jasper Spaans. hosted at Digital OceanAdvertise on this site