]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
dbus: add infrastructure for changing multiple properties at once on units and hook...
authorLennart Poettering <lennart@poettering.net>
Thu, 27 Jun 2013 19:14:56 +0000 (21:14 +0200)
committerLennart Poettering <lennart@poettering.net>
Thu, 27 Jun 2013 19:14:56 +0000 (21:14 +0200)
This introduces two bus calls to make runtime changes to selected bus
properties, optionally with persistence.

This currently hooks this up only for three cgroup atributes, but this
brings the infrastructure to add more changable attributes.

This allows setting multiple attributes at once, and takes an array
rather than a dictionary of properties, in order to implement simple
resetting of lists using the same approach as when they are sourced from
unit files. This means, that list properties are appended to by this
call, unless they are first reset via assigning the empty list.

12 files changed:
TODO
src/core/dbus-cgroup.c
src/core/dbus-cgroup.h
src/core/dbus-manager.c
src/core/dbus-slice.c
src/core/dbus-slice.h
src/core/dbus-unit.c
src/core/dbus-unit.h
src/core/slice.c
src/core/unit.c
src/core/unit.h
src/systemctl/systemctl.c

diff --git a/TODO b/TODO
index a61fa92b68ca591f1df86b9b768c3132d58f1fd2..7098833881e64a3d8b0dd2fb65941e08f694bc33 100644 (file)
--- a/TODO
+++ b/TODO
@@ -28,6 +28,10 @@ Fedora 19:
 
 Features:
 
+* implement system-wide DefaultCPUAccounting=1 switch (and similar for blockio, memory, fair scheduling?)
+
+* handle jointly mounted controllers correctly
+
 * split out CreateMachine into systemd-machined
 
 * "transient" units, i.e units that are not sourced from disk but
index 08ee9c8db4e630a0b2fe890e69761bee9d4ba8c5..f7d1dd12ad35dfb31a918381a732bf341566ab47 100644 (file)
@@ -137,3 +137,65 @@ const BusProperty bus_cgroup_context_properties[] = {
         { "DeviceAllow",             bus_cgroup_append_device_allow,      "a(ss)", 0                                           },
         {}
 };
+
+int bus_cgroup_set_property(
+                Unit *u,
+                CGroupContext *c,
+                const char *name,
+                DBusMessageIter *i,
+                UnitSetPropertiesMode mode,
+                DBusError *error) {
+
+        assert(name);
+        assert(u);
+        assert(c);
+        assert(i);
+
+        if (streq(name, "CPUAccounting")) {
+                dbus_bool_t b;
+
+                if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_BOOLEAN)
+                        return -EINVAL;
+
+                if (mode != UNIT_CHECK) {
+                        dbus_message_iter_get_basic(i, &b);
+
+                        c->cpu_accounting = b;
+                        unit_write_drop_in(u, mode, "cpu-accounting", b ? "CPUAccounting=yes" : "CPUAccounting=no");
+                }
+
+                return 1;
+
+        } else if (streq(name, "BlockIOAccounting")) {
+                dbus_bool_t b;
+
+                if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_BOOLEAN)
+                        return -EINVAL;
+
+                if (mode != UNIT_CHECK) {
+                        dbus_message_iter_get_basic(i, &b);
+
+                        c->blockio_accounting = b;
+                        unit_write_drop_in(u, mode, "block-io-accounting", b ? "BlockIOAccounting=yes" : "BlockIOAccounting=no");
+                }
+
+                return 1;
+        } else if (streq(name, "MemoryAccounting")) {
+                dbus_bool_t b;
+
+                if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_BOOLEAN)
+                        return -EINVAL;
+
+                if (mode != UNIT_CHECK) {
+                        dbus_message_iter_get_basic(i, &b);
+
+                        c->blockio_accounting = b;
+                        unit_write_drop_in(u, mode, "memory-accounting", b ? "MemoryAccounting=yes" : "MemoryAccounting=no");
+                }
+
+                return 1;
+        }
+
+
+        return 0;
+}
index a0a7a7771e2e22372c50277ff6a25ee8fe1a4eb4..c5908dd9768657e5483d90456a257b2f64698073 100644 (file)
@@ -42,3 +42,5 @@
         "  <property name=\"DeviceAllow\" type=\"a(ss)\" access=\"read\"/>\n"
 
 extern const BusProperty bus_cgroup_context_properties[];
