[systemd-commits] Makefile.am src/core src/shared src/systemctl

Lennart Poettering lennart at kemper.freedesktop.org
Wed Feb 27 09:52:26 PST 2013


 Makefile.am                           |    2 
 src/core/cgroup-attr.c                |   58 +++--
 src/core/cgroup-attr.h                |    7 
 src/core/cgroup-semantics.c           |  333 ++++++++++++++++++++++++++++++++++
 src/core/cgroup-semantics.h           |   43 ++++
 src/core/dbus-manager.c               |   41 ++--
 src/core/dbus-unit.c                  |  329 +++++++++++++++++++--------------
 src/core/dbus-unit.h                  |   25 +-
 src/core/load-fragment-gperf.gperf.m4 |   16 -
 src/core/load-fragment.c              |  300 ++----------------------------
 src/core/load-fragment.h              |    6 
 src/core/unit.c                       |  159 +++++++++-------
 src/core/unit.h                       |    3 
 src/shared/path-lookup.c              |    9 
 src/shared/strv.c                     |   25 ++
 src/shared/strv.h                     |    1 
 src/systemctl/systemctl.c             |  155 ++++++---------
 17 files changed, 874 insertions(+), 638 deletions(-)

New commits:
commit 26d04f86a36595e3565c74d67863e076c3e3c773
Author: Lennart Poettering <lennart at poettering.net>
Date:   Wed Feb 27 18:50:41 2013 +0100

    unit: rework resource management API
    
    This introduces a new static list of known attributes and their special
    semantics. This means that cgroup attribute values can now be
    automatically translated from user to kernel notation for command line
    set settings, too.
    
    This also adds proper support for multi-line attributes.

diff --git a/Makefile.am b/Makefile.am
index 74534cc..2108abe 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -852,6 +852,8 @@ libsystemd_core_la_SOURCES = \
 	src/core/tcpwrap.h \
 	src/core/cgroup-attr.c \
 	src/core/cgroup-attr.h \
+	src/core/cgroup-semantics.c \
+	src/core/cgroup-semantics.h \
 	src/core/securebits.h \
 	src/core/initreq.h \
 	src/core/special.h \
