]> git.ipfire.org Git - pakfire.git/commitdiff
libpakfire: Create a simple cgroup library
authorMichael Tremer <michael.tremer@ipfire.org>
Sat, 27 Mar 2021 14:00:18 +0000 (14:00 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Sat, 27 Mar 2021 14:00:18 +0000 (14:00 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
.gitignore
Makefile.am
src/libpakfire/cgroup.c [new file with mode: 0644]
src/libpakfire/include/pakfire/cgroup.h [new file with mode: 0644]
tests/libpakfire/cgroup.c [new file with mode: 0644]
tests/testsuite.h

index f5cb824785641fd4bd9e6139f14e766e122b7580..db23c3ca6e4aa437cea976b265f20e3f0762b77a 100644 (file)
@@ -12,6 +12,7 @@
 /tests/.root
 /tests/libpakfire/arch
 /tests/libpakfire/archive
+/tests/libpakfire/cgroup
 /tests/libpakfire/compress
 /tests/libpakfire/db
 /tests/libpakfire/downloader
index 296c7962f7ab6700499f0b53663fd300b4eb41ae..632a9ad2d54186badf47c4ff233ba68d3bdfb7f8 100644 (file)
@@ -256,6 +256,7 @@ lib_LTLIBRARIES += \
 libpakfire_la_SOURCES = \
        src/libpakfire/arch.c \
        src/libpakfire/archive.c \
+       src/libpakfire/cgroup.c \
        src/libpakfire/compress.c \
        src/libpakfire/db.c \
        src/libpakfire/dist.c \
@@ -289,6 +290,7 @@ libpakfire_la_SOURCES = \
 pkginclude_HEADERS += \
        src/libpakfire/include/pakfire/arch.h \
        src/libpakfire/include/pakfire/archive.h \
+       src/libpakfire/include/pakfire/cgroup.h \
        src/libpakfire/include/pakfire/compress.h \
        src/libpakfire/include/pakfire/constants.h \
        src/libpakfire/include/pakfire/db.h \
@@ -371,6 +373,7 @@ check_PROGRAMS += \
        tests/libpakfire/main \
        tests/libpakfire/arch \
        tests/libpakfire/archive \
+       tests/libpakfire/cgroup \
        tests/libpakfire/compress \
        tests/libpakfire/db \
        tests/libpakfire/downloader \
@@ -413,6 +416,23 @@ tests_libpakfire_archive_LDADD = \
        $(TESTSUITE_LDADD) \
        $(PAKFIRE_LIBS)
 
+dist_tests_libpakfire_cgroup_SOURCES = \
+       tests/libpakfire/cgroup.c \
+       src/libpakfire/cgroup.c \
+       src/libpakfire/util.c
+
+tests_libpakfire_cgroup_CPPFLAGS = \
+       $(TESTSUITE_CPPFLAGS) \
+       $(JSON_C_CFLAGS) \
+       -DPAKFIRE_PRIVATE
+
+tests_libpakfire_cgroup_LDADD = \
+       $(TESTSUITE_LDADD) \
+       $(PAKFIRE_LIBS) \
+       $(ARCHIVE_LIBS) \
+       $(JSON_C_LIBS) \
+       $(UUID_LIBS)
+
 dist_tests_libpakfire_compress_SOURCES = \
        tests/libpakfire/compress.c \
        src/libpakfire/compress.c
diff --git a/src/libpakfire/cgroup.c b/src/libpakfire/cgroup.c
new file mode 100644 (file)
index 0000000..1cd55e5
--- /dev/null
@@ -0,0 +1,276 @@
+/*#############################################################################
+#                                                                             #
+# 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;
+}
diff --git a/src/libpakfire/include/pakfire/cgroup.h b/src/libpakfire/include/pakfire/cgroup.h
new file mode 100644 (file)
index 0000000..32c0f55
--- /dev/null
@@ -0,0 +1,33 @@
+/*#############################################################################
+#                                                                             #
+# 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/>.       #
+#                                                                             #
+#############################################################################*/
+
+#ifndef PAKFIRE_CGROUP_H
+#define PAKFIRE_CGROUP_H
+
+#ifdef PAKFIRE_PRIVATE
+
+#include <pakfire/types.h>
+
+int pakfire_cgroup_create(Pakfire pakfire, const char* group);
+int pakfire_cgroup_destroy(Pakfire pakfire, const char* group);
+
+#endif
+
+#endif /* PAKFIRE_CGROUP_H */
diff --git a/tests/libpakfire/cgroup.c b/tests/libpakfire/cgroup.c
new file mode 100644 (file)
index 0000000..04a4166
--- /dev/null
@@ -0,0 +1,41 @@
+/*#############################################################################
+#                                                                             #
+# 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();
+}
index 02d3c0ea0c6909d81bb065627fb0595c6929c604..2dcb842ae7825f121e0ffe5d214e3a5b9fb71bc2 100644 (file)
@@ -63,6 +63,15 @@ int testsuite_run();
                } \
        } while (0)
 
+#define ASSERT_SUCCESS(expr) \
+       do { \
+               if ((expr)) { \
+                       LOG_ERROR("Failed assertion: " #expr " %s:%d %s\n", \
+                               __FILE__, __LINE__, __PRETTY_FUNCTION__); \
+                       return EXIT_FAILURE; \
+               } \
+       } while (0)
+
 #define ASSERT_STRING_EQUALS(value, string) \
        do { \
                if (!value) { \