+
+int bus_cgroup_set_property(Unit *u, CGroupContext *c, const char *name, DBusMessageIter *i, UnitSetPropertiesMode mode, DBusError *error);
index c081ff5d1666b4ccbda841727afa6e86ed61fc87..353cb2286793a54648cd5865038420b506253245 100644 (file)
         "   <arg name=\"runtime\" type=\"b\" direction=\"in\"/>\n"      \
         "   <arg name=\"changes\" type=\"a(sss)\" direction=\"out\"/>\n" \
         "  </method>\n"                                                 \
-        "  <method name=\"SetDefaultTarget\">\n"                         \
+        "  <method name=\"SetDefaultTarget\">\n"                        \
         "   <arg name=\"files\" type=\"as\" direction=\"in\"/>\n"       \
         "   <arg name=\"changes\" type=\"a(sss)\" direction=\"out\"/>\n" \
         "  </method>\n"                                                 \
-        "  <method name=\"GetDefaultTarget\">\n"                         \
-        "   <arg name=\"name\" type=\"s\" direction=\"out\"/>\n" \
+        "  <method name=\"GetDefaultTarget\">\n"                        \
+        "   <arg name=\"name\" type=\"s\" direction=\"out\"/>\n"        \
+        "  </method>\n"                                                 \
+        "  <method name=\"SetUnitProperties\">\n"                       \
+        "   <arg name=\"name\" type=\"s\" direction=\"out\"/>\n"        \
+        "   <arg name=\"runtime\" type=\"b\" direction=\"in\"/>\n"      \
+        "   <arg name=\"properties\" type=\"a(sv)\" direction=\"in\"/>\n" \
         "  </method>\n"
 
 #define BUS_MANAGER_INTERFACE_SIGNALS                                   \
@@ -1725,13 +1730,42 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
                         goto oom;
 
                 r = unit_file_get_default(scope, NULL, &default_target);
