The Pedigree Project 0.1
recvmmsg-contract-test/main.c
1/* Copyright (c) 2026, Pedigree Developers. */
2#define _GNU_SOURCE
3#include <errno.h>
4#include <fcntl.h>
5#include <poll.h>
6#include <pthread.h>
7#include <signal.h>
8#include <stddef.h>
9#include <stdint.h>
10#include <stdio.h>
11#include <stdlib.h>
12#include <string.h>
13#include <time.h>
14#include <unistd.h>
15
16#include <sys/epoll.h>
17#include <sys/socket.h>
18#include <sys/syscall.h>
19#include <sys/un.h>
20#include <sys/wait.h>
21
22#define CHECK(expression) \
23 do { \
24 if (!(expression)) { \
25 fprintf(stderr, "RECVMMSG-CONTRACT: FAIL line=%d errno=%d\n", __LINE__, errno); \
26 return 1; \
27 } \
28 } while (0)
29
30static char socket_paths[16][sizeof(((struct sockaddr_un*)0)->sun_path)];
31static size_t socket_path_count;
32
33static void cleanup_paths(void) {
34 for (size_t i = 0; i < socket_path_count; ++i)
35 unlink(socket_paths[i]);
36}
37
38static int datagram_pair(int sockets[2]) {
39 struct sockaddr_un addresses[2] = {{.sun_family = AF_UNIX}, {.sun_family = AF_UNIX}};
40 sockets[0] = sockets[1] = -1;
41 for (int i = 0; i < 2; ++i) {
42 if (socket_path_count == sizeof(socket_paths) / sizeof(socket_paths[0])) {
43 errno = ENOSPC;
44 goto fail;
45 }
46 char* path = socket_paths[socket_path_count];
47 snprintf(path, sizeof(socket_paths[0]), "/tmp/recvmmsg-%ld-%zu", (long)getpid(),
48 socket_path_count);
49 ++socket_path_count;
50 memcpy(addresses[i].sun_path, path, strlen(path) + 1);
51 sockets[i] = socket(AF_UNIX, SOCK_DGRAM, 0);
52 if (sockets[i] < 0 || bind(sockets[i], (struct sockaddr*)&addresses[i],
53 offsetof(struct sockaddr_un, sun_path) + strlen(path) + 1))
54 goto fail;
55 }
56 for (int i = 0; i < 2; ++i) {
57 const struct sockaddr_un* peer = &addresses[1 - i];
58 if (connect(sockets[i], (const struct sockaddr*)peer,
59 offsetof(struct sockaddr_un, sun_path) + strlen(peer->sun_path) + 1))
60 goto fail;
61 }
62 return 0;
63fail: {
64 const int error = errno;
65 if (sockets[0] >= 0)
66 close(sockets[0]);
67 if (sockets[1] >= 0)
68 close(sockets[1]);
69 errno = error;
70 return -1;
71}
72}
73
74static volatile sig_atomic_t signalled;
75static void handler(int signal) {
76 (void)signal;
77 signalled = 1;
78}
79
80static void initialise(struct mmsghdr* messages, struct iovec* vectors, char data[][16], size_t n) {
81 memset(messages, 0, n * sizeof(*messages));
82 for (size_t i = 0; i < n; ++i) {
83 vectors[i].iov_base = data[i];
84 vectors[i].iov_len = 16;
85 messages[i].msg_hdr.msg_iov = &vectors[i];
86 messages[i].msg_hdr.msg_iovlen = 1;
87 }
88}
89
90static int pendingError(int fd, int expected) {
91 int error = 0;
92 socklen_t length = sizeof(error);
93 return getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &length) == 0 && error == expected;
94}
95
96static int datagrams(void) {
97 int sockets[2];
98 CHECK(datagram_pair(sockets) == 0);
99 struct mmsghdr messages[3];
100 struct iovec vectors[3];
101 char data[3][16];
102 initialise(messages, vectors, data, 3);
103 CHECK(write(sockets[0], "one", 3) == 3 && write(sockets[0], "two", 3) == 3);
104 CHECK(recvmmsg(sockets[1], messages, 3, MSG_WAITFORONE, NULL) == 2);
105 CHECK(messages[0].msg_len == 3 && messages[1].msg_len == 3 && !memcmp(data[0], "one", 3) &&
106 !memcmp(data[1], "two", 3));
107 CHECK(!(fcntl(sockets[1], F_GETFL) & O_NONBLOCK));
108 CHECK(recvmmsg(sockets[1], messages, 1, MSG_DONTWAIT, NULL) == -1 && errno == EAGAIN);
109 CHECK(syscall(SYS_recvmmsg, sockets[1], NULL, 0, 0, NULL) == 0);
110 CHECK(syscall(SYS_recvmmsg, -1, NULL, 0, 0, NULL) == -1 && errno == EBADF);
111
112 initialise(messages, vectors, data, 3);
113 CHECK(write(sockets[0], "before", 6) == 6 && write(sockets[0], "after", 5) == 5);
114 vectors[1].iov_base = (void*)1;
115 CHECK(recvmmsg(sockets[1], messages, 2, MSG_DONTWAIT, NULL) == 1);
116 CHECK(messages[0].msg_len == 6 && !memcmp(data[0], "before", 6));
117 CHECK(pendingError(sockets[1], EFAULT));
118 initialise(messages, vectors, data, 3);
119 CHECK(recvmmsg(sockets[1], messages, 1, MSG_DONTWAIT, NULL) == 1);
120 CHECK(messages[0].msg_len == 5 && !memcmp(data[0], "after", 5));
121
122 CHECK(write(sockets[0], "abcdef", 6) == 6);
123 initialise(messages, vectors, data, 3);
124 vectors[0].iov_len = 2;
125 CHECK(recvmmsg(sockets[1], messages, 1, MSG_TRUNC, NULL) == 1);
126 CHECK(messages[0].msg_len == 6 && (messages[0].msg_hdr.msg_flags & MSG_TRUNC) &&
127 !memcmp(data[0], "ab", 2));
128 CHECK(write(sockets[0], "", 0) == 0);
129 initialise(messages, vectors, data, 3);
130 CHECK(recvmmsg(sockets[1], messages, 1, MSG_DONTWAIT, NULL) == 1 && messages[0].msg_len == 0);
131 CHECK(fcntl(sockets[1], F_SETFL, O_NONBLOCK) == 0);
132 CHECK(recvmmsg(sockets[1], messages, 1, 0, NULL) == -1 && errno == EAGAIN);
133 CHECK(close(sockets[0]) == 0 && close(sockets[1]) == 0);
134 return 0;
135}
136
137static int deadlines(void) {
138 int sockets[2];
139 CHECK(datagram_pair(sockets) == 0);
140 struct mmsghdr messages[2];
141 struct iovec vectors[2];
142 char data[2][16];
143 initialise(messages, vectors, data, 2);
144 struct timespec timeout = {0, 30000000}, before, after;
145 CHECK(clock_gettime(CLOCK_MONOTONIC, &before) == 0);
146 CHECK(recvmmsg(sockets[1], messages, 2, 0, &timeout) == 0);
147 CHECK(clock_gettime(CLOCK_MONOTONIC, &after) == 0);
148 int64_t elapsed = (after.tv_sec - before.tv_sec) * 1000000000LL + after.tv_nsec - before.tv_nsec;
149 CHECK(elapsed >= 20000000 && elapsed < 2000000000LL);
150 CHECK(write(sockets[0], "partial", 7) == 7);
151 timeout = (struct timespec){0, 30000000};
152 CHECK(recvmmsg(sockets[1], messages, 2, 0, &timeout) == 1);
153 CHECK(messages[0].msg_len == 7 && !memcmp(data[0], "partial", 7));
154 CHECK(timeout.tv_sec == 0 && timeout.tv_nsec == 0);
155 timeout = (struct timespec){-1, 0};
156 CHECK(recvmmsg(sockets[1], messages, 1, 0, &timeout) == -1 && errno == EINVAL);
157 timeout = (struct timespec){0, 1000000000};
158 CHECK(recvmmsg(sockets[1], messages, 1, 0, &timeout) == -1 && errno == EINVAL);
159 CHECK(syscall(SYS_recvmmsg, sockets[1], messages, 1, 0, (void*)1) == -1 && errno == EFAULT);
160 CHECK(syscall(SYS_recvmmsg, sockets[1], (void*)1, 1, MSG_DONTWAIT, NULL) == -1 &&
161 errno == EFAULT);
162 CHECK(close(sockets[0]) == 0 && close(sockets[1]) == 0);
163 return 0;
164}
165
166static int signals(void) {
167 struct sigaction action = {.sa_handler = handler};
168 sigemptyset(&action.sa_mask);
169 CHECK(sigaction(SIGUSR1, &action, NULL) == 0);
170 int sockets[2];
171 CHECK(datagram_pair(sockets) == 0);
172 struct mmsghdr messages[2];
173 struct iovec vectors[2];
174 char data[2][16];
175 for (int partial = 0; partial <= 1; ++partial) {
176 initialise(messages, vectors, data, 2);
177 signalled = 0;
178 if (partial)
179 CHECK(write(sockets[0], "saved", 5) == 5);
180 pid_t child = fork();
181 CHECK(child >= 0);
182 if (!child) {
183 usleep(30000);
184 _exit(kill(getppid(), SIGUSR1) ? 2 : 0);
185 }
186 struct timespec timeout = {2, 0};
187 const int result = recvmmsg(sockets[1], messages, 2, 0, &timeout);
188 const int error = errno;
189 CHECK(signalled && (partial ? result == 1 : result == -1 && error == EINTR));
190 if (partial) {
191 CHECK(messages[0].msg_len == 5 && !memcmp(data[0], "saved", 5));
192 CHECK(pendingError(sockets[1], EINTR));
193 }
194 int status;
195 CHECK(waitpid(child, &status, 0) == child && WIFEXITED(status) && WEXITSTATUS(status) == 0);
196 }
197 action.sa_flags = SA_RESTART;
198 CHECK(sigaction(SIGUSR1, &action, NULL) == 0);
199 for (int timed = 0; timed <= 1; ++timed) {
200 initialise(messages, vectors, data, 2);
201 signalled = 0;
202 pid_t child = fork();
203 CHECK(child >= 0);
204 if (!child) {
205 usleep(30000);
206 if (kill(getppid(), SIGUSR1))
207 _exit(2);
208 usleep(30000);
209 _exit(write(sockets[0], "restart", 7) == 7 ? 0 : 3);
210 }
211 struct timespec timeout = {2, 0};
212 const int result = recvmmsg(sockets[1], messages, 1, 0, timed ? &timeout : NULL);
213 const int error = errno;
214 CHECK(signalled && (timed ? result == -1 && error == EINTR : result == 1));
215 int status;
216 CHECK(waitpid(child, &status, 0) == child && WIFEXITED(status) && WEXITSTATUS(status) == 0);
217 if (timed) {
218 CHECK(timeout.tv_sec == 2 && timeout.tv_nsec == 0);
219 CHECK(recvmmsg(sockets[1], messages, 1, MSG_DONTWAIT, NULL) == 1);
220 }
221 CHECK(messages[0].msg_len == 7 && !memcmp(data[0], "restart", 7));
222 }
223 CHECK(close(sockets[0]) == 0 && close(sockets[1]) == 0);
224 return 0;
225}
226
228 int fd;
229 int result;
230 char payload[16];
231};
232
233static void* receive_thread(void* argument) {
234 struct delayed_receive* call = argument;
235 struct iovec vector = {call->payload, sizeof(call->payload)};
236 struct mmsghdr messages[2] = {{.msg_hdr = {.msg_iov = &vector, .msg_iovlen = 1}}, {0}};
237 struct timespec timeout = {2, 0};
238 call->result = recvmmsg(call->fd, messages, 2, MSG_WAITFORONE, &timeout);
239 return NULL;
240}
241
242static int descriptor_reuse(void) {
243 int original[2], replacement[2];
244 CHECK(datagram_pair(original) == 0);
245 CHECK(datagram_pair(replacement) == 0);
246 int keep = dup(original[1]);
247 CHECK(keep >= 0);
248 struct delayed_receive call = {.fd = original[1], .result = -2};
249 pthread_t thread;
250 CHECK(pthread_create(&thread, NULL, receive_thread, &call) == 0);
251 usleep(50000);
252 CHECK(dup2(replacement[1], original[1]) == original[1]);
253 CHECK(write(replacement[0], "new", 3) == 3);
254 CHECK(write(original[0], "old", 3) == 3);
255 CHECK(pthread_join(thread, NULL) == 0);
256 CHECK(call.result == 1 && !memcmp(call.payload, "old", 3));
257 char payload[4];
258 CHECK(recv(original[1], payload, sizeof(payload), MSG_DONTWAIT) == 3 &&
259 !memcmp(payload, "new", 3));
260 CHECK(!(fcntl(keep, F_GETFL) & O_NONBLOCK));
261 CHECK(close(keep) == 0 && close(original[0]) == 0 && close(original[1]) == 0);
262 CHECK(close(replacement[0]) == 0 && close(replacement[1]) == 0);
263 return 0;
264}
265
267 int fd;
268 int result;
269 short events;
270};
271
272static void* poll_error(void* argument) {
273 struct error_poll* call = argument;
274 struct pollfd descriptor = {.fd = call->fd};
275 call->result = poll(&descriptor, 1, 2000);
276 call->events = descriptor.revents;
277 return NULL;
278}
279
280static int deferred_readiness(void) {
281 int sockets[2];
282 CHECK(datagram_pair(sockets) == 0);
283 int epoll = epoll_create1(EPOLL_CLOEXEC);
284 CHECK(epoll >= 0);
285 struct epoll_event event = {.events = EPOLLET, .data.fd = sockets[1]};
286 CHECK(epoll_ctl(epoll, EPOLL_CTL_ADD, sockets[1], &event) == 0);
287 for (int iteration = 0; iteration < 2; ++iteration) {
288 struct error_poll call = {.fd = sockets[1]};
289 pthread_t waiter;
290 CHECK(pthread_create(&waiter, NULL, poll_error, &call) == 0);
291 usleep(30000);
292 struct mmsghdr messages[2];
293 struct iovec vectors[2];
294 char data[2][16];
295 initialise(messages, vectors, data, 2);
296 vectors[1].iov_base = (void*)1;
297 CHECK(write(sockets[0], "error", 5) == 5);
298 CHECK(recvmmsg(sockets[1], messages, 2, MSG_DONTWAIT, NULL) == 1);
299 CHECK(pthread_join(waiter, NULL) == 0);
300 CHECK(call.result == 1 && (call.events & POLLERR));
301 CHECK(epoll_wait(epoll, &event, 1, 0) == 1 && (event.events & EPOLLERR));
302 CHECK(epoll_wait(epoll, &event, 1, 0) == 0);
303 struct pollfd descriptor = {.fd = sockets[1]};
304 CHECK(poll(&descriptor, 1, 0) == 1 && (descriptor.revents & POLLERR));
305 CHECK(pendingError(sockets[1], EFAULT));
306 CHECK(poll(&descriptor, 1, 0) == 0 && epoll_wait(epoll, &event, 1, 0) == 0);
307 }
308 CHECK(close(epoll) == 0 && close(sockets[0]) == 0 && close(sockets[1]) == 0);
309 return 0;
310}
311
312static int rights(void) {
313 int sockets[2], pipefd[2];
314 CHECK(datagram_pair(sockets) == 0 && pipe(pipefd) == 0);
315 union {
316 struct cmsghdr alignment;
317 char bytes[CMSG_SPACE(sizeof(int))];
318 } control;
319 memset(&control, 0, sizeof(control));
320 char payload = 'x';
321 struct iovec vector = {&payload, 1};
322 struct msghdr message = {.msg_iov = &vector,
323 .msg_iovlen = 1,
324 .msg_control = control.bytes,
325 .msg_controllen = sizeof(control)};
326 struct cmsghdr* header = CMSG_FIRSTHDR(&message);
327 header->cmsg_level = SOL_SOCKET;
328 header->cmsg_type = SCM_RIGHTS;
329 header->cmsg_len = CMSG_LEN(sizeof(int));
330 memcpy(CMSG_DATA(header), &pipefd[0], sizeof(int));
331 CHECK(sendmsg(sockets[0], &message, 0) == 1 && sendmsg(sockets[0], &message, 0) == 1);
332
333 struct mmsghdr messages[2];
334 struct iovec vectors[2];
335 char data[2][16];
336 union {
337 struct cmsghdr alignment;
338 char bytes[CMSG_SPACE(sizeof(int))];
339 } received[2];
340 initialise(messages, vectors, data, 2);
341 for (int i = 0; i < 2; ++i) {
342 messages[i].msg_hdr.msg_control = received[i].bytes;
343 messages[i].msg_hdr.msg_controllen = sizeof(received[i]);
344 }
345 CHECK(recvmmsg(sockets[1], messages, 2, MSG_CMSG_CLOEXEC, NULL) == 2);
346 for (int i = 0; i < 2; ++i) {
347 header = CMSG_FIRSTHDR(&messages[i].msg_hdr);
348 CHECK(header && header->cmsg_level == SOL_SOCKET && header->cmsg_type == SCM_RIGHTS);
349 int fd;
350 memcpy(&fd, CMSG_DATA(header), sizeof(fd));
351 CHECK(fcntl(fd, F_GETFD) & FD_CLOEXEC);
352 CHECK(write(pipefd[1], "p", 1) == 1 && read(fd, &payload, 1) == 1 && payload == 'p');
353 CHECK(close(fd) == 0);
354 }
355 CHECK(close(sockets[0]) == 0 && close(sockets[1]) == 0);
356 CHECK(close(pipefd[0]) == 0 && close(pipefd[1]) == 0);
357 return 0;
358}
359
360int main(void) {
361 setvbuf(stdout, NULL, _IONBF, 0);
362 alarm(30);
363 CHECK(atexit(cleanup_paths) == 0);
364 puts("RECVMMSG-CONTRACT: datagrams");
365 CHECK(datagrams() == 0);
366 puts("RECVMMSG-CONTRACT: deadlines");
367 CHECK(deadlines() == 0);
368 puts("RECVMMSG-CONTRACT: signals");
369 CHECK(signals() == 0);
370 puts("RECVMMSG-CONTRACT: rights");
371 CHECK(rights() == 0);
372 puts("RECVMMSG-CONTRACT: descriptor_reuse");
373 CHECK(descriptor_reuse() == 0);
374 puts("RECVMMSG-CONTRACT: deferred_readiness");
375 CHECK(deferred_readiness() == 0);
376 puts("RECVMMSG-CONTRACT: PASS");
377 return 0;
378}