The Pedigree Project 0.1
sysv-message-syscalls.cc
1/* Copyright (c) 2026, Pedigree Developers. See LICENSE for licensing details. */
2#include "pedigree/kernel/LockGuard.h"
3#include "pedigree/kernel/process/ConditionVariable.h"
4#include "pedigree/kernel/process/Mutex.h"
5#include "pedigree/kernel/process/TerminationDeferral.h"
6#include "pedigree/kernel/syscallError.h"
7#include "pedigree/kernel/time/Time.h"
8#include "pedigree/kernel/utilities/List.h"
9#include "pedigree/kernel/utilities/SharedPointer.h"
10#include "pedigree/kernel/utilities/utility.h"
11
12#include "PosixSubsystem.h"
13#include "ipc-common.h"
14#include "sysv-message-syscalls.h"
15
16namespace {
17constexpr size_t MaximumQueues = 128;
18constexpr size_t MaximumMessage = 8192;
19constexpr size_t DefaultQueueBytes = 16384;
20constexpr size_t MaximumQueueBytes = 65536;
21constexpr int Create = 01000, Exclusive = 02000, Nowait = 04000;
22constexpr int Noerror = 010000, Except = 020000, Copy = 040000;
23constexpr int Remove = 0, Set = 1, Stat = 2, Info = 3;
24constexpr int MessageStat = 11, MessageInfo = 12, MessageStatAny = 13;
25
26struct QueueStatus {
27 PosixIpc::Permission permission;
28 int64_t sendTime, receiveTime, changeTime;
29 uint64_t bytes, count, capacity;
30 int32_t sender, receiver;
31 uint64_t unused[2];
32};
33static_assert(sizeof(QueueStatus) == 120, "Linux amd64 msqid_ds layout");
34static_assert(__builtin_offsetof(QueueStatus, capacity) == 88, "Linux msg_qbytes offset");
35
36struct QueueInfo {
37 int32_t pool, map, maximum, defaultBytes, queues, segmentSize, messages;
38 uint16_t segments, padding;
39};
40static_assert(sizeof(QueueInfo) == 32, "Linux msginfo layout");
41
42struct Message {
43 explicit Message(size_t length) : size(length), data(new uint8_t[length + sizeof(int64_t)]) {}
44 ~Message() {
45 delete[] data;
46 }
47 int64_t type() const {
48 int64_t value;
49 MemoryCopy(&value, data, sizeof(value));
50 return value;
51 }
52 size_t size;
53 uint8_t* data;
54};
55
56struct Queue {
57 uint64_t namespaceId = posix_ipc_namespace_id();
58 Queue(int identifier, int key, int mode) : id(identifier), removed(false), status{} {
59 PosixIpc::initialize(status.permission, key, mode, identifier / MaximumQueues);
60 status.capacity = DefaultQueueBytes;
61 status.changeTime = Time::getTime();
62 }
63 int id;
64 bool removed;
65 QueueStatus status;
66 Mutex lock;
67 ConditionVariable changed;
68 // Retire each payload as soon as a receive releases its queue capacity.
69 List<SharedPointer<Message>, 0> messages;
70};
71
72Mutex registryLock;
73SharedPointer<Queue> queues[MaximumQueues];
74uint32_t nextIdentifier = 0;
75
76SharedPointer<Queue> findQueue(int id, bool index = false) {
77 LockGuard<Mutex> guard(registryLock);
78 if (id >= 0) {
79 if (index) {
80 if (static_cast<size_t>(id) < MaximumQueues && queues[id] &&
81 queues[id]->namespaceId == posix_ipc_namespace_id()) {
82 return queues[id];
83 }
84 } else {
85 for (size_t i = 0; i < MaximumQueues; ++i) {
86 if (queues[i] && queues[i]->namespaceId == posix_ipc_namespace_id() &&
87 queues[i]->id == id) {
88 return queues[i];
89 }
90 }
91 }
92 }
93 SYSCALL_ERROR(InvalidArgument);
94 return SharedPointer<Queue>();
95}
96
97bool usable(Queue& queue, unsigned access) {
98 if (queue.removed) {
99 SYSCALL_ERROR(IdentifierRemoved);
100 return false;
101 }
102 if (!PosixIpc::allowed(queue.status.permission, access)) {
103 SYSCALL_ERROR(PermissionDenied);
104 return false;
105 }
106 return true;
107}
108
109bool wait(Queue& queue, LockGuard<Mutex>& guard) {
110 ConditionVariable::Error error = ConditionVariable::NoError;
111 if (queue.changed.wait(queue.lock, error)) {
112 return true;
113 }
115 guard.disown();
116 }
117 if (ConditionVariable::mutexAcquired(error) && queue.removed) {
118 SYSCALL_ERROR(IdentifierRemoved);
119 } else {
120 SYSCALL_ERROR(Interrupted);
121 }
122 return false;
123}
124
125int queueInfo(int command, void* buffer) {
126 QueueInfo result = {};
127 result.maximum = MaximumMessage;
128 result.defaultBytes = DefaultQueueBytes;
129 result.queues = MaximumQueues;
130 result.segmentSize = 16;
131 result.segments = 65535;
132 int highest = 0;
133 LockGuard<Mutex> guard(registryLock);
134 for (size_t i = 0; i < MaximumQueues; ++i) {
135 if (!queues[i] || queues[i]->namespaceId != posix_ipc_namespace_id()) {
136 continue;
137 }
138 highest = i;
139 if (command == MessageInfo) {
140 LockGuard<Mutex> queueGuard(queues[i]->lock);
141 ++result.pool;
142 result.map += queues[i]->status.count;
143 result.messages += queues[i]->status.bytes;
144 }
145 }
146 if (command == Info) {
147 result.pool = MaximumQueues * DefaultQueueBytes / 1024;
148 result.map = MaximumQueues;
149 result.messages = MaximumQueues * MaximumQueueBytes;
150 }
151 if (!PosixSubsystem::copyToUser(buffer, &result, sizeof(result))) {
152 SYSCALL_ERROR(BadAddress);
153 return -1;
154 }
155 return highest;
156}
157} // namespace
158
159int posix_msgget(int32_t key, int flags) {
160 TerminationDeferral lifetime;
161 if (flags & ~(0777 | Create | Exclusive)) {
162 SYSCALL_ERROR(InvalidArgument);
163 return -1;
164 }
165 LockGuard<Mutex> guard(registryLock);
166 size_t freeSlot = MaximumQueues;
167 for (size_t i = 0; i < MaximumQueues; ++i) {
168 if (!queues[i]) {
169 if (freeSlot == MaximumQueues) {
170 freeSlot = i;
171 }
172 } else if (key && queues[i]->namespaceId == posix_ipc_namespace_id() &&
173 queues[i]->status.permission.key == key) {
174 if ((flags & (Create | Exclusive)) == (Create | Exclusive)) {
175 SYSCALL_ERROR(FileExists);
176 return -1;
177 }
178 LockGuard<Mutex> queueGuard(queues[i]->lock);
179 const unsigned access = ((flags >> 6) | (flags >> 3) | flags) & 7;
180 return usable(*queues[i], access) ? queues[i]->id : -1;
181 }
182 }
183 if (key && !(flags & Create)) {
184 SYSCALL_ERROR(DoesNotExist);
185 return -1;
186 }
187 if (freeSlot == MaximumQueues || nextIdentifier > 0x7fffffffU) {
188 SYSCALL_ERROR(NoSpaceLeftOnDevice);
189 return -1;
190 }
191 const int id = nextIdentifier++;
192 queues[freeSlot].reset(new Queue(id, key, flags));
193 return id;
194}
195
196int posix_msgsnd(int id, const void* message, size_t size, int flags) {
197 TerminationDeferral lifetime;
198 if (size > MaximumMessage || (flags & ~Nowait)) {
199 SYSCALL_ERROR(InvalidArgument);
200 return -1;
201 }
202 SharedPointer<Message> snapshot(new Message(size));
203 if (!PosixSubsystem::copyFromUser(snapshot->data, message, size + sizeof(int64_t))) {
204 SYSCALL_ERROR(BadAddress);
205 return -1;
206 }
207 if (snapshot->type() <= 0) {
208 SYSCALL_ERROR(InvalidArgument);
209 return -1;
210 }
211 SharedPointer<Queue> queue = findQueue(id);
212 if (!queue) {
213 return -1;
214 }
215 LockGuard<Mutex> guard(queue->lock);
216 while (usable(*queue, 2)) {
217 // Counting empty records as well as bytes keeps zero-length sends bounded.
218 if (queue->status.bytes <= queue->status.capacity &&
219 size <= queue->status.capacity - queue->status.bytes &&
220 queue->status.count < queue->status.capacity) {
221 queue->messages.pushBack(snapshot);
222 queue->status.bytes += size;
223 ++queue->status.count;
224 queue->status.sender = PosixIpc::process()->getUserspaceId();
225 queue->status.sendTime = Time::getTime();
226 queue->changed.broadcast();
227 return 0;
228 }
229 if (flags & Nowait) {
230 SYSCALL_ERROR(NoMoreProcesses);
231 return -1;
232 }
233 if (!wait(*queue, guard)) {
234 return -1;
235 }
236 }
237 return -1;
238}
239
240ssize_t posix_msgrcv(int id, void* message, size_t size, int64_t type, int flags) {
241 TerminationDeferral lifetime;
242 if (size > static_cast<size_t>(0x7fffffffffffffffULL) ||
243 (flags & ~(Nowait | Noerror | Except | Copy)) ||
244 ((flags & Copy) && (!(flags & Nowait) || (flags & Except)))) {
245 SYSCALL_ERROR(InvalidArgument);
246 return -1;
247 }
248 SharedPointer<Queue> queue = findQueue(id);
249 if (!queue) {
250 return -1;
251 }
252 LockGuard<Mutex> guard(queue->lock);
253 while (usable(*queue, 4)) {
254 auto selected = queue->messages.end();
255 uint64_t position = 0;
256 // Unsigned negation also handles the minimum signed message selector.
257 const uint64_t ceiling = 0 - static_cast<uint64_t>(type);
258 for (auto it = queue->messages.begin(); it != queue->messages.end(); ++it, ++position) {
259 const int64_t candidate = (*it)->type();
260 if (flags & Copy) {
261 if (position == static_cast<uint64_t>(type)) {
262 selected = it;
263 break;
264 }
265 } else if (!type ||
266 (type > 0 && ((flags & Except) ? candidate != type : candidate == type))) {
267 selected = it;
268 break;
269 } else if (type < 0 && static_cast<uint64_t>(candidate) <= ceiling &&
270 (selected == queue->messages.end() || candidate < (*selected)->type())) {
271 selected = it;
272 }
273 }
274 if (selected != queue->messages.end()) {
275 Message& record = **selected;
276 if (record.size > size && !(flags & Noerror)) {
277 SYSCALL_ERROR(TooBig);
278 return -1;
279 }
280 if ((flags & Copy) && record.size > size) {
281 SYSCALL_ERROR(InvalidArgument);
282 return -1;
283 }
284 const size_t copied = size < record.size ? size : record.size;
285 // Hold the queue transaction through the copy so EFAULT cannot consume a
286 // message and another receiver cannot observe an uncommitted dequeue.
287 if (!PosixSubsystem::copyToUser(message, record.data, copied + sizeof(int64_t))) {
288 SYSCALL_ERROR(BadAddress);
289 return -1;
290 }
291 if (!(flags & Copy)) {
292 queue->status.bytes -= record.size;
293 --queue->status.count;
294 queue->status.receiver = PosixIpc::process()->getUserspaceId();
295 queue->status.receiveTime = Time::getTime();
296 queue->messages.erase(selected);
297 queue->changed.broadcast();
298 }
299 return copied;
300 }
301 if (flags & Nowait) {
302 SYSCALL_ERROR(NoMessage);
303 return -1;
304 }
305 if (!wait(*queue, guard)) {
306 return -1;
307 }
308 }
309 return -1;
310}
311
312int posix_msgctl(int id, int command, void* buffer) {
313 TerminationDeferral lifetime;
314 if (command == Info || command == MessageInfo) {
315 return queueInfo(command, buffer);
316 }
317 if (command != Remove && command != Set && command != Stat && command != MessageStat &&
318 command != MessageStatAny) {
319 SYSCALL_ERROR(InvalidArgument);
320 return -1;
321 }
322 QueueStatus input = {};
323 if (command == Set && !PosixSubsystem::copyFromUser(&input, buffer, sizeof(input))) {
324 SYSCALL_ERROR(BadAddress);
325 return -1;
326 }
327 SharedPointer<Queue> queue = findQueue(id, command == MessageStat || command == MessageStatAny);
328 if (!queue) {
329 return -1;
330 }
331 // Registry-before-queue is the only nested lock order, including removal.
332 LockGuard<Mutex> registryGuard(registryLock, command == Remove);
333 LockGuard<Mutex> guard(queue->lock);
334 const unsigned access = command == Stat || command == MessageStat ? 4 : 0;
335 if (!usable(*queue, access)) {
336 return -1;
337 }
338 if (command == Stat || command == MessageStat || command == MessageStatAny) {
339 if (!PosixSubsystem::copyToUser(buffer, &queue->status, sizeof(queue->status))) {
340 SYSCALL_ERROR(BadAddress);
341 return -1;
342 }
343 return command == Stat ? 0 : queue->id;
344 }
345 if (!PosixIpc::owner(queue->status.permission)) {
346 SYSCALL_ERROR(NotEnoughPermissions);
347 return -1;
348 }
349 if (command == Remove) {
350 queue->removed = true;
351 for (size_t i = 0; i < MaximumQueues; ++i) {
352 if (queues[i] && queues[i]->namespaceId == posix_ipc_namespace_id() && queues[i]->id == id) {
353 queues[i].reset();
354 break;
355 }
356 }
357 queue->messages.clear();
358 } else {
359 if (input.permission.uid == 0xffffffffU || input.permission.gid == 0xffffffffU) {
360 SYSCALL_ERROR(InvalidArgument);
361 return -1;
362 }
363 if (input.capacity > DefaultQueueBytes &&
364 !posix_namespace_capable(posix_ipc_owner(), PosixCapabilities::SysResource)) {
365 SYSCALL_ERROR(NotEnoughPermissions);
366 return -1;
367 }
368 if (input.capacity > MaximumQueueBytes) {
369 SYSCALL_ERROR(InvalidArgument);
370 return -1;
371 }
372 queue->status.permission.uid = input.permission.uid;
373 queue->status.permission.gid = input.permission.gid;
374 queue->status.permission.mode = input.permission.mode & 0777;
375 queue->status.capacity = input.capacity;
376 queue->status.changeTime = Time::getTime();
377 }
378 queue->changed.broadcast();
379 return 0;
380}
381
382void posix_msg_namespace_exit(uint64_t identity) {
383 LockGuard<Mutex> registryGuard(registryLock);
384 for (auto& entry : queues) {
385 if (entry && entry->namespaceId == identity) {
386 auto queue = entry;
387 LockGuard<Mutex> guard(queue->lock);
388 queue->removed = true;
389 queue->messages.clear();
390 queue->changed.broadcast();
391 entry.reset();
392 }
393 }
394}
static bool mutexAcquired(Error error)
Definition List.h:61
void disown()
Definition LockGuard.h:69
Definition Mutex.h:56
static bool copyFromUser(void *destination, const void *source, size_t count, size_t elementSize=1)
static bool copyToUser(void *destination, const void *source, size_t count, size_t elementSize=1)
size_t getUserspaceId() const
Definition Process.h:504