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