]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
cgroup: Add CPUSetPartition= setting
authorglemco <32201227+glemco@users.noreply.github.com>
Sun, 10 May 2026 09:48:27 +0000 (11:48 +0200)
committerglemco <32201227+glemco@users.noreply.github.com>
Thu, 14 May 2026 06:55:44 +0000 (08:55 +0200)
Add support for configuring cpuset partition type via the
CPUSetPartition= unit file setting. This controls the kernel's
cpuset.cpus.partition cgroup attribute.

The setting takes one of "member", "root", or "isolated". This is
useful for real-time workloads that require dedicated CPU resources
without interference from other processes.

When set, systemd will write the partition type to the
cpuset.cpus.partition cgroup file. If the kernel rejects the value
(e.g., due to partition hierarchy rules), a warning is logged and the
unit continues with the kernel's default partition type.

Co-developed-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
14 files changed:
docs/TRANSIENT-SETTINGS.md
man/org.freedesktop.systemd1.xml
man/systemd.resource-control.xml
src/core/cgroup.c
src/core/cgroup.h
src/core/dbus-cgroup.c
src/core/execute-serialize.c
src/core/load-fragment-gperf.gperf.in
src/core/load-fragment.c
src/core/load-fragment.h
src/core/varlink-cgroup.c
src/shared/bus-unit-util.c
src/shared/varlink-io.systemd.Unit.c
src/test/test-bus-unit-util.c

index 652ac3d95e64c3ea314b7bbb2d53c18824e71f1c..3ea51163499bbb607371d9eacf0475e1dd142084 100644 (file)
@@ -307,6 +307,7 @@ All cgroup/resource control settings are available for transient units
 ✓ StartupAllowedCPUs=
 ✓ AllowedMemoryNodes=
 ✓ StartupAllowedMemoryNodes=
+✓ CPUSetPartition=
 ✓ DisableControllers=
 ✓ Delegate=
 ✓ MemoryMin=
