From: Michael Tremer Date: Sat, 27 Mar 2021 14:00:18 +0000 (+0000) Subject: libpakfire: Create a simple cgroup library X-Git-Tag: 0.9.28~1285^2~460 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9dd11ed00f85b16b649fb7de3bdd29d144cf0416;p=pakfire.git libpakfire: Create a simple cgroup library Signed-off-by: Michael Tremer --- diff --git a/.gitignore b/.gitignore index f5cb82478..db23c3ca6 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ /tests/.root /tests/libpakfire/arch /tests/libpakfire/archive +/tests/libpakfire/cgroup /tests/libpakfire/compress /tests/libpakfire/db /tests/libpakfire/downloader diff --git a/Makefile.am b/Makefile.am index 296c7962f..632a9ad2d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 index 000000000..1cd55e574 --- /dev/null +++ b/src/libpakfire/cgroup.c @@ -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 . # +# # +#############################################################################*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* + 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 index 000000000..32c0f551e --- /dev/null +++ b/src/libpakfire/include/pakfire/cgroup.h @@ -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 . # +# # +#############################################################################*/ + +#ifndef PAKFIRE_CGROUP_H +#define PAKFIRE_CGROUP_H + +#ifdef PAKFIRE_PRIVATE + +#include + +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 index 000000000..04a41661a --- /dev/null +++ b/tests/libpakfire/cgroup.c @@ -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 . # +# # +#############################################################################*/ + +#include + +#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(); +} diff --git a/tests/testsuite.h b/tests/testsuite.h index 02d3c0ea0..2dcb842ae 100644 --- a/tests/testsuite.h +++ b/tests/testsuite.h @@ -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) { \