-
                 if (r < 0)
                         return bus_send_error_reply(connection, message, NULL, r);
 
                 if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &default_target, DBUS_TYPE_INVALID)) {
                         goto oom;
                 }
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "SetUnitProperties")) {
+                DBusMessageIter iter;
+                dbus_bool_t runtime;
+                const char *name;
+                Unit *u;
+
+                if (!dbus_message_iter_init(message, &iter))
+                        goto oom;
+
+                if (bus_iter_get_basic_and_next(&iter, DBUS_TYPE_STRING, &name, true) < 0 ||
+                    bus_iter_get_basic_and_next(&iter, DBUS_TYPE_BOOLEAN, &runtime, true) < 0)
+                        return bus_send_error_reply(connection, message, NULL, -EINVAL);
+
+                u = manager_get_unit(m, name);
+                if (!u) {
+                        dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name);
+                        return bus_send_error_reply(connection, message, &error, -ENOENT);
+                }
+
+                SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "start");
+
+                r = bus_unit_set_properties(u, &iter, runtime ? UNIT_RUNTIME : UNIT_PERSISTENT, &error);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, &error, r);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
         } else {
                 const BusBoundProperties bps[] = {
                         { "org.freedesktop.systemd1.Manager", bus_systemd_properties, systemd_property_string },
index 8243305848b1f3051840637cdb3af70b2646cceb..db356adf306e7c23aeda104a72b6b0f9cd21a9ae 100644 (file)
@@ -53,10 +53,38 @@ DBusHandlerResult bus_slice_message_handler(Unit *u, DBusConnection *c, DBusMess
         const BusBoundProperties bps[] = {
                 { "org.freedesktop.systemd1.Unit",  bus_unit_properties,           u },
                 { "org.freedesktop.systemd1.Slice", bus_cgroup_context_properties, &s->cgroup_context },
-                { NULL, }
+                {}
         };
 
         SELINUX_UNIT_ACCESS_CHECK(u, c, message, "status");
 
         return bus_default_message_handler(c, message, INTROSPECTION, INTERFACES_LIST, bps);
 }
+
+int bus_slice_set_property(
+                Unit *u,
+                const char *name,
+                DBusMessageIter *i,
+                UnitSetPropertiesMode mode,
+                DBusError *error) {
+
+        Slice *s = SLICE(u);
+        int r;
+
+        assert(name);
+        assert(u);
+        assert(i);
+
+        r = bus_cgroup_set_property(u, &s->cgroup_context, name, i, mode, error);
+        if (r != 0)
+                return r;
+
+        return 0;
+}
+
+int bus_slice_commit_properties(Unit *u) {
+        assert(u);
+
+        unit_realize_cgroup(u);
+        return 0;
+}
index 7e7e29982d01f45052c3244a5f00be1ebca68d56..c5ac473763e46d0d4791eb9416a8855dfa1489e9 100644 (file)
@@ -27,4 +27,7 @@
 
 DBusHandlerResult bus_slice_message_handler(Unit *u, DBusConnection *c, DBusMessage *message);
 
+int bus_slice_set_property(Unit *u, const char *name, DBusMessageIter *i, UnitSetPropertiesMode mode, DBusError *error);
+int bus_slice_commit_properties(Unit *u);
+
 extern const char bus_slice_interface[];
index cbd41342f42f54e2778fcd4ce52983b4bc94eea5..1611da0172daa336cb7fb5ef4adb19c4647cb9f7 100644 (file)
@@ -403,6 +403,25 @@ static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *conn
                 reply = dbus_message_new_method_return(message);
                 if (!reply)
                         goto oom;
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "SetProperties")) {
+                DBusMessageIter iter;
+                dbus_bool_t runtime;
+
+                if (!dbus_message_iter_init(message, &iter))
+                        goto oom;
+
+                if (bus_iter_get_basic_and_next(&iter, DBUS_TYPE_BOOLEAN, &runtime, true) < 0)
+                        return bus_send_error_reply(connection, message, NULL, -EINVAL);
+
+                SELINUX_UNIT_ACCESS_CHECK(u, connection, message, "start");
+
+                r = bus_unit_set_properties(u, &iter, runtime ? UNIT_RUNTIME : UNIT_PERSISTENT, &error);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, &error, r);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
 
         } else if (UNIT_VTABLE(u)->bus_message_handler)
                 return UNIT_VTABLE(u)->bus_message_handler(u, connection, message);
@@ -745,6 +764,75 @@ oom:
         return DBUS_HANDLER_RESULT_NEED_MEMORY;
 }
 