diff --git a/src/core/cgroup-attr.c b/src/core/cgroup-attr.c
index 1373684..2ab4d46 100644
--- a/src/core/cgroup-attr.c
+++ b/src/core/cgroup-attr.c
@@ -25,8 +25,8 @@
 #include "fileio.h"
 
 int cgroup_attribute_apply(CGroupAttribute *a, CGroupBonding *b) {
-        int r;
         _cleanup_free_ char *path = NULL, *v = NULL;
+        int r;
 
         assert(a);
 
@@ -34,8 +34,8 @@ int cgroup_attribute_apply(CGroupAttribute *a, CGroupBonding *b) {
         if (!b)
                 return 0;
 
-        if (a->map_callback) {
-                r = a->map_callback(a->controller, a->name, a->value, &v);
+        if (a->semantics && a->semantics->map_write) {
+                r = a->semantics->map_write(a->semantics, a->value, &v);
                 if (r < 0)
                         return r;
         }
@@ -66,6 +66,29 @@ int cgroup_attribute_apply_list(CGroupAttribute *first, CGroupBonding *b) {
         return r;
 }
 
+bool cgroup_attribute_matches(CGroupAttribute *a, const char *controller, const char *name) {
+        assert(a);
+
+        if (controller) {
+                if (streq(a->controller, controller) && (!name || streq(a->name, name)))
+                        return true;
+
+        } else if (!name)
+                return true;
+        else if (streq(a->name, name)) {
+                size_t x, y;
+                x = strlen(a->controller);
+                y = strlen(name);
+
+                if (y > x &&
+                    memcmp(a->controller, name, x) == 0 &&
+                    name[x] == '.')
+                        return true;
+        }
+
+        return false;
+}
+
 CGroupAttribute *cgroup_attribute_find_list(
                 CGroupAttribute *first,
                 const char *controller,
@@ -74,24 +97,9 @@ CGroupAttribute *cgroup_attribute_find_list(
 
         assert(name);
 
-        LIST_FOREACH(by_unit, a, first) {
-
-
-                if (controller) {
-                        if (streq(a->controller, controller) && streq(a->name, name))
-                                return a;
-
-                } else if (streq(a->name, name)) {
-                        size_t x, y;
-                        x = strlen(a->controller);
-                        y = strlen(name);
-
-                        if (y > x &&
-                            memcmp(a->controller, name, x) == 0 &&
-                            name[x] == '.')
-                                return a;
-                }
-        }
+        LIST_FOREACH(by_unit, a, first)
+                if (cgroup_attribute_matches(a, controller, name))
+                        return a;
 
         return NULL;
 }
@@ -114,3 +122,11 @@ void cgroup_attribute_free_list(CGroupAttribute *first) {
         LIST_FOREACH_SAFE(by_unit, a, n, first)
                 cgroup_attribute_free(a);
 }
+
+void cgroup_attribute_free_some(CGroupAttribute *first, const char *controller, const char *name) {
+        CGroupAttribute *a, *n;
+
+        LIST_FOREACH_SAFE(by_unit, a, n, first)
+                if (cgroup_attribute_matches(a, controller, name))
+                        cgroup_attribute_free(a);
+}
diff --git a/src/core/cgroup-attr.h b/src/core/cgroup-attr.h
index 0f5b854..0b54298 100644
--- a/src/core/cgroup-attr.h
+++ b/src/core/cgroup-attr.h
@@ -23,10 +23,9 @@
 
 typedef struct CGroupAttribute CGroupAttribute;
 
-typedef int (*CGroupAttributeMapCallback)(const char *controller, const char*name, const char *value, char **ret);
-
 #include "unit.h"
 #include "cgroup.h"
+#include "cgroup-semantics.h"
 
 struct CGroupAttribute {
         char *controller;
@@ -35,7 +34,7 @@ struct CGroupAttribute {
 
         Unit *unit;
 
-        CGroupAttributeMapCallback map_callback;
+        const CGroupSemantics *semantics;
 
         LIST_FIELDS(CGroupAttribute, by_unit);
 };
@@ -43,7 +42,9 @@ struct CGroupAttribute {
 int cgroup_attribute_apply(CGroupAttribute *a, CGroupBonding *b);
 int cgroup_attribute_apply_list(CGroupAttribute *first, CGroupBonding *b);
 
+bool cgroup_attribute_matches(CGroupAttribute *a, const char *controller, const char *name);
 CGroupAttribute *cgroup_attribute_find_list(CGroupAttribute *first, const char *controller, const char *name);
 
 void cgroup_attribute_free(CGroupAttribute *a);
 void cgroup_attribute_free_list(CGroupAttribute *first);
+void cgroup_attribute_free_some(CGroupAttribute *first, const char *controller, const char *name);
diff --git a/src/core/cgroup-semantics.c b/src/core/cgroup-semantics.c
new file mode 100644
index 0000000..82b02bb
--- /dev/null
+++ b/src/core/cgroup-semantics.c
@@ -0,0 +1,333 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "util.h"
+#include "strv.h"
+#include "path-util.h"
+#include "cgroup-util.h"
+
+#include "cgroup-semantics.h"
+
+static int parse_cpu_shares(const CGroupSemantics *s, const char *value, char **ret) {
+        unsigned long ul;
+
+        assert(s);
+        assert(value);
+        assert(ret);
+
+        if (safe_atolu(value, &ul) < 0 || ul < 1)
+                return -EINVAL;
+
+        if (asprintf(ret, "%lu", ul) < 0)
+                return -ENOMEM;
+
+        return 1;
+}
+
+static int parse_memory_limit(const CGroupSemantics *s, const char *value, char **ret) {
+        off_t sz;
+
+        assert(s);
+        assert(value);
+        assert(ret);
+
+        if (parse_bytes(value, &sz) < 0 || sz <= 0)
+                return -EINVAL;
+
+        if (asprintf(ret, "%llu", (unsigned long long) sz) < 0)
+                return -ENOMEM;
+
+        return 1;
+}
+
+static int parse_device(const CGroupSemantics *s, const char *value, char **ret) {
+        _cleanup_strv_free_ char **l = NULL;
+        char *x;
+        unsigned k;
+
+        assert(s);
+        assert(value);
+        assert(ret);
+
+        l = strv_split_quoted(value);
+        if (!l)
+                return -ENOMEM;
+
+        k = strv_length(l);
+        if (k < 1 || k > 2)
+                return -EINVAL;
+
+        if (!streq(l[0], "*") && !path_startswith(l[0], "/dev"))
+                return -EINVAL;
+
+        if (!isempty(l[1]) && !in_charset(l[1], "rwm"))
+                return -EINVAL;
+
+        x = strdup(value);
+        if (!x)
+                return -ENOMEM;
+
+        *ret = x;
+        return 1;
+}
+
+static int parse_blkio_weight(const CGroupSemantics *s, const char *value, char **ret) {
+        _cleanup_strv_free_ char **l = NULL;
+        unsigned long ul;
+
+        assert(s);
+        assert(value);
+        assert(ret);
+
+        l = strv_split_quoted(value);
+        if (!l)
+                return -ENOMEM;
+
+        if (strv_length(l) != 1)
+                return 0; /* Returning 0 will cause parse_blkio_weight_device() be tried instead */
+
+        if (safe_atolu(l[0], &ul) < 0 || ul < 10 || ul > 1000)
+                return -EINVAL;
+
+        if (asprintf(ret, "%lu", ul) < 0)
+                return -ENOMEM;
+
+        return 1;
+}
+
+static int parse_blkio_weight_device(const CGroupSemantics *s, const char *value, char **ret) {
+        _cleanup_strv_free_ char **l = NULL;
+        unsigned long ul;
+
+        assert(s);
+        assert(value);
+        assert(ret);
+
+        l = strv_split_quoted(value);
+        if (!l)
+                return -ENOMEM;
+
+        if (strv_length(l) != 2)
+                return -EINVAL;
+
+        if (!path_startswith(l[0], "/dev"))
+                return -EINVAL;
+
+        if (safe_atolu(l[1], &ul) < 0 || ul < 10 || ul > 1000)
+                return -EINVAL;
+
+        if (asprintf(ret, "%s %lu", l[0], ul) < 0)
+                return -ENOMEM;
+
+        return 1;
+}
+
+static int parse_blkio_bandwidth(const CGroupSemantics *s, const char *value, char **ret) {
+        _cleanup_strv_free_ char **l = NULL;
+        off_t bytes;
+
+        assert(s);
+        assert(value);
+        assert(ret);
+
+        l = strv_split_quoted(value);
+        if (!l)
+                return -ENOMEM;
+
+        if (strv_length(l) != 2)
+                return -EINVAL;
+
+        if (!path_startswith(l[0], "/dev")) {
+                return -EINVAL;
+        }
+
+        if (parse_bytes(l[1], &bytes) < 0 || bytes <= 0)
+                return -EINVAL;
+
+        if (asprintf(ret, "%s %llu", l[0], (unsigned long long) bytes) < 0)
+                return -ENOMEM;
+
+        return 0;
+}
+
+static int map_device(const CGroupSemantics *s, const char *value, char **ret) {
+        _cleanup_strv_free_ char **l = NULL;
+        unsigned k;
+
+        assert(s);
+        assert(value);
+        assert(ret);
+
+        l = strv_split_quoted(value);
+        if (!l)
+                return -ENOMEM;
+
+        k = strv_length(l);
+        if (k < 1 || k > 2)
+                return -EINVAL;
+
+        if (streq(l[0], "*")) {
+
+                if (asprintf(ret, "a *:*%s%s",
+                             isempty(l[1]) ? "" : " ", strempty(l[1])) < 0)
+                        return -ENOMEM;
+        } else {
+                struct stat st;
+
+                if (stat(l[0], &st) < 0) {
+                        log_warning("Couldn't stat device %s", l[0]);
+                        return -errno;
+                }
+
+                if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) {
+                        log_warning("%s is not a device.", l[0]);
+                        return -ENODEV;
+                }
+
+                if (asprintf(ret, "%c %u:%u%s%s",
+                             S_ISCHR(st.st_mode) ? 'c' : 'b',
+                             major(st.st_rdev), minor(st.st_rdev),
+                             isempty(l[1]) ? "" : " ", strempty(l[1])) < 0)
+                        return -ENOMEM;
+        }
+
+        return 0;
+}
+
+static int map_blkio(const CGroupSemantics *s, const char *value, char **ret) {
+        _cleanup_strv_free_ char **l = NULL;
+        struct stat st;
+        dev_t d;
+
+        assert(s);
+        assert(value);
+        assert(ret);
+
+        l = strv_split_quoted(value);
+        if (!l)
+                return log_oom();
+
+        if (strv_length(l) != 2)
+                return -EINVAL;
+
+        if (stat(l[0], &st) < 0) {
+                log_warning("Couldn't stat device %s", l[0]);
+                return -errno;
+        }
+
+        if (S_ISBLK(st.st_mode))
+                d = st.st_rdev;
+        else if (major(st.st_dev) != 0) {
+                /* If this is not a device node then find the block
+                 * device this file is stored on */
+                d = st.st_dev;
+
+                /* If this is a partition, try to get the originating
+                 * block device */
+                block_get_whole_disk(d, &d);
+        } else {
+                log_warning("%s is not a block device and file system block device cannot be determined or is not local.", l[0]);
+                return -ENODEV;
+        }
+
+        if (asprintf(ret, "%u:%u %s", major(d), minor(d), l[1]) < 0)
+                return -ENOMEM;
+
+        return 0;
+}
+
+static const CGroupSemantics semantics[] = {
+        { "cpu",     "cpu.shares",                 "CPUShare",              false, parse_cpu_shares,          NULL,       NULL },
+        { "memory",  "memory.soft_limit_in_bytes", "MemorySoftLimit",       false, parse_memory_limit,        NULL,       NULL },
+        { "memory",  "memory.limit_in_bytes",      "MemoryLimit",           false, parse_memory_limit,        NULL,       NULL },
+        { "devices", "devices.allow",              "DeviceAllow",           true,  parse_device,              map_device, NULL },
+        { "devices", "devices.deny",               "DeviceDeny",            true,  parse_device,              map_device, NULL },
+        { "blkio",   "blkio.weight",               "BlockIOWeight",         false, parse_blkio_weight,        NULL,       NULL },
+        { "blkio",   "blkio.weight_device",        "BlockIOWeight",         true,  parse_blkio_weight_device, map_blkio,  NULL },
+        { "blkio",   "blkio.read_bps_device",      "BlockIOReadBandwidth",  true,  parse_blkio_bandwidth,     map_blkio,  NULL },
+        { "blkio",   "blkio.write_bps_device",     "BlockIOWriteBandwidth", true,  parse_blkio_bandwidth,     map_blkio,  NULL }
+};
+
+int cgroup_semantics_find(
+                const char *controller,
+                const char *name,
+                const char *value,
+                char **ret,
+                const CGroupSemantics **_s) {
+
+        _cleanup_free_ char *c = NULL;
+        unsigned i;
+        int r;
+
+        assert(name);
+        assert(_s);
+        assert(!value == !ret);
+
+        if (!controller) {
+                r = cg_controller_from_attr(name, &c);
+                if (r < 0)
+                        return r;
+
+                controller = c;
+        }
+
+        for (i = 0; i < ELEMENTSOF(semantics); i++) {
+                const CGroupSemantics *s = semantics + i;
+                bool matches_name, matches_pretty;
+
+                if (controller && s->controller && !streq(s->controller, controller))
+                        continue;
+
+                matches_name = s->name && streq(s->name, name);
+                matches_pretty = s->pretty && streq(s->pretty, name);
+
+                if (!matches_name && !matches_pretty)
+                        continue;
+
+                if (value) {
+                        if (matches_pretty && s->map_pretty) {
+
+                                r = s->map_pretty(s, value, ret);
+                                if (r < 0)
+                                        return r;
+
+                                if (r == 0)
+                                        continue;
+
+                        } else {
+                                char *x;
+
+                                x = strdup(value);
+                                if (!x)
+                                        return -ENOMEM;
+
+                                *ret = x;
+                        }
+                }
+
+                *_s = s;
+                return 1;
+        }
+
+        *ret = NULL;
+        *_s = NULL;
+        return 0;
+}
diff --git a/src/core/cgroup-semantics.h b/src/core/cgroup-semantics.h
new file mode 100644
index 0000000..4f848f4
--- /dev/null
+++ b/src/core/cgroup-semantics.h
@@ -0,0 +1,43 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+  This file is part of systemd.
+
+  Copyright 2011 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct CGroupSemantics CGroupSemantics;
+
+struct CGroupSemantics {
+        const char *controller;
+        const char *name;
+        const char *pretty;
+
+        bool multiple;
+
+        /* This call is used for parsing the pretty value to the actual attribute value */
+        int (*map_pretty)(const CGroupSemantics *semantics, const char *value, char **ret);
+
+        /* Right before writing this attribute the attribute value is converted to a low-level value */
+        int (*map_write)(const CGroupSemantics *semantics, const char *value, char **ret);
+
+        /* If this attribute takes a list, this call can be used to reset the list to empty */
+        int (*reset)(const CGroupSemantics *semantics, const char *group);
+};
+
+int cgroup_semantics_find(const char *controller, const char *name, const char *value, char **ret, const CGroupSemantics **semantics);
diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c
index de23369..8f4bbc5 100644
--- a/src/core/dbus-manager.c
+++ b/src/core/dbus-manager.c
@@ -103,30 +103,31 @@
         "  <method name=\"ResetFailedUnit\">\n"                         \
         "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
         "  </method>\n"                                                 \
-        "  <method name=\"GetUnitControlGroupAttributes\">\n"           \
+        "  <method name=\"SetUnitControlGroup\">\n"                     \
         "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
-        "   <arg name=\"attributes\" type=\"as\" direction=\"in\"/>\n"  \
-        "   <arg name=\"values\" type=\"as\" direction=\"out\"/>\n"      \
+        "   <arg name=\"group\" type=\"s\" direction=\"in\"/>\n"        \
+        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
         "  </method>\n"                                                 \
-        "  <method name=\"SetUnitControlGroupAttributes\">\n"           \
+        "  <method name=\"UnsetUnitControlGroup\">\n"                   \
         "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
-        "   <arg name=\"attributes\" type=\"a(sss)\" direction=\"in\"/>\n" \
+        "   <arg name=\"group\" type=\"s\" direction=\"in\"/>\n"        \
         "   <arg name=\"mode\" type=\"s\" direction=\"in\"\n/>"         \
         "  </method>\n"                                                 \
-        "  <method name=\"UnsetUnitControlGroupAttributes\">\n"         \
+        "  <method name=\"GetUnitControlGroupAttribute\">\n"            \
         "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
-        "   <arg name=\"attributes\" type=\"a(ss)\" direction=\"in\"/>\n" \
-        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"attribute\" type=\"s\" direction=\"in\"/>\n"    \
+        "   <arg name=\"values\" type=\"as\" direction=\"out\"/>\n"     \
         "  </method>\n"                                                 \
-        "  <method name=\"SetUnitControlGroups\">\n"                    \
+        "  <method name=\"SetUnitControlGroupAttribute\">\n"            \
         "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
-        "   <arg name=\"groups\" type=\"as\" direction=\"in\"/>\n"      \
-        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"attribute\" type=\"s\" direction=\"in\"/>\n"    \
+        "   <arg name=\"values\" type=\"as\" direction=\"in\"/>\n"      \
+        "   <arg name=\"mode\" type=\"s\" direction=\"in\"\n/>"         \
         "  </method>\n"                                                 \
-        "  <method name=\"UnsetUnitControlGroups\">\n"                  \
+        "  <method name=\"UnsetUnitControlGroupAttributes\">\n"         \
         "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
-        "   <arg name=\"groups\" type=\"as\" direction=\"in\"/>\n"      \
-        "   <arg name=\"mode\" type=\"s\" direction=\"in\"\n/>"         \
+        "   <arg name=\"attribute\" type=\"s\" direction=\"in\"/>\n"    \
+        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
         "  </method>\n"                                                 \
         "  <method name=\"GetJob\">\n"                                  \
         "   <arg name=\"id\" type=\"u\" direction=\"in\"/>\n"           \
@@ -874,7 +875,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
                 if (!reply)
                         goto oom;
 
-        } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "SetUnitControlGroups")) {
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "SetUnitControlGroup")) {
                 const char *name;
                 Unit *u;
                 DBusMessageIter iter;
@@ -902,7 +903,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
                 if (!reply)
                         goto oom;
 
-        } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "UnsetUnitControlGroups")) {
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "UnsetUnitControlGroup")) {
                 const char *name;
                 Unit *u;
                 DBusMessageIter iter;
@@ -930,7 +931,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
                 if (!reply)
                         goto oom;
 
-        } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "SetUnitControlGroupAttributes")) {
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "SetUnitControlGroupAttribute")) {
                 const char *name;
                 Unit *u;
                 DBusMessageIter iter;
@@ -949,6 +950,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
                 }
 
                 SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "start");
