16#define MSG_COPY 040000
24#define CHECK(condition, label) \
27 printf("IPC-MESSAGES: FAIL %s errno=%d\n", label, errno); \
32static int send_byte(
int id,
long type,
char value) {
34 return msgsnd(
id, &
message, 1, IPC_NOWAIT);
37static int receive_byte(
int id,
long type,
int flags,
long expected_type,
char value) {
39 return msgrcv(
id, &
message,
sizeof(
message.bytes), type, flags | IPC_NOWAIT) == 1 &&
43static int selection(
int id) {
44 struct msqid_ds status;
45 CHECK(!msgctl(
id, IPC_STAT, &status),
"initial status");
46 CHECK(status.msg_qnum == 0 && status.msg_cbytes == 0 && status.msg_qbytes > 0 &&
47 status.msg_perm.uid == geteuid() && status.msg_perm.cuid == geteuid() &&
48 !status.msg_lspid && !status.msg_lrpid && !status.msg_stime && !status.msg_rtime,
49 "initial queue metadata");
50 CHECK(msgget(status.msg_perm.__ipc_perm_key, 0600) ==
id,
"named queue lookup");
53 msgget(status.msg_perm.__ipc_perm_key, IPC_CREAT | IPC_EXCL | 0600) == -1 && errno == EEXIST,
54 "exclusive queue creation");
55 int private_id = msgget(IPC_PRIVATE, 0600);
56 CHECK(private_id >= 0 && private_id !=
id,
"private queue identity");
57 CHECK(!msgctl(private_id, IPC_RMID, NULL),
"private queue removal");
59 int highest = msgctl(0, MSG_INFO, (
struct msqid_ds*)&info);
60 CHECK(highest >= 0 && info.msgpool >= 1 && info.msgmax >= 8 && info.msgmni >= 1,
63 for (
int slot = 0; slot <= highest; ++slot) {
64 if (msgctl(slot, MSG_STAT, &status) ==
id) {
65 CHECK(msgctl(slot, MSG_STAT_ANY, &status) ==
id,
"unrestricted index query");
70 CHECK(found,
"queue index enumeration");
71 CHECK(msgctl(0, IPC_INFO, (
struct msqid_ds*)&info) >= 0 && info.msgmnb > 0,
"queue limits query");
72 CHECK(!send_byte(
id, 7,
'a') && !send_byte(
id, 2,
'b') && !send_byte(
id, 2,
'c') &&
73 !send_byte(
id, 5,
'd'),
74 "queue typed records");
75 CHECK(!msgctl(
id, IPC_STAT, &status) && status.msg_qnum == 4 && status.msg_cbytes == 4 &&
76 status.msg_lspid == getpid(),
78 CHECK(receive_byte(
id, 1, MSG_COPY, 2,
'b'),
"ordinal nondestructive copy");
79 CHECK(!msgctl(
id, IPC_STAT, &status) && status.msg_qnum == 4 && status.msg_lrpid == 0,
80 "copy preserves metadata");
81 CHECK(receive_byte(
id, -6, 0, 2,
'b'),
"negative selector lowest type");
82 CHECK(receive_byte(
id, 2, 0, 2,
'c'),
"equal type FIFO order");
83 CHECK(receive_byte(
id, 7, MSG_EXCEPT, 5,
'd'),
"excluded type");
84 CHECK(receive_byte(
id, 0, 0, 7,
'a'),
"zero selector FIFO");
85 CHECK(!send_byte(
id, LONG_MAX,
'z') && receive_byte(
id, LONG_MIN, 0, LONG_MAX,
'z'),
86 "minimum signed selector");
87 CHECK(!msgctl(
id, IPC_STAT, &status) && !status.msg_qnum && !status.msg_cbytes &&
88 status.msg_lrpid == getpid(),
92 CHECK(msgrcv(
id, &
message,
sizeof(
message.bytes), 0, IPC_NOWAIT) == -1 && errno == ENOMSG,
93 "empty queue nonblocking receive");
94 CHECK(!msgsnd(
id, &
message, 0, IPC_NOWAIT) && msgrcv(
id, &
message, 0, 0, IPC_NOWAIT) == 0,
95 "zero length message");
98 CHECK(msgsnd(
id, &
message, 0, IPC_NOWAIT) == -1 && errno == EINVAL,
"invalid message type");
102static int copies(
int id) {
104 CHECK(!msgsnd(
id, &
message, 4, IPC_NOWAIT),
"queue complete payload");
106 CHECK(msgrcv(
id, &
message, 2, 0, IPC_NOWAIT) == -1 && errno == E2BIG,
107 "short receive preserves message");
109 CHECK(msgrcv(
id, NULL, 4, 0, IPC_NOWAIT) == -1 && errno == EFAULT,
110 "invalid destination rejected");
111 struct msqid_ds status;
112 CHECK(!msgctl(
id, IPC_STAT, &status) && status.msg_qnum == 1 && status.msg_cbytes == 4,
113 "failed copy preserves message");
115 CHECK(msgrcv(
id, &
message, 2, 0, MSG_NOERROR | IPC_NOWAIT) == 2 &&
message.type == 3 &&
117 "explicit truncation");
118 CHECK(!msgctl(
id, IPC_STAT, &status) && status.msg_qnum == 0,
119 "truncation consumes complete message");
121 CHECK(msgsnd(
id, NULL, 1, IPC_NOWAIT) == -1 && errno == EFAULT,
"invalid sender buffer");
123 CHECK(msgctl(
id, IPC_STAT, NULL) == -1 && errno == EFAULT,
"invalid status buffer");
125 CHECK(msgrcv(
id, &
message, 0, 0, MSG_COPY) == -1 && errno == EINVAL,
"copy requires nonblocking");
126 status.msg_qbytes = 2;
127 CHECK(!msgctl(
id, IPC_SET, &status),
"limit zero length records");
129 CHECK(!msgsnd(
id, &
message, 0, IPC_NOWAIT) && !msgsnd(
id, &
message, 0, IPC_NOWAIT),
130 "fill queue with zero length records");
132 CHECK(msgsnd(
id, &
message, 0, IPC_NOWAIT) == -1 && errno == EAGAIN,
133 "zero length records consume queue capacity");
134 CHECK(msgrcv(
id, &
message, 0, 0, IPC_NOWAIT) == 0 && msgrcv(
id, &
message, 0, 0, IPC_NOWAIT) == 0,
135 "drain zero length records");
139static int wait_child(pid_t child) {
143 result = waitpid(child, &status, 0);
144 }
while (result < 0 && errno == EINTR);
145 return result == child && WIFEXITED(status) && WEXITSTATUS(status) == 0;
148static int wait_ready(
int descriptor, pid_t child) {
151 const struct timespec delay = {.tv_nsec = 100000000};
152 if (read(descriptor, &ready, 1) != 1 || ready !=
'r' || nanosleep(&delay, NULL)) {
155 return waitpid(child, &status, WNOHANG) == 0;
159static int blocking(
int id,
int sending,
int removing,
int terminating) {
160 struct msqid_ds status;
161 CHECK(!msgctl(
id, IPC_STAT, &status),
"read queue capacity");
162 status.msg_qbytes = 1;
163 CHECK(!msgctl(
id, IPC_SET, &status),
"set queue capacity");
165 CHECK(!send_byte(
id, 1,
'a'),
"fill queue");
167 CHECK(send_byte(
id, 1,
'b') == -1 && errno == EAGAIN,
"full queue nonblocking send");
170 CHECK(!pipe(ready),
"peer readiness pipe");
171 pid_t child = fork();
172 CHECK(child >= 0,
"fork blocking peer");
177 if (write(ready[1],
"r", 1) != 1) {
182 ssize_t result = sending ? msgsnd(
id, &
message, 1, 0) : msgrcv(id, &
message, 1, 0, 0);
185 _exit(result == -1 && error == EIDRM ? 0 : 3);
187 _exit((sending ? result == 0 : result == 1 &&
message.bytes[0] ==
'a') ? 0 : 4);
190 CHECK(wait_ready(ready[0], child),
"peer remains blocked");
193 CHECK(!kill(child, SIGKILL),
"terminate blocked peer");
195 CHECK(waitpid(child, &child_status, 0) == child && WIFSIGNALED(child_status) &&
196 WTERMSIG(child_status) == SIGKILL,
197 "reap terminated peer");
198 CHECK(!send_byte(
id, 1,
'k') && receive_byte(
id, 0, 0, 1,
'k'),
199 "queue survives peer termination");
203 CHECK(!msgctl(
id, IPC_RMID, NULL),
"remove queue with blocked peer");
204 }
else if (sending) {
205 CHECK(receive_byte(
id, 0, 0, 1,
'a'),
"receive releases sender capacity");
207 CHECK(!send_byte(
id, 1,
'a'),
"send wakes receiver");
209 CHECK(wait_child(child),
"blocking peer result");
210 if (sending && !removing) {
211 CHECK(receive_byte(
id, 0, 0, 1,
'b'),
"blocked sender delivered payload");
215 CHECK(msgctl(
id, IPC_STAT, &status) == -1 && errno == EINVAL,
"removed identifier retired");
220static volatile sig_atomic_t interrupted;
221static void interrupt_handler(
int signal_number) {
222 if (signal_number == SIGALRM) {
223 if (++interrupted >= 3) {
230static int interruption(
int id,
int sending) {
231 struct sigaction action = {.sa_handler = interrupt_handler, .sa_flags = SA_RESTART};
232 CHECK(!sigemptyset(&action.sa_mask) && !sigaction(SIGALRM, &action, NULL),
233 "install interrupt handler");
235 struct msqid_ds status;
236 CHECK(!msgctl(
id, IPC_STAT, &status),
"interrupted sender status");
237 status.msg_qbytes = 1;
238 CHECK(!msgctl(
id, IPC_SET, &status) && !send_byte(
id, 1,
'a'),
"fill interrupted sender");
244 ssize_t result = sending ? msgsnd(
id, &
message, 1, 0) : msgrcv(id, &
message, 1, 0, 0);
247 CHECK(result == -1 && error == EINTR && interrupted,
"IPC is interrupted with SA_RESTART");
249 CHECK(receive_byte(
id, 0, 0, 1,
'a'),
"interrupted sender preserved queued payload");
254static int permissions(
int id) {
255 if (geteuid() != 0) {
256 puts(
"IPC-MESSAGES: SKIP alternate credentials requires root");
259 struct msqid_ds status;
260 CHECK(!msgctl(
id, IPC_STAT, &status),
"permission status");
261 pid_t child = fork();
262 CHECK(child >= 0,
"fork credential peer");
265 if (setgroups(0, NULL) || setgid(65534) || setuid(65534)) {
270 if (msgget(status.msg_perm.__ipc_perm_key, 0600) != -1 || errno != EACCES) {
274 if (msgsnd(
id, &
message, 0, IPC_NOWAIT) != -1 || errno != EACCES) {
278 if (msgrcv(
id, &
message, 0, 0, IPC_NOWAIT) != -1 || errno != EACCES) {
282 if (msgctl(
id, IPC_STAT, &status) != -1 || errno != EACCES) {
286 if (msgctl(
id, IPC_SET, &status) != -1 || errno != EPERM) {
290 _exit(msgctl(
id, IPC_RMID, NULL) == -1 && errno == EPERM ? 0 : 8);
292 CHECK(wait_child(child),
"enforce queue permissions");
296int ipc_test_messages(
void) {
297 static const char* names[] = {
298 "selection",
"copies",
"send-wait",
"receive-wait",
"send-remove",
299 "receive-remove",
"send-interrupt",
"receive-interrupt",
"permissions",
"peer-termination"};
300 for (
unsigned int test = 0; test <
sizeof(names) /
sizeof(names[0]); ++test) {
301 key_t key = (key_t)(0x4d000000U | ((
unsigned int)getpid() & 0xffffU) << 8 | test);
302 int id = msgget(key, IPC_CREAT | IPC_EXCL | 0600);
303 CHECK(
id >= 0,
"create case queue");
304 pid_t child = fork();
310 result = selection(
id);
316 result = blocking(
id, 1, 0, 0);
319 result = blocking(
id, 0, 0, 0);
322 result = blocking(
id, 1, 1, 0);
325 result = blocking(
id, 0, 1, 0);
328 result = interruption(
id, 1);
331 result = interruption(
id, 0);
334 result = permissions(
id);
337 result = blocking(
id, 0, 0, 1);
342 int passed = child > 0 && wait_child(child);
343 int removed = msgctl(
id, IPC_RMID, NULL);
344 if (removed && errno != EINVAL) {
347 printf(
"IPC-MESSAGES: %s %s\n", passed ?
"PASS" :
"FAIL", names[test]);