summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TODO4
-rw-r--r--src/core/dbus-cgroup.c62
-rw-r--r--src/core/dbus-cgroup.h2
-rw-r--r--src/core/dbus-manager.c42
-rw-r--r--src/core/dbus-slice.c30
-rw-r--r--src/core/dbus-slice.h3
-rw-r--r--src/core/dbus-unit.c88
-rw-r--r--src/core/dbus-unit.h20
-rw-r--r--src/core/slice.c2
-rw-r--r--src/core/unit.c25
-rw-r--r--src/core/unit.h16
-rw-r--r--src/systemctl/systemctl.c106
12 files changed, 371 insertions, 29 deletions
diff --git a/TODO b/TODO
index a61fa92b68..7098833881 100644
--- 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
diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c
index 08ee9c8db4..f7d1dd12ad 100644
--- a/src/core/dbus-cgroup.c
+++ b/src/core/dbus-cgroup.c
@@ -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;
+}
diff --git a/src/core/dbus-cgroup.h b/src/core/dbus-cgroup.h
index a0a7a7771e..c5908dd976 100644
--- a/src/core/dbus-cgroup.h
+++ b/src/core/dbus-cgroup.h
@@ -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);
diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c
index c081ff5d16..353cb22867 100644
--- a/src/core/dbus-manager.c
+++ b/src/core/dbus-manager.c
@@ -228,12 +228,17 @@
" <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 },
diff --git a/src/core/dbus-slice.c b/src/core/dbus-slice.c
index 8243305848..db356adf30 100644
--- a/src/core/dbus-slice.c
+++ b/src/core/dbus-slice.c
@@ -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;
+}
diff --git a/src/core/dbus-slice.h b/src/core/dbus-slice.h
index 7e7e29982d..c5ac473763 100644
--- a/src/core/dbus-slice.h
+++ b/src/core/dbus-slice.h
@@ -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[];
diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c
index cbd41342f4..1611da0172 100644
--- a/src/core/dbus-unit.c
+++ b/src/core/dbus-unit.c
@@ -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 },
diff --git a/src/core/dbus-unit.h b/src/core/dbus-unit.h
index 1e226ef451..2fd56f25c3 100644
--- a/src/core/dbus-unit.h
+++ b/src/core/dbus-unit.h
@@ -61,6 +61,10 @@
" <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;
diff --git a/src/core/slice.c b/src/core/slice.c
index df2d91e473..557f829088 100644
--- a/src/core/slice.c
+++ b/src/core/slice.c
@@ -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 = {
diff --git a/src/core/unit.c b/src/core/unit.c
index 0dcf85b5e0..be554dac20 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -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
diff --git a/src/core/unit.h b/src/core/unit.h
index fbcaabe167..c344719b16 100644
--- a/src/core/unit.h
+++ b/src/core/unit.h
@@ -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);
diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
index a4f8f2326e..1f81bda7e3 100644
--- a/src/systemctl/systemctl.c
+++ b/src/systemctl/systemctl.c
@@ -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;