]> git.ipfire.org Git - pakfire.git/blame - src/libpakfire/cgroup.c
cgroup: Fix destruction function
[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>
9dd11ed0 23#include <stdlib.h>
9dd11ed0 24#include <sys/types.h>
9dd11ed0
MT
25
26#include <pakfire/cgroup.h>
27#include <pakfire/logging.h>
e3ddb498 28#include <pakfire/pakfire.h>
9dd11ed0
MT
29#include <pakfire/util.h>
30
e3ddb498
MT
31#define ROOT "/sys/fs/cgroup"
32#define BUFFER_SIZE 64 * 1024
9dd11ed0 33
2901c3a7
MT
34enum pakfire_cgroup_controllers {
35 PAKFIRE_CGROUP_CONTROLLER_CPU = (1 << 0),
36 PAKFIRE_CGROUP_CONTROLLER_MEMORY = (1 << 1),
37 PAKFIRE_CGROUP_CONTROLLER_PIDS = (1 << 2),
38 PAKFIRE_CGROUP_CONTROLLER_IO = (1 << 3),
39};
40
41static const enum pakfire_cgroup_controllers pakfire_cgroup_accounting_controllers =
42 PAKFIRE_CGROUP_CONTROLLER_CPU |
43 PAKFIRE_CGROUP_CONTROLLER_MEMORY |
44 PAKFIRE_CGROUP_CONTROLLER_PIDS |
45 PAKFIRE_CGROUP_CONTROLLER_IO;
46
e3ddb498
MT
47struct pakfire_cgroup {
48 struct pakfire* pakfire;
49 int nrefs;
9dd11ed0 50
2901c3a7
MT
51 // Flags
52 int flags;
53
e3ddb498
MT
54 // Store the path
55 char path[PATH_MAX];
9dd11ed0 56
e3ddb498
MT
57 // File descriptor to cgroup
58 int fd;
59};
9e1e7985 60
e3ddb498
MT
61// Returns true if this is the root cgroup
62static int pakfire_cgroup_is_root(struct pakfire_cgroup* cgroup) {
63 return !*cgroup->path;
64}
9e1e7985 65
2901c3a7
MT
66static int pakfire_cgroup_has_flag(struct pakfire_cgroup* cgroup, int flag) {
67 return cgroup->flags & flag;
68}
69
e3ddb498
MT
70static const char* pakfire_cgroup_name(struct pakfire_cgroup* cgroup) {
71 if (pakfire_cgroup_is_root(cgroup))
72 return "(root)";
9e1e7985 73
e3ddb498 74 return cgroup->path;
9e1e7985
MT
75}
76
2901c3a7
MT
77static const char* pakfire_cgroup_controller_name(
78 enum pakfire_cgroup_controllers controller) {
79 switch (controller) {
80 case PAKFIRE_CGROUP_CONTROLLER_CPU:
81 return "cpu";
82
83 case PAKFIRE_CGROUP_CONTROLLER_MEMORY:
84 return "memory";
85
86 case PAKFIRE_CGROUP_CONTROLLER_PIDS:
87 return "pids";
88
89 case PAKFIRE_CGROUP_CONTROLLER_IO:
90 return "io";
91 }
92
93 return NULL;
94}
95
96static enum pakfire_cgroup_controllers pakfire_cgroup_find_controller_by_name(
97 const char* name) {
98 const char* n = NULL;
99
100 // Walk through the bitmap
101 for (unsigned int i = 1; i; i <<= 1) {
102 n = pakfire_cgroup_controller_name(i);
103 if (!n)
104 break;
105
106 // Match
107 if (strcmp(name, n) == 0)
108 return i;
109 }
110
111 // Nothing found
112 return 0;
113}
114
115static struct pakfire_cgroup* pakfire_cgroup_parent(struct pakfire_cgroup* cgroup) {
116 struct pakfire_cgroup* parent = NULL;
117 int r;
118
119 // Cannot return parent for root group
120 if (pakfire_cgroup_is_root(cgroup))
121 return NULL;
122
123 // Determine the path of the parent
124 char* path = pakfire_dirname(cgroup->path);
125 if (!path) {
126 ERROR(cgroup->pakfire, "Could not determine path for parent cgroup: %m\n");
127 return NULL;
128 }
129
130 // dirname() returns . if no directory component could be found
131 if (strcmp(path, ".") == 0)
132 *path = '\0';
133
134 // Open the cgroup
135 r = pakfire_cgroup_open(&parent, cgroup->pakfire, path, 0);
136 if (r) {
137 ERROR(cgroup->pakfire, "Could not open parent cgroup: %m\n");
138 parent = NULL;
139 }
140
141 // Cleanup
142 free(path);
143
144 return parent;
145}
146
e3ddb498
MT
147static void pakfire_cgroup_free(struct pakfire_cgroup* cgroup) {
148 DEBUG(cgroup->pakfire, "Releasing cgroup %s at %p\n",
149 pakfire_cgroup_name(cgroup), cgroup);
9e1e7985 150
e3ddb498
MT
151 // Close the file descriptor
152 if (cgroup->fd)
153 close(cgroup->fd);
9e1e7985 154
e3ddb498
MT
155 pakfire_unref(cgroup->pakfire);
156 free(cgroup);
9e1e7985
MT
157}
158
01cf6134
MT
159static int pakfire_cgroup_open_root(struct pakfire_cgroup* cgroup) {
160 int fd = open(ROOT, O_DIRECTORY|O_PATH|O_CLOEXEC);
161 if (fd < 0) {
162 ERROR(cgroup->pakfire, "Could not open %s: %m\n", ROOT);
163 return -1;
164 }
165
166 return fd;
167}
168
e3ddb498
MT
169static int __pakfire_cgroup_create(struct pakfire_cgroup* cgroup) {
170 char path[PATH_MAX];
171 int r;
9dd11ed0 172
e3ddb498 173 DEBUG(cgroup->pakfire, "Trying to create cgroup %s\n", pakfire_cgroup_name(cgroup));
9dd11ed0 174
e3ddb498
MT
175 // Compose the absolute path
176 r = pakfire_path_join(path, ROOT, cgroup->path);
177 if (r < 0)
178 return 1;
9dd11ed0 179
e3ddb498
MT
180 // Try creating the directory
181 return pakfire_mkdir(path, 0755);
182}
9dd11ed0 183
e3ddb498
MT
184/*
185 Opens the cgroup and returns a file descriptor.
9dd11ed0 186
e3ddb498 187 If the cgroup does not exist, it will try to create it.
9dd11ed0 188
e3ddb498
MT
189 This function returns a negative value on error.
190*/
191static int __pakfire_cgroup_open(struct pakfire_cgroup* cgroup) {
e3ddb498
MT
192 int fd = -1;
193 int r;
9dd11ed0 194
e3ddb498 195 // Open file descriptor of the cgroup root
01cf6134
MT
196 int rootfd = pakfire_cgroup_open_root(cgroup);
197 if (rootfd < 0)
e3ddb498 198 return -1;
9dd11ed0 199
e3ddb498
MT
200 // Return the rootfd for the root group
201 if (pakfire_cgroup_is_root(cgroup))
202 return rootfd;
9dd11ed0 203
e3ddb498
MT
204RETRY:
205 fd = openat(rootfd, cgroup->path, O_DIRECTORY|O_PATH|O_CLOEXEC);
206 if (fd < 0) {
207 switch (errno) {
208 // If the cgroup doesn't exist yet, try to create it
209 case ENOENT:
210 r = __pakfire_cgroup_create(cgroup);
211 if (r)
212 goto ERROR;
9dd11ed0 213
e3ddb498
MT
214 // Retry open after successful creation
215 goto RETRY;
9dd11ed0 216
e3ddb498
MT
217 // Exit on all other errors
218 default:
219 ERROR(cgroup->pakfire, "Could not open cgroup %s: %m\n",
220 pakfire_cgroup_name(cgroup));
221 goto ERROR;
222 }
223 }
9dd11ed0 224
e3ddb498
MT
225ERROR:
226 if (rootfd > 0)
227 close(rootfd);
9dd11ed0 228
e3ddb498
MT
229 return fd;
230}
9dd11ed0 231
2901c3a7 232static ssize_t pakfire_cgroup_read(struct pakfire_cgroup* cgroup, const char* path,
e3ddb498 233 char* buffer, size_t length) {
2901c3a7 234 ssize_t bytes_read = -1;
9dd11ed0 235
e3ddb498
MT
236 // Check if this cgroup has been destroyed already
237 if (!cgroup->fd) {
238 ERROR(cgroup->pakfire, "Trying to read from destroyed cgroup\n");
2901c3a7 239 return -1;
9dd11ed0
MT
240 }
241
e3ddb498
MT
242 // Open the file
243 int fd = openat(cgroup->fd, path, O_CLOEXEC);
244 if (fd < 0) {
245 DEBUG(cgroup->pakfire, "Could not open %s/%s: %m\n",
246 pakfire_cgroup_name(cgroup), path);
247 goto ERROR;
248 }
9dd11ed0 249
e3ddb498 250 // Read file content into buffer
2901c3a7
MT
251 bytes_read = read(fd, buffer, length);
252 if (bytes_read < 0) {
e3ddb498
MT
253 DEBUG(cgroup->pakfire, "Could not read from %s/%s: %m\n",
254 pakfire_cgroup_name(cgroup), path);
255 goto ERROR;
256 }
9dd11ed0 257
2901c3a7
MT
258 // Terminate the buffer
259 if (bytes_read < length)
260 buffer[bytes_read] = '\0';
9dd11ed0 261
e3ddb498
MT
262ERROR:
263 if (fd > 0)
264 close(fd);
9dd11ed0 265
2901c3a7 266 return bytes_read;
9dd11ed0
MT
267}
268
e3ddb498
MT
269static int pakfire_cgroup_write(struct pakfire_cgroup* cgroup,
270 const char* path, const char* format, ...) {
305de320 271 va_list args;
e3ddb498
MT
272 int r = 0;
273
274 // Check if this cgroup has been destroyed already
275 if (!cgroup->fd) {
276 ERROR(cgroup->pakfire, "Trying to write to destroyed cgroup\n");
277 errno = EPERM;
278 return 1;
279 }
9dd11ed0 280
e3ddb498
MT
281 // Open the file
282 int fd = openat(cgroup->fd, path, O_WRONLY|O_CLOEXEC);
283 if (fd < 0) {
284 DEBUG(cgroup->pakfire, "Could not open %s/%s for writing: %m\n",
285 pakfire_cgroup_name(cgroup), path);
9dd11ed0 286 return 1;
e3ddb498 287 }
9dd11ed0 288
e3ddb498 289 // Write buffer
305de320 290 va_start(args, format);
e3ddb498 291 ssize_t bytes_written = vdprintf(fd, format, args);
305de320
MT
292 va_end(args);
293
e3ddb498
MT
294 // Check if content was written okay
295 if (bytes_written < 0) {
296 DEBUG(cgroup->pakfire, "Could not write to %s/%s: %m\n",
297 pakfire_cgroup_name(cgroup), path);
298 r = 1;
299 }
820c32c7 300
e3ddb498
MT
301 // Close fd
302 close(fd);
305de320
MT
303
304 return r;
305}
306
2901c3a7
MT
307static int pakfire_cgroup_read_controllers(
308 struct pakfire_cgroup* cgroup, const char* name) {
309 char buffer[BUFFER_SIZE];
310 char* p = NULL;
9dd11ed0
MT
311 int r;
312
2901c3a7
MT
313 // Discovered controllers
314 int controllers = 0;
69cfa22d 315
2901c3a7
MT
316 // Read cgroup.controllers file
317 ssize_t bytes_read = pakfire_cgroup_read(cgroup, name, buffer, sizeof(buffer));
318 if (bytes_read < 0)
319 return -1;
320
321 // If the file was empty, there is nothing more to do
322 if (bytes_read == 0)
323 return 0;
324
325 char* token = strtok_r(buffer, " \n", &p);
326
327 while (token) {
328 DEBUG(cgroup->pakfire, "Found controller '%s'\n", token);
329
330 // Try finding this controller
331 int controller = pakfire_cgroup_find_controller_by_name(token);
332 if (controller)
333 controllers |= controller;
334
335 // Move on to next token
336 token = strtok_r(NULL, " \n", &p);
9dd11ed0
MT
337 }
338
2901c3a7
MT
339 // Return discovered controllers
340 return controllers;
341}
9dd11ed0 342
2901c3a7
MT
343/*
344 Returns a bitmap of all available controllers
345*/
346static int pakfire_cgroup_available_controllers(struct pakfire_cgroup* cgroup) {
347 return pakfire_cgroup_read_controllers(cgroup, "cgroup.controllers");
348}
349
350/*
351 Returns a bitmap of all enabled controllers
352*/
353static int pakfire_cgroup_enabled_controllers(struct pakfire_cgroup* cgroup) {
354 return pakfire_cgroup_read_controllers(cgroup, "cgroup.subtree_control");
355}
356
357/*
358 This function takes a bitmap of controllers that should be enabled.
359*/
360static int pakfire_cgroup_enable_controllers(struct pakfire_cgroup* cgroup,
361 enum pakfire_cgroup_controllers controllers) {
362 struct pakfire_cgroup* parent = NULL;
363 int r = 1;
364
365 // Find all enabled controllers
366 const int enabled_controllers = pakfire_cgroup_enabled_controllers(cgroup);
367 if (enabled_controllers < 0) {
368 ERROR(cgroup->pakfire, "Could not fetch enabled controllers: %m\n");
369 goto ERROR;
9dd11ed0
MT
370 }
371
2901c3a7
MT
372 // Filter out anything that is already enabled
373 controllers = (controllers & ~enabled_controllers);
9dd11ed0 374
2901c3a7
MT
375 // Exit if everything is already enabled
376 if (!controllers) {
377 DEBUG(cgroup->pakfire, "All controllers are already enabled\n");
378 return 0;
379 }
380
381 // Find all available controllers
382 const int available_controllers = pakfire_cgroup_available_controllers(cgroup);
383 if (available_controllers < 0) {
384 ERROR(cgroup->pakfire, "Could not fetch available controllers: %m\n");
385 goto ERROR;
386 }
387
388 // Are all controllers we need available, yet?
389 if (controllers & ~available_controllers) {
390 DEBUG(cgroup->pakfire, "Not all controllers are available, yet\n");
391
392 parent = pakfire_cgroup_parent(cgroup);
393
394 // Enable everything we need on the parent group
395 if (parent) {
396 r = pakfire_cgroup_enable_controllers(parent, controllers);
397 if (r)
398 goto ERROR;
399 }
400 }
401
402 // Determine how many iterations we will need
403 const int iterations = 1 << (sizeof(controllers) * 8 - __builtin_clz(controllers));
404
405 // Iterate over all known controllers
406 for (int controller = 1; controller < iterations; controller <<= 1) {
407 // Skip enabling this controller if not requested
408 if (!(controller & controllers))
409 continue;
410
411 // Fetch name
412 const char* name = pakfire_cgroup_controller_name(controller);
413
414 DEBUG(cgroup->pakfire, "Enabling controller %s in cgroup %s\n",
415 name, pakfire_cgroup_name(cgroup));
416
417 // Try enabling the controller (this will succeed if it already is enabled)
418 r = pakfire_cgroup_write(cgroup, "cgroup.subtree_control", "+%s\n", name);
419 if (r) {
420 ERROR(cgroup->pakfire, "Could not enable controller %s in cgroup %s\n",
421 name, pakfire_cgroup_name(cgroup));
422 goto ERROR;
423 }
424 }
425
426ERROR:
427 if (parent)
428 pakfire_cgroup_unref(parent);
429
430 return r;
431}
432
433static int pakfire_cgroup_enable_accounting(struct pakfire_cgroup* cgroup) {
434 // Enable all accounting controllers
435 return pakfire_cgroup_enable_controllers(cgroup,
436 pakfire_cgroup_accounting_controllers);
9dd11ed0
MT
437}
438
e3ddb498
MT
439/*
440 Entry function to open a new cgroup.
9dd11ed0 441
e3ddb498
MT
442 If the cgroup doesn't exist, it will be created including any parent cgroups.
443*/
444int pakfire_cgroup_open(struct pakfire_cgroup** cgroup,
2901c3a7 445 struct pakfire* pakfire, const char* path, int flags) {
e3ddb498 446 int r = 1;
9dd11ed0 447
e3ddb498
MT
448 // Allocate the cgroup struct
449 struct pakfire_cgroup* c = calloc(1, sizeof(*c));
450 if (!c)
451 return 1;
9dd11ed0 452
e3ddb498 453 DEBUG(pakfire, "Allocated cgroup %s at %p\n", path, c);
9dd11ed0 454
e3ddb498
MT
455 // Keep a reference to pakfire
456 c->pakfire = pakfire_ref(pakfire);
4630031c 457
e3ddb498
MT
458 // Initialize reference counter
459 c->nrefs = 1;
5ae21aa1 460
e3ddb498
MT
461 // Copy path
462 pakfire_string_set(c->path, path);
5ae21aa1 463
2901c3a7
MT
464 // Copy flags
465 c->flags = flags;
466
e3ddb498
MT
467 // Open a file descriptor
468 c->fd = __pakfire_cgroup_open(c);
469 if (c->fd < 0)
470 goto ERROR;
4630031c 471
2901c3a7
MT
472 // Enable accounting if requested
473 if (pakfire_cgroup_has_flag(c, PAKFIRE_CGROUP_ENABLE_ACCOUNTING)) {
474 r = pakfire_cgroup_enable_accounting(c);
475 if (r)
476 goto ERROR;
477 }
478
e3ddb498 479 *cgroup = c;
4630031c 480 return 0;
4630031c 481
e3ddb498
MT
482ERROR:
483 pakfire_cgroup_free(c);
484 return r;
4630031c
MT
485}
486
e3ddb498
MT
487struct pakfire_cgroup* pakfire_cgroup_ref(struct pakfire_cgroup* cgroup) {
488 ++cgroup->nrefs;
4630031c 489
e3ddb498 490 return cgroup;
1b41d3b1
MT
491}
492
e3ddb498
MT
493struct pakfire_cgroup* pakfire_cgroup_unref(struct pakfire_cgroup* cgroup) {
494 if (--cgroup->nrefs > 0)
495 return cgroup;
1b41d3b1 496
e3ddb498
MT
497 pakfire_cgroup_free(cgroup);
498 return NULL;
1b41d3b1
MT
499}
500
e3ddb498
MT
501/*
502 Immediately kills all processes in this cgroup
503*/
504int pakfire_cgroup_killall(struct pakfire_cgroup* cgroup) {
505 int r = pakfire_cgroup_write(cgroup, "cgroup.kill", "1");
506 if (r)
507 ERROR(cgroup->pakfire, "Could not kill processes: %m\n");
1b41d3b1 508
e3ddb498
MT
509 return r;
510}
1b41d3b1 511
e3ddb498
MT
512/*
513 Immediately destroys this cgroup
514*/
515int pakfire_cgroup_destroy(struct pakfire_cgroup* cgroup) {
516 int r;
1b41d3b1 517
01cf6134
MT
518 // Cannot call this for the root group
519 if (pakfire_cgroup_is_root(cgroup)) {
520 errno = EPERM;
521 return 1;
522 }
523
524 DEBUG(cgroup->pakfire, "Destroying cgroup %s\n", pakfire_cgroup_name(cgroup));
525
e3ddb498
MT
526 // Kill everything in this group
527 r = pakfire_cgroup_killall(cgroup);
528 if (r)
529 return r;
1b41d3b1 530
e3ddb498
MT
531 // Close the file descriptor
532 if (cgroup->fd) {
533 close(cgroup->fd);
534 cgroup->fd = 0;
d5256224 535 }
d5256224 536
01cf6134
MT
537 // Open the root directory
538 int fd = pakfire_cgroup_open_root(cgroup);
539 if (fd < 0)
540 return 1;
541
542 // Delete the directory
543 r = unlinkat(fd, cgroup->path, AT_REMOVEDIR);
544 if (r)
545 ERROR(cgroup->pakfire, "Could not destroy cgroup: %m\n");
546
547 // Close fd
548 close(fd);
549
550 return r;
d5256224 551}
820c32c7 552
e3ddb498
MT
553int pakfire_cgroup_fd(struct pakfire_cgroup* cgroup) {
554 return cgroup->fd;
820c32c7 555}
46dd01c6
MT
556
557// Memory
558
559int pakfire_cgroup_set_guaranteed_memory(struct pakfire_cgroup* cgroup, size_t mem) {
560 int r;
561
562 // Enable memory controller
563 r = pakfire_cgroup_enable_controllers(cgroup, PAKFIRE_CGROUP_CONTROLLER_MEMORY);
564 if (r)
565 return r;
566
567 DEBUG(cgroup->pakfire, "%s: Setting guaranteed memory to %zu byte(s)\n",
568 pakfire_cgroup_name(cgroup), mem);
569
570 // Set value
571 r = pakfire_cgroup_write(cgroup, "memory.min", "%zu\n", mem);
572 if (r)
573 ERROR(cgroup->pakfire, "%s: Could not set guaranteed memory: %m\n",
574 pakfire_cgroup_name(cgroup));
575
576 return r;
577}
578
579int pakfire_cgroup_set_memory_limit(struct pakfire_cgroup* cgroup, size_t mem) {
580 int r;
581
582 // Enable memory controller
583 r = pakfire_cgroup_enable_controllers(cgroup, PAKFIRE_CGROUP_CONTROLLER_MEMORY);
584 if (r)
585 return r;
586
587 DEBUG(cgroup->pakfire, "%s: Setting memory limit to %zu byte(s)\n",
588 pakfire_cgroup_name(cgroup), mem);
589
590 // Set value
591 r = pakfire_cgroup_write(cgroup, "memory.max", "%zu\n", mem);
592 if (r)
593 ERROR(cgroup->pakfire, "%s: Could not set memory limit: %m\n",
594 pakfire_cgroup_name(cgroup));
595
596 return r;
597}