1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2011 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/>.
27 #include <systemd/sd-id128.h>
28 #include <systemd/sd-messages.h>
31 #include "dbus-common.h"
34 #include "path-util.h"
36 #include "sleep-config.h"
37 #include "fileio-label.h"
40 #include "unit-name.h"
41 #include "bus-errors.h"
44 #define BUS_MANAGER_INTERFACE \
45 " <interface name=\"org.freedesktop.machine1.Manager\">\n" \
46 " <method name=\"GetMachine\">\n" \
47 " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
48 " <arg name=\"machine\" type=\"o\" direction=\"out\"/>\n" \
50 " <method name=\"GetMachineByPID\">\n" \
51 " <arg name=\"pid\" type=\"u\" direction=\"in\"/>\n" \
52 " <arg name=\"machine\" type=\"o\" direction=\"out\"/>\n" \
54 " <method name=\"ListMachines\">\n" \
55 " <arg name=\"machines\" type=\"a(ssso)\" direction=\"out\"/>\n" \
57 " <method name=\"CreateMachine\">\n" \
58 " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
59 " <arg name=\"id\" type=\"ay\" direction=\"in\"/>\n" \
60 " <arg name=\"service\" type=\"s\" direction=\"in\"/>\n" \
61 " <arg name=\"class\" type=\"s\" direction=\"in\"/>\n" \
62 " <arg name=\"leader\" type=\"u\" direction=\"in\"/>\n" \
63 " <arg name=\"root_directory\" type=\"s\" direction=\"in\"/>\n" \
64 " <arg name=\"scope_properties\" type=\"a(sv)\" direction=\"in\"/>\n" \
65 " <arg name=\"path\" type=\"o\" direction=\"out\"/>\n" \
67 " <method name=\"KillMachine\">\n" \
68 " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
69 " <arg name=\"who\" type=\"s\" direction=\"in\"/>\n" \
70 " <arg name=\"signal\" type=\"s\" direction=\"in\"/>\n" \
72 " <method name=\"TerminateMachine\">\n" \
73 " <arg name=\"id\" type=\"s\" direction=\"in\"/>\n" \
75 " <signal name=\"MachineNew\">\n" \
76 " <arg name=\"machine\" type=\"s\"/>\n" \
77 " <arg name=\"path\" type=\"o\"/>\n" \
79 " <signal name=\"MachineRemoved\">\n" \
80 " <arg name=\"machine\" type=\"s\"/>\n" \
81 " <arg name=\"path\" type=\"o\"/>\n" \
85 #define INTROSPECTION_BEGIN \
86 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
88 BUS_MANAGER_INTERFACE \
89 BUS_PROPERTIES_INTERFACE \
91 BUS_INTROSPECTABLE_INTERFACE
93 #define INTROSPECTION_END \
96 #define INTERFACES_LIST \
97 BUS_GENERIC_INTERFACES_LIST \
98 "org.freedesktop.machine1.Manager\0"
100 static bool valid_machine_name(const char *p) {
103 if (!filename_is_safe(p))
106 if (!ascii_is_valid(p))
117 static int bus_manager_create_machine(Manager *manager, DBusMessage *message) {
119 const char *name, *service, *class, *root_directory;
120 _cleanup_free_ char *p = NULL;
121 DBusMessageIter iter, sub;
132 if (!dbus_message_iter_init(message, &iter) ||
133 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
136 dbus_message_iter_get_basic(&iter, &name);
138 if (!valid_machine_name(name) ||
139 !dbus_message_iter_next(&iter) ||
140 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
141 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_BYTE)
144 dbus_message_iter_recurse(&iter, &sub);
145 dbus_message_iter_get_fixed_array(&sub, &v, &n);
154 if (!dbus_message_iter_next(&iter) ||
155 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
158 dbus_message_iter_get_basic(&iter, &service);
160 if (!dbus_message_iter_next(&iter) ||
161 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
164 dbus_message_iter_get_basic(&iter, &class);
167 c = _MACHINE_CLASS_INVALID;
169 c = machine_class_from_string(class);
174 if (!dbus_message_iter_next(&iter) ||
175 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32)
178 dbus_message_iter_get_basic(&iter, &leader);
179 if (!dbus_message_iter_next(&iter) ||
180 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
183 dbus_message_iter_get_basic(&iter, &root_directory);
185 if (!(isempty(root_directory) || path_is_absolute(root_directory)))
188 if (hashmap_get(manager->machines, name))
192 leader = bus_get_unix_process_id(manager->bus, dbus_message_get_sender(message), NULL);
197 r = manager_add_machine(manager, name, &m);
205 if (!isempty(service)) {
206 m->service = strdup(service);
213 if (!isempty(root_directory)) {
214 m->root_directory = strdup(root_directory);
215 if (!m->root_directory) {
221 r = machine_start(m);
225 m->create_message = dbus_message_ref(message);
231 machine_add_to_gc_queue(m);
236 static DBusHandlerResult manager_message_handler(
237 DBusConnection *connection,
238 DBusMessage *message,
241 Manager *m = userdata;
244 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
251 dbus_error_init(&error);
253 if (dbus_message_is_method_call(message, "org.freedesktop.machine1.Manager", "GetMachine")) {
259 if (!dbus_message_get_args(
262 DBUS_TYPE_STRING, &name,
264 return bus_send_error_reply(connection, message, &error, -EINVAL);
266 machine = hashmap_get(m->machines, name);
268 return bus_send_error_reply(connection, message, &error, -ENOENT);
270 reply = dbus_message_new_method_return(message);
274 p = machine_bus_path(machine);
278 b = dbus_message_append_args(
280 DBUS_TYPE_OBJECT_PATH, &p,
287 } else if (dbus_message_is_method_call(message, "org.freedesktop.machine1.Manager", "GetMachineByPID")) {
293 if (!dbus_message_get_args(
296 DBUS_TYPE_UINT32, &pid,
298 return bus_send_error_reply(connection, message, &error, -EINVAL);
300 r = manager_get_machine_by_pid(m, pid, &machine);
302 return bus_send_error_reply(connection, message, NULL, r < 0 ? r : -ENOENT);
304 reply = dbus_message_new_method_return(message);
308 p = machine_bus_path(machine);
312 b = dbus_message_append_args(
314 DBUS_TYPE_OBJECT_PATH, &p,
321 } else if (dbus_message_is_method_call(message, "org.freedesktop.machine1.Manager", "ListMachines")) {
324 DBusMessageIter iter, sub;
326 reply = dbus_message_new_method_return(message);
330 dbus_message_iter_init_append(reply, &iter);
332 if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(ssso)", &sub))
335 HASHMAP_FOREACH(machine, m->machines, i) {
336 _cleanup_free_ char *p = NULL;
337 DBusMessageIter sub2;
340 if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2))
343 p = machine_bus_path(machine);
347 class = strempty(machine_class_to_string(machine->class));
349 if (!dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &machine->name) ||
350 !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &class) ||
351 !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &machine->service) ||
352 !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &p)) {
357 if (!dbus_message_iter_close_container(&sub, &sub2))
361 if (!dbus_message_iter_close_container(&iter, &sub))
364 } else if (dbus_message_is_method_call(message, "org.freedesktop.machine1.Manager", "CreateMachine")) {
366 r = bus_manager_create_machine(m, message);
368 return bus_send_error_reply(connection, message, NULL, r);
370 } else if (dbus_message_is_method_call(message, "org.freedesktop.machine1.Manager", "KillMachine")) {
377 if (!dbus_message_get_args(
380 DBUS_TYPE_STRING, &name,
381 DBUS_TYPE_STRING, &swho,
382 DBUS_TYPE_INT32, &signo,
384 return bus_send_error_reply(connection, message, &error, -EINVAL);
389 who = kill_who_from_string(swho);
391 return bus_send_error_reply(connection, message, &error, -EINVAL);
394 if (signo <= 0 || signo >= _NSIG)
395 return bus_send_error_reply(connection, message, &error, -EINVAL);
397 machine = hashmap_get(m->machines, name);
399 return bus_send_error_reply(connection, message, &error, -ENOENT);
401 r = machine_kill(machine, who, signo);
403 return bus_send_error_reply(connection, message, NULL, r);
405 reply = dbus_message_new_method_return(message);
409 } else if (dbus_message_is_method_call(message, "org.freedesktop.machine1.Manager", "TerminateMachine")) {
413 if (!dbus_message_get_args(
416 DBUS_TYPE_STRING, &name,
418 return bus_send_error_reply(connection, message, &error, -EINVAL);
420 machine = hashmap_get(m->machines, name);
422 return bus_send_error_reply(connection, message, &error, -ENOENT);
424 r = machine_stop(machine);
426 return bus_send_error_reply(connection, message, NULL, r);
428 reply = dbus_message_new_method_return(message);
432 } else if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) {
433 char *introspection = NULL;
440 reply = dbus_message_new_method_return(message);
444 /* We roll our own introspection code here, instead of
445 * relying on bus_default_message_handler() because we
446 * need to generate our introspection string
449 f = open_memstream(&introspection, &size);
453 fputs(INTROSPECTION_BEGIN, f);
455 HASHMAP_FOREACH(machine, m->machines, i) {
456 p = bus_path_escape(machine->name);
459 fprintf(f, "<node name=\"machine/%s\"/>", p);
464 fputs(INTROSPECTION_END, f);
477 if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID)) {
484 return bus_default_message_handler(connection, message, NULL, INTERFACES_LIST, NULL);
487 if (!bus_maybe_send_reply(connection, message, reply))
491 return DBUS_HANDLER_RESULT_HANDLED;
494 dbus_error_free(&error);
496 return DBUS_HANDLER_RESULT_NEED_MEMORY;
499 const DBusObjectPathVTable bus_manager_vtable = {
500 .message_function = manager_message_handler
503 DBusHandlerResult bus_message_filter(
504 DBusConnection *connection,
505 DBusMessage *message,
508 Manager *m = userdata;
515 dbus_error_init(&error);
517 log_debug("Got message: %s %s %s", strna(dbus_message_get_sender(message)), strna(dbus_message_get_interface(message)), strna(dbus_message_get_member(message)));
519 if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Manager", "JobRemoved")) {
520 const char *path, *result, *unit;
524 if (!dbus_message_get_args(message, &error,
525 DBUS_TYPE_UINT32, &id,
526 DBUS_TYPE_OBJECT_PATH, &path,
527 DBUS_TYPE_STRING, &unit,
528 DBUS_TYPE_STRING, &result,
529 DBUS_TYPE_INVALID)) {
530 log_error("Failed to parse JobRemoved message: %s", bus_error_message(&error));
534 mm = hashmap_get(m->machine_units, unit);
536 if (streq_ptr(path, mm->scope_job)) {
538 mm->scope_job = NULL;
542 if (streq(result, "done"))
543 machine_send_create_reply(mm, NULL);
545 dbus_set_error(&error, BUS_ERROR_JOB_FAILED, "Start job for unit %s failed with '%s'", unit, result);
546 machine_send_create_reply(mm, &error);
551 machine_add_to_gc_queue(mm);
554 } else if (dbus_message_is_signal(message, "org.freedesktop.DBus.Properties", "PropertiesChanged")) {
556 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
557 _cleanup_free_ char *unit = NULL;
560 path = dbus_message_get_path(message);
564 unit_name_from_dbus_path(path, &unit);
568 mm = hashmap_get(m->machine_units, unit);
570 machine_add_to_gc_queue(mm);
573 } else if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Manager", "UnitRemoved")) {
574 const char *path, *unit;
577 if (!dbus_message_get_args(message, &error,
578 DBUS_TYPE_STRING, &unit,
579 DBUS_TYPE_OBJECT_PATH, &path,
580 DBUS_TYPE_INVALID)) {
581 log_error("Failed to parse UnitRemoved message: %s", bus_error_message(&error));
585 mm = hashmap_get(m->machine_units, unit);
587 hashmap_remove(m->machine_units, mm->scope);
591 machine_add_to_gc_queue(mm);
596 dbus_error_free(&error);
598 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
601 int manager_start_scope(
606 const char *description,
610 _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL;
611 DBusMessageIter iter, sub, sub2, sub3, sub4;
612 const char *timeout_stop_property = "TimeoutStopUSec";
613 const char *pids_property = "PIDs";
614 uint64_t timeout = 500 * USEC_PER_MSEC;
615 const char *fail = "fail";
625 m = dbus_message_new_method_call(
626 "org.freedesktop.systemd1",
627 "/org/freedesktop/systemd1",
628 "org.freedesktop.systemd1.Manager",
629 "StartTransientUnit");
633 dbus_message_iter_init_append(m, &iter);
635 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &scope) ||
636 !dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &fail) ||
637 !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(sv)", &sub))
640 if (!isempty(slice)) {
641 const char *slice_property = "Slice";
643 if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) ||
644 !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &slice_property) ||
645 !dbus_message_iter_open_container(&sub2, DBUS_TYPE_VARIANT, "s", &sub3) ||
646 !dbus_message_iter_append_basic(&sub3, DBUS_TYPE_STRING, &slice) ||
647 !dbus_message_iter_close_container(&sub2, &sub3) ||
648 !dbus_message_iter_close_container(&sub, &sub2))
652 if (!isempty(description)) {
653 const char *description_property = "Description";
655 if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) ||
656 !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &description_property) ||
657 !dbus_message_iter_open_container(&sub2, DBUS_TYPE_VARIANT, "s", &sub3) ||
658 !dbus_message_iter_append_basic(&sub3, DBUS_TYPE_STRING, &description) ||
659 !dbus_message_iter_close_container(&sub2, &sub3) ||
660 !dbus_message_iter_close_container(&sub, &sub2))
664 /* cgroup empty notification is not available in containers
665 * currently. To make this less problematic, let's shorten the
666 * stop timeout for sessions, so that we don't wait
669 if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) ||
670 !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &timeout_stop_property) ||
671 !dbus_message_iter_open_container(&sub2, DBUS_TYPE_VARIANT, "t", &sub3) ||
672 !dbus_message_iter_append_basic(&sub3, DBUS_TYPE_UINT64, &timeout) ||
673 !dbus_message_iter_close_container(&sub2, &sub3) ||
674 !dbus_message_iter_close_container(&sub, &sub2))
678 if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2) ||
679 !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &pids_property) ||
680 !dbus_message_iter_open_container(&sub2, DBUS_TYPE_VARIANT, "au", &sub3) ||
681 !dbus_message_iter_open_container(&sub3, DBUS_TYPE_ARRAY, "u", &sub4) ||
682 !dbus_message_iter_append_basic(&sub4, DBUS_TYPE_UINT32, &u) ||
683 !dbus_message_iter_close_container(&sub3, &sub4) ||
684 !dbus_message_iter_close_container(&sub2, &sub3) ||
685 !dbus_message_iter_close_container(&sub, &sub2) ||
686 !dbus_message_iter_close_container(&iter, &sub))
689 reply = dbus_connection_send_with_reply_and_block(manager->bus, m, -1, error);
697 if (!dbus_message_get_args(reply, error, DBUS_TYPE_OBJECT_PATH, &j, DBUS_TYPE_INVALID))
710 int manager_stop_unit(Manager *manager, const char *unit, DBusError *error, char **job) {
711 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
712 const char *fail = "fail";
718 r = bus_method_call_with_reply(
720 "org.freedesktop.systemd1",
721 "/org/freedesktop/systemd1",
722 "org.freedesktop.systemd1.Manager",
726 DBUS_TYPE_STRING, &unit,
727 DBUS_TYPE_STRING, &fail,
730 log_error("Failed to stop unit %s: %s", unit, bus_error(error, r));
738 if (!dbus_message_get_args(reply, error,
739 DBUS_TYPE_OBJECT_PATH, &j,
740 DBUS_TYPE_INVALID)) {
741 log_error("Failed to parse reply.");
755 int manager_kill_unit(Manager *manager, const char *unit, KillWho who, int signo, DBusError *error) {
756 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
763 w = who == KILL_LEADER ? "process" : "cgroup";
764 assert_cc(sizeof(signo) == sizeof(int32_t));
766 r = bus_method_call_with_reply(
768 "org.freedesktop.systemd1",
769 "/org/freedesktop/systemd1",
770 "org.freedesktop.systemd1.Manager",
774 DBUS_TYPE_STRING, &unit,
775 DBUS_TYPE_STRING, &w,
776 DBUS_TYPE_INT32, &signo,
779 log_error("Failed to stop unit %s: %s", unit, bus_error(error, r));
786 int manager_unit_is_active(Manager *manager, const char *unit) {
788 const char *interface = "org.freedesktop.systemd1.Unit";
789 const char *property = "ActiveState";
790 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
791 _cleanup_free_ char *path = NULL;
792 DBusMessageIter iter, sub;
800 dbus_error_init(&error);
802 path = unit_dbus_path_from_name(unit);
806 r = bus_method_call_with_reply(
808 "org.freedesktop.systemd1",
810 "org.freedesktop.DBus.Properties",
814 DBUS_TYPE_STRING, &interface,
815 DBUS_TYPE_STRING, &property,
819 log_error("Failed to query ActiveState: %s", bus_error(&error, r));
820 dbus_error_free(&error);
824 if (!dbus_message_iter_init(reply, &iter) ||
825 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
826 log_error("Failed to parse reply.");
830 dbus_message_iter_recurse(&iter, &sub);
831 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) {
832 log_error("Failed to parse reply.");
836 dbus_message_iter_get_basic(&sub, &state);
838 return !streq(state, "inactive") && !streq(state, "failed");