19union stream_control_buffer {
20 struct cmsghdr alignment;
21 unsigned char bytes[CMSG_SPACE(2 *
sizeof(
int))];
24static ssize_t send_stream_rights(
int socketFd,
const int* descriptors,
size_t descriptorCount,
25 const void* payload,
size_t payloadLength) {
26 union stream_control_buffer control = {0};
27 struct iovec vector = {(
void*)payload, payloadLength};
31 .msg_control = control.bytes,
32 .msg_controllen = CMSG_SPACE(descriptorCount *
sizeof(
int)),
34 struct cmsghdr* header = CMSG_FIRSTHDR(&
message);
35 header->cmsg_len = CMSG_LEN(descriptorCount *
sizeof(
int));
36 header->cmsg_level = SOL_SOCKET;
37 header->cmsg_type = SCM_RIGHTS;
38 memcpy(CMSG_DATA(header), descriptors, descriptorCount *
sizeof(
int));
39 return sendmsg(socketFd, &
message, MSG_NOSIGNAL);
42static ssize_t receive_stream_rights(
int socketFd,
int flags,
void* payload,
size_t payloadCapacity,
43 size_t controlCapacity,
int expectedCount,
int* received,
45 union stream_control_buffer control = {0};
46 struct iovec vector = {payload, payloadCapacity};
50 .msg_control = control.bytes,
51 .msg_controllen = controlCapacity,
53 const ssize_t result = recvmsg(socketFd, &
message, flags);
57 *outputFlags =
message.msg_flags;
64 struct cmsghdr* header = CMSG_FIRSTHDR(&
message);
65 if (!header || header->cmsg_level != SOL_SOCKET || header->cmsg_type != SCM_RIGHTS ||
66 header->cmsg_len != CMSG_LEN((
size_t)expectedCount *
sizeof(
int)))
68 memcpy(received, CMSG_DATA(header), (
size_t)expectedCount *
sizeof(
int));
72static void expect_broken_pipe(
int descriptor) {
73 void (*previousSigpipe)(int) = signal(SIGPIPE, SIG_IGN);
74 if (previousSigpipe == SIG_ERR)
77 const ssize_t result = write(descriptor,
"x", 1);
78 const int error = errno;
79 if (signal(SIGPIPE, previousSigpipe) == SIG_ERR || result != -1 || error != EPIPE)
83static void test_ordering_and_sender_close(
void) {
86 if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, sockets) || pipe(channel))
90 int received[2] = {-1, -1};
93 if (receive_stream_rights(sockets[1], 0, &empty, 1,
sizeof(
union stream_control_buffer), 0,
94 received, &outputFlags) != -1 ||
98 if (write(sockets[0],
"ab", 2) != 2 ||
99 send_stream_rights(sockets[0], &channel[0], 1,
"cd", 2) != 2 ||
100 write(sockets[0],
"ef", 2) != 2 || close(channel[0]) || close(sockets[0]))
104 if (read(sockets[1], &
byte, 1) != 1 ||
byte !=
'a')
107 char marked[8] = {0};
108 const ssize_t markedLength =
109 receive_stream_rights(sockets[1], MSG_CMSG_CLOEXEC, marked,
sizeof(marked),
110 CMSG_SPACE(
sizeof(
int)), 1, received, &outputFlags);
111 if (markedLength != 2 || memcmp(marked,
"bc", 2) || outputFlags || received[0] < 0 ||
112 fcntl(received[0], F_GETFD) != FD_CLOEXEC || write(channel[1],
"R", 1) != 1 ||
113 read(received[0], &
byte, 1) != 1 ||
byte !=
'R' || close(received[0]) || close(channel[1]))
117 if (read(sockets[1], tail,
sizeof(tail)) != 3 || memcmp(tail,
"def", 3) ||
118 read(sockets[1], tail,
sizeof(tail)) != 0 || close(sockets[1]))
122static void test_split_iovec_marker(
void) {
125 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) || pipe(channel) ||
126 write(sockets[0],
"a", 1) != 1)
129 union stream_control_buffer sendControl = {0};
130 struct iovec sendVectors[2] = {
134 struct msghdr sendMessage = {
135 .msg_iov = sendVectors,
137 .msg_control = sendControl.bytes,
138 .msg_controllen = CMSG_SPACE(
sizeof(
int)),
140 struct cmsghdr* sendHeader = CMSG_FIRSTHDR(&sendMessage);
141 sendHeader->cmsg_len = CMSG_LEN(
sizeof(
int));
142 sendHeader->cmsg_level = SOL_SOCKET;
143 sendHeader->cmsg_type = SCM_RIGHTS;
144 memcpy(CMSG_DATA(sendHeader), &channel[0],
sizeof(channel[0]));
145 if (sendmsg(sockets[0], &sendMessage, MSG_NOSIGNAL) != 2 || close(channel[0]))
148 char payload[4] = {0};
149 union stream_control_buffer receiveControl = {0};
150 struct iovec receiveVectors[2] = {
152 {payload + 1,
sizeof(payload) - 1},
154 struct msghdr receiveMessage = {
155 .msg_iov = receiveVectors,
157 .msg_control = receiveControl.bytes,
158 .msg_controllen = CMSG_SPACE(
sizeof(
int)),
160 if (recvmsg(sockets[1], &receiveMessage, 0) != 2 || memcmp(payload,
"ab", 2) ||
161 receiveMessage.msg_flags)
163 struct cmsghdr* receiveHeader = CMSG_FIRSTHDR(&receiveMessage);
164 if (!receiveHeader || receiveHeader->cmsg_level != SOL_SOCKET ||
165 receiveHeader->cmsg_type != SCM_RIGHTS || receiveHeader->cmsg_len != CMSG_LEN(
sizeof(
int)))
168 memcpy(&received, CMSG_DATA(receiveHeader),
sizeof(received));
170 if (received < 0 || write(channel[1],
"I", 1) != 1 || read(received, &
byte, 1) != 1 ||
171 byte !=
'I' || close(received) || read(sockets[1], &
byte, 1) != 1 ||
byte !=
'c' ||
172 close(channel[1]) || close(sockets[0]) || close(sockets[1]))
176static void test_plain_read_discards_rights(
void) {
179 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) || pipe(channel) ||
180 send_stream_rights(sockets[0], &channel[0], 1,
"p", 1) != 1 || close(channel[0]))
184 if (read(sockets[1], &payload, 1) != 1 || payload !=
'p')
186 expect_broken_pipe(channel[1]);
187 if (close(channel[1]) || close(sockets[0]) || close(sockets[1]))
191static void test_truncation(
void) {
194 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) || pipe(channels[0]) || pipe(channels[1]))
197 int sent[2] = {channels[0][0], channels[1][0]};
198 if (send_stream_rights(sockets[0], sent, 2,
"t", 1) != 1 || close(sent[0]) || close(sent[1]))
202 int received[2] = {-1, -1};
204 if (receive_stream_rights(sockets[1], 0, &payload, 1, CMSG_LEN(
sizeof(
int)), 1, received,
205 &outputFlags) != 1 ||
206 payload !=
't' || !(outputFlags & MSG_CTRUNC) || write(channels[0][1],
"T", 1) != 1)
208 if (read(received[0], &payload, 1) != 1 || payload !=
'T' || close(received[0]) ||
209 close(channels[0][1]))
211 expect_broken_pipe(channels[1][1]);
212 if (close(channels[1][1]) || close(sockets[0]) || close(sockets[1]))
216static void test_multiple_controls_and_fault_retry(
void) {
219 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) || pipe(channels[0]) || pipe(channels[1]))
222 if (send_stream_rights(sockets[0], &channels[0][0], 1,
"12", 2) != 2 ||
223 send_stream_rights(sockets[0], &channels[1][0], 1,
"34", 2) != 2 || close(channels[0][0]) ||
224 close(channels[1][0]))
227 char payload[8] = {0};
228 int received[2] = {-1, -1};
230 if (receive_stream_rights(sockets[1], 0, payload,
sizeof(payload), CMSG_SPACE(
sizeof(
int)), 1,
231 received, &outputFlags) != 1 ||
232 payload[0] !=
'1' || write(channels[0][1],
"A", 1) != 1 ||
233 read(received[0], payload, 1) != 1 || payload[0] !=
'A' || close(received[0]) ||
234 close(channels[0][1]))
237 memset(payload, 0,
sizeof(payload));
238 if (receive_stream_rights(sockets[1], 0, payload,
sizeof(payload), CMSG_SPACE(
sizeof(
int)), 1,
239 received, &outputFlags) != 2 ||
240 memcmp(payload,
"23", 2) || write(channels[1][1],
"B", 1) != 1 ||
241 read(received[0], payload, 1) != 1 || payload[0] !=
'B' || close(received[0]) ||
242 close(channels[1][1]) || read(sockets[1], payload, 1) != 1 || payload[0] !=
'4')
246 if (pipe(faultChannel) || send_stream_rights(sockets[0], &faultChannel[0], 1,
"f", 1) != 1 ||
247 close(faultChannel[0]))
250 char faultPayload = 0;
251 struct iovec faultVector = {&faultPayload,
sizeof(faultPayload)};
252 struct msghdr faultMessage = {
253 .msg_iov = &faultVector,
255 .msg_control = (
void*)-1,
256 .msg_controllen = CMSG_SPACE(
sizeof(
int)),
259 if (recvmsg(sockets[1], &faultMessage, 0) != -1 || errno != EFAULT ||
260 receive_stream_rights(sockets[1], 0, &faultPayload, 1, CMSG_SPACE(
sizeof(
int)), 1, received,
261 &outputFlags) != 1 ||
262 faultPayload !=
'f' || close(received[0]) || close(faultChannel[1]) || close(sockets[0]) ||
267static void test_empty_payload_and_close_drain(
void) {
270 if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, sockets) || pipe(channel))
273 struct iovec emptyVector = {(
void*)
"", 0};
274 struct msghdr emptyMessage = {
275 .msg_iov = &emptyVector,
279 if (send_stream_rights(sockets[0], &channel[0], 1,
"", 0) != -1 || errno != EINVAL ||
280 sendmsg(sockets[0], &emptyMessage, MSG_NOSIGNAL) != 0 || close(channel[0]))
282 expect_broken_pipe(channel[1]);
285 if (recv(sockets[1], &payload, 1, 0) != -1 || errno != EAGAIN || close(channel[1]) ||
286 close(sockets[0]) || close(sockets[1]))
289 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) || pipe(channel) ||
290 send_stream_rights(sockets[0], &channel[0], 1,
"c", 1) != 1 || close(channel[0]) ||
293 expect_broken_pipe(channel[1]);
294 if (close(channel[1]) || close(sockets[0]))
298void test_scm_rights_stream(
void) {
299 puts(
"Testing AF_UNIX stream descriptor passing... ");
301 test_ordering_and_sender_close();
302 test_split_iovec_marker();
303 test_plain_read_discards_rights();
305 test_multiple_controls_and_fault_retry();
306 test_empty_payload_and_close_drain();