+
                 r = bus_unit_cgroup_attribute_set(u, &iter);
                 if (r < 0)
                         return bus_send_error_reply(connection, message, NULL, r);
@@ -957,7 +959,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
                 if (!reply)
                         goto oom;
 
-        } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "UnsetUnitControlGroupAttributes")) {
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "UnsetUnitControlGroupAttribute")) {
                 const char *name;
                 Unit *u;
                 DBusMessageIter iter;
@@ -985,7 +987,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
                 if (!reply)
                         goto oom;
 
-        } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "GetUnitControlGroupAttributes")) {
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "GetUnitControlGroupAttribute")) {
                 const char *name;
                 Unit *u;
                 DBusMessageIter iter;
@@ -1005,6 +1007,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
                 }
 
                 SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "status");
+
                 r = bus_unit_cgroup_attribute_get(u, &iter, &list);
                 if (r < 0)
                         return bus_send_error_reply(connection, message, NULL, r);
diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c
index 4f968c2..7c23e1e 100644
--- a/src/core/dbus-unit.c
+++ b/src/core/dbus-unit.c
@@ -344,8 +344,8 @@ static int bus_unit_append_cgroup_attrs(DBusMessageIter *i, const char *property
                 char _cleanup_free_ *v = NULL;
                 bool success;
 
-                if (a->map_callback)
-                        a->map_callback(a->controller, a->name, a->value, &v);
+                if (a->semantics && a->semantics->map_write)
+                        a->semantics->map_write(a->semantics, a->value, &v);
 
                 success =
                         dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) &&
@@ -472,7 +472,7 @@ static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *conn
                 if (!reply)
                         goto oom;
 
-        } else if (streq_ptr(dbus_message_get_member(message), "SetControlGroups")) {
+        } else if (streq_ptr(dbus_message_get_member(message), "SetControlGroup")) {
                 DBusMessageIter iter;
 
                 SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "start");
@@ -488,7 +488,7 @@ static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *conn
                 if (!reply)
                         goto oom;
 
-        } else if (streq_ptr(dbus_message_get_member(message), "UnsetControlGroups")) {
+        } else if (streq_ptr(dbus_message_get_member(message), "UnsetControlGroup")) {
                 DBusMessageIter iter;
 
                 SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "stop");
@@ -496,14 +496,14 @@ static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *conn
                 if (!dbus_message_iter_init(message, &iter))
                         goto oom;
 
-                r = bus_unit_cgroup_set(u, &iter);
+                r = bus_unit_cgroup_unset(u, &iter);
                 if (r < 0)
                         return bus_send_error_reply(connection, message, NULL, r);
 
                 reply = dbus_message_new_method_return(message);
                 if (!reply)
                         goto oom;
-        } else if (streq_ptr(dbus_message_get_member(message), "GetControlGroupAttributes")) {
+        } else if (streq_ptr(dbus_message_get_member(message), "GetControlGroupAttribute")) {
                 DBusMessageIter iter;
                 _cleanup_strv_free_ char **list = NULL;
 
@@ -524,7 +524,7 @@ static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *conn
                 if (bus_append_strv_iter(&iter, list) < 0)
                         goto oom;
 
-        } else if (streq_ptr(dbus_message_get_member(message), "SetControlGroupAttributes")) {
+        } else if (streq_ptr(dbus_message_get_member(message), "SetControlGroupAttribute")) {
                 DBusMessageIter iter;
 
                 SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "start");
@@ -540,7 +540,7 @@ static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *conn
                 if (!reply)
                         goto oom;
 
-        } else if (streq_ptr(dbus_message_get_member(message), "UnsetControlGroupAttributes")) {
+        } else if (streq_ptr(dbus_message_get_member(message), "UnsetControlGroupAttribute")) {
                 DBusMessageIter iter;
 
                 SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "stop");
@@ -897,17 +897,17 @@ oom:
         return DBUS_HANDLER_RESULT_NEED_MEMORY;
 }
 
-static int next_and_parse_mode(DBusMessageIter *iter, bool *runtime) {
+static int parse_mode(DBusMessageIter *iter, bool *runtime, bool next) {
         const char *mode;
+        int r;
 
         assert(iter);
         assert(runtime);
 
-        dbus_message_iter_next(iter);
-        if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING)
-                return -EINVAL;
+        r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &mode, next);
+        if (r < 0)
+                return r;
 
-        dbus_message_iter_get_basic(iter, &mode);
         if (streq(mode, "runtime"))
                 *runtime = true;
         else if (streq(mode, "persistent"))
