1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2010 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
26 #include "dbus-unit.h"
27 #include "bus-errors.h"
28 #include "dbus-common.h"
30 const char bus_unit_interface[] _introspect_("Unit") = BUS_UNIT_INTERFACE;
32 #define INVALIDATING_PROPERTIES \
36 "InactiveExitTimestamp\0" \
37 "ActiveEnterTimestamp\0" \
38 "ActiveExitTimestamp\0" \
39 "InactiveEnterTimestamp\0" \
43 static int bus_unit_append_names(DBusMessageIter *i, const char *property, void *data) {
49 if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "s", &sub))
52 SET_FOREACH(t, u->names, j)
53 if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &t))
56 if (!dbus_message_iter_close_container(i, &sub))
62 static int bus_unit_append_following(DBusMessageIter *i, const char *property, void *data) {
70 f = unit_following(u);
73 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d))
79 static int bus_unit_append_dependencies(DBusMessageIter *i, const char *property, void *data) {
85 if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "s", &sub))
89 if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &u->id))
92 if (!dbus_message_iter_close_container(i, &sub))
98 static int bus_unit_append_description(DBusMessageIter *i, const char *property, void *data) {
106 d = unit_description(u);
108 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d))
114 static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_unit_append_load_state, unit_load_state, UnitLoadState);
116 static int bus_unit_append_active_state(DBusMessageIter *i, const char *property, void *data) {
124 state = unit_active_state_to_string(unit_active_state(u));
126 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &state))
132 static int bus_unit_append_sub_state(DBusMessageIter *i, const char *property, void *data) {
140 state = unit_sub_state_to_string(u);
142 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &state))
148 static int bus_unit_append_file_state(DBusMessageIter *i, const char *property, void *data) {
156 state = strempty(unit_file_state_to_string(unit_get_unit_file_state(u)));
158 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &state))
164 static int bus_unit_append_can_start(DBusMessageIter *i, const char *property, void *data) {
172 b = unit_can_start(u) &&
173 !u->refuse_manual_start;
175 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
181 static int bus_unit_append_can_stop(DBusMessageIter *i, const char *property, void *data) {
189 /* On the lower levels we assume that every unit we can start
190 * we can also stop */
192 b = unit_can_start(u) &&
193 !u->refuse_manual_stop;
195 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
201 static int bus_unit_append_can_reload(DBusMessageIter *i, const char *property, void *data) {
209 b = unit_can_reload(u);
211 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
217 static int bus_unit_append_can_isolate(DBusMessageIter *i, const char *property, void *data) {
225 b = unit_can_isolate(u) &&
226 !u->refuse_manual_start;
228 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
234 static int bus_unit_append_job(DBusMessageIter *i, const char *property, void *data) {
243 if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub))
248 if (!(p = job_dbus_path(u->job)))
251 if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT32, &u->job->id) ||
252 !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &p)) {
259 /* No job, so let's fill in some placeholder
260 * data. Since we need to fill in a valid path we
261 * simple point to ourselves. */
263 if (!(p = unit_dbus_path(u)))
266 if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT32, &id) ||
267 !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &p)) {
275 if (!dbus_message_iter_close_container(i, &sub))
281 static int bus_unit_append_default_cgroup(DBusMessageIter *i, const char *property, void *data) {
291 if ((cgb = unit_get_default_cgroup(u))) {
292 if (!(t = cgroup_bonding_to_string(cgb)))
297 success = dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t);
302 return success ? 0 : -ENOMEM;
305 static int bus_unit_append_cgroups(DBusMessageIter *i, const char *property, void *data) {
310 if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "s", &sub))
313 LIST_FOREACH(by_unit, cgb, u->cgroup_bondings) {
317 if (!(t = cgroup_bonding_to_string(cgb)))
320 success = dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &t);
327 if (!dbus_message_iter_close_container(i, &sub))
333 static int bus_unit_append_cgroup_attrs(DBusMessageIter *i, const char *property, void *data) {
336 DBusMessageIter sub, sub2;
338 if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(sss)", &sub))
341 LIST_FOREACH(by_unit, a, u->cgroup_attributes) {
346 a->map_callback(a->controller, a->name, a->value, &v);
349 dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) &&
350 dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &a->controller) &&
351 dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &a->name) &&
352 dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, v ? &v : &a->value) &&
353 dbus_message_iter_close_container(&sub, &sub2);
361 if (!dbus_message_iter_close_container(i, &sub))
367 static int bus_unit_append_need_daemon_reload(DBusMessageIter *i, const char *property, void *data) {
375 b = unit_need_daemon_reload(u);
377 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
383 static int bus_unit_append_load_error(DBusMessageIter *i, const char *property, void *data) {
385 const char *name, *message;
392 if (u->load_error != 0) {
393 name = bus_errno_to_dbus(u->load_error);
394 message = strempty(strerror(-u->load_error));
398 if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub) ||
399 !dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name) ||
400 !dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &message) ||
401 !dbus_message_iter_close_container(i, &sub))
407 static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *connection, DBusMessage *message) {
408 DBusMessage *reply = NULL;
409 Manager *m = u->manager;
411 JobType job_type = _JOB_TYPE_INVALID;
413 bool reload_if_possible = false;
415 dbus_error_init(&error);
417 if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Start"))
418 job_type = JOB_START;
419 else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Stop"))
421 else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Reload"))
422 job_type = JOB_RELOAD;
423 else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Restart"))
424 job_type = JOB_RESTART;
425 else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "TryRestart"))
426 job_type = JOB_TRY_RESTART;
427 else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "ReloadOrRestart")) {
428 reload_if_possible = true;
429 job_type = JOB_RESTART;
430 } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "ReloadOrTryRestart")) {
431 reload_if_possible = true;
432 job_type = JOB_TRY_RESTART;
433 } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Kill")) {
439 if (!dbus_message_get_args(
442 DBUS_TYPE_STRING, &swho,
443 DBUS_TYPE_INT32, &signo,
445 return bus_send_error_reply(connection, message, &error, -EINVAL);
450 who = kill_who_from_string(swho);
452 return bus_send_error_reply(connection, message, &error, -EINVAL);
455 if (signo <= 0 || signo >= _NSIG)
456 return bus_send_error_reply(connection, message, &error, -EINVAL);
458 r = unit_kill(u, who, signo, &error);
460 return bus_send_error_reply(connection, message, &error, r);
462 reply = dbus_message_new_method_return(message);
466 } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "ResetFailed")) {
468 unit_reset_failed(u);
470 if (!(reply = dbus_message_new_method_return(message)))
473 } else if (UNIT_VTABLE(u)->bus_message_handler)
474 return UNIT_VTABLE(u)->bus_message_handler(u, connection, message);
476 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
478 if (job_type != _JOB_TYPE_INVALID) {
484 if ((job_type == JOB_START && u->refuse_manual_start) ||
485 (job_type == JOB_STOP && u->refuse_manual_stop) ||
486 ((job_type == JOB_RESTART || job_type == JOB_TRY_RESTART) &&
487 (u->refuse_manual_start || u->refuse_manual_stop))) {
488 dbus_set_error(&error, BUS_ERROR_ONLY_BY_DEPENDENCY, "Operation refused, may be requested by dependency only.");
489 return bus_send_error_reply(connection, message, &error, -EPERM);
492 if (!dbus_message_get_args(
495 DBUS_TYPE_STRING, &smode,
497 return bus_send_error_reply(connection, message, &error, -EINVAL);
499 if (reload_if_possible && unit_can_reload(u)) {
500 if (job_type == JOB_RESTART)
501 job_type = JOB_RELOAD_OR_START;
502 else if (job_type == JOB_TRY_RESTART)
503 job_type = JOB_RELOAD;
506 if ((mode = job_mode_from_string(smode)) == _JOB_MODE_INVALID) {
507 dbus_set_error(&error, BUS_ERROR_INVALID_JOB_MODE, "Job mode %s is invalid.", smode);
508 return bus_send_error_reply(connection, message, &error, -EINVAL);
511 if ((r = manager_add_job(m, job_type, u, mode, true, &error, &j)) < 0)
512 return bus_send_error_reply(connection, message, &error, r);
514 if (!(reply = dbus_message_new_method_return(message)))
517 if (!(path = job_dbus_path(j)))
520 if (!dbus_message_append_args(
522 DBUS_TYPE_OBJECT_PATH, &path,
528 if (!dbus_connection_send(connection, reply, NULL))
531 dbus_message_unref(reply);
536 return DBUS_HANDLER_RESULT_HANDLED;
542 dbus_message_unref(reply);
544 dbus_error_free(&error);
546 return DBUS_HANDLER_RESULT_NEED_MEMORY;
549 static DBusHandlerResult bus_unit_message_handler(DBusConnection *connection, DBusMessage *message, void *data) {
553 DBusMessage *reply = NULL;
560 dbus_error_init(&error);
562 if (streq(dbus_message_get_path(message), "/org/freedesktop/systemd1/unit")) {
563 /* Be nice to gdbus and return introspection data for our mid-level paths */
565 if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) {
566 char *introspection = NULL;
572 if (!(reply = dbus_message_new_method_return(message)))
575 /* We roll our own introspection code here, instead of
576 * relying on bus_default_message_handler() because we
577 * need to generate our introspection string
580 if (!(f = open_memstream(&introspection, &size)))
583 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
586 fputs(BUS_INTROSPECTABLE_INTERFACE, f);
587 fputs(BUS_PEER_INTERFACE, f);
589 HASHMAP_FOREACH_KEY(u, k, m->units, i) {
595 if (!(p = bus_path_escape(k))) {
601 fprintf(f, "<node name=\"%s\"/>", p);
605 fputs("</node>\n", f);
618 if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID)) {
625 if (!dbus_connection_send(connection, reply, NULL))
628 dbus_message_unref(reply);
630 return DBUS_HANDLER_RESULT_HANDLED;
633 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
636 r = manager_load_unit_from_dbus_path(m, dbus_message_get_path(message), &error, &u);
641 return bus_send_error_reply(connection, message, &error, r);
644 return bus_unit_message_dispatch(u, connection, message);
648 dbus_message_unref(reply);
650 dbus_error_free(&error);
652 return DBUS_HANDLER_RESULT_NEED_MEMORY;
655 const DBusObjectPathVTable bus_unit_vtable = {
656 .message_function = bus_unit_message_handler
659 void bus_unit_send_change_signal(Unit *u) {
661 DBusMessage *m = NULL;
665 if (u->in_dbus_queue) {
666 LIST_REMOVE(Unit, dbus_queue, u->manager->dbus_unit_queue, u);
667 u->in_dbus_queue = false;
673 if (!bus_has_subscriber(u->manager)) {
674 u->sent_dbus_new_signal = true;
678 if (!(p = unit_dbus_path(u)))
681 if (u->sent_dbus_new_signal) {
682 /* Send a properties changed signal. First for the
683 * specific type, then for the generic unit. The
684 * clients may rely on this order to get atomic
685 * behaviour if needed. */
687 if (UNIT_VTABLE(u)->bus_invalidating_properties) {
689 if (!(m = bus_properties_changed_new(p,
690 UNIT_VTABLE(u)->bus_interface,
691 UNIT_VTABLE(u)->bus_invalidating_properties)))
694 if (bus_broadcast(u->manager, m) < 0)
697 dbus_message_unref(m);
700 if (!(m = bus_properties_changed_new(p, "org.freedesktop.systemd1.Unit", INVALIDATING_PROPERTIES)))
704 /* Send a new signal */
706 if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnitNew")))
709 if (!dbus_message_append_args(m,
710 DBUS_TYPE_STRING, &u->id,
711 DBUS_TYPE_OBJECT_PATH, &p,
716 if (bus_broadcast(u->manager, m) < 0)
720 dbus_message_unref(m);
722 u->sent_dbus_new_signal = true;
730 dbus_message_unref(m);
732 log_error("Failed to allocate unit change/new signal.");
735 void bus_unit_send_removed_signal(Unit *u) {
737 DBusMessage *m = NULL;
741 if (!bus_has_subscriber(u->manager))
744 if (!u->sent_dbus_new_signal)
745 bus_unit_send_change_signal(u);
750 if (!(p = unit_dbus_path(u)))
753 if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnitRemoved")))
756 if (!dbus_message_append_args(m,
757 DBUS_TYPE_STRING, &u->id,
758 DBUS_TYPE_OBJECT_PATH, &p,
762 if (bus_broadcast(u->manager, m) < 0)
766 dbus_message_unref(m);
774 dbus_message_unref(m);
776 log_error("Failed to allocate unit remove signal.");
779 const BusProperty bus_unit_properties[] = {
780 { "Id", bus_property_append_string, "s", offsetof(Unit, id), true },
781 { "Names", bus_unit_append_names, "as", 0 },
782 { "Following", bus_unit_append_following, "s", 0 },
783 { "Requires", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRES]), true },
784 { "RequiresOverridable", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRES_OVERRIDABLE]), true },
785 { "Requisite", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUISITE]), true },
786 { "RequisiteOverridable", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUISITE_OVERRIDABLE]), true },
787 { "Wants", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_WANTS]), true },
788 { "BindsTo", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_BINDS_TO]), true },
789 { "PartOf", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_PART_OF]), true },
790 { "RequiredBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRED_BY]), true },
791 { "RequiredByOverridable",bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRED_BY_OVERRIDABLE]), true },
792 { "WantedBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_WANTED_BY]), true },
793 { "BoundBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_BOUND_BY]), true },
794 { "ConsistsOf", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_CONSISTS_OF]), true },
795 { "Conflicts", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_CONFLICTS]), true },
796 { "ConflictedBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_CONFLICTED_BY]), true },
797 { "Before", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_BEFORE]), true },
798 { "After", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_AFTER]), true },
799 { "OnFailure", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_ON_FAILURE]), true },
800 { "Triggers", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_TRIGGERS]), true },
801 { "TriggeredBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_TRIGGERED_BY]), true },
802 { "PropagatesReloadTo", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_PROPAGATES_RELOAD_TO]), true },
803 { "ReloadPropagatedFrom", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_RELOAD_PROPAGATED_FROM]), true },
804 { "RequiresMountsFor", bus_property_append_strv, "as", offsetof(Unit, requires_mounts_for), true },
805 { "Documentation", bus_property_append_strv, "as", offsetof(Unit, documentation), true },
806 { "Description", bus_unit_append_description, "s", 0 },
807 { "LoadState", bus_unit_append_load_state, "s", offsetof(Unit, load_state) },
808 { "ActiveState", bus_unit_append_active_state, "s", 0 },
809 { "SubState", bus_unit_append_sub_state, "s", 0 },
810 { "FragmentPath", bus_property_append_string, "s", offsetof(Unit, fragment_path), true },
811 { "SourcePath", bus_property_append_string, "s", offsetof(Unit, source_path), true },
812 { "UnitFileState", bus_unit_append_file_state, "s", 0 },
813 { "InactiveExitTimestamp",bus_property_append_usec, "t", offsetof(Unit, inactive_exit_timestamp.realtime) },
814 { "InactiveExitTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, inactive_exit_timestamp.monotonic) },
815 { "ActiveEnterTimestamp", bus_property_append_usec, "t", offsetof(Unit, active_enter_timestamp.realtime) },
816 { "ActiveEnterTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, active_enter_timestamp.monotonic) },
817 { "ActiveExitTimestamp", bus_property_append_usec, "t", offsetof(Unit, active_exit_timestamp.realtime) },
818 { "ActiveExitTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, active_exit_timestamp.monotonic) },
819 { "InactiveEnterTimestamp", bus_property_append_usec, "t", offsetof(Unit, inactive_enter_timestamp.realtime) },
820 { "InactiveEnterTimestampMonotonic",bus_property_append_usec, "t", offsetof(Unit, inactive_enter_timestamp.monotonic) },
821 { "CanStart", bus_unit_append_can_start, "b", 0 },
822 { "CanStop", bus_unit_append_can_stop, "b", 0 },
823 { "CanReload", bus_unit_append_can_reload, "b", 0 },
824 { "CanIsolate", bus_unit_append_can_isolate, "b", 0 },
825 { "Job", bus_unit_append_job, "(uo)", 0 },
826 { "StopWhenUnneeded", bus_property_append_bool, "b", offsetof(Unit, stop_when_unneeded) },
827 { "RefuseManualStart", bus_property_append_bool, "b", offsetof(Unit, refuse_manual_start) },
828 { "RefuseManualStop", bus_property_append_bool, "b", offsetof(Unit, refuse_manual_stop) },
829 { "AllowIsolate", bus_property_append_bool, "b", offsetof(Unit, allow_isolate) },
830 { "DefaultDependencies", bus_property_append_bool, "b", offsetof(Unit, default_dependencies) },
831 { "OnFailureIsolate", bus_property_append_bool, "b", offsetof(Unit, on_failure_isolate) },
832 { "IgnoreOnIsolate", bus_property_append_bool, "b", offsetof(Unit, ignore_on_isolate) },
833 { "IgnoreOnSnapshot", bus_property_append_bool, "b", offsetof(Unit, ignore_on_snapshot) },
834 { "DefaultControlGroup", bus_unit_append_default_cgroup, "s", 0 },
835 { "ControlGroup", bus_unit_append_cgroups, "as", 0 },
836 { "ControlGroupAttributes", bus_unit_append_cgroup_attrs,"a(sss)", 0 },
837 { "NeedDaemonReload", bus_unit_append_need_daemon_reload, "b", 0 },
838 { "JobTimeoutUSec", bus_property_append_usec, "t", offsetof(Unit, job_timeout) },
839 { "ConditionTimestamp", bus_property_append_usec, "t", offsetof(Unit, condition_timestamp.realtime) },
840 { "ConditionTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, condition_timestamp.monotonic) },
841 { "ConditionResult", bus_property_append_bool, "b", offsetof(Unit, condition_result) },
842 { "LoadError", bus_unit_append_load_error, "(ss)", 0 },