diff --git a/TODO b/TODO
index 674aa50df5..dbd5b10839 100644
--- a/TODO
+++ b/TODO
@@ -40,7 +40,7 @@ Features:
* consider splitting out all temporary file creation APIs (we have so many in
fileio.h and elsewhere!) into a new util file of its own.
-* set memory.oom.group in cgroupsv2 for all leaf cgroups
+* set memory.oom.group in cgroupsv2 for all leaf cgroups (kernel v4.19+)
* drop umask() calls and suchlike from our generators, pid1 should set things up correctly anyway
diff --git a/man/systemd-system.conf.xml b/man/systemd-system.conf.xml
index 5ce2c6fb96..ea17111bc5 100644
--- a/man/systemd-system.conf.xml
+++ b/man/systemd-system.conf.xml
@@ -106,29 +106,6 @@
systemd.exec5.
-
- JoinControllers=cpu,cpuacct net_cls,netprio
-
- Configures controllers that shall be mounted
- in a single hierarchy. By default, systemd will mount all
- controllers which are enabled in the kernel in individual
- hierarchies, with the exception of those listed in this
- setting. Takes a space-separated list of comma-separated
- controller names, in order to allow multiple joined
- hierarchies. Defaults to 'cpu,cpuacct'. Pass an empty string
- to ensure that systemd mounts all controllers in separate
- hierarchies.
-
- Note that this option is only applied once, at very
- early boot. If you use an initial RAM disk (initrd) that uses
- systemd, it might hence be necessary to rebuild the initrd if
- this option is changed, and make sure the new configuration
- file is included in it. Otherwise, the initrd might mount the
- controller hierarchies in a different configuration than
- intended, and the main system cannot remount them
- anymore.
-
-
RuntimeWatchdogSec=
ShutdownWatchdogSec=
diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c
index 70c91249d7..0450850832 100644
--- a/src/basic/cgroup-util.c
+++ b/src/basic/cgroup-util.c
@@ -2108,6 +2108,7 @@ done:
int cg_create_everywhere(CGroupMask supported, CGroupMask mask, const char *path) {
CGroupController c;
+ CGroupMask done;
bool created;
int r;
@@ -2132,20 +2133,28 @@ int cg_create_everywhere(CGroupMask supported, CGroupMask mask, const char *path
if (r > 0)
return created;
+ supported &= CGROUP_MASK_V1;
+ mask = CGROUP_MASK_EXTEND_JOINED(mask);
+ done = 0;
+
/* Otherwise, do the same in the other hierarchies */
for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c);
const char *n;
- if (!FLAGS_SET(CGROUP_MASK_V1, bit))
+ if (!FLAGS_SET(supported, bit))
+ continue;
+
+ if (FLAGS_SET(done, bit))
continue;
n = cgroup_controller_to_string(c);
-
if (FLAGS_SET(mask, bit))
(void) cg_create(n, path);
- else if (FLAGS_SET(supported, bit))
+ else
(void) cg_trim(n, path, true);
+
+ done |= CGROUP_MASK_EXTEND_JOINED(bit);
}
return created;
@@ -2153,6 +2162,7 @@ int cg_create_everywhere(CGroupMask supported, CGroupMask mask, const char *path
int cg_attach_everywhere(CGroupMask supported, const char *path, pid_t pid, cg_migrate_callback_t path_callback, void *userdata) {
CGroupController c;
+ CGroupMask done;
int r;
r = cg_attach(SYSTEMD_CGROUP_CONTROLLER, path, pid);
@@ -2165,23 +2175,26 @@ int cg_attach_everywhere(CGroupMask supported, const char *path, pid_t pid, cg_m
if (r > 0)
return 0;
+ supported &= CGROUP_MASK_V1;
+ done = 0;
+
for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c);
const char *p = NULL;
- if (!FLAGS_SET(CGROUP_MASK_V1, bit))
+ if (!FLAGS_SET(supported, bit))
continue;
- if (!FLAGS_SET(supported, bit))
+ if (FLAGS_SET(done, bit))
continue;
if (path_callback)
p = path_callback(bit, userdata);
-
if (!p)
p = path;
(void) cg_attach_fallback(cgroup_controller_to_string(c), p, pid);
+ done |= CGROUP_MASK_EXTEND_JOINED(bit);
}
return 0;
@@ -2206,6 +2219,7 @@ int cg_attach_many_everywhere(CGroupMask supported, const char *path, Set* pids,
int cg_migrate_everywhere(CGroupMask supported, const char *from, const char *to, cg_migrate_callback_t to_callback, void *userdata) {
CGroupController c;
+ CGroupMask done;
int r = 0, q;
if (!path_equal(from, to)) {
@@ -2220,30 +2234,34 @@ int cg_migrate_everywhere(CGroupMask supported, const char *from, const char *to
if (q > 0)
return r;
+ supported &= CGROUP_MASK_V1;
+ done = 0;
+
for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c);
const char *p = NULL;
- if (!FLAGS_SET(CGROUP_MASK_V1, bit))
+ if (!FLAGS_SET(supported, bit))
continue;
- if (!FLAGS_SET(supported, bit))
+ if (FLAGS_SET(done, bit))
continue;
if (to_callback)
p = to_callback(bit, userdata);
-
if (!p)
p = to;
(void) cg_migrate_recursive_fallback(SYSTEMD_CGROUP_CONTROLLER, to, cgroup_controller_to_string(c), p, 0);
+ done |= CGROUP_MASK_EXTEND_JOINED(bit);
}
- return 0;
+ return r;
}
int cg_trim_everywhere(CGroupMask supported, const char *path, bool delete_root) {
CGroupController c;
+ CGroupMask done;
int r, q;
r = cg_trim(SYSTEMD_CGROUP_CONTROLLER, path, delete_root);
@@ -2256,19 +2274,23 @@ int cg_trim_everywhere(CGroupMask supported, const char *path, bool delete_root)
if (q > 0)
return r;
+ supported &= CGROUP_MASK_V1;
+ done = 0;
+
for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c);
- if (!FLAGS_SET(CGROUP_MASK_V1, bit))
- continue;
-
if (!FLAGS_SET(supported, bit))
continue;
+ if (FLAGS_SET(done, bit))
+ continue;
+
(void) cg_trim(cgroup_controller_to_string(c), path, delete_root);
+ done |= CGROUP_MASK_EXTEND_JOINED(bit);
}
- return 0;
+ return r;
}
int cg_mask_to_string(CGroupMask mask, char **ret) {
diff --git a/src/basic/cgroup-util.h b/src/basic/cgroup-util.h
index 9b410f1f58..463a8dc84a 100644
--- a/src/basic/cgroup-util.h
+++ b/src/basic/cgroup-util.h
@@ -60,6 +60,15 @@ typedef enum CGroupMask {
_CGROUP_MASK_ALL = CGROUP_CONTROLLER_TO_MASK(_CGROUP_CONTROLLER_MAX) - 1
} CGroupMask;
+static inline CGroupMask CGROUP_MASK_EXTEND_JOINED(CGroupMask mask) {
+ /* We always mount "cpu" and "cpuacct" in the same hierarchy. Hence, when one bit is set also set the other */
+
+ if (mask & (CGROUP_MASK_CPU|CGROUP_MASK_CPUACCT))
+ mask |= (CGROUP_MASK_CPU|CGROUP_MASK_CPUACCT);
+
+ return mask;
+}
+
/* Special values for all weight knobs on unified hierarchy */
#define CGROUP_WEIGHT_INVALID ((uint64_t) -1)
#define CGROUP_WEIGHT_MIN UINT64_C(1)
diff --git a/src/cgtop/cgtop.c b/src/cgtop/cgtop.c
index 69a5fde732..efbfd240a6 100644
--- a/src/cgtop/cgtop.c
+++ b/src/cgtop/cgtop.c
@@ -98,6 +98,8 @@ static void group_hashmap_free(Hashmap *h) {
hashmap_free(h);
}
+DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, group_hashmap_free);
+
static const char *maybe_format_bytes(char *buf, size_t l, bool is_valid, uint64_t t) {
if (!is_valid)
return "-";
@@ -499,31 +501,14 @@ static int refresh_one(
}
static int refresh(const char *root, Hashmap *a, Hashmap *b, unsigned iteration) {
+ const char *c;
int r;
- assert(a);
-
- r = refresh_one(SYSTEMD_CGROUP_CONTROLLER, root, a, b, iteration, 0, NULL);
- if (r < 0)
- return r;
- r = refresh_one("cpu", root, a, b, iteration, 0, NULL);
- if (r < 0)
- return r;
- r = refresh_one("cpuacct", root, a, b, iteration, 0, NULL);
- if (r < 0)
- return r;
- r = refresh_one("memory", root, a, b, iteration, 0, NULL);
- if (r < 0)
- return r;
- r = refresh_one("io", root, a, b, iteration, 0, NULL);
- if (r < 0)
- return r;
- r = refresh_one("blkio", root, a, b, iteration, 0, NULL);
- if (r < 0)
- return r;
- r = refresh_one("pids", root, a, b, iteration, 0, NULL);
- if (r < 0)
- return r;
+ FOREACH_STRING(c, SYSTEMD_CGROUP_CONTROLLER, "cpu", "cpuacct", "memory", "io", "blkio", "pids") {
+ r = refresh_one(c, root, a, b, iteration, 0, NULL);
+ if (r < 0)
+ return r;
+ }
return 0;
}
@@ -924,13 +909,13 @@ static const char* counting_what(void) {
}
int main(int argc, char *argv[]) {
- int r;
- Hashmap *a = NULL, *b = NULL;
+ _cleanup_(group_hashmap_freep) Hashmap *a = NULL, *b = NULL;
unsigned iteration = 0;
usec_t last_refresh = 0;
bool quit = false, immediate_refresh = false;
_cleanup_free_ char *root = NULL;
CGroupMask mask;
+ int r;
log_parse_environment();
log_open();
@@ -1138,8 +1123,6 @@ int main(int argc, char *argv[]) {
r = 0;
finish:
- group_hashmap_free(a);
- group_hashmap_free(b);
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}
diff --git a/src/core/cgroup.c b/src/core/cgroup.c
index e62e05f0ff..45a7581d45 100644
--- a/src/core/cgroup.c
+++ b/src/core/cgroup.c
@@ -835,7 +835,7 @@ static void cgroup_context_apply(
weight = cgroup_cpu_shares_to_weight(shares);
- log_cgroup_compat(u, "Applying [Startup]CpuShares %" PRIu64 " as [Startup]CpuWeight %" PRIu64 " on %s",
+ log_cgroup_compat(u, "Applying [Startup]CPUShares %" PRIu64 " as [Startup]CPUWeight %" PRIu64 " on %s",
shares, weight, path);
} else
weight = CGROUP_WEIGHT_DEFAULT;
@@ -849,7 +849,7 @@ static void cgroup_context_apply(
shares = cgroup_cpu_weight_to_shares(weight);
- log_cgroup_compat(u, "Applying [Startup]CpuWeight %" PRIu64 " as [Startup]CpuShares %" PRIu64 " on %s",
+ log_cgroup_compat(u, "Applying [Startup]CPUWeight %" PRIu64 " as [Startup]CPUShares %" PRIu64 " on %s",
weight, shares, path);
} else if (has_shares)
shares = cgroup_context_cpu_shares(c, state);
@@ -1175,13 +1175,15 @@ static void cgroup_context_apply(
CGroupMask cgroup_context_get_mask(CGroupContext *c) {
CGroupMask mask = 0;
- /* Figure out which controllers we need */
+ /* Figure out which controllers we need, based on the cgroup context object */
- if (c->cpu_accounting ||
- cgroup_context_has_cpu_weight(c) ||
+ if (c->cpu_accounting)
+ mask |= CGROUP_MASK_CPUACCT;
+
+ if (cgroup_context_has_cpu_weight(c) ||
cgroup_context_has_cpu_shares(c) ||
c->cpu_quota_per_sec_usec != USEC_INFINITY)
- mask |= CGROUP_MASK_CPUACCT | CGROUP_MASK_CPU;
+ mask |= CGROUP_MASK_CPU;
if (cgroup_context_has_io_config(c) || cgroup_context_has_blockio_config(c))
mask |= CGROUP_MASK_IO | CGROUP_MASK_BLKIO;
@@ -1199,12 +1201,15 @@ CGroupMask cgroup_context_get_mask(CGroupContext *c) {
c->tasks_max != CGROUP_LIMIT_MAX)
mask |= CGROUP_MASK_PIDS;
- return mask;
+ return CGROUP_MASK_EXTEND_JOINED(mask);
}
CGroupMask unit_get_bpf_mask(Unit *u) {
CGroupMask mask = 0;
+ /* Figure out which controllers we need, based on the cgroup context, possibly taking into account children
+ * too. */
+
if (unit_get_needs_bpf_firewall(u))
mask |= CGROUP_MASK_BPF_FIREWALL;
@@ -1243,7 +1248,7 @@ CGroupMask unit_get_delegate_mask(Unit *u) {
}
assert_se(c = unit_get_cgroup_context(u));
- return c->delegate_controllers;
+ return CGROUP_MASK_EXTEND_JOINED(c->delegate_controllers);
}
CGroupMask unit_get_members_mask(Unit *u) {
diff --git a/src/core/main.c b/src/core/main.c
index 11537c8140..cabcb9ec16 100644
--- a/src/core/main.c
+++ b/src/core/main.c
@@ -100,7 +100,6 @@ static ShowStatus arg_show_status = _SHOW_STATUS_INVALID;
static bool arg_switched_root = false;
static PagerFlags arg_pager_flags = 0;
static bool arg_service_watchdogs = true;
-static char ***arg_join_controllers = NULL;
static ExecOutput arg_default_std_output = EXEC_OUTPUT_JOURNAL;
static ExecOutput arg_default_std_error = EXEC_OUTPUT_INHERIT;
static usec_t arg_default_restart_usec = DEFAULT_RESTART_USEC;
@@ -667,7 +666,7 @@ static int parse_config_file(void) {
{ "Manager", "CrashReboot", config_parse_bool, 0, &arg_crash_reboot },
{ "Manager", "ShowStatus", config_parse_show_status, 0, &arg_show_status },
{ "Manager", "CPUAffinity", config_parse_cpu_affinity2, 0, NULL },
- { "Manager", "JoinControllers", config_parse_join_controllers, 0, &arg_join_controllers },
+ { "Manager", "JoinControllers", config_parse_warn_compat, DISABLED_CONFIGURATION, NULL },
{ "Manager", "RuntimeWatchdogSec", config_parse_sec, 0, &arg_runtime_watchdog },
{ "Manager", "ShutdownWatchdogSec", config_parse_sec, 0, &arg_shutdown_watchdog },
{ "Manager", "WatchdogDevice", config_parse_path, 0, &arg_watchdog_device },
@@ -1956,7 +1955,7 @@ static int initialize_runtime(
install_crash_handler();
if (!skip_setup) {
- r = mount_cgroup_controllers(arg_join_controllers);
+ r = mount_cgroup_controllers();
if (r < 0) {
*ret_error_message = "Failed to mount cgroup hierarchies";
return r;
@@ -2081,7 +2080,6 @@ static void free_arguments(void) {
arg_default_unit = mfree(arg_default_unit);
arg_confirm_spawn = mfree(arg_confirm_spawn);
- arg_join_controllers = strv_free_free(arg_join_controllers);
arg_default_environment = strv_free(arg_default_environment);
arg_syscall_archs = set_free(arg_syscall_archs);
}
diff --git a/src/core/mount-setup.c b/src/core/mount-setup.c
index 16880e6157..e15d94d98a 100644
--- a/src/core/mount-setup.c
+++ b/src/core/mount-setup.c
@@ -229,76 +229,105 @@ int mount_setup_early(void) {
return mount_points_setup(N_EARLY_MOUNT, false);
}
-int mount_cgroup_controllers(char ***join_controllers) {
+static const char *join_with(const char *controller) {
+
+ static const char* const pairs[] = {
+ "cpu", "cpuacct",
+ "net_cls", "net_prio",
+ NULL
+ };
+
+ const char *const *x, *const *y;
+
+ 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);
+
+#ifdef SMACK_RUN_LABEL
+ const char *p;
+
+ p = strjoina("/sys/fs/cgroup/", target);
+
+ r = mac_smack_copy(a, p);
+ if (r < 0 && r != -EOPNOTSUPP)
+ return log_error_errno(r, "Failed to copy smack label from %s to %s: %m", p, a);
+#endif
+
+ return 0;
+}
+
+int mount_cgroup_controllers(void) {
_cleanup_set_free_free_ Set *controllers = NULL;
- bool has_argument = !!join_controllers;
int r;
if (!cg_is_legacy_wanted())
return 0;
/* Mount all available cgroup controllers that are built into the kernel. */
-
- if (!has_argument)
- /* The defaults:
- * mount "cpu" + "cpuacct" together, and "net_cls" + "net_prio".
- *
- * We'd like to add "cpuset" to the mix, but "cpuset" doesn't really
- * work for groups with no initialized attributes.
- */
- join_controllers = (char**[]) {
- STRV_MAKE("cpu", "cpuacct"),
- STRV_MAKE("net_cls", "net_prio"),
- NULL,
- };
-
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,
};
- char ***k = NULL;
controller = set_steal_first(controllers);
if (!controller)
break;
- for (k = join_controllers; *k; k++)
- if (strv_find(*k, 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;
- if (*k) {
- char **i, **j;
+ /* Check if the other controller is actually available in the kernel too */
+ c = set_remove(controllers, other_controller);
+ if (c) {
- for (i = *k, j = *k; *i; i++) {
-
- if (!streq(*i, controller)) {
- _cleanup_free_ char *t;
-
- t = set_remove(controllers, *i);
- if (!t) {
- if (has_argument)
- free(*i);
- continue;
- }
- }
-
- *(j++) = *i;
+ /* 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();
}
+ }
- *j = NULL;
-
- options = strv_join(*k, ",");
- if (!options)
- return log_oom();
- } else
+ /* The simple case, where there's only one controller to mount together */
+ if (!options)
options = TAKE_PTR(controller);
where = strappend("/sys/fs/cgroup/", options);
@@ -312,35 +341,14 @@ int mount_cgroup_controllers(char ***join_controllers) {
if (r < 0)
return r;
- if (r > 0 && *k) {
- char **i;
-
- for (i = *k; *i; i++) {
- _cleanup_free_ char *t = NULL;
-
- t = strappend("/sys/fs/cgroup/", *i);
- if (!t)
- return log_oom();
-
- r = symlink(options, t);
- if (r >= 0) {
-#ifdef SMACK_RUN_LABEL
- _cleanup_free_ char *src;
- src = strappend("/sys/fs/cgroup/", options);
- if (!src)
- return log_oom();
- r = mac_smack_copy(t, src);
- if (r < 0 && r != -EOPNOTSUPP)
- return log_error_errno(r, "Failed to copy smack label from %s to %s: %m", src, t);
-#endif
- } else if (errno != EEXIST)
- return log_error_errno(errno, "Failed to create symlink %s: %m", t);
- }
- }
+ /* 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. */
+ /* Now that we mounted everything, let's make the tmpfs the cgroup file systems are mounted into read-only. */
(void) mount("tmpfs", "/sys/fs/cgroup", "tmpfs", MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME|MS_RDONLY, "mode=755");
return 0;
diff --git a/src/core/mount-setup.h b/src/core/mount-setup.h
index 43cd8908de..b4ca2cf4b4 100644
--- a/src/core/mount-setup.h
+++ b/src/core/mount-setup.h
@@ -6,7 +6,7 @@
int mount_setup_early(void);
int mount_setup(bool loaded_policy);
-int mount_cgroup_controllers(char ***join_controllers);
+int mount_cgroup_controllers(void);
bool mount_point_is_api(const char *path);
bool mount_point_ignore(const char *path);
diff --git a/src/core/system.conf.in b/src/core/system.conf.in
index ef1bbbd948..0a58737b82 100644
--- a/src/core/system.conf.in
+++ b/src/core/system.conf.in
@@ -23,7 +23,6 @@
#CrashReboot=no
#CtrlAltDelBurstAction=reboot-force
#CPUAffinity=1 2
-#JoinControllers=cpu,cpuacct net_cls,net_prio
#RuntimeWatchdogSec=0
#ShutdownWatchdogSec=10min
#WatchdogDevice=
diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c
index 493ece9d09..b417ac2d6d 100644
--- a/src/shared/conf-parser.c
+++ b/src/shared/conf-parser.c
@@ -1013,121 +1013,6 @@ int config_parse_ip_port(
return 0;
}
-int config_parse_join_controllers(
- const char *unit,
- const char *filename,
- unsigned line,
- const char *section,
- unsigned section_line,
- const char *lvalue,
- int ltype,
- const char *rvalue,
- void *data,
- void *userdata) {
-
- char ****ret = data;
- const char *whole_rvalue = rvalue;
- unsigned n = 0;
- _cleanup_(strv_free_freep) char ***controllers = NULL;
-
- assert(filename);
- assert(lvalue);
- assert(rvalue);
- assert(ret);
-
- for (;;) {
- _cleanup_free_ char *word = NULL;
- char **l;
- int r;
-
- r = extract_first_word(&rvalue, &word, NULL, EXTRACT_QUOTES);
- if (r < 0) {
- log_syntax(unit, LOG_ERR, filename, line, r, "Invalid value for %s: %s", lvalue, whole_rvalue);
- return r;
- }
- if (r == 0)
- break;
-
- l = strv_split(word, ",");
- if (!l)
- return log_oom();
- strv_uniq(l);
-
- if (strv_length(l) <= 1) {
- strv_free(l);
- continue;
- }
-
- if (!controllers) {
- controllers = new(char**, 2);
- if (!controllers) {
- strv_free(l);
- return log_oom();
- }
-
- controllers[0] = l;
- controllers[1] = NULL;
-
- n = 1;
- } else {
- char ***a;
- char ***t;
-
- t = new0(char**, n+2);
- if (!t) {
- strv_free(l);
- return log_oom();
- }
-
- n = 0;
-
- for (a = controllers; *a; a++)
- if (strv_overlap(*a, l)) {
- if (strv_extend_strv(&l, *a, false) < 0) {
- strv_free(l);
- strv_free_free(t);
- return log_oom();
- }
-
- } else {
- char **c;
-
- c = strv_copy(*a);
- if (!c) {
- strv_free(l);
- strv_free_free(t);
- return log_oom();
- }
-
- t[n++] = c;
- }
-
- t[n++] = strv_uniq(l);
-
- strv_free_free(controllers);
- controllers = t;
- }
- }
- if (!isempty(rvalue))
- log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring.");
-
- /* As a special case, return a single empty strv, to override the default */
- if (!controllers) {
- controllers = new(char**, 2);
- if (!controllers)
- return log_oom();
- controllers[0] = strv_new(NULL);
- if (!controllers[0])
- return log_oom();
- controllers[1] = NULL;
- }
-
- strv_free_free(*ret);
- *ret = TAKE_PTR(controllers);
-
- return 0;
-}
-
int config_parse_mtu(
const char *unit,
const char *filename,
diff --git a/src/shared/conf-parser.h b/src/shared/conf-parser.h
index 16f042d894..865db4278b 100644
--- a/src/shared/conf-parser.h
+++ b/src/shared/conf-parser.h
@@ -137,7 +137,6 @@ CONFIG_PARSER_PROTOTYPE(config_parse_personality);
CONFIG_PARSER_PROTOTYPE(config_parse_permille);
CONFIG_PARSER_PROTOTYPE(config_parse_ifname);
CONFIG_PARSER_PROTOTYPE(config_parse_ip_port);
-CONFIG_PARSER_PROTOTYPE(config_parse_join_controllers);
CONFIG_PARSER_PROTOTYPE(config_parse_mtu);
CONFIG_PARSER_PROTOTYPE(config_parse_rlimit);
diff --git a/src/test/test-conf-parser.c b/src/test/test-conf-parser.c
index 368e02cb33..497becff73 100644
--- a/src/test/test-conf-parser.c
+++ b/src/test/test-conf-parser.c
@@ -210,45 +210,6 @@ static void test_config_parse_iec_uint64(void) {
assert_se(config_parse_iec_uint64(NULL, "/this/file", 11, "Section", 22, "Size", 0, "4.5M", &offset, NULL) == 0);
}
-static void test_config_parse_join_controllers(void) {
- int r;
- _cleanup_(strv_free_freep) char ***c = NULL;
- char ***c2;
-
- /* Test normal operation */
- r = config_parse_join_controllers(NULL, "example.conf", 11, "Section", 10, "JoinControllers", 0, "cpu,cpuacct net_cls,netprio", &c, NULL);
- assert_se(r == 0);
- assert_se(c);
- assert_se(strv_length(c[0]) == 2);
- assert_se(strv_equal(c[0], STRV_MAKE("cpu", "cpuacct")));
- assert_se(strv_length(c[1]) == 2);
- assert_se(strv_equal(c[1], STRV_MAKE("net_cls", "netprio")));
- assert_se(c[2] == NULL);
-
- /* Test special case of no mounted controllers */
- r = config_parse_join_controllers(NULL, "example.conf", 12, "Section", 10, "JoinControllers", 0, "", &c, NULL);
- assert_se(r == 0);
- assert_se(c);
- assert_se(strv_equal(c[0], STRV_MAKE_EMPTY));
- assert_se(c[1] == NULL);
-
- /* Test merging of overlapping lists */
- r = config_parse_join_controllers(NULL, "example.conf", 13, "Section", 10, "JoinControllers", 0, "a,b b,c", &c, NULL);
- assert_se(r == 0);
- assert_se(c);
- assert_se(strv_length(c[0]) == 3);
- assert_se(strv_contains(c[0], "a"));
- assert_se(strv_contains(c[0], "b"));
- assert_se(strv_contains(c[0], "c"));
- assert_se(c[1] == NULL);
-
- /* Test ignoring of bad lines */
- c2 = c;
- r = config_parse_join_controllers(NULL, "example.conf", 14, "Section", 10, "JoinControllers", 0, "a,\"b ", &c, NULL);
- assert_se(r < 0);
- assert_se(c == c2);
-}
-
#define x10(x) x x x x x x x x x x
#define x100(x) x10(x10(x))
#define x1000(x) x10(x100(x))
@@ -407,7 +368,6 @@ int main(int argc, char **argv) {
test_config_parse_sec();
test_config_parse_nsec();
test_config_parse_iec_uint64();
- test_config_parse_join_controllers();
for (i = 0; i < ELEMENTSOF(config_file); i++)
test_config_parse(i, config_file[i]);
diff --git a/test/fuzz/fuzz-unit-file/directives.service b/test/fuzz/fuzz-unit-file/directives.service
index fd830dde41..f454fd313e 100644
--- a/test/fuzz/fuzz-unit-file/directives.service
+++ b/test/fuzz/fuzz-unit-file/directives.service
@@ -690,7 +690,6 @@ HibernateMode=
HibernateState=
HybridSleepMode=
HybridSleepState=
-JoinControllers=
LogColor=
LogLevel=
LogLocation=