The Pedigree Project 0.1
wait-syscalls.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/process/Thread.h"
3#include "pedigree/kernel/processor/Processor.h"
4#include "pedigree/kernel/processor/ProcessorInformation.h"
5#include "pedigree/kernel/processor/VirtualAddressSpace.h"
6#include "pedigree/kernel/syscallError.h"
7#include "pedigree/kernel/time/Time.h"
8
9#include "PosixProcess.h"
10#include "PosixSubsystem.h"
11#include "linux-resource-abi.h"
12#include "wait-state.h"
13#include "wait-syscalls.h"
14
15namespace {
16constexpr unsigned NoHang = 1, Stopped = 2, Exited = 4, Continued = 8;
17constexpr unsigned NoWait = 0x01000000;
18constexpr unsigned DeferredOptions = 0xE0000000; // __WNOTHREAD, __WALL, __WCLONE.
19
20struct LinuxWaitInformation {
21 int32_t signal;
22 int32_t error;
23 int32_t code;
24 uint32_t reserved;
25 int32_t pid;
26 uint32_t uid;
27 int32_t status;
28 uint8_t remainder[100];
29};
30static_assert(sizeof(LinuxWaitInformation) == 128, "Linux amd64 siginfo size changed");
31static_assert(offsetof(LinuxWaitInformation, pid) == 16, "Linux amd64 child PID offset changed");
32static_assert(offsetof(LinuxWaitInformation, status) == 24,
33 "Linux amd64 child status offset changed");
34
35class WaitResult {
36 public:
37 WaitResult() : thread(*Processor::information().getCurrentThread()) {}
38 ~WaitResult() {
39 thread.setErrno(error);
40 }
41 int finish(int result) {
42 error = result < 0 ? thread.getErrno() : 0;
43 return result;
44 }
45
46 private:
47 Thread& thread;
48 size_t error = 0;
49};
50
51void snapshotCurrentGroup(PosixWait::Request& request) {
52 auto* process =
53 static_cast<PosixProcess*>(Processor::information().getCurrentThread()->getParent());
54 size_t group = 0;
55 request.id = process->getProcessGroupId(group, process->pidNamespace().get())
56 ? static_cast<int32_t>(group)
57 : -1;
58}
59
60void eventOptions(unsigned options, PosixWait::Request& request) {
61 request.events = ((options & Exited) ? static_cast<unsigned>(PosixWait::Exited) : 0U) |
62 ((options & Stopped) ? static_cast<unsigned>(PosixWait::Stopped) : 0U) |
63 ((options & Continued) ? static_cast<unsigned>(PosixWait::Continued) : 0U);
64 request.noHang = options & NoHang;
65 request.noWait = options & NoWait;
66}
67
68int prepareWait4(int pid, unsigned options, PosixWait::Request& request) {
69 if (options & ~(NoHang | Stopped | Continued | DeferredOptions)) {
70 SYSCALL_ERROR(InvalidArgument);
71 return -1;
72 }
73 if (pid == INT32_MIN) {
74 SYSCALL_ERROR(NoSuchProcess);
75 return -1;
76 }
77 if (options & DeferredOptions) {
78 SYSCALL_ERROR(OperationNotSupported);
79 return -1;
80 }
81 eventOptions(options | Exited, request);
82 request.traceStops = true;
83 if (pid > 0) {
84 request.selector = PosixWait::Selector::Pid;
85 request.id = pid;
86 } else if (pid != -1) {
87 request.selector = PosixWait::Selector::Pgid;
88 if (!pid)
89 snapshotCurrentGroup(request);
90 else
91 request.id = -pid;
92 }
93 return 0;
94}
95
96int prepareWaitId(int which, int32_t id, unsigned options, PosixWait::Request& request) {
97 if ((options & ~(NoHang | NoWait | Exited | Stopped | Continued | DeferredOptions)) ||
98 !(options & (Exited | Stopped | Continued))) {
99 SYSCALL_ERROR(InvalidArgument);
100 return -1;
101 }
102 if (which < 0 || which > 3 || (which == 1 && id <= 0) || (which >= 2 && id < 0)) {
103 SYSCALL_ERROR(InvalidArgument);
104 return -1;
105 }
106 if ((options & DeferredOptions) || which == 3) {
107 SYSCALL_ERROR(OperationNotSupported);
108 return -1;
109 }
110 eventOptions(options, request);
111 if (which == 1) {
112 request.selector = PosixWait::Selector::Pid;
113 request.id = id;
114 } else if (which == 2) {
115 request.selector = PosixWait::Selector::Pgid;
116 request.id = id;
117 if (!id)
118 snapshotCurrentGroup(request);
119 }
120 return 0;
121}
122
123LinuxRusage64 usageFor(const PosixWait::Report& report) {
124 LinuxRusage64 usage = {};
125 usage.userSeconds = report.userNanoseconds / Time::Multiplier::Second;
126 usage.userMicroseconds =
127 (report.userNanoseconds % Time::Multiplier::Second) / Time::Multiplier::Microsecond;
128 usage.systemSeconds = report.kernelNanoseconds / Time::Multiplier::Second;
129 usage.systemMicroseconds =
130 (report.kernelNanoseconds % Time::Multiplier::Second) / Time::Multiplier::Microsecond;
131 return usage;
132}
133
134int encodedStatus(const PosixWait::Report& report) {
135 switch (report.cause) {
136 case PosixWait::Exit:
137 return (report.status & 0xFF) << 8;
138 case PosixWait::Trapped:
139 case PosixWait::Stop:
140 return ((report.status & 0xFF) << 8) | 0x7F;
141 case PosixWait::Continue:
142 return 0xFFFF;
143 default:
144 return (report.status & 0x7F) | (report.cause == PosixWait::Dumped ? 0x80 : 0);
145 }
146}
147
148bool copyInformation(void* destination, const PosixWait::Report& report, bool selected) {
149 const uintptr_t address = reinterpret_cast<uintptr_t>(destination);
150 constexpr size_t extent = sizeof(LinuxWaitInformation);
151 VirtualAddressSpace& space = Processor::information().getVirtualAddressSpace();
152 // Linux access_ok admits the whole ABI object by address range, while the
153 // actual stores touch six fields only. Inaccessible padding is not a fault.
154 if (address > ~uintptr_t(0) - (extent - 1))
155 return false;
156 const uintptr_t end = address + extent - 1;
157 if (address < space.getUserStart() || end >= space.getKernelStart() ||
158 !space.isAddressValid(reinterpret_cast<void*>(address)) ||
159 !space.isAddressValid(reinterpret_cast<void*>(end))) {
160 return false;
161 }
162 LinuxWaitInformation snapshot = {};
163 if (selected) {
164 snapshot.signal = 17; // Linux SIGCHLD.
165 snapshot.code = report.cause;
166 snapshot.pid = report.pid;
167 snapshot.uid = report.uid;
168 snapshot.status = report.status;
169 }
170 constexpr size_t offsets[] = {0, 4, 8, 16, 20, 24};
171 const auto* bytes = reinterpret_cast<const uint8_t*>(&snapshot);
172 for (size_t offset : offsets) {
173 if (!PosixSubsystem::copyToUser(reinterpret_cast<void*>(address + offset), bytes + offset,
174 sizeof(uint32_t))) {
175 return false;
176 }
177 }
178 return true;
179}
180} // namespace
181
182int posix_waitpid(int pid, int* status, int options, LinuxRusage64* usage) {
183 WaitResult completion;
184 PosixWait::Request request;
185 if (prepareWait4(pid, static_cast<unsigned>(options), request) < 0)
186 return completion.finish(-1);
187 PosixWait::Report report;
188 const int result = PosixWait::collect(request, report);
189 if (result <= 0)
190 return completion.finish(result);
191
192 // Linux consumes the selected event even if either output subsequently
193 // faults, and a failed status write prevents the rusage write.
194 const int encoded = encodedStatus(report);
195 if (status && !PosixSubsystem::copyToUser(status, &encoded, sizeof(encoded))) {
196 SYSCALL_ERROR(BadAddress);
197 return completion.finish(-1);
198 }
199 if (usage) {
200 const auto snapshot = usageFor(report);
201 if (!PosixSubsystem::copyToUser(usage, &snapshot, sizeof(snapshot))) {
202 SYSCALL_ERROR(BadAddress);
203 return completion.finish(-1);
204 }
205 }
206 return completion.finish(report.pid);
207}
208
209int posix_waitid(int which, int32_t id, void* information, int options, LinuxRusage64* usage) {
210 WaitResult completion;
211 PosixWait::Request request;
212 PosixWait::Report report;
213 int result = prepareWaitId(which, id, static_cast<unsigned>(options), request);
214 if (!result)
215 result = PosixWait::collect(request, report);
216 Thread* thread = Processor::information().getCurrentThread();
217 const size_t savedError = result < 0 ? thread->getErrno() : 0;
218 if (result > 0 && usage) {
219 const auto snapshot = usageFor(report);
220 if (!PosixSubsystem::copyToUser(usage, &snapshot, sizeof(snapshot))) {
221 SYSCALL_ERROR(BadAddress);
222 return completion.finish(-1);
223 }
224 }
225 // Even ECHILD/EINVAL or a WNOHANG miss writes the six zero fields. A NULL
226 // info pointer is permitted by the Linux syscall, independently of libc.
227 if (information && !copyInformation(information, report, result > 0)) {
228 SYSCALL_ERROR(BadAddress);
229 return completion.finish(-1);
230 }
231 thread->setErrno(savedError);
232 return completion.finish(result < 0 ? -1 : 0);
233}
static bool copyToUser(void *destination, const void *source, size_t count, size_t elementSize=1)
static ProcessorInformation & information()
T * get() const
void setErrno(size_t err)
Definition Thread.h:482
size_t getErrno()
Definition Thread.h:477
virtual uintptr_t getUserStart() const =0
virtual uintptr_t getKernelStart() const =0
virtual bool isAddressValid(void *virtualAddress)=0