]> git.ipfire.org Git - pakfire.git/blame - src/libpakfire/cgroup.c
cgroup: Be less verbose when parsing stats
[pakfire.git] / src / libpakfire / cgroup.c
CommitLineData
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
60enum 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
67static 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 73struct 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
94static int pakfire_cgroup_is_root(struct pakfire_cgroup* cgroup) {
95 return !*cgroup->path;
96}
9e1e7985 97
2901c3a7
MT
98static int pakfire_cgroup_has_flag(struct pakfire_cgroup* cgroup, int flag) {
99 return cgroup->flags & flag;
100}
101
31d7e29a
MT
102static 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
127static 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
134static 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
153static 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
172static 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 202static 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 216static 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 253static 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
263static 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*/
285static 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
298RETRY:
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
319ERROR:
320 if (rootfd > 0)
321 close(rootfd);
9dd11ed0 322
e3ddb498
MT
323 return fd;
324}
9dd11ed0 325
034ba70e
MT
326static 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
331static 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
348ERROR:
2868fb2f 349 if (fd > 0)
034ba70e
MT
350 close(fd);
351
352 return f;
353}
354
2901c3a7 355static 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
385ERROR:
386 if (fd > 0)
387 close(fd);
9dd11ed0 388
2901c3a7 389 return bytes_read;
9dd11ed0
MT
390}
391
e3ddb498
MT
392static 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
430static 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*/
468static 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*/
475static 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*/
482static 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
548ERROR:
549 if (parent)
550 pakfire_cgroup_unref(parent);
551
552 return r;
553}
554
555static 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*/
566int 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
616ERROR:
617 pakfire_cgroup_free(c);
618 return r;
4630031c
MT
619}
620
e3ddb498
MT
621struct pakfire_cgroup* pakfire_cgroup_ref(struct pakfire_cgroup* cgroup) {
622 ++cgroup->nrefs;
4630031c 623
e3ddb498 624 return cgroup;
1b41d3b1
MT
625}
626
e3ddb498
MT
627struct 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
636int 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
656static 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
698static 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 713static 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*/
730int 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
768int pakfire_cgroup_fd(struct pakfire_cgroup* cgroup) {
769 return cgroup->fd;
820c32c7 770}
46dd01c6
MT
771
772// Memory
773
774int 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
794int 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
816int 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
838static 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
883static 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
909ERROR:
910 return r;
911}
912
913struct pakfire_cgroup_stat_entry {
914 const char* key;
915 unsigned long* val;
916};
917
918static 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
941static 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
1007int 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
1029ERROR:
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
1037int 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}