]> git.ipfire.org Git - pakfire.git/blob - src/libpakfire/cgroup.c
cgroups: Make killall function static and fix memory leak
[pakfire.git] / src / libpakfire / cgroup.c
1 /*#############################################################################
2 # #
3 # Pakfire - The IPFire package management system #
4 # Copyright (C) 2022 Pakfire development team #
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>
22 #include <fcntl.h>
23 #include <signal.h>
24 #include <stdlib.h>
25 #include <sys/types.h>
26
27 #include <pakfire/cgroup.h>
28 #include <pakfire/logging.h>
29 #include <pakfire/pakfire.h>
30 #include <pakfire/string.h>
31 #include <pakfire/util.h>
32
33 #define ROOT "/sys/fs/cgroup"
34 #define BUFFER_SIZE 64 * 1024
35
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
49 struct pakfire_cgroup {
50 struct pakfire* pakfire;
51 int nrefs;
52
53 // Flags
54 int flags;
55
56 // Store the path
57 char path[PATH_MAX];
58
59 // File descriptor to cgroup
60 int fd;
61 };
62
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 }
67
68 static int pakfire_cgroup_has_flag(struct pakfire_cgroup* cgroup, int flag) {
69 return cgroup->flags & flag;
70 }
71
72 static const char* pakfire_cgroup_name(struct pakfire_cgroup* cgroup) {
73 if (pakfire_cgroup_is_root(cgroup))
74 return "(root)";
75
76 return cgroup->path;
77 }
78
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
126 const char* path = pakfire_dirname(cgroup->path);
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)
134 path = NULL;
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
143 return parent;
144 }
145
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);
149
150 // Close the file descriptor
151 if (cgroup->fd)
152 close(cgroup->fd);
153
154 pakfire_unref(cgroup->pakfire);
155 free(cgroup);
156 }
157
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
168 static int __pakfire_cgroup_create(struct pakfire_cgroup* cgroup) {
169 char path[PATH_MAX];
170 int r;
171
172 DEBUG(cgroup->pakfire, "Trying to create cgroup %s\n", pakfire_cgroup_name(cgroup));
173
174 // Compose the absolute path
175 r = pakfire_path_join(path, ROOT, cgroup->path);
176 if (r)
177 return 1;
178
179 // Try creating the directory
180 return pakfire_mkdir(path, 0755);
181 }
182
183 /*
184 Opens the cgroup and returns a file descriptor.
185
186 If the cgroup does not exist, it will try to create it.
187
188 This function returns a negative value on error.
189 */
190 static int __pakfire_cgroup_open(struct pakfire_cgroup* cgroup) {
191 int fd = -1;
192 int r;
193
194 // Open file descriptor of the cgroup root
195 int rootfd = pakfire_cgroup_open_root(cgroup);
196 if (rootfd < 0)
197 return -1;
198
199 // Return the rootfd for the root group
200 if (pakfire_cgroup_is_root(cgroup))
201 return rootfd;
202
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;
212
213 // Retry open after successful creation
214 goto RETRY;
215
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 }
223
224 ERROR:
225 if (rootfd > 0)
226 close(rootfd);
227
228 return fd;
229 }
230
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
260 static ssize_t pakfire_cgroup_read(struct pakfire_cgroup* cgroup, const char* path,
261 char* buffer, size_t length) {
262 ssize_t bytes_read = -1;
263
264 // Check if this cgroup has been destroyed already
265 if (!cgroup->fd) {
266 ERROR(cgroup->pakfire, "Trying to read from destroyed cgroup\n");
267 return -1;
268 }
269
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 }
277
278 // Read file content into buffer
279 bytes_read = read(fd, buffer, length);
280 if (bytes_read < 0) {
281 DEBUG(cgroup->pakfire, "Could not read from %s/%s: %m\n",
282 pakfire_cgroup_name(cgroup), path);
283 goto ERROR;
284 }
285
286 // Terminate the buffer
287 if ((size_t)bytes_read < length)
288 buffer[bytes_read] = '\0';
289
290 ERROR:
291 if (fd > 0)
292 close(fd);
293
294 return bytes_read;
295 }
296
297 static int pakfire_cgroup_write(struct pakfire_cgroup* cgroup,
298 const char* path, const char* format, ...) {
299 va_list args;
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 }
308
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);
314 return 1;
315 }
316
317 // Write buffer
318 va_start(args, format);
319 ssize_t bytes_written = vdprintf(fd, format, args);
320 va_end(args);
321
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 }
328
329 // Close fd
330 close(fd);
331
332 return r;
333 }
334
335 static int pakfire_cgroup_read_controllers(
336 struct pakfire_cgroup* cgroup, const char* name) {
337 char buffer[BUFFER_SIZE];
338 char* p = NULL;
339
340 // Discovered controllers
341 int controllers = 0;
342
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);
364 }
365
366 // Return discovered controllers
367 return controllers;
368 }
369
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;
397 }
398
399 // Filter out anything that is already enabled
400 controllers = (controllers & ~enabled_controllers);
401
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);
464 }
465
466 /*
467 Entry function to open a new cgroup.
468
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,
472 struct pakfire* pakfire, const char* path, int flags) {
473 int r = 1;
474
475 // Allocate the cgroup struct
476 struct pakfire_cgroup* c = calloc(1, sizeof(*c));
477 if (!c)
478 return 1;
479
480 DEBUG(pakfire, "Allocated cgroup %s at %p\n", path, c);
481
482 // Keep a reference to pakfire
483 c->pakfire = pakfire_ref(pakfire);
484
485 // Initialize reference counter
486 c->nrefs = 1;
487
488 // Copy path
489 pakfire_string_set(c->path, path);
490
491 // Copy flags
492 c->flags = flags;
493
494 // Open a file descriptor
495 c->fd = __pakfire_cgroup_open(c);
496 if (c->fd < 0)
497 goto ERROR;
498
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
506 *cgroup = c;
507 return 0;
508
509 ERROR:
510 pakfire_cgroup_free(c);
511 return r;
512 }
513
514 struct pakfire_cgroup* pakfire_cgroup_ref(struct pakfire_cgroup* cgroup) {
515 ++cgroup->nrefs;
516
517 return cgroup;
518 }
519
520 struct pakfire_cgroup* pakfire_cgroup_unref(struct pakfire_cgroup* cgroup) {
521 if (--cgroup->nrefs > 0)
522 return cgroup;
523
524 pakfire_cgroup_free(cgroup);
525 return NULL;
526 }
527
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
534 // Check input
535 if (!name) {
536 errno = EINVAL;
537 return 1;
538 }
539
540 // Join paths
541 r = pakfire_path_join(path, cgroup->path, name);
542 if (r)
543 return 1;
544
545 // Open the child group
546 return pakfire_cgroup_open(child, cgroup->pakfire, path, flags);
547 }
548
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 if (line)
584 free(line);
585 if (f)
586 fclose(f);
587
588 return r;
589 }
590
591 static int send_sigkill(struct pakfire_cgroup* cgroup, const pid_t pid, void* data) {
592 DEBUG(cgroup->pakfire, "Sending signal SIGKILL to PID %d\n", pid);
593
594 int r = kill(pid, SIGKILL);
595 if (r < 0 && errno != ESRCH) {
596 ERROR(cgroup->pakfire, "Could not send signal SIGKILL to PID %d: %m\n", pid);
597 return r;
598 }
599
600 return r;
601 }
602
603 /*
604 Immediately kills all processes in this cgroup
605 */
606 static int pakfire_cgroup_killall(struct pakfire_cgroup* cgroup) {
607 DEBUG(cgroup->pakfire, "%s: Killing all processes\n", pakfire_cgroup_name(cgroup));
608
609 // Do we have support for cgroup.kill?
610 int r = pakfire_cgroup_access(cgroup, "cgroup.kill", F_OK, 0);
611
612 // Fall back to the legacy version
613 if (r && errno == ENOENT) {
614 return pakfire_cgroup_procs_callback(cgroup, send_sigkill, NULL);
615 }
616
617 return pakfire_cgroup_write(cgroup, "cgroup.kill", "1");
618 }
619
620 /*
621 Immediately destroys this cgroup
622 */
623 int pakfire_cgroup_destroy(struct pakfire_cgroup* cgroup) {
624 int r;
625
626 // Cannot call this for the root group
627 if (pakfire_cgroup_is_root(cgroup)) {
628 errno = EPERM;
629 return 1;
630 }
631
632 DEBUG(cgroup->pakfire, "Destroying cgroup %s\n", pakfire_cgroup_name(cgroup));
633
634 // Kill everything in this group
635 r = pakfire_cgroup_killall(cgroup);
636 if (r)
637 return r;
638
639 // Close the file descriptor
640 if (cgroup->fd) {
641 close(cgroup->fd);
642 cgroup->fd = 0;
643 }
644
645 // Open the root directory
646 int fd = pakfire_cgroup_open_root(cgroup);
647 if (fd < 0)
648 return 1;
649
650 // Delete the directory
651 r = unlinkat(fd, cgroup->path, AT_REMOVEDIR);
652 if (r)
653 ERROR(cgroup->pakfire, "Could not destroy cgroup: %m\n");
654
655 // Close fd
656 close(fd);
657
658 return r;
659 }
660
661 int pakfire_cgroup_fd(struct pakfire_cgroup* cgroup) {
662 return cgroup->fd;
663 }
664
665 // Memory
666
667 int pakfire_cgroup_set_guaranteed_memory(struct pakfire_cgroup* cgroup, size_t mem) {
668 int r;
669
670 // Enable memory controller
671 r = pakfire_cgroup_enable_controllers(cgroup, PAKFIRE_CGROUP_CONTROLLER_MEMORY);
672 if (r)
673 return r;
674
675 DEBUG(cgroup->pakfire, "%s: Setting guaranteed memory to %zu byte(s)\n",
676 pakfire_cgroup_name(cgroup), mem);
677
678 // Set value
679 r = pakfire_cgroup_write(cgroup, "memory.min", "%zu\n", mem);
680 if (r)
681 ERROR(cgroup->pakfire, "%s: Could not set guaranteed memory: %m\n",
682 pakfire_cgroup_name(cgroup));
683
684 return r;
685 }
686
687 int pakfire_cgroup_set_memory_limit(struct pakfire_cgroup* cgroup, size_t mem) {
688 int r;
689
690 // Enable memory controller
691 r = pakfire_cgroup_enable_controllers(cgroup, PAKFIRE_CGROUP_CONTROLLER_MEMORY);
692 if (r)
693 return r;
694
695 DEBUG(cgroup->pakfire, "%s: Setting memory limit to %zu byte(s)\n",
696 pakfire_cgroup_name(cgroup), mem);
697
698 // Set value
699 r = pakfire_cgroup_write(cgroup, "memory.max", "%zu\n", mem);
700 if (r)
701 ERROR(cgroup->pakfire, "%s: Could not set memory limit: %m\n",
702 pakfire_cgroup_name(cgroup));
703
704 return r;
705 }
706
707 // PIDs
708
709 int pakfire_cgroup_set_pid_limit(struct pakfire_cgroup* cgroup, size_t limit) {
710 int r;
711
712 // Enable PID controller
713 r = pakfire_cgroup_enable_controllers(cgroup, PAKFIRE_CGROUP_CONTROLLER_PIDS);
714 if (r)
715 return r;
716
717 DEBUG(cgroup->pakfire, "%s: Setting PID limit to %zu\n",
718 pakfire_cgroup_name(cgroup), limit);
719
720 // Set value
721 r = pakfire_cgroup_write(cgroup, "pids.max", "%zu\n", limit);
722 if (r)
723 ERROR(cgroup->pakfire, "%s: Could not set PID limit: %m\n",
724 pakfire_cgroup_name(cgroup));
725
726 return r;
727 }
728
729 // Stats
730
731 static int __pakfire_cgroup_read_stats_line(struct pakfire_cgroup* cgroup,
732 int (*callback)(struct pakfire_cgroup* cgroup, const char* key, unsigned long val, void* data),
733 void* data, char* line) {
734 char* p = NULL;
735
736 DEBUG(cgroup->pakfire, "Parsing line: %s\n", line);
737
738 char key[NAME_MAX];
739 unsigned long val = 0;
740
741 // Number of the field
742 int i = 0;
743
744 char* elem = strtok_r(line, " ", &p);
745 while (elem) {
746 switch (i++) {
747 // First field is the key
748 case 0:
749 // Copy the key
750 pakfire_string_set(key, elem);
751 break;
752
753 // The second field is some value
754 case 1:
755 val = strtoul(elem, NULL, 10);
756 break;
757
758 // Ignore the rest
759 default:
760 DEBUG(cgroup->pakfire, "%s: Unknown value in cgroup stats (%d): %s\n",
761 pakfire_cgroup_name(cgroup), i, elem);
762 break;
763 }
764
765 elem = strtok_r(NULL, " ", &p);
766 }
767
768 // Check if we parsed both fields
769 if (i < 2) {
770 ERROR(cgroup->pakfire, "Could not parse line\n");
771 return 1;
772 }
773
774 // Call the callback
775 return callback(cgroup, key, val, data);
776 }
777
778 static int __pakfire_cgroup_read_stats(struct pakfire_cgroup* cgroup, const char* path,
779 int (*callback)(struct pakfire_cgroup* cgroup, const char* key, unsigned long val, void* data),
780 void* data) {
781 char* p = NULL;
782 int r;
783
784 char buffer[BUFFER_SIZE];
785
786 DEBUG(cgroup->pakfire, "%s: Reading stats from %s\n", pakfire_cgroup_name(cgroup), path);
787
788 // Open the file
789 r = pakfire_cgroup_read(cgroup, path, buffer, sizeof(buffer));
790 if (r < 0)
791 goto ERROR;
792
793 char* line = strtok_r(buffer, "\n", &p);
794 while (line) {
795 // Parse the line
796 r = __pakfire_cgroup_read_stats_line(cgroup, callback, data, line);
797 if (r)
798 goto ERROR;
799
800 // Move to the next line
801 line = strtok_r(NULL, "\n", &p);
802 }
803
804 ERROR:
805 return r;
806 }
807
808 struct pakfire_cgroup_stat_entry {
809 const char* key;
810 unsigned long* val;
811 };
812
813 static int __pakfire_cgroup_parse_cpu_stats(struct pakfire_cgroup* cgroup,
814 const char* key, unsigned long val, void* data) {
815 struct pakfire_cgroup_cpu_stats* stats = (struct pakfire_cgroup_cpu_stats*)data;
816
817 const struct pakfire_cgroup_stat_entry entries[] = {
818 { "system_usec", &stats->system_usec },
819 { "usage_usec", &stats->usage_usec },
820 { "user_usec", &stats->user_usec },
821 { NULL, NULL },
822 };
823 // Find and store value
824 for (const struct pakfire_cgroup_stat_entry* entry = entries; entry->key; entry++) {
825 if (strcmp(entry->key, key) == 0) {
826 *entry->val = val;
827 return 0;
828 }
829 }
830
831 DEBUG(cgroup->pakfire, "Unknown key for CPU stats: %s = %ld\n", key, val);
832
833 return 0;
834 }
835
836 static int __pakfire_cgroup_parse_memory_stats(struct pakfire_cgroup* cgroup,
837 const char* key, unsigned long val, void* data) {
838 struct pakfire_cgroup_memory_stats* stats = (struct pakfire_cgroup_memory_stats*)data;
839
840 const struct pakfire_cgroup_stat_entry entries[] = {
841 { "anon", &stats->anon },
842 { "file", &stats->file },
843 { "kernel", &stats->kernel },
844 { "kernel_stack", &stats->kernel_stack },
845 { "pagetables", &stats->pagetables },
846 { "percpu", &stats->percpu },
847 { "sock", &stats->sock },
848 { "vmalloc", &stats->vmalloc },
849 { "shmem", &stats->shmem },
850 { "zswap", &stats->zswap },
851 { "zswapped", &stats->zswapped },
852 { "file_mapped", &stats->file_mapped },
853 { "file_dirty", &stats->file_dirty },
854 { "file_writeback", &stats->file_writeback },
855 { "swapcached", &stats->swapcached },
856 { "anon_thp", &stats->anon_thp },
857 { "file_thp", &stats->file_thp },
858 { "shmem_thp", &stats->shmem_thp },
859 { "inactive_anon", &stats->inactive_anon },
860 { "active_anon", &stats->active_anon },
861 { "inactive_file", &stats->inactive_file },
862 { "active_file", &stats->active_file },
863 { "unevictable", &stats->unevictable },
864 { "slab_reclaimable", &stats->slab_reclaimable },
865 { "slab_unreclaimable", &stats->slab_unreclaimable },
866 { "slab", &stats->slab },
867 { "workingset_refault_anon", &stats->workingset_refault_anon },
868 { "workingset_refault_file", &stats->workingset_refault_file },
869 { "workingset_activate_anon", &stats->workingset_activate_anon },
870 { "workingset_activate_file", &stats->workingset_activate_file },
871 { "workingset_restore_anon", &stats->workingset_restore_anon },
872 { "workingset_restore_file", &stats->workingset_restore_file },
873 { "workingset_nodereclaim", &stats->workingset_nodereclaim },
874 { "pgfault", &stats->pgfault },
875 { "pgmajfault", &stats->pgmajfault },
876 { "pgrefill", &stats->pgrefill },
877 { "pgscan", &stats->pgscan },
878 { "pgsteal", &stats->pgsteal },
879 { "pgactivate", &stats->pgactivate },
880 { "pgdeactivate", &stats->pgdeactivate },
881 { "pglazyfree", &stats->pglazyfree },
882 { "pglazyfreed", &stats->pglazyfreed },
883 { "thp_fault_alloc", &stats->thp_fault_alloc },
884 { "thp_collapse_alloc", &stats->thp_collapse_alloc },
885 { NULL, NULL },
886 };
887
888 // Find and store value
889 for (const struct pakfire_cgroup_stat_entry* entry = entries; entry->key; entry++) {
890 if (strcmp(entry->key, key) == 0) {
891 *entry->val = val;
892 return 0;
893 }
894 }
895
896 // Log any unknown keys
897 DEBUG(cgroup->pakfire, "Unknown key for memory stats: %s = %ld\n", key, val);
898
899 return 0;
900 }
901
902 int pakfire_cgroup_stat(struct pakfire_cgroup* cgroup,
903 struct pakfire_cgroup_stats* stats) {
904 int r;
905
906 // Check input
907 if (!stats) {
908 errno = EINVAL;
909 return 1;
910 }
911
912 // Read CPU stats
913 r = __pakfire_cgroup_read_stats(cgroup, "cpu.stat",
914 __pakfire_cgroup_parse_cpu_stats, &stats->cpu);
915 if (r)
916 goto ERROR;
917
918 // Read memory stats
919 r = __pakfire_cgroup_read_stats(cgroup, "memory.stat",
920 __pakfire_cgroup_parse_memory_stats, &stats->memory);
921 if (r)
922 goto ERROR;
923
924 ERROR:
925 if (r)
926 ERROR(cgroup->pakfire, "%s: Could not read cgroup stats: %m\n",
927 pakfire_cgroup_name(cgroup));
928
929 return r;
930 }
931
932 int pakfire_cgroup_stat_dump(struct pakfire_cgroup* cgroup,
933 const struct pakfire_cgroup_stats* stats) {
934 // Check input
935 if (!stats) {
936 errno = EINVAL;
937 return 1;
938 }
939
940 DEBUG(cgroup->pakfire, "%s: Total CPU time usage: %lu\n",
941 pakfire_cgroup_name(cgroup), stats->cpu.usage_usec);
942
943 return 0;
944 }