The Pedigree Project 0.1
unix-seqpacket.c
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted.
6 */
7
8#include <errno.h>
9#include <fcntl.h>
10#include <poll.h>
11#include <pthread.h>
12#include <stddef.h>
13#include <stdio.h>
14#include <string.h>
15#include <unistd.h>
16
17#include <sys/socket.h>
18#include <sys/un.h>
19
20extern void fail(void) __attribute__((noreturn));
21
22union seqpacket_control {
23 struct cmsghdr alignment;
24 unsigned char bytes[CMSG_SPACE(sizeof(int))];
25};
26
27static void expect_empty(int descriptor) {
28 char byte = 0;
29 errno = 0;
30 if (recv(descriptor, &byte, sizeof(byte), MSG_DONTWAIT) != -1 || errno != EAGAIN) {
31 fail();
32 }
33}
34
35static void record_contracts(void) {
36 int pair[2];
37 if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, pair)) {
38 fail();
39 }
40 for (int i = 0; i < 2; ++i) {
41 int type = 0;
42 socklen_t type_length = sizeof(type);
43 if (fcntl(pair[i], F_GETFD) != FD_CLOEXEC || !(fcntl(pair[i], F_GETFL) & O_NONBLOCK) ||
44 getsockopt(pair[i], SOL_SOCKET, SO_TYPE, &type, &type_length) || type != SOCK_SEQPACKET ||
45 type_length != sizeof(type)) {
46 fail();
47 }
48 }
49
50 expect_empty(pair[1]);
51 if (send(pair[0], "abcdef", 6, 0) != 6 || write(pair[0], "XY", 2) != 2) {
52 fail();
53 }
54 char payload[32] = {0};
55 struct iovec vector = {.iov_base = payload, .iov_len = 3};
56 struct msghdr message = {.msg_iov = &vector, .msg_iovlen = 1};
57 if (recvmsg(pair[1], &message, 0) != 3 || memcmp(payload, "abc", 3) ||
58 !(message.msg_flags & MSG_TRUNC) || read(pair[1], payload, sizeof(payload)) != 2 ||
59 memcmp(payload, "XY", 2)) {
60 fail();
61 }
62 expect_empty(pair[1]);
63
64 if (send(pair[0], "abcdef", 6, 0) != 6 || send(pair[0], "z", 1, 0) != 1) {
65 fail();
66 }
67 message.msg_flags = 0;
68 if (recvmsg(pair[1], &message, MSG_TRUNC) != 6 || memcmp(payload, "abc", 3) ||
69 !(message.msg_flags & MSG_TRUNC) || recv(pair[1], payload, sizeof(payload), 0) != 1 ||
70 payload[0] != 'z') {
71 fail();
72 }
73
74 /* Empty sends are records; an empty receive buffer also consumes one record. */
75 if (write(pair[0], "", 0) != 0 || send(pair[0], "q", 1, 0) != 1) {
76 fail();
77 }
78 struct pollfd reader = {.fd = pair[1], .events = POLLIN};
79 if (poll(&reader, 1, 0) != 1 || !(reader.revents & POLLIN) ||
80 recv(pair[1], payload, sizeof(payload), 0) != 0 ||
81 recv(pair[1], payload, sizeof(payload), 0) != 1 || payload[0] != 'q') {
82 fail();
83 }
84 if (send(pair[0], "discard", 7, 0) != 7 || send(pair[0], "r", 1, 0) != 1 ||
85 recv(pair[1], payload, 0, 0) != 0 || recv(pair[1], payload, sizeof(payload), 0) != 1 ||
86 payload[0] != 'r') {
87 fail();
88 }
89 expect_empty(pair[1]);
90
91 char first[] = "one";
92 char second[] = "two";
93 struct iovec vectors[2] = {{first, 3}, {second, 3}};
94 message.msg_iov = vectors;
95 message.msg_iovlen = 2;
96 if (sendmsg(pair[0], &message, MSG_DONTWAIT) != 6) {
97 fail();
98 }
99 memset(first, 0, sizeof(first));
100 memset(second, 0, sizeof(second));
101 if (recvmsg(pair[1], &message, 0) != 6 || memcmp(first, "one", 3) || memcmp(second, "two", 3) ||
102 (message.msg_flags & MSG_TRUNC)) {
103 fail();
104 }
105
106 static char oversized[65537];
107 errno = 0;
108 if (send(pair[0], oversized, sizeof(oversized), 0) != -1 || errno != EMSGSIZE) {
109 fail();
110 }
111 expect_empty(pair[1]);
112 memset(oversized, 'm', sizeof(oversized));
113 if (send(pair[0], oversized, sizeof(oversized) - 1, 0) != (ssize_t)sizeof(oversized) - 1) {
114 fail();
115 }
116 memset(oversized, 0, sizeof(oversized));
117 if (recv(pair[1], oversized, sizeof(oversized), 0) != (ssize_t)sizeof(oversized) - 1) {
118 fail();
119 }
120 for (size_t i = 0; i < sizeof(oversized) - 1; ++i) {
121 if (oversized[i] != 'm') {
122 fail();
123 }
124 }
125 if (close(pair[0]) || close(pair[1])) {
126 fail();
127 }
128}
129
130static void rights_contracts(void) {
131 int pair[2];
132 int channel[2];
133 if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) || pipe(channel)) {
134 fail();
135 }
136 union seqpacket_control control = {0};
137 char payload = 'c';
138 struct iovec vector = {.iov_base = &payload, .iov_len = 1};
139 struct msghdr message = {
140 .msg_iov = &vector,
141 .msg_iovlen = 1,
142 .msg_control = control.bytes,
143 .msg_controllen = sizeof(control.bytes),
144 };
145 struct cmsghdr* header = CMSG_FIRSTHDR(&message);
146 header->cmsg_len = CMSG_LEN(sizeof(int));
147 header->cmsg_level = SOL_SOCKET;
148 header->cmsg_type = SCM_RIGHTS;
149 memcpy(CMSG_DATA(header), &channel[0], sizeof(int));
150 if (sendmsg(pair[0], &message, 0) != 1 || close(channel[0])) {
151 fail();
152 }
153 payload = 0;
154 memset(&control, 0, sizeof(control));
155 if (recvmsg(pair[1], &message, MSG_CMSG_CLOEXEC) != 1 || payload != 'c' ||
156 (message.msg_flags & MSG_CTRUNC)) {
157 fail();
158 }
159 header = CMSG_FIRSTHDR(&message);
160 if (!header || header->cmsg_level != SOL_SOCKET || header->cmsg_type != SCM_RIGHTS ||
161 header->cmsg_len != CMSG_LEN(sizeof(int))) {
162 fail();
163 }
164 int received = -1;
165 memcpy(&received, CMSG_DATA(header), sizeof(int));
166 if (fcntl(received, F_GETFD) != FD_CLOEXEC || write(channel[1], "C", 1) != 1 ||
167 read(received, &payload, 1) != 1 || payload != 'C' || close(received) || close(channel[1])) {
168 fail();
169 }
170
171 /* SCM_RIGHTS stays attached to a zero-length record. */
172 if (pipe(channel)) {
173 fail();
174 }
175 memset(&control, 0, sizeof(control));
176 vector.iov_len = 0;
177 message.msg_controllen = sizeof(control.bytes);
178 message.msg_flags = 0;
179 header = CMSG_FIRSTHDR(&message);
180 header->cmsg_len = CMSG_LEN(sizeof(int));
181 header->cmsg_level = SOL_SOCKET;
182 header->cmsg_type = SCM_RIGHTS;
183 memcpy(CMSG_DATA(header), &channel[0], sizeof(int));
184 if (sendmsg(pair[0], &message, 0) != 0 || close(channel[0])) {
185 fail();
186 }
187 memset(&control, 0, sizeof(control));
188 if (recvmsg(pair[1], &message, 0) != 0 || (message.msg_flags & MSG_CTRUNC)) {
189 fail();
190 }
191 header = CMSG_FIRSTHDR(&message);
192 if (!header || header->cmsg_level != SOL_SOCKET || header->cmsg_type != SCM_RIGHTS ||
193 header->cmsg_len != CMSG_LEN(sizeof(int))) {
194 fail();
195 }
196 memcpy(&received, CMSG_DATA(header), sizeof(int));
197 if (write(channel[1], "Z", 1) != 1 || read(received, &payload, 1) != 1 || payload != 'Z' ||
198 close(received) || close(channel[1])) {
199 fail();
200 }
201
202 /* Read shutdown releases descriptors carried by unread records. */
203 if (pipe2(channel, O_NONBLOCK)) {
204 fail();
205 }
206 memset(&control, 0, sizeof(control));
207 message.msg_controllen = sizeof(control.bytes);
208 message.msg_flags = 0;
209 header = CMSG_FIRSTHDR(&message);
210 header->cmsg_len = CMSG_LEN(sizeof(int));
211 header->cmsg_level = SOL_SOCKET;
212 header->cmsg_type = SCM_RIGHTS;
213 memcpy(CMSG_DATA(header), &channel[1], sizeof(int));
214 if (sendmsg(pair[0], &message, 0) != 0 || close(channel[1])) {
215 fail();
216 }
217 errno = 0;
218 if (read(channel[0], &payload, 1) != -1 || errno != EAGAIN || shutdown(pair[1], SHUT_RD) ||
219 read(channel[0], &payload, 1) != 0 || close(channel[0]) || close(pair[0]) || close(pair[1])) {
220 fail();
221 }
222}
223
224static void shutdown_contracts(void) {
225 int pair[2];
226 char payload[16];
227 if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK, 0, pair) ||
228 send(pair[0], "first", 5, 0) != 5 || send(pair[0], "", 0, 0) != 0 ||
229 send(pair[0], "last", 4, 0) != 4 || shutdown(pair[0], SHUT_WR)) {
230 fail();
231 }
232 if (recv(pair[1], payload, sizeof(payload), 0) != 5 || memcmp(payload, "first", 5) ||
233 recv(pair[1], payload, sizeof(payload), 0) != 0 ||
234 recv(pair[1], payload, sizeof(payload), 0) != 4 || memcmp(payload, "last", 4) ||
235 recv(pair[1], payload, sizeof(payload), 0) != 0 ||
236 recv(pair[1], payload, sizeof(payload), MSG_DONTWAIT) != 0) {
237 fail();
238 }
239 errno = 0;
240 if (send(pair[0], "x", 1, MSG_NOSIGNAL) != -1 || errno != EPIPE ||
241 send(pair[1], "reply", 5, 0) != 5 || recv(pair[0], payload, sizeof(payload), 0) != 5 ||
242 memcmp(payload, "reply", 5) || shutdown(pair[0], SHUT_RD) ||
243 recv(pair[0], payload, sizeof(payload), MSG_DONTWAIT) != 0) {
244 fail();
245 }
246 errno = 0;
247 if (send(pair[1], "x", 1, MSG_NOSIGNAL) != -1 || errno != EPIPE || close(pair[0]) ||
248 close(pair[1])) {
249 fail();
250 }
251
252 /* Peer close preserves queued records, followed by persistent EOF. */
253 if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK, 0, pair) ||
254 send(pair[0], "queued", 6, 0) != 6 || close(pair[0]) ||
255 recv(pair[1], payload, sizeof(payload), 0) != 6 || memcmp(payload, "queued", 6) ||
256 recv(pair[1], payload, sizeof(payload), 0) != 0 || close(pair[1])) {
257 fail();
258 }
259}
260
261static void backpressure_contracts(void) {
262 int pair[2];
263 if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair)) {
264 fail();
265 }
266 int count = 0;
267 for (; count < 1024; ++count) {
268 errno = 0;
269 ssize_t result = send(pair[0], "p", 1, MSG_DONTWAIT);
270 if (result == -1 && errno == EAGAIN) {
271 break;
272 }
273 if (result != 1) {
274 fail();
275 }
276 }
277 if (!count || count == 1024) {
278 fail();
279 }
280 struct pollfd writer = {.fd = pair[0], .events = POLLOUT};
281 if (poll(&writer, 1, 0) != 0) {
282 fail();
283 }
284 char payload = 0;
285 if (recv(pair[1], &payload, 1, 0) != 1 || payload != 'p' || poll(&writer, 1, 0) != 1 ||
286 !(writer.revents & POLLOUT) || send(pair[0], "", 0, MSG_DONTWAIT) != 0) {
287 fail();
288 }
289 for (int i = 1; i < count; ++i) {
290 if (recv(pair[1], &payload, 1, 0) != 1 || payload != 'p') {
291 fail();
292 }
293 }
294 if (recv(pair[1], &payload, 1, 0) != 0) {
295 fail();
296 }
297 expect_empty(pair[1]);
298 if (close(pair[0]) || close(pair[1])) {
299 fail();
300 }
301}
302
304 int descriptor;
305 unsigned char label;
306 int failed;
307};
308
309static void* write_records(void* parameter) {
310 struct seqpacket_writer* writer = parameter;
311 unsigned char payload[32];
312 memset(payload, writer->label, sizeof(payload));
313 for (unsigned char i = 0; i < 64; ++i) {
314 payload[1] = i;
315 if (send(writer->descriptor, payload, sizeof(payload), 0) != (ssize_t)sizeof(payload)) {
316 writer->failed = 1;
317 break;
318 }
319 }
320 return NULL;
321}
322
323static void concurrent_contracts(void) {
324 int pair[2];
325 pthread_t threads[2];
326 struct seqpacket_writer writers[2];
327 if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair)) {
328 fail();
329 }
330 for (int i = 0; i < 2; ++i) {
331 writers[i] =
332 (struct seqpacket_writer){.descriptor = pair[0], .label = (unsigned char)('a' + i)};
333 if (pthread_create(&threads[i], NULL, write_records, &writers[i])) {
334 fail();
335 }
336 }
337 unsigned char expected[2] = {0, 0};
338 for (int i = 0; i < 128; ++i) {
339 unsigned char payload[32];
340 if (recv(pair[1], payload, sizeof(payload), 0) != (ssize_t)sizeof(payload) ||
341 payload[0] < 'a' || payload[0] > 'b') {
342 fail();
343 }
344 unsigned int source = payload[0] - 'a';
345 if (payload[1] != expected[source]++) {
346 fail();
347 }
348 for (size_t j = 2; j < sizeof(payload); ++j) {
349 if (payload[j] != payload[0]) {
350 fail();
351 }
352 }
353 }
354 for (int i = 0; i < 2; ++i) {
355 if (pthread_join(threads[i], NULL) || writers[i].failed || expected[i] != 64) {
356 fail();
357 }
358 }
359 if (close(pair[0]) || close(pair[1])) {
360 fail();
361 }
362}
363
364static void pathname_contracts(void) {
365 static const char path[] = "/tmp/seqpacket-contract.sock";
366 struct sockaddr_un address = {.sun_family = AF_UNIX};
367 memcpy(address.sun_path, path, sizeof(path));
368 socklen_t address_length = offsetof(struct sockaddr_un, sun_path) + sizeof(path);
369 int listener = socket(AF_UNIX, SOCK_SEQPACKET, 0);
370 int client = socket(AF_UNIX, SOCK_SEQPACKET, 0);
371 if (listener < 0 || client < 0) {
372 fail();
373 }
374 (void)unlink(path);
375 if (bind(listener, (struct sockaddr*)&address, address_length) || listen(listener, 4) ||
376 connect(client, (struct sockaddr*)&address, address_length)) {
377 fail();
378 }
379 int accepted = accept4(listener, NULL, NULL, SOCK_CLOEXEC);
380 char payload[16];
381 if (accepted < 0 || fcntl(accepted, F_GETFD) != FD_CLOEXEC || send(client, "path", 4, 0) != 4 ||
382 recv(accepted, payload, sizeof(payload), 0) != 4 || memcmp(payload, "path", 4) ||
383 send(accepted, "reply", 5, 0) != 5 || recv(client, payload, sizeof(payload), 0) != 5 ||
384 memcmp(payload, "reply", 5) || close(accepted) || close(client) || close(listener) ||
385 unlink(path)) {
386 fail();
387 }
388}
389
390void test_unix_seqpacket(void) {
391 puts("Testing AF_UNIX sequenced-packet record contracts... ");
392 fflush(stdout);
393 record_contracts();
394 puts("SEQPACKET-RECORD-PASS");
395 fflush(stdout);
396 rights_contracts();
397 puts("SEQPACKET-RIGHTS-PASS");
398 fflush(stdout);
399 shutdown_contracts();
400 puts("SEQPACKET-SHUTDOWN-PASS");
401 fflush(stdout);
402 backpressure_contracts();
403 puts("SEQPACKET-BACKPRESSURE-PASS");
404 fflush(stdout);
405 concurrent_contracts();
406 puts("SEQPACKET-CONCURRENT-PASS");
407 fflush(stdout);
408 pathname_contracts();
409 puts("SEQPACKET-PATHNAME-PASS");
410 fflush(stdout);
411 puts("SEQPACKET-CONTRACTS-PASS");
412 fflush(stdout);
413}
Definition waits.c:9