The Pedigree Project 0.1
blocking.c
1#define _GNU_SOURCE
2#include <poll.h>
3#include <pthread.h>
4#include <signal.h>
5#include <unistd.h>
6
7#include "contract.h"
8#include <sys/socket.h>
9
10struct lock_wait {
11 int fd, kind, probe, probe_error, result, error;
12 volatile int entered, done;
13 int64_t started, finished;
14};
15static volatile sig_atomic_t signal_count;
16static void caught(int signal) {
17 (void)signal;
18 __atomic_add_fetch(&signal_count, 1, __ATOMIC_RELAXED);
19}
20static void finished(void* argument) {
21 __atomic_store_n((volatile int*)argument, 1, __ATOMIC_RELEASE);
22}
23static void* lock_wait(void* argument) {
24 struct lock_wait* wait = argument;
25 pthread_cleanup_push(finished, (void*)&wait->done);
26 wait->started = fl_now();
27 wait->probe = fl_lock(wait->fd, wait->kind, F_WRLCK, 0);
28 wait->probe_error = errno;
29 if (wait->probe == -1 && wait->probe_error == EAGAIN) {
30 __atomic_store_n(&wait->entered, 1, __ATOMIC_RELEASE);
31 wait->result = fl_lock(wait->fd, wait->kind, F_WRLCK, 1);
32 wait->error = errno;
33 }
34 wait->finished = fl_now();
35 pthread_cleanup_pop(1);
36 return NULL;
37}
38static void diagnostic(const struct lock_wait* wait, int action, int64_t acted) {
39 const int entered = __atomic_load_n(&wait->entered, __ATOMIC_ACQUIRE);
40 const int done = __atomic_load_n(&wait->done, __ATOMIC_ACQUIRE);
41 fprintf(stderr,
42 "FILE-LOCK-CONTRACT: wait kind=%d action=%d entered=%d done=%d "
43 "probe=%d/%d result=%d/%d start=%lld action_time=%lld finish=%lld now=%lld\n",
44 wait->kind, action, entered, done, entered || done ? wait->probe : -2,
45 entered || done ? wait->probe_error : -2, done ? wait->result : -2,
46 done ? wait->error : -2, entered || done ? (long long)wait->started : -1,
47 (long long)acted, done ? (long long)wait->finished : -1, (long long)fl_now());
48}
49static pid_t holder(const char* path, int inherited, int kind, int* control) {
50 int pair[2];
51 if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair))
52 return -1;
53 pid_t child = fork();
54 if (!child) {
55 alarm(10);
56 close(pair[0]);
57 close(inherited);
58 int fd = open(path, O_RDWR);
59 if (fd < 0 || fl_lock(fd, kind, F_WRLCK, 0) || write(pair[1], "h", 1) != 1 ||
60 fl_byte(pair[1], 'x'))
61 _exit(10);
62 _exit(fl_lock(fd, kind, F_UNLCK, 0) ? 11 : 0);
63 }
64 close(pair[1]);
65 if (child < 0) {
66 close(pair[0]);
67 return -1;
68 }
69 *control = pair[0];
70 return child;
71}
72
73enum { RELEASE, INTERRUPT, CANCEL, REPLACE };
74static int blocking_case(int kind, int action) {
75 int failed = 0, fd = -1, other = -1, probe = -1, control = -1, running = 0;
76 pid_t child = -1;
77 pthread_t worker;
78 struct lock_wait wait = {.kind = kind, .result = -2, .error = -2};
79 char path[128] = {0}, other_path[128] = {0};
80 int64_t acted = -1;
81 CHECK((fd = fl_file(path, "/tmp")) >= 0 && (other = fl_file(other_path, "/tmp")) >= 0);
82 CHECK((child = holder(path, fd, kind, &control)) > 0 && fl_byte(control, 'h') == 0);
83 CHECK(fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) == 0);
84 wait.fd = fd;
85 CHECK(pthread_create(&worker, NULL, lock_wait, &wait) == 0);
86 running = 1;
87 CHECK(fl_wait(&wait.entered, 1000) == 0);
88 CHECK(__atomic_load_n(&wait.done, __ATOMIC_ACQUIRE) == 0);
89 if (action == REPLACE) {
90 // Public contention is observable, but syscall enrollment is not. Retain
91 // timing evidence if this scheduling interval proves insufficient.
92 fl_pause(100);
93 CHECK(__atomic_load_n(&wait.done, __ATOMIC_ACQUIRE) == 0);
94 acted = fl_now();
95 CHECK(dup2(other, fd) == fd);
96 if (kind == FL_CLASSIC) {
97 CHECK(fl_wait(&wait.done, 2000) == 0 && wait.result == -1 && wait.error == EBADF);
98 } else {
99 CHECK(__atomic_load_n(&wait.done, __ATOMIC_ACQUIRE) == 0);
100 }
101 } else if (action == INTERRUPT) {
102 acted = fl_now();
103 const int64_t until = acted + 2000000000;
104 while (!__atomic_load_n(&wait.done, __ATOMIC_ACQUIRE) && fl_now() < until) {
105 CHECK(pthread_kill(worker, SIGUSR1) == 0);
106 fl_pause(10);
107 }
108 CHECK(fl_wait(&wait.done, 100) == 0 && wait.result == -1 && wait.error == EINTR);
109 CHECK(fl_lock(fd, kind, F_WRLCK, 0) == -1 && errno == EAGAIN);
110 } else if (action == CANCEL) {
111 acted = fl_now();
112 CHECK(pthread_cancel(worker) == 0 && fl_wait(&wait.done, 2000) == 0);
113 void* result;
114 CHECK(pthread_join(worker, &result) == 0 && result == PTHREAD_CANCELED);
115 running = 0;
116 }
117 if (acted < 0)
118 acted = fl_now();
119 CHECK(write(control, "x", 1) == 1 && fl_reap(child, 2000) == 0);
120 child = -1;
121 if (running) {
122 CHECK(fl_wait(&wait.done, 2000) == 0);
123 if (action == RELEASE || (action == REPLACE && kind != FL_CLASSIC))
124 CHECK(wait.result == 0);
125 CHECK(pthread_join(worker, NULL) == 0);
126 running = 0;
127 }
128 if (action == REPLACE) {
129 CHECK((probe = open(path, O_RDWR)) >= 0);
130 CHECK(fl_lock(probe, kind == FL_CLASSIC ? FL_OFD : kind, F_WRLCK, 0) == 0);
131 close(probe);
132 probe = -1;
133 CHECK((probe = open(other_path, O_RDWR)) >= 0);
134 CHECK(fl_lock(probe, kind == FL_CLASSIC ? FL_OFD : kind, F_WRLCK, 0) == 0);
135 } else {
136 CHECK(fl_lock(fd, kind, F_WRLCK, 0) == 0);
137 }
138out:
139 if (failed)
140 diagnostic(&wait, action, acted);
141 if (child > 0) {
142 kill(child, SIGKILL);
143 fl_reap(child, 1000);
144 }
145 if (running) {
146 if (fl_wait(&wait.done, 2000))
147 _exit(1);
148 pthread_join(worker, NULL);
149 }
150 if (control >= 0)
151 close(control);
152 if (probe >= 0)
153 close(probe);
154 if (other >= 0)
155 close(other);
156 if (fd >= 0)
157 close(fd);
158 if (*path)
159 unlink(path);
160 if (*other_path)
161 unlink(other_path);
162 return failed;
163}
164
165static int deadlock(void) {
166 int failed = 0, a = -1, b = -1, pair[2] = {-1, -1}, running = 0;
167 pid_t child = -1;
168 char path[128] = {0}, second[128] = {0};
169 pthread_t worker;
170 struct lock_wait wait = {.kind = FL_CLASSIC, .result = -2, .error = -2};
171 CHECK((a = fl_file(path, "/tmp")) >= 0 && (b = fl_file(second, "/tmp")) >= 0);
172 CHECK(fl_lock(a, FL_CLASSIC, F_WRLCK, 0) == 0);
173 CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == 0 && (child = fork()) >= 0);
174 if (!child) {
175 alarm(8);
176 close(pair[0]);
177 if (fl_lock(b, FL_CLASSIC, F_WRLCK, 0) || fl_lock(a, FL_CLASSIC, F_WRLCK, 0) != -1 ||
178 errno != EAGAIN || write(pair[1], "c", 1) != 1)
179 _exit(10);
180 int result = fl_lock(a, FL_CLASSIC, F_WRLCK, 1), error = errno;
181 char report = result == 0 ? 'a' : error == EDEADLK ? 'd' : 'e';
182 if (fl_lock(b, FL_CLASSIC, F_UNLCK, 0) || write(pair[1], &report, 1) != 1)
183 _exit(11);
184 _exit(report == 'e' ? 12 : 0);
185 }
186 close(pair[1]);
187 pair[1] = -1;
188 CHECK(fl_byte(pair[0], 'c') == 0);
189 wait.fd = b;
190 CHECK(pthread_create(&worker, NULL, lock_wait, &wait) == 0);
191 running = 1;
192 CHECK(fl_wait(&wait.entered, 1000) == 0);
193 const int64_t until = fl_now() + 4000000000;
194 while (!__atomic_load_n(&wait.done, __ATOMIC_ACQUIRE)) {
195 struct pollfd ready = {.fd = pair[0], .events = POLLIN};
196 if (poll(&ready, 1, 0) > 0)
197 break;
198 CHECK(fl_now() < until);
199 fl_pause(2);
200 }
201 const int parent_deadlock =
202 __atomic_load_n(&wait.done, __ATOMIC_ACQUIRE) && wait.result == -1 && wait.error == EDEADLK;
203 if (parent_deadlock)
204 CHECK(fl_lock(a, FL_CLASSIC, F_UNLCK, 0) == 0);
205 CHECK(fl_byte(pair[0], parent_deadlock ? 'a' : 'd') == 0);
206 CHECK(fl_wait(&wait.done, 2000) == 0 &&
207 (parent_deadlock ? wait.result == -1 && wait.error == EDEADLK : wait.result == 0));
208 CHECK(pthread_join(worker, NULL) == 0);
209 running = 0;
210 CHECK(fl_reap(child, 2000) == 0);
211 child = -1;
212out:
213 if (failed)
214 diagnostic(&wait, 4, -1);
215 if (a >= 0)
216 fl_lock(a, FL_CLASSIC, F_UNLCK, 0);
217 if (b >= 0)
218 fl_lock(b, FL_CLASSIC, F_UNLCK, 0);
219 if (child > 0) {
220 kill(child, SIGKILL);
221 fl_reap(child, 1000);
222 }
223 if (running) {
224 if (fl_wait(&wait.done, 2000))
225 _exit(1);
226 pthread_join(worker, NULL);
227 }
228 for (int n = 0; n < 2; ++n)
229 if (pair[n] >= 0)
230 close(pair[n]);
231 if (b >= 0)
232 close(b);
233 if (a >= 0)
234 close(a);
235 if (*path)
236 unlink(path);
237 if (*second)
238 unlink(second);
239 return failed;
240}
241int file_lock_blocking(void) {
242 int failed = 0, mask_changed = 0;
243 struct sigaction action = {.sa_handler = caught}, original;
244 sigset_t unblocked, previous;
245 sigemptyset(&action.sa_mask);
246 CHECK(sigaction(SIGUSR1, &action, &original) == 0);
247 sigemptyset(&unblocked);
248 sigaddset(&unblocked, SIGUSR1);
249 if (pthread_sigmask(SIG_UNBLOCK, &unblocked, &previous)) {
250 failed = 1;
251 goto restore;
252 }
253 mask_changed = 1;
254 for (int kind = 0; kind < 3; ++kind) {
255 if (blocking_case(kind, RELEASE) || blocking_case(kind, INTERRUPT) ||
256 blocking_case(kind, REPLACE)) {
257 failed = 1;
258 goto restore;
259 }
260 }
261 if (!__atomic_load_n(&signal_count, __ATOMIC_RELAXED) || blocking_case(FL_CLASSIC, CANCEL) ||
262 deadlock())
263 failed = 1;
264restore:
265 if (sigaction(SIGUSR1, &original, NULL))
266 failed = 1;
267out:
268 if (mask_changed && pthread_sigmask(SIG_SETMASK, &previous, NULL))
269 failed = 1;
270 return failed;
271}