The Pedigree Project 0.1
mqueue-state.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "mqueue-state.h"
3#include "pedigree/kernel/LockGuard.h"
4#include "pedigree/kernel/process/Scheduler.h"
5#include "pedigree/kernel/processor/Processor.h"
6#include "pedigree/kernel/processor/ProcessorInformation.h"
7#include "pedigree/kernel/syscallError.h"
8#include "pedigree/kernel/utilities/utility.h"
9
10#include <errno.h>
11#include <fcntl.h>
12
13#include "modules/subsys/posix/PosixSubsystem.h"
14#include "mqueue-netlink.h"
15
16namespace {
17bool copyDeadline(const LinuxMqTimespec* user, Time::Timestamp& deadline) {
18 deadline = Time::Infinity;
19 if (!user) {
20 return true;
21 }
22 LinuxMqTimespec value = {};
23 if (!PosixSubsystem::copyFromUser(&value, user, sizeof(value))) {
24 SYSCALL_ERROR(BadAddress);
25 return false;
26 }
27 if (value.seconds < 0 || value.nanoseconds < 0 || value.nanoseconds >= 1000000000) {
28 SYSCALL_ERROR(InvalidArgument);
29 return false;
30 }
31 const uint64_t seconds = static_cast<uint64_t>(value.seconds);
32 const uint64_t nanos = static_cast<uint64_t>(value.nanoseconds);
33 deadline = seconds > (Time::Infinity - 1 - nanos) / Time::Multiplier::Second
34 ? Time::Infinity - 1
35 : seconds * Time::Multiplier::Second + nanos;
36 return true;
37}
38
39bool waitQueue(ConditionVariable& condition, Mutex& lock, Time::Timestamp deadline, bool nonblock) {
40 if (nonblock) {
41 SYSCALL_ERROR(NoMoreProcesses);
42 return false;
43 }
44 Time::Timestamp remaining = Time::Infinity;
45 if (deadline != Time::Infinity) {
46 const Time::Timestamp now = Time::getTimeNanoseconds();
47 if (deadline <= now) {
48 SYSCALL_ERROR(TimedOut);
49 return false;
50 }
51 remaining = deadline - now;
52 }
53 ConditionVariable::Error error = ConditionVariable::NoError;
54 if (condition.wait(lock, remaining, error) || error == ConditionVariable::TimedOut) {
55 return true;
56 }
57 SYSCALL_ERROR(Interrupted);
58 return false;
59}
60} // namespace
61
62MqueueState::MqueueState(const String& queueName, size_t maxMessages, size_t messageSize,
63 int64_t owner, int64_t group, unsigned permissions)
64 : lock(),
65 readers(),
66 writers(),
67 name(queueName),
68 capacity(maxMessages),
69 size(messageSize),
70 count(0),
71 receiverCount(0),
72 uid(owner),
73 gid(group),
74 mode(permissions),
75 head(-1),
76 free(0),
77 messages(UniqueArray<Message>::allocate(maxMessages)),
78 storage(UniqueArray<uint8_t>::allocate(maxMessages * messageSize)),
79 notification(),
80 generations() {
81 for (size_t n = 0; n < capacity; ++n) {
82 messages.get()[n].next = n + 1 == capacity ? -1 : static_cast<int>(n + 1);
83 }
84}
85
86PosixMessageQueue::PosixMessageQueue(const String& name, size_t capacity, size_t size, int64_t uid,
87 int64_t gid, unsigned mode)
88 : m_State(new MqueueState(name, capacity, size, uid, gid, mode)) {}
89
90PosixMessageQueue::~PosixMessageQueue() {
91 {
92 LockGuard<Mutex> guard(g_MqueueRegistryLock);
93 for (auto it = g_Mqueues.begin(); it != g_Mqueues.end(); ++it) {
94 if (*it == this) {
95 g_Mqueues.erase(it);
96 break;
97 }
98 }
99 }
100 m_State->notification.complete(true);
102 delete m_State;
103}
104
105const String& PosixMessageQueue::name() const {
106 return m_State->name;
107}
108
109bool PosixMessageQueue::mayOpen(Process* process, int flags) const {
110 const auto& state = *m_State;
111 const int64_t uid = process->getEffectiveUserId();
112 if (uid == 0) {
113 return true;
114 }
115 unsigned mode = state.mode;
116 bool group = state.gid == process->getEffectiveGroupId();
117 if (!group) {
118 Vector<int64_t> groups;
119 process->getSupplementalGroupIds(groups);
120 for (size_t n = 0; n < groups.count(); ++n) {
121 group |= groups[n] == state.gid;
122 }
123 }
124 mode >>= uid == state.uid ? 6 : group ? 3 : 0;
125 const int access = flags & O_ACCMODE;
126 return (access == O_WRONLY || (mode & 4)) && (access == O_RDONLY || (mode & 2));
127}
128
129bool PosixMessageQueue::mayUnlink(Process* process) const {
130 const int64_t uid = process->getEffectiveUserId();
131 return uid == 0 || uid == m_State->uid;
132}
133
134int PosixMessageQueue::send(const char* data, size_t length, unsigned priority, bool nonblock,
135 const LinuxMqTimespec* timeout) {
136 auto& state = *m_State;
137 if (priority >= 32768) {
138 SYSCALL_ERROR(InvalidArgument);
139 return -1;
140 }
141 if (length > state.size) {
142 syscallError(EMSGSIZE);
143 return -1;
144 }
145 Time::Timestamp deadline;
146 if (!copyDeadline(timeout, deadline)) {
147 return -1;
148 }
149 auto copy = UniqueArray<uint8_t>::allocate(length ? length : 1);
150 if (!PosixSubsystem::copyFromUser(copy.get(), data, length)) {
151 SYSCALL_ERROR(BadAddress);
152 return -1;
153 }
154 state.lock.acquire();
155 while (state.count == state.capacity) {
156 if (!waitQueue(state.writers, state.lock, deadline, nonblock)) {
157 state.lock.release();
158 return -1;
159 }
160 }
161 const bool wasEmpty = !state.count;
162 const int slot = state.free;
163 auto& message = state.messages.get()[slot];
164 state.free = message.next;
165 message.length = length;
166 message.priority = priority;
167 MemoryCopy(state.storage.get() + slot * state.size, copy.get(), length);
168 int* insertion = &state.head;
169 while (*insertion >= 0 && state.messages.get()[*insertion].priority >= priority) {
170 insertion = &state.messages.get()[*insertion].next;
171 }
172 message.next = *insertion;
173 *insertion = slot;
174 ++state.count;
175 if (wasEmpty) {
176 ++state.generations.read;
177 if (!state.receiverCount) {
178 state.notification.complete(false);
179 }
180 }
181 state.lock.release();
182 state.readers.broadcast();
183 notifyReadiness(ReadyRead | ReadyWrite);
184 return 0;
185}
186
187int PosixMessageQueue::receive(char* data, size_t length, unsigned* priority, bool nonblock,
188 const LinuxMqTimespec* timeout) {
189 auto& state = *m_State;
190 if (length < state.size) {
191 syscallError(EMSGSIZE);
192 return -1;
193 }
194 Time::Timestamp deadline;
195 if (!copyDeadline(timeout, deadline)) {
196 return -1;
197 }
198 state.lock.acquire();
199 while (!state.count) {
200 ++state.receiverCount;
201 const bool resumed = waitQueue(state.readers, state.lock, deadline, nonblock);
202 --state.receiverCount;
203 if (!resumed) {
204 state.lock.release();
205 return -1;
206 }
207 }
208 const int slot = state.head;
209 auto& message = state.messages.get()[slot];
210 const size_t received = message.length;
211 // Retain the head until every result has reached userspace. A bad priority
212 // pointer or a concurrently unmapped data buffer cannot consume a message.
213 if ((priority && !PosixSubsystem::copyToUser(priority, &message.priority, sizeof(*priority))) ||
214 !PosixSubsystem::copyToUser(data, state.storage.get() + slot * state.size, received)) {
215 state.lock.release();
216 SYSCALL_ERROR(BadAddress);
217 return -1;
218 }
219 state.head = message.next;
220 message.next = state.free;
221 state.free = slot;
222 if (state.count-- == state.capacity) {
223 ++state.generations.write;
224 }
225 state.lock.release();
226 state.writers.broadcast();
227 notifyReadiness(ReadyRead | ReadyWrite);
228 return static_cast<int>(received);
229}
230
231int PosixMessageQueue::attributes(FileDescriptor& descriptor, const LinuxMqAttr* requested,
232 LinuxMqAttr* previous) {
233 LinuxMqAttr attr = {};
234 if (requested) {
235 if (!PosixSubsystem::copyFromUser(&attr, requested, sizeof(attr))) {
236 SYSCALL_ERROR(BadAddress);
237 return -1;
238 }
239 if (attr.flags & ~static_cast<int64_t>(O_NONBLOCK)) {
240 SYSCALL_ERROR(InvalidArgument);
241 return -1;
242 }
243 }
244 auto& state = *m_State;
245 LockGuard<Mutex> guard(state.lock);
246 LinuxMqAttr old = {descriptor.getStatusFlags() & O_NONBLOCK,
247 static_cast<int64_t>(state.capacity),
248 static_cast<int64_t>(state.size),
249 static_cast<int64_t>(state.count),
250 {}};
251 if (previous && !PosixSubsystem::copyToUser(previous, &old, sizeof(old))) {
252 SYSCALL_ERROR(BadAddress);
253 return -1;
254 }
255 if (requested) {
256 if (attr.flags & O_NONBLOCK) {
257 descriptor.addStatusFlag(O_NONBLOCK);
258 } else {
259 descriptor.removeStatusFlag(O_NONBLOCK);
260 }
261 }
262 return 0;
263}
264
265ReadyMask PosixMessageQueue::queryReady() {
266 LockGuard<Mutex> guard(m_State->lock);
267 return (m_State->count ? ReadyRead : ReadyNone) |
268 (m_State->count < m_State->capacity ? ReadyWrite : ReadyNone);
269}
270
272 LockGuard<Mutex> guard(m_State->lock);
273 return m_State->generations;
274}
275
276void MqueueNotification::complete(bool removed) {
277 if (!process) {
278 return;
279 }
280 if (socket) {
281 static_cast<MqueueNetlinkSocket*>(socket.get())->deliverCookie(cookie, removed);
282 } else if (!removed && event.notify == 0) {
283 // The queue lock also serializes exit cancellation. Pin the exact process
284 // before dropping the registration so PID recycling cannot retarget it.
286 if (Scheduler::instance().acquireProcess(target, process)) {
288 if (target->acquireProcessSignalThread(thread)) {
289 auto* subsystem = static_cast<PosixSubsystem*>(target->getSubsystem());
290 subsystem->queueSignalDelivery(thread.get(), event.signal, nullptr, -3, true, event.value);
291 }
292 }
293 }
294 process = nullptr;
295 socket.reset();
296}
297
298int PosixMessageQueue::notify(const LinuxMqSigevent* userEvent) {
299 MqueueNotification notification;
300 Process* process = Processor::information().getCurrentThread()->getParent();
301 if (userEvent) {
302 if (!PosixSubsystem::copyFromUser(&notification.event, userEvent, sizeof(notification.event))) {
303 SYSCALL_ERROR(BadAddress);
304 return -1;
305 }
306 const auto& event = notification.event;
307 if (event.notify < 0 || event.notify > 2 ||
308 (event.notify == 0 && (event.signal <= 0 || static_cast<size_t>(event.signal) >
309 PosixSubsystem::MaximumSupportedSignal))) {
310 SYSCALL_ERROR(InvalidArgument);
311 return -1;
312 }
313 if (event.notify == 2) {
314 DescriptorLease socket;
315 if (!acquireDescriptor(event.signal, socket) || !socket->networkImpl ||
316 socket->networkImpl->getDomain() != 16) {
317 SYSCALL_ERROR(BadFileDescriptor);
318 return -1;
319 }
320 if (!PosixSubsystem::copyFromUser(notification.cookie,
321 reinterpret_cast<const void*>(event.value), 32)) {
322 SYSCALL_ERROR(BadAddress);
323 return -1;
324 }
325 notification.socket = socket->networkImpl;
326 }
327 notification.process = process;
328 notification.pid = process->getUserspaceId();
329 }
330 auto& state = *m_State;
331 LockGuard<Mutex> guard(state.lock);
332 if (!userEvent) {
333 if (state.notification.process == process) {
334 state.notification.complete(true);
335 }
336 return 0;
337 }
338 if (state.notification.process) {
339 SYSCALL_ERROR(DeviceBusy);
340 return -1;
341 }
342 if (notification.socket &&
343 !static_cast<MqueueNetlinkSocket*>(notification.socket.get())->reserveCookie()) {
344 return -1;
345 }
346 state.notification = notification;
347 return 0;
348}
349
350void PosixMessageQueue::cancelNotification(size_t pid) {
351 LockGuard<Mutex> guard(m_State->lock);
352 if (m_State->notification.process && m_State->notification.pid == pid) {
353 m_State->notification.complete(true);
354 }
355}
356
357void PosixMessageQueue::clockChanged() {
358 LockGuard<Mutex> guard(m_State->lock);
359 m_State->readers.broadcast();
360 m_State->writers.broadcast();
361}
MUST_USE_RESULT bool wait(Mutex &mutex, Time::Timestamp &timeout, Error &error, WaitQueue::StackDiscardCleanup onStackDiscard=nullptr, void *stackDiscardContext=nullptr)
void removeStatusFlag(int flag)
Helper to remove a single flag from the status flags.
int getStatusFlags() const
Get current status flags.
void addStatusFlag(int newFlag)
Helper to add a single flag to the status flags.
SharedPointer< NetworkSyscalls > networkImpl
Network syscall implementation for this descriptor (if it's a socket).
Definition Mutex.h:56
ReadinessGenerations readinessGenerations() override
static bool copyFromUser(void *destination, const void *source, size_t count, size_t elementSize=1)
SignalDeliveryResult queueSignalDelivery(Thread *target, size_t sig, uint32_t *flags=nullptr, int32_t signalCode=0, bool processDirected=false, uint64_t signalValue=0, const SharedPointer< SignalEventState > &state=SharedPointer< SignalEventState >())
static bool copyToUser(void *destination, const void *source, size_t count, size_t elementSize=1)
size_t getUserspaceId() const
Definition Process.h:468
MUST_USE_RESULT bool acquireProcessSignalThread(ThreadLease &lease)
Definition Process.cc:1270
Process * getParent()
Definition Process.h:568
static ProcessorInformation & information()
void notifyReadiness(ReadyMask mask)
Definition Readiness.cc:201
void closeReadiness(ReadyMask mask=ReadyInvalid|ReadyHangup)
Definition Readiness.cc:208
static Scheduler & instance()
Definition Scheduler.h:96
MUST_USE_RESULT bool acquireProcess(ProcessLease &lease, size_t n)
Definition Scheduler.cc:264
T * get() const
A vector / dynamic array.
Definition Vector.h:33
size_t count() const
Definition Vector.h:270