diff --git a/docs/TRANSIENT-SETTINGS.md b/docs/TRANSIENT-SETTINGS.md
index 652ac3d95e..3ea5116349 100644
--- a/docs/TRANSIENT-SETTINGS.md
+++ b/docs/TRANSIENT-SETTINGS.md
@@ -307,6 +307,7 @@ All cgroup/resource control settings are available for transient units
✓ StartupAllowedCPUs=
✓ AllowedMemoryNodes=
✓ StartupAllowedMemoryNodes=
+✓ CPUSetPartition=
✓ DisableControllers=
✓ Delegate=
✓ MemoryMin=
diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml
index 847e76f95c..5474dd788e 100644
--- a/man/org.freedesktop.systemd1.xml
+++ b/man/org.freedesktop.systemd1.xml
@@ -3010,6 +3010,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly ay StartupAllowedMemoryNodes = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly s CPUSetPartition = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly b IOAccounting = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t IOWeight = ...;
@@ -3689,6 +3691,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
+
+
@@ -4387,6 +4391,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
+
+
@@ -5294,6 +5300,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly ay StartupAllowedMemoryNodes = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly s CPUSetPartition = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly b IOAccounting = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t IOWeight = ...;
@@ -5989,6 +5997,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
+
+
@@ -6661,6 +6671,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
+
+
@@ -7391,6 +7403,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly ay StartupAllowedMemoryNodes = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly s CPUSetPartition = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly b IOAccounting = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t IOWeight = ...;
@@ -8010,6 +8024,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
+
+
@@ -8590,6 +8606,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
+
+
@@ -9453,6 +9471,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly ay StartupAllowedMemoryNodes = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly s CPUSetPartition = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly b IOAccounting = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t IOWeight = ...;
@@ -10054,6 +10074,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
+
+
@@ -10616,6 +10638,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
+
+
@@ -11332,6 +11356,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly ay StartupAllowedMemoryNodes = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly s CPUSetPartition = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly b IOAccounting = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t IOWeight = ...;
@@ -11515,6 +11541,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice {
+
+
@@ -11709,6 +11737,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice {
+
+
@@ -11928,6 +11958,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly ay StartupAllowedMemoryNodes = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+ readonly s CPUSetPartition = '...';
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly b IOAccounting = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly t IOWeight = ...;
@@ -12125,6 +12157,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope {
+
+
@@ -12343,6 +12377,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope {
+
+
@@ -12750,8 +12786,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
RefreshOnReload, and RootMStack were added in version 260.
CPUPressureThresholdUSec,
CPUPressureWatch,
- IOPressureThresholdUSec, and
- IOPressureWatch were added in version 261.
+ IOPressureThresholdUSec,
+ IOPressureWatch, and
+ CPUSetPartition were added in version 261.
Socket Unit Objects
@@ -12824,8 +12861,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
RootMStack were added in version 260.
CPUPressureThresholdUSec,
CPUPressureWatch,
- IOPressureThresholdUSec, and
- IOPressureWatch were added in version 261.
+ IOPressureThresholdUSec,
+ IOPressureWatch, and
+ CPUSetPartition were added in version 261.
Mount Unit Objects
@@ -12893,8 +12931,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
RootMStack were added in version 260.
CPUPressureThresholdUSec,
CPUPressureWatch,
- IOPressureThresholdUSec, and
- IOPressureWatch were added in version 261.
+ IOPressureThresholdUSec,
+ IOPressureWatch, and
+ CPUSetPartition were added in version 261.
Swap Unit Objects
@@ -12960,8 +12999,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
RootMStack were added in version 260.
CPUPressureThresholdUSec,
CPUPressureWatch,
- IOPressureThresholdUSec, and
- IOPressureWatch were added in version 261.
+ IOPressureThresholdUSec,
+ IOPressureWatch, and
+ CPUSetPartition were added in version 261.
Slice Unit Objects
@@ -12997,8 +13037,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
BindNetworkInterface was added in version 260.
CPUPressureThresholdUSec,
CPUPressureWatch,
- IOPressureThresholdUSec, and
- IOPressureWatch were added in version 261.
+ IOPressureThresholdUSec,
+ IOPressureWatch, and
+ CPUSetPartition were added in version 261.
Scope Unit Objects
@@ -13032,8 +13073,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
BindNetworkInterface was added in version 260.
CPUPressureThresholdUSec,
CPUPressureWatch,
- IOPressureThresholdUSec, and
- IOPressureWatch were added in version 261.
+ IOPressureThresholdUSec,
+ IOPressureWatch, and
+ CPUSetPartition were added in version 261.
Job Objects
diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml
index b5d559849d..fcad4b3183 100644
--- a/man/systemd.resource-control.xml
+++ b/man/systemd.resource-control.xml
@@ -521,6 +521,28 @@ CPUWeight=20 DisableControllers=cpu / \
+
+ CPUSetPartition=
+
+
+ Sets the partition type for the executed processes. Takes one
+ of member, root, or isolated. This setting
+ controls the cpuset.cpus.partition cgroup attribute.
+
+ When set to member, the cpuset operates in normal mode.
+ root creates a partition root, which can further divide CPUs among child cgroups.
+ isolated provides full CPU isolation, useful for real-time workloads that
+ require dedicated CPU resources without interference from other processes.
+ Defaults to the kernel default, which is member. For more details about this
+ control group attribute, see
+ Control Groups v2.
+
+ This setting requires AllowedCPUs= to also be set.
+
+
+
+
+
Process Accounting and Control
diff --git a/src/core/cgroup.c b/src/core/cgroup.c
index acf2e8147f..52d73b3747 100644
--- a/src/core/cgroup.c
+++ b/src/core/cgroup.c
@@ -178,6 +178,8 @@ void cgroup_context_init(CGroupContext *c) {
.tasks_max = CGROUP_TASKS_MAX_UNSET,
+ .cpuset_partition = _CPUSET_PARTITION_INVALID,
+
.moom_swap = MANAGED_OOM_AUTO,
.moom_mem_pressure = MANAGED_OOM_AUTO,
.moom_preference = MANAGED_OOM_PREFERENCE_NONE,
@@ -508,6 +510,7 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) {
"%sStartupAllowedCPUs: %s\n"
"%sAllowedMemoryNodes: %s\n"
"%sStartupAllowedMemoryNodes: %s\n"
+ "%sCPUSetPartition: %s\n"
"%sIOWeight: %" PRIu64 "\n"
"%sStartupIOWeight: %" PRIu64 "\n"
"%sMemoryMin: %" PRIu64 "%s\n"
@@ -546,6 +549,7 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) {
prefix, strempty(startup_cpuset_cpus),
prefix, strempty(cpuset_mems),
prefix, strempty(startup_cpuset_mems),
+ prefix, strna(cpuset_partition_to_string(c->cpuset_partition)),
prefix, c->io_weight,
prefix, c->startup_io_weight,
prefix, c->memory_min, format_cgroup_memory_limit_comparison(u, "MemoryMin", cda, sizeof(cda)),
@@ -1132,6 +1136,53 @@ static void cgroup_apply_cpuset(Unit *u, const CPUSet *cpus, const char *name) {
(void) set_attribute_and_warn(u, name, buf);
}
+static int cgroup_cpuset_partition_invalid(const char *partition) {
+ _cleanup_free_ char *part_str = NULL, *invalid = NULL;
+ int r;
+
+ assert(partition);
+
+ /* An invalid line looks like invalid () */
+ r = extract_many_words(&partition, /* separators= */ NULL, /* flags= */ 0, &part_str, &invalid);
+ if (r < 0)
+ return r;
+ if (r < 2)
+ return false;
+
+ return streq_ptr(invalid, "invalid");
+}
+
+static void cgroup_apply_cpuset_partition(Unit *u, const char *name, const char *partition) {
+ _cleanup_free_ char *buf = NULL;
+ CGroupRuntime *crt;
+ int r;
+
+ assert(u);
+ assert(name);
+ assert(partition);
+
+ if (set_attribute_and_warn(u, name, partition) < 0)
+ return;
+
+ /* We are writing and then reading back, crt is already checked while writing */
+ crt = ASSERT_PTR(unit_get_cgroup_runtime(u));
+
+ r = cg_get_attribute(crt->cgroup_path, name, &buf);
+ if (r < 0) {
+ log_unit_full_errno(u, LOG_LEVEL_CGROUP_WRITE(r), r, "Failed to read back '%s' attribute on '%s' as '%.*s': %m",
+ name, empty_to_root(crt->cgroup_path), (int) strcspn(partition, NEWLINE), partition);
+ return;
+ }
+
+ r = cgroup_cpuset_partition_invalid(buf);
+ if (r < 0)
+ log_unit_full_errno(u, LOG_LEVEL_CGROUP_WRITE(r), r, "Failed to read back '%s' attribute on '%s' as '%.*s': %m",
+ name, empty_to_root(crt->cgroup_path), (int) strcspn(partition, NEWLINE), partition);
+ else if (r)
+ log_unit_warning(u, "Failed to set '%s' attribute on '%s' to '%.*s': %s",
+ name, empty_to_root(crt->cgroup_path), (int) strcspn(partition, NEWLINE), partition, buf);
+}
+
static bool cgroup_context_has_io_config(CGroupContext *c) {
assert(c);
@@ -1464,6 +1515,9 @@ static void cgroup_context_apply(
if ((apply_mask & CGROUP_MASK_CPUSET) && !is_local_root) {
cgroup_apply_cpuset(u, cgroup_context_allowed_cpus(c, state), "cpuset.cpus");
cgroup_apply_cpuset(u, cgroup_context_allowed_mems(c, state), "cpuset.mems");
+
+ if (c->cpuset_partition >= 0)
+ cgroup_apply_cpuset_partition(u, "cpuset.cpus.partition", cpuset_partition_to_string(c->cpuset_partition));
}
/* The 'io' controller attributes are not exported on the host's root cgroup (being a pure cgroup v2
@@ -4582,6 +4636,14 @@ static const char* const cgroup_device_policy_table[_CGROUP_DEVICE_POLICY_MAX] =
DEFINE_STRING_TABLE_LOOKUP(cgroup_device_policy, CGroupDevicePolicy);
+static const char* const cpuset_partition_table[_CPUSET_PARTITION_MAX] = {
+ [CPUSET_PARTITION_MEMBER] = "member",
+ [CPUSET_PARTITION_ROOT] = "root",
+ [CPUSET_PARTITION_ISOLATED] = "isolated",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(cpuset_partition, CPUSetPartition);
+
static const char* const cgroup_pressure_watch_table[_CGROUP_PRESSURE_WATCH_MAX] = {
[CGROUP_PRESSURE_WATCH_NO] = "no",
[CGROUP_PRESSURE_WATCH_YES] = "yes",
diff --git a/src/core/cgroup.h b/src/core/cgroup.h
index d9a6ded110..b7213d8d59 100644
--- a/src/core/cgroup.h
+++ b/src/core/cgroup.h
@@ -47,6 +47,14 @@ typedef enum FreezerAction {
_FREEZER_ACTION_INVALID = -EINVAL,
} FreezerAction;
+typedef enum CPUSetPartition {
+ CPUSET_PARTITION_MEMBER,
+ CPUSET_PARTITION_ROOT,
+ CPUSET_PARTITION_ISOLATED,
+ _CPUSET_PARTITION_MAX,
+ _CPUSET_PARTITION_INVALID = -EINVAL,
+} CPUSetPartition;
+
typedef enum CGroupDevicePermissions {
/* We reuse the same bit meanings the kernel's BPF_DEVCG_ACC_xyz definitions use */
CGROUP_DEVICE_MKNOD = 1 << 0,
@@ -136,6 +144,7 @@ typedef struct CGroupContext {
CPUSet startup_cpuset_cpus;
CPUSet cpuset_mems;
CPUSet startup_cpuset_mems;
+ CPUSetPartition cpuset_partition;
uint64_t io_weight;
uint64_t startup_io_weight;
@@ -478,6 +487,7 @@ int unit_get_cpuset(Unit *u, CPUSet *cpus, const char *name);
int unit_cgroup_freezer_action(Unit *u, FreezerAction action);
DECLARE_STRING_TABLE_LOOKUP(freezer_action, FreezerAction);
+DECLARE_STRING_TABLE_LOOKUP(cpuset_partition, CPUSetPartition);
CGroupRuntime* cgroup_runtime_new(void);
CGroupRuntime* cgroup_runtime_free(CGroupRuntime *crt);
diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c
index 927c133dd9..6cecc8b9e7 100644
--- a/src/core/dbus-cgroup.c
+++ b/src/core/dbus-cgroup.c
@@ -26,6 +26,7 @@ BUS_DEFINE_PROPERTY_GET(bus_property_get_tasks_max, "t", CGroupTasksMax, cgroup_
BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_cgroup_pressure_watch, cgroup_pressure_watch, CGroupPressureWatch);
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_cgroup_device_policy, cgroup_device_policy, CGroupDevicePolicy);
+static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_cpuset_partition, cpuset_partition, CPUSetPartition);
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_managed_oom_mode, managed_oom_mode, ManagedOOMMode);
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_managed_oom_preference, managed_oom_preference, ManagedOOMPreference);
@@ -385,6 +386,7 @@ const sd_bus_vtable bus_cgroup_vtable[] = {
SD_BUS_PROPERTY("StartupAllowedCPUs", "ay", property_get_cpuset, offsetof(CGroupContext, startup_cpuset_cpus), 0),
SD_BUS_PROPERTY("AllowedMemoryNodes", "ay", property_get_cpuset, offsetof(CGroupContext, cpuset_mems), 0),
SD_BUS_PROPERTY("StartupAllowedMemoryNodes", "ay", property_get_cpuset, offsetof(CGroupContext, startup_cpuset_mems), 0),
+ SD_BUS_PROPERTY("CPUSetPartition", "s", property_get_cpuset_partition, offsetof(CGroupContext, cpuset_partition), 0),
SD_BUS_PROPERTY("IOAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, io_accounting), 0),
SD_BUS_PROPERTY("IOWeight", "t", NULL, offsetof(CGroupContext, io_weight), 0),
SD_BUS_PROPERTY("StartupIOWeight", "t", NULL, offsetof(CGroupContext, startup_io_weight), 0),
@@ -1493,6 +1495,35 @@ int bus_cgroup_set_property(
return 1;
+ } else if (streq(name, "CPUSetPartition")) {
+ const char *partition_str;
+ CPUSetPartition p;
+
+ r = sd_bus_message_read(message, "s", &partition_str);
+ if (r < 0)
+ return r;
+
+ if (isempty(partition_str))
+ p = _CPUSET_PARTITION_INVALID;
+ else {
+ p = cpuset_partition_from_string(partition_str);
+ if (p < 0)
+ return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS,
+ "Invalid CPUSetPartition value: %s", partition_str);
+ }
+
+ if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
+ c->cpuset_partition = p;
+ unit_invalidate_cgroup(u, CGROUP_MASK_CPUSET);
+
+ if (p == _CPUSET_PARTITION_INVALID)
+ unit_write_settingf(u, flags, name, "%s=", name);
+ else
+ unit_write_settingf(u, flags, name, "%s=%s", name, partition_str);
+ }
+
+ return 1;
+
} else if (streq(name, "DeviceAllow")) {
const char *path, *rwm;
unsigned n = 0;
diff --git a/src/core/execute-serialize.c b/src/core/execute-serialize.c
index 5d0ebfa37c..5f205772fd 100644
--- a/src/core/execute-serialize.c
+++ b/src/core/execute-serialize.c
@@ -121,6 +121,12 @@ static int exec_cgroup_context_serialize(const CGroupContext *c, FILE *f) {
if (r < 0)
return r;
+ if (c->cpuset_partition >= 0) {
+ r = serialize_item(f, "exec-cgroup-context-cpuset-partition", cpuset_partition_to_string(c->cpuset_partition));
+ if (r < 0)
+ return r;
+ }
+
if (c->io_weight != CGROUP_WEIGHT_INVALID) {
r = serialize_item_format(f, "exec-cgroup-context-io-weight", "%" PRIu64, c->io_weight);
if (r < 0)
@@ -513,6 +519,10 @@ static int exec_cgroup_context_deserialize(CGroupContext *c, FILE *f) {
r = parse_cpu_set(val, &c->startup_cpuset_mems);
if (r < 0)
return r;
+ } else if ((val = startswith(l, "exec-cgroup-context-cpuset-partition="))) {
+ c->cpuset_partition = cpuset_partition_from_string(val);
+ if (c->cpuset_partition < 0)
+ return -EINVAL;
} else if ((val = startswith(l, "exec-cgroup-context-io-weight="))) {
r = safe_atou64(val, &c->io_weight);
if (r < 0)
diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in
index 79551adad8..b8d744c1f4 100644
--- a/src/core/load-fragment-gperf.gperf.in
+++ b/src/core/load-fragment-gperf.gperf.in
@@ -215,6 +215,7 @@
{{type}}.StartupAllowedCPUs, config_parse_unit_cpu_set, 0, offsetof({{type}}, cgroup_context.startup_cpuset_cpus)
{{type}}.AllowedMemoryNodes, config_parse_unit_cpu_set, 0, offsetof({{type}}, cgroup_context.cpuset_mems)
{{type}}.StartupAllowedMemoryNodes, config_parse_unit_cpu_set, 0, offsetof({{type}}, cgroup_context.startup_cpuset_mems)
+{{type}}.CPUSetPartition, config_parse_cpuset_partition, 0, offsetof({{type}}, cgroup_context.cpuset_partition)
{{type}}.CPUAccounting, config_parse_warn_compat, DISABLED_LEGACY, 0
{{type}}.CPUWeight, config_parse_cg_cpu_weight, 0, offsetof({{type}}, cgroup_context.cpu_weight)
{{type}}.StartupCPUWeight, config_parse_cg_cpu_weight, 0, offsetof({{type}}, cgroup_context.startup_cpu_weight)
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index d08e71f9c9..2a268d813b 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -131,6 +131,7 @@ DEFINE_CONFIG_PARSE(config_parse_socket_protocol, parse_socket_protocol);
DEFINE_CONFIG_PARSE(config_parse_exec_secure_bits, secure_bits_from_string);
DEFINE_CONFIG_PARSE_ENUM(config_parse_collect_mode, collect_mode, CollectMode);
DEFINE_CONFIG_PARSE_ENUM(config_parse_device_policy, cgroup_device_policy, CGroupDevicePolicy);
+DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_cpuset_partition, cpuset_partition, CPUSetPartition, _CPUSET_PARTITION_INVALID);
DEFINE_CONFIG_PARSE_ENUM(config_parse_exec_keyring_mode, exec_keyring_mode, ExecKeyringMode);
DEFINE_CONFIG_PARSE_ENUM(config_parse_protect_proc, protect_proc, ProtectProc);
DEFINE_CONFIG_PARSE_ENUM(config_parse_proc_subset, proc_subset, ProcSubset);
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
index ed8060b042..fafb004028 100644
--- a/src/core/load-fragment.h
+++ b/src/core/load-fragment.h
@@ -170,6 +170,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_mount_node);
CONFIG_PARSER_PROTOTYPE(config_parse_concurrency_max);
CONFIG_PARSER_PROTOTYPE(config_parse_bind_network_interface);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_memory_thp);
+CONFIG_PARSER_PROTOTYPE(config_parse_cpuset_partition);
/* gperf prototypes */
const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
diff --git a/src/core/varlink-cgroup.c b/src/core/varlink-cgroup.c
index 3793975e8c..9953707417 100644
--- a/src/core/varlink-cgroup.c
+++ b/src/core/varlink-cgroup.c
@@ -258,6 +258,7 @@ int unit_cgroup_context_build_json(sd_json_variant **ret, const char *name, void
JSON_BUILD_PAIR_FINITE_USEC("CPUQuotaPeriodUSec", c->cpu_quota_period_usec),
JSON_BUILD_PAIR_CALLBACK_NON_NULL("AllowedCPUs", cpuset_build_json, &c->cpuset_cpus),
JSON_BUILD_PAIR_CALLBACK_NON_NULL("StartupAllowedCPUs", cpuset_build_json, &c->startup_cpuset_cpus),
+ JSON_BUILD_PAIR_ENUM("CPUSetPartition", cpuset_partition_to_string(c->cpuset_partition)),
/* Memory Accounting and Control */
SD_JSON_BUILD_PAIR_BOOLEAN("MemoryAccounting", c->memory_accounting),
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index 7076322d2d..9b69ebd1a9 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -2403,6 +2403,7 @@ static const BusProperty cgroup_properties[] = {
{ "StartupAllowedCPUs", bus_append_parse_cpu_set },
{ "AllowedMemoryNodes", bus_append_parse_cpu_set },
{ "StartupAllowedMemoryNodes", bus_append_parse_cpu_set },
+ { "CPUSetPartition", bus_append_string },
{ "DisableControllers", bus_append_strv },
{ "Delegate", bus_append_parse_delegate },
{ "MemoryMin", bus_append_parse_resource_limit },
diff --git a/src/shared/varlink-io.systemd.Unit.c b/src/shared/varlink-io.systemd.Unit.c
index 883154b2f1..e10bf76bf9 100644
--- a/src/shared/varlink-io.systemd.Unit.c
+++ b/src/shared/varlink-io.systemd.Unit.c
@@ -261,6 +261,12 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
SD_VARLINK_FIELD_COMMENT("The device permissions"),
SD_VARLINK_DEFINE_FIELD(permissions, SD_VARLINK_STRING, 0));
+static SD_VARLINK_DEFINE_ENUM_TYPE(
+ CPUSetPartition,
+ SD_VARLINK_DEFINE_ENUM_VALUE(member),
+ SD_VARLINK_DEFINE_ENUM_VALUE(root),
+ SD_VARLINK_DEFINE_ENUM_VALUE(isolated));
+
static SD_VARLINK_DEFINE_STRUCT_TYPE(
CGroupContext,
@@ -281,6 +287,8 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
SD_VARLINK_DEFINE_FIELD(AllowedCPUs, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#AllowedCPUs="),
SD_VARLINK_DEFINE_FIELD(StartupAllowedCPUs, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#CPUSetPartition="),
+ SD_VARLINK_DEFINE_FIELD_BY_TYPE(CPUSetPartition, CPUSetPartition, SD_VARLINK_NULLABLE),
/* Memory Accounting and Control
* https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#Memory%20Accounting%20and%20Control */
@@ -1627,6 +1635,7 @@ SD_VARLINK_DEFINE_INTERFACE(
&vl_type_CGroupBPFProgram,
&vl_type_CGroupController,
&vl_type_CGroupDeviceAllow,
+ &vl_type_CPUSetPartition,
SD_VARLINK_SYMBOL_COMMENT("CGroup context of a unit"),
&vl_type_CGroupContext,
SD_VARLINK_SYMBOL_COMMENT("CGroup runtime of a unit"),
diff --git a/src/test/test-bus-unit-util.c b/src/test/test-bus-unit-util.c
index a330bd8054..ae2cb19c9c 100644
--- a/src/test/test-bus-unit-util.c
+++ b/src/test/test-bus-unit-util.c
@@ -117,6 +117,11 @@ TEST(cgroup_properties) {
"StartupAllowedMemoryNodes=0",
"StartupAllowedMemoryNodes=1-3",
+ "CPUSetPartition=member",
+ "CPUSetPartition=root",
+ "CPUSetPartition=isolated",
+ "CPUSetPartition=",
+
"DisableControllers=cpu",
"DisableControllers= "
" cpu cpuacct cpuset io blkio memory devices pids bpf-firewall bpf-devices "