// This tests handling of signals sent from outside the process in the // following combinations: sync and async signals, caught and uncaught // signals, and while blocking or not blocking in a syscall. This exercises // various different paths in Valgrind's signal handling. // // It does this by installing signal handlers for one signal S, spawning // another process P, sending S from P multiple times (all caught), then // sending another signal from P (not caught). #include #include #include #include #include #include #include #include static const struct timespec bip = { 0, 1000000000 / 5 }; // 0.2 seconds. static void handler(int sig) { } static void install_handler(int sig, void (*sig_handler)(int)) { struct sigaction sa; sa.sa_handler = sig_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(sig, &sa, 0); } /* Kill our child, but use a separate kill command. This is so that it's running independently of Valgrind, and so is async with respect to thread scheduling. */ static void do_kill(int pid, int sig) { int status; int killer; int ret; killer = vfork(); if (killer == -1) { perror("killer/vfork"); exit(1); } // In the child, exec 'kill' in order to send the signal. if (killer == 0) { char sigbuf[20]; char pidbuf[20]; sprintf(sigbuf, "-%d", sig); sprintf(pidbuf, "%d", pid); execl("/bin/kill", "kill", sigbuf, pidbuf, (char *) NULL); perror("exec failed"); exit(1); } // In the parent, just wait for the child and then check it ran ok. do ret = waitpid(killer, &status, 0); while (ret == -1 && errno == EINTR); if (ret != killer) { perror("kill/waitpid"); exit(1); } if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { fprintf(stderr, "kill %d failed status=%s %d\n", killer, WIFEXITED(status) ? "exit" : "signal", WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status)); exit(1); } } static void test(int block, int caughtsig, int fatalsig) { int pid; int status; int i; fprintf(stderr, "testing: blocking=%d caught=%d fatal=%d... ", block, caughtsig, fatalsig); pid = fork(); if (pid == -1) { perror("fork"); exit(1); } // In the child, install the signal handler, then wait for the signal to // arrive: // - if 'block' is set, wait on a system call; // - otherwise, wait in client code (by spinning). // The alarm() calls is so that if something breaks, we don't get stuck. if (pid == 0) { install_handler(caughtsig, handler); alarm(10); for (;;) if (block) { pause(); } } // In the parent, send the signals. nanosleep(&bip, 0); // Wait for child to get going. for (i = 0; i < 5; i++) { do_kill(pid, caughtsig); // Should be caught. nanosleep(&bip, 0); do_kill(pid, caughtsig); // Ditto. do_kill(pid, caughtsig); // Ditto. } nanosleep(&bip, 0); do_kill(pid, fatalsig); // Should kill it. // Check that the child behaved as expected when it received the signals. if (waitpid(pid, &status, 0) != pid) { fprintf(stderr, "FAILED: waitpid failed: %s\n", strerror(errno)); } else if (!WIFSIGNALED(status) || WTERMSIG(status) != fatalsig) { fprintf(stderr, "FAILED: child exited with unexpected status %s %d\n", WIFEXITED(status) ? "exit" : "signal", WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status)); } else { fprintf(stderr, "PASSED\n"); } } int main() { /* Restore default behaviour of SIGHUP when forked from cron. */ install_handler(SIGHUP, SIG_DFL); test(/*non-blocked*/0, /* sync*/SIGSEGV, /* sync*/SIGBUS); test(/*non-blocked*/0, /* sync*/SIGSEGV, /*async*/SIGHUP); test(/*non-blocked*/0, /*async*/SIGUSR1, /* sync*/SIGBUS); test(/*non-blocked*/0, /*async*/SIGUSR1, /*async*/SIGHUP); test(/* blocked*/1, /* sync*/SIGSEGV, /* sync*/SIGBUS); test(/* blocked*/1, /* sync*/SIGSEGV, /*async*/SIGHUP); test(/* blocked*/1, /*async*/SIGUSR1, /* sync*/SIGBUS); test(/* blocked*/1, /*async*/SIGUSR1, /*async*/SIGHUP); return 0; }