]>
Commit | Line | Data |
---|---|---|
9dd11ed0 MT |
1 | /*############################################################################# |
2 | # # | |
3 | # Pakfire - The IPFire package management system # | |
e3ddb498 | 4 | # Copyright (C) 2022 Pakfire development team # |
9dd11ed0 MT |
5 | # # |
6 | # This program is free software: you can redistribute it and/or modify # | |
7 | # it under the terms of the GNU General Public License as published by # | |
8 | # the Free Software Foundation, either version 3 of the License, or # | |
9 | # (at your option) any later version. # | |
10 | # # | |
11 | # This program is distributed in the hope that it will be useful, # | |
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # | |
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # | |
14 | # GNU General Public License for more details. # | |
15 | # # | |
16 | # You should have received a copy of the GNU General Public License # | |
17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. # | |
18 | # # | |
19 | #############################################################################*/ | |
20 | ||
21 | #include <errno.h> | |
e3ddb498 | 22 | #include <fcntl.h> |
d59e29c8 | 23 | #include <linux/bpf.h> |
034ba70e | 24 | #include <signal.h> |
9dd11ed0 | 25 | #include <stdlib.h> |
9dd11ed0 | 26 | #include <sys/types.h> |
9dd11ed0 | 27 | |
d59e29c8 MT |
28 | // libbpf |
29 | #include <bpf/bpf.h> | |
30 | ||
35bf392c | 31 | #include <pakfire/ctx.h> |
9dd11ed0 MT |
32 | #include <pakfire/cgroup.h> |
33 | #include <pakfire/logging.h> | |
729827f7 | 34 | #include <pakfire/path.h> |
d973a13d | 35 | #include <pakfire/string.h> |
9dd11ed0 MT |
36 | #include <pakfire/util.h> |
37 | ||
e3ddb498 | 38 | #define BUFFER_SIZE 64 * 1024 |
9dd11ed0 | 39 | |
d59e29c8 MT |
40 | // Short form of mov, dst_reg = src_reg |
41 | #define BPF_MOV64_IMM(DST, IMM) \ | |
42 | ((struct bpf_insn){ \ | |
43 | .code = BPF_ALU64 | BPF_MOV | BPF_K, \ | |
44 | .dst_reg = DST, \ | |
45 | .src_reg = 0, \ | |
46 | .off = 0, \ | |
47 | .imm = IMM \ | |
48 | }) | |
49 | ||
50 | // Program exit | |
51 | #define BPF_EXIT_INSN() \ | |
52 | ((struct bpf_insn){ \ | |
53 | .code = BPF_JMP | BPF_EXIT, \ | |
54 | .dst_reg = 0, \ | |
55 | .src_reg = 0, \ | |
56 | .off = 0, \ | |
57 | .imm = 0 \ | |
58 | }) | |
59 | ||
2901c3a7 MT |
60 | enum pakfire_cgroup_controllers { |
61 | PAKFIRE_CGROUP_CONTROLLER_CPU = (1 << 0), | |
62 | PAKFIRE_CGROUP_CONTROLLER_MEMORY = (1 << 1), | |
63 | PAKFIRE_CGROUP_CONTROLLER_PIDS = (1 << 2), | |
64 | PAKFIRE_CGROUP_CONTROLLER_IO = (1 << 3), | |
65 | }; | |
66 | ||
67 | static const enum pakfire_cgroup_controllers pakfire_cgroup_accounting_controllers = | |
68 | PAKFIRE_CGROUP_CONTROLLER_CPU | | |
69 | PAKFIRE_CGROUP_CONTROLLER_MEMORY | | |
70 | PAKFIRE_CGROUP_CONTROLLER_PIDS | | |
71 | PAKFIRE_CGROUP_CONTROLLER_IO; | |
72 | ||
e3ddb498 | 73 | struct pakfire_cgroup { |
35bf392c | 74 | struct pakfire_ctx* ctx; |
e3ddb498 | 75 | int nrefs; |
9dd11ed0 | 76 | |
31d7e29a MT |
77 | // Store the root path |
78 | char root[PATH_MAX]; | |
79 | ||
2901c3a7 MT |
80 | // Flags |
81 | int flags; | |
82 | ||
e3ddb498 MT |
83 | // Store the path |
84 | char path[PATH_MAX]; | |
9dd11ed0 | 85 | |
e3ddb498 MT |
86 | // File descriptor to cgroup |
87 | int fd; | |
d59e29c8 MT |
88 | |
89 | // FD to the devices filter program | |
90 | int devicesfd; | |
e3ddb498 | 91 | }; |
9e1e7985 | 92 | |
e3ddb498 MT |
93 | // Returns true if this is the root cgroup |
94 | static int pakfire_cgroup_is_root(struct pakfire_cgroup* cgroup) { | |
95 | return !*cgroup->path; | |
96 | } | |
9e1e7985 | 97 | |
2901c3a7 MT |
98 | static int pakfire_cgroup_has_flag(struct pakfire_cgroup* cgroup, int flag) { |
99 | return cgroup->flags & flag; | |
100 | } | |
101 | ||
31d7e29a MT |
102 | static int pakfire_cgroup_set_root(struct pakfire_cgroup* cgroup) { |
103 | int r; | |
104 | ||
105 | // Find the current UID | |
106 | const uid_t uid = getuid(); | |
107 | ||
108 | switch (uid) { | |
109 | // root | |
110 | case 0: | |
111 | r = pakfire_string_set(cgroup->root, "/sys/fs/cgroup"); | |
98eefc02 | 112 | break; |
31d7e29a MT |
113 | |
114 | // unprivileged users | |
115 | default: | |
116 | r = pakfire_string_format(cgroup->root, | |
a8a41064 | 117 | "/sys/fs/cgroup/user.slice/user-%u.slice/user@%u.service", uid, uid); |
98eefc02 | 118 | break; |
31d7e29a MT |
119 | } |
120 | ||
121 | if (r) | |
35bf392c | 122 | CTX_ERROR(cgroup->ctx, "Could not determine cgroup root: %m\n"); |
31d7e29a MT |
123 | |
124 | return r; | |
125 | } | |
126 | ||
e3ddb498 MT |
127 | static const char* pakfire_cgroup_name(struct pakfire_cgroup* cgroup) { |
128 | if (pakfire_cgroup_is_root(cgroup)) | |
129 | return "(root)"; | |
9e1e7985 | 130 | |
e3ddb498 | 131 | return cgroup->path; |
9e1e7985 MT |
132 | } |
133 | ||
2901c3a7 MT |
134 | static const char* pakfire_cgroup_controller_name( |
135 | enum pakfire_cgroup_controllers controller) { | |
136 | switch (controller) { | |
137 | case PAKFIRE_CGROUP_CONTROLLER_CPU: | |
138 | return "cpu"; | |
139 | ||
140 | case PAKFIRE_CGROUP_CONTROLLER_MEMORY: | |
141 | return "memory"; | |
142 | ||
143 | case PAKFIRE_CGROUP_CONTROLLER_PIDS: | |
144 | return "pids"; | |
145 | ||
146 | case PAKFIRE_CGROUP_CONTROLLER_IO: | |
147 | return "io"; | |
148 | } | |
149 | ||
150 | return NULL; | |
151 | } | |
152 | ||
153 | static enum pakfire_cgroup_controllers pakfire_cgroup_find_controller_by_name( | |
154 | const char* name) { | |
155 | const char* n = NULL; | |
156 | ||
157 | // Walk through the bitmap | |
158 | for (unsigned int i = 1; i; i <<= 1) { | |
159 | n = pakfire_cgroup_controller_name(i); | |
160 | if (!n) | |
161 | break; | |
162 | ||
163 | // Match | |
164 | if (strcmp(name, n) == 0) | |
165 | return i; | |
166 | } | |
167 | ||
168 | // Nothing found | |
169 | return 0; | |
170 | } | |
171 | ||
172 | static struct pakfire_cgroup* pakfire_cgroup_parent(struct pakfire_cgroup* cgroup) { | |
173 | struct pakfire_cgroup* parent = NULL; | |
f54eb6e7 | 174 | char path[PATH_MAX]; |
2901c3a7 MT |
175 | int r; |
176 | ||
177 | // Cannot return parent for root group | |
178 | if (pakfire_cgroup_is_root(cgroup)) | |
179 | return NULL; | |
180 | ||
181 | // Determine the path of the parent | |
b4ae14b2 | 182 | r = pakfire_path_dirname(path, cgroup->path); |
f54eb6e7 | 183 | if (r) { |
35bf392c | 184 | CTX_ERROR(cgroup->ctx, "Could not determine path for parent cgroup: %m\n"); |
2901c3a7 MT |
185 | return NULL; |
186 | } | |
187 | ||
188 | // dirname() returns . if no directory component could be found | |
189 | if (strcmp(path, ".") == 0) | |
f54eb6e7 | 190 | *path = '\0'; |
2901c3a7 MT |
191 | |
192 | // Open the cgroup | |
35bf392c | 193 | r = pakfire_cgroup_open(&parent, cgroup->ctx, path, 0); |
2901c3a7 | 194 | if (r) { |
35bf392c | 195 | CTX_ERROR(cgroup->ctx, "Could not open parent cgroup: %m\n"); |
2901c3a7 MT |
196 | parent = NULL; |
197 | } | |
198 | ||
2901c3a7 MT |
199 | return parent; |
200 | } | |
201 | ||
e3ddb498 | 202 | static void pakfire_cgroup_free(struct pakfire_cgroup* cgroup) { |
35bf392c | 203 | CTX_DEBUG(cgroup->ctx, "Releasing cgroup %s at %p\n", |
e3ddb498 | 204 | pakfire_cgroup_name(cgroup), cgroup); |
9e1e7985 | 205 | |
d59e29c8 | 206 | // Close the file descriptors |
35bf392c | 207 | if (cgroup->fd >= 0) |
e3ddb498 | 208 | close(cgroup->fd); |
35bf392c | 209 | if (cgroup->devicesfd >= 0) |
d59e29c8 | 210 | close(cgroup->devicesfd); |
35bf392c MT |
211 | if (cgroup->ctx) |
212 | pakfire_ctx_unref(cgroup->ctx); | |
e3ddb498 | 213 | free(cgroup); |
9e1e7985 MT |
214 | } |
215 | ||
d59e29c8 | 216 | static int pakfire_cgroup_setup_devices(struct pakfire_cgroup* cgroup) { |
e8a2696c | 217 | static char bpf_log_buffer[BPF_LOG_BUF_SIZE]; |
b4d087f3 | 218 | |
d59e29c8 MT |
219 | LIBBPF_OPTS(bpf_prog_load_opts, opts, |
220 | // Log Buffer | |
221 | .log_buf = bpf_log_buffer, | |
222 | .log_size = sizeof(bpf_log_buffer), | |
223 | ); | |
224 | int r; | |
225 | ||
226 | struct bpf_insn program[] = { | |
227 | BPF_MOV64_IMM(BPF_REG_0, 1), // r0 = 1 | |
228 | BPF_EXIT_INSN(), // return r0 | |
229 | }; | |
230 | ||
231 | // Load the BPF program | |
232 | r = bpf_prog_load(BPF_PROG_TYPE_CGROUP_DEVICE, NULL, "GPL", | |
233 | program, sizeof(program) / sizeof(*program), &opts); | |
234 | if (r < 0) { | |
35bf392c | 235 | CTX_ERROR(cgroup->ctx, "Could not load BPF program: %m\n"); |
d59e29c8 MT |
236 | return r; |
237 | } | |
238 | ||
239 | // Store the file descriptor | |
240 | cgroup->devicesfd = r; | |
241 | ||
242 | // Attach the program to the cgroup | |
243 | r = bpf_prog_attach(cgroup->devicesfd, cgroup->fd, | |
244 | BPF_CGROUP_DEVICE, BPF_F_ALLOW_MULTI); | |
245 | if (r) { | |
35bf392c | 246 | CTX_ERROR(cgroup->ctx, "Could not attach BPF program to cgroup: %m\n"); |
d59e29c8 MT |
247 | return r; |
248 | } | |
249 | ||
250 | return 0; | |
251 | } | |
252 | ||
01cf6134 | 253 | static int pakfire_cgroup_open_root(struct pakfire_cgroup* cgroup) { |
31d7e29a | 254 | int fd = open(cgroup->root, O_DIRECTORY|O_PATH|O_CLOEXEC); |
01cf6134 | 255 | if (fd < 0) { |
35bf392c | 256 | CTX_ERROR(cgroup->ctx, "Could not open %s: %m\n", cgroup->root); |
01cf6134 MT |
257 | return -1; |
258 | } | |
259 | ||
260 | return fd; | |
261 | } | |
262 | ||
e3ddb498 MT |
263 | static int __pakfire_cgroup_create(struct pakfire_cgroup* cgroup) { |
264 | char path[PATH_MAX]; | |
265 | int r; | |
9dd11ed0 | 266 | |
35bf392c | 267 | CTX_DEBUG(cgroup->ctx, "Trying to create cgroup %s\n", pakfire_cgroup_name(cgroup)); |
9dd11ed0 | 268 | |
e3ddb498 | 269 | // Compose the absolute path |
819232d6 | 270 | r = pakfire_path_append(path, cgroup->root, cgroup->path); |
56796f84 | 271 | if (r) |
e3ddb498 | 272 | return 1; |
9dd11ed0 | 273 | |
e3ddb498 MT |
274 | // Try creating the directory |
275 | return pakfire_mkdir(path, 0755); | |
276 | } | |
9dd11ed0 | 277 | |
e3ddb498 MT |
278 | /* |
279 | Opens the cgroup and returns a file descriptor. | |
9dd11ed0 | 280 | |
e3ddb498 | 281 | If the cgroup does not exist, it will try to create it. |
9dd11ed0 | 282 | |
e3ddb498 MT |
283 | This function returns a negative value on error. |
284 | */ | |
285 | static int __pakfire_cgroup_open(struct pakfire_cgroup* cgroup) { | |
e3ddb498 MT |
286 | int fd = -1; |
287 | int r; | |
9dd11ed0 | 288 | |
e3ddb498 | 289 | // Open file descriptor of the cgroup root |
01cf6134 MT |
290 | int rootfd = pakfire_cgroup_open_root(cgroup); |
291 | if (rootfd < 0) | |
e3ddb498 | 292 | return -1; |
9dd11ed0 | 293 | |
e3ddb498 MT |
294 | // Return the rootfd for the root group |
295 | if (pakfire_cgroup_is_root(cgroup)) | |
296 | return rootfd; | |
9dd11ed0 | 297 | |
e3ddb498 MT |
298 | RETRY: |
299 | fd = openat(rootfd, cgroup->path, O_DIRECTORY|O_PATH|O_CLOEXEC); | |
300 | if (fd < 0) { | |
301 | switch (errno) { | |
302 | // If the cgroup doesn't exist yet, try to create it | |
303 | case ENOENT: | |
304 | r = __pakfire_cgroup_create(cgroup); | |
305 | if (r) | |
306 | goto ERROR; | |
9dd11ed0 | 307 | |
e3ddb498 MT |
308 | // Retry open after successful creation |
309 | goto RETRY; | |
9dd11ed0 | 310 | |
e3ddb498 MT |
311 | // Exit on all other errors |
312 | default: | |
35bf392c | 313 | CTX_ERROR(cgroup->ctx, "Could not open cgroup %s: %m\n", |
e3ddb498 MT |
314 | pakfire_cgroup_name(cgroup)); |
315 | goto ERROR; | |
316 | } | |
317 | } | |
9dd11ed0 | 318 | |
e3ddb498 MT |
319 | ERROR: |
320 | if (rootfd > 0) | |
321 | close(rootfd); | |
9dd11ed0 | 322 | |
e3ddb498 MT |
323 | return fd; |
324 | } | |
9dd11ed0 | 325 | |
034ba70e MT |
326 | static int pakfire_cgroup_access(struct pakfire_cgroup* cgroup, const char* path, |
327 | int mode, int flags) { | |
328 | return faccessat(cgroup->fd, path, mode, flags); | |
329 | } | |
330 | ||
331 | static FILE* pakfire_cgroup_open_file(struct pakfire_cgroup* cgroup, | |
332 | const char* path, const char* mode) { | |
333 | FILE* f = NULL; | |
334 | ||
335 | // Open cgroup.procs | |
336 | int fd = openat(cgroup->fd, "cgroup.procs", O_CLOEXEC); | |
337 | if (fd < 0) { | |
35bf392c | 338 | CTX_ERROR(cgroup->ctx, "%s: Could not open %s: %m\n", |
034ba70e MT |
339 | pakfire_cgroup_name(cgroup), path); |
340 | goto ERROR; | |
341 | } | |
342 | ||
343 | // Convert into file handle | |
344 | f = fdopen(fd, mode); | |
345 | if (!f) | |
346 | goto ERROR; | |
347 | ||
348 | ERROR: | |
2868fb2f | 349 | if (fd > 0) |
034ba70e MT |
350 | close(fd); |
351 | ||
352 | return f; | |
353 | } | |
354 | ||
2901c3a7 | 355 | static ssize_t pakfire_cgroup_read(struct pakfire_cgroup* cgroup, const char* path, |
e3ddb498 | 356 | char* buffer, size_t length) { |
2901c3a7 | 357 | ssize_t bytes_read = -1; |
9dd11ed0 | 358 | |
e3ddb498 MT |
359 | // Check if this cgroup has been destroyed already |
360 | if (!cgroup->fd) { | |
35bf392c | 361 | CTX_ERROR(cgroup->ctx, "Trying to read from destroyed cgroup\n"); |
2901c3a7 | 362 | return -1; |
9dd11ed0 MT |
363 | } |
364 | ||
e3ddb498 MT |
365 | // Open the file |
366 | int fd = openat(cgroup->fd, path, O_CLOEXEC); | |
367 | if (fd < 0) { | |
35bf392c | 368 | CTX_DEBUG(cgroup->ctx, "Could not open %s/%s: %m\n", |
e3ddb498 MT |
369 | pakfire_cgroup_name(cgroup), path); |
370 | goto ERROR; | |
371 | } | |
9dd11ed0 | 372 | |
e3ddb498 | 373 | // Read file content into buffer |
2901c3a7 MT |
374 | bytes_read = read(fd, buffer, length); |
375 | if (bytes_read < 0) { | |
35bf392c | 376 | CTX_DEBUG(cgroup->ctx, "Could not read from %s/%s: %m\n", |
e3ddb498 MT |
377 | pakfire_cgroup_name(cgroup), path); |
378 | goto ERROR; | |
379 | } | |
9dd11ed0 | 380 | |
2901c3a7 | 381 | // Terminate the buffer |
cc7f589e | 382 | if ((size_t)bytes_read < length) |
2901c3a7 | 383 | buffer[bytes_read] = '\0'; |
9dd11ed0 | 384 | |
e3ddb498 MT |
385 | ERROR: |
386 | if (fd > 0) | |
387 | close(fd); | |
9dd11ed0 | 388 | |
2901c3a7 | 389 | return bytes_read; |
9dd11ed0 MT |
390 | } |
391 | ||
e3ddb498 MT |
392 | static int pakfire_cgroup_write(struct pakfire_cgroup* cgroup, |
393 | const char* path, const char* format, ...) { | |
305de320 | 394 | va_list args; |
e3ddb498 MT |
395 | int r = 0; |
396 | ||
397 | // Check if this cgroup has been destroyed already | |
398 | if (!cgroup->fd) { | |
35bf392c | 399 | CTX_ERROR(cgroup->ctx, "Trying to write to destroyed cgroup\n"); |
e3ddb498 MT |
400 | errno = EPERM; |
401 | return 1; | |
402 | } | |
9dd11ed0 | 403 | |
e3ddb498 MT |
404 | // Open the file |
405 | int fd = openat(cgroup->fd, path, O_WRONLY|O_CLOEXEC); | |
406 | if (fd < 0) { | |
35bf392c | 407 | CTX_DEBUG(cgroup->ctx, "Could not open %s/%s for writing: %m\n", |
e3ddb498 | 408 | pakfire_cgroup_name(cgroup), path); |
9dd11ed0 | 409 | return 1; |
e3ddb498 | 410 | } |
9dd11ed0 | 411 | |
e3ddb498 | 412 | // Write buffer |
305de320 | 413 | va_start(args, format); |
e3ddb498 | 414 | ssize_t bytes_written = vdprintf(fd, format, args); |
305de320 MT |
415 | va_end(args); |
416 | ||
e3ddb498 MT |
417 | // Check if content was written okay |
418 | if (bytes_written < 0) { | |
35bf392c | 419 | CTX_DEBUG(cgroup->ctx, "Could not write to %s/%s: %m\n", |
e3ddb498 MT |
420 | pakfire_cgroup_name(cgroup), path); |
421 | r = 1; | |
422 | } | |
820c32c7 | 423 | |
e3ddb498 MT |
424 | // Close fd |
425 | close(fd); | |
305de320 MT |
426 | |
427 | return r; | |
428 | } | |
429 | ||
2901c3a7 MT |
430 | static int pakfire_cgroup_read_controllers( |
431 | struct pakfire_cgroup* cgroup, const char* name) { | |
432 | char buffer[BUFFER_SIZE]; | |
433 | char* p = NULL; | |
9dd11ed0 | 434 | |
2901c3a7 MT |
435 | // Discovered controllers |
436 | int controllers = 0; | |
69cfa22d | 437 | |
2901c3a7 MT |
438 | // Read cgroup.controllers file |
439 | ssize_t bytes_read = pakfire_cgroup_read(cgroup, name, buffer, sizeof(buffer)); | |
440 | if (bytes_read < 0) | |
441 | return -1; | |
442 | ||
443 | // If the file was empty, there is nothing more to do | |
444 | if (bytes_read == 0) | |
445 | return 0; | |
446 | ||
447 | char* token = strtok_r(buffer, " \n", &p); | |
448 | ||
449 | while (token) { | |
35bf392c | 450 | CTX_DEBUG(cgroup->ctx, "Found controller '%s'\n", token); |
2901c3a7 MT |
451 | |
452 | // Try finding this controller | |
453 | int controller = pakfire_cgroup_find_controller_by_name(token); | |
454 | if (controller) | |
455 | controllers |= controller; | |
456 | ||
457 | // Move on to next token | |
458 | token = strtok_r(NULL, " \n", &p); | |
9dd11ed0 MT |
459 | } |
460 | ||
2901c3a7 MT |
461 | // Return discovered controllers |
462 | return controllers; | |
463 | } | |
9dd11ed0 | 464 | |
2901c3a7 MT |
465 | /* |
466 | Returns a bitmap of all available controllers | |
467 | */ | |
468 | static int pakfire_cgroup_available_controllers(struct pakfire_cgroup* cgroup) { | |
469 | return pakfire_cgroup_read_controllers(cgroup, "cgroup.controllers"); | |
470 | } | |
471 | ||
472 | /* | |
473 | Returns a bitmap of all enabled controllers | |
474 | */ | |
475 | static int pakfire_cgroup_enabled_controllers(struct pakfire_cgroup* cgroup) { | |
476 | return pakfire_cgroup_read_controllers(cgroup, "cgroup.subtree_control"); | |
477 | } | |
478 | ||
479 | /* | |
480 | This function takes a bitmap of controllers that should be enabled. | |
481 | */ | |
482 | static int pakfire_cgroup_enable_controllers(struct pakfire_cgroup* cgroup, | |
483 | enum pakfire_cgroup_controllers controllers) { | |
484 | struct pakfire_cgroup* parent = NULL; | |
485 | int r = 1; | |
486 | ||
487 | // Find all enabled controllers | |
488 | const int enabled_controllers = pakfire_cgroup_enabled_controllers(cgroup); | |
489 | if (enabled_controllers < 0) { | |
35bf392c | 490 | CTX_ERROR(cgroup->ctx, "Could not fetch enabled controllers: %m\n"); |
2901c3a7 | 491 | goto ERROR; |
9dd11ed0 MT |
492 | } |
493 | ||
2901c3a7 MT |
494 | // Filter out anything that is already enabled |
495 | controllers = (controllers & ~enabled_controllers); | |
9dd11ed0 | 496 | |
2901c3a7 MT |
497 | // Exit if everything is already enabled |
498 | if (!controllers) { | |
35bf392c | 499 | CTX_DEBUG(cgroup->ctx, "All controllers are already enabled\n"); |
2901c3a7 MT |
500 | return 0; |
501 | } | |
502 | ||
503 | // Find all available controllers | |
504 | const int available_controllers = pakfire_cgroup_available_controllers(cgroup); | |
505 | if (available_controllers < 0) { | |
35bf392c | 506 | CTX_ERROR(cgroup->ctx, "Could not fetch available controllers: %m\n"); |
2901c3a7 MT |
507 | goto ERROR; |
508 | } | |
509 | ||
510 | // Are all controllers we need available, yet? | |
511 | if (controllers & ~available_controllers) { | |
35bf392c | 512 | CTX_DEBUG(cgroup->ctx, "Not all controllers are available, yet\n"); |
2901c3a7 MT |
513 | |
514 | parent = pakfire_cgroup_parent(cgroup); | |
515 | ||
516 | // Enable everything we need on the parent group | |
517 | if (parent) { | |
518 | r = pakfire_cgroup_enable_controllers(parent, controllers); | |
519 | if (r) | |
520 | goto ERROR; | |
521 | } | |
522 | } | |
523 | ||
524 | // Determine how many iterations we will need | |
525 | const int iterations = 1 << (sizeof(controllers) * 8 - __builtin_clz(controllers)); | |
526 | ||
527 | // Iterate over all known controllers | |
528 | for (int controller = 1; controller < iterations; controller <<= 1) { | |
529 | // Skip enabling this controller if not requested | |
530 | if (!(controller & controllers)) | |
531 | continue; | |
532 | ||
533 | // Fetch name | |
534 | const char* name = pakfire_cgroup_controller_name(controller); | |
535 | ||
35bf392c | 536 | CTX_DEBUG(cgroup->ctx, "Enabling controller %s in cgroup %s\n", |
2901c3a7 MT |
537 | name, pakfire_cgroup_name(cgroup)); |
538 | ||
539 | // Try enabling the controller (this will succeed if it already is enabled) | |
540 | r = pakfire_cgroup_write(cgroup, "cgroup.subtree_control", "+%s\n", name); | |
541 | if (r) { | |
35bf392c | 542 | CTX_ERROR(cgroup->ctx, "Could not enable controller %s in cgroup %s\n", |
2901c3a7 MT |
543 | name, pakfire_cgroup_name(cgroup)); |
544 | goto ERROR; | |
545 | } | |
546 | } | |
547 | ||
548 | ERROR: | |
549 | if (parent) | |
550 | pakfire_cgroup_unref(parent); | |
551 | ||
552 | return r; | |
553 | } | |
554 | ||
555 | static int pakfire_cgroup_enable_accounting(struct pakfire_cgroup* cgroup) { | |
556 | // Enable all accounting controllers | |
557 | return pakfire_cgroup_enable_controllers(cgroup, | |
558 | pakfire_cgroup_accounting_controllers); | |
9dd11ed0 MT |
559 | } |
560 | ||
e3ddb498 MT |
561 | /* |
562 | Entry function to open a new cgroup. | |
9dd11ed0 | 563 | |
e3ddb498 MT |
564 | If the cgroup doesn't exist, it will be created including any parent cgroups. |
565 | */ | |
566 | int pakfire_cgroup_open(struct pakfire_cgroup** cgroup, | |
35bf392c | 567 | struct pakfire_ctx* ctx, const char* path, int flags) { |
e3ddb498 | 568 | int r = 1; |
9dd11ed0 | 569 | |
e3ddb498 MT |
570 | // Allocate the cgroup struct |
571 | struct pakfire_cgroup* c = calloc(1, sizeof(*c)); | |
572 | if (!c) | |
573 | return 1; | |
9dd11ed0 | 574 | |
35bf392c | 575 | CTX_DEBUG(ctx, "Allocated cgroup %s at %p\n", path, c); |
9dd11ed0 | 576 | |
35bf392c MT |
577 | // Store a reference to the context |
578 | c->ctx = pakfire_ctx_ref(ctx); | |
4630031c | 579 | |
e3ddb498 MT |
580 | // Initialize reference counter |
581 | c->nrefs = 1; | |
5ae21aa1 | 582 | |
31d7e29a MT |
583 | // Find the root |
584 | r = pakfire_cgroup_set_root(c); | |
585 | if (r) | |
586 | goto ERROR; | |
587 | ||
e3ddb498 MT |
588 | // Copy path |
589 | pakfire_string_set(c->path, path); | |
5ae21aa1 | 590 | |
2901c3a7 MT |
591 | // Copy flags |
592 | c->flags = flags; | |
593 | ||
e3ddb498 MT |
594 | // Open a file descriptor |
595 | c->fd = __pakfire_cgroup_open(c); | |
e8a18682 MT |
596 | if (c->fd < 0) { |
597 | r = 1; | |
e3ddb498 | 598 | goto ERROR; |
e8a18682 | 599 | } |
4630031c | 600 | |
2901c3a7 MT |
601 | // Enable accounting if requested |
602 | if (pakfire_cgroup_has_flag(c, PAKFIRE_CGROUP_ENABLE_ACCOUNTING)) { | |
603 | r = pakfire_cgroup_enable_accounting(c); | |
604 | if (r) | |
605 | goto ERROR; | |
606 | } | |
607 | ||
d59e29c8 MT |
608 | // Setup the devices filter |
609 | r = pakfire_cgroup_setup_devices(c); | |
610 | if (r) | |
611 | goto ERROR; | |
612 | ||
e3ddb498 | 613 | *cgroup = c; |
4630031c | 614 | return 0; |
4630031c | 615 | |
e3ddb498 MT |
616 | ERROR: |
617 | pakfire_cgroup_free(c); | |
618 | return r; | |
4630031c MT |
619 | } |
620 | ||
e3ddb498 MT |
621 | struct pakfire_cgroup* pakfire_cgroup_ref(struct pakfire_cgroup* cgroup) { |
622 | ++cgroup->nrefs; | |
4630031c | 623 | |
e3ddb498 | 624 | return cgroup; |
1b41d3b1 MT |
625 | } |
626 | ||
e3ddb498 MT |
627 | struct pakfire_cgroup* pakfire_cgroup_unref(struct pakfire_cgroup* cgroup) { |
628 | if (--cgroup->nrefs > 0) | |
629 | return cgroup; | |
1b41d3b1 | 630 | |
e3ddb498 MT |
631 | pakfire_cgroup_free(cgroup); |
632 | return NULL; | |
1b41d3b1 MT |
633 | } |
634 | ||
aca565fc MT |
635 | // Open a child cgroup |
636 | int pakfire_cgroup_child(struct pakfire_cgroup** child, | |
637 | struct pakfire_cgroup* cgroup, const char* name, int flags) { | |
638 | char path[PATH_MAX]; | |
639 | int r; | |
640 | ||
366a3be1 MT |
641 | // Check input |
642 | if (!name) { | |
643 | errno = EINVAL; | |
644 | return 1; | |
645 | } | |
646 | ||
aca565fc | 647 | // Join paths |
819232d6 | 648 | r = pakfire_path_append(path, cgroup->path, name); |
56796f84 | 649 | if (r) |
aca565fc MT |
650 | return 1; |
651 | ||
652 | // Open the child group | |
35bf392c | 653 | return pakfire_cgroup_open(child, cgroup->ctx, path, flags); |
aca565fc MT |
654 | } |
655 | ||
034ba70e MT |
656 | static int pakfire_cgroup_procs_callback(struct pakfire_cgroup* cgroup, |
657 | int (*callback)(struct pakfire_cgroup* cgroup, pid_t pid, void* data), void* data) { | |
658 | int r = 0; | |
659 | ||
660 | // Check if we have a callback | |
661 | if (!callback) { | |
662 | errno = EINVAL; | |
663 | return 1; | |
664 | } | |
665 | ||
666 | // Open cgroup.procs | |
667 | FILE* f = pakfire_cgroup_open_file(cgroup, "cgroup.procs", "r"); | |
668 | if (!f) | |
669 | return 1; | |
670 | ||
671 | char* line = NULL; | |
672 | size_t l = 0; | |
673 | ||
674 | // Walk through all PIDs | |
675 | while (1) { | |
676 | ssize_t bytes_read = getline(&line, &l, f); | |
677 | if (bytes_read < 0) | |
678 | break; | |
679 | ||
680 | // Parse PID | |
681 | pid_t pid = strtol(line, NULL, 10); | |
682 | ||
683 | // Call callback function | |
684 | r = callback(cgroup, pid, data); | |
685 | if (r) | |
686 | break; | |
687 | } | |
688 | ||
689 | // Cleanup | |
689aa7de MT |
690 | if (line) |
691 | free(line); | |
692 | if (f) | |
693 | fclose(f); | |
034ba70e MT |
694 | |
695 | return r; | |
696 | } | |
697 | ||
698 | static int send_sigkill(struct pakfire_cgroup* cgroup, const pid_t pid, void* data) { | |
35bf392c | 699 | CTX_DEBUG(cgroup->ctx, "Sending signal SIGKILL to PID %d\n", pid); |
034ba70e MT |
700 | |
701 | int r = kill(pid, SIGKILL); | |
702 | if (r < 0 && errno != ESRCH) { | |
35bf392c | 703 | CTX_ERROR(cgroup->ctx, "Could not send signal SIGKILL to PID %d: %m\n", pid); |
034ba70e MT |
704 | return r; |
705 | } | |
706 | ||
707 | return r; | |
708 | } | |
709 | ||
e3ddb498 MT |
710 | /* |
711 | Immediately kills all processes in this cgroup | |
712 | */ | |
689aa7de | 713 | static int pakfire_cgroup_killall(struct pakfire_cgroup* cgroup) { |
35bf392c | 714 | CTX_DEBUG(cgroup->ctx, "%s: Killing all processes\n", pakfire_cgroup_name(cgroup)); |
1b41d3b1 | 715 | |
034ba70e MT |
716 | // Do we have support for cgroup.kill? |
717 | int r = pakfire_cgroup_access(cgroup, "cgroup.kill", F_OK, 0); | |
718 | ||
719 | // Fall back to the legacy version | |
720 | if (r && errno == ENOENT) { | |
721 | return pakfire_cgroup_procs_callback(cgroup, send_sigkill, NULL); | |
722 | } | |
723 | ||
724 | return pakfire_cgroup_write(cgroup, "cgroup.kill", "1"); | |
e3ddb498 | 725 | } |
1b41d3b1 | 726 | |
e3ddb498 MT |
727 | /* |
728 | Immediately destroys this cgroup | |
729 | */ | |
730 | int pakfire_cgroup_destroy(struct pakfire_cgroup* cgroup) { | |
731 | int r; | |
1b41d3b1 | 732 | |
01cf6134 MT |
733 | // Cannot call this for the root group |
734 | if (pakfire_cgroup_is_root(cgroup)) { | |
735 | errno = EPERM; | |
736 | return 1; | |
737 | } | |
738 | ||
35bf392c | 739 | CTX_DEBUG(cgroup->ctx, "Destroying cgroup %s\n", pakfire_cgroup_name(cgroup)); |
01cf6134 | 740 | |
e3ddb498 MT |
741 | // Kill everything in this group |
742 | r = pakfire_cgroup_killall(cgroup); | |
743 | if (r) | |
744 | return r; | |
1b41d3b1 | 745 | |
e3ddb498 | 746 | // Close the file descriptor |
2868fb2f | 747 | if (cgroup->fd > 0) { |
e3ddb498 MT |
748 | close(cgroup->fd); |
749 | cgroup->fd = 0; | |
d5256224 | 750 | } |
d5256224 | 751 | |
01cf6134 MT |
752 | // Open the root directory |
753 | int fd = pakfire_cgroup_open_root(cgroup); | |
754 | if (fd < 0) | |
755 | return 1; | |
756 | ||
757 | // Delete the directory | |
758 | r = unlinkat(fd, cgroup->path, AT_REMOVEDIR); | |
759 | if (r) | |
35bf392c | 760 | CTX_ERROR(cgroup->ctx, "Could not destroy cgroup: %m\n"); |
01cf6134 MT |
761 | |
762 | // Close fd | |
763 | close(fd); | |
764 | ||
765 | return r; | |
d5256224 | 766 | } |
820c32c7 | 767 | |
e3ddb498 MT |
768 | int pakfire_cgroup_fd(struct pakfire_cgroup* cgroup) { |
769 | return cgroup->fd; | |
820c32c7 | 770 | } |
46dd01c6 MT |
771 | |
772 | // Memory | |
773 | ||
774 | int pakfire_cgroup_set_guaranteed_memory(struct pakfire_cgroup* cgroup, size_t mem) { | |
775 | int r; | |
776 | ||
777 | // Enable memory controller | |
778 | r = pakfire_cgroup_enable_controllers(cgroup, PAKFIRE_CGROUP_CONTROLLER_MEMORY); | |
779 | if (r) | |
780 | return r; | |
781 | ||
35bf392c | 782 | CTX_DEBUG(cgroup->ctx, "%s: Setting guaranteed memory to %zu byte(s)\n", |
46dd01c6 MT |
783 | pakfire_cgroup_name(cgroup), mem); |
784 | ||
785 | // Set value | |
786 | r = pakfire_cgroup_write(cgroup, "memory.min", "%zu\n", mem); | |
787 | if (r) | |
35bf392c | 788 | CTX_ERROR(cgroup->ctx, "%s: Could not set guaranteed memory: %m\n", |
46dd01c6 MT |
789 | pakfire_cgroup_name(cgroup)); |
790 | ||
791 | return r; | |
792 | } | |
793 | ||
794 | int pakfire_cgroup_set_memory_limit(struct pakfire_cgroup* cgroup, size_t mem) { | |
795 | int r; | |
796 | ||
797 | // Enable memory controller | |
798 | r = pakfire_cgroup_enable_controllers(cgroup, PAKFIRE_CGROUP_CONTROLLER_MEMORY); | |
799 | if (r) | |
800 | return r; | |
801 | ||
35bf392c | 802 | CTX_DEBUG(cgroup->ctx, "%s: Setting memory limit to %zu byte(s)\n", |
46dd01c6 MT |
803 | pakfire_cgroup_name(cgroup), mem); |
804 | ||
805 | // Set value | |
806 | r = pakfire_cgroup_write(cgroup, "memory.max", "%zu\n", mem); | |
807 | if (r) | |
35bf392c | 808 | CTX_ERROR(cgroup->ctx, "%s: Could not set memory limit: %m\n", |
46dd01c6 MT |
809 | pakfire_cgroup_name(cgroup)); |
810 | ||
811 | return r; | |
812 | } | |
d3b93302 MT |
813 | |
814 | // PIDs | |
815 | ||
816 | int pakfire_cgroup_set_pid_limit(struct pakfire_cgroup* cgroup, size_t limit) { | |
817 | int r; | |
818 | ||
819 | // Enable PID controller | |
820 | r = pakfire_cgroup_enable_controllers(cgroup, PAKFIRE_CGROUP_CONTROLLER_PIDS); | |
821 | if (r) | |
822 | return r; | |
823 | ||
35bf392c | 824 | CTX_DEBUG(cgroup->ctx, "%s: Setting PID limit to %zu\n", |
d3b93302 MT |
825 | pakfire_cgroup_name(cgroup), limit); |
826 | ||
827 | // Set value | |
828 | r = pakfire_cgroup_write(cgroup, "pids.max", "%zu\n", limit); | |
829 | if (r) | |
35bf392c | 830 | CTX_ERROR(cgroup->ctx, "%s: Could not set PID limit: %m\n", |
d3b93302 MT |
831 | pakfire_cgroup_name(cgroup)); |
832 | ||
833 | return r; | |
834 | } | |
6b7cf275 MT |
835 | |
836 | // Stats | |
837 | ||
838 | static int __pakfire_cgroup_read_stats_line(struct pakfire_cgroup* cgroup, | |
839 | int (*callback)(struct pakfire_cgroup* cgroup, const char* key, unsigned long val, void* data), | |
840 | void* data, char* line) { | |
841 | char* p = NULL; | |
842 | ||
6b7cf275 MT |
843 | char key[NAME_MAX]; |
844 | unsigned long val = 0; | |
845 | ||
846 | // Number of the field | |
847 | int i = 0; | |
848 | ||
849 | char* elem = strtok_r(line, " ", &p); | |
850 | while (elem) { | |
851 | switch (i++) { | |
852 | // First field is the key | |
853 | case 0: | |
854 | // Copy the key | |
855 | pakfire_string_set(key, elem); | |
856 | break; | |
857 | ||
858 | // The second field is some value | |
859 | case 1: | |
860 | val = strtoul(elem, NULL, 10); | |
861 | break; | |
862 | ||
863 | // Ignore the rest | |
864 | default: | |
35bf392c | 865 | CTX_DEBUG(cgroup->ctx, "%s: Unknown value in cgroup stats (%d): %s\n", |
6b7cf275 MT |
866 | pakfire_cgroup_name(cgroup), i, elem); |
867 | break; | |
868 | } | |
869 | ||
870 | elem = strtok_r(NULL, " ", &p); | |
871 | } | |
872 | ||
873 | // Check if we parsed both fields | |
874 | if (i < 2) { | |
35bf392c | 875 | CTX_ERROR(cgroup->ctx, "Could not parse line\n"); |
6b7cf275 MT |
876 | return 1; |
877 | } | |
878 | ||
879 | // Call the callback | |
880 | return callback(cgroup, key, val, data); | |
881 | } | |
882 | ||
883 | static int __pakfire_cgroup_read_stats(struct pakfire_cgroup* cgroup, const char* path, | |
884 | int (*callback)(struct pakfire_cgroup* cgroup, const char* key, unsigned long val, void* data), | |
885 | void* data) { | |
886 | char* p = NULL; | |
887 | int r; | |
888 | ||
889 | char buffer[BUFFER_SIZE]; | |
890 | ||
35bf392c | 891 | CTX_DEBUG(cgroup->ctx, "%s: Reading stats from %s\n", pakfire_cgroup_name(cgroup), path); |
6b7cf275 MT |
892 | |
893 | // Open the file | |
894 | r = pakfire_cgroup_read(cgroup, path, buffer, sizeof(buffer)); | |
895 | if (r < 0) | |
896 | goto ERROR; | |
897 | ||
898 | char* line = strtok_r(buffer, "\n", &p); | |
899 | while (line) { | |
900 | // Parse the line | |
901 | r = __pakfire_cgroup_read_stats_line(cgroup, callback, data, line); | |
902 | if (r) | |
903 | goto ERROR; | |
904 | ||
905 | // Move to the next line | |
906 | line = strtok_r(NULL, "\n", &p); | |
907 | } | |
908 | ||
909 | ERROR: | |
910 | return r; | |
911 | } | |
912 | ||
913 | struct pakfire_cgroup_stat_entry { | |
914 | const char* key; | |
915 | unsigned long* val; | |
916 | }; | |
917 | ||
918 | static int __pakfire_cgroup_parse_cpu_stats(struct pakfire_cgroup* cgroup, | |
919 | const char* key, unsigned long val, void* data) { | |
920 | struct pakfire_cgroup_cpu_stats* stats = (struct pakfire_cgroup_cpu_stats*)data; | |
921 | ||
922 | const struct pakfire_cgroup_stat_entry entries[] = { | |
923 | { "system_usec", &stats->system_usec }, | |
924 | { "usage_usec", &stats->usage_usec }, | |
925 | { "user_usec", &stats->user_usec }, | |
926 | { NULL, NULL }, | |
927 | }; | |
928 | // Find and store value | |
929 | for (const struct pakfire_cgroup_stat_entry* entry = entries; entry->key; entry++) { | |
930 | if (strcmp(entry->key, key) == 0) { | |
931 | *entry->val = val; | |
932 | return 0; | |
933 | } | |
934 | } | |
935 | ||
35bf392c | 936 | CTX_DEBUG(cgroup->ctx, "Unknown key for CPU stats: %s = %lu\n", key, val); |
6b7cf275 MT |
937 | |
938 | return 0; | |
939 | } | |
940 | ||
941 | static int __pakfire_cgroup_parse_memory_stats(struct pakfire_cgroup* cgroup, | |
942 | const char* key, unsigned long val, void* data) { | |
943 | struct pakfire_cgroup_memory_stats* stats = (struct pakfire_cgroup_memory_stats*)data; | |
944 | ||
945 | const struct pakfire_cgroup_stat_entry entries[] = { | |
946 | { "anon", &stats->anon }, | |
947 | { "file", &stats->file }, | |
948 | { "kernel", &stats->kernel }, | |
949 | { "kernel_stack", &stats->kernel_stack }, | |
950 | { "pagetables", &stats->pagetables }, | |
951 | { "percpu", &stats->percpu }, | |
952 | { "sock", &stats->sock }, | |
953 | { "vmalloc", &stats->vmalloc }, | |
954 | { "shmem", &stats->shmem }, | |
955 | { "zswap", &stats->zswap }, | |
956 | { "zswapped", &stats->zswapped }, | |
957 | { "file_mapped", &stats->file_mapped }, | |
958 | { "file_dirty", &stats->file_dirty }, | |
959 | { "file_writeback", &stats->file_writeback }, | |
960 | { "swapcached", &stats->swapcached }, | |
961 | { "anon_thp", &stats->anon_thp }, | |
962 | { "file_thp", &stats->file_thp }, | |
963 | { "shmem_thp", &stats->shmem_thp }, | |
964 | { "inactive_anon", &stats->inactive_anon }, | |
965 | { "active_anon", &stats->active_anon }, | |
966 | { "inactive_file", &stats->inactive_file }, | |
967 | { "active_file", &stats->active_file }, | |
968 | { "unevictable", &stats->unevictable }, | |
969 | { "slab_reclaimable", &stats->slab_reclaimable }, | |
970 | { "slab_unreclaimable", &stats->slab_unreclaimable }, | |
971 | { "slab", &stats->slab }, | |
972 | { "workingset_refault_anon", &stats->workingset_refault_anon }, | |
973 | { "workingset_refault_file", &stats->workingset_refault_file }, | |
974 | { "workingset_activate_anon", &stats->workingset_activate_anon }, | |
975 | { "workingset_activate_file", &stats->workingset_activate_file }, | |
976 | { "workingset_restore_anon", &stats->workingset_restore_anon }, | |
977 | { "workingset_restore_file", &stats->workingset_restore_file }, | |
978 | { "workingset_nodereclaim", &stats->workingset_nodereclaim }, | |
979 | { "pgfault", &stats->pgfault }, | |
980 | { "pgmajfault", &stats->pgmajfault }, | |
981 | { "pgrefill", &stats->pgrefill }, | |
982 | { "pgscan", &stats->pgscan }, | |
983 | { "pgsteal", &stats->pgsteal }, | |
984 | { "pgactivate", &stats->pgactivate }, | |
985 | { "pgdeactivate", &stats->pgdeactivate }, | |
986 | { "pglazyfree", &stats->pglazyfree }, | |
987 | { "pglazyfreed", &stats->pglazyfreed }, | |
988 | { "thp_fault_alloc", &stats->thp_fault_alloc }, | |
989 | { "thp_collapse_alloc", &stats->thp_collapse_alloc }, | |
990 | { NULL, NULL }, | |
991 | }; | |
992 | ||
993 | // Find and store value | |
994 | for (const struct pakfire_cgroup_stat_entry* entry = entries; entry->key; entry++) { | |
995 | if (strcmp(entry->key, key) == 0) { | |
996 | *entry->val = val; | |
997 | return 0; | |
998 | } | |
999 | } | |
1000 | ||
1001 | // Log any unknown keys | |
35bf392c | 1002 | CTX_DEBUG(cgroup->ctx, "Unknown key for memory stats: %s = %lu\n", key, val); |
6b7cf275 MT |
1003 | |
1004 | return 0; | |
1005 | } | |
1006 | ||
1007 | int pakfire_cgroup_stat(struct pakfire_cgroup* cgroup, | |
1008 | struct pakfire_cgroup_stats* stats) { | |
1009 | int r; | |
1010 | ||
1011 | // Check input | |
1012 | if (!stats) { | |
1013 | errno = EINVAL; | |
1014 | return 1; | |
1015 | } | |
1016 | ||
1017 | // Read CPU stats | |
1018 | r = __pakfire_cgroup_read_stats(cgroup, "cpu.stat", | |
1019 | __pakfire_cgroup_parse_cpu_stats, &stats->cpu); | |
1020 | if (r) | |
1021 | goto ERROR; | |
1022 | ||
1023 | // Read memory stats | |
1024 | r = __pakfire_cgroup_read_stats(cgroup, "memory.stat", | |
1025 | __pakfire_cgroup_parse_memory_stats, &stats->memory); | |
1026 | if (r) | |
1027 | goto ERROR; | |
1028 | ||
1029 | ERROR: | |
1030 | if (r) | |
35bf392c | 1031 | CTX_ERROR(cgroup->ctx, "%s: Could not read cgroup stats: %m\n", |
6b7cf275 MT |
1032 | pakfire_cgroup_name(cgroup)); |
1033 | ||
1034 | return r; | |
1035 | } | |
1036 | ||
1037 | int pakfire_cgroup_stat_dump(struct pakfire_cgroup* cgroup, | |
1038 | const struct pakfire_cgroup_stats* stats) { | |
1039 | // Check input | |
1040 | if (!stats) { | |
1041 | errno = EINVAL; | |
1042 | return 1; | |
1043 | } | |
1044 | ||
35bf392c | 1045 | CTX_DEBUG(cgroup->ctx, "%s: Total CPU time usage: %lu\n", |
6b7cf275 MT |
1046 | pakfire_cgroup_name(cgroup), stats->cpu.usage_usec); |
1047 | ||
1048 | return 0; | |
1049 | } |