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"
29 #include "selinux-access.h"
31 const char bus_unit_interface[] _introspect_("Unit") = BUS_UNIT_INTERFACE;
33 #define INVALIDATING_PROPERTIES \
37 "InactiveExitTimestamp\0" \
38 "ActiveEnterTimestamp\0" \
39 "ActiveExitTimestamp\0" \
40 "InactiveEnterTimestamp\0" \
44 static int bus_unit_append_names(DBusMessageIter *i, const char *property, void *data) {
50 if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "s", &sub))
53 SET_FOREACH(t, u->names, j)
54 if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &t))
57 if (!dbus_message_iter_close_container(i, &sub))
63 static int bus_unit_append_following(DBusMessageIter *i, const char *property, void *data) {
71 f = unit_following(u);
74 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d))
80 static int bus_unit_append_dependencies(DBusMessageIter *i, const char *property, void *data) {
86 if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "s", &sub))
90 if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &u->id))
93 if (!dbus_message_iter_close_container(i, &sub))
99 static int bus_unit_append_description(DBusMessageIter *i, const char *property, void *data) {
107 d = unit_description(u);
109 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d))
115 static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_unit_append_load_state, unit_load_state, UnitLoadState);
117 static int bus_unit_append_active_state(DBusMessageIter *i, const char *property, void *data) {
125 state = unit_active_state_to_string(unit_active_state(u));
127 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &state))
133 static int bus_unit_append_sub_state(DBusMessageIter *i, const char *property, void *data) {
141 state = unit_sub_state_to_string(u);
143 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &state))
149 static int bus_unit_append_file_state(DBusMessageIter *i, const char *property, void *data) {
157 state = strempty(unit_file_state_to_string(unit_get_unit_file_state(u)));
159 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &state))
165 static int bus_unit_append_can_start(DBusMessageIter *i, const char *property, void *data) {
173 b = unit_can_start(u) &&
174 !u->refuse_manual_start;
176 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
182 static int bus_unit_append_can_stop(DBusMessageIter *i, const char *property, void *data) {
190 /* On the lower levels we assume that every unit we can start
191 * we can also stop */
193 b = unit_can_start(u) &&
194 !u->refuse_manual_stop;
196 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
202 static int bus_unit_append_can_reload(DBusMessageIter *i, const char *property, void *data) {
210 b = unit_can_reload(u);
212 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
218 static int bus_unit_append_can_isolate(DBusMessageIter *i, const char *property, void *data) {
226 b = unit_can_isolate(u) &&
227 !u->refuse_manual_start;
229 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
235 static int bus_unit_append_job(DBusMessageIter *i, const char *property, void *data) {
244 if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub))
249 if (!(p = job_dbus_path(u->job)))
252 if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT32, &u->job->id) ||
253 !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &p)) {
260 /* No job, so let's fill in some placeholder
261 * data. Since we need to fill in a valid path we
262 * simple point to ourselves. */
264 if (!(p = unit_dbus_path(u)))
267 if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT32, &id) ||
268 !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &p)) {
276 if (!dbus_message_iter_close_container(i, &sub))
282 static int bus_unit_append_default_cgroup(DBusMessageIter *i, const char *property, void *data) {
292 if ((cgb = unit_get_default_cgroup(u))) {
293 if (!(t = cgroup_bonding_to_string(cgb)))
298 success = dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t);
303 return success ? 0 : -ENOMEM;
306 static int bus_unit_append_cgroups(DBusMessageIter *i, const char *property, void *data) {
311 if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "s", &sub))
314 LIST_FOREACH(by_unit, cgb, u->cgroup_bondings) {
318 if (!(t = cgroup_bonding_to_string(cgb)))
321 success = dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &t);
328 if (!dbus_message_iter_close_container(i, &sub))
334 static int bus_unit_append_cgroup_attrs(DBusMessageIter *i, const char *property, void *data) {
337 DBusMessageIter sub, sub2;
339 if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "(sss)", &sub))
342 LIST_FOREACH(by_unit, a, u->cgroup_attributes) {
347 a->map_callback(a->controller, a->name, a->value, &v);
350 dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) &&
351 dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &a->controller) &&
352 dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &a->name) &&
353 dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, v ? &v : &a->value) &&
354 dbus_message_iter_close_container(&sub, &sub2);
362 if (!dbus_message_iter_close_container(i, &sub))
368 static int bus_unit_append_need_daemon_reload(DBusMessageIter *i, const char *property, void *data) {
376 b = unit_need_daemon_reload(u);
378 if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
384 static int bus_unit_append_load_error(DBusMessageIter *i, const char *property, void *data) {
386 const char *name, *message;
393 if (u->load_error != 0) {
394 name = bus_errno_to_dbus(u->load_error);
395 message = strempty(strerror(-u->load_error));
399 if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub) ||
400 !dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name) ||
401 !dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &message) ||
402 !dbus_message_iter_close_container(i, &sub))
408 static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *connection, DBusMessage *message) {
409 DBusMessage *reply = NULL;
410 Manager *m = u->manager;
412 JobType job_type = _JOB_TYPE_INVALID;
414 bool reload_if_possible = false;
417 dbus_error_init(&error);
419 r = selinux_unit_access_check(
423 u->source_path ? u->source_path : u->fragment_path,
426 return bus_send_error_reply(connection, message, &error, r);
428 if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Start"))
429 job_type = JOB_START;
430 else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Stop"))
432 else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Reload"))
433 job_type = JOB_RELOAD;
434 else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Restart"))
435 job_type = JOB_RESTART;
436 else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "TryRestart"))
437 job_type = JOB_TRY_RESTART;
438 else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "ReloadOrRestart")) {
439 reload_if_possible = true;
440 job_type = JOB_RESTART;
441 } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "ReloadOrTryRestart")) {
442 reload_if_possible = true;
443 job_type = JOB_TRY_RESTART;
444 } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Kill")) {
449 if (!dbus_message_get_args(
452 DBUS_TYPE_STRING, &swho,
453 DBUS_TYPE_INT32, &signo,
455 return bus_send_error_reply(connection, message, &error, -EINVAL);
460 who = kill_who_from_string(swho);
462 return bus_send_error_reply(connection, message, &error, -EINVAL);
465 if (signo <= 0 || signo >= _NSIG)
466 return bus_send_error_reply(connection, message, &error, -EINVAL);
468 r = unit_kill(u, who, signo, &error);
470 return bus_send_error_reply(connection, message, &error, r);
472 reply = dbus_message_new_method_return(message);
476 } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "ResetFailed")) {
478 unit_reset_failed(u);
480 if (!(reply = dbus_message_new_method_return(message)))
483 } else if (UNIT_VTABLE(u)->bus_message_handler)
484 return UNIT_VTABLE(u)->bus_message_handler(u, connection, message);
486 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
488 if (job_type != _JOB_TYPE_INVALID) {
493 if ((job_type == JOB_START && u->refuse_manual_start) ||
494 (job_type == JOB_STOP && u->refuse_manual_stop) ||
495 ((job_type == JOB_RESTART || job_type == JOB_TRY_RESTART) &&
496 (u->refuse_manual_start || u->refuse_manual_stop))) {
497 dbus_set_error(&error, BUS_ERROR_ONLY_BY_DEPENDENCY, "Operation refused, may be requested by dependency only.");
498 return bus_send_error_reply(connection, message, &error, -EPERM);
501 if (!dbus_message_get_args(
504 DBUS_TYPE_STRING, &smode,
506 return bus_send_error_reply(connection, message, &error, -EINVAL);
508 if (reload_if_possible && unit_can_reload(u)) {
509 if (job_type == JOB_RESTART)
510 job_type = JOB_RELOAD_OR_START;
511 else if (job_type == JOB_TRY_RESTART)
512 job_type = JOB_RELOAD;
515 if ((mode = job_mode_from_string(smode)) == _JOB_MODE_INVALID) {
516 dbus_set_error(&error, BUS_ERROR_INVALID_JOB_MODE, "Job mode %s is invalid.", smode);
517 return bus_send_error_reply(connection, message, &error, -EINVAL);
520 if ((r = manager_add_job(m, job_type, u, mode, true, &error, &j)) < 0)
521 return bus_send_error_reply(connection, message, &error, r);
523 if (!(reply = dbus_message_new_method_return(message)))
526 if (!(path = job_dbus_path(j)))
529 if (!dbus_message_append_args(
531 DBUS_TYPE_OBJECT_PATH, &path,
537 if (!dbus_connection_send(connection, reply, NULL))
540 dbus_message_unref(reply);
545 return DBUS_HANDLER_RESULT_HANDLED;
551 dbus_message_unref(reply);
553 dbus_error_free(&error);
555 return DBUS_HANDLER_RESULT_NEED_MEMORY;
558 static DBusHandlerResult bus_unit_message_handler(DBusConnection *connection, DBusMessage *message, void *data) {
562 DBusMessage *reply = NULL;
569 dbus_error_init(&error);
571 if (streq(dbus_message_get_path(message), "/org/freedesktop/systemd1/unit")) {
572 /* Be nice to gdbus and return introspection data for our mid-level paths */
574 if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) {
575 char *introspection = NULL;
581 if (!(reply = dbus_message_new_method_return(message)))
584 /* We roll our own introspection code here, instead of
585 * relying on bus_default_message_handler() because we
586 * need to generate our introspection string
589 if (!(f = open_memstream(&introspection, &size)))
592 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
595 fputs(BUS_INTROSPECTABLE_INTERFACE, f);
596 fputs(BUS_PEER_INTERFACE, f);
598 HASHMAP_FOREACH_KEY(u, k, m->units, i) {
604 if (!(p = bus_path_escape(k))) {
610 fprintf(f, "<node name=\"%s\"/>", p);
614 fputs("</node>\n", f);
627 if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID)) {
634 if (!dbus_connection_send(connection, reply, NULL))
637 dbus_message_unref(reply);
639 return DBUS_HANDLER_RESULT_HANDLED;
642 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
645 r = manager_load_unit_from_dbus_path(m, dbus_message_get_path(message), &error, &u);
650 return bus_send_error_reply(connection, message, &error, r);
653 return bus_unit_message_dispatch(u, connection, message);
657 dbus_message_unref(reply);
659 dbus_error_free(&error);
661 return DBUS_HANDLER_RESULT_NEED_MEMORY;
664 const DBusObjectPathVTable bus_unit_vtable = {
665 .message_function = bus_unit_message_handler
668 void bus_unit_send_change_signal(Unit *u) {
670 DBusMessage *m = NULL;
674 if (u->in_dbus_queue) {
675 LIST_REMOVE(Unit, dbus_queue, u->manager->dbus_unit_queue, u);
676 u->in_dbus_queue = false;
682 if (!bus_has_subscriber(u->manager)) {
683 u->sent_dbus_new_signal = true;
687 if (!(p = unit_dbus_path(u)))
690 if (u->sent_dbus_new_signal) {
691 /* Send a properties changed signal. First for the
692 * specific type, then for the generic unit. The
693 * clients may rely on this order to get atomic
694 * behavior if needed. */
696 if (UNIT_VTABLE(u)->bus_invalidating_properties) {
698 if (!(m = bus_properties_changed_new(p,
699 UNIT_VTABLE(u)->bus_interface,
700 UNIT_VTABLE(u)->bus_invalidating_properties)))
703 if (bus_broadcast(u->manager, m) < 0)
706 dbus_message_unref(m);
709 if (!(m = bus_properties_changed_new(p, "org.freedesktop.systemd1.Unit", INVALIDATING_PROPERTIES)))
713 /* Send a new signal */
715 if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnitNew")))
718 if (!dbus_message_append_args(m,
719 DBUS_TYPE_STRING, &u->id,
720 DBUS_TYPE_OBJECT_PATH, &p,
725 if (bus_broadcast(u->manager, m) < 0)
729 dbus_message_unref(m);
731 u->sent_dbus_new_signal = true;
739 dbus_message_unref(m);
741 log_error("Failed to allocate unit change/new signal.");
744 void bus_unit_send_removed_signal(Unit *u) {
746 DBusMessage *m = NULL;
750 if (!bus_has_subscriber(u->manager))
753 if (!u->sent_dbus_new_signal)
754 bus_unit_send_change_signal(u);
759 if (!(p = unit_dbus_path(u)))
762 if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnitRemoved")))
765 if (!dbus_message_append_args(m,
766 DBUS_TYPE_STRING, &u->id,
767 DBUS_TYPE_OBJECT_PATH, &p,
771 if (bus_broadcast(u->manager, m) < 0)
775 dbus_message_unref(m);
783 dbus_message_unref(m);
785 log_error("Failed to allocate unit remove signal.");
788 const BusProperty bus_unit_properties[] = {
789 { "Id", bus_property_append_string, "s", offsetof(Unit, id), true },
790 { "Names", bus_unit_append_names, "as", 0 },
791 { "Following", bus_unit_append_following, "s", 0 },
792 { "Requires", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRES]), true },
793 { "RequiresOverridable", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRES_OVERRIDABLE]), true },
794 { "Requisite", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUISITE]), true },
795 { "RequisiteOverridable", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUISITE_OVERRIDABLE]), true },
796 { "Wants", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_WANTS]), true },
797 { "BindsTo", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_BINDS_TO]), true },
798 { "PartOf", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_PART_OF]), true },
799 { "RequiredBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRED_BY]), true },
800 { "RequiredByOverridable",bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_REQUIRED_BY_OVERRIDABLE]), true },
801 { "WantedBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_WANTED_BY]), true },
802 { "BoundBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_BOUND_BY]), true },
803 { "ConsistsOf", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_CONSISTS_OF]), true },
804 { "Conflicts", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_CONFLICTS]), true },
805 { "ConflictedBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_CONFLICTED_BY]), true },
806 { "Before", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_BEFORE]), true },
807 { "After", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_AFTER]), true },
808 { "OnFailure", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_ON_FAILURE]), true },
809 { "Triggers", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_TRIGGERS]), true },
810 { "TriggeredBy", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_TRIGGERED_BY]), true },
811 { "PropagatesReloadTo", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_PROPAGATES_RELOAD_TO]), true },
812 { "ReloadPropagatedFrom", bus_unit_append_dependencies, "as", offsetof(Unit, dependencies[UNIT_RELOAD_PROPAGATED_FROM]), true },
813 { "RequiresMountsFor", bus_property_append_strv, "as", offsetof(Unit, requires_mounts_for), true },
814 { "Documentation", bus_property_append_strv, "as", offsetof(Unit, documentation), true },
815 { "Description", bus_unit_append_description, "s", 0 },
816 { "LoadState", bus_unit_append_load_state, "s", offsetof(Unit, load_state) },
817 { "ActiveState", bus_unit_append_active_state, "s", 0 },
818 { "SubState", bus_unit_append_sub_state, "s", 0 },
819 { "FragmentPath", bus_property_append_string, "s", offsetof(Unit, fragment_path), true },
820 { "SourcePath", bus_property_append_string, "s", offsetof(Unit, source_path), true },
821 { "UnitFileState", bus_unit_append_file_state, "s", 0 },
822 { "InactiveExitTimestamp",bus_property_append_usec, "t", offsetof(Unit, inactive_exit_timestamp.realtime) },
823 { "InactiveExitTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, inactive_exit_timestamp.monotonic) },
824 { "ActiveEnterTimestamp", bus_property_append_usec, "t", offsetof(Unit, active_enter_timestamp.realtime) },
825 { "ActiveEnterTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, active_enter_timestamp.monotonic) },
826 { "ActiveExitTimestamp", bus_property_append_usec, "t", offsetof(Unit, active_exit_timestamp.realtime) },
827 { "ActiveExitTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, active_exit_timestamp.monotonic) },
828 { "InactiveEnterTimestamp", bus_property_append_usec, "t", offsetof(Unit, inactive_enter_timestamp.realtime) },
829 { "InactiveEnterTimestampMonotonic",bus_property_append_usec, "t", offsetof(Unit, inactive_enter_timestamp.monotonic) },
830 { "CanStart", bus_unit_append_can_start, "b", 0 },
831 { "CanStop", bus_unit_append_can_stop, "b", 0 },
832 { "CanReload", bus_unit_append_can_reload, "b", 0 },
833 { "CanIsolate", bus_unit_append_can_isolate, "b", 0 },
834 { "Job", bus_unit_append_job, "(uo)", 0 },
835 { "StopWhenUnneeded", bus_property_append_bool, "b", offsetof(Unit, stop_when_unneeded) },
836 { "RefuseManualStart", bus_property_append_bool, "b", offsetof(Unit, refuse_manual_start) },
837 { "RefuseManualStop", bus_property_append_bool, "b", offsetof(Unit, refuse_manual_stop) },
838 { "AllowIsolate", bus_property_append_bool, "b", offsetof(Unit, allow_isolate) },
839 { "DefaultDependencies", bus_property_append_bool, "b", offsetof(Unit, default_dependencies) },
840 { "OnFailureIsolate", bus_property_append_bool, "b", offsetof(Unit, on_failure_isolate) },
841 { "IgnoreOnIsolate", bus_property_append_bool, "b", offsetof(Unit, ignore_on_isolate) },
842 { "IgnoreOnSnapshot", bus_property_append_bool, "b", offsetof(Unit, ignore_on_snapshot) },
843 { "DefaultControlGroup", bus_unit_append_default_cgroup, "s", 0 },
844 { "ControlGroup", bus_unit_append_cgroups, "as", 0 },
845 { "ControlGroupAttributes", bus_unit_append_cgroup_attrs,"a(sss)", 0 },
846 { "NeedDaemonReload", bus_unit_append_need_daemon_reload, "b", 0 },
847 { "JobTimeoutUSec", bus_property_append_usec, "t", offsetof(Unit, job_timeout) },
848 { "ConditionTimestamp", bus_property_append_usec, "t", offsetof(Unit, condition_timestamp.realtime) },
849 { "ConditionTimestampMonotonic", bus_property_append_usec, "t", offsetof(Unit, condition_timestamp.monotonic) },
850 { "ConditionResult", bus_property_append_bool, "b", offsetof(Unit, condition_result) },
851 { "LoadError", bus_unit_append_load_error, "(ss)", 0 },