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
105uint64_t PosixMessageQueue::namespaceId() const {
106 return m_State->namespaceId;
107}
108
109const String& PosixMessageQueue::name() const {
110 return m_State->name;
111}
112
113bool PosixMessageQueue::mayOpen(Process* process, int flags) const {
114 const auto& state = *m_State;
115 const int64_t uid = process->getEffectiveUserId();
116 if (posix_namespace_capable(posix_ipc_owner(), PosixCapabilities::DacOverride)) {
117 return true;
118 }
119 unsigned mode = state.mode;
120 bool group = state.gid == process->getEffectiveGroupId();
121 if (!group) {
122 Vector<int64_t> groups;
123 process->getSupplementalGroupIds(groups);
124 for (size_t n = 0; n < groups.count(); ++n) {
125 group |= groups[n] == state.gid;
126 }
127 }
128 mode >>= uid == state.uid ? 6 : group ? 3 : 0;
129 const int access = flags & O_ACCMODE;
130 return (access == O_WRONLY || (mode & 4)) && (access == O_RDONLY || (mode & 2));
131}
132
133bool PosixMessageQueue::mayUnlink(Process* process) const {
134 const int64_t uid = process->getEffectiveUserId();
135 return uid == m_State->uid ||
136 posix_namespace_capable(posix_ipc_owner(), PosixCapabilities::Fowner);
137}
138
139int PosixMessageQueue::send(const char* data, size_t length, unsigned priority, bool nonblock,
140 const LinuxMqTimespec* timeout) {
141 auto& state = *m_State;
142 if (priority >= 32768) {
143 SYSCALL_ERROR(InvalidArgument);
144 return -1;
145 }
146 if (length > state.size) {
147 syscallError(EMSGSIZE);
148 return -1;
149 }
150 Time::Timestamp deadline;
151 if (!copyDeadline(timeout, deadline)) {
152 return -1;
153 }
154 auto copy = UniqueArray<uint8_t>::allocate(length ? length : 1);
155 if (!PosixSubsystem::copyFromUser(copy.get(), data, length)) {
156 SYSCALL_ERROR(BadAddress);
157 return -1;
158 }
159 state.lock.acquire();
160 while (state.count == state.capacity) {
161 if (!waitQueue(state.writers, state.lock, deadline, nonblock)) {
162 state.lock.release();
163 return -1;
164 }
165 }
166 const bool wasEmpty = !state.count;
167 const int slot = state.free;
168 auto& message = state.messages.get()[slot];
169 state.free = message.next;
170 message.length = length;
171 message.priority = priority;
172 MemoryCopy(state.storage.get() + slot * state.size, copy.get(), length);
173 int* insertion = &state.head;
174 while (*insertion >= 0 && state.messages.get()[*insertion].priority >= priority) {
175 insertion = &state.messages.get()[*insertion].next;
176 }
177 message.next = *insertion;
178 *insertion = slot;
179 ++state.count;
180 if (wasEmpty) {
181 ++state.generations.read;
182 if (!state.receiverCount) {
183 state.notification.complete(false);
184 }
185 }
186 state.lock.release();
187 state.readers.broadcast();
188 notifyReadiness(ReadyRead | ReadyWrite);
189 return 0;
190}
191
192int PosixMessageQueue::receive(char* data, size_t length, unsigned* priority, bool nonblock,
193 const LinuxMqTimespec* timeout) {
194 auto& state = *m_State;
195 if (length < state.size) {
196 syscallError(EMSGSIZE);
197 return -1;
198 }
199 Time::Timestamp deadline;
200 if (!copyDeadline(timeout, deadline)) {
201 return -1;
202 }
203 state.lock.acquire();
204 while (!state.count) {
205 ++state.receiverCount;
206 const bool resumed = waitQueue(state.readers, state.lock, deadline, nonblock);
207 --state.receiverCount;
208 if (!resumed) {
209 state.lock.release();
210 return -1;
211 }
212 }
213 const int slot = state.head;
214 auto& message = state.messages.get()[slot];
215 const size_t received = message.length;
216 // Retain the head until every result has reached userspace. A bad priority
217 // pointer or a concurrently unmapped data buffer cannot consume a message.
218 if ((priority && !PosixSubsystem::copyToUser(priority, &message.priority, sizeof(*priority))) ||
219 !PosixSubsystem::copyToUser(data, state.storage.get() + slot * state.size, received)) {
220 state.lock.release();
221 SYSCALL_ERROR(BadAddress);
222 return -1;
223 }
224 state.head = message.next;
225 message.next = state.free;
226 state.free = slot;
227 if (state.count-- == state.capacity) {
228 ++state.generations.write;
229 }
230 state.lock.release();
231 state.writers.broadcast();
232 notifyReadiness(ReadyRead | ReadyWrite);
233 return static_cast<int>(received);
234}
235
236int PosixMessageQueue::attributes(FileDescriptor& descriptor, const LinuxMqAttr* requested,
237 LinuxMqAttr* previous) {
238 LinuxMqAttr attr = {};
239 if (requested) {
240 if (!PosixSubsystem::copyFromUser(&attr, requested, sizeof(attr))) {
241 SYSCALL_ERROR(BadAddress);
242 return -1;
243 }
244 if (attr.flags & ~static_cast<int64_t>(O_NONBLOCK)) {
245 SYSCALL_ERROR(InvalidArgument);
246 return -1;
247 }
248 }
249 auto& state = *m_State;
250 LockGuard<Mutex> guard(state.lock);
251 LinuxMqAttr old = {descriptor.getStatusFlags() & O_NONBLOCK,
252 static_cast<int64_t>(state.capacity),
253 static_cast<int64_t>(state.size),
254 static_cast<int64_t>(state.count),
255 {}};
256 if (previous && !PosixSubsystem::copyToUser(previous, &old, sizeof(old))) {
257 SYSCALL_ERROR(BadAddress);
258 return -1;
259 }
260 if (requested) {
261 if (attr.flags & O_NONBLOCK) {
262 descriptor.addStatusFlag(O_NONBLOCK);
263 } else {
264 descriptor.removeStatusFlag(O_NONBLOCK);
265 }
266 }
267 return 0;
268}
269
270ReadyMask PosixMessageQueue::queryReady() {
271 LockGuard<Mutex> guard(m_State->lock);
272 return (m_State->count ? ReadyRead : ReadyNone) |
273 (m_State->count < m_State->capacity ? ReadyWrite : ReadyNone);
274}
275
277 LockGuard<Mutex> guard(m_State->lock);
278 return m_State->generations;
279}
280
281void MqueueNotification::complete(bool removed) {
282 if (!process) {
283 return;
284 }
285 if (socket) {
286 socket->asMqueueNetlink()->deliverCookie(cookie, removed);
287 } else if (!removed && event.notify == 0) {
288 // The queue lock also serializes exit cancellation. Pin the exact process
289 // before dropping the registration so PID recycling cannot retarget it.
291 if (Scheduler::instance().acquireProcess(target, process)) {
293 if (target->acquireProcessSignalThread(thread)) {
294 auto* subsystem = static_cast<PosixSubsystem*>(target->getSubsystem());
295 subsystem->queueSignalDelivery(thread.get(), event.signal, nullptr, -3, true, event.value);
296 }
297 }
298 }
299 process = nullptr;
300 socket.reset();
301}
302
303int PosixMessageQueue::notify(const LinuxMqSigevent* userEvent) {
304 MqueueNotification notification;
305 Process* process = Processor::information().getCurrentThread()->getParent();
306 if (userEvent) {
307 if (!PosixSubsystem::copyFromUser(&notification.event, userEvent, sizeof(notification.event))) {
308 SYSCALL_ERROR(BadAddress);
309 return -1;
310 }
311 const auto& event = notification.event;
312 if (event.notify < 0 || event.notify > 2 ||
313 (event.notify == 0 && (event.signal <= 0 || static_cast<size_t>(event.signal) >
314 PosixSubsystem::MaximumSupportedSignal))) {
315 SYSCALL_ERROR(InvalidArgument);
316 return -1;
317 }
318 if (event.notify == 2) {
319 DescriptorLease socket;
320 if (!acquireDescriptor(event.signal, socket) || !socket->networkImpl ||
321 !socket->networkImpl->asMqueueNetlink()) {
322 SYSCALL_ERROR(BadFileDescriptor);
323 return -1;
324 }
325 if (!PosixSubsystem::copyFromUser(notification.cookie,
326 reinterpret_cast<const void*>(event.value), 32)) {
327 SYSCALL_ERROR(BadAddress);
328 return -1;
329 }
330 notification.socket = socket->networkImpl;
331 }
332 notification.process = process;
333 notification.pid = process->getId();
334 }
335 auto& state = *m_State;
336 LockGuard<Mutex> guard(state.lock);
337 if (!userEvent) {
338 if (state.notification.process == process) {
339 state.notification.complete(true);
340 }
341 return 0;
342 }
343 if (state.notification.process) {
344 SYSCALL_ERROR(DeviceBusy);
345 return -1;
346 }
347 if (notification.socket && !notification.socket->asMqueueNetlink()->reserveCookie()) {
348 return -1;
349 }
350 state.notification = notification;
351 return 0;
352}
353
354void PosixMessageQueue::cancelNotification(size_t pid) {
355 LockGuard<Mutex> guard(m_State->lock);
356 if (m_State->notification.process && m_State->notification.pid == pid) {
357 m_State->notification.complete(true);
358 }
359}
360
361void PosixMessageQueue::clockChanged() {
362 LockGuard<Mutex> guard(m_State->lock);
363 m_State->readers.broadcast();
364 m_State->writers.broadcast();
365}
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
virtual MqueueNetlinkSocket * asMqueueNetlink()
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 getId()
Definition Process.h:499
MUST_USE_RESULT bool acquireProcessSignalThread(ThreadLease &lease)
Definition Process.cc:1407
Process * getParent()
Definition Process.h:620
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
A vector / dynamic array.
Definition Vector.h:33
size_t count() const
Definition Vector.h:270