The Pedigree Project 0.1
resource.c
1#define _GNU_SOURCE
2
3/*
4 * Copyright (c) 2026, Pedigree Developers
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted.
8 */
9
10#include <errno.h>
11#include <sched.h>
12#include <stddef.h>
13#include <stdint.h>
14#include <stdio.h>
15#include <string.h>
16#include <unistd.h>
17
18#include <sys/resource.h>
19#include <sys/syscall.h>
20#include <sys/times.h>
21#include <sys/wait.h>
22
23extern void fail(void) __attribute__((noreturn));
24
25struct linux_rusage_abi {
26 int64_t slots[18];
27};
28
30 struct linux_rusage_abi usage;
31 unsigned char canary[32];
32};
33
34_Static_assert(sizeof(struct linux_rusage_abi) == 144, "Linux amd64 rusage ABI changed");
35_Static_assert(RUSAGE_SELF == 0, "musl RUSAGE_SELF selector changed");
36_Static_assert(RUSAGE_CHILDREN == -1, "musl RUSAGE_CHILDREN selector changed");
37_Static_assert(RUSAGE_THREAD == 1, "musl RUSAGE_THREAD selector changed");
38_Static_assert(offsetof(struct rusage, __reserved) == sizeof(struct linux_rusage_abi),
39 "musl rusage prefix no longer matches the Linux syscall ABI");
40_Static_assert(sizeof(struct rusage) == sizeof(struct linux_rusage_abi) + 16 * sizeof(long),
41 "musl rusage reserve changed");
42
44 int64_t user_microseconds;
45 int64_t system_microseconds;
46 int valid;
47};
48
49static int64_t timeval_microseconds(struct timeval value) {
50 return (int64_t)value.tv_sec * 1000000 + value.tv_usec;
51}
52
53static int public_rusage_tail_valid(const struct rusage* usage) {
54 for (size_t i = offsetof(struct rusage, ru_maxrss); i < offsetof(struct rusage, __reserved);
55 ++i) {
56 if (((const unsigned char*)usage)[i])
57 return 0;
58 }
59 for (size_t i = offsetof(struct rusage, __reserved); i < sizeof(*usage); ++i) {
60 if (((const unsigned char*)usage)[i] != 0xA5)
61 return 0;
62 }
63 return 1;
64}
65
66static int burn_observable_user_time(void) {
67 struct rusage before;
68 struct rusage after;
69 if (getrusage(RUSAGE_SELF, &before))
70 return -1;
71 const int64_t before_user = timeval_microseconds(before.ru_utime);
72
73 volatile uint64_t value = 1;
74 for (size_t attempt = 0; attempt < 256; ++attempt) {
75 for (size_t i = 0; i < 100000; ++i)
76 value = value * 1664525 + 1013904223;
77 if (getrusage(RUSAGE_SELF, &after))
78 return -1;
79 if (timeval_microseconds(after.ru_utime) >= before_user + 20000)
80 return 0;
81 }
82 return -1;
83}
84
85static int write_report(int descriptor, const struct descendant_usage_report* report) {
86 const unsigned char* bytes = (const unsigned char*)report;
87 size_t remaining = sizeof(*report);
88 while (remaining) {
89 ssize_t written = write(descriptor, bytes, remaining);
90 if (written < 0 && errno == EINTR)
91 continue;
92 if (written <= 0)
93 return -1;
94 bytes += written;
95 remaining -= (size_t)written;
96 }
97 return 0;
98}
99
100static int read_report(int descriptor, struct descendant_usage_report* report) {
101 unsigned char* bytes = (unsigned char*)report;
102 size_t remaining = sizeof(*report);
103 while (remaining) {
104 ssize_t received = read(descriptor, bytes, remaining);
105 if (received < 0 && errno == EINTR)
106 continue;
107 if (received <= 0)
108 return -1;
109 bytes += received;
110 remaining -= (size_t)received;
111 }
112 return 0;
113}
114
115static void test_child_resource_accounting(void) {
116 struct rusage children_before;
117 memset(&children_before, 0xA5, sizeof(children_before));
118 if (getrusage(RUSAGE_CHILDREN, &children_before) || !public_rusage_tail_valid(&children_before))
119 fail();
120 const int64_t user_before = timeval_microseconds(children_before.ru_utime);
121 const int64_t system_before = timeval_microseconds(children_before.ru_stime);
122
123 struct tms times_before;
124 if (times(&times_before) == (clock_t)-1 ||
125 times_before.tms_cutime !=
126 children_before.ru_utime.tv_sec * 100 + children_before.ru_utime.tv_usec / 10000 ||
127 times_before.tms_cstime !=
128 children_before.ru_stime.tv_sec * 100 + children_before.ru_stime.tv_usec / 10000)
129 fail();
130
131 int report_pipe[2];
132 if (pipe(report_pipe))
133 fail();
134 pid_t child = fork();
135 if (child < 0)
136 fail();
137 if (!child) {
138 close(report_pipe[0]);
139 struct descendant_usage_report report = {0};
140 pid_t descendant = fork();
141 if (!descendant)
142 _exit(burn_observable_user_time() ? 124 : 0);
143
144 struct rusage waited_descendant;
145 memset(&waited_descendant, 0xA5, sizeof(waited_descendant));
146 int descendant_status = 0;
147 if (descendant > 0 &&
148 wait4(descendant, &descendant_status, 0, &waited_descendant) == descendant &&
149 WIFEXITED(descendant_status) && !WEXITSTATUS(descendant_status) &&
150 public_rusage_tail_valid(&waited_descendant)) {
151 struct rusage descendants;
152 memset(&descendants, 0xA5, sizeof(descendants));
153 if (!getrusage(RUSAGE_CHILDREN, &descendants) && public_rusage_tail_valid(&descendants)) {
154 report.user_microseconds = timeval_microseconds(descendants.ru_utime);
155 report.system_microseconds = timeval_microseconds(descendants.ru_stime);
156 report.valid =
157 report.user_microseconds >= timeval_microseconds(waited_descendant.ru_utime) &&
158 report.system_microseconds >= timeval_microseconds(waited_descendant.ru_stime) &&
159 report.user_microseconds > 0;
160 }
161 }
162 const int reported = write_report(report_pipe[1], &report);
163 close(report_pipe[1]);
164 _exit(!reported && report.valid ? 0 : 125);
165 }
166
167 close(report_pipe[1]);
168 struct descendant_usage_report report = {0};
169 const int report_read = read_report(report_pipe[0], &report);
170 close(report_pipe[0]);
171 struct rusage waited_child;
172 memset(&waited_child, 0xA5, sizeof(waited_child));
173 int child_status = 0;
174 if (report_read || wait4(child, &child_status, 0, &waited_child) != child ||
175 !WIFEXITED(child_status) || WEXITSTATUS(child_status) || !report.valid ||
176 !public_rusage_tail_valid(&waited_child) ||
177 timeval_microseconds(waited_child.ru_utime) < report.user_microseconds ||
178 timeval_microseconds(waited_child.ru_stime) < report.system_microseconds)
179 fail();
180
181 struct rusage children_after;
182 memset(&children_after, 0xA5, sizeof(children_after));
183 if (getrusage(RUSAGE_CHILDREN, &children_after) || !public_rusage_tail_valid(&children_after))
184 fail();
185 const int64_t user_added = timeval_microseconds(children_after.ru_utime) - user_before;
186 const int64_t system_added = timeval_microseconds(children_after.ru_stime) - system_before;
187 const int64_t waited_user = timeval_microseconds(waited_child.ru_utime);
188 const int64_t waited_system = timeval_microseconds(waited_child.ru_stime);
189 if (user_added + 1 < waited_user || user_added > waited_user + 1 ||
190 system_added + 1 < waited_system || system_added > waited_system + 1)
191 fail();
192
193 struct tms times_after;
194 if (times(&times_after) == (clock_t)-1 ||
195 times_after.tms_cutime !=
196 children_after.ru_utime.tv_sec * 100 + children_after.ru_utime.tv_usec / 10000 ||
197 times_after.tms_cstime !=
198 children_after.ru_stime.tv_sec * 100 + children_after.ru_stime.tv_usec / 10000 ||
199 times_after.tms_cutime <= times_before.tms_cutime)
200 fail();
201
202 int gate[2];
203 if (pipe(gate))
204 fail();
205 pid_t raw_child = fork();
206 if (raw_child < 0)
207 fail();
208 if (!raw_child) {
209 close(gate[1]);
210 char token = 0;
211 ssize_t received;
212 do {
213 received = read(gate[0], &token, sizeof(token));
214 } while (received < 0 && errno == EINTR);
215 close(gate[0]);
216 _exit(received == sizeof(token) && token == 'x' ? 0 : 126);
217 }
218
219 close(gate[0]);
220 struct linux_rusage_packet packet;
221 struct linux_rusage_packet untouched_packet;
222 memset(&packet, 0xA5, sizeof(packet));
223 untouched_packet = packet;
224 int raw_status = 0x5A5A5A5A;
225 errno = 0;
226 if (syscall(SYS_wait4, raw_child, &raw_status, WNOHANG, packet.usage.slots) ||
227 raw_status != 0x5A5A5A5A || memcmp(&packet, &untouched_packet, sizeof(packet)))
228 fail();
229
230 const char token = 'x';
231 if (write(gate[1], &token, sizeof(token)) != sizeof(token) || close(gate[1]))
232 fail();
233 memset(&packet, 0xA5, sizeof(packet));
234 if (syscall(SYS_wait4, raw_child, &raw_status, 0, packet.usage.slots) != raw_child ||
235 !WIFEXITED(raw_status) || WEXITSTATUS(raw_status) || packet.usage.slots[0] < 0 ||
236 packet.usage.slots[1] < 0 || packet.usage.slots[1] >= 1000000 || packet.usage.slots[2] < 0 ||
237 packet.usage.slots[3] < 0 || packet.usage.slots[3] >= 1000000)
238 fail();
239 for (size_t i = 4; i < 18; ++i) {
240 if (packet.usage.slots[i])
241 fail();
242 }
243 for (size_t i = 0; i < sizeof(packet.canary); ++i) {
244 if (packet.canary[i] != 0xA5)
245 fail();
246 }
247
248 pid_t null_child = fork();
249 if (null_child < 0)
250 fail();
251 if (!null_child)
252 _exit(0);
253 if (wait4(null_child, &child_status, 0, 0) != null_child || !WIFEXITED(child_status) ||
254 WEXITSTATUS(child_status))
255 fail();
256
257 memset(&packet, 0xA5, sizeof(packet));
258 untouched_packet = packet;
259 raw_status = 0x5A5A5A5A;
260 errno = 0;
261 if (syscall(SYS_wait4, raw_child, &raw_status, WNOHANG, packet.usage.slots) != -1 ||
262 errno != ECHILD || raw_status != 0x5A5A5A5A ||
263 memcmp(&packet, &untouched_packet, sizeof(packet)))
264 fail();
265}
266
267void test_resource_accounting(void) {
268 puts("Testing process resource accounting... ");
269 fflush(stdout);
270
271 struct tms processTimes = {0};
272 const clock_t elapsed = times(&processTimes);
273 if (elapsed == (clock_t)-1 || processTimes.tms_utime < 0 || processTimes.tms_stime < 0 ||
274 processTimes.tms_cutime || processTimes.tms_cstime)
275 fail();
276
277 if (times(0) < elapsed)
278 fail();
279
280 struct rusage usage = {0};
281 if (getrusage(RUSAGE_SELF, &usage) || usage.ru_utime.tv_sec < 0 || usage.ru_utime.tv_usec < 0 ||
282 usage.ru_utime.tv_usec >= 1000000 || usage.ru_stime.tv_sec < 0 ||
283 usage.ru_stime.tv_usec < 0 || usage.ru_stime.tv_usec >= 1000000)
284 fail();
285
286 const clock_t userTicks = usage.ru_utime.tv_sec * 100 + usage.ru_utime.tv_usec / 10000;
287 if (userTicks < processTimes.tms_utime || userTicks > processTimes.tms_utime + 10)
288 fail();
289
290 struct linux_rusage_packet packet;
291 memset(&packet, 0xA5, sizeof(packet));
292 if (syscall(SYS_getrusage, RUSAGE_SELF, packet.usage.slots) || packet.usage.slots[0] < 0 ||
293 packet.usage.slots[1] < 0 || packet.usage.slots[1] >= 1000000 || packet.usage.slots[2] < 0 ||
294 packet.usage.slots[3] < 0 || packet.usage.slots[3] >= 1000000)
295 fail();
296 for (size_t i = 0; i < sizeof(packet.canary); ++i) {
297 if (packet.canary[i] != 0xA5)
298 fail();
299 }
300
301 struct rusage thread_usage;
302 memset(&thread_usage, 0xA5, sizeof(thread_usage));
303 if (getrusage(RUSAGE_THREAD, &thread_usage) || thread_usage.ru_utime.tv_sec < 0 ||
304 thread_usage.ru_utime.tv_usec < 0 || thread_usage.ru_utime.tv_usec >= 1000000 ||
305 thread_usage.ru_stime.tv_sec < 0 || thread_usage.ru_stime.tv_usec < 0 ||
306 thread_usage.ru_stime.tv_usec >= 1000000)
307 fail();
308 for (size_t i = offsetof(struct rusage, ru_maxrss); i < offsetof(struct rusage, __reserved);
309 ++i) {
310 if (((unsigned char*)&thread_usage)[i])
311 fail();
312 }
313 for (size_t i = offsetof(struct rusage, __reserved); i < sizeof(thread_usage); ++i) {
314 if (((unsigned char*)&thread_usage)[i] != 0xA5)
315 fail();
316 }
317
318 memset(&packet, 0xA5, sizeof(packet));
319 if (syscall(SYS_getrusage, RUSAGE_THREAD, packet.usage.slots) || packet.usage.slots[0] < 0 ||
320 packet.usage.slots[1] < 0 || packet.usage.slots[1] >= 1000000 || packet.usage.slots[2] < 0 ||
321 packet.usage.slots[3] < 0 || packet.usage.slots[3] >= 1000000)
322 fail();
323 for (size_t i = 4; i < 18; ++i) {
324 if (packet.usage.slots[i])
325 fail();
326 }
327 for (size_t i = 0; i < sizeof(packet.canary); ++i) {
328 if (packet.canary[i] != 0xA5)
329 fail();
330 }
331
332 test_child_resource_accounting();
333
334 puts("OK\n");
335 fflush(stdout);
336}