@@ -919,10 +919,11 @@ static int next_and_parse_mode(DBusMessageIter *iter, bool *runtime) {
 }
 
 int bus_unit_cgroup_set(Unit *u, DBusMessageIter *iter) {
-        int r;
-        _cleanup_strv_free_ char **a = NULL;
-        char **name;
+        _cleanup_free_ char *controller = NULL, *old_path = NULL, *new_path = NULL, *contents = NULL;
+        const char *name;
+        CGroupBonding *b;
         bool runtime;
+        int r;
 
         assert(u);
         assert(iter);
@@ -930,60 +931,74 @@ int bus_unit_cgroup_set(Unit *u, DBusMessageIter *iter) {
         if (!unit_get_exec_context(u))
                 return -EINVAL;
 
-        r = bus_parse_strv_iter(iter, &a);
+        r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &name, true);
         if (r < 0)
                 return r;
 
-        r = next_and_parse_mode(iter, &runtime);
+        r = parse_mode(iter, &runtime, false);
         if (r < 0)
                 return r;
 
-        STRV_FOREACH(name, a) {
-                _cleanup_free_ char *controller = NULL, *old_path = NULL, *new_path = NULL, *contents = NULL;
-                CGroupBonding *b;
-
-                r = cg_split_spec(*name, &controller, &new_path);
-                if (r < 0)
-                        return r;
+        r = cg_split_spec(name, &controller, &new_path);
+        if (r < 0)
+                return r;
 
-                b = cgroup_bonding_find_list(u->cgroup_bondings, controller);
-                if (b) {
-                        old_path = strdup(b->path);
-                        if (!old_path)
-                                return -ENOMEM;
-                }
+        if (!new_path) {
+                new_path = unit_default_cgroup_path(u);
+                if (!new_path)
+                        return -ENOMEM;
+        }
 
-                r = unit_add_cgroup_from_text(u, *name, true, &b);
-                if (r < 0)
-                        return r;
+        if (!controller || streq(controller, SYSTEMD_CGROUP_CONTROLLER))
+                return -EINVAL;
 
-                if (r > 0) {
-                        /* Try to move things to the new place, and clean up the old place */
-                        cgroup_bonding_realize(b);
-                        cgroup_bonding_migrate(b, u->cgroup_bondings);
+        b = cgroup_bonding_find_list(u->cgroup_bondings, controller);
+        if (b) {
+                if (streq(b->path, new_path))
+                        return 0;
 
-                        if (old_path)
-                                cg_trim(controller, old_path, true);
-                }
+                if (b->essential)
+                        return -EINVAL;
 
-                contents = strjoin("[", UNIT_VTABLE(u)->exec_section, "]\n"
-                                   "ControlGroup=", *name, "\n", NULL);
-                if (!contents)
+                old_path = strdup(b->path);
+                if (!old_path)
                         return -ENOMEM;
+        }
 
-                r = unit_write_drop_in(u, runtime, *name, contents);
-                if (r < 0)
-                        return r;
+        r = unit_add_cgroup_from_text(u, name, true, &b);
+        if (r < 0)
+                return r;
+        if (r > 0) {
+                CGroupAttribute *a;
+
+                /* Try to move things to the new place, and clean up the old place */
+                cgroup_bonding_realize(b);
+                cgroup_bonding_migrate(b, u->cgroup_bondings);
+
+                if (old_path)
+                        cg_trim(controller, old_path, true);
+
+                /* Apply the attributes to the new group */
+                LIST_FOREACH(by_unit, a, u->cgroup_attributes)
+                        if (streq(a->controller, controller))
+                                cgroup_attribute_apply(a, b);
         }
 
-        return 0;
+        contents = strjoin("[", UNIT_VTABLE(u)->exec_section, "]\n"
+                           "ControlGroup=", name, "\n", NULL);
+        if (!contents)
+                return -ENOMEM;
+
+        return unit_write_drop_in(u, runtime, controller, contents);
 }
 
 int bus_unit_cgroup_unset(Unit *u, DBusMessageIter *iter) {
-        _cleanup_strv_free_ char **a = NULL;
-        char **name;
-        int r;
+        _cleanup_free_ char *controller = NULL, *path = NULL, *target = NULL;
+        const char *name;
+        CGroupAttribute *a, *n;
+        CGroupBonding *b;
         bool runtime;
+        int r;
 
         assert(u);
         assert(iter);
@@ -991,50 +1006,57 @@ int bus_unit_cgroup_unset(Unit *u, DBusMessageIter *iter) {
         if (!unit_get_exec_context(u))
                 return -EINVAL;
 
-        r = bus_parse_strv_iter(iter, &a);
+        r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &name, true);
         if (r < 0)
                 return r;
 
-        r = next_and_parse_mode(iter, &runtime);
+        r = parse_mode(iter, &runtime, false);
         if (r < 0)
                 return r;
 
-        STRV_FOREACH(name, a) {
-                _cleanup_free_ char *controller = NULL, *path = NULL, *target = NULL;
-                CGroupBonding *b;
+        r = cg_split_spec(name, &controller, &path);
+        if (r < 0)
+                return r;
 
-                r = cg_split_spec(*name, &controller, &path);
-                if (r < 0)
-                        return r;
+        if (!controller || streq(controller, SYSTEMD_CGROUP_CONTROLLER))
+                return -EINVAL;
 
-                if (!controller || streq(controller, SYSTEMD_CGROUP_CONTROLLER))
-                        return -EINVAL;
+        b = cgroup_bonding_find_list(u->cgroup_bondings, controller);
+        if (!b)
+                return -ENOENT;
 
-                unit_remove_drop_in(u, runtime, *name);
+        if (path && !path_equal(path, b->path))
+                return -ENOENT;
 
-                b = cgroup_bonding_find_list(u->cgroup_bondings, controller);
-                if (!b)
-                        continue;
+        if (b->essential)
+                return -EINVAL;
 
-                if (path && !path_equal(path, b->path))
-                        continue;
+        unit_remove_drop_in(u, runtime, controller);
 
-                if (b->essential)
-                        return -EINVAL;
+        /* Try to migrate the old group away */
+        if (cg_get_by_pid(controller, 0, &target) >= 0)
+                cgroup_bonding_migrate_to(u->cgroup_bondings, target, false);
+
+        cgroup_bonding_free(b, true);
 
-                /* Try to migrate the old group away */
-                if (cg_get_by_pid(controller, 0, &target) >= 0)
-                        cgroup_bonding_migrate_to(u->cgroup_bondings, target, false);
+        /* Drop all attributes of this controller */
+        LIST_FOREACH_SAFE(by_unit, a, n, u->cgroup_attributes) {
+                if (!streq(a->controller, controller))
+                        continue;
 
-                cgroup_bonding_free(b, true);
+                unit_remove_drop_in(u, runtime, a->name);
+                cgroup_attribute_free(a);
         }
 
         return 0;
 }
 
 int bus_unit_cgroup_attribute_get(Unit *u, DBusMessageIter *iter, char ***_result) {
-        _cleanup_strv_free_ char **l = NULL, **result = NULL;
-        char **name;
+        _cleanup_free_ char *controller = NULL;
+        CGroupAttribute *a;
+        CGroupBonding *b;
+        const char *name;
+        char **l = NULL;
         int r;
 
         assert(u);
@@ -1044,63 +1066,99 @@ int bus_unit_cgroup_attribute_get(Unit *u, DBusMessageIter *iter, char ***_resul
         if (!unit_get_exec_context(u))
                 return -EINVAL;
 
-        r = bus_parse_strv_iter(iter, &l);
+        r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &name, false);
         if (r < 0)
                 return r;
 
-        STRV_FOREACH(name, l) {
-                _cleanup_free_ char *controller = NULL;
-                CGroupAttribute *a;
-                CGroupBonding *b;
+        r = cg_controller_from_attr(name, &controller);
+        if (r < 0)
+                return r;
+
+        /* First attempt, read the value from the kernel */
+        b = cgroup_bonding_find_list(u->cgroup_bondings, controller);
+        if (b) {
+                _cleanup_free_ char *p = NULL, *v = NULL;
 
-                r = cg_controller_from_attr(*name, &controller);
+                r = cg_get_path(b->controller, b->path, name, &p);
                 if (r < 0)
                         return r;
 
-                /* First attempt, read the value from the kernel */
-                b = cgroup_bonding_find_list(u->cgroup_bondings, controller);
-                if (b) {
-                        _cleanup_free_ char *p = NULL, *v = NULL;
+                r = read_full_file(p, &v, NULL);
+                if (r >= 0) {
+                        /* Split on new lines */
+                        l = strv_split_newlines(v);
+                        if (!l)
+                                return -ENOMEM;
 
-                        r = cg_get_path(b->controller, b->path, *name, &p);
-                        if (r < 0)
-                                return r;
+                        *_result = l;
+                        return 0;
 
-                        r = read_full_file(p, &v, NULL);
-                        if (r >= 0) {
-                                r = strv_extend(&result, v);
-                                if (r < 0)
-                                        return r;
-
-                                continue;
-                        } else if (r != -ENOENT)
-                                return r;
                 }
+        }
 
-                /* If that didn't work, read our cached value */
-                a = cgroup_attribute_find_list(u->cgroup_attributes, NULL, *name);
-                if (a) {
-                        r = strv_extend(&result, a->value);
-                        if (r < 0)
-                                return r;
+        /* If that didn't work, read our cached value */
+        LIST_FOREACH(by_unit, a, u->cgroup_attributes) {
 
+                if (!cgroup_attribute_matches(a, controller, name))
                         continue;
-                }
 
-                return -ENOENT;
+                r = strv_extend(&l, a->value);
+                if (r < 0) {
+                        strv_free(l);
+                        return r;
+                }
         }
 
-        *_result = result;
-        result = NULL;
+        if (!l)
+                return -ENOENT;
 
+        *_result = l;
         return 0;
 }
 
+static int update_attribute_drop_in(Unit *u, bool runtime, const char *name) {
+        _cleanup_free_ char *buf = NULL;
+        CGroupAttribute *a;
+
+        assert(u);
+        assert(name);
+
+        LIST_FOREACH(by_unit, a, u->cgroup_attributes) {
+                if (!cgroup_attribute_matches(a, NULL, name))
+                        continue;
+
+                if (!buf) {
+                        buf = strjoin("[", UNIT_VTABLE(u)->exec_section, "]\n"
+                                      "ControlGroupAttribute=", a->name, " ", a->value, "\n", NULL);
+
+                        if (!buf)
+                                return -ENOMEM;
+                } else {
+                        char *b;
+
+                        b = strjoin(buf,
+                                    "ControlGroupAttribute=", a->name, " ", a->value, "\n", NULL);
+
+                        if (!b)
+                                return -ENOMEM;
+
+                        free(buf);
+                        buf = b;
+                }
+        }
+
+        if (buf)
+                return unit_write_drop_in(u, runtime, name, buf);
+        else
+                return unit_remove_drop_in(u, runtime, name);
+}
+
 int bus_unit_cgroup_attribute_set(Unit *u, DBusMessageIter *iter) {
         _cleanup_strv_free_ char **l = NULL;
         int r;
         bool runtime = false;
-        char **name, **value;
+        char **value;
+        const char *name;
 
         assert(u);
         assert(iter);
@@ -1108,19 +1166,34 @@ int bus_unit_cgroup_attribute_set(Unit *u, DBusMessageIter *iter) {
         if (!unit_get_exec_context(u))
                 return -EINVAL;
 
-        r = bus_parse_strv_pairs_iter(iter, &l);
+        r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &name, true);
+        if (r < 0)
+                return r;
+
+        r = bus_parse_strv_iter(iter, &l);
         if (r < 0)
                 return r;
 
-        r = next_and_parse_mode(iter, &runtime);
+        if (!dbus_message_iter_next(iter))
+                return -EINVAL;
+
+        r = parse_mode(iter, &runtime, false);
         if (r < 0)
                 return r;
 
-        STRV_FOREACH_PAIR(name, value, l) {
-                _cleanup_free_ char *contents = NULL;
+        STRV_FOREACH(value, l) {
+                _cleanup_free_ char *v = NULL;
                 CGroupAttribute *a;
+                const CGroupSemantics *s;
+
+                r = cgroup_semantics_find(NULL, name, *value, &v, &s);
+                if (r < 0)
+                        return r;
+
+                if (s && !s->multiple && l[1])
+                        return -EINVAL;
 
-                r = unit_add_cgroup_attribute(u, NULL, *name, *value, NULL, &a);
+                r = unit_add_cgroup_attribute(u, s, NULL, name, v ? v : *value, &a);
                 if (r < 0)
                         return r;
 
@@ -1144,22 +1217,17 @@ int bus_unit_cgroup_attribute_set(Unit *u, DBusMessageIter *iter) {
                         cgroup_attribute_apply(a, u->cgroup_bondings);
                 }
 
-                contents = strjoin("[", UNIT_VTABLE(u)->exec_section, "]\n"
-                                   "ControlGroupAttribute=", *name, " ", *value, "\n", NULL);
-                if (!contents)
-                        return -ENOMEM;
-
-                r = unit_write_drop_in(u, runtime, *name, contents);
-                if (r < 0)
-                        return r;
         }
 
+        r = update_attribute_drop_in(u, runtime, name);
+        if (r < 0)
+                return r;
+
         return 0;
 }
 
 int bus_unit_cgroup_attribute_unset(Unit *u, DBusMessageIter *iter) {
-        _cleanup_strv_free_ char **l = NULL;
-        char **name;
+        const char *name;
         bool runtime;
         int r;
 
@@ -1169,23 +1237,16 @@ int bus_unit_cgroup_attribute_unset(Unit *u, DBusMessageIter *iter) {
         if (!unit_get_exec_context(u))
                 return -EINVAL;
 
-        r = bus_parse_strv_iter(iter, &l);
+        r = bus_iter_get_basic_and_next(iter, DBUS_TYPE_STRING, &name, true);
         if (r < 0)
                 return r;
 
-        r = next_and_parse_mode(iter, &runtime);
+        r = parse_mode(iter, &runtime, false);
         if (r < 0)
                 return r;
 
-        STRV_FOREACH(name, l) {
-                CGroupAttribute *a;
-
-                a = cgroup_attribute_find_list(u->cgroup_attributes, NULL, *name);
-                if (a)
-                        cgroup_attribute_free(a);
-
-                unit_remove_drop_in(u, runtime, *name);
-        }
+        cgroup_attribute_free_some(u->cgroup_attributes, NULL, name);
+        update_attribute_drop_in(u, runtime, name);
 
         return 0;
 }
diff --git a/src/core/dbus-unit.h b/src/core/dbus-unit.h
index c8903f8..34980b0 100644
--- a/src/core/dbus-unit.h
+++ b/src/core/dbus-unit.h
@@ -127,24 +127,25 @@
         "  <property name=\"DefaultControlGroup\" type=\"s\" access=\"read\"/>\n" \
         "  <property name=\"ControlGroups\" type=\"as\" access=\"read\"/>\n" \
         "  <property name=\"ControlGroupAttributes\" type=\"a(sss)\" access=\"read\"/>\n" \
-        "  <method name=\"GetControlGroupAttributes\">\n"               \
-        "   <arg name=\"attributes\" type=\"as\" direction=\"in\"/>\n"  \
-        "   <arg name=\"values\" type=\"as\" direction=\"out\"/>\n"     \
-        "  </method>\n"                                                 \
-        "  <method name=\"SetControlGroupAttributes\">\n"               \
-        "   <arg name=\"attributes\" type=\"a(ss)\" direction=\"in\"/>\n" \
+        "  <method name=\"SetControlGroup\">\n"                         \
+        "   <arg name=\"group\" type=\"s\" direction=\"in\"/>\n"        \
         "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
         "  </method>\n"                                                 \
-        "  <method name=\"UnsetControlGroupAttributes\">\n"             \
-        "   <arg name=\"attributes\" type=\"as\" direction=\"in\"/>\n"  \
+        "  <method name=\"UnsetControlGroup\">\n"                       \
+        "   <arg name=\"group\" type=\"s\" direction=\"in\"/>\n"        \
         "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
         "  </method>\n"                                                 \
-        "  <method name=\"SetControlGroups\">\n"                        \
-        "   <arg name=\"groups\" type=\"as\" direction=\"in\"/>\n"      \
+        "  <method name=\"GetControlGroupAttribute\">\n"                \
+        "   <arg name=\"attribute\" type=\"s\" direction=\"in\"/>\n"    \
+        "   <arg name=\"values\" type=\"as\" direction=\"out\"/>\n"     \
+        "  </method>\n"                                                 \
+        "  <method name=\"SetControlGroupAttribute\">\n"                \
+        "   <arg name=\"attribute\" type=\"s\" direction=\"in\"/>\n"    \
+        "   <arg name=\"values\" type=\"as\" direction=\"in\"/>\n"      \
         "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
         "  </method>\n"                                                 \
-        "  <method name=\"UnsetControlGroups\">\n"                      \
-        "   <arg name=\"groups\" type=\"as\" direction=\"in\"/>\n"      \
+        "  <method name=\"UnsetControlGroupAttribute\">\n"              \
+        "   <arg name=\"attribute\" type=\"s\" direction=\"in\"/>\n"    \
         "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
         "  </method>\n"
 
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
index 0b6a5cc..22c1761 100644
--- a/src/core/load-fragment-gperf.gperf.m4
+++ b/src/core/load-fragment-gperf.gperf.m4
@@ -68,14 +68,14 @@ $1.LimitRTPRIO,                  config_parse_limit,                 RLIMIT_RTPR
 $1.LimitRTTIME,                  config_parse_limit,                 RLIMIT_RTTIME,                 offsetof($1, exec_context.rlimit)
 $1.ControlGroup,                 config_parse_unit_cgroup,           0,                             0
 $1.ControlGroupAttribute,        config_parse_unit_cgroup_attr,      0,                             0
-$1.CPUShares,                    config_parse_unit_cpu_shares,       0,                             0
-$1.MemoryLimit,                  config_parse_unit_memory_limit,     0,                             0
-$1.MemorySoftLimit,              config_parse_unit_memory_limit,     0,                             0
-$1.DeviceAllow,                  config_parse_unit_device_allow,     0,                             0
-$1.DeviceDeny,                   config_parse_unit_device_allow,     0,                             0
-$1.BlockIOWeight,                config_parse_unit_blkio_weight,     0,                             0
-$1.BlockIOReadBandwidth,         config_parse_unit_blkio_bandwidth,  0,                             0
-$1.BlockIOWriteBandwidth,        config_parse_unit_blkio_bandwidth,  0,                             0
+$1.CPUShares,                    config_parse_unit_cgroup_attr_pretty, 0,                           0
+$1.MemoryLimit,                  config_parse_unit_cgroup_attr_pretty, 0,                           0
+$1.MemorySoftLimit,              config_parse_unit_cgroup_attr_pretty, 0,                           0
+$1.DeviceAllow,                  config_parse_unit_cgroup_attr_pretty, 0,                           0
+$1.DeviceDeny,                   config_parse_unit_cgroup_attr_pretty, 0,                           0
+$1.BlockIOWeight,                config_parse_unit_cgroup_attr_pretty, 0,                           0
+$1.BlockIOReadBandwidth,         config_parse_unit_cgroup_attr_pretty, 0,                           0
+$1.BlockIOWriteBandwidth,        config_parse_unit_cgroup_attr_pretty, 0,                           0
 $1.ReadWriteDirectories,         config_parse_path_strv,             0,                             offsetof($1, exec_context.read_write_dirs)
 $1.ReadOnlyDirectories,          config_parse_path_strv,             0,                             offsetof($1, exec_context.read_only_dirs)
 $1.InaccessibleDirectories,      config_parse_path_strv,             0,                             offsetof($1, exec_context.inaccessible_dirs)
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index 6e333aa..d79e1d9 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -1769,7 +1769,9 @@ int config_parse_unit_cgroup_attr(
                 void *userdata) {
 
         Unit *u = data;
-        _cleanup_strv_free_ char **l = NULL;
+        size_t a, b;
+        _cleanup_free_ char *n = NULL, *v = NULL;
+        const CGroupSemantics *s;
         int r;
 
         assert(filename);
@@ -1784,160 +1786,24 @@ int config_parse_unit_cgroup_attr(
                 return 0;
         }
 
-        l = strv_split_quoted(rvalue);
-        if (!l)
-                return log_oom();
-
-        if (strv_length(l) != 2) {
+        a = strcspn(rvalue, WHITESPACE);
+        b = strspn(rvalue + a, WHITESPACE);
+        if (a <= 0 || b <= 0) {
                 log_error("[%s:%u] Failed to parse cgroup attribute value, ignoring: %s", filename, line, rvalue);
                 return 0;
         }
 
-        r = unit_add_cgroup_attribute(u, NULL, l[0], l[1], NULL, NULL);
-        if (r < 0) {
-                log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue);
-                return 0;
-        }
-
-        return 0;
-}
-
-int config_parse_unit_cpu_shares(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) {
-        Unit *u = data;
-        int r;
-        unsigned long ul;
-        _cleanup_free_ char *t = NULL;
-
-        assert(filename);
-        assert(lvalue);
-        assert(rvalue);
-        assert(data);
-
-        if (safe_atolu(rvalue, &ul) < 0 || ul < 1) {
-                log_error("[%s:%u] Failed to parse CPU shares value, ignoring: %s", filename, line, rvalue);
-                return 0;
-        }
-
-        if (asprintf(&t, "%lu", ul) < 0)
-                return log_oom();
-
-        r = unit_add_cgroup_attribute(u, "cpu", "cpu.shares", t, NULL, NULL);
-        if (r < 0) {
-                log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue);
-                return 0;
-        }
-
-        return 0;
-}
-
-int config_parse_unit_memory_limit(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) {
-        Unit *u = data;
-        int r;
-        off_t sz;
-        _cleanup_free_ char *t = NULL;
-
-        assert(filename);
-        assert(lvalue);
-        assert(rvalue);
-        assert(data);
-
-        if (parse_bytes(rvalue, &sz) < 0 || sz <= 0) {
-                log_error("[%s:%u] Failed to parse memory limit value, ignoring: %s", filename, line, rvalue);
-                return 0;
-        }
-
-        if (asprintf(&t, "%llu", (unsigned long long) sz) < 0)
+        n = strndup(rvalue, a);
+        if (!n)
                 return log_oom();
 
-        r = unit_add_cgroup_attribute(u,
-                                      "memory",
-                                      streq(lvalue, "MemorySoftLimit") ? "memory.soft_limit_in_bytes" : "memory.limit_in_bytes",
-                                      t, NULL, NULL);
+        r = cgroup_semantics_find(NULL, n, rvalue + a + b, &v, &s);
         if (r < 0) {
-                log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue);
-                return 0;
-        }
-
-        return 0;
-}
-
-static int device_map(const char *controller, const char *name, const char *value, char **ret) {
-        _cleanup_strv_free_ char **l = NULL;
-
-        assert(controller);
-        assert(name);
-        assert(value);
-        assert(ret);
-
-        l = strv_split_quoted(value);
-        if (!l)
-                return -ENOMEM;
-
-        assert(strv_length(l) >= 1);
-
-        if (streq(l[0], "*")) {
-
-                if (asprintf(ret, "a *:*%s%s",
-                             isempty(l[1]) ? "" : " ", strempty(l[1])) < 0)
-                        return -ENOMEM;
-        } else {
-                struct stat st;
-
-                if (stat(l[0], &st) < 0) {
-                        log_warning("Couldn't stat device %s", l[0]);
-                        return -errno;
-                }
-
-                if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) {
-                        log_warning("%s is not a device.", l[0]);
-                        return -ENODEV;
-                }
-
-                if (asprintf(ret, "%c %u:%u%s%s",
-                             S_ISCHR(st.st_mode) ? 'c' : 'b',
-                             major(st.st_rdev), minor(st.st_rdev),
-                             isempty(l[1]) ? "" : " ", strempty(l[1])) < 0)
-                        return -ENOMEM;
-        }
-
-        return 0;
-}
-
-int config_parse_unit_device_allow(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) {
-        Unit *u = data;
-        _cleanup_strv_free_ char **l = NULL;
-        int r;
-        unsigned k;
-
-        assert(filename);
-        assert(lvalue);
-        assert(rvalue);
-        assert(data);
-
-        l = strv_split_quoted(rvalue);
-        if (!l)
-                return log_oom();
-
-        k = strv_length(l);
-        if (k < 1 || k > 2) {
-                log_error("[%s:%u] Failed to parse device value, ignoring: %s", filename, line, rvalue);
-                return 0;
-        }
-
-        if (!streq(l[0], "*") && !path_startswith(l[0], "/dev")) {
-                log_error("[%s:%u] Device node path not absolute, ignoring: %s", filename, line, rvalue);
-                return 0;
-        }
-
-        if (!isempty(l[1]) && !in_charset(l[1], "rwm")) {
-                log_error("[%s:%u] Device access string invalid, ignoring: %s", filename, line, rvalue);
+                log_error("[%s:%u] Failed to parse cgroup attribute value, ignoring: %s", filename, line, rvalue);
                 return 0;
         }
 
-        r = unit_add_cgroup_attribute(u, "devices",
-                                      streq(lvalue, "DeviceAllow") ? "devices.allow" : "devices.deny",
-                                      rvalue, device_map, NULL);
-
+        r = unit_add_cgroup_attribute(u, s, NULL, n, v ? v : rvalue + a + b, NULL);
         if (r < 0) {
                 log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue);
                 return 0;
@@ -1946,148 +1812,36 @@ int config_parse_unit_device_allow(const char *filename, unsigned line, const ch
         return 0;
 }
 
-static int blkio_map(const char *controller, const char *name, const char *value, char **ret) {
-        struct stat st;
-        _cleanup_strv_free_ char **l = NULL;
-        dev_t d;
-
-        assert(controller);
-        assert(name);
-        assert(value);
-        assert(ret);
-
-        l = strv_split_quoted(value);
-        if (!l)
-                return log_oom();
-
-        assert(strv_length(l) == 2);
-
-        if (stat(l[0], &st) < 0) {
-                log_warning("Couldn't stat device %s", l[0]);
-                return -errno;
-        }
-
-        if (S_ISBLK(st.st_mode))
-                d = st.st_rdev;
-        else if (major(st.st_dev) != 0) {
-                /* If this is not a device node then find the block
-                 * device this file is stored on */
-                d = st.st_dev;
-
-                /* If this is a partition, try to get the originating
-                 * block device */
-                block_get_whole_disk(d, &d);
-        } else {
-                log_warning("%s is not a block device and file system block device cannot be determined or is not local.", l[0]);
-                return -ENODEV;
-        }
-
-        if (asprintf(ret, "%u:%u %s", major(d), minor(d), l[1]) < 0)
-                return -ENOMEM;
-
-        return 0;
-}
+int config_parse_unit_cgroup_attr_pretty(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
 
-int config_parse_unit_blkio_weight(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) {
         Unit *u = data;
+        _cleanup_free_ char *v = NULL;
+        const CGroupSemantics *s;
         int r;
-        unsigned long ul;
-        const char *device = NULL, *weight;
-        unsigned k;
-        _cleanup_free_ char *t = NULL;
-        _cleanup_strv_free_ char **l = NULL;
 
         assert(filename);
         assert(lvalue);
         assert(rvalue);
         assert(data);
 
-        l = strv_split_quoted(rvalue);
-        if (!l)
-                return log_oom();
-
-        k = strv_length(l);
-        if (k < 1 || k > 2) {
-                log_error("[%s:%u] Failed to parse weight value, ignoring: %s", filename, line, rvalue);
-                return 0;
-        }
-
-        if (k == 1)
-                weight = l[0];
-        else {
-                device = l[0];
-                weight = l[1];
-        }
-
-        if (device && !path_is_absolute(device)) {
-                log_error("[%s:%u] Failed to parse block device node value, ignoring: %s", filename, line, rvalue);
-                return 0;
-        }
-
-        if (safe_atolu(weight, &ul) < 0 || ul < 10 || ul > 1000) {
-                log_error("[%s:%u] Failed to parse block IO weight value, ignoring: %s", filename, line, rvalue);
-                return 0;
-        }
-
-        if (device)
-                r = asprintf(&t, "%s %lu", device, ul);
-        else
-                r = asprintf(&t, "%lu", ul);
-        if (r < 0)
-                return log_oom();
-
-        if (device)
-                r = unit_add_cgroup_attribute(u, "blkio", "blkio.weight_device", t, blkio_map, NULL);
-        else
-                r = unit_add_cgroup_attribute(u, "blkio", "blkio.weight", t, NULL, NULL);
+        r = cgroup_semantics_find(NULL, lvalue, rvalue, &v, &s);
         if (r < 0) {
-                log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue);
-                return 0;
-        }
-
-        return 0;
-}
-
-int config_parse_unit_blkio_bandwidth(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) {
-        Unit *u = data;
-        int r;
-        off_t bytes;
-        unsigned k;
-        _cleanup_free_ char *t = NULL;
-        _cleanup_strv_free_ char **l = NULL;
-
-        assert(filename);
-        assert(lvalue);
-        assert(rvalue);
-        assert(data);
-
-        l = strv_split_quoted(rvalue);
-        if (!l)
-                return log_oom();
-
-        k = strv_length(l);
-        if (k != 2) {
-                log_error("[%s:%u] Failed to parse bandwidth value, ignoring: %s", filename, line, rvalue);
+                log_error("[%s:%u] Failed to parse cgroup attribute value, ignoring: %s", filename, line, rvalue);
                 return 0;
-        }
-
-        if (!path_is_absolute(l[0])) {
-                log_error("[%s:%u] Failed to parse block device node value, ignoring: %s", filename, line, rvalue);
+        } else if (r == 0) {
+                log_error("[%s:%u] Unknown or unsupported cgroup attribute %s, ignoring: %s", filename, line, lvalue, rvalue);
                 return 0;
         }
 
-        if (parse_bytes(l[1], &bytes) < 0 || bytes <= 0) {
-                log_error("[%s:%u] Failed to parse block IO bandwidth value, ignoring: %s", filename, line, rvalue);
-                return 0;
-        }
-
-        r = asprintf(&t, "%s %llu", l[0], (unsigned long long) bytes);
-        if (r < 0)
-                return log_oom();
-
-        r = unit_add_cgroup_attribute(u, "blkio",
-                                      streq(lvalue, "BlockIOReadBandwidth") ? "blkio.read_bps_device" : "blkio.write_bps_device",
-                                      t, blkio_map, NULL);
+        r = unit_add_cgroup_attribute(u, s, NULL, NULL, v, NULL);
         if (r < 0) {
                 log_error("[%s:%u] Failed to add cgroup attribute value, ignoring: %s", filename, line, rvalue);
                 return 0;
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
index 421b4c3..dfb2ef0 100644
--- a/src/core/load-fragment.h
+++ b/src/core/load-fragment.h
@@ -75,11 +75,7 @@ int config_parse_kill_mode(const char *filename, unsigned line, const char *sect
 int config_parse_notify_access(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
 int config_parse_start_limit_action(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
 int config_parse_unit_cgroup_attr(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
-int config_parse_unit_cpu_shares(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
-int config_parse_unit_memory_limit(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
-int config_parse_unit_device_allow(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
-int config_parse_unit_blkio_weight(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
-int config_parse_unit_blkio_bandwidth(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_unit_cgroup_attr_pretty(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
 int config_parse_unit_requires_mounts_for(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
 int config_parse_syscall_filter(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
 int config_parse_environ(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
diff --git a/src/core/unit.c b/src/core/unit.c
index 3a88996..370ad67 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -749,15 +749,13 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) {
                                 prefix, b->controller, b->path);
 
                 LIST_FOREACH(by_unit, a, u->cgroup_attributes) {
-                        char *v = NULL;
+                        _cleanup_free_ char *v = NULL;
 
-                        if (a->map_callback)
-                                a->map_callback(a->controller, a->name, a->value, &v);
+                        if (a->semantics && a->semantics->map_write)
+                                a->semantics->map_write(a->semantics, a->value, &v);
 
                         fprintf(f, "%s\tControlGroupAttribute: %s %s \"%s\"\n",
                                 prefix, a->controller, a->name, v ? v : a->value);
-
-                        free(v);
                 }
 
                 if (UNIT_VTABLE(u)->dump)
@@ -1900,30 +1898,12 @@ finish:
 }
 
 int set_unit_path(const char *p) {
-        char *cwd, *c;
-        int r;
+        _cleanup_free_ char *c = NULL;
 
         /* This is mostly for debug purposes */
-
-        if (path_is_absolute(p)) {
-                if (!(c = strdup(p)))
-                        return -ENOMEM;
-        } else {
-                if (!(cwd = get_current_dir_name()))
-                        return -errno;
-
-                r = asprintf(&c, "%s/%s", cwd, p);
-                free(cwd);
-
-                if (r < 0)
-                        return -ENOMEM;
-        }
-
-        if (setenv("SYSTEMD_UNIT_PATH", c, 0) < 0) {
-                r = -errno;
-                free(c);
-                return r;
-        }
+        c = path_make_absolute_cwd(p);
+        if (setenv("SYSTEMD_UNIT_PATH", c, 0) < 0)
+                return -errno;
 
         return 0;
 }
@@ -2109,7 +2089,7 @@ static int unit_add_one_default_cgroup(Unit *u, const char *controller) {
         if (r < 0)
                 goto fail;
 
-        return 0;
+        return 1;
 
 fail:
         free(b->path);
@@ -2153,10 +2133,10 @@ CGroupBonding* unit_get_default_cgroup(Unit *u) {
 
 int unit_add_cgroup_attribute(
                 Unit *u,
+                const CGroupSemantics *semantics,
                 const char *controller,
                 const char *name,
                 const char *value,
-                CGroupAttributeMapCallback map_callback,
                 CGroupAttribute **ret) {
 
         _cleanup_free_ char *c = NULL;
@@ -2164,48 +2144,67 @@ int unit_add_cgroup_attribute(
         int r;
 
         assert(u);
-        assert(name);
         assert(value);
 
+        if (semantics) {
+                /* Semantics always take precedence */
+                if (semantics->name)
+                        name = semantics->name;
+
+                if (semantics->controller)
+                        controller = semantics->controller;
+        }
+
+        if (!name)
+                return -EINVAL;
+
         if (!controller) {
                 r = cg_controller_from_attr(name, &c);
                 if (r < 0)
                         return -EINVAL;
 
                 controller = c;
-        } else {
-                if (!filename_is_safe(name))
-                        return -EINVAL;
-
-                if (!filename_is_safe(controller))
-                        return -EINVAL;
         }
 
         if (!controller || streq(controller, SYSTEMD_CGROUP_CONTROLLER))
                 return -EINVAL;
 
+        if (!filename_is_safe(name))
+                return -EINVAL;
+
+        if (!filename_is_safe(controller))
+                return -EINVAL;
+
+        /* Check if this attribute already exists. Note that we will
+         * explicitly check for the value here too, as there are
+         * attributes which accept multiple values. */
         a = cgroup_attribute_find_list(u->cgroup_attributes, controller, name);
         if (a) {
-                char *v;
-
                 if (streq(value, a->value)) {
+                        /* Exactly the same value is always OK, let's ignore this */
                         if (ret)
                                 *ret = a;
 
                         return 0;
                 }
 
-                v = strdup(value);
-                if (!v)
-                        return -ENOMEM;
+                if (semantics && !semantics->multiple) {
+                        char *v;
 
-                free(a->value);
-                a->value = v;
+                        /* If this is a single-item entry, we can
+                         * simply patch the existing attribute */
+
+                        v = strdup(value);
+                        if (!v)
+                                return -ENOMEM;
 
-                if (ret)
-                        *ret = a;
+                        free(a->value);
+                        a->value = v;
 
-                return 1;
+                        if (ret)
+                                *ret = a;
+                        return 1;
+                }
         }
 
         a = new0(CGroupAttribute, 1);
@@ -2226,11 +2225,10 @@ int unit_add_cgroup_attribute(
                 free(a->name);
                 free(a->value);
                 free(a);
-
                 return -ENOMEM;
         }
 
-        a->map_callback = map_callback;
+        a->semantics = semantics;
         a->unit = u;
 
         LIST_PREPEND(CGroupAttribute, by_unit, u->cgroup_attributes, a);
@@ -2763,52 +2761,77 @@ ExecContext *unit_get_exec_context(Unit *u) {
         return (ExecContext*) ((uint8_t*) u + offset);
 }
 
-int unit_write_drop_in(Unit *u, bool runtime, const char *name, const char *data) {
-        _cleanup_free_ char *p = NULL, *q = NULL;
+static int drop_in_file(Unit *u, bool runtime, const char *name, char **_p, char **_q) {
+        char *p, *q;
+        int r;
+
         assert(u);
+        assert(name);
+        assert(_p);
+        assert(_q);
 
-        if (u->manager->running_as != SYSTEMD_SYSTEM)
+        if (u->manager->running_as == SYSTEMD_USER && runtime)
                 return -ENOTSUP;
 
         if (!filename_is_safe(name))
                 return -EINVAL;
 
-        p = strjoin(runtime ? "/run/systemd/system/" : "/etc/systemd/system/", u->id, ".d", NULL);
+        if (u->manager->running_as == SYSTEMD_USER) {
+                _cleanup_free_ char *c = NULL;
+
+                r = user_config_home(&c);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        return -ENOENT;
+
+                p = strjoin(c, "/", u->id, ".d", NULL);
+        } else  if (runtime)
+                p = strjoin("/run/systemd/system/", u->id, ".d", NULL);
+        else
+                p = strjoin("/etc/systemd/system/", u->id, ".d", NULL);
         if (!p)
                 return -ENOMEM;
 
         q = strjoin(p, "/50-", name, ".conf", NULL);
-        if (!q)
+        if (!q) {
+                free(p);
                 return -ENOMEM;
+        }
 
-        mkdir_p(p, 0755);
-        return write_one_line_file_atomic_label(q, data);
+        *_p = p;
+        *_q = q;
+        return 0;
 }
 
-int unit_remove_drop_in(Unit *u, bool runtime, const char *name) {
+int unit_write_drop_in(Unit *u, bool runtime, const char *name, const char *data) {
         _cleanup_free_ char *p = NULL, *q = NULL;
+        int r;
 
         assert(u);
 
-        if (u->manager->running_as != SYSTEMD_SYSTEM)
-                return -ENOTSUP;
+        r = drop_in_file(u, runtime, name, &p, &q);
+        if (r < 0)
+                return r;
 
-        if (!filename_is_safe(name))
-                return -EINVAL;
+        mkdir_p(p, 0755);
+        return write_one_line_file_atomic_label(q, data);
+}
 
-        p = strjoin(runtime ? "/run/systemd/system/" : "/etc/systemd/system/", u->id, ".d", NULL);
-        if (!p)
-                return -ENOMEM;
+int unit_remove_drop_in(Unit *u, bool runtime, const char *name) {
+        _cleanup_free_ char *p = NULL, *q = NULL;
+        int r;
 
-        q = strjoin(p, "/50-", name, ".conf", NULL);
-        if (!q)
-                return -ENOMEM;
+        assert(u);
 
+        r = drop_in_file(u, runtime, name, &p, &q);
         if (unlink(q) < 0)
-                return -errno;
+                r = -errno;
+        else
+                r = 0;
 
         rmdir(p);
-        return 0;
+        return r;
 }
 
 int unit_kill_context(
diff --git a/src/core/unit.h b/src/core/unit.h
index c902103..17a5a5f 100644
--- a/src/core/unit.h
+++ b/src/core/unit.h
@@ -40,6 +40,7 @@ typedef struct UnitStatusMessageFormats UnitStatusMessageFormats;
 #include "condition.h"
 #include "install.h"
 #include "unit-name.h"
+#include "cgroup-semantics.h"
 
 enum UnitActiveState {
         UNIT_ACTIVE,
@@ -445,7 +446,7 @@ int unit_add_cgroup(Unit *u, CGroupBonding *b);
 int unit_add_cgroup_from_text(Unit *u, const char *name, bool overwrite, CGroupBonding **ret);
 int unit_add_default_cgroups(Unit *u);
 CGroupBonding* unit_get_default_cgroup(Unit *u);
-int unit_add_cgroup_attribute(Unit *u, const char *controller, const char *name, const char *value, CGroupAttributeMapCallback map_callback, CGroupAttribute **ret);
+int unit_add_cgroup_attribute(Unit *u, const CGroupSemantics *semantics, const char *controller, const char *name, const char *value, CGroupAttribute **ret);
 
 int unit_choose_id(Unit *u, const char *name);
 int unit_set_description(Unit *u, const char *description);
diff --git a/src/shared/path-lookup.c b/src/shared/path-lookup.c
index fa4995c..ffdc536 100644
--- a/src/shared/path-lookup.c
+++ b/src/shared/path-lookup.c
@@ -41,21 +41,26 @@ DEFINE_STRING_TABLE_LOOKUP(systemd_running_as, SystemdRunningAs);
 
 int user_config_home(char **config_home) {
         const char *e;
+        char *r;
 
         e = getenv("XDG_CONFIG_HOME");
         if (e) {
-                if (asprintf(config_home, "%s/systemd/user", e) < 0)
+                r = strappend(e, "/systemd/user");
+                if (!r)
                         return -ENOMEM;
 
+                *config_home = r;
                 return 1;
         } else {
                 const char *home;
 
                 home = getenv("HOME");
                 if (home) {
-                        if (asprintf(config_home, "%s/.config/systemd/user", home) < 0)
+                        r = strappend(home, "/.config/systemd/user");
+                        if (!r)
                                 return -ENOMEM;
 
+                        *config_home = r;
                         return 1;
                 }
         }
diff --git a/src/shared/strv.c b/src/shared/strv.c
index ec25755..60c4762 100644
--- a/src/shared/strv.c
+++ b/src/shared/strv.c
@@ -305,6 +305,31 @@ char **strv_split_quoted(const char *s) {
         return r;
 }
 
+char **strv_split_newlines(const char *s) {
+        char **l;
+        unsigned n;
+
+        assert(s);
+
+        /* Special version of strv_split() that splits on newlines and
+         * suppresses an empty string at the end */
+
+        l = strv_split(s, NEWLINE);
+        if (!l)
+                return NULL;
+
+        n = strv_length(l);
+        if (n <= 0)
+                return l;
+
+        if (isempty(l[n-1])) {
+                free(l[n-1]);
+                l[n-1] = NULL;
+        }
+
+        return l;
+}
+
 char *strv_join(char **l, const char *separator) {
         char *r, *e;
         char **s;
diff --git a/src/shared/strv.h b/src/shared/strv.h
index b3802a7..623f102 100644
--- a/src/shared/strv.h
+++ b/src/shared/strv.h
@@ -58,6 +58,7 @@ static inline bool strv_isempty(char **l) {
 
 char **strv_split(const char *s, const char *separator) _malloc_;
 char **strv_split_quoted(const char *s) _malloc_;
+char **strv_split_newlines(const char *s) _malloc_;
 
 char *strv_join(char **l, const char *separator) _malloc_;
 
diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
index 65c5eca..a2dbbe8 100644
--- a/src/systemctl/systemctl.c
+++ b/src/systemctl/systemctl.c
@@ -1963,53 +1963,41 @@ static int kill_unit(DBusConnection *bus, char **args) {
 }
 
 static int set_cgroup(DBusConnection *bus, char **args) {
-        _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL;
-        DBusError error;
-        const char *method;
-        DBusMessageIter iter;
-        int r;
         _cleanup_free_ char *n = NULL;
-        const char *runtime;
+        const char *method, *runtime;
+        char **argument;
+        int r;
 
         assert(bus);
         assert(args);
 
-        dbus_error_init(&error);
-
         method =
-                streq(args[0], "set-cgroup")  ? "SetUnitControlGroups" :
-                streq(args[0], "unset-group") ? "UnsetUnitControlGroups"
-                                              : "UnsetUnitControlGroupAttributes";
+                streq(args[0], "set-cgroup")   ? "SetUnitControlGroup" :
+                streq(args[0], "unset-cgroup") ? "UnsetUnitControlGroup"
+                                               : "UnsetUnitControlGroupAttribute";
+
+        runtime = arg_runtime ? "runtime" : "persistent";
 
         n = unit_name_mangle(args[1]);
         if (!n)
                 return log_oom();
 
-        m = dbus_message_new_method_call(
-                        "org.freedesktop.systemd1",
-                        "/org/freedesktop/systemd1",
-                        "org.freedesktop.systemd1.Manager",
-                        method);
-        if (!m)
-                return log_oom();
-
-        dbus_message_iter_init_append(m, &iter);
-        if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &n))
-                return log_oom();
-
-        r = bus_append_strv_iter(&iter, args + 2);
-        if (r < 0)
-                return log_oom();
-
-        runtime = arg_runtime ? "runtime" : "persistent";
-        if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &runtime))
-                return log_oom();
+        STRV_FOREACH(argument, args + 2) {
 
-        reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
-        if (!reply) {
-                log_error("Failed to issue method call: %s", bus_error_message(&error));
-                dbus_error_free(&error);
-                return -EIO;
+                r = bus_method_call_with_reply(
+                                bus,
+                                "org.freedesktop.systemd1",
+                                "/org/freedesktop/systemd1",
+                                "org.freedesktop.systemd1.Manager",
+                                method,
+                                NULL,
+                                NULL,
+                                DBUS_TYPE_STRING, &n,
+                                DBUS_TYPE_STRING, argument,
+                                DBUS_TYPE_STRING, &runtime,
+                                DBUS_TYPE_INVALID);
+                if (r < 0)
+                        return r;
         }
 
         return 0;
@@ -2018,20 +2006,17 @@ static int set_cgroup(DBusConnection *bus, char **args) {
 static int set_cgroup_attr(DBusConnection *bus, char **args) {
         _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL;
         DBusError error;
-        DBusMessageIter iter, sub, sub2;
-        char **x, **y;
+        DBusMessageIter iter;
         _cleanup_free_ char *n = NULL;
         const char *runtime;
+        int r;
 
         assert(bus);
         assert(args);
 
         dbus_error_init(&error);
 
-        if (strv_length(args) % 2 != 0) {
-                log_error("Expecting an uneven number of arguments!");
-                return -EINVAL;
-        }
+        runtime = arg_runtime ? "runtime" : "persistent";
 
         n = unit_name_mangle(args[1]);
         if (!n)
@@ -2041,26 +2026,20 @@ static int set_cgroup_attr(DBusConnection *bus, char **args) {
                         "org.freedesktop.systemd1",
                         "/org/freedesktop/systemd1",
                         "org.freedesktop.systemd1.Manager",
-                        "SetUnitControlGroupAttributes");
+                        "SetUnitControlGroupAttribute");
         if (!m)
                 return log_oom();
 
         dbus_message_iter_init_append(m, &iter);
         if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &n) ||
-            !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(ss)", &sub))
+            !dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &args[2]))
                 return log_oom();
 
-        STRV_FOREACH_PAIR(x, y, args + 2) {
-                if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) ||
-                    !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, x) ||
-                    !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, y) ||
-                    !dbus_message_iter_close_container(&sub, &sub2))
-                        return log_oom();
-        }
+        r = bus_append_strv_iter(&iter, args + 3);
+        if (r < 0)
+                return log_oom();
 
-        runtime = arg_runtime ? "runtime" : "persistent";
-        if (!dbus_message_iter_close_container(&iter, &sub) ||
-            !dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &runtime))
+        if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &runtime))
                 return log_oom();
 
         reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
@@ -2075,57 +2054,49 @@ static int set_cgroup_attr(DBusConnection *bus, char **args) {
 
 static int get_cgroup_attr(DBusConnection *bus, char **args) {
         _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL;
-        DBusError error;
-        DBusMessageIter iter;
-        int r;
         _cleanup_free_ char *n = NULL;
-        _cleanup_strv_free_ char **list = NULL;
-        char **a;
+        char **argument;
+        int r;
 
         assert(bus);
         assert(args);
 
-        dbus_error_init(&error);
-
         n = unit_name_mangle(args[1]);
         if (!n)
                 return log_oom();
 
-        m = dbus_message_new_method_call(
-                        "org.freedesktop.systemd1",
-                        "/org/freedesktop/systemd1",
-                        "org.freedesktop.systemd1.Manager",
-                        "GetUnitControlGroupAttributes");
-        if (!m)
-                return log_oom();
-
-        dbus_message_iter_init_append(m, &iter);
-        if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &n))
-                return log_oom();
-
-        r = bus_append_strv_iter(&iter, args + 2);
-        if (r < 0)
-                return log_oom();
+        STRV_FOREACH(argument, args + 2) {
+                _cleanup_strv_free_ char **list = NULL;
+                DBusMessageIter iter;
+                char **a;
 
-        reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
-        if (!reply) {
-                log_error("Failed to issue method call: %s", bus_error_message(&error));
-                dbus_error_free(&error);
-                return -EIO;
-        }
+                r = bus_method_call_with_reply(
+                                bus,
+                                "org.freedesktop.systemd1",
+                                "/org/freedesktop/systemd1",
+                                "org.freedesktop.systemd1.Manager",
+                                "GetUnitControlGroupAttribute",
+                                &reply,
+                                NULL,
+                                DBUS_TYPE_STRING, &n,
+                                DBUS_TYPE_STRING, argument,
+                                DBUS_TYPE_INVALID);
+                if (r < 0)
+                        return r;
 
-        dbus_message_iter_init(reply, &iter);
-        r = bus_parse_strv_iter(&iter, &list);
-        if (r < 0) {
-                log_error("Failed to parse value list.");
-                return r;
-        }
+                dbus_message_iter_init(reply, &iter);
+                r = bus_parse_strv_iter(&iter, &list);
+                if (r < 0) {
+                        log_error("Failed to parse value list.");
+                        return r;
+                }
 
-        STRV_FOREACH(a, list) {
-                if (endswith(*a, "\n"))
-                        fputs(*a, stdout);
-                else
-                        puts(*a);
+                STRV_FOREACH(a, list) {
+                        if (endswith(*a, "\n"))
+                                fputs(*a, stdout);
+                        else
+                                puts(*a);
+                }
         }
 
         return 0;



More information about the systemd-commits mailing list