index 847e76f95c7676694634fd9aea3dfbe51cdd1ae0..5474dd788ed6d954ab2977e5aff4e5f6707821ad 100644 (file)
@@ -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 {
 
     <!--property StartupAllowedMemoryNodes is not documented!-->
 
+    <!--property CPUSetPartition is not documented!-->
+
     <!--property IOAccounting is not documented!-->
 
     <!--property IOWeight is not documented!-->
@@ -4387,6 +4391,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
 
     <variablelist class="dbus-property" generated="True" extra-ref="StartupAllowedMemoryNodes"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="CPUSetPartition"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="IOAccounting"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="IOWeight"/>
@@ -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 {
 
     <!--property StartupAllowedMemoryNodes is not documented!-->
 
+    <!--property CPUSetPartition is not documented!-->
+
     <!--property IOAccounting is not documented!-->
 
     <!--property IOWeight is not documented!-->
@@ -6661,6 +6671,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
 
     <variablelist class="dbus-property" generated="True" extra-ref="StartupAllowedMemoryNodes"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="CPUSetPartition"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="IOAccounting"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="IOWeight"/>
@@ -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 {
 
     <!--property StartupAllowedMemoryNodes is not documented!-->
 
+    <!--property CPUSetPartition is not documented!-->
+
     <!--property IOAccounting is not documented!-->
 
     <!--property IOWeight is not documented!-->
@@ -8590,6 +8606,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
 
     <variablelist class="dbus-property" generated="True" extra-ref="StartupAllowedMemoryNodes"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="CPUSetPartition"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="IOAccounting"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="IOWeight"/>
@@ -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 {
 
     <!--property StartupAllowedMemoryNodes is not documented!-->
 
+    <!--property CPUSetPartition is not documented!-->
+
     <!--property IOAccounting is not documented!-->
 
     <!--property IOWeight is not documented!-->
@@ -10616,6 +10638,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
 
     <variablelist class="dbus-property" generated="True" extra-ref="StartupAllowedMemoryNodes"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="CPUSetPartition"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="IOAccounting"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="IOWeight"/>
@@ -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 {
 
     <!--property StartupAllowedMemoryNodes is not documented!-->
 
+    <!--property CPUSetPartition is not documented!-->
+
     <!--property IOAccounting is not documented!-->
 
     <!--property IOWeight is not documented!-->
@@ -11709,6 +11737,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice {
 
     <variablelist class="dbus-property" generated="True" extra-ref="StartupAllowedMemoryNodes"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="CPUSetPartition"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="IOAccounting"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="IOWeight"/>
@@ -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 {
 
     <!--property StartupAllowedMemoryNodes is not documented!-->
 
+    <!--property CPUSetPartition is not documented!-->
+
     <!--property IOAccounting is not documented!-->
 
     <!--property IOWeight is not documented!-->
@@ -12343,6 +12377,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope {
 
     <variablelist class="dbus-property" generated="True" extra-ref="StartupAllowedMemoryNodes"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="CPUSetPartition"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="IOAccounting"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="IOWeight"/>
@@ -12750,8 +12786,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
       <varname>RefreshOnReload</varname>, and <varname>RootMStack</varname> were added in version 260.</para>
       <para><varname>CPUPressureThresholdUSec</varname>,
       <varname>CPUPressureWatch</varname>,
-      <varname>IOPressureThresholdUSec</varname>, and
-      <varname>IOPressureWatch</varname> were added in version 261.</para>
+      <varname>IOPressureThresholdUSec</varname>,
+      <varname>IOPressureWatch</varname>, and
+      <varname>CPUSetPartition</varname> were added in version 261.</para>
     </refsect2>
     <refsect2>
       <title>Socket Unit Objects</title>
@@ -12824,8 +12861,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
       <varname>RootMStack</varname> were added in version 260.</para>
       <para><varname>CPUPressureThresholdUSec</varname>,
       <varname>CPUPressureWatch</varname>,
-      <varname>IOPressureThresholdUSec</varname>, and
-      <varname>IOPressureWatch</varname> were added in version 261.</para>
+      <varname>IOPressureThresholdUSec</varname>,
+      <varname>IOPressureWatch</varname>, and
+      <varname>CPUSetPartition</varname> were added in version 261.</para>
     </refsect2>
     <refsect2>
       <title>Mount Unit Objects</title>
@@ -12893,8 +12931,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
       <varname>RootMStack</varname> were added in version 260.</para>
       <para><varname>CPUPressureThresholdUSec</varname>,
       <varname>CPUPressureWatch</varname>,
-      <varname>IOPressureThresholdUSec</varname>, and
-      <varname>IOPressureWatch</varname> were added in version 261.</para>
+      <varname>IOPressureThresholdUSec</varname>,
+      <varname>IOPressureWatch</varname>, and
+      <varname>CPUSetPartition</varname> were added in version 261.</para>
     </refsect2>
     <refsect2>
       <title>Swap Unit Objects</title>
@@ -12960,8 +12999,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
       <varname>RootMStack</varname> were added in version 260.</para>
       <para><varname>CPUPressureThresholdUSec</varname>,
       <varname>CPUPressureWatch</varname>,
-      <varname>IOPressureThresholdUSec</varname>, and
-      <varname>IOPressureWatch</varname> were added in version 261.</para>
+      <varname>IOPressureThresholdUSec</varname>,
+      <varname>IOPressureWatch</varname>, and
+      <varname>CPUSetPartition</varname> were added in version 261.</para>
     </refsect2>
     <refsect2>
       <title>Slice Unit Objects</title>
@@ -12997,8 +13037,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
       <para><varname>BindNetworkInterface</varname> was added in version 260.</para>
       <para><varname>CPUPressureThresholdUSec</varname>,
       <varname>CPUPressureWatch</varname>,
-      <varname>IOPressureThresholdUSec</varname>, and
-      <varname>IOPressureWatch</varname> were added in version 261.</para>
+      <varname>IOPressureThresholdUSec</varname>,
+      <varname>IOPressureWatch</varname>, and
+      <varname>CPUSetPartition</varname> were added in version 261.</para>
     </refsect2>
     <refsect2>
       <title>Scope Unit Objects</title>
@@ -13032,8 +13073,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
       <para><varname>BindNetworkInterface</varname> was added in version 260.</para>
       <para><varname>CPUPressureThresholdUSec</varname>,
       <varname>CPUPressureWatch</varname>,
-      <varname>IOPressureThresholdUSec</varname>, and
-      <varname>IOPressureWatch</varname> were added in version 261.</para>
+      <varname>IOPressureThresholdUSec</varname>,
+      <varname>IOPressureWatch</varname>, and
+      <varname>CPUSetPartition</varname> were added in version 261.</para>
     </refsect2>
     <refsect2>
       <title>Job Objects</title>
index b5d559849dc3a03a21508da58e48a78e019e226c..fcad4b31839eacf0a28edf34ed9ee658bdd45221 100644 (file)
@@ -521,6 +521,28 @@ CPUWeight=20   DisableControllers=cpu              /          \
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>CPUSetPartition=</varname></term>
+
+        <listitem>
+          <para>Sets the <option>cpuset</option> partition type for the executed processes. Takes one
+          of <literal>member</literal>, <literal>root</literal>, or <literal>isolated</literal>. This setting
+          controls the <literal>cpuset.cpus.partition</literal> cgroup attribute.</para>
+
+          <para>When set to <literal>member</literal>, the cpuset operates in normal mode.
+          <literal>root</literal> creates a partition root, which can further divide CPUs among child cgroups.
+          <literal>isolated</literal> 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 <literal>member</literal>. For more details about this
+          control group attribute, see <ulink url="https://docs.kernel.org/admin-guide/cgroup-v2.html">
+          Control Groups v2</ulink>.</para>
+
+          <para>This setting requires <varname>AllowedCPUs=</varname> to also be set.</para>
+
+          <xi:include href="version-info.xml" xpointer="v261"/>
+        </listitem>
+      </varlistentry>
+
     </variablelist>
 
     </refsect2><refsect2><title>Process Accounting and Control</title>
index acf2e8147f41b8a1a0b6b76a5caa6da43916a673..52d73b3747edb2b809757c4f1ed78e8214a4ea22 100644 (file)
@@ -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 <partition> invalid (<reason>) */
+        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",
index d9a6ded110214b1788456bc37a5b5ac4421e49ea..b7213d8d59494bb5bab6ad4395af066178cf03a2 100644 (file)
@@ -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);
index 927c133dd9e47bcaeaa4faf47b1f87725a9a8004..6cecc8b9e74194b6070d4255b1e674973ac0b84d 100644 (file)
@@ -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;
index 5d0ebfa37c0bb1e2c109fff295e0db6966d82f28..5f205772fd81abae61eacf930c302a79abfc44cd 100644 (file)
@@ -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)
index 79551adad8b1a1012a3435a3a82c295ec60cdbc1..b8d744c1f49598085e18ab5958226690abba0642 100644 (file)
 {{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)
index d08e71f9c9782cd4514a32828ccbd03400d92fb7..2a268d813b5bbd9816f466894997c3f3d3b7ddf8 100644 (file)
@@ -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);
index ed8060b0428ac5cdceecb87182e578b7d7a0082f..fafb00402830e8223a1025f2e826121a7454cf48 100644 (file)
@@ -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);
index 3793975e8cc2e63ef8d1357ff560a87dcb111584..9953707417d5d00802a4818a257f6dde85bed491 100644 (file)
@@ -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),
index 7076322d2d315e9ad7f86e197f315f20a44c3d58..9b69ebd1a9361524353e2590a99a118c0e5a4c33 100644 (file)
@@ -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               },
index 883154b2f1951c77937fec20888a1bcc510444ce..e10bf76bf9b2721ecc22f49f8745d1caf530bded 100644 (file)
@@ -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"),
index a330bd80545d31f8e1e5cacfaa19cab5d6a34c86..ae2cb19c9ce0a98a5b73e6537ee8791500f0d5af 100644 (file)
@@ -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     "