The Pedigree Project 0.1
recvmmsg-syscalls.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/process/Readiness.h"
3#include "pedigree/kernel/process/Semaphore.h"
4#include "pedigree/kernel/process/TerminationDeferral.h"
5#include "pedigree/kernel/process/Thread.h"
6#include "pedigree/kernel/processor/Processor.h"
7#include "pedigree/kernel/processor/ProcessorInformation.h"
8#include "pedigree/kernel/syscallError.h"
9#include "pedigree/kernel/time/Time.h"
10
11#include <errno.h>
12#include <limits.h>
13
14#include "FileDescriptor.h"
15#include "PosixSubsystem.h"
16#include "net-syscalls.h"
17#include "poll-syscalls.h"
18#include "recvmmsg-syscalls.h"
19
20namespace {
21class ReceiveObserver final : public ReadinessObserver {
22 public:
23 explicit ReceiveObserver(const SharedPointer<Semaphore>& semaphore) : m_Semaphore(semaphore) {}
24 void readinessChanged(ReadyMask) override {
25 m_Semaphore->release();
26 }
27
28 private:
29 SharedPointer<Semaphore> m_Semaphore;
30};
31
32struct ReceiveResult {
33 int count;
34 int error;
35};
36
37bool deadlineExpired(const PollDeadline& deadline) {
38 return deadline.type == PollDeadlineType::Immediate ||
39 (deadline.type == PollDeadlineType::Finite && Time::getTicks() >= deadline.expires);
40}
41
42// The socket registration outlives every receive attempt and wakeup. Numeric
43// descriptor reuse cannot redirect either the predicate or its cleanup.
44class ReceiveWait {
45 public:
46 bool prepare(NetworkSyscalls& socket) {
47 if (m_Subscription)
48 return true;
49 m_Semaphore = SharedPointer<Semaphore>::tryAllocate(0, true);
50 if (!m_Semaphore)
51 return false;
52 m_Observer = SharedPointer<ReadinessObserver>::tryAdopt(new ReceiveObserver(m_Semaphore));
53 return m_Observer && socket.subscribeReadiness(
54 ReadyRead | ReadyError | ReadyHangup | ReadyReadHangup | ReadyInvalid,
55 m_Observer, m_Subscription);
56 }
57
58 int wait(NetworkSyscalls& socket, const PollDeadline& deadline, Thread& thread) {
59 for (;;) {
60 if (socket.queryReady(true, false) &
61 (ReadyRead | ReadyError | ReadyHangup | ReadyReadHangup | ReadyInvalid))
62 return 1;
63 if (thread.getInterruptionReason() == Thread::InterruptedBySignal)
64 return -1;
65 if (deadlineExpired(deadline))
66 return 0;
67
68 size_t seconds = 0, microseconds = 0;
69 if (deadline.type == PollDeadlineType::Finite) {
70 const uint64_t now = Time::getTicks();
71 if (now >= deadline.expires)
72 return 0;
73 const uint64_t remaining = deadline.expires - now;
74 const uint64_t rounded = remaining / Time::Multiplier::Microsecond +
75 (remaining % Time::Multiplier::Microsecond != 0);
76 seconds = static_cast<size_t>(rounded / 1000000);
77 microseconds = static_cast<size_t>(rounded % 1000000);
78 }
79 Semaphore::SemaphoreError error = Semaphore::NoError;
80 const bool signalled = m_Semaphore->acquireWithError(1, seconds, microseconds, error);
81 if (signalled) {
82 while (m_Semaphore->tryAcquire()) {
83 }
84 } else if (error != Semaphore::TimedOut) {
85 // A final readiness query lets already-received data win a signal race.
86 return socket.queryReady(true, false) &
87 (ReadyRead | ReadyError | ReadyHangup | ReadyReadHangup | ReadyInvalid)
88 ? 1
89 : -1;
90 }
91 }
92 }
93
94 private:
95 SharedPointer<Semaphore> m_Semaphore;
97 ReadinessSubscription m_Subscription;
98};
99
100ReceiveResult receiveMessages(int fd, LinuxMmsghdr* messages, unsigned int count,
101 unsigned int flags, LinuxKernelTimespec* timeout) {
102 TerminationDeferral lifetime;
103 Thread& thread = *Processor::information().getCurrentThread();
104 thread.clearInterruption();
105 LinuxKernelTimespec duration = {};
106 if (timeout) {
107 if (!PosixSubsystem::copyFromUser(&duration, timeout, sizeof(duration)))
108 return {-1, EFAULT};
109 if (duration.tv_sec < 0 || duration.tv_nsec < 0 ||
110 duration.tv_nsec >= static_cast<int64_t>(Time::Multiplier::Second))
111 return {-1, EINVAL};
112 }
113 const PollDeadline deadline = posix_poll_deadline(timeout ? &duration : nullptr);
114 auto* subsystem = static_cast<PosixSubsystem*>(thread.getParent()->getSubsystem());
115 DescriptorLease descriptor;
116 if (!subsystem || !subsystem->acquireFileDescriptor(fd, descriptor))
117 return {-1, EBADF};
118 if (!descriptor->networkImpl)
119 return {-1, ENOTSOCK};
120 NetworkSyscalls& socket = *descriptor->networkImpl;
121 const int deferred = socket.takeReceiveError();
122 if (deferred)
123 return {-1, deferred};
124 if (!count)
125 return {0, 0};
126 if (count > static_cast<unsigned int>(INT_MAX))
127 return {-1, EINVAL};
128
129 ReceiveWait wait;
130 unsigned int received = 0;
131 int error = 0;
132 bool interrupted = false;
133 uintptr_t address = reinterpret_cast<uintptr_t>(messages);
134 while (received < count) {
135 if (address > ~uintptr_t(0) - sizeof(LinuxMmsghdr)) {
136 error = EFAULT;
137 break;
138 }
139 auto* message = reinterpret_cast<LinuxMmsghdr*>(address);
140 const int receiveFlags = static_cast<int>(flags & ~MSG_WAITFORONE) | MSG_DONTWAIT;
141 interrupted |= thread.getInterruptionReason() == Thread::InterruptedBySignal;
142 syscallError(0);
143 const ssize_t bytes = posix_recvmsg_user_descriptor(descriptor, &message->msg_hdr, receiveFlags,
144 &message->msg_len);
145 if (bytes >= 0) {
146 ++received;
147 address += sizeof(LinuxMmsghdr);
148 if (timeout && deadlineExpired(deadline))
149 break;
150 continue;
151 }
152 error = thread.getErrno();
153 if (!error)
154 error = EIO;
155 if (error != EAGAIN)
156 break;
157 if (interrupted) {
158 error = EINTR;
159 break;
160 }
161 if ((flags & MSG_DONTWAIT) || !socket.isBlocking() || (received && (flags & MSG_WAITFORONE)))
162 break;
163 if (deadlineExpired(deadline)) {
164 error = 0;
165 break;
166 }
167 const ReadyMask readyState = socket.queryReady(true, false);
168 if (readyState & ReadyInvalid) {
169 error = EBADF;
170 break;
171 }
172 if (readyState & ReadyError) {
173 int socketError = 0;
174 socklen_t length = sizeof(socketError);
175 if (socket.getsockopt(SOL_SOCKET, SO_ERROR, &socketError, &length) < 0) {
176 error = thread.getErrno() ? thread.getErrno() : EIO;
177 break;
178 }
179 if (socketError) {
180 error = socketError;
181 break;
182 }
183 }
184 if (!wait.prepare(socket)) {
185 error = ENOMEM;
186 break;
187 }
188 const int ready = wait.wait(socket, deadline, thread);
189 if (ready <= 0) {
190 error = ready ? EINTR : 0;
191 break;
192 }
193 }
194
195 if (received && error && error != EAGAIN)
196 socket.deferReceiveError(error);
197 if (timeout && received) {
198 LinuxKernelTimespec remaining = {};
199 if (deadline.type == PollDeadlineType::Finite) {
200 const uint64_t now = Time::getTicks();
201 if (now < deadline.expires) {
202 const uint64_t nanoseconds = deadline.expires - now;
203 remaining.tv_sec = nanoseconds / Time::Multiplier::Second;
204 remaining.tv_nsec = nanoseconds % Time::Multiplier::Second;
205 }
206 }
207 if (!PosixSubsystem::copyToUser(timeout, &remaining, sizeof(remaining)))
208 return {-1, EFAULT};
209 }
210 if (received || !error) {
211 thread.clearInterruption();
212 return {static_cast<int>(received), 0};
213 }
214 return {-1, error};
215}
216} // namespace
217
218int posix_recvmmsg(int fd, LinuxMmsghdr* messages, unsigned int count, unsigned int flags,
219 LinuxKernelTimespec* timeout) {
220 if (flags & 0x80000000U) {
221 syscallError(EINVAL);
222 return -1;
223 }
224 const ReceiveResult result = receiveMessages(fd, messages, count, flags, timeout);
225 syscallError(result.error);
226 return result.count;
227}
SharedPointer< NetworkSyscalls > networkImpl
Network syscall implementation for this descriptor (if it's a socket).
virtual ReadyMask queryReady(bool reading, bool writing)
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)
static ProcessorInformation & information()
virtual void readinessChanged(ReadyMask mask)=0
MUST_USE_RESULT bool subscribeReadiness(ReadyMask interest, const SharedPointer< ReadinessObserver > &observer, ReadinessSubscription &subscription)
Definition Readiness.cc:181
static SharedPointer< T > tryAdopt(T *ptr)
static SharedPointer< T > tryAllocate(Args...)
size_t getErrno()
Definition Thread.h:477
Process * getParent() const
Definition Thread.h:340