The Pedigree Project 0.1
signal-exec-test/signals.c
1#define _GNU_SOURCE
2
3#include <errno.h>
4#include <fcntl.h>
5#include <poll.h>
6#include <pthread.h>
7#include <signal.h>
8#include <stdio.h>
9#include <string.h>
10#include <time.h>
11#include <unistd.h>
12
13#include "contracts.h"
14#include <sys/epoll.h>
15#include <sys/select.h>
16#include <sys/syscall.h>
17#include <sys/wait.h>
18
19static volatile int signal_calls;
20static volatile sig_atomic_t reset_observed;
21
22static void signal_handler(int number) {
23 const int saved_errno = errno;
24 if (number == SIGUSR1) {
25 __atomic_add_fetch(&signal_calls, 1, __ATOMIC_RELEASE);
26 }
27 errno = saved_errno;
28}
29
30static void reset_handler(int number) {
31 const int saved_errno = errno;
32 struct sigaction current;
33 reset_observed =
34 number == SIGUSR1 && !sigaction(SIGUSR1, 0, &current) && current.sa_handler == SIG_DFL;
35 errno = saved_errno;
36}
37
38static int install_handler(int flags) {
39 struct sigaction action = {.sa_handler = signal_handler, .sa_flags = flags};
40 signal_calls = 0;
41 return sigemptyset(&action.sa_mask) || sigaction(SIGUSR1, &action, 0);
42}
43
44enum operation {
45 read_operation,
46 write_operation,
47 wait_operation,
48 sleep_operation,
49 futex_operation
50};
51
52enum { futex_wait = 0, futex_wake = 1, futex_private = 128 };
53
55 enum operation operation;
56 int descriptor;
57 pid_t child;
58 char* bytes;
59 size_t length;
60 volatile int entered;
61 volatile int returned;
62 ssize_t result;
63 int error;
64 int status;
65 int futex_word;
66 struct timespec remaining;
67};
68
69static void* run_blocking_call(void* argument) {
70 struct blocking_call* call = argument;
71 const struct timespec delay = {.tv_sec = 3};
72 errno = 0;
73 __atomic_store_n(&call->entered, 1, __ATOMIC_RELEASE);
74 switch (call->operation) {
75 case read_operation:
76 call->result = read(call->descriptor, call->bytes, call->length);
77 break;
78 case write_operation:
79 call->result = write(call->descriptor, call->bytes, call->length);
80 break;
81 case wait_operation:
82 call->result = waitpid(call->child, &call->status, 0);
83 break;
84 case sleep_operation:
85 call->result = nanosleep(&delay, &call->remaining);
86 break;
87 case futex_operation:
88 call->result = syscall(SYS_futex, &call->futex_word, futex_wait | futex_private, 0, 0, 0, 0);
89 break;
90 }
91 call->error = errno;
92 __atomic_store_n(&call->returned, 1, __ATOMIC_RELEASE);
93 return 0;
94}
95
96static int interrupt_call(pthread_t worker, struct blocking_call* call, int restart) {
97 if (test_wait_flag(&call->entered)) {
98 return -1;
99 }
100 // Multiple acknowledged deliveries cover the interval between the ready
101 // publication and actual syscall entry without relying on one timed signal.
102 for (int round = 0; round < 8; ++round) {
103 test_pause();
104 if (__atomic_load_n(&call->returned, __ATOMIC_ACQUIRE)) {
105 return restart ? -1 : 0;
106 }
107 const int previous = __atomic_load_n(&signal_calls, __ATOMIC_ACQUIRE);
108 if (pthread_kill(worker, SIGUSR1)) {
109 return -1;
110 }
111 const long long start = test_milliseconds();
112 while (__atomic_load_n(&signal_calls, __ATOMIC_ACQUIRE) == previous) {
113 if (__atomic_load_n(&call->returned, __ATOMIC_ACQUIRE)) {
114 return restart ? -1 : 0;
115 }
116 if (start < 0 || test_milliseconds() - start >= 1000) {
117 return -1;
118 }
119 test_pause();
120 }
121 }
122 test_pause();
123 return restart ? (__atomic_load_n(&call->returned, __ATOMIC_ACQUIRE) ? -1 : 0)
124 : test_wait_flag(&call->returned);
125}
126
127static int read_contract(int restart) {
128 int descriptors[2];
129 char bytes[2] = {0};
130 if (install_handler(restart ? SA_RESTART : 0) || pipe(descriptors)) {
131 return 10;
132 }
133 struct blocking_call call = {.operation = read_operation,
134 .descriptor = descriptors[0],
135 .bytes = bytes,
136 .length = sizeof(bytes)};
137 pthread_t worker;
138 if (pthread_create(&worker, 0, run_blocking_call, &call) ||
139 interrupt_call(worker, &call, restart)) {
140 _exit(11);
141 }
142 if (write(descriptors[1], "xy", 2) != 2 || test_wait_flag(&call.returned) ||
143 pthread_join(worker, 0)) {
144 _exit(12);
145 }
146 if (restart) {
147 if (call.result != 2 || memcmp(bytes, "xy", 2)) {
148 return 13;
149 }
150 } else if (call.result != -1 || call.error != EINTR || bytes[0] || bytes[1] ||
151 read(descriptors[0], bytes, sizeof(bytes)) != 2 || memcmp(bytes, "xy", 2)) {
152 return 14;
153 }
154 if (fcntl(descriptors[0], F_SETFL, O_NONBLOCK)) {
155 return 15;
156 }
157 errno = 0;
158 if (read(descriptors[0], bytes, sizeof(bytes)) != -1 || errno != EAGAIN) {
159 return 16;
160 }
161 return close(descriptors[0]) || close(descriptors[1]) ? 17 : 0;
162}
163
164static int wait_contract(int restart) {
165 int gate[2];
166 if (install_handler(restart ? SA_RESTART : 0) || pipe(gate)) {
167 return 20;
168 }
169 const pid_t child = fork();
170 if (child < 0) {
171 return 21;
172 }
173 if (!child) {
174 alarm(8);
175 char token;
176 close(gate[1]);
177 _exit(read(gate[0], &token, 1) == 1 && token == 'x' ? 42 : 43);
178 }
179 struct blocking_call call = {.operation = wait_operation, .child = child};
180 pthread_t worker;
181 int result = 0;
182 if (pthread_create(&worker, 0, run_blocking_call, &call) ||
183 interrupt_call(worker, &call, restart)) {
184 result = 22;
185 } else if (write(gate[1], "x", 1) != 1 || test_wait_flag(&call.returned) ||
186 pthread_join(worker, 0)) {
187 result = 23;
188 } else if (restart) {
189 if (call.result != child || !WIFEXITED(call.status) || WEXITSTATUS(call.status) != 42) {
190 result = 24;
191 }
192 } else {
193 int status;
194 if (call.result != -1 || call.error != EINTR || waitpid(child, &status, 0) != child ||
195 !WIFEXITED(status) || WEXITSTATUS(status) != 42) {
196 result = 25;
197 }
198 }
199 if (result) {
200 (void)kill(child, SIGKILL);
201 (void)waitpid(child, 0, 0);
202 _exit(result);
203 }
204 close(gate[0]);
205 close(gate[1]);
206 return result;
207}
208
209static int partial_write_contract(void) {
210 enum { chunk = 4096, maximum_fill = 1024 * 1024 };
211 char bytes[2 * chunk];
212 memset(bytes, 'q', sizeof(bytes));
213 int descriptors[2];
214 if (install_handler(SA_RESTART) || pipe(descriptors) ||
215 fcntl(descriptors[1], F_SETFL, O_NONBLOCK)) {
216 return 30;
217 }
218 size_t filled = 0;
219 while (filled < maximum_fill) {
220 const ssize_t written = write(descriptors[1], bytes, chunk);
221 if (written == -1 && errno == EAGAIN) {
222 break;
223 }
224 if (written <= 0) {
225 return 31;
226 }
227 filled += written;
228 }
229 if (filled < chunk || filled == maximum_fill || read(descriptors[0], bytes, chunk) != chunk ||
230 fcntl(descriptors[1], F_SETFL, 0)) {
231 return 32;
232 }
233 memset(bytes, 'x', chunk);
234 memset(bytes + chunk, 'y', chunk);
235 struct blocking_call call = {.operation = write_operation,
236 .descriptor = descriptors[1],
237 .bytes = bytes,
238 .length = sizeof(bytes)};
239 pthread_t worker;
240 if (pthread_create(&worker, 0, run_blocking_call, &call) || interrupt_call(worker, &call, 0) ||
241 pthread_join(worker, 0) || call.result != chunk || !signal_calls) {
242 fprintf(stderr, "partial write: result=%ld errno=%d\n", (long)call.result, call.error);
243 _exit(33);
244 }
245 if (close(descriptors[1])) {
246 return 34;
247 }
248 size_t received = 0;
249 ssize_t count;
250 while ((count = read(descriptors[0], bytes, sizeof(bytes))) > 0) {
251 for (ssize_t i = 0; i < count; ++i, ++received) {
252 const char expected = received < filled - chunk ? 'q' : 'x';
253 if (received >= filled || bytes[i] != expected) {
254 return 35;
255 }
256 }
257 }
258 return count || received != filled || close(descriptors[0]) ? 36 : 0;
259}
260
261static int sleep_contract(void) {
262 if (install_handler(SA_RESTART)) {
263 return 40;
264 }
265 struct blocking_call call = {.operation = sleep_operation};
266 pthread_t worker;
267 if (pthread_create(&worker, 0, run_blocking_call, &call) || interrupt_call(worker, &call, 0) ||
268 pthread_join(worker, 0)) {
269 _exit(41);
270 }
271 return call.result != -1 || call.error != EINTR || call.remaining.tv_sec < 0 ||
272 call.remaining.tv_sec > 3 || call.remaining.tv_nsec < 0 ||
273 call.remaining.tv_nsec >= 1000000000 ||
274 (!call.remaining.tv_sec && !call.remaining.tv_nsec)
275 ? 42
276 : 0;
277}
278
279static int futex_contract(int restart) {
280 if (install_handler(restart ? SA_RESTART : 0)) {
281 return 43;
282 }
283 struct blocking_call call = {.operation = futex_operation};
284 pthread_t worker;
285 if (pthread_create(&worker, 0, run_blocking_call, &call) ||
286 interrupt_call(worker, &call, restart)) {
287 _exit(44);
288 }
289 if (restart) {
290 __atomic_store_n(&call.futex_word, 1, __ATOMIC_RELEASE);
291 if (syscall(SYS_futex, &call.futex_word, futex_wake | futex_private, 1, 0, 0, 0) < 0) {
292 _exit(45);
293 }
294 }
295 if (test_wait_flag(&call.returned) || pthread_join(worker, 0)) {
296 _exit(46);
297 }
298 // A restarted wait may observe the changed word before it rejoins the queue.
299 return restart ? (call.result != 0 && !(call.result == -1 && call.error == EAGAIN) ? 47 : 0)
300 : (call.result != -1 || call.error != EINTR ? 48 : 0);
301}
302
303static int masked_wait_contract(const char* name) {
304 sigset_t blocked, original, temporary, after;
305 if (install_handler(SA_RESTART) || sigemptyset(&blocked) || sigaddset(&blocked, SIGUSR1) ||
306 pthread_sigmask(SIG_BLOCK, &blocked, &original)) {
307 return 50;
308 }
309 temporary = original;
310 if (sigdelset(&temporary, SIGUSR1) || raise(SIGUSR1) || signal_calls) {
311 return 51;
312 }
313 const struct timespec timeout = {.tv_sec = 2};
314 int result, saved_errno, descriptor = -1;
315 errno = 0;
316 if (!strcmp(name, "ppoll-eintr")) {
317 result = ppoll(0, 0, &timeout, &temporary);
318 } else if (!strcmp(name, "pselect-eintr")) {
319 result = pselect(0, 0, 0, 0, &timeout, &temporary);
320 } else if (!strcmp(name, "sigsuspend-eintr")) {
321 result = sigsuspend(&temporary);
322 } else {
323 descriptor = epoll_create1(EPOLL_CLOEXEC);
324 if (descriptor < 0) {
325 return 52;
326 }
327 struct epoll_event event;
328 result = epoll_pwait(descriptor, &event, 1, 2000, &temporary);
329 }
330 saved_errno = errno;
331 if (result != -1 || saved_errno != EINTR || signal_calls != 1 ||
332 pthread_sigmask(SIG_SETMASK, 0, &after) || sigismember(&after, SIGUSR1) != 1 ||
333 pthread_sigmask(SIG_SETMASK, &original, 0)) {
334 return 53;
335 }
336 return descriptor >= 0 && close(descriptor) ? 54 : 0;
337}
338
339static int reset_contract(void) {
340 struct sigaction action = {.sa_handler = reset_handler, .sa_flags = SA_RESETHAND};
341 struct sigaction after;
342 if (sigemptyset(&action.sa_mask) || sigaction(SIGUSR1, &action, 0) || raise(SIGUSR1) ||
343 !reset_observed || sigaction(SIGUSR1, 0, &after) || after.sa_handler != SIG_DFL) {
344 return 60;
345 }
346 return 0;
347}
348
349int signal_contract(const char* name) {
350 if (!strncmp(name, "read-", 5)) {
351 return read_contract(!strcmp(name, "read-restart"));
352 }
353 if (!strncmp(name, "wait-", 5)) {
354 return wait_contract(!strcmp(name, "wait-restart"));
355 }
356 if (!strcmp(name, "partial-write")) {
357 return partial_write_contract();
358 }
359 if (!strcmp(name, "nanosleep-eintr")) {
360 return sleep_contract();
361 }
362 if (!strncmp(name, "futex-", 6)) {
363 return futex_contract(!strcmp(name, "futex-restart"));
364 }
365 if (!strcmp(name, "reset-hand")) {
366 return reset_contract();
367 }
368 return masked_wait_contract(name);
369}