aboutsummaryrefslogtreecommitdiff
path: root/none/tests/async-sigs.c
blob: f05eb6e4ebda2c2dee867950a424732c63e72f73 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// 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 <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>

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;
}