+int bus_unit_set_properties(Unit *u, DBusMessageIter *iter, UnitSetPropertiesMode mode, DBusError *error) {
+        bool for_real = false;
+        DBusMessageIter sub;
+        unsigned n = 0;
+        int r;
+
+        assert(u);
+        assert(iter);
+
+        /* We iterate through the array twice. First run we just check
+         * if all passed data is valid, second run actually applies
+         * it. This is to implement transaction-like behaviour without
+         * actually providing full transactions. */
+
+        if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY ||
+            dbus_message_iter_get_element_type(iter) != DBUS_TYPE_STRUCT)
+                return -EINVAL;
+
+        dbus_message_iter_recurse(iter, &sub);
+
+        for (;;) {
+                DBusMessageIter sub2, sub3;
+                const char *name;
+
+                if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_INVALID) {
+
+                        if (for_real)
+                                break;
+
+                        /* Reached EOF. Let's try again, and this time for realz... */
+                        dbus_message_iter_recurse(iter, &sub);
+                        for_real = true;
+                        continue;
+                }
+
+                if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT)
+                        return -EINVAL;
+
+                dbus_message_iter_recurse(&sub, &sub2);
+
+                if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0 ||
+                    dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT)
+                        return -EINVAL;
+
+                if (!UNIT_VTABLE(u)->bus_set_property) {
+                        dbus_set_error(error, DBUS_ERROR_PROPERTY_READ_ONLY, "Objects of this type do not support setting properties.");
+                        return -ENOENT;
+                }
+
+                dbus_message_iter_recurse(&sub2, &sub3);
+                r = UNIT_VTABLE(u)->bus_set_property(u, name, &sub3, for_real ? mode : UNIT_CHECK, error);
+                if (r < 0)
+                        return r;
+                if (r == 0) {
+                        dbus_set_error(error, DBUS_ERROR_PROPERTY_READ_ONLY, "Cannot set property %s, or unknown property.", name);
+                        return -ENOENT;
+                }
+
+                dbus_message_iter_next(&sub);
+
+                n += for_real;
+        }
+
+        if (n > 0 && UNIT_VTABLE(u)->bus_commit_properties)
+                UNIT_VTABLE(u)->bus_commit_properties(u);
+
+        return 0;
+}
+
 const BusProperty bus_unit_properties[] = {
         { "Id",                   bus_property_append_string,         "s", offsetof(Unit, id),                                         true },
         { "Names",                bus_unit_append_names,             "as", 0 },
index 1e226ef451dd5ccc5b312a672d7e44903f27d4e3..2fd56f25c36bf216ca3e7ba3a2b015343b6b1fdf 100644 (file)
         "   <arg name=\"signal\" type=\"i\" direction=\"in\"/>\n"       \
         "  </method>\n"                                                 \
         "  <method name=\"ResetFailed\"/>\n"                            \
+        "  <method name=\"SetProperties\">\n"                           \
+        "   <arg name=\"runtime\" type=\"b\" direction=\"in\"/>\n"         \
+        "   <arg name=\"properties\" type=\"a(sv)\" direction=\"in\"/>\n" \
+        "  </method>\n"                                                 \
         "  <property name=\"Id\" type=\"s\" access=\"read\"/>\n"        \
         "  <property name=\"Names\" type=\"as\" access=\"read\"/>\n"    \
         "  <property name=\"Following\" type=\"s\" access=\"read\"/>\n" \
@@ -135,19 +139,9 @@ extern const BusProperty bus_unit_properties[];
 void bus_unit_send_change_signal(Unit *u);
 void bus_unit_send_removed_signal(Unit *u);
 
-DBusHandlerResult bus_unit_queue_job(
-                DBusConnection *connection,
-                DBusMessage *message,
-                Unit *u,
-                JobType type,
-                JobMode mode,
-                bool reload_if_possible);
-
-int bus_unit_cgroup_set(Unit *u, DBusMessageIter *iter);
-int bus_unit_cgroup_unset(Unit *u, DBusMessageIter *iter);
-int bus_unit_cgroup_attribute_get(Unit *u, DBusMessageIter *iter, char ***_result);
-int bus_unit_cgroup_attribute_set(Unit *u, DBusMessageIter *iter);
-int bus_unit_cgroup_attribute_unset(Unit *u, DBusMessageIter *iter);
+DBusHandlerResult bus_unit_queue_job(DBusConnection *connection, DBusMessage *message, Unit *u, JobType type, JobMode mode, bool reload_if_possible);
+
+int bus_unit_set_properties(Unit *u, DBusMessageIter *i, UnitSetPropertiesMode mode, DBusError *error);
 
 extern const DBusObjectPathVTable bus_unit_vtable;
 
index df2d91e47308805049c2a9ade7dec7388e669374..557f8290883c1a794511cace3509e4f07ed388ce 100644 (file)
@@ -304,6 +304,8 @@ const UnitVTable slice_vtable = {
 
         .bus_interface = "org.freedesktop.systemd1.Slice",
         .bus_message_handler = bus_slice_message_handler,
+        .bus_set_property = bus_slice_set_property,
+        .bus_commit_properties = bus_slice_commit_properties,
 
         .status_message_formats = {
                 .finished_start_job = {
index 0dcf85b5e0d3f242b633195348fc1a9a2c4b77aa..be554dac2082840297d88602a5afb14ae94f0e43 100644 (file)
@@ -2642,7 +2642,7 @@ CGroupContext *unit_get_cgroup_context(Unit *u) {
         return (CGroupContext*) ((uint8_t*) u + offset);
 }
 
-static int drop_in_file(Unit *u, bool runtime, const char *name, char **_p, char **_q) {
+static int drop_in_file(Unit *u, UnitSetPropertiesMode mode, const char *name, char **_p, char **_q) {
         char *p, *q;
         int r;
 
@@ -2650,8 +2650,9 @@ static int drop_in_file(Unit *u, bool runtime, const char *name, char **_p, char
         assert(name);
         assert(_p);
         assert(_q);
+        assert(mode & (UNIT_PERSISTENT|UNIT_RUNTIME));
 
-        if (u->manager->running_as == SYSTEMD_USER && runtime)
+        if (u->manager->running_as == SYSTEMD_USER && !(mode & UNIT_PERSISTENT))
                 return -ENOTSUP;
 
         if (!filename_is_safe(name))
@@ -2667,10 +2668,10 @@ static int drop_in_file(Unit *u, bool runtime, const char *name, char **_p, char
                         return -ENOENT;
 
                 p = strjoin(c, "/", u->id, ".d", NULL);
-        } else  if (runtime)
-                p = strjoin("/run/systemd/system/", u->id, ".d", NULL);
-        else
+        } else  if (mode & UNIT_PERSISTENT)
                 p = strjoin("/etc/systemd/system/", u->id, ".d", NULL);
+        else
+                p = strjoin("/run/systemd/system/", u->id, ".d", NULL);
         if (!p)
                 return -ENOMEM;
 
@@ -2685,13 +2686,16 @@ static int drop_in_file(Unit *u, bool runtime, const char *name, char **_p, char
         return 0;
 }
 
-int unit_write_drop_in(Unit *u, bool runtime, const char *name, const char *data) {
+int unit_write_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data) {
         _cleanup_free_ char *p = NULL, *q = NULL;
         int r;
 
         assert(u);
 
-        r = drop_in_file(u, runtime, name, &p, &q);
+        if (!(mode & (UNIT_PERSISTENT|UNIT_RUNTIME)))
+                return 0;
+
+        r = drop_in_file(u, mode, name, &p, &q);
         if (r < 0)
                 return r;
 
@@ -2699,13 +2703,16 @@ int unit_write_drop_in(Unit *u, bool runtime, const char *name, const char *data
         return write_string_file_atomic_label(q, data);
 }
 
-int unit_remove_drop_in(Unit *u, bool runtime, const char *name) {
+int unit_remove_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name) {
         _cleanup_free_ char *p = NULL, *q = NULL;
         int r;
 
         assert(u);
 
-        r = drop_in_file(u, runtime, name, &p, &q);
+        if (!(mode & (UNIT_PERSISTENT|UNIT_RUNTIME)))
+                return 0;
+
+        r = drop_in_file(u, mode, name, &p, &q);
         if (unlink(q) < 0)
                 r = -errno;
         else
index fbcaabe167ed346276c7523e311d59b6bec05952..c344719b1603c57201e29dd1614f81958ce6e1fb 100644 (file)
@@ -260,6 +260,12 @@ struct UnitStatusMessageFormats {
         const char *finished_stop_job[_JOB_RESULT_MAX];
 };
 
+typedef enum UnitSetPropertiesMode {
+        UNIT_CHECK = 0,
+        UNIT_RUNTIME = 1,
+        UNIT_PERSISTENT = 2,
+} UnitSetPropertiesMode;
+
 #include "service.h"
 #include "timer.h"
 #include "socket.h"
@@ -372,6 +378,12 @@ struct UnitVTable {
         /* Called for each message received on the bus */
         DBusHandlerResult (*bus_message_handler)(Unit *u, DBusConnection *c, DBusMessage *message);
 
+        /* Called for each property that is being set */
+        int (*bus_set_property)(Unit *u, const char *name, DBusMessageIter *i, UnitSetPropertiesMode mode, DBusError *error);
+
+        /* Called after at least one property got changed to apply the necessary change */
+        int (*bus_commit_properties)(Unit *u);
+
         /* Return the unit this unit is following */
         Unit *(*following)(Unit *u);
 
@@ -577,8 +589,8 @@ int unit_exec_context_defaults(Unit *u, ExecContext *c);
 ExecContext *unit_get_exec_context(Unit *u) _pure_;
 CGroupContext *unit_get_cgroup_context(Unit *u) _pure_;
 
-int unit_write_drop_in(Unit *u, bool runtime, const char *name, const char *data);
-int unit_remove_drop_in(Unit *u, bool runtime, const char *name);
+int unit_write_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data);
+int unit_remove_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name);
 
 int unit_kill_context(Unit *u, KillContext *c, bool sigkill, pid_t main_pid, pid_t control_pid, bool main_pid_alien);
 
index a4f8f2326e99e6582284429a0b71f94773618c22..1f81bda7e3fd38516580a4b86e021b9d69860e16 100644 (file)
@@ -3596,6 +3596,109 @@ static int show(DBusConnection *bus, char **args) {
         return ret;
 }
 
+static int append_assignment(DBusMessageIter *iter, const char *assignment) {
+        const char *eq;
+        char *field;
+        DBusMessageIter sub;
+        int r;
+
+        assert(iter);
+        assert(assignment);
+
+        eq = strchr(assignment, '=');
+        if (!eq) {
+                log_error("Not an assignment: %s", assignment);
+                return -EINVAL;
+        }
+
+        field = strndupa(assignment, eq - assignment);
+        eq ++;
+
+        if (!dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &field))
+                return log_oom();
+
+        if (streq(field, "CPUAccounting") ||
+            streq(field, "MemoryAccounting") ||
+            streq(field, "BlockIOAccounting")) {
+                dbus_bool_t b;
+
+                r = parse_boolean(eq);
+                if (r < 0) {
+                        log_error("Failed to parse boolean assignment %s.", assignment);
+                        return -EINVAL;
+                }
+
+                b = r;
+                if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "b", &sub) ||
+                    !dbus_message_iter_append_basic(&sub, DBUS_TYPE_BOOLEAN, &b))
+                        return log_oom();
+        } else {
+                log_error("Unknown assignment %s.", assignment);
+                return -EINVAL;
+        }
+
+        if (!dbus_message_iter_close_container(iter, &sub))
+                return log_oom();
+
+        return 0;
+}
+
+static int set_property(DBusConnection *bus, char **args) {
+
+        _cleanup_free_ DBusMessage *m = NULL, *reply = NULL;
+        DBusMessageIter iter, sub;
+        dbus_bool_t runtime;
+        DBusError error;
+        char **i;
+        int r;
+
+        dbus_error_init(&error);
+
+        m = dbus_message_new_method_call(
+                        "org.freedesktop.systemd1",
+                        "/org/freedesktop/systemd1",
+                        "org.freedesktop.systemd1.Manager",
+                        "SetUnitProperties");
+        if (!m)
+                return log_oom();
+
+        dbus_message_iter_init_append(m, &iter);
+
+        runtime = arg_runtime;
+
+        if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &args[1]) ||
+            !dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &runtime) ||
+            !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(sv)", &sub))
+                return log_oom();
+
+        STRV_FOREACH(i, args + 2) {
+                DBusMessageIter sub2;
+
+                if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2))
+                        return log_oom();
+
+                r = append_assignment(&sub2, *i);
+                if (r < 0)
+                        return r;
+
+                if (!dbus_message_iter_close_container(&sub, &sub2))
+                        return log_oom();
+
+        }
+
+        if (!dbus_message_iter_close_container(&iter, &sub))
+                return log_oom();
+
+        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;
+        }
+
+        return 0;
+}
+
 static int dump(DBusConnection *bus, char **args) {
         _cleanup_free_ DBusMessage *reply = NULL;
         DBusError error;
@@ -4560,6 +4663,8 @@ static int systemctl_help(void) {
                "  status [NAME...|PID...]         Show runtime status of one or more units\n"
                "  show [NAME...|JOB...]           Show properties of one or more\n"
                "                                  units/jobs or the manager\n"
+               "  set-property [NAME] [ASSIGNMENT...]\n"
+               "                                  Sets one or more properties of a unit\n"
                "  help [NAME...|PID...]           Show manual for one or more units\n"
                "  reset-failed [NAME...]          Reset failed state for all, one, or more\n"
                "                                  units\n"
@@ -5639,6 +5744,7 @@ static int systemctl_main(DBusConnection *bus, int argc, char *argv[], DBusError
                 { "set-default",           EQUAL, 2, enable_unit       },
                 { "get-default",           LESS,  1, get_default       },
                 { "set-log-level",         EQUAL, 2, set_log_level     },
+                { "set-property",          MORE,  3, set_property      },
         };
 
         int left;