The Pedigree Project 0.1
futex-clock-contract-test/main.c
1/* Copyright (c) 2026, Pedigree Developers. */
2#define _GNU_SOURCE
3#include <errno.h>
4#include <limits.h>
5#include <pthread.h>
6#include <signal.h>
7#include <stdint.h>
8#include <stdio.h>
9#include <string.h>
10#include <time.h>
11#include <unistd.h>
12
13#include <sys/syscall.h>
14#include <sys/wait.h>
15
16enum { Wait = 128, Wake = 129, Requeue = 131, Bitset = 137, Realtime = 256 };
17#define CHECK(x) \
18 do { \
19 if (!(x)) { \
20 fprintf(stderr, "FUTEX-CLOCK: line=%d errno=%d check=%s\n", __LINE__, errno, #x); \
21 return -1; \
22 } \
23 } while (0)
24
25static int64_t now(clockid_t clock) {
26 struct timespec value;
27 return syscall(SYS_clock_gettime, clock, &value)
28 ? -1
29 : (int64_t)value.tv_sec * 1000000000 + value.tv_nsec;
30}
31static struct timespec timespec(int64_t value) {
32 return (struct timespec){value / 1000000000, value % 1000000000};
33}
34static void pause_ms(int milliseconds) {
35 struct timespec duration = timespec((int64_t)milliseconds * 1000000);
36 while (nanosleep(&duration, &duration) && errno == EINTR) {
37 }
38}
39static int step(int64_t delta) {
40 struct timespec target = timespec(now(CLOCK_REALTIME) + delta);
41 return clock_settime(CLOCK_REALTIME, &target);
42}
43static int wake(int* address) {
44 return syscall(SYS_futex, address, Wake, INT_MAX, NULL, NULL, 0);
45}
46static int requeue(int* source, int* destination) {
47 return syscall(SYS_futex, source, Requeue, 0, 1, destination, 0);
48}
49
50struct waiter {
51 int* address;
52 int operation, timed, result, error;
53 struct timespec deadline;
54 int64_t started, finished;
55 volatile int done;
56};
57static void* wait_thread(void* argument) {
58 struct waiter* wait = argument;
59 wait->started = now(CLOCK_MONOTONIC);
60 wait->result = syscall(SYS_futex, wait->address, wait->operation, 0,
61 wait->timed ? &wait->deadline : NULL, NULL, UINT32_MAX);
62 wait->error = errno;
63 wait->finished = now(CLOCK_MONOTONIC);
64 __atomic_store_n(&wait->done, 1, __ATOMIC_RELEASE);
65 return NULL;
66}
67static int completed(struct waiter* wait, int milliseconds) {
68 const int64_t until = now(CLOCK_MONOTONIC) + (int64_t)milliseconds * 1000000;
69 while (!__atomic_load_n(&wait->done, __ATOMIC_ACQUIRE)) {
70 if (now(CLOCK_MONOTONIC) >= until)
71 return 0;
72 pause_ms(1);
73 }
74 return 1;
75}
76static int enrolled(struct waiter* wait, int* destination) {
77 const int64_t until = now(CLOCK_MONOTONIC) + 2000000000;
78 while (!__atomic_load_n(&wait->done, __ATOMIC_ACQUIRE) && now(CLOCK_MONOTONIC) < until) {
79 int moved = requeue(wait->address, destination);
80 if (moved)
81 return moved == 1;
82 pause_ms(1);
83 }
84 return 0;
85}
86
87static int validation(void) {
88 int word = 0;
89 struct timespec past = {0}, invalid = {0, 1000000000};
90 CHECK(syscall(SYS_futex, &word, Bitset | Realtime, 1, &past, NULL, UINT32_MAX) == -1 &&
91 errno == EAGAIN);
92 CHECK(syscall(SYS_futex, &word, Bitset | Realtime, 0, &past, NULL, UINT32_MAX) == -1 &&
93 errno == ETIMEDOUT);
94 CHECK(syscall(SYS_futex, &word, Bitset, 0, &past, NULL, UINT32_MAX) == -1 && errno == ETIMEDOUT);
95 CHECK(syscall(SYS_futex, &word, Wait, 0, &past, NULL, 0) == -1 && errno == ETIMEDOUT);
96 CHECK(syscall(SYS_futex, &word, Bitset, 0, &invalid, NULL, UINT32_MAX) == -1 && errno == EINVAL);
97 invalid = (struct timespec){-1, 0};
98 CHECK(syscall(SYS_futex, &word, Bitset, 0, &invalid, NULL, UINT32_MAX) == -1 && errno == EINVAL);
99 CHECK(syscall(SYS_futex, &word, Bitset, 0, (void*)-1, NULL, UINT32_MAX) == -1 && errno == EFAULT);
100 CHECK(syscall(SYS_futex, (char*)&word + 1, Bitset, 0, NULL, NULL, UINT32_MAX) == -1 &&
101 errno == EINVAL);
102 CHECK(syscall(SYS_futex, &word, Bitset, 0, NULL, NULL, 0) == -1 && errno == EINVAL);
103 CHECK(syscall(SYS_futex, &word, Bitset, 0, NULL, NULL, 1) == -1 && errno == ENOSYS);
104 CHECK(syscall(SYS_futex, &word, Wake | Realtime, 1, NULL, NULL, 0) == -1 && errno == ENOSYS);
105 CHECK(wake(&word) == 0);
106 return 0;
107}
108
109static int ordinary_and_indefinite(void) {
110 int word = 0;
111 const int operations[] = {Wait, Bitset, Bitset | Realtime};
112 for (unsigned n = 0; n < sizeof(operations) / sizeof(operations[0]); ++n) {
113 pthread_t thread;
114 struct waiter wait = {.address = &word, .operation = operations[n]};
115 CHECK(pthread_create(&thread, NULL, wait_thread, &wait) == 0);
116 CHECK(enrolled(&wait, &word));
117 CHECK(wake(&word) == 1 && completed(&wait, 2000));
118 CHECK(pthread_join(thread, NULL) == 0 && wait.result == 0);
119 CHECK(wake(&word) == 0);
120 }
121 return 0;
122}
123
124static int deadlines(void) {
125 int word = 0;
126 for (int realtime = 0; realtime <= 1; ++realtime) {
127 struct waiter wait = {
128 .address = &word, .operation = Bitset | (realtime ? Realtime : 0), .timed = 1};
129 const int64_t before = now(CLOCK_MONOTONIC);
130 wait.deadline = timespec(now(realtime ? CLOCK_REALTIME : CLOCK_MONOTONIC) + 200000000);
131 pthread_t thread;
132 CHECK(pthread_create(&thread, NULL, wait_thread, &wait) == 0);
133 CHECK(completed(&wait, 3000) && pthread_join(thread, NULL) == 0);
134 CHECK(wait.result == -1 && wait.error == ETIMEDOUT && wait.finished - before >= 200000000);
135 CHECK(wake(&word) == 0);
136 }
137 return 0;
138}
139
140static int clock_steps(void) {
141 int word = 0;
142 pthread_t thread;
143 struct waiter wait = {.address = &word, .operation = Bitset | Realtime, .timed = 1};
144 wait.deadline = timespec(now(CLOCK_REALTIME) + 10000000000);
145 CHECK(pthread_create(&thread, NULL, wait_thread, &wait) == 0 && enrolled(&wait, &word));
146 CHECK(step(20000000000) == 0);
147 CHECK(completed(&wait, 2000) && pthread_join(thread, NULL) == 0);
148 CHECK(wait.result == -1 && wait.error == ETIMEDOUT && wake(&word) == 0);
149
150 wait = (struct waiter){.address = &word, .operation = Bitset | Realtime, .timed = 1};
151 wait.deadline = timespec(now(CLOCK_REALTIME) + 1000000000);
152 CHECK(pthread_create(&thread, NULL, wait_thread, &wait) == 0 && enrolled(&wait, &word));
153 CHECK(step(-5000000000) == 0);
154 pause_ms(1500);
155 CHECK(!__atomic_load_n(&wait.done, __ATOMIC_ACQUIRE));
156 CHECK(step(10000000000) == 0);
157 CHECK(completed(&wait, 2000) && pthread_join(thread, NULL) == 0);
158 CHECK(wait.result == -1 && wait.error == ETIMEDOUT && wake(&word) == 0);
159 return 0;
160}
161
162static int requeue_clock_change(void) {
163 int source = 0, destination = 0;
164 pthread_t threads[2];
165 struct waiter waits[2] = {{.address = &source, .operation = Wait},
166 {.address = &source, .operation = Bitset | Realtime, .timed = 1}};
167 waits[1].deadline = timespec(now(CLOCK_REALTIME) + 10000000000);
168 for (int n = 0; n < 2; ++n) {
169 CHECK(pthread_create(&threads[n], NULL, wait_thread, &waits[n]) == 0);
170 CHECK(enrolled(&waits[n], &destination));
171 }
172 __atomic_store_n(&source, 1, __ATOMIC_RELEASE);
173 CHECK(step(-1000000000) == 0);
174 pause_ms(10);
175 CHECK(wake(&source) == 0 && wake(&destination) == 2);
176 for (int n = 0; n < 2; ++n)
177 CHECK(completed(&waits[n], 2000) && pthread_join(threads[n], NULL) == 0 &&
178 waits[n].result == 0);
179 CHECK(wake(&destination) == 0);
180 return 0;
181}
182
183static volatile sig_atomic_t signals;
184static void handler(int signal) {
185 (void)signal;
186 ++signals;
187}
188static int interruption(void) {
189 int word = 0;
190 struct sigaction action = {.sa_handler = handler};
191 sigemptyset(&action.sa_mask);
192 CHECK(sigaction(SIGUSR1, &action, NULL) == 0);
193 for (int n = 0; n < 4; ++n) {
194 pthread_t thread;
195 struct waiter wait = {.address = &word, .operation = Bitset | Realtime, .timed = 1};
196 wait.deadline = timespec(now(CLOCK_REALTIME) + 10000000000);
197 CHECK(pthread_create(&thread, NULL, wait_thread, &wait) == 0 && enrolled(&wait, &word));
198 CHECK(pthread_kill(thread, SIGUSR1) == 0);
199 CHECK(completed(&wait, 2000) && pthread_join(thread, NULL) == 0);
200 CHECK(wait.result == -1 && wait.error == EINTR && wake(&word) == 0);
201 CHECK(step(1) == 0);
202 }
203 CHECK(signals == 4);
204 return 0;
205}
206
207static int timeout_wake_race(void) {
208 int word = 0;
209 for (int n = 0; n < 24; ++n) {
210 pthread_t thread;
211 struct waiter wait = {.address = &word, .operation = Bitset, .timed = 1};
212 wait.deadline = timespec(now(CLOCK_MONOTONIC) + 10000000);
213 CHECK(pthread_create(&thread, NULL, wait_thread, &wait) == 0);
214 pause_ms(10);
215 const int woken = wake(&word);
216 CHECK(completed(&wait, 2000) && pthread_join(thread, NULL) == 0);
217 CHECK((woken == 1 && wait.result == 0) ||
218 (woken == 0 && wait.result == -1 && wait.error == ETIMEDOUT));
219 }
220 return 0;
221}
222
223static int terminate_wait(void) {
224 int ready[2];
225 CHECK(pipe(ready) == 0);
226 pid_t child = fork();
227 CHECK(child >= 0);
228 if (!child) {
229 close(ready[0]);
230 alarm(10);
231 int word = 0;
232 pthread_t thread;
233 struct waiter wait = {.address = &word, .operation = Bitset | Realtime, .timed = 1};
234 wait.deadline = timespec(now(CLOCK_REALTIME) + 10000000000);
235 if (pthread_create(&thread, NULL, wait_thread, &wait) || !enrolled(&wait, &word) ||
236 write(ready[1], "r", 1) != 1)
237 _exit(1);
238 for (;;)
239 pause();
240 }
241 close(ready[1]);
242 char token = 0;
243 int valid = read(ready[0], &token, 1) == 1 && token == 'r';
244 close(ready[0]);
245 int status;
246 CHECK(kill(child, SIGKILL) == 0 && waitpid(child, &status, 0) == child);
247 CHECK(valid && WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL);
248 CHECK(step(1) == 0);
249 return 0;
250}
251
252int main(void) {
253 setvbuf(stdout, NULL, _IONBF, 0);
254 if (geteuid() != 0) {
255 fputs("FUTEX-CLOCK: FAIL requires root for clock-step contracts\n", stderr);
256 return 1;
257 }
258 const struct {
259 const char* name;
260 int (*test)(void);
261 } tests[] = {{"validation", validation},
262 {"ordinary-and-indefinite", ordinary_and_indefinite},
263 {"deadlines", deadlines},
264 {"clock-steps", clock_steps},
265 {"requeue-clock-change", requeue_clock_change},
266 {"interruption", interruption},
267 {"timeout-wake-race", timeout_wake_race},
268 {"terminate-wait", terminate_wait}};
269 for (unsigned n = 0; n < sizeof(tests) / sizeof(tests[0]); ++n) {
270 printf("FUTEX-CLOCK: RUN %s\n", tests[n].name);
271 const int64_t realtime = now(CLOCK_REALTIME), monotonic = now(CLOCK_MONOTONIC);
272 pid_t child = fork();
273 if (!child) {
274 alarm(20);
275 _exit(tests[n].test() ? 1 : 0);
276 }
277 int status = -1;
278 pid_t reaped;
279 do {
280 reaped = waitpid(child, &status, 0);
281 } while (reaped < 0 && errno == EINTR);
282 struct timespec restored = timespec(realtime + now(CLOCK_MONOTONIC) - monotonic);
283 int failed = child < 0 || reaped != child || !WIFEXITED(status) || WEXITSTATUS(status);
284 if (clock_settime(CLOCK_REALTIME, &restored))
285 failed = 1;
286 printf("FUTEX-CLOCK: %s %s status=%d\n", failed ? "FAIL" : "PASS", tests[n].name, status);
287 if (failed)
288 return 1;
289 }
290 puts("FUTEX-CLOCK: END PASS");
291 return 0;
292}