cgroup v1 preliminaries (#36622)
This commit is contained in:
@@ -4,7 +4,6 @@
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
# default to Debian testing
|
||||
DISTRO="${DISTRO:-debian}"
|
||||
RELEASE="${RELEASE:-bookworm}"
|
||||
SALSA_URL="${SALSA_URL:-https://salsa.debian.org/systemd-team/systemd.git}"
|
||||
@@ -24,9 +23,19 @@ create_container() {
|
||||
sudo lxc-create -n "$CONTAINER" -t download -- -d "$DISTRO" -r "$RELEASE" -a "$ARCH"
|
||||
|
||||
# unconfine the container, otherwise some tests fail
|
||||
echo 'lxc.apparmor.profile = unconfined' | sudo tee -a "/var/lib/lxc/$CONTAINER/config"
|
||||
#
|
||||
# disable automatic cgroup setup, instead let pid1 figure it out in mount_setup().
|
||||
# This is especially important to ensure we get unified cgroup hierarchy
|
||||
#
|
||||
# FIXME: remove cgroup workarounds once the host runs on unified hierarchy
|
||||
sudo tee "/var/lib/lxc/$CONTAINER/config.systemd_upstream" <<EOF
|
||||
lxc.apparmor.profile = unconfined
|
||||
lxc.mount.auto =
|
||||
lxc.mount.auto = proc:mixed sys:mixed
|
||||
lxc.init.cmd = /sbin/init systemd.unified_cgroup_hierarchy=1
|
||||
EOF
|
||||
|
||||
sudo lxc-start -n "$CONTAINER"
|
||||
sudo lxc-start -n "$CONTAINER" --define "lxc.include=/var/lib/lxc/$CONTAINER/config.systemd_upstream"
|
||||
|
||||
# enable source repositories so that apt-get build-dep works
|
||||
sudo lxc-attach -n "$CONTAINER" -- sh -ex <<EOF
|
||||
@@ -112,6 +121,7 @@ EOF
|
||||
../systemd_*.dsc \
|
||||
-o "$ARTIFACTS_DIR" \
|
||||
-- lxc -s "$CONTAINER" \
|
||||
--define "lxc.include=/var/lib/lxc/$CONTAINER/config.systemd_upstream" \
|
||||
|| [ $? -eq 2 ]
|
||||
;;
|
||||
*)
|
||||
|
||||
@@ -52,7 +52,8 @@ REQUIREMENTS:
|
||||
# FIXME: actually drop compat glue before v258
|
||||
|
||||
Linux kernel ≥ 5.6 for getrandom() GRND_INSECURE
|
||||
≥ 5.7 for CLONE_INTO_CGROUP, BPF links and the BPF LSM hook
|
||||
≥ 5.7 for CLONE_INTO_CGROUP, cgroup2fs memory_recursiveprot option,
|
||||
BPF links and the BPF LSM hook
|
||||
|
||||
⚠️ Kernel versions below 5.7 ("recommended baseline") have significant gaps
|
||||
in functionality and are not recommended for use with this version
|
||||
|
||||
@@ -263,16 +263,10 @@
|
||||
returns the user slice (as managed by the user's systemd instance)
|
||||
of a process.</para>
|
||||
|
||||
<para><function>sd_pid_get_cgroup()</function> returns the control
|
||||
group path of the specified process, relative to the root of the
|
||||
hierarchy. Returns the path without trailing slash, except for
|
||||
processes located in the root control group, where "/" is
|
||||
returned. To find the actual control group path in the file system,
|
||||
the returned path needs to be prefixed with
|
||||
<filename>/sys/fs/cgroup/</filename> (if the unified control group
|
||||
setup is used), or
|
||||
<filename>/sys/fs/cgroup/<replaceable>HIERARCHY</replaceable>/</filename>
|
||||
(if the legacy multi-hierarchy control group setup is used).</para>
|
||||
<para><function>sd_pid_get_cgroup()</function> returns the control group path of the specified process,
|
||||
relative to the root of the hierarchy. Returns the path without trailing slash, except for processes
|
||||
located in the root control group, where "/" is returned. To find the actual control group path
|
||||
in the file system, the returned path needs to be prefixed with <filename>/sys/fs/cgroup/</filename>.</para>
|
||||
|
||||
<para>If the <varname>pid</varname> parameter of any of these
|
||||
functions is passed as 0, the operation is executed for the
|
||||
|
||||
@@ -2316,20 +2316,6 @@ static const char* const cgroup_io_limit_type_table[_CGROUP_IO_LIMIT_TYPE_MAX] =
|
||||
|
||||
DEFINE_STRING_TABLE_LOOKUP(cgroup_io_limit_type, CGroupIOLimitType);
|
||||
|
||||
bool is_cgroup_fs(const struct statfs *s) {
|
||||
return is_fs_type(s, CGROUP_SUPER_MAGIC) ||
|
||||
is_fs_type(s, CGROUP2_SUPER_MAGIC);
|
||||
}
|
||||
|
||||
bool fd_is_cgroup_fs(int fd) {
|
||||
struct statfs s;
|
||||
|
||||
if (fstatfs(fd, &s) < 0)
|
||||
return -errno;
|
||||
|
||||
return is_cgroup_fs(&s);
|
||||
}
|
||||
|
||||
static const char *const cgroup_controller_table[_CGROUP_CONTROLLER_MAX] = {
|
||||
[CGROUP_CONTROLLER_CPU] = "cpu",
|
||||
[CGROUP_CONTROLLER_CPUACCT] = "cpuacct",
|
||||
|
||||
@@ -329,9 +329,6 @@ static inline int cg_unified(void) {
|
||||
const char* cgroup_controller_to_string(CGroupController c) _const_;
|
||||
CGroupController cgroup_controller_from_string(const char *s) _pure_;
|
||||
|
||||
bool is_cgroup_fs(const struct statfs *s);
|
||||
bool fd_is_cgroup_fs(int fd);
|
||||
|
||||
typedef enum ManagedOOMMode {
|
||||
MANAGED_OOM_AUTO,
|
||||
MANAGED_OOM_KILL,
|
||||
|
||||
+17
-8
@@ -3201,14 +3201,6 @@ int main(int argc, char *argv[]) {
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (!skip_setup) {
|
||||
r = mount_cgroup_legacy_controllers(loaded_policy);
|
||||
if (r < 0) {
|
||||
error_message = "Failed to mount cgroup v1 hierarchy";
|
||||
goto finish;
|
||||
}
|
||||
}
|
||||
|
||||
/* The efivarfs is now mounted, let's lock down the system token. */
|
||||
lock_down_efi_variables();
|
||||
} else {
|
||||
@@ -3310,6 +3302,23 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
log_execution_mode(&first_boot);
|
||||
|
||||
r = cg_has_legacy();
|
||||
if (r < 0) {
|
||||
error_message = "Failed to check cgroup hierarchy";
|
||||
goto finish;
|
||||
}
|
||||
if (r > 0) {
|
||||
r = log_full_errno(LOG_EMERG, SYNTHETIC_ERRNO(EPROTO),
|
||||
"Detected cgroup v1 hierarchy at /sys/fs/cgroup/, which is no longer supported by current version of systemd.\n"
|
||||
"Please instruct your initrd to mount cgroup v2 (unified) hierarchy,\n"
|
||||
"possibly by removing any stale kernel command line options, such as:\n"
|
||||
" systemd.legacy_systemd_cgroup_controller=1\n"
|
||||
" systemd.unified_cgroup_hierarchy=0");
|
||||
|
||||
error_message = "Detected unsupported legacy cgroup hierarchy, refusing execution";
|
||||
goto finish;
|
||||
}
|
||||
|
||||
r = initialize_runtime(skip_setup,
|
||||
first_boot,
|
||||
&saved_rlimit_nofile,
|
||||
|
||||
+3
-20
@@ -26,6 +26,7 @@
|
||||
#include "loopback-setup.h"
|
||||
#include "missing_syscall.h"
|
||||
#include "mkdir-label.h"
|
||||
#include "mount-setup.h"
|
||||
#include "mount-util.h"
|
||||
#include "mountpoint-util.h"
|
||||
#include "namespace-util.h"
|
||||
@@ -1373,34 +1374,16 @@ static int mount_private_sysfs(const MountEntry *m, const NamespaceParameters *p
|
||||
return mount_private_apivfs("sysfs", mount_entry_path(m), "/sys", /* opts = */ NULL, p->runtime_scope);
|
||||
}
|
||||
|
||||
static bool check_recursiveprot_supported(void) {
|
||||
int r;
|
||||
|
||||
/* memory_recursiveprot is only supported for kernels >= 5.7. Note mount_option_supported uses fsopen()
|
||||
* and fsconfig() which are supported for kernels >= 5.2. So if mount_option_supported() returns an
|
||||
* error, we can assume memory_recursiveprot is not supported. */
|
||||
r = mount_option_supported("cgroup2", "memory_recursiveprot", NULL);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to determine whether the 'memory_recursiveprot' mount option is supported, assuming not: %m");
|
||||
else if (r == 0)
|
||||
log_debug("This kernel version does not support 'memory_recursiveprot', not using mount option.");
|
||||
|
||||
return r > 0;
|
||||
}
|
||||
|
||||
static int mount_private_cgroup2fs(const MountEntry *m, const NamespaceParameters *p) {
|
||||
_cleanup_free_ char *opts = NULL;
|
||||
|
||||
assert(m);
|
||||
assert(p);
|
||||
|
||||
if (check_recursiveprot_supported()) {
|
||||
opts = strdup(strempty(mount_entry_options(m)));
|
||||
if (cgroupfs_recursiveprot_supported()) {
|
||||
opts = strextend_with_separator(NULL, ",", mount_entry_options(m) ?: POINTER_MAX, "memory_recursiveprot");
|
||||
if (!opts)
|
||||
return -ENOMEM;
|
||||
|
||||
if (!strextend_with_separator(&opts, ",", "memory_recursiveprot"))
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return mount_private_apivfs("cgroup2", mount_entry_path(m), "/sys/fs/cgroup", opts ?: mount_entry_options(m), p->runtime_scope);
|
||||
|
||||
@@ -52,9 +52,6 @@ char** taint_strv(void) {
|
||||
if (readlink_malloc("/var/run", &var_run) < 0 || !PATH_IN_SET(var_run, "../run", "/run"))
|
||||
stage[n++] = "var-run-bad";
|
||||
|
||||
if (cg_all_unified() == 0)
|
||||
stage[n++] = "cgroupsv1";
|
||||
|
||||
if (clock_is_localtime(NULL) > 0)
|
||||
stage[n++] = "local-hwclock";
|
||||
|
||||
|
||||
+27
-158
@@ -1,6 +1,5 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <threads.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "cgroup-setup.h"
|
||||
@@ -9,171 +8,15 @@
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "fs-util.h"
|
||||
#include "missing_magic.h"
|
||||
#include "mkdir.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
#include "proc-cmdline.h"
|
||||
#include "process-util.h"
|
||||
#include "recurse-dir.h"
|
||||
#include "stdio-util.h"
|
||||
#include "string-util.h"
|
||||
#include "user-util.h"
|
||||
#include "virt.h"
|
||||
|
||||
static int cg_any_controller_used_for_v1(void) {
|
||||
_cleanup_free_ char *buf = NULL;
|
||||
_cleanup_strv_free_ char **lines = NULL;
|
||||
int r;
|
||||
|
||||
r = read_full_virtual_file("/proc/cgroups", &buf, NULL);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Could not read /proc/cgroups, ignoring: %m");
|
||||
|
||||
r = strv_split_newlines_full(&lines, buf, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* The intention of this is to check if the fully unified cgroup tree setup is possible, meaning all
|
||||
* enabled kernel cgroup controllers are currently not in use by cgroup1. For reference:
|
||||
* https://systemd.io/CGROUP_DELEGATION/#three-different-tree-setups-
|
||||
*
|
||||
* Note that this is typically only useful to check inside a container where we don't know what
|
||||
* cgroup tree setup is in use by the host; if the host is using legacy or hybrid, we can't use
|
||||
* unified since some or all controllers would be missing. This is not the best way to detect this,
|
||||
* as whatever container manager created our container should have mounted /sys/fs/cgroup
|
||||
* appropriately, but in case that wasn't done, we try to detect if it's possible for us to use
|
||||
* unified cgroups. */
|
||||
STRV_FOREACH(line, lines) {
|
||||
_cleanup_free_ char *name = NULL, *hierarchy_id = NULL, *num = NULL, *enabled = NULL;
|
||||
|
||||
/* Skip header line */
|
||||
if (startswith(*line, "#"))
|
||||
continue;
|
||||
|
||||
const char *p = *line;
|
||||
r = extract_many_words(&p, NULL, 0, &name, &hierarchy_id, &num, &enabled);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Error parsing /proc/cgroups line, ignoring: %m");
|
||||
else if (r < 4) {
|
||||
log_debug("Invalid /proc/cgroups line, ignoring.");
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Ignore disabled controllers. */
|
||||
if (streq(enabled, "0"))
|
||||
continue;
|
||||
|
||||
/* Ignore controllers we don't care about. */
|
||||
if (cgroup_controller_from_string(name) < 0)
|
||||
continue;
|
||||
|
||||
/* Since the unified cgroup doesn't use multiple hierarchies, if any controller has a
|
||||
* non-zero hierarchy_id that means it's in use already in a legacy (or hybrid) cgroup v1
|
||||
* hierarchy, and can't be used in a unified cgroup. */
|
||||
if (!streq(hierarchy_id, "0")) {
|
||||
log_debug("Cgroup controller %s in use by legacy v1 hierarchy.", name);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool cg_is_unified_wanted(void) {
|
||||
static thread_local int wanted = -1;
|
||||
int r;
|
||||
|
||||
/* If we have a cached value, return that. */
|
||||
if (wanted >= 0)
|
||||
return wanted;
|
||||
|
||||
/* If the hierarchy is already mounted, then follow whatever was chosen for it. */
|
||||
r = cg_unified_cached(true);
|
||||
if (r >= 0)
|
||||
return (wanted = r >= CGROUP_UNIFIED_ALL);
|
||||
|
||||
/* If we have explicit configuration for v1 or v2, respect that. */
|
||||
if (cg_is_legacy_force_enabled())
|
||||
return (wanted = false);
|
||||
|
||||
bool b;
|
||||
r = proc_cmdline_get_bool("systemd.unified_cgroup_hierarchy", /* flags = */ 0, &b);
|
||||
if (r > 0 && b)
|
||||
return (wanted = true);
|
||||
|
||||
/* If we passed cgroup_no_v1=all with no other instructions, it seems highly unlikely that we want to
|
||||
* use hybrid or legacy hierarchy. */
|
||||
_cleanup_free_ char *c = NULL;
|
||||
r = proc_cmdline_get_key("cgroup_no_v1", 0, &c);
|
||||
if (r > 0 && streq_ptr(c, "all"))
|
||||
return (wanted = true);
|
||||
|
||||
/* If any controller is in use as v1, don't use unified. */
|
||||
if (cg_any_controller_used_for_v1() > 0)
|
||||
return (wanted = false);
|
||||
|
||||
return (wanted = true);
|
||||
}
|
||||
|
||||
bool cg_is_legacy_wanted(void) {
|
||||
/* Check if we have cgroup v2 already mounted. */
|
||||
if (cg_unified_cached(true) == CGROUP_UNIFIED_ALL)
|
||||
return false;
|
||||
|
||||
/* Otherwise, assume that at least partial legacy is wanted,
|
||||
* since cgroup v2 should already be mounted at this point. */
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cg_is_hybrid_wanted(void) {
|
||||
static thread_local int wanted = -1;
|
||||
int r;
|
||||
|
||||
/* If we have a cached value, return that. */
|
||||
if (wanted >= 0)
|
||||
return wanted;
|
||||
|
||||
/* If the hierarchy is already mounted, then follow whatever was chosen for it. */
|
||||
if (cg_unified_cached(true) == CGROUP_UNIFIED_ALL)
|
||||
return (wanted = false);
|
||||
|
||||
/* Otherwise, let's see what the kernel command line has to say. Since checking is expensive, cache
|
||||
* a non-error result.
|
||||
* The meaning of the kernel option is reversed wrt. to the return value of this function, hence the
|
||||
* negation. */
|
||||
bool b;
|
||||
r = proc_cmdline_get_bool("systemd.legacy_systemd_cgroup_controller", /* flags = */ 0, &b);
|
||||
if (r > 0)
|
||||
return (wanted = !b);
|
||||
|
||||
/* The default hierarchy is "unified". But if this is reached, it means that unified hierarchy was
|
||||
* not mounted, so return true too. */
|
||||
return (wanted = true);
|
||||
}
|
||||
|
||||
bool cg_is_legacy_enabled(void) {
|
||||
int r;
|
||||
bool b;
|
||||
|
||||
r = proc_cmdline_get_bool("systemd.unified_cgroup_hierarchy", /* flags = */ 0, &b);
|
||||
return r > 0 && !b;
|
||||
}
|
||||
|
||||
bool cg_is_legacy_force_enabled(void) {
|
||||
int r;
|
||||
bool b;
|
||||
|
||||
/* Require both systemd.unified_cgroup_hierarchy=0 and SYSTEMD_CGROUP_ENABLE_LEGACY_FORCE=1. */
|
||||
|
||||
if (!cg_is_legacy_enabled())
|
||||
return false;
|
||||
|
||||
r = proc_cmdline_get_bool("SYSTEMD_CGROUP_ENABLE_LEGACY_FORCE", /* flags = */ 0, &b);
|
||||
if (r <= 0 || !b)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int cg_weight_parse(const char *s, uint64_t *ret) {
|
||||
uint64_t u;
|
||||
@@ -1109,3 +952,29 @@ int cg_uninstall_release_agent(const char *controller) {
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cg_has_legacy(void) {
|
||||
struct statfs fs;
|
||||
|
||||
/* Checks if any legacy controller/hierarchy is mounted. */
|
||||
|
||||
if (statfs("/sys/fs/cgroup/", &fs) < 0) {
|
||||
if (errno == ENOENT) /* sysfs not mounted? */
|
||||
return false;
|
||||
|
||||
return log_error_errno(errno, "Failed to statfs /sys/fs/cgroup/: %m");
|
||||
}
|
||||
|
||||
if (is_fs_type(&fs, CGROUP2_SUPER_MAGIC) ||
|
||||
is_fs_type(&fs, SYSFS_MAGIC)) /* not mounted yet */
|
||||
return false;
|
||||
|
||||
if (is_fs_type(&fs, TMPFS_MAGIC)) {
|
||||
log_info("Found tmpfs on /sys/fs/cgroup/, assuming legacy hierarchy.");
|
||||
return true;
|
||||
}
|
||||
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOMEDIUM),
|
||||
"Unknown filesystem type %llx mounted on /sys/fs/cgroup/.",
|
||||
(unsigned long long) fs.f_type);
|
||||
}
|
||||
|
||||
@@ -7,12 +7,6 @@
|
||||
|
||||
#include "cgroup-util.h"
|
||||
|
||||
bool cg_is_unified_wanted(void);
|
||||
bool cg_is_legacy_wanted(void);
|
||||
bool cg_is_hybrid_wanted(void);
|
||||
bool cg_is_legacy_enabled(void);
|
||||
bool cg_is_legacy_force_enabled(void);
|
||||
|
||||
int cg_weight_parse(const char *s, uint64_t *ret);
|
||||
int cg_cpu_weight_parse(const char *s, uint64_t *ret);
|
||||
int cg_cpu_shares_parse(const char *s, uint64_t *ret);
|
||||
@@ -46,3 +40,5 @@ int cg_trim_v1_controllers(CGroupMask supported, CGroupMask mask, const char *pa
|
||||
|
||||
int cg_install_release_agent(const char *controller, const char *agent);
|
||||
int cg_uninstall_release_agent(const char *controller);
|
||||
|
||||
int cg_has_legacy(void);
|
||||
|
||||
+17
-210
@@ -52,27 +52,16 @@ typedef struct MountPoint {
|
||||
MountMode mode;
|
||||
} MountPoint;
|
||||
|
||||
/* The first three entries we might need before SELinux is up. The
|
||||
* fourth (securityfs) is needed by IMA to load a custom policy. The
|
||||
* other ones we can delay until SELinux and IMA are loaded. When
|
||||
* SMACK is enabled we need smackfs, too, so it's a fifth one. */
|
||||
#if ENABLE_SMACK
|
||||
#define N_EARLY_MOUNT 5
|
||||
#else
|
||||
#define N_EARLY_MOUNT 4
|
||||
#endif
|
||||
|
||||
static bool check_recursiveprot_supported(void) {
|
||||
bool cgroupfs_recursiveprot_supported(void) {
|
||||
int r;
|
||||
|
||||
if (!cg_is_unified_wanted())
|
||||
return false;
|
||||
/* Added in kernel 5.7 */
|
||||
|
||||
r = mount_option_supported("cgroup2", "memory_recursiveprot", NULL);
|
||||
r = mount_option_supported("cgroup2", "memory_recursiveprot", /* value = */ NULL);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to determine whether the 'memory_recursiveprot' mount option is supported, assuming not: %m");
|
||||
log_debug_errno(r, "Failed to determine whether cgroupfs supports 'memory_recursiveprot' mount option, assuming not: %m");
|
||||
else if (r == 0)
|
||||
log_debug("This kernel version does not support 'memory_recursiveprot', not using mount option.");
|
||||
log_debug("'memory_recursiveprot' not supported by cgroupfs, not using mount option.");
|
||||
|
||||
return r > 0;
|
||||
}
|
||||
@@ -103,11 +92,9 @@ static const MountPoint mount_table[] = {
|
||||
{ "tmpfs", "/run", "tmpfs", "mode=0755" TMPFS_LIMITS_RUN, MS_NOSUID|MS_NODEV|MS_STRICTATIME,
|
||||
NULL, MNT_FATAL|MNT_IN_CONTAINER },
|
||||
{ "cgroup2", "/sys/fs/cgroup", "cgroup2", "nsdelegate,memory_recursiveprot", MS_NOSUID|MS_NOEXEC|MS_NODEV,
|
||||
check_recursiveprot_supported, MNT_IN_CONTAINER|MNT_CHECK_WRITABLE },
|
||||
cgroupfs_recursiveprot_supported, MNT_FATAL|MNT_IN_CONTAINER|MNT_CHECK_WRITABLE },
|
||||
{ "cgroup2", "/sys/fs/cgroup", "cgroup2", "nsdelegate", MS_NOSUID|MS_NOEXEC|MS_NODEV,
|
||||
cg_is_unified_wanted, MNT_IN_CONTAINER|MNT_CHECK_WRITABLE },
|
||||
{ "cgroup2", "/sys/fs/cgroup", "cgroup2", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV,
|
||||
cg_is_unified_wanted, MNT_IN_CONTAINER|MNT_CHECK_WRITABLE },
|
||||
NULL, MNT_FATAL|MNT_IN_CONTAINER|MNT_CHECK_WRITABLE },
|
||||
#if ENABLE_PSTORE
|
||||
{ "pstore", "/sys/fs/pstore", "pstore", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV,
|
||||
NULL, MNT_NONE },
|
||||
@@ -120,6 +107,16 @@ static const MountPoint mount_table[] = {
|
||||
NULL, MNT_NONE, },
|
||||
};
|
||||
|
||||
/* The first three entries we might need before SELinux is up. The
|
||||
* fourth (securityfs) is needed by IMA to load a custom policy. The
|
||||
* other ones we can delay until SELinux and IMA are loaded. When
|
||||
* SMACK is enabled we need smackfs, too, so it's a fifth one. */
|
||||
#if ENABLE_SMACK
|
||||
#define N_EARLY_MOUNT 5
|
||||
#else
|
||||
#define N_EARLY_MOUNT 4
|
||||
#endif
|
||||
|
||||
assert_cc(N_EARLY_MOUNT <= ELEMENTSOF(mount_table));
|
||||
|
||||
bool mount_point_is_api(const char *path) {
|
||||
@@ -249,56 +246,6 @@ int mount_setup_early(void) {
|
||||
return mount_points_setup(N_EARLY_MOUNT, /* loaded_policy= */ false);
|
||||
}
|
||||
|
||||
static const char *join_with(const char *controller) {
|
||||
|
||||
static const char* const pairs[] = {
|
||||
"cpu", "cpuacct",
|
||||
"net_cls", "net_prio",
|
||||
NULL
|
||||
};
|
||||
|
||||
assert(controller);
|
||||
|
||||
/* This will lookup which controller to mount another controller with. Input is a controller name, and output
|
||||
* is the other controller name. The function works both ways: you can input one and get the other, and input
|
||||
* the other to get the one. */
|
||||
|
||||
STRV_FOREACH_PAIR(x, y, pairs) {
|
||||
if (streq(controller, *x))
|
||||
return *y;
|
||||
if (streq(controller, *y))
|
||||
return *x;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int symlink_controller(const char *target, const char *alias) {
|
||||
const char *a;
|
||||
int r;
|
||||
|
||||
assert(target);
|
||||
assert(alias);
|
||||
|
||||
a = strjoina("/sys/fs/cgroup/", alias);
|
||||
|
||||
r = symlink_idempotent(target, a, false);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create symlink %s: %m", a);
|
||||
|
||||
#if HAVE_SMACK_RUN_LABEL
|
||||
const char *p;
|
||||
|
||||
p = strjoina("/sys/fs/cgroup/", target);
|
||||
|
||||
r = mac_smack_copy(a, p);
|
||||
if (r < 0 && !ERRNO_IS_NOT_SUPPORTED(r))
|
||||
return log_error_errno(r, "Failed to copy smack label from %s to %s: %m", p, a);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if HAVE_SELINUX || ENABLE_SMACK
|
||||
static int relabel_cb(
|
||||
RecurseDirEvent event,
|
||||
@@ -486,143 +433,3 @@ int mount_setup(bool loaded_policy, bool leave_propagation) {
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const MountPoint cgroupv1_mount_table[] = {
|
||||
{ "tmpfs", "/sys/fs/cgroup", "tmpfs", "mode=0755" TMPFS_LIMITS_SYS_FS_CGROUP, MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME,
|
||||
cg_is_legacy_wanted, MNT_FATAL|MNT_IN_CONTAINER },
|
||||
{ "cgroup2", "/sys/fs/cgroup/unified", "cgroup2", "nsdelegate", MS_NOSUID|MS_NOEXEC|MS_NODEV,
|
||||
cg_is_hybrid_wanted, MNT_IN_CONTAINER|MNT_CHECK_WRITABLE },
|
||||
{ "cgroup2", "/sys/fs/cgroup/unified", "cgroup2", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV,
|
||||
cg_is_hybrid_wanted, MNT_IN_CONTAINER|MNT_CHECK_WRITABLE },
|
||||
{ "cgroup", "/sys/fs/cgroup/systemd", "cgroup", "none,name=systemd,xattr", MS_NOSUID|MS_NOEXEC|MS_NODEV,
|
||||
cg_is_legacy_wanted, MNT_IN_CONTAINER },
|
||||
{ "cgroup", "/sys/fs/cgroup/systemd", "cgroup", "none,name=systemd", MS_NOSUID|MS_NOEXEC|MS_NODEV,
|
||||
cg_is_legacy_wanted, MNT_FATAL|MNT_IN_CONTAINER },
|
||||
};
|
||||
|
||||
static void relabel_cgroup_legacy_hierarchy(void) {
|
||||
#if HAVE_SELINUX || ENABLE_SMACK
|
||||
struct statfs st;
|
||||
|
||||
assert(cg_is_legacy_wanted());
|
||||
|
||||
/* Temporarily remount the root cgroup filesystem to give it a proper label. Do this
|
||||
only when the filesystem has been already populated by a previous instance of systemd
|
||||
running from initrd. Otherwise don't remount anything and leave the filesystem read-write
|
||||
for the cgroup filesystems to be mounted inside. */
|
||||
if (statfs("/sys/fs/cgroup", &st) < 0)
|
||||
return (void) log_error_errno(errno, "Failed to determine mount flags for /sys/fs/cgroup/: %m");
|
||||
|
||||
if (st.f_flags & ST_RDONLY)
|
||||
(void) mount_nofollow(NULL, "/sys/fs/cgroup", NULL, MS_REMOUNT, NULL);
|
||||
|
||||
(void) label_fix("/sys/fs/cgroup", 0);
|
||||
(void) relabel_tree("/sys/fs/cgroup");
|
||||
|
||||
if (st.f_flags & ST_RDONLY)
|
||||
(void) mount_nofollow(NULL, "/sys/fs/cgroup", NULL, MS_REMOUNT|MS_RDONLY, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
int mount_cgroup_legacy_controllers(bool loaded_policy) {
|
||||
_cleanup_set_free_ Set *controllers = NULL;
|
||||
int r;
|
||||
|
||||
/* Before we actually start deleting cgroup v1 code, make it harder to boot in cgroupv1 mode first.
|
||||
* See also #30852. */
|
||||
|
||||
if (detect_container() <= 0) { /* If in container, we have to follow host's cgroup hierarchy. Only
|
||||
* do the deprecation checks below if we're not in a container. */
|
||||
if (cg_is_legacy_force_enabled())
|
||||
log_warning("Legacy support for cgroup v1 enabled via SYSTEMD_CGROUP_ENABLE_LEGACY_FORCE=1.");
|
||||
else if (cg_is_legacy_enabled()) {
|
||||
log_full(LOG_CRIT,
|
||||
"Legacy cgroup v1 configured. This will stop being supported soon.\n"
|
||||
"Will proceed with cgroup v2 after 30 s.\n"
|
||||
"Set systemd.unified_cgroup_hierarchy=1 to switch to cgroup v2 "
|
||||
"or set SYSTEMD_CGROUP_ENABLE_LEGACY_FORCE=1 to reenable v1 temporarily.");
|
||||
(void) usleep_safe(30 * USEC_PER_SEC);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cg_is_legacy_wanted())
|
||||
return 0;
|
||||
|
||||
FOREACH_ELEMENT(mp, cgroupv1_mount_table) {
|
||||
r = mount_one(mp, loaded_policy);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (loaded_policy)
|
||||
relabel_cgroup_legacy_hierarchy();
|
||||
|
||||
/* Mount all available cgroup controllers that are built into the kernel. */
|
||||
r = cg_kernel_controllers(&controllers);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to enumerate cgroup controllers: %m");
|
||||
|
||||
for (;;) {
|
||||
_cleanup_free_ char *options = NULL, *controller = NULL, *where = NULL;
|
||||
const char *other_controller;
|
||||
MountPoint p = {
|
||||
.what = "cgroup",
|
||||
.type = "cgroup",
|
||||
.flags = MS_NOSUID|MS_NOEXEC|MS_NODEV,
|
||||
.mode = MNT_IN_CONTAINER,
|
||||
};
|
||||
|
||||
controller = set_steal_first(controllers);
|
||||
if (!controller)
|
||||
break;
|
||||
|
||||
/* Check if we shall mount this together with another controller */
|
||||
other_controller = join_with(controller);
|
||||
if (other_controller) {
|
||||
_cleanup_free_ char *c = NULL;
|
||||
|
||||
/* Check if the other controller is actually available in the kernel too */
|
||||
c = set_remove(controllers, other_controller);
|
||||
if (c) {
|
||||
|
||||
/* Join the two controllers into one string, and maintain a stable ordering */
|
||||
if (strcmp(controller, other_controller) < 0)
|
||||
options = strjoin(controller, ",", other_controller);
|
||||
else
|
||||
options = strjoin(other_controller, ",", controller);
|
||||
if (!options)
|
||||
return log_oom();
|
||||
}
|
||||
}
|
||||
|
||||
/* The simple case, where there's only one controller to mount together */
|
||||
if (!options)
|
||||
options = TAKE_PTR(controller);
|
||||
|
||||
where = path_join("/sys/fs/cgroup", options);
|
||||
if (!where)
|
||||
return log_oom();
|
||||
|
||||
p.where = where;
|
||||
p.options = options;
|
||||
|
||||
r = mount_one(&p, true);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Create symlinks from the individual controller names, in case we have a joined mount */
|
||||
if (controller)
|
||||
(void) symlink_controller(options, controller);
|
||||
if (other_controller)
|
||||
(void) symlink_controller(options, other_controller);
|
||||
}
|
||||
|
||||
/* Now that we mounted everything, let's make the tmpfs the cgroup file systems are mounted into read-only. */
|
||||
(void) mount_nofollow("tmpfs", "/sys/fs/cgroup", "tmpfs",
|
||||
MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME|MS_RDONLY,
|
||||
"mode=0755" TMPFS_LIMITS_SYS_FS_CGROUP);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -9,4 +9,4 @@ bool mount_point_ignore(const char *path);
|
||||
int mount_setup_early(void);
|
||||
int mount_setup(bool loaded_policy, bool leave_propagation);
|
||||
|
||||
int mount_cgroup_legacy_controllers(bool loaded_policy);
|
||||
bool cgroupfs_recursiveprot_supported(void);
|
||||
|
||||
+2
-2
@@ -8,12 +8,12 @@
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "btrfs-util.h"
|
||||
#include "cgroup-util.h"
|
||||
#include "dirent-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "log.h"
|
||||
#include "macro.h"
|
||||
#include "missing_magic.h"
|
||||
#include "mountpoint-util.h"
|
||||
#include "path-util.h"
|
||||
#include "rm-rf.h"
|
||||
@@ -24,7 +24,7 @@
|
||||
* after all: we can create arbitrary directory hierarchies in it, and hence can also use rm_rf() on it
|
||||
* to remove those again. */
|
||||
static bool is_physical_fs(const struct statfs *sfs) {
|
||||
return !is_temporary_fs(sfs) && !is_cgroup_fs(sfs);
|
||||
return !is_temporary_fs(sfs) && !is_fs_type(sfs, CGROUP2_SUPER_MAGIC);
|
||||
}
|
||||
|
||||
static int patch_dirfd_mode(
|
||||
|
||||
@@ -55,7 +55,6 @@ simple_tests += files(
|
||||
'test-build-path.c',
|
||||
'test-bus-util.c',
|
||||
'test-calendarspec.c',
|
||||
'test-cgroup-setup.c',
|
||||
'test-cgroup-util.c',
|
||||
'test-cgroup.c',
|
||||
'test-chase.c',
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "cgroup-setup.h"
|
||||
#include "errno-util.h"
|
||||
#include "log.h"
|
||||
#include "proc-cmdline.h"
|
||||
#include "string-util.h"
|
||||
#include "tests.h"
|
||||
|
||||
static void test_is_wanted_print_one(bool header) {
|
||||
_cleanup_free_ char *cmdline = NULL;
|
||||
|
||||
log_info("-- %s --", __func__);
|
||||
ASSERT_OK(proc_cmdline(&cmdline));
|
||||
log_info("cmdline: %s", cmdline);
|
||||
if (header)
|
||||
(void) system("findmnt -n /sys/fs/cgroup");
|
||||
|
||||
log_info("is_unified_wanted() → %s", yes_no(cg_is_unified_wanted()));
|
||||
log_info("is_hybrid_wanted() → %s", yes_no(cg_is_hybrid_wanted()));
|
||||
log_info("is_legacy_wanted() → %s", yes_no(cg_is_legacy_wanted()));
|
||||
log_info(" ");
|
||||
}
|
||||
|
||||
TEST(is_wanted_print) {
|
||||
test_is_wanted_print_one(true);
|
||||
test_is_wanted_print_one(false); /* run twice to test caching */
|
||||
}
|
||||
|
||||
TEST(is_wanted) {
|
||||
ASSERT_OK_ERRNO(setenv("SYSTEMD_PROC_CMDLINE",
|
||||
"systemd.unified_cgroup_hierarchy", 1));
|
||||
test_is_wanted_print_one(false);
|
||||
|
||||
ASSERT_OK_ERRNO(setenv("SYSTEMD_PROC_CMDLINE",
|
||||
"systemd.unified_cgroup_hierarchy=0", 1));
|
||||
test_is_wanted_print_one(false);
|
||||
|
||||
ASSERT_OK_ERRNO(setenv("SYSTEMD_PROC_CMDLINE",
|
||||
"systemd.unified_cgroup_hierarchy=0 "
|
||||
"systemd.legacy_systemd_cgroup_controller", 1));
|
||||
test_is_wanted_print_one(false);
|
||||
|
||||
ASSERT_OK_ERRNO(setenv("SYSTEMD_PROC_CMDLINE",
|
||||
"systemd.unified_cgroup_hierarchy=0 "
|
||||
"systemd.legacy_systemd_cgroup_controller=0", 1));
|
||||
test_is_wanted_print_one(false);
|
||||
|
||||
/* cgroup_no_v1=all implies unified cgroup hierarchy, unless otherwise
|
||||
* explicitly specified. */
|
||||
ASSERT_OK_ERRNO(setenv("SYSTEMD_PROC_CMDLINE",
|
||||
"cgroup_no_v1=all", 1));
|
||||
test_is_wanted_print_one(false);
|
||||
|
||||
ASSERT_OK_ERRNO(setenv("SYSTEMD_PROC_CMDLINE",
|
||||
"cgroup_no_v1=all "
|
||||
"systemd.unified_cgroup_hierarchy=0", 1));
|
||||
test_is_wanted_print_one(false);
|
||||
}
|
||||
|
||||
static int intro(void) {
|
||||
if (access("/proc/cmdline", R_OK) < 0 && ERRNO_IS_PRIVILEGE(errno))
|
||||
return log_tests_skipped("can't read /proc/cmdline");
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro);
|
||||
@@ -375,28 +375,6 @@ TEST(mask_supported, .sd_booted = true) {
|
||||
yes_no(m & CGROUP_CONTROLLER_TO_MASK(c)));
|
||||
}
|
||||
|
||||
TEST(is_cgroup_fs, .sd_booted = true) {
|
||||
struct statfs sfs;
|
||||
assert_se(statfs("/sys/fs/cgroup", &sfs) == 0);
|
||||
if (is_temporary_fs(&sfs))
|
||||
assert_se(statfs("/sys/fs/cgroup/systemd", &sfs) == 0);
|
||||
assert_se(is_cgroup_fs(&sfs));
|
||||
}
|
||||
|
||||
TEST(fd_is_cgroup_fs, .sd_booted = true) {
|
||||
int fd;
|
||||
|
||||
fd = open("/sys/fs/cgroup", O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
|
||||
assert_se(fd >= 0);
|
||||
if (fd_is_temporary_fs(fd)) {
|
||||
fd = safe_close(fd);
|
||||
fd = open("/sys/fs/cgroup/systemd", O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
|
||||
assert_se(fd >= 0);
|
||||
}
|
||||
assert_se(fd_is_cgroup_fs(fd));
|
||||
fd = safe_close(fd);
|
||||
}
|
||||
|
||||
TEST(cg_tests) {
|
||||
int all, hybrid, systemd, r;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "clock-util.h"
|
||||
#include "taint.h"
|
||||
#include "tests.h"
|
||||
|
||||
@@ -8,7 +9,7 @@ TEST(taint_string) {
|
||||
assert_se(a);
|
||||
log_debug("taint string: '%s'", a);
|
||||
|
||||
assert_se(!!strstr(a, "cgroupsv1") == (cg_all_unified() == 0));
|
||||
assert_se(!!strstr(a, "local-hwclock") == (clock_is_localtime(NULL) > 0));
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_DEBUG);
|
||||
|
||||
Reference in New Issue
Block a user