--- /dev/null
+/*#############################################################################
+# #
+# Pakfire - The IPFire package management system #
+# Copyright (C) 2021 Pakfire development team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <errno.h>
+#include <linux/limits.h>
+#include <linux/magic.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/vfs.h>
+
+#include <pakfire/cgroup.h>
+#include <pakfire/logging.h>
+#include <pakfire/types.h>
+#include <pakfire/util.h>
+
+/*
+ We expect this to be a cgroupv2 file system
+*/
+#define CGROUP_ROOT "/sys/fs/cgroup"
+
+// Cache whether cgroups are supported on this system
+static int pakfire_cgroups_supported = -1;
+
+static const char* cgroup_controllers[] = {
+ "cpu",
+ "memory",
+ NULL,
+};
+
+/*
+ Returns the name of the parent group
+*/
+static char* pakfire_cgroup_parent_name(const char* group) {
+ if (!group || !*group)
+ return NULL;
+
+ // Find the last / in group
+ char* slash = strrchr(group, '/');
+
+ // If nothing was found, the next level up is "root"
+ if (!slash)
+ return strdup("");
+
+ size_t length = slash - group + 1;
+
+ // Allocate the parent group name
+ char* parent = malloc(length + 1);
+ if (!parent)
+ return NULL;
+
+ // Write everything up to "slash" which snprintf will replace by NUL
+ snprintf(parent, length, "%s", group);
+
+ return parent;
+}
+
+static int pakfire_cgroup_supported(Pakfire pakfire) {
+ if (pakfire_cgroups_supported < 0) {
+ struct statfs fs;
+
+ int r = statfs(CGROUP_ROOT, &fs);
+ if (r == 0) {
+ // Check if this is a cgroupv2 file system
+ if (fs.f_type == CGROUP2_SUPER_MAGIC)
+ pakfire_cgroups_supported = 1;
+ else {
+ ERROR(pakfire, "%s is not a cgroupv2 hierarchy\n", CGROUP_ROOT);
+ pakfire_cgroups_supported = 0;
+ }
+ } else if (r < 0) {
+ ERROR(pakfire, "Could not stat %s: %s\n", CGROUP_ROOT, strerror(errno));
+ pakfire_cgroups_supported = 0;
+ }
+ }
+
+ return pakfire_cgroups_supported;
+}
+
+static int pakfire_cgroup_make_path(Pakfire pakfire, char* path, size_t length,
+ const char* subgroup, const char* file) {
+ // Store up to where we have written and how much space is left
+ char* p = path;
+ size_t l = length;
+
+ // Write root
+ size_t bytes_written = snprintf(p, l, "%s", CGROUP_ROOT);
+ if (bytes_written >= l)
+ return -1;
+
+ p += bytes_written;
+ l -= bytes_written;
+
+ // Append subgroup
+ if (subgroup) {
+ bytes_written = snprintf(p, l, "/%s", subgroup);
+ if (bytes_written >= l)
+ return -1;
+
+ p += bytes_written;
+ l -= bytes_written;
+ }
+
+ // Append file
+ if (file) {
+ bytes_written = snprintf(p, l, "/%s", file);
+ if (bytes_written >= l)
+ return -1;
+
+ p += bytes_written;
+ l -= bytes_written;
+ }
+
+ // Return total bytes written
+ return length - l;
+}
+
+static FILE* pakfire_cgroup_fopen(Pakfire pakfire,
+ const char* group, const char* file, const char* mode) {
+ char path[PATH_MAX];
+
+ int r = pakfire_cgroup_make_path(pakfire, path, sizeof(path) - 1, group, file);
+ if (r < 0)
+ return NULL;
+
+ FILE* f = fopen(path, mode);
+ if (!f) {
+ ERROR(pakfire, "Could not open %s: %s\n", path, strerror(errno));
+ return NULL;
+ }
+
+ return f;
+}
+
+static int pakfire_cgroup_enable_controller(Pakfire pakfire,
+ const char* group, const char* controller) {
+ int r;
+
+ FILE* f = pakfire_cgroup_fopen(pakfire, group, "cgroup.subtree_control", "w");
+ if (!f)
+ return 1;
+
+RETRY:
+ // Enable controller
+ r = fprintf(f, "+%s", controller);
+
+ // fprintf might set errno when there was a problem, although the write itself was ok
+ if (errno) {
+ r = 1;
+
+ // The parent group does not seem to have this controller enabled
+ if (errno == ENOENT) {
+ char* parent = pakfire_cgroup_parent_name(group);
+ if (!parent)
+ goto ERROR;
+
+ // Try to enable this on the parent level
+ r = pakfire_cgroup_enable_controller(pakfire, parent, controller);
+ free(parent);
+
+ // If this failed, we fail
+ if (r)
+ goto ERROR;
+
+ // Otherwise we try again
+ goto RETRY;
+ }
+ }
+
+ // Success
+ r = 0;
+
+ERROR:
+ fclose(f);
+
+ return r;
+}
+
+static int pakfire_cgroup_enable_controllers(Pakfire pakfire,
+ const char* group, const char** controllers) {
+ int r;
+
+ // Enable all controllers
+ for (const char** controller = controllers; *controller; controller++) {
+ r = pakfire_cgroup_enable_controller(pakfire, group, *controller);
+ if (r)
+ return r;
+ }
+
+ return 0;
+}
+
+int pakfire_cgroup_create(Pakfire pakfire, const char* group) {
+ int supported = pakfire_cgroup_supported(pakfire);
+ if (!supported)
+ return 1;
+
+ // Ensure that parent groups exist
+ char* parent = pakfire_cgroup_parent_name(group);
+ if (parent) {
+ int r = pakfire_cgroup_create(pakfire, parent);
+ free(parent);
+ if (r)
+ return 1;
+ }
+
+ // Make path
+ char path[PATH_MAX];
+ int r = pakfire_cgroup_make_path(pakfire, path, sizeof(path) - 1, group, NULL);
+ if (r < 0)
+ return r;
+
+ // Create group
+ r = pakfire_mkdir(path, 0755);
+ if (r) {
+ switch (errno) {
+ // The group already exists
+ case EEXIST:
+ return 0;
+
+ default:
+ ERROR(pakfire, "Could not create cgroup %s: %s\n", group, strerror(errno));
+ return r;
+ }
+ }
+
+ DEBUG(pakfire, "Created cgroup %s\n", group);
+
+ // Enable default controllers
+ r = pakfire_cgroup_enable_controllers(pakfire, group, cgroup_controllers);
+ printf("r = %d\n", r);
+ if (r) {
+ pakfire_cgroup_destroy(pakfire, group);
+ return r;
+ }
+
+ return 0;
+}
+
+int pakfire_cgroup_destroy(Pakfire pakfire, const char* group) {
+ // Never attempt to destroy root
+ if (!*group)
+ return EINVAL;
+
+ char path[PATH_MAX];
+
+ int r = pakfire_cgroup_make_path(pakfire, path, sizeof(path) - 1, group, NULL);
+ if (r < 0)
+ return r;
+
+ // Remove the directory
+ r = rmdir(path);
+ if (r) {
+ ERROR(pakfire, "Could not destroy cgroup %s: %s\n", group, strerror(errno));
+ return r;
+ }
+
+ return 0;
+}
--- /dev/null
+/*#############################################################################
+# #
+# Pakfire - The IPFire package management system #
+# Copyright (C) 2021 Pakfire development team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <pakfire/cgroup.h>
+
+#include "../testsuite.h"
+
+static int test_create_and_destroy(const struct test* t) {
+ ASSERT_SUCCESS(
+ pakfire_cgroup_create(t->pakfire, "pakfire/test")
+ );
+
+ ASSERT_SUCCESS(
+ pakfire_cgroup_destroy(t->pakfire, "pakfire/test")
+ );
+
+ return EXIT_SUCCESS;
+}
+
+int main(int argc, char** argv) {
+ testsuite_add_test(test_create_and_destroy);
+
+ return testsuite_run();
+}