15#include <sys/select.h>
16#include <sys/syscall.h>
19static volatile int signal_calls;
20static volatile sig_atomic_t reset_observed;
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);
30static void reset_handler(
int number) {
31 const int saved_errno = errno;
32 struct sigaction current;
34 number == SIGUSR1 && !sigaction(SIGUSR1, 0, ¤t) && current.sa_handler == SIG_DFL;
38static int install_handler(
int flags) {
39 struct sigaction action = {.sa_handler = signal_handler, .sa_flags = flags};
41 return sigemptyset(&action.sa_mask) || sigaction(SIGUSR1, &action, 0);
52enum { futex_wait = 0, futex_wake = 1, futex_private = 128 };
55 enum operation operation;
61 volatile int returned;
66 struct timespec remaining;
69static void* run_blocking_call(
void* argument) {
71 const struct timespec delay = {.tv_sec = 3};
73 __atomic_store_n(&call->entered, 1, __ATOMIC_RELEASE);
74 switch (call->operation) {
76 call->result = read(call->descriptor, call->bytes, call->length);
79 call->result = write(call->descriptor, call->bytes, call->length);
82 call->result = waitpid(call->child, &call->status, 0);
85 call->result = nanosleep(&delay, &call->remaining);
88 call->result = syscall(SYS_futex, &call->futex_word, futex_wait | futex_private, 0, 0, 0, 0);
92 __atomic_store_n(&call->returned, 1, __ATOMIC_RELEASE);
97 if (test_wait_flag(&call->entered)) {
102 for (
int round = 0; round < 8; ++round) {
104 if (__atomic_load_n(&call->returned, __ATOMIC_ACQUIRE)) {
105 return restart ? -1 : 0;
107 const int previous = __atomic_load_n(&signal_calls, __ATOMIC_ACQUIRE);
108 if (pthread_kill(
worker, SIGUSR1)) {
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;
116 if (start < 0 || test_milliseconds() - start >= 1000) {
123 return restart ? (__atomic_load_n(&call->returned, __ATOMIC_ACQUIRE) ? -1 : 0)
124 : test_wait_flag(&call->returned);
127static int read_contract(
int restart) {
130 if (install_handler(restart ? SA_RESTART : 0) || pipe(descriptors)) {
134 .descriptor = descriptors[0],
136 .length =
sizeof(bytes)};
138 if (pthread_create(&
worker, 0, run_blocking_call, &call) ||
139 interrupt_call(
worker, &call, restart)) {
142 if (write(descriptors[1],
"xy", 2) != 2 || test_wait_flag(&call.returned) ||
143 pthread_join(
worker, 0)) {
147 if (call.result != 2 || memcmp(bytes,
"xy", 2)) {
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)) {
154 if (fcntl(descriptors[0], F_SETFL, O_NONBLOCK)) {
158 if (read(descriptors[0], bytes,
sizeof(bytes)) != -1 || errno != EAGAIN) {
161 return close(descriptors[0]) || close(descriptors[1]) ? 17 : 0;
164static int wait_contract(
int restart) {
166 if (install_handler(restart ? SA_RESTART : 0) || pipe(gate)) {
169 const pid_t child = fork();
177 _exit(read(gate[0], &token, 1) == 1 && token ==
'x' ? 42 : 43);
179 struct blocking_call call = {.operation = wait_operation, .child = child};
182 if (pthread_create(&
worker, 0, run_blocking_call, &call) ||
183 interrupt_call(
worker, &call, restart)) {
185 }
else if (write(gate[1],
"x", 1) != 1 || test_wait_flag(&call.returned) ||
186 pthread_join(
worker, 0)) {
188 }
else if (restart) {
189 if (call.result != child || !WIFEXITED(call.status) || WEXITSTATUS(call.status) != 42) {
194 if (call.result != -1 || call.error != EINTR || waitpid(child, &status, 0) != child ||
195 !WIFEXITED(status) || WEXITSTATUS(status) != 42) {
200 (void)kill(child, SIGKILL);
201 (void)waitpid(child, 0, 0);
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));
214 if (install_handler(SA_RESTART) || pipe(descriptors) ||
215 fcntl(descriptors[1], F_SETFL, O_NONBLOCK)) {
219 while (filled < maximum_fill) {
220 const ssize_t written = write(descriptors[1], bytes, chunk);
221 if (written == -1 && errno == EAGAIN) {
229 if (filled < chunk || filled == maximum_fill || read(descriptors[0], bytes, chunk) != chunk ||
230 fcntl(descriptors[1], F_SETFL, 0)) {
233 memset(bytes,
'x', chunk);
234 memset(bytes + chunk,
'y', chunk);
236 .descriptor = descriptors[1],
238 .length =
sizeof(bytes)};
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);
245 if (close(descriptors[1])) {
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) {
258 return count || received != filled || close(descriptors[0]) ? 36 : 0;
261static int sleep_contract(
void) {
262 if (install_handler(SA_RESTART)) {
267 if (pthread_create(&
worker, 0, run_blocking_call, &call) || interrupt_call(
worker, &call, 0) ||
268 pthread_join(
worker, 0)) {
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)
279static int futex_contract(
int restart) {
280 if (install_handler(restart ? SA_RESTART : 0)) {
285 if (pthread_create(&
worker, 0, run_blocking_call, &call) ||
286 interrupt_call(
worker, &call, 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) {
295 if (test_wait_flag(&call.returned) || pthread_join(
worker, 0)) {
299 return restart ? (call.result != 0 && !(call.result == -1 && call.error == EAGAIN) ? 47 : 0)
300 : (call.result != -1 || call.error != EINTR ? 48 : 0);
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)) {
309 temporary = original;
310 if (sigdelset(&temporary, SIGUSR1) || raise(SIGUSR1) || signal_calls) {
313 const struct timespec timeout = {.tv_sec = 2};
314 int result, saved_errno, descriptor = -1;
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);
323 descriptor = epoll_create1(EPOLL_CLOEXEC);
324 if (descriptor < 0) {
327 struct epoll_event event;
328 result = epoll_pwait(descriptor, &event, 1, 2000, &temporary);
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)) {
336 return descriptor >= 0 && close(descriptor) ? 54 : 0;
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) {
349int signal_contract(
const char* name) {
350 if (!strncmp(name,
"read-", 5)) {
351 return read_contract(!strcmp(name,
"read-restart"));
353 if (!strncmp(name,
"wait-", 5)) {
354 return wait_contract(!strcmp(name,
"wait-restart"));
356 if (!strcmp(name,
"partial-write")) {
357 return partial_write_contract();
359 if (!strcmp(name,
"nanosleep-eintr")) {
360 return sleep_contract();
362 if (!strncmp(name,
"futex-", 6)) {
363 return futex_contract(!strcmp(name,
"futex-restart"));
365 if (!strcmp(name,
"reset-hand")) {
366 return reset_contract();
368 return masked_wait_contract(name);