summaryrefslogtreecommitdiff
path: root/src/grp-machine/libmachine-core
diff options
context:
space:
mode:
Diffstat (limited to 'src/grp-machine/libmachine-core')
-rw-r--r--src/grp-machine/libmachine-core/include/machine-core/image-dbus.h35
-rw-r--r--src/grp-machine/libmachine-core/include/machine-core/machine-dbus.h44
-rw-r--r--src/grp-machine/libmachine-core/include/machine-core/machine.h111
-rw-r--r--src/grp-machine/libmachine-core/include/machine-core/machined.h82
-rw-r--r--src/grp-machine/libmachine-core/include/machine-core/operation.h49
-rw-r--r--src/grp-machine/libmachine-core/src/image-dbus.c422
-rw-r--r--src/grp-machine/libmachine-core/src/machine-dbus.c1472
-rw-r--r--src/grp-machine/libmachine-core/src/machine.c630
-rw-r--r--src/grp-machine/libmachine-core/src/machined-dbus.c1804
-rw-r--r--src/grp-machine/libmachine-core/src/operation.c152
-rw-r--r--src/grp-machine/libmachine-core/test/test-machine-tables.c29
11 files changed, 4830 insertions, 0 deletions
diff --git a/src/grp-machine/libmachine-core/include/machine-core/image-dbus.h b/src/grp-machine/libmachine-core/include/machine-core/image-dbus.h
new file mode 100644
index 0000000000..b62da996c6
--- /dev/null
+++ b/src/grp-machine/libmachine-core/include/machine-core/image-dbus.h
@@ -0,0 +1,35 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "machined.h"
+
+extern const sd_bus_vtable image_vtable[];
+
+char *image_bus_path(const char *name);
+
+int image_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error);
+int image_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error);
+
+int bus_image_method_remove(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int bus_image_method_rename(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int bus_image_method_clone(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int bus_image_method_mark_read_only(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int bus_image_method_set_limit(sd_bus_message *message, void *userdata, sd_bus_error *error);
diff --git a/src/grp-machine/libmachine-core/include/machine-core/machine-dbus.h b/src/grp-machine/libmachine-core/include/machine-core/machine-dbus.h
new file mode 100644
index 0000000000..d3faf5cb07
--- /dev/null
+++ b/src/grp-machine/libmachine-core/include/machine-core/machine-dbus.h
@@ -0,0 +1,44 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <systemd/sd-bus.h>
+
+#include "machine.h"
+
+extern const sd_bus_vtable machine_vtable[];
+
+char *machine_bus_path(Machine *s);
+int machine_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error);
+int machine_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error);
+
+int bus_machine_method_terminate(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int bus_machine_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int bus_machine_method_get_addresses(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int bus_machine_method_open_pty(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int bus_machine_method_open_login(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int bus_machine_method_open_root_directory(sd_bus_message *message, void *userdata, sd_bus_error *error);
+
+int machine_send_signal(Machine *m, bool new_machine);
+int machine_send_create_reply(Machine *m, sd_bus_error *error);
diff --git a/src/grp-machine/libmachine-core/include/machine-core/machine.h b/src/grp-machine/libmachine-core/include/machine-core/machine.h
new file mode 100644
index 0000000000..361451b1c8
--- /dev/null
+++ b/src/grp-machine/libmachine-core/include/machine-core/machine.h
@@ -0,0 +1,111 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "systemd-basic/list.h"
+
+typedef enum KillWho KillWho;
+typedef struct Machine Machine;
+
+#include "machined.h"
+#include "operation.h"
+
+typedef enum MachineState {
+ MACHINE_OPENING, /* Machine is being registered */
+ MACHINE_RUNNING, /* Machine is running */
+ MACHINE_CLOSING, /* Machine is terminating */
+ _MACHINE_STATE_MAX,
+ _MACHINE_STATE_INVALID = -1
+} MachineState;
+
+typedef enum MachineClass {
+ MACHINE_CONTAINER,
+ MACHINE_VM,
+ MACHINE_HOST,
+ _MACHINE_CLASS_MAX,
+ _MACHINE_CLASS_INVALID = -1
+} MachineClass;
+
+enum KillWho {
+ KILL_LEADER,
+ KILL_ALL,
+ _KILL_WHO_MAX,
+ _KILL_WHO_INVALID = -1
+};
+
+struct Machine {
+ Manager *manager;
+
+ char *name;
+ sd_id128_t id;
+
+ MachineClass class;
+
+ char *state_file;
+ char *service;
+ char *root_directory;
+
+ char *unit;
+ char *scope_job;
+
+ pid_t leader;
+
+ dual_timestamp timestamp;
+
+ bool in_gc_queue:1;
+ bool started:1;
+ bool stopping:1;
+
+ sd_bus_message *create_message;
+
+ int *netif;
+ unsigned n_netif;
+
+ LIST_HEAD(Operation, operations);
+
+ LIST_FIELDS(Machine, gc_queue);
+};
+
+Machine* machine_new(Manager *manager, MachineClass class, const char *name);
+void machine_free(Machine *m);
+bool machine_check_gc(Machine *m, bool drop_not_started);
+void machine_add_to_gc_queue(Machine *m);
+int machine_start(Machine *m, sd_bus_message *properties, sd_bus_error *error);
+int machine_stop(Machine *m);
+int machine_finalize(Machine *m);
+int machine_save(Machine *m);
+int machine_load(Machine *m);
+int machine_kill(Machine *m, KillWho who, int signo);
+
+void machine_release_unit(Machine *m);
+
+MachineState machine_get_state(Machine *u);
+
+const char* machine_class_to_string(MachineClass t) _const_;
+MachineClass machine_class_from_string(const char *s) _pure_;
+
+const char* machine_state_to_string(MachineState t) _const_;
+MachineState machine_state_from_string(const char *s) _pure_;
+
+const char *kill_who_to_string(KillWho k) _const_;
+KillWho kill_who_from_string(const char *s) _pure_;
+
+int machine_openpt(Machine *m, int flags);
+int machine_open_terminal(Machine *m, const char *path, int mode);
diff --git a/src/grp-machine/libmachine-core/include/machine-core/machined.h b/src/grp-machine/libmachine-core/include/machine-core/machined.h
new file mode 100644
index 0000000000..8994097db0
--- /dev/null
+++ b/src/grp-machine/libmachine-core/include/machine-core/machined.h
@@ -0,0 +1,82 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+#include <systemd/sd-bus.h>
+#include <systemd/sd-event.h>
+
+#include "systemd-basic/hashmap.h"
+#include "systemd-basic/list.h"
+
+typedef struct Manager Manager;
+
+#include "image-dbus.h"
+#include "machine-dbus.h"
+#include "machine.h"
+#include "operation.h"
+
+struct Manager {
+ sd_event *event;
+ sd_bus *bus;
+
+ Hashmap *machines;
+ Hashmap *machine_units;
+ Hashmap *machine_leaders;
+
+ Hashmap *polkit_registry;
+
+ Hashmap *image_cache;
+ sd_event_source *image_cache_defer_event;
+
+ LIST_HEAD(Machine, machine_gc_queue);
+
+ Machine *host_machine;
+
+ LIST_HEAD(Operation, operations);
+ unsigned n_operations;
+};
+
+Manager *manager_new(void);
+void manager_free(Manager *m);
+
+int manager_add_machine(Manager *m, const char *name, Machine **_machine);
+int manager_enumerate_machines(Manager *m);
+
+int manager_startup(Manager *m);
+int manager_run(Manager *m);
+
+void manager_gc(Manager *m, bool drop_not_started);
+
+int manager_get_machine_by_pid(Manager *m, pid_t pid, Machine **machine);
+
+extern const sd_bus_vtable manager_vtable[];
+
+int match_reloading(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int match_unit_removed(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int match_properties_changed(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *error);
+
+int manager_start_scope(Manager *manager, const char *scope, pid_t pid, const char *slice, const char *description, sd_bus_message *more_properties, sd_bus_error *error, char **job);
+int manager_stop_unit(Manager *manager, const char *unit, sd_bus_error *error, char **job);
+int manager_kill_unit(Manager *manager, const char *unit, int signo, sd_bus_error *error);
+int manager_unit_is_active(Manager *manager, const char *unit);
+int manager_job_is_active(Manager *manager, const char *path);
diff --git a/src/grp-machine/libmachine-core/include/machine-core/operation.h b/src/grp-machine/libmachine-core/include/machine-core/operation.h
new file mode 100644
index 0000000000..5eed62bd6f
--- /dev/null
+++ b/src/grp-machine/libmachine-core/include/machine-core/operation.h
@@ -0,0 +1,49 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2016 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+
+#include <systemd/sd-bus.h>
+#include <systemd/sd-event.h>
+
+#include "systemd-basic/list.h"
+
+typedef struct Operation Operation;
+
+#include "machined.h"
+
+#define OPERATIONS_MAX 64
+
+struct Operation {
+ Manager *manager;
+ Machine *machine;
+ pid_t pid;
+ sd_bus_message *message;
+ int errno_fd;
+ int extra_fd;
+ sd_event_source *event_source;
+ int (*done)(Operation *o, int ret, sd_bus_error *error);
+ LIST_FIELDS(Operation, operations);
+ LIST_FIELDS(Operation, operations_by_machine);
+};
+
+int operation_new(Manager *manager, Machine *machine, pid_t child, sd_bus_message *message, int errno_fd, Operation **ret);
+Operation *operation_free(Operation *o);
diff --git a/src/grp-machine/libmachine-core/src/image-dbus.c b/src/grp-machine/libmachine-core/src/image-dbus.c
new file mode 100644
index 0000000000..cee2d2d7f2
--- /dev/null
+++ b/src/grp-machine/libmachine-core/src/image-dbus.c
@@ -0,0 +1,422 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "machine-core/image-dbus.h"
+#include "systemd-basic/alloc-util.h"
+#include "systemd-basic/bus-label.h"
+#include "systemd-basic/fd-util.h"
+#include "systemd-basic/io-util.h"
+#include "systemd-basic/process-util.h"
+#include "systemd-basic/strv.h"
+#include "systemd-basic/user-util.h"
+#include "systemd-shared/bus-util.h"
+#include "systemd-shared/machine-image.h"
+
+static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, image_type, ImageType);
+
+int bus_image_method_remove(
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error) {
+
+ _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 };
+ Image *image = userdata;
+ Manager *m = image->userdata;
+ pid_t child;
+ int r;
+
+ assert(message);
+ assert(image);
+
+ if (m->n_operations >= OPERATIONS_MAX)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing operations.");
+
+ r = bus_verify_polkit_async(
+ message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.machine1.manage-images",
+ NULL,
+ false,
+ UID_INVALID,
+ &m->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0)
+ return sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m");
+
+ child = fork();
+ if (child < 0)
+ return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m");
+ if (child == 0) {
+ errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
+
+ r = image_remove(image);
+ if (r < 0) {
+ (void) write(errno_pipe_fd[1], &r, sizeof(r));
+ _exit(EXIT_FAILURE);
+ }
+
+ _exit(EXIT_SUCCESS);
+ }
+
+ errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
+
+ r = operation_new(m, NULL, child, message, errno_pipe_fd[0], NULL);
+ if (r < 0) {
+ (void) sigkill_wait(child);
+ return r;
+ }
+
+ errno_pipe_fd[0] = -1;
+
+ return 1;
+}
+
+int bus_image_method_rename(
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Image *image = userdata;
+ Manager *m = image->userdata;
+ const char *new_name;
+ int r;
+
+ assert(message);
+ assert(image);
+
+ r = sd_bus_message_read(message, "s", &new_name);
+ if (r < 0)
+ return r;
+
+ if (!image_name_is_valid(new_name))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", new_name);
+
+ r = bus_verify_polkit_async(
+ message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.machine1.manage-images",
+ NULL,
+ false,
+ UID_INVALID,
+ &m->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ r = image_rename(image, new_name);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+int bus_image_method_clone(
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error) {
+
+ _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 };
+ Image *image = userdata;
+ Manager *m = image->userdata;
+ const char *new_name;
+ int r, read_only;
+ pid_t child;
+
+ assert(message);
+ assert(image);
+ assert(m);
+
+ if (m->n_operations >= OPERATIONS_MAX)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing operations.");
+
+ r = sd_bus_message_read(message, "sb", &new_name, &read_only);
+ if (r < 0)
+ return r;
+
+ if (!image_name_is_valid(new_name))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", new_name);
+
+ r = bus_verify_polkit_async(
+ message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.machine1.manage-images",
+ NULL,
+ false,
+ UID_INVALID,
+ &m->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0)
+ return sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m");
+
+ child = fork();
+ if (child < 0)
+ return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m");
+ if (child == 0) {
+ errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
+
+ r = image_clone(image, new_name, read_only);
+ if (r < 0) {
+ (void) write(errno_pipe_fd[1], &r, sizeof(r));
+ _exit(EXIT_FAILURE);
+ }
+
+ _exit(EXIT_SUCCESS);
+ }
+
+ errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
+
+ r = operation_new(m, NULL, child, message, errno_pipe_fd[0], NULL);
+ if (r < 0) {
+ (void) sigkill_wait(child);
+ return r;
+ }
+
+ errno_pipe_fd[0] = -1;
+
+ return 1;
+}
+
+int bus_image_method_mark_read_only(
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Image *image = userdata;
+ Manager *m = image->userdata;
+ int r, read_only;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "b", &read_only);
+ if (r < 0)
+ return r;
+
+ r = bus_verify_polkit_async(
+ message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.machine1.manage-images",
+ NULL,
+ false,
+ UID_INVALID,
+ &m->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ r = image_read_only(image, read_only);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+int bus_image_method_set_limit(
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Image *image = userdata;
+ Manager *m = image->userdata;
+ uint64_t limit;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "t", &limit);
+ if (r < 0)
+ return r;
+ if (!FILE_SIZE_VALID_OR_INFINITY(limit))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "New limit out of range");
+
+ r = bus_verify_polkit_async(
+ message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.machine1.manage-images",
+ NULL,
+ false,
+ UID_INVALID,
+ &m->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ r = image_set_limit(image, limit);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+const sd_bus_vtable image_vtable[] = {
+ SD_BUS_VTABLE_START(0),
+ SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Image, name), 0),
+ SD_BUS_PROPERTY("Path", "s", NULL, offsetof(Image, path), 0),
+ SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Image, type), 0),
+ SD_BUS_PROPERTY("ReadOnly", "b", bus_property_get_bool, offsetof(Image, read_only), 0),
+ SD_BUS_PROPERTY("CreationTimestamp", "t", NULL, offsetof(Image, crtime), 0),
+ SD_BUS_PROPERTY("ModificationTimestamp", "t", NULL, offsetof(Image, mtime), 0),
+ SD_BUS_PROPERTY("Usage", "t", NULL, offsetof(Image, usage), 0),
+ SD_BUS_PROPERTY("Limit", "t", NULL, offsetof(Image, limit), 0),
+ SD_BUS_PROPERTY("UsageExclusive", "t", NULL, offsetof(Image, usage_exclusive), 0),
+ SD_BUS_PROPERTY("LimitExclusive", "t", NULL, offsetof(Image, limit_exclusive), 0),
+ SD_BUS_METHOD("Remove", NULL, NULL, bus_image_method_remove, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("Rename", "s", NULL, bus_image_method_rename, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("Clone", "sb", NULL, bus_image_method_clone, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("MarkReadOnly", "b", NULL, bus_image_method_mark_read_only, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("SetLimit", "t", NULL, bus_image_method_set_limit, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_VTABLE_END
+};
+
+static int image_flush_cache(sd_event_source *s, void *userdata) {
+ Manager *m = userdata;
+ Image *i;
+
+ assert(s);
+ assert(m);
+
+ while ((i = hashmap_steal_first(m->image_cache)))
+ image_unref(i);
+
+ return 0;
+}
+
+int image_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) {
+ _cleanup_free_ char *e = NULL;
+ Manager *m = userdata;
+ Image *image = NULL;
+ const char *p;
+ int r;
+
+ assert(bus);
+ assert(path);
+ assert(interface);
+ assert(found);
+
+ p = startswith(path, "/org/freedesktop/machine1/image/");
+ if (!p)
+ return 0;
+
+ e = bus_label_unescape(p);
+ if (!e)
+ return -ENOMEM;
+
+ image = hashmap_get(m->image_cache, e);
+ if (image) {
+ *found = image;
+ return 1;
+ }
+
+ r = hashmap_ensure_allocated(&m->image_cache, &string_hash_ops);
+ if (r < 0)
+ return r;
+
+ if (!m->image_cache_defer_event) {
+ r = sd_event_add_defer(m->event, &m->image_cache_defer_event, image_flush_cache, m);
+ if (r < 0)
+ return r;
+
+ r = sd_event_source_set_priority(m->image_cache_defer_event, SD_EVENT_PRIORITY_IDLE);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_event_source_set_enabled(m->image_cache_defer_event, SD_EVENT_ONESHOT);
+ if (r < 0)
+ return r;
+
+ r = image_find(e, &image);
+ if (r <= 0)
+ return r;
+
+ image->userdata = m;
+
+ r = hashmap_put(m->image_cache, image->name, image);
+ if (r < 0) {
+ image_unref(image);
+ return r;
+ }
+
+ *found = image;
+ return 1;
+}
+
+char *image_bus_path(const char *name) {
+ _cleanup_free_ char *e = NULL;
+
+ assert(name);
+
+ e = bus_label_escape(name);
+ if (!e)
+ return NULL;
+
+ return strappend("/org/freedesktop/machine1/image/", e);
+}
+
+int image_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
+ _cleanup_(image_hashmap_freep) Hashmap *images = NULL;
+ _cleanup_strv_free_ char **l = NULL;
+ Image *image;
+ Iterator i;
+ int r;
+
+ assert(bus);
+ assert(path);
+ assert(nodes);
+
+ images = hashmap_new(&string_hash_ops);
+ if (!images)
+ return -ENOMEM;
+
+ r = image_discover(images);
+ if (r < 0)
+ return r;
+
+ HASHMAP_FOREACH(image, images, i) {
+ char *p;
+
+ p = image_bus_path(image->name);
+ if (!p)
+ return -ENOMEM;
+
+ r = strv_consume(&l, p);
+ if (r < 0)
+ return r;
+ }
+
+ *nodes = l;
+ l = NULL;
+
+ return 1;
+}
diff --git a/src/grp-machine/libmachine-core/src/machine-dbus.c b/src/grp-machine/libmachine-core/src/machine-dbus.c
new file mode 100644
index 0000000000..b18d851898
--- /dev/null
+++ b/src/grp-machine/libmachine-core/src/machine-dbus.c
@@ -0,0 +1,1472 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/wait.h>
+
+/* When we include libgen.h because we need dirname() we immediately
+ * undefine basename() since libgen.h defines it as a macro to the POSIX
+ * version which is really broken. We prefer GNU basename(). */
+#include <libgen.h>
+#undef basename
+
+#include "machine-core/machine-dbus.h"
+#include "machine-core/machine.h"
+#include "sd-bus/bus-common-errors.h"
+#include "sd-bus/bus-internal.h"
+#include "sd-netlink/local-addresses.h"
+#include "systemd-basic/alloc-util.h"
+#include "systemd-basic/bus-label.h"
+#include "systemd-basic/copy.h"
+#include "systemd-basic/env-util.h"
+#include "systemd-basic/fd-util.h"
+#include "systemd-basic/fileio.h"
+#include "systemd-basic/formats-util.h"
+#include "systemd-basic/fs-util.h"
+#include "systemd-basic/in-addr-util.h"
+#include "systemd-basic/mkdir.h"
+#include "systemd-basic/path-util.h"
+#include "systemd-basic/process-util.h"
+#include "systemd-basic/signal-util.h"
+#include "systemd-basic/strv.h"
+#include "systemd-basic/terminal-util.h"
+#include "systemd-basic/user-util.h"
+#include "systemd-shared/bus-util.h"
+
+static int property_get_id(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Machine *m = userdata;
+
+ assert(bus);
+ assert(reply);
+ assert(m);
+
+ return sd_bus_message_append_array(reply, 'y', &m->id, 16);
+}
+
+static int property_get_state(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Machine *m = userdata;
+ const char *state;
+ int r;
+
+ assert(bus);
+ assert(reply);
+ assert(m);
+
+ state = machine_state_to_string(machine_get_state(m));
+
+ r = sd_bus_message_append_basic(reply, 's', state);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
+static int property_get_netif(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Machine *m = userdata;
+
+ assert(bus);
+ assert(reply);
+ assert(m);
+
+ assert_cc(sizeof(int) == sizeof(int32_t));
+
+ return sd_bus_message_append_array(reply, 'i', m->netif, m->n_netif * sizeof(int));
+}
+
+static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_class, machine_class, MachineClass);
+
+int bus_machine_method_terminate(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Machine *m = userdata;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = bus_verify_polkit_async(
+ message,
+ CAP_KILL,
+ "org.freedesktop.machine1.manage-machines",
+ NULL,
+ false,
+ UID_INVALID,
+ &m->manager->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ r = machine_stop(m);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+int bus_machine_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Machine *m = userdata;
+ const char *swho;
+ int32_t signo;
+ KillWho who;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = sd_bus_message_read(message, "si", &swho, &signo);
+ if (r < 0)
+ return r;
+
+ if (isempty(swho))
+ who = KILL_ALL;
+ else {
+ who = kill_who_from_string(swho);
+ if (who < 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid kill parameter '%s'", swho);
+ }
+
+ if (!SIGNAL_VALID(signo))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid signal %i", signo);
+
+ r = bus_verify_polkit_async(
+ message,
+ CAP_KILL,
+ "org.freedesktop.machine1.manage-machines",
+ NULL,
+ false,
+ UID_INVALID,
+ &m->manager->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ r = machine_kill(m, who, signo);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+int bus_machine_method_get_addresses(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ Machine *m = userdata;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = sd_bus_message_new_method_return(message, &reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(reply, 'a', "(iay)");
+ if (r < 0)
+ return r;
+
+ switch (m->class) {
+
+ case MACHINE_HOST: {
+ _cleanup_free_ struct local_address *addresses = NULL;
+ struct local_address *a;
+ int n, i;
+
+ n = local_addresses(NULL, 0, AF_UNSPEC, &addresses);
+ if (n < 0)
+ return n;
+
+ for (a = addresses, i = 0; i < n; a++, i++) {
+
+ r = sd_bus_message_open_container(reply, 'r', "iay");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(reply, "i", addresses[i].family);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append_array(reply, 'y', &addresses[i].address, FAMILY_ADDRESS_SIZE(addresses[i].family));
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ return r;
+ }
+
+ break;
+ }
+
+ case MACHINE_CONTAINER: {
+ _cleanup_close_pair_ int pair[2] = { -1, -1 };
+ _cleanup_free_ char *us = NULL, *them = NULL;
+ _cleanup_close_ int netns_fd = -1;
+ const char *p;
+ siginfo_t si;
+ pid_t child;
+
+ r = readlink_malloc("/proc/self/ns/net", &us);
+ if (r < 0)
+ return r;
+
+ p = procfs_file_alloca(m->leader, "ns/net");
+ r = readlink_malloc(p, &them);
+ if (r < 0)
+ return r;
+
+ if (streq(us, them))
+ return sd_bus_error_setf(error, BUS_ERROR_NO_PRIVATE_NETWORKING, "Machine %s does not use private networking", m->name);
+
+ r = namespace_open(m->leader, NULL, NULL, &netns_fd, NULL, NULL);
+ if (r < 0)
+ return r;
+
+ if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) < 0)
+ return -errno;
+
+ child = fork();
+ if (child < 0)
+ return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m");
+
+ if (child == 0) {
+ _cleanup_free_ struct local_address *addresses = NULL;
+ struct local_address *a;
+ int i, n;
+
+ pair[0] = safe_close(pair[0]);
+
+ r = namespace_enter(-1, -1, netns_fd, -1, -1);
+ if (r < 0)
+ _exit(EXIT_FAILURE);
+
+ n = local_addresses(NULL, 0, AF_UNSPEC, &addresses);
+ if (n < 0)
+ _exit(EXIT_FAILURE);
+
+ for (a = addresses, i = 0; i < n; a++, i++) {
+ struct iovec iov[2] = {
+ { .iov_base = &a->family, .iov_len = sizeof(a->family) },
+ { .iov_base = &a->address, .iov_len = FAMILY_ADDRESS_SIZE(a->family) },
+ };
+
+ r = writev(pair[1], iov, 2);
+ if (r < 0)
+ _exit(EXIT_FAILURE);
+ }
+
+ pair[1] = safe_close(pair[1]);
+
+ _exit(EXIT_SUCCESS);
+ }
+
+ pair[1] = safe_close(pair[1]);
+
+ for (;;) {
+ int family;
+ ssize_t n;
+ union in_addr_union in_addr;
+ struct iovec iov[2];
+ struct msghdr mh = {
+ .msg_iov = iov,
+ .msg_iovlen = 2,
+ };
+
+ iov[0] = (struct iovec) { .iov_base = &family, .iov_len = sizeof(family) };
+ iov[1] = (struct iovec) { .iov_base = &in_addr, .iov_len = sizeof(in_addr) };
+
+ n = recvmsg(pair[0], &mh, 0);
+ if (n < 0)
+ return -errno;
+ if ((size_t) n < sizeof(family))
+ break;
+
+ r = sd_bus_message_open_container(reply, 'r', "iay");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(reply, "i", family);
+ if (r < 0)
+ return r;
+
+ switch (family) {
+
+ case AF_INET:
+ if (n != sizeof(struct in_addr) + sizeof(family))
+ return -EIO;
+
+ r = sd_bus_message_append_array(reply, 'y', &in_addr.in, sizeof(in_addr.in));
+ break;
+
+ case AF_INET6:
+ if (n != sizeof(struct in6_addr) + sizeof(family))
+ return -EIO;
+
+ r = sd_bus_message_append_array(reply, 'y', &in_addr.in6, sizeof(in_addr.in6));
+ break;
+ }
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ return r;
+ }
+
+ r = wait_for_terminate(child, &si);
+ if (r < 0)
+ return sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m");
+ if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally.");
+ break;
+ }
+
+ default:
+ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Requesting IP address data is only supported on container machines.");
+ }
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ return r;
+
+ return sd_bus_send(NULL, reply, NULL);
+}
+
+int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_strv_free_ char **l = NULL;
+ Machine *m = userdata;
+ char **k, **v;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ switch (m->class) {
+
+ case MACHINE_HOST:
+ r = load_env_file_pairs(NULL, "/etc/os-release", NULL, &l);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case MACHINE_CONTAINER: {
+ _cleanup_close_ int mntns_fd = -1, root_fd = -1;
+ _cleanup_close_pair_ int pair[2] = { -1, -1 };
+ _cleanup_fclose_ FILE *f = NULL;
+ siginfo_t si;
+ pid_t child;
+
+ r = namespace_open(m->leader, NULL, &mntns_fd, NULL, NULL, &root_fd);
+ if (r < 0)
+ return r;
+
+ if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) < 0)
+ return -errno;
+
+ child = fork();
+ if (child < 0)
+ return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m");
+
+ if (child == 0) {
+ _cleanup_close_ int fd = -1;
+
+ pair[0] = safe_close(pair[0]);
+
+ r = namespace_enter(-1, mntns_fd, -1, -1, root_fd);
+ if (r < 0)
+ _exit(EXIT_FAILURE);
+
+ fd = open("/etc/os-release", O_RDONLY|O_CLOEXEC);
+ if (fd < 0) {
+ fd = open("/usr/lib/os-release", O_RDONLY|O_CLOEXEC);
+ if (fd < 0)
+ _exit(EXIT_FAILURE);
+ }
+
+ r = copy_bytes(fd, pair[1], (uint64_t) -1, false);
+ if (r < 0)
+ _exit(EXIT_FAILURE);
+
+ _exit(EXIT_SUCCESS);
+ }
+
+ pair[1] = safe_close(pair[1]);
+
+ f = fdopen(pair[0], "re");
+ if (!f)
+ return -errno;
+
+ pair[0] = -1;
+
+ r = load_env_file_pairs(f, "/etc/os-release", NULL, &l);
+ if (r < 0)
+ return r;
+
+ r = wait_for_terminate(child, &si);
+ if (r < 0)
+ return sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m");
+ if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally.");
+
+ break;
+ }
+
+ default:
+ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Requesting OS release data is only supported on container machines.");
+ }
+
+ r = sd_bus_message_new_method_return(message, &reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(reply, 'a', "{ss}");
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH_PAIR(k, v, l) {
+ r = sd_bus_message_append(reply, "{ss}", *k, *v);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ return r;
+
+ return sd_bus_send(NULL, reply, NULL);
+}
+
+int bus_machine_method_open_pty(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_free_ char *pty_name = NULL;
+ _cleanup_close_ int master = -1;
+ Machine *m = userdata;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = bus_verify_polkit_async(
+ message,
+ CAP_SYS_ADMIN,
+ m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-open-pty" : "org.freedesktop.machine1.open-pty",
+ NULL,
+ false,
+ UID_INVALID,
+ &m->manager->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ master = machine_openpt(m, O_RDWR|O_NOCTTY|O_CLOEXEC);
+ if (master < 0)
+ return master;
+
+ r = ptsname_namespace(master, &pty_name);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_new_method_return(message, &reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(reply, "hs", master, pty_name);
+ if (r < 0)
+ return r;
+
+ return sd_bus_send(NULL, reply, NULL);
+}
+
+static int container_bus_new(Machine *m, sd_bus_error *error, sd_bus **ret) {
+ int r;
+
+ assert(m);
+ assert(ret);
+
+ switch (m->class) {
+
+ case MACHINE_HOST:
+ *ret = NULL;
+ break;
+
+ case MACHINE_CONTAINER: {
+ _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
+ char *address;
+
+ r = sd_bus_new(&bus);
+ if (r < 0)
+ return r;
+
+ if (asprintf(&address, "x-machine-kernel:pid=%1$" PID_PRI ";x-machine-unix:pid=%1$" PID_PRI, m->leader) < 0)
+ return -ENOMEM;
+
+ bus->address = address;
+ bus->bus_client = true;
+ bus->trusted = false;
+ bus->is_system = true;
+
+ r = sd_bus_start(bus);
+ if (r == -ENOENT)
+ return sd_bus_error_set_errnof(error, r, "There is no system bus in container %s.", m->name);
+ if (r < 0)
+ return r;
+
+ *ret = bus;
+ bus = NULL;
+ break;
+ }
+
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+int bus_machine_method_open_login(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_free_ char *pty_name = NULL;
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *allocated_bus = NULL;
+ _cleanup_close_ int master = -1;
+ sd_bus *container_bus = NULL;
+ Machine *m = userdata;
+ const char *p, *getty;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = bus_verify_polkit_async(
+ message,
+ CAP_SYS_ADMIN,
+ m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-login" : "org.freedesktop.machine1.login",
+ NULL,
+ false,
+ UID_INVALID,
+ &m->manager->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ master = machine_openpt(m, O_RDWR|O_NOCTTY|O_CLOEXEC);
+ if (master < 0)
+ return master;
+
+ r = ptsname_namespace(master, &pty_name);
+ if (r < 0)
+ return r;
+
+ p = path_startswith(pty_name, "/dev/pts/");
+ if (!p)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "PTS name %s is invalid", pty_name);
+
+ r = container_bus_new(m, error, &allocated_bus);
+ if (r < 0)
+ return r;
+
+ container_bus = allocated_bus ?: m->manager->bus;
+
+ getty = strjoina("container-getty@", p, ".service");
+
+ r = sd_bus_call_method(
+ container_bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "StartUnit",
+ error, NULL,
+ "ss", getty, "replace");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_new_method_return(message, &reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(reply, "hs", master, pty_name);
+ if (r < 0)
+ return r;
+
+ return sd_bus_send(NULL, reply, NULL);
+}
+
+int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *tm = NULL;
+ _cleanup_free_ char *pty_name = NULL;
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *allocated_bus = NULL;
+ sd_bus *container_bus = NULL;
+ _cleanup_close_ int master = -1, slave = -1;
+ _cleanup_strv_free_ char **env = NULL, **args = NULL;
+ Machine *m = userdata;
+ const char *p, *unit, *user, *path, *description, *utmp_id;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = sd_bus_message_read(message, "ss", &user, &path);
+ if (r < 0)
+ return r;
+ user = empty_to_null(user);
+ if (isempty(path))
+ path = "/bin/sh";
+ if (!path_is_absolute(path))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified path '%s' is not absolute", path);
+
+ r = sd_bus_message_read_strv(message, &args);
+ if (r < 0)
+ return r;
+ if (strv_isempty(args)) {
+ args = strv_free(args);
+
+ args = strv_new(path, NULL);
+ if (!args)
+ return -ENOMEM;
+
+ args[0][0] = '-'; /* Tell /bin/sh that this shall be a login shell */
+ }
+
+ r = sd_bus_message_read_strv(message, &env);
+ if (r < 0)
+ return r;
+ if (!strv_env_is_valid(env))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment assignments");
+
+ r = bus_verify_polkit_async(
+ message,
+ CAP_SYS_ADMIN,
+ m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-shell" : "org.freedesktop.machine1.shell",
+ NULL,
+ false,
+ UID_INVALID,
+ &m->manager->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ master = machine_openpt(m, O_RDWR|O_NOCTTY|O_CLOEXEC);
+ if (master < 0)
+ return master;
+
+ r = ptsname_namespace(master, &pty_name);
+ if (r < 0)
+ return r;
+
+ p = path_startswith(pty_name, "/dev/pts/");
+ assert(p);
+
+ slave = machine_open_terminal(m, pty_name, O_RDWR|O_NOCTTY|O_CLOEXEC);
+ if (slave < 0)
+ return slave;
+
+ utmp_id = path_startswith(pty_name, "/dev/");
+ assert(utmp_id);
+
+ r = container_bus_new(m, error, &allocated_bus);
+ if (r < 0)
+ return r;
+
+ container_bus = allocated_bus ?: m->manager->bus;
+
+ r = sd_bus_message_new_method_call(
+ container_bus,
+ &tm,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "StartTransientUnit");
+ if (r < 0)
+ return r;
+
+ /* Name and mode */
+ unit = strjoina("container-shell@", p, ".service");
+ r = sd_bus_message_append(tm, "ss", unit, "fail");
+ if (r < 0)
+ return r;
+
+ /* Properties */
+ r = sd_bus_message_open_container(tm, 'a', "(sv)");
+ if (r < 0)
+ return r;
+
+ description = strjoina("Shell for User ", isempty(user) ? "root" : user);
+ r = sd_bus_message_append(tm,
+ "(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)",
+ "Description", "s", description,
+ "StandardInputFileDescriptor", "h", slave,
+ "StandardOutputFileDescriptor", "h", slave,
+ "StandardErrorFileDescriptor", "h", slave,
+ "SendSIGHUP", "b", true,
+ "IgnoreSIGPIPE", "b", false,
+ "KillMode", "s", "mixed",
+ "TTYReset", "b", true,
+ "UtmpIdentifier", "s", utmp_id,
+ "UtmpMode", "s", "user",
+ "PAMName", "s", "login",
+ "WorkingDirectory", "s", "-~");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(tm, "(sv)", "User", "s", isempty(user) ? "root" : user);
+ if (r < 0)
+ return r;
+
+ if (!strv_isempty(env)) {
+ r = sd_bus_message_open_container(tm, 'r', "sv");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(tm, "s", "Environment");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(tm, 'v', "as");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append_strv(tm, env);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_close_container(tm);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_close_container(tm);
+ if (r < 0)
+ return r;
+ }
+
+ /* Exec container */
+ r = sd_bus_message_open_container(tm, 'r', "sv");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(tm, "s", "ExecStart");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(tm, 'v', "a(sasb)");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(tm, 'a', "(sasb)");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(tm, 'r', "sasb");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(tm, "s", path);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append_strv(tm, args);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(tm, "b", true);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_close_container(tm);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_close_container(tm);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_close_container(tm);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_close_container(tm);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_close_container(tm);
+ if (r < 0)
+ return r;
+
+ /* Auxiliary units */
+ r = sd_bus_message_append(tm, "a(sa(sv))", 0);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_call(container_bus, tm, 0, error, NULL);
+ if (r < 0)
+ return r;
+
+ slave = safe_close(slave);
+
+ r = sd_bus_message_new_method_return(message, &reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(reply, "hs", master, pty_name);
+ if (r < 0)
+ return r;
+
+ return sd_bus_send(NULL, reply, NULL);
+}
+
+int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 };
+ char mount_slave[] = "/tmp/propagate.XXXXXX", *mount_tmp, *mount_outside, *p;
+ bool mount_slave_created = false, mount_slave_mounted = false,
+ mount_tmp_created = false, mount_tmp_mounted = false,
+ mount_outside_created = false, mount_outside_mounted = false;
+ const char *dest, *src;
+ Machine *m = userdata;
+ int read_only, make_directory;
+ pid_t child;
+ siginfo_t si;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ if (m->class != MACHINE_CONTAINER)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Bind mounting is only supported on container machines.");
+
+ r = sd_bus_message_read(message, "ssbb", &src, &dest, &read_only, &make_directory);
+ if (r < 0)
+ return r;
+
+ if (!path_is_absolute(src) || !path_is_safe(src))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute and not contain ../.");
+
+ if (isempty(dest))
+ dest = src;
+ else if (!path_is_absolute(dest) || !path_is_safe(dest))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute and not contain ../.");
+
+ r = bus_verify_polkit_async(
+ message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.machine1.manage-machines",
+ NULL,
+ false,
+ UID_INVALID,
+ &m->manager->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ /* One day, when bind mounting /proc/self/fd/n works across
+ * namespace boundaries we should rework this logic to make
+ * use of it... */
+
+ p = strjoina("/run/systemd/nspawn/propagate/", m->name, "/");
+ if (laccess(p, F_OK) < 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Container does not allow propagation of mount points.");
+
+ /* Our goal is to install a new bind mount into the container,
+ possibly read-only. This is irritatingly complex
+ unfortunately, currently.
+
+ First, we start by creating a private playground in /tmp,
+ that we can mount MS_SLAVE. (Which is necessary, since
+ MS_MOVE cannot be applied to mounts with MS_SHARED parent
+ mounts.) */
+
+ if (!mkdtemp(mount_slave))
+ return sd_bus_error_set_errnof(error, errno, "Failed to create playground %s: %m", mount_slave);
+
+ mount_slave_created = true;
+
+ if (mount(mount_slave, mount_slave, NULL, MS_BIND, NULL) < 0) {
+ r = sd_bus_error_set_errnof(error, errno, "Failed to make bind mount %s: %m", mount_slave);
+ goto finish;
+ }
+
+ mount_slave_mounted = true;
+
+ if (mount(NULL, mount_slave, NULL, MS_SLAVE, NULL) < 0) {
+ r = sd_bus_error_set_errnof(error, errno, "Failed to remount slave %s: %m", mount_slave);
+ goto finish;
+ }
+
+ /* Second, we mount the source directory to a directory inside
+ of our MS_SLAVE playground. */
+ mount_tmp = strjoina(mount_slave, "/mount");
+ if (mkdir(mount_tmp, 0700) < 0) {
+ r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount point %s: %m", mount_tmp);
+ goto finish;
+ }
+
+ mount_tmp_created = true;
+
+ if (mount(src, mount_tmp, NULL, MS_BIND, NULL) < 0) {
+ r = sd_bus_error_set_errnof(error, errno, "Failed to overmount %s: %m", mount_tmp);
+ goto finish;
+ }
+
+ mount_tmp_mounted = true;
+
+ /* Third, we remount the new bind mount read-only if requested. */
+ if (read_only)
+ if (mount(NULL, mount_tmp, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY, NULL) < 0) {
+ r = sd_bus_error_set_errnof(error, errno, "Failed to remount read-only %s: %m", mount_tmp);
+ goto finish;
+ }
+
+ /* Fourth, we move the new bind mount into the propagation
+ * directory. This way it will appear there read-only
+ * right-away. */
+
+ mount_outside = strjoina("/run/systemd/nspawn/propagate/", m->name, "/XXXXXX");
+ if (!mkdtemp(mount_outside)) {
+ r = sd_bus_error_set_errnof(error, errno, "Cannot create propagation directory %s: %m", mount_outside);
+ goto finish;
+ }
+
+ mount_outside_created = true;
+
+ if (mount(mount_tmp, mount_outside, NULL, MS_MOVE, NULL) < 0) {
+ r = sd_bus_error_set_errnof(error, errno, "Failed to move %s to %s: %m", mount_tmp, mount_outside);
+ goto finish;
+ }
+
+ mount_outside_mounted = true;
+ mount_tmp_mounted = false;
+
+ (void) rmdir(mount_tmp);
+ mount_tmp_created = false;
+
+ (void) umount(mount_slave);
+ mount_slave_mounted = false;
+
+ (void) rmdir(mount_slave);
+ mount_slave_created = false;
+
+ if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0) {
+ r = sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m");
+ goto finish;
+ }
+
+ child = fork();
+ if (child < 0) {
+ r = sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m");
+ goto finish;
+ }
+
+ if (child == 0) {
+ const char *mount_inside;
+ int mntfd;
+ const char *q;
+
+ errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
+
+ q = procfs_file_alloca(m->leader, "ns/mnt");
+ mntfd = open(q, O_RDONLY|O_NOCTTY|O_CLOEXEC);
+ if (mntfd < 0) {
+ r = log_error_errno(errno, "Failed to open mount namespace of leader: %m");
+ goto child_fail;
+ }
+
+ if (setns(mntfd, CLONE_NEWNS) < 0) {
+ r = log_error_errno(errno, "Failed to join namespace of leader: %m");
+ goto child_fail;
+ }
+
+ if (make_directory)
+ (void) mkdir_p(dest, 0755);
+
+ /* Fifth, move the mount to the right place inside */
+ mount_inside = strjoina("/run/systemd/nspawn/incoming/", basename(mount_outside));
+ if (mount(mount_inside, dest, NULL, MS_MOVE, NULL) < 0) {
+ r = log_error_errno(errno, "Failed to mount: %m");
+ goto child_fail;
+ }
+
+ _exit(EXIT_SUCCESS);
+
+ child_fail:
+ (void) write(errno_pipe_fd[1], &r, sizeof(r));
+ errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
+
+ _exit(EXIT_FAILURE);
+ }
+
+ errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
+
+ r = wait_for_terminate(child, &si);
+ if (r < 0) {
+ r = sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m");
+ goto finish;
+ }
+ if (si.si_code != CLD_EXITED) {
+ r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally.");
+ goto finish;
+ }
+ if (si.si_status != EXIT_SUCCESS) {
+
+ if (read(errno_pipe_fd[0], &r, sizeof(r)) == sizeof(r))
+ r = sd_bus_error_set_errnof(error, r, "Failed to mount: %m");
+ else
+ r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child failed.");
+ goto finish;
+ }
+
+ r = sd_bus_reply_method_return(message, NULL);
+
+finish:
+ if (mount_outside_mounted)
+ umount(mount_outside);
+ if (mount_outside_created)
+ rmdir(mount_outside);
+
+ if (mount_tmp_mounted)
+ umount(mount_tmp);
+ if (mount_tmp_created)
+ rmdir(mount_tmp);
+
+ if (mount_slave_mounted)
+ umount(mount_slave);
+ if (mount_slave_created)
+ rmdir(mount_slave);
+
+ return r;
+}
+
+int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ const char *src, *dest, *host_path, *container_path, *host_basename, *host_dirname, *container_basename, *container_dirname;
+ _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 };
+ _cleanup_close_ int hostfd = -1;
+ Machine *m = userdata;
+ bool copy_from;
+ pid_t child;
+ char *t;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ if (m->manager->n_operations >= OPERATIONS_MAX)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing copies.");
+
+ if (m->class != MACHINE_CONTAINER)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Copying files is only supported on container machines.");
+
+ r = sd_bus_message_read(message, "ss", &src, &dest);
+ if (r < 0)
+ return r;
+
+ if (!path_is_absolute(src))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute.");
+
+ if (isempty(dest))
+ dest = src;
+ else if (!path_is_absolute(dest))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute.");
+
+ r = bus_verify_polkit_async(
+ message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.machine1.manage-machines",
+ NULL,
+ false,
+ UID_INVALID,
+ &m->manager->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ copy_from = strstr(sd_bus_message_get_member(message), "CopyFrom");
+
+ if (copy_from) {
+ container_path = src;
+ host_path = dest;
+ } else {
+ host_path = src;
+ container_path = dest;
+ }
+
+ host_basename = basename(host_path);
+ t = strdupa(host_path);
+ host_dirname = dirname(t);
+
+ container_basename = basename(container_path);
+ t = strdupa(container_path);
+ container_dirname = dirname(t);
+
+ hostfd = open(host_dirname, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_DIRECTORY);
+ if (hostfd < 0)
+ return sd_bus_error_set_errnof(error, errno, "Failed to open host directory %s: %m", host_dirname);
+
+ if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0)
+ return sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m");
+
+ child = fork();
+ if (child < 0)
+ return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m");
+
+ if (child == 0) {
+ int containerfd;
+ const char *q;
+ int mntfd;
+
+ errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
+
+ q = procfs_file_alloca(m->leader, "ns/mnt");
+ mntfd = open(q, O_RDONLY|O_NOCTTY|O_CLOEXEC);
+ if (mntfd < 0) {
+ r = log_error_errno(errno, "Failed to open mount namespace of leader: %m");
+ goto child_fail;
+ }
+
+ if (setns(mntfd, CLONE_NEWNS) < 0) {
+ r = log_error_errno(errno, "Failed to join namespace of leader: %m");
+ goto child_fail;
+ }
+
+ containerfd = open(container_dirname, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_DIRECTORY);
+ if (containerfd < 0) {
+ r = log_error_errno(errno, "Failed top open destination directory: %m");
+ goto child_fail;
+ }
+
+ if (copy_from)
+ r = copy_tree_at(containerfd, container_basename, hostfd, host_basename, true);
+ else
+ r = copy_tree_at(hostfd, host_basename, containerfd, container_basename, true);
+
+ hostfd = safe_close(hostfd);
+ containerfd = safe_close(containerfd);
+
+ if (r < 0) {
+ r = log_error_errno(r, "Failed to copy tree: %m");
+ goto child_fail;
+ }
+
+ _exit(EXIT_SUCCESS);
+
+ child_fail:
+ (void) write(errno_pipe_fd[1], &r, sizeof(r));
+ _exit(EXIT_FAILURE);
+ }
+
+ errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
+
+ /* Copying might take a while, hence install a watch on the child, and return */
+
+ r = operation_new(m->manager, m, child, message, errno_pipe_fd[0], NULL);
+ if (r < 0) {
+ (void) sigkill_wait(child);
+ return r;
+ }
+ errno_pipe_fd[0] = -1;
+
+ return 1;
+}
+
+int bus_machine_method_open_root_directory(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_close_ int fd = -1;
+ Machine *m = userdata;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = bus_verify_polkit_async(
+ message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.machine1.manage-machines",
+ NULL,
+ false,
+ UID_INVALID,
+ &m->manager->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ switch (m->class) {
+
+ case MACHINE_HOST:
+ fd = open("/", O_RDONLY|O_CLOEXEC|O_DIRECTORY);
+ if (fd < 0)
+ return -errno;
+
+ break;
+
+ case MACHINE_CONTAINER: {
+ _cleanup_close_ int mntns_fd = -1, root_fd = -1;
+ _cleanup_close_pair_ int pair[2] = { -1, -1 };
+ siginfo_t si;
+ pid_t child;
+
+ r = namespace_open(m->leader, NULL, &mntns_fd, NULL, NULL, &root_fd);
+ if (r < 0)
+ return r;
+
+ if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0)
+ return -errno;
+
+ child = fork();
+ if (child < 0)
+ return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m");
+
+ if (child == 0) {
+ _cleanup_close_ int dfd = -1;
+
+ pair[0] = safe_close(pair[0]);
+
+ r = namespace_enter(-1, mntns_fd, -1, -1, root_fd);
+ if (r < 0)
+ _exit(EXIT_FAILURE);
+
+ dfd = open("/", O_RDONLY|O_CLOEXEC|O_DIRECTORY);
+ if (dfd < 0)
+ _exit(EXIT_FAILURE);
+
+ r = send_one_fd(pair[1], dfd, 0);
+ dfd = safe_close(dfd);
+ if (r < 0)
+ _exit(EXIT_FAILURE);
+
+ _exit(EXIT_SUCCESS);
+ }
+
+ pair[1] = safe_close(pair[1]);
+
+ r = wait_for_terminate(child, &si);
+ if (r < 0)
+ return sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m");
+ if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally.");
+
+ fd = receive_one_fd(pair[0], MSG_DONTWAIT);
+ if (fd < 0)
+ return fd;
+
+ break;
+ }
+
+ default:
+ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Opening the root directory is only supported on container machines.");
+ }
+
+ return sd_bus_reply_method_return(message, "h", fd);
+}
+
+const sd_bus_vtable machine_vtable[] = {
+ SD_BUS_VTABLE_START(0),
+ SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Machine, name), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("Id", "ay", property_get_id, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+ BUS_PROPERTY_DUAL_TIMESTAMP("Timestamp", offsetof(Machine, timestamp), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("Service", "s", NULL, offsetof(Machine, service), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("Unit", "s", NULL, offsetof(Machine, unit), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("Scope", "s", NULL, offsetof(Machine, unit), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
+ SD_BUS_PROPERTY("Leader", "u", NULL, offsetof(Machine, leader), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("Class", "s", property_get_class, offsetof(Machine, class), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("RootDirectory", "s", NULL, offsetof(Machine, root_directory), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("NetworkInterfaces", "ai", property_get_netif, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("State", "s", property_get_state, 0, 0),
+ SD_BUS_METHOD("Terminate", NULL, NULL, bus_machine_method_terminate, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("Kill", "si", NULL, bus_machine_method_kill, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("GetAddresses", NULL, "a(iay)", bus_machine_method_get_addresses, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("GetOSRelease", NULL, "a{ss}", bus_machine_method_get_os_release, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("OpenPTY", NULL, "hs", bus_machine_method_open_pty, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("OpenLogin", NULL, "hs", bus_machine_method_open_login, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("OpenShell", "ssasas", "hs", bus_machine_method_open_shell, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("BindMount", "ssbb", NULL, bus_machine_method_bind_mount, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("CopyFrom", "ss", NULL, bus_machine_method_copy, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("CopyTo", "ss", NULL, bus_machine_method_copy, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("OpenRootDirectory", NULL, "h", bus_machine_method_open_root_directory, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_VTABLE_END
+};
+
+int machine_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) {
+ Manager *m = userdata;
+ Machine *machine;
+ int r;
+
+ assert(bus);
+ assert(path);
+ assert(interface);
+ assert(found);
+ assert(m);
+
+ if (streq(path, "/org/freedesktop/machine1/machine/self")) {
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+ sd_bus_message *message;
+ pid_t pid;
+
+ message = sd_bus_get_current_message(bus);
+ if (!message)
+ return 0;
+
+ r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_creds_get_pid(creds, &pid);
+ if (r < 0)
+ return r;
+
+ r = manager_get_machine_by_pid(m, pid, &machine);
+ if (r <= 0)
+ return 0;
+ } else {
+ _cleanup_free_ char *e = NULL;
+ const char *p;
+
+ p = startswith(path, "/org/freedesktop/machine1/machine/");
+ if (!p)
+ return 0;
+
+ e = bus_label_unescape(p);
+ if (!e)
+ return -ENOMEM;
+
+ machine = hashmap_get(m->machines, e);
+ if (!machine)
+ return 0;
+ }
+
+ *found = machine;
+ return 1;
+}
+
+char *machine_bus_path(Machine *m) {
+ _cleanup_free_ char *e = NULL;
+
+ assert(m);
+
+ e = bus_label_escape(m->name);
+ if (!e)
+ return NULL;
+
+ return strappend("/org/freedesktop/machine1/machine/", e);
+}
+
+int machine_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
+ _cleanup_strv_free_ char **l = NULL;
+ Machine *machine = NULL;
+ Manager *m = userdata;
+ Iterator i;
+ int r;
+
+ assert(bus);
+ assert(path);
+ assert(nodes);
+
+ HASHMAP_FOREACH(machine, m->machines, i) {
+ char *p;
+
+ p = machine_bus_path(machine);
+ if (!p)
+ return -ENOMEM;
+
+ r = strv_consume(&l, p);
+ if (r < 0)
+ return r;
+ }
+
+ *nodes = l;
+ l = NULL;
+
+ return 1;
+}
+
+int machine_send_signal(Machine *m, bool new_machine) {
+ _cleanup_free_ char *p = NULL;
+
+ assert(m);
+
+ p = machine_bus_path(m);
+ if (!p)
+ return -ENOMEM;
+
+ return sd_bus_emit_signal(
+ m->manager->bus,
+ "/org/freedesktop/machine1",
+ "org.freedesktop.machine1.Manager",
+ new_machine ? "MachineNew" : "MachineRemoved",
+ "so", m->name, p);
+}
+
+int machine_send_create_reply(Machine *m, sd_bus_error *error) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *c = NULL;
+ _cleanup_free_ char *p = NULL;
+
+ assert(m);
+
+ if (!m->create_message)
+ return 0;
+
+ c = m->create_message;
+ m->create_message = NULL;
+
+ if (error)
+ return sd_bus_reply_method_error(c, error);
+
+ /* Update the machine state file before we notify the client
+ * about the result. */
+ machine_save(m);
+
+ p = machine_bus_path(m);
+ if (!p)
+ return -ENOMEM;
+
+ return sd_bus_reply_method_return(c, "o", p);
+}
diff --git a/src/grp-machine/libmachine-core/src/machine.c b/src/grp-machine/libmachine-core/src/machine.c
new file mode 100644
index 0000000000..9eca08948f
--- /dev/null
+++ b/src/grp-machine/libmachine-core/src/machine.c
@@ -0,0 +1,630 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <systemd/sd-messages.h>
+
+#include "machine-core/machine-dbus.h"
+#include "machine-core/machine.h"
+#include "sd-bus/bus-error.h"
+#include "systemd-basic/alloc-util.h"
+#include "systemd-basic/escape.h"
+#include "systemd-basic/extract-word.h"
+#include "systemd-basic/fd-util.h"
+#include "systemd-basic/fileio.h"
+#include "systemd-basic/formats-util.h"
+#include "systemd-basic/hashmap.h"
+#include "systemd-basic/mkdir.h"
+#include "systemd-basic/parse-util.h"
+#include "systemd-basic/process-util.h"
+#include "systemd-basic/special.h"
+#include "systemd-basic/string-table.h"
+#include "systemd-basic/terminal-util.h"
+#include "systemd-basic/unit-name.h"
+#include "systemd-basic/util.h"
+#include "systemd-shared/bus-util.h"
+
+Machine* machine_new(Manager *manager, MachineClass class, const char *name) {
+ Machine *m;
+
+ assert(manager);
+ assert(class < _MACHINE_CLASS_MAX);
+ assert(name);
+
+ /* Passing class == _MACHINE_CLASS_INVALID here is fine. It
+ * means as much as "we don't know yet", and that we'll figure
+ * it out later when loading the state file. */
+
+ m = new0(Machine, 1);
+ if (!m)
+ return NULL;
+
+ m->name = strdup(name);
+ if (!m->name)
+ goto fail;
+
+ if (class != MACHINE_HOST) {
+ m->state_file = strappend("/run/systemd/machines/", m->name);
+ if (!m->state_file)
+ goto fail;
+ }
+
+ m->class = class;
+
+ if (hashmap_put(manager->machines, m->name, m) < 0)
+ goto fail;
+
+ m->manager = manager;
+
+ return m;
+
+fail:
+ free(m->state_file);
+ free(m->name);
+ free(m);
+
+ return NULL;
+}
+
+void machine_free(Machine *m) {
+ assert(m);
+
+ while (m->operations)
+ operation_free(m->operations);
+
+ if (m->in_gc_queue)
+ LIST_REMOVE(gc_queue, m->manager->machine_gc_queue, m);
+
+ machine_release_unit(m);
+
+ free(m->scope_job);
+
+ (void) hashmap_remove(m->manager->machines, m->name);
+
+ if (m->manager->host_machine == m)
+ m->manager->host_machine = NULL;
+
+ if (m->leader > 0)
+ (void) hashmap_remove_value(m->manager->machine_leaders, PID_TO_PTR(m->leader), m);
+
+ sd_bus_message_unref(m->create_message);
+
+ free(m->name);
+ free(m->state_file);
+ free(m->service);
+ free(m->root_directory);
+ free(m->netif);
+ free(m);
+}
+
+int machine_save(Machine *m) {
+ _cleanup_free_ char *temp_path = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ assert(m);
+
+ if (!m->state_file)
+ return 0;
+
+ if (!m->started)
+ return 0;
+
+ r = mkdir_safe_label("/run/systemd/machines", 0755, 0, 0);
+ if (r < 0)
+ goto fail;
+
+ r = fopen_temporary(m->state_file, &f, &temp_path);
+ if (r < 0)
+ goto fail;
+
+ (void) fchmod(fileno(f), 0644);
+
+ fprintf(f,
+ "# This is private data. Do not parse.\n"
+ "NAME=%s\n",
+ m->name);
+
+ if (m->unit) {
+ _cleanup_free_ char *escaped;
+
+ escaped = cescape(m->unit);
+ if (!escaped) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ fprintf(f, "SCOPE=%s\n", escaped); /* We continue to call this "SCOPE=" because it is internal only, and we want to stay compatible with old files */
+ }
+
+ if (m->scope_job)
+ fprintf(f, "SCOPE_JOB=%s\n", m->scope_job);
+
+ if (m->service) {
+ _cleanup_free_ char *escaped;
+
+ escaped = cescape(m->service);
+ if (!escaped) {
+ r = -ENOMEM;
+ goto fail;
+ }
+ fprintf(f, "SERVICE=%s\n", escaped);
+ }
+
+ if (m->root_directory) {
+ _cleanup_free_ char *escaped;
+
+ escaped = cescape(m->root_directory);
+ if (!escaped) {
+ r = -ENOMEM;
+ goto fail;
+ }
+ fprintf(f, "ROOT=%s\n", escaped);
+ }
+
+ if (!sd_id128_is_null(m->id))
+ fprintf(f, "ID=" SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(m->id));
+
+ if (m->leader != 0)
+ fprintf(f, "LEADER="PID_FMT"\n", m->leader);
+
+ if (m->class != _MACHINE_CLASS_INVALID)
+ fprintf(f, "CLASS=%s\n", machine_class_to_string(m->class));
+
+ if (dual_timestamp_is_set(&m->timestamp))
+ fprintf(f,
+ "REALTIME="USEC_FMT"\n"
+ "MONOTONIC="USEC_FMT"\n",
+ m->timestamp.realtime,
+ m->timestamp.monotonic);
+
+ if (m->n_netif > 0) {
+ unsigned i;
+
+ fputs("NETIF=", f);
+
+ for (i = 0; i < m->n_netif; i++) {
+ if (i != 0)
+ fputc(' ', f);
+
+ fprintf(f, "%i", m->netif[i]);
+ }
+
+ fputc('\n', f);
+ }
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ goto fail;
+
+ if (rename(temp_path, m->state_file) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ if (m->unit) {
+ char *sl;
+
+ /* Create a symlink from the unit name to the machine
+ * name, so that we can quickly find the machine for
+ * each given unit. Ignore error. */
+ sl = strjoina("/run/systemd/machines/unit:", m->unit);
+ (void) symlink(m->name, sl);
+ }
+
+ return 0;
+
+fail:
+ (void) unlink(m->state_file);
+
+ if (temp_path)
+ (void) unlink(temp_path);
+
+ return log_error_errno(r, "Failed to save machine data %s: %m", m->state_file);
+}
+
+static void machine_unlink(Machine *m) {
+ assert(m);
+
+ if (m->unit) {
+
+ char *sl;
+
+ sl = strjoina("/run/systemd/machines/unit:", m->unit);
+ (void) unlink(sl);
+ }
+
+ if (m->state_file)
+ (void) unlink(m->state_file);
+}
+
+int machine_load(Machine *m) {
+ _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *id = NULL, *leader = NULL, *class = NULL, *netif = NULL;
+ int r;
+
+ assert(m);
+
+ if (!m->state_file)
+ return 0;
+
+ r = parse_env_file(m->state_file, NEWLINE,
+ "SCOPE", &m->unit,
+ "SCOPE_JOB", &m->scope_job,
+ "SERVICE", &m->service,
+ "ROOT", &m->root_directory,
+ "ID", &id,
+ "LEADER", &leader,
+ "CLASS", &class,
+ "REALTIME", &realtime,
+ "MONOTONIC", &monotonic,
+ "NETIF", &netif,
+ NULL);
+ if (r < 0) {
+ if (r == -ENOENT)
+ return 0;
+
+ return log_error_errno(r, "Failed to read %s: %m", m->state_file);
+ }
+
+ if (id)
+ sd_id128_from_string(id, &m->id);
+
+ if (leader)
+ parse_pid(leader, &m->leader);
+
+ if (class) {
+ MachineClass c;
+
+ c = machine_class_from_string(class);
+ if (c >= 0)
+ m->class = c;
+ }
+
+ if (realtime)
+ timestamp_deserialize(realtime, &m->timestamp.realtime);
+ if (monotonic)
+ timestamp_deserialize(monotonic, &m->timestamp.monotonic);
+
+ if (netif) {
+ size_t allocated = 0, nr = 0;
+ const char *p;
+ int *ni = NULL;
+
+ p = netif;
+ for (;;) {
+ _cleanup_free_ char *word = NULL;
+ int ifi;
+
+ r = extract_first_word(&p, &word, NULL, 0);
+ if (r == 0)
+ break;
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_warning_errno(r, "Failed to parse NETIF: %s", netif);
+ break;
+ }
+
+ if (parse_ifindex(word, &ifi) < 0)
+ continue;
+
+ if (!GREEDY_REALLOC(ni, allocated, nr+1)) {
+ free(ni);
+ return log_oom();
+ }
+
+ ni[nr++] = ifi;
+ }
+
+ free(m->netif);
+ m->netif = ni;
+ m->n_netif = nr;
+ }
+
+ return r;
+}
+
+static int machine_start_scope(Machine *m, sd_bus_message *properties, sd_bus_error *error) {
+ int r = 0;
+
+ assert(m);
+ assert(m->class != MACHINE_HOST);
+
+ if (!m->unit) {
+ _cleanup_free_ char *escaped = NULL;
+ char *scope, *description, *job = NULL;
+
+ escaped = unit_name_escape(m->name);
+ if (!escaped)
+ return log_oom();
+
+ scope = strjoin("machine-", escaped, ".scope", NULL);
+ if (!scope)
+ return log_oom();
+
+ description = strjoina(m->class == MACHINE_VM ? "Virtual Machine " : "Container ", m->name);
+
+ r = manager_start_scope(m->manager, scope, m->leader, SPECIAL_MACHINE_SLICE, description, properties, error, &job);
+ if (r < 0) {
+ log_error("Failed to start machine scope: %s", bus_error_message(error, r));
+ free(scope);
+ return r;
+ } else {
+ m->unit = scope;
+
+ free(m->scope_job);
+ m->scope_job = job;
+ }
+ }
+
+ if (m->unit)
+ hashmap_put(m->manager->machine_units, m->unit, m);
+
+ return r;
+}
+
+int machine_start(Machine *m, sd_bus_message *properties, sd_bus_error *error) {
+ int r;
+
+ assert(m);
+
+ if (!IN_SET(m->class, MACHINE_CONTAINER, MACHINE_VM))
+ return -EOPNOTSUPP;
+
+ if (m->started)
+ return 0;
+
+ r = hashmap_put(m->manager->machine_leaders, PID_TO_PTR(m->leader), m);
+ if (r < 0)
+ return r;
+
+ /* Create cgroup */
+ r = machine_start_scope(m, properties, error);
+ if (r < 0)
+ return r;
+
+ log_struct(LOG_INFO,
+ LOG_MESSAGE_ID(SD_MESSAGE_MACHINE_START),
+ "NAME=%s", m->name,
+ "LEADER="PID_FMT, m->leader,
+ LOG_MESSAGE("New machine %s.", m->name),
+ NULL);
+
+ if (!dual_timestamp_is_set(&m->timestamp))
+ dual_timestamp_get(&m->timestamp);
+
+ m->started = true;
+
+ /* Save new machine data */
+ machine_save(m);
+
+ machine_send_signal(m, true);
+
+ return 0;
+}
+
+static int machine_stop_scope(Machine *m) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ char *job = NULL;
+ int r;
+
+ assert(m);
+ assert(m->class != MACHINE_HOST);
+
+ if (!m->unit)
+ return 0;
+
+ r = manager_stop_unit(m->manager, m->unit, &error, &job);
+ if (r < 0) {
+ log_error("Failed to stop machine scope: %s", bus_error_message(&error, r));
+ return r;
+ }
+
+ free(m->scope_job);
+ m->scope_job = job;
+
+ return 0;
+}
+
+int machine_stop(Machine *m) {
+ int r;
+ assert(m);
+
+ if (!IN_SET(m->class, MACHINE_CONTAINER, MACHINE_VM))
+ return -EOPNOTSUPP;
+
+ r = machine_stop_scope(m);
+
+ m->stopping = true;
+
+ machine_save(m);
+
+ return r;
+}
+
+int machine_finalize(Machine *m) {
+ assert(m);
+
+ if (m->started)
+ log_struct(LOG_INFO,
+ LOG_MESSAGE_ID(SD_MESSAGE_MACHINE_STOP),
+ "NAME=%s", m->name,
+ "LEADER="PID_FMT, m->leader,
+ LOG_MESSAGE("Machine %s terminated.", m->name),
+ NULL);
+
+ machine_unlink(m);
+ machine_add_to_gc_queue(m);
+
+ if (m->started) {
+ machine_send_signal(m, false);
+ m->started = false;
+ }
+
+ return 0;
+}
+
+bool machine_check_gc(Machine *m, bool drop_not_started) {
+ assert(m);
+
+ if (m->class == MACHINE_HOST)
+ return true;
+
+ if (drop_not_started && !m->started)
+ return false;
+
+ if (m->scope_job && manager_job_is_active(m->manager, m->scope_job))
+ return true;
+
+ if (m->unit && manager_unit_is_active(m->manager, m->unit))
+ return true;
+
+ return false;
+}
+
+void machine_add_to_gc_queue(Machine *m) {
+ assert(m);
+
+ if (m->in_gc_queue)
+ return;
+
+ LIST_PREPEND(gc_queue, m->manager->machine_gc_queue, m);
+ m->in_gc_queue = true;
+}
+
+MachineState machine_get_state(Machine *s) {
+ assert(s);
+
+ if (s->class == MACHINE_HOST)
+ return MACHINE_RUNNING;
+
+ if (s->stopping)
+ return MACHINE_CLOSING;
+
+ if (s->scope_job)
+ return MACHINE_OPENING;
+
+ return MACHINE_RUNNING;
+}
+
+int machine_kill(Machine *m, KillWho who, int signo) {
+ assert(m);
+
+ if (!IN_SET(m->class, MACHINE_VM, MACHINE_CONTAINER))
+ return -EOPNOTSUPP;
+
+ if (!m->unit)
+ return -ESRCH;
+
+ if (who == KILL_LEADER) {
+ /* If we shall simply kill the leader, do so directly */
+
+ if (kill(m->leader, signo) < 0)
+ return -errno;
+
+ return 0;
+ }
+
+ /* Otherwise, make PID 1 do it for us, for the entire cgroup */
+ return manager_kill_unit(m->manager, m->unit, signo, NULL);
+}
+
+int machine_openpt(Machine *m, int flags) {
+ assert(m);
+
+ switch (m->class) {
+
+ case MACHINE_HOST: {
+ int fd;
+
+ fd = posix_openpt(flags);
+ if (fd < 0)
+ return -errno;
+
+ if (unlockpt(fd) < 0)
+ return -errno;
+
+ return fd;
+ }
+
+ case MACHINE_CONTAINER:
+ if (m->leader <= 0)
+ return -EINVAL;
+
+ return openpt_in_namespace(m->leader, flags);
+
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+int machine_open_terminal(Machine *m, const char *path, int mode) {
+ assert(m);
+
+ switch (m->class) {
+
+ case MACHINE_HOST:
+ return open_terminal(path, mode);
+
+ case MACHINE_CONTAINER:
+ if (m->leader <= 0)
+ return -EINVAL;
+
+ return open_terminal_in_namespace(m->leader, path, mode);
+
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+void machine_release_unit(Machine *m) {
+ assert(m);
+
+ if (!m->unit)
+ return;
+
+ (void) hashmap_remove(m->manager->machine_units, m->unit);
+ m->unit = mfree(m->unit);
+}
+
+static const char* const machine_class_table[_MACHINE_CLASS_MAX] = {
+ [MACHINE_CONTAINER] = "container",
+ [MACHINE_VM] = "vm",
+ [MACHINE_HOST] = "host",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(machine_class, MachineClass);
+
+static const char* const machine_state_table[_MACHINE_STATE_MAX] = {
+ [MACHINE_OPENING] = "opening",
+ [MACHINE_RUNNING] = "running",
+ [MACHINE_CLOSING] = "closing"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(machine_state, MachineState);
+
+static const char* const kill_who_table[_KILL_WHO_MAX] = {
+ [KILL_LEADER] = "leader",
+ [KILL_ALL] = "all"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho);
diff --git a/src/grp-machine/libmachine-core/src/machined-dbus.c b/src/grp-machine/libmachine-core/src/machined-dbus.c
new file mode 100644
index 0000000000..9a50dcbaa8
--- /dev/null
+++ b/src/grp-machine/libmachine-core/src/machined-dbus.c
@@ -0,0 +1,1804 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <systemd/sd-id128.h>
+
+#include "machine-core/image-dbus.h"
+#include "machine-core/machine-dbus.h"
+#include "machine-core/machined.h"
+#include "sd-bus/bus-common-errors.h"
+#include "systemd-basic/alloc-util.h"
+#include "systemd-basic/btrfs-util.h"
+#include "systemd-basic/cgroup-util.h"
+#include "systemd-basic/fd-util.h"
+#include "systemd-basic/fileio.h"
+#include "systemd-basic/formats-util.h"
+#include "systemd-basic/hostname-util.h"
+#include "systemd-basic/io-util.h"
+#include "systemd-basic/path-util.h"
+#include "systemd-basic/process-util.h"
+#include "systemd-basic/stdio-util.h"
+#include "systemd-basic/strv.h"
+#include "systemd-basic/unit-name.h"
+#include "systemd-basic/user-util.h"
+#include "systemd-shared/bus-util.h"
+#include "systemd-shared/machine-image.h"
+#include "systemd-shared/machine-pool.h"
+
+static int property_get_pool_path(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ assert(bus);
+ assert(reply);
+
+ return sd_bus_message_append(reply, "s", "/var/lib/machines");
+}
+
+static int property_get_pool_usage(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ _cleanup_close_ int fd = -1;
+ uint64_t usage = (uint64_t) -1;
+ struct stat st;
+
+ assert(bus);
+ assert(reply);
+
+ /* We try to read the quota info from /var/lib/machines, as
+ * well as the usage of the loopback file
+ * /var/lib/machines.raw, and pick the larger value. */
+
+ fd = open("/var/lib/machines", O_RDONLY|O_CLOEXEC|O_DIRECTORY);
+ if (fd >= 0) {
+ BtrfsQuotaInfo q;
+
+ if (btrfs_subvol_get_subtree_quota_fd(fd, 0, &q) >= 0)
+ usage = q.referenced;
+ }
+
+ if (stat("/var/lib/machines.raw", &st) >= 0) {
+ if (usage == (uint64_t) -1 || st.st_blocks * 512ULL > usage)
+ usage = st.st_blocks * 512ULL;
+ }
+
+ return sd_bus_message_append(reply, "t", usage);
+}
+
+static int property_get_pool_limit(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ _cleanup_close_ int fd = -1;
+ uint64_t size = (uint64_t) -1;
+ struct stat st;
+
+ assert(bus);
+ assert(reply);
+
+ /* We try to read the quota limit from /var/lib/machines, as
+ * well as the size of the loopback file
+ * /var/lib/machines.raw, and pick the smaller value. */
+
+ fd = open("/var/lib/machines", O_RDONLY|O_CLOEXEC|O_DIRECTORY);
+ if (fd >= 0) {
+ BtrfsQuotaInfo q;
+
+ if (btrfs_subvol_get_subtree_quota_fd(fd, 0, &q) >= 0)
+ size = q.referenced_max;
+ }
+
+ if (stat("/var/lib/machines.raw", &st) >= 0) {
+ if (size == (uint64_t) -1 || (uint64_t) st.st_size < size)
+ size = st.st_size;
+ }
+
+ return sd_bus_message_append(reply, "t", size);
+}
+
+static int method_get_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_free_ char *p = NULL;
+ Manager *m = userdata;
+ Machine *machine;
+ const char *name;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = sd_bus_message_read(message, "s", &name);
+ if (r < 0)
+ return r;
+
+ machine = hashmap_get(m->machines, name);
+ if (!machine)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name);
+
+ p = machine_bus_path(machine);
+ if (!p)
+ return -ENOMEM;
+
+ return sd_bus_reply_method_return(message, "o", p);
+}
+
+static int method_get_image(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_free_ char *p = NULL;
+ Manager *m = userdata;
+ const char *name;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = sd_bus_message_read(message, "s", &name);
+ if (r < 0)
+ return r;
+
+ r = image_find(name, NULL);
+ if (r == 0)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name);
+ if (r < 0)
+ return r;
+
+ p = image_bus_path(name);
+ if (!p)
+ return -ENOMEM;
+
+ return sd_bus_reply_method_return(message, "o", p);
+}
+
+static int method_get_machine_by_pid(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_free_ char *p = NULL;
+ Manager *m = userdata;
+ Machine *machine = NULL;
+ pid_t pid;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ assert_cc(sizeof(pid_t) == sizeof(uint32_t));
+
+ r = sd_bus_message_read(message, "u", &pid);
+ if (r < 0)
+ return r;
+
+ if (pid < 0)
+ return -EINVAL;
+
+ if (pid == 0) {
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+
+ r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_creds_get_pid(creds, &pid);
+ if (r < 0)
+ return r;
+ }
+
+ r = manager_get_machine_by_pid(m, pid, &machine);
+ if (r < 0)
+ return r;
+ if (!machine)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_MACHINE_FOR_PID, "PID "PID_FMT" does not belong to any known machine", pid);
+
+ p = machine_bus_path(machine);
+ if (!p)
+ return -ENOMEM;
+
+ return sd_bus_reply_method_return(message, "o", p);
+}
+
+static int method_list_machines(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ Manager *m = userdata;
+ Machine *machine;
+ Iterator i;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = sd_bus_message_new_method_return(message, &reply);
+ if (r < 0)
+ return sd_bus_error_set_errno(error, r);
+
+ r = sd_bus_message_open_container(reply, 'a', "(ssso)");
+ if (r < 0)
+ return sd_bus_error_set_errno(error, r);
+
+ HASHMAP_FOREACH(machine, m->machines, i) {
+ _cleanup_free_ char *p = NULL;
+
+ p = machine_bus_path(machine);
+ if (!p)
+ return -ENOMEM;
+
+ r = sd_bus_message_append(reply, "(ssso)",
+ machine->name,
+ strempty(machine_class_to_string(machine->class)),
+ machine->service,
+ p);
+ if (r < 0)
+ return sd_bus_error_set_errno(error, r);
+ }
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ return sd_bus_error_set_errno(error, r);
+
+ return sd_bus_send(NULL, reply, NULL);
+}
+
+static int method_create_or_register_machine(Manager *manager, sd_bus_message *message, bool read_network, Machine **_m, sd_bus_error *error) {
+ const char *name, *service, *class, *root_directory;
+ const int32_t *netif = NULL;
+ MachineClass c;
+ uint32_t leader;
+ sd_id128_t id;
+ const void *v;
+ Machine *m;
+ size_t n, n_netif = 0;
+ int r;
+
+ assert(manager);
+ assert(message);
+ assert(_m);
+
+ r = sd_bus_message_read(message, "s", &name);
+ if (r < 0)
+ return r;
+ if (!machine_name_is_valid(name))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine name");
+
+ r = sd_bus_message_read_array(message, 'y', &v, &n);
+ if (r < 0)
+ return r;
+ if (n == 0)
+ id = SD_ID128_NULL;
+ else if (n == 16)
+ memcpy(&id, v, n);
+ else
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine ID parameter");
+
+ r = sd_bus_message_read(message, "ssus", &service, &class, &leader, &root_directory);
+ if (r < 0)
+ return r;
+
+ if (read_network) {
+ size_t i;
+
+ r = sd_bus_message_read_array(message, 'i', (const void**) &netif, &n_netif);
+ if (r < 0)
+ return r;
+
+ n_netif /= sizeof(int32_t);
+
+ for (i = 0; i < n_netif; i++) {
+ if (netif[i] <= 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid network interface index %i", netif[i]);
+ }
+ }
+
+ if (isempty(class))
+ c = _MACHINE_CLASS_INVALID;
+ else {
+ c = machine_class_from_string(class);
+ if (c < 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine class parameter");
+ }
+
+ if (leader == 1)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid leader PID");
+
+ if (!isempty(root_directory) && !path_is_absolute(root_directory))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Root directory must be empty or an absolute path");
+
+ if (leader == 0) {
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+
+ r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds);
+ if (r < 0)
+ return r;
+
+ assert_cc(sizeof(uint32_t) == sizeof(pid_t));
+
+ r = sd_bus_creds_get_pid(creds, (pid_t*) &leader);
+ if (r < 0)
+ return r;
+ }
+
+ if (hashmap_get(manager->machines, name))
+ return sd_bus_error_setf(error, BUS_ERROR_MACHINE_EXISTS, "Machine '%s' already exists", name);
+
+ r = manager_add_machine(manager, name, &m);
+ if (r < 0)
+ return r;
+
+ m->leader = leader;
+ m->class = c;
+ m->id = id;
+
+ if (!isempty(service)) {
+ m->service = strdup(service);
+ if (!m->service) {
+ r = -ENOMEM;
+ goto fail;
+ }
+ }
+
+ if (!isempty(root_directory)) {
+ m->root_directory = strdup(root_directory);
+ if (!m->root_directory) {
+ r = -ENOMEM;
+ goto fail;
+ }
+ }
+
+ if (n_netif > 0) {
+ assert_cc(sizeof(int32_t) == sizeof(int));
+ m->netif = memdup(netif, sizeof(int32_t) * n_netif);
+ if (!m->netif) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ m->n_netif = n_netif;
+ }
+
+ *_m = m;
+
+ return 1;
+
+fail:
+ machine_add_to_gc_queue(m);
+ return r;
+}
+
+static int method_create_machine_internal(sd_bus_message *message, bool read_network, void *userdata, sd_bus_error *error) {
+ Manager *manager = userdata;
+ Machine *m = NULL;
+ int r;
+
+ assert(message);
+ assert(manager);
+
+ r = method_create_or_register_machine(manager, message, read_network, &m, error);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_enter_container(message, 'a', "(sv)");
+ if (r < 0)
+ goto fail;
+
+ r = machine_start(m, message, error);
+ if (r < 0)
+ goto fail;
+
+ m->create_message = sd_bus_message_ref(message);
+ return 1;
+
+fail:
+ machine_add_to_gc_queue(m);
+ return r;
+}
+
+static int method_create_machine_with_network(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ return method_create_machine_internal(message, true, userdata, error);
+}
+
+static int method_create_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ return method_create_machine_internal(message, false, userdata, error);
+}
+
+static int method_register_machine_internal(sd_bus_message *message, bool read_network, void *userdata, sd_bus_error *error) {
+ Manager *manager = userdata;
+ _cleanup_free_ char *p = NULL;
+ Machine *m = NULL;
+ int r;
+
+ assert(message);
+ assert(manager);
+
+ r = method_create_or_register_machine(manager, message, read_network, &m, error);
+ if (r < 0)
+ return r;
+
+ r = cg_pid_get_unit(m->leader, &m->unit);
+ if (r < 0) {
+ r = sd_bus_error_set_errnof(error, r, "Failed to determine unit of process "PID_FMT" : %s", m->leader, strerror(-r));
+ goto fail;
+ }
+
+ r = machine_start(m, NULL, error);
+ if (r < 0)
+ goto fail;
+
+ p = machine_bus_path(m);
+ if (!p) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ return sd_bus_reply_method_return(message, "o", p);
+
+fail:
+ machine_add_to_gc_queue(m);
+ return r;
+}
+
+static int method_register_machine_with_network(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ return method_register_machine_internal(message, true, userdata, error);
+}
+
+static int method_register_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ return method_register_machine_internal(message, false, userdata, error);
+}
+
+static int method_terminate_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = userdata;
+ Machine *machine;
+ const char *name;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = sd_bus_message_read(message, "s", &name);
+ if (r < 0)
+ return sd_bus_error_set_errno(error, r);
+
+ machine = hashmap_get(m->machines, name);
+ if (!machine)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name);
+
+ return bus_machine_method_terminate(message, machine, error);
+}
+
+static int method_kill_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = userdata;
+ Machine *machine;
+ const char *name;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = sd_bus_message_read(message, "s", &name);
+ if (r < 0)
+ return sd_bus_error_set_errno(error, r);
+
+ machine = hashmap_get(m->machines, name);
+ if (!machine)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name);
+
+ return bus_machine_method_kill(message, machine, error);
+}
+
+static int method_get_machine_addresses(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = userdata;
+ Machine *machine;
+ const char *name;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = sd_bus_message_read(message, "s", &name);
+ if (r < 0)
+ return sd_bus_error_set_errno(error, r);
+
+ machine = hashmap_get(m->machines, name);
+ if (!machine)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name);
+
+ return bus_machine_method_get_addresses(message, machine, error);
+}
+
+static int method_get_machine_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = userdata;
+ Machine *machine;
+ const char *name;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = sd_bus_message_read(message, "s", &name);
+ if (r < 0)
+ return sd_bus_error_set_errno(error, r);
+
+ machine = hashmap_get(m->machines, name);
+ if (!machine)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name);
+
+ return bus_machine_method_get_os_release(message, machine, error);
+}
+
+static int method_list_images(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(image_hashmap_freep) Hashmap *images = NULL;
+ Manager *m = userdata;
+ Image *image;
+ Iterator i;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ images = hashmap_new(&string_hash_ops);
+ if (!images)
+ return -ENOMEM;
+
+ r = image_discover(images);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_new_method_return(message, &reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(reply, 'a', "(ssbttto)");
+ if (r < 0)
+ return r;
+
+ HASHMAP_FOREACH(image, images, i) {
+ _cleanup_free_ char *p = NULL;
+
+ p = image_bus_path(image->name);
+ if (!p)
+ return -ENOMEM;
+
+ r = sd_bus_message_append(reply, "(ssbttto)",
+ image->name,
+ image_type_to_string(image->type),
+ image->read_only,
+ image->crtime,
+ image->mtime,
+ image->usage,
+ p);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ return r;
+
+ return sd_bus_send(NULL, reply, NULL);
+}
+
+static int method_open_machine_pty(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = userdata;
+ Machine *machine;
+ const char *name;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = sd_bus_message_read(message, "s", &name);
+ if (r < 0)
+ return sd_bus_error_set_errno(error, r);
+
+ machine = hashmap_get(m->machines, name);
+ if (!machine)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name);
+
+ return bus_machine_method_open_pty(message, machine, error);
+}
+
+static int method_open_machine_login(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = userdata;
+ Machine *machine;
+ const char *name;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = sd_bus_message_read(message, "s", &name);
+ if (r < 0)
+ return r;
+
+ machine = hashmap_get(m->machines, name);
+ if (!machine)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name);
+
+ return bus_machine_method_open_login(message, machine, error);
+}
+
+static int method_open_machine_shell(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = userdata;
+ Machine *machine;
+ const char *name;
+
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = sd_bus_message_read(message, "s", &name);
+ if (r < 0)
+ return r;
+
+ machine = hashmap_get(m->machines, name);
+ if (!machine)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name);
+
+ return bus_machine_method_open_shell(message, machine, error);
+}
+
+static int method_bind_mount_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = userdata;
+ Machine *machine;
+ const char *name;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = sd_bus_message_read(message, "s", &name);
+ if (r < 0)
+ return r;
+
+ machine = hashmap_get(m->machines, name);
+ if (!machine)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name);
+
+ return bus_machine_method_bind_mount(message, machine, error);
+}
+
+static int method_copy_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = userdata;
+ Machine *machine;
+ const char *name;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = sd_bus_message_read(message, "s", &name);
+ if (r < 0)
+ return r;
+
+ machine = hashmap_get(m->machines, name);
+ if (!machine)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name);
+
+ return bus_machine_method_copy(message, machine, error);
+}
+
+static int method_open_machine_root_directory(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = userdata;
+ Machine *machine;
+ const char *name;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = sd_bus_message_read(message, "s", &name);
+ if (r < 0)
+ return r;
+
+ machine = hashmap_get(m->machines, name);
+ if (!machine)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name);
+
+ return bus_machine_method_open_root_directory(message, machine, error);
+}
+
+static int method_remove_image(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_(image_unrefp) Image* i = NULL;
+ const char *name;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "s", &name);
+ if (r < 0)
+ return r;
+
+ if (!image_name_is_valid(name))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name);
+
+ r = image_find(name, &i);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name);
+
+ i->userdata = userdata;
+ return bus_image_method_remove(message, i, error);
+}
+
+static int method_rename_image(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_(image_unrefp) Image* i = NULL;
+ const char *old_name;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "s", &old_name);
+ if (r < 0)
+ return r;
+
+ if (!image_name_is_valid(old_name))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", old_name);
+
+ r = image_find(old_name, &i);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", old_name);
+
+ i->userdata = userdata;
+ return bus_image_method_rename(message, i, error);
+}
+
+static int method_clone_image(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_(image_unrefp) Image *i = NULL;
+ const char *old_name;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "s", &old_name);
+ if (r < 0)
+ return r;
+
+ if (!image_name_is_valid(old_name))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", old_name);
+
+ r = image_find(old_name, &i);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", old_name);
+
+ i->userdata = userdata;
+ return bus_image_method_clone(message, i, error);
+}
+
+static int method_mark_image_read_only(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_(image_unrefp) Image *i = NULL;
+ const char *name;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "s", &name);
+ if (r < 0)
+ return r;
+
+ if (!image_name_is_valid(name))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name);
+
+ r = image_find(name, &i);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name);
+
+ i->userdata = userdata;
+ return bus_image_method_mark_read_only(message, i, error);
+}
+
+static int clean_pool_done(Operation *operation, int ret, sd_bus_error *error) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ bool success;
+ size_t n;
+ int r;
+
+ assert(operation);
+ assert(operation->extra_fd >= 0);
+
+ if (lseek(operation->extra_fd, 0, SEEK_SET) == (off_t) -1)
+ return -errno;
+
+ f = fdopen(operation->extra_fd, "re");
+ if (!f)
+ return -errno;
+
+ operation->extra_fd = -1;
+
+ /* The resulting temporary file starts with a boolean value that indicates success or not. */
+ errno = 0;
+ n = fread(&success, 1, sizeof(success), f);
+ if (n != sizeof(success))
+ return ret < 0 ? ret : (errno != 0 ? -errno : -EIO);
+
+ if (ret < 0) {
+ _cleanup_free_ char *name = NULL;
+
+ /* The clean-up operation failed. In this case the resulting temporary file should contain a boolean
+ * set to false followed by the name of the failed image. Let's try to read this and use it for the
+ * error message. If we can't read it, don't mind, and return the naked error. */
+
+ if (success) /* The resulting temporary file could not be updated, ignore it. */
+ return ret;
+
+ r = read_nul_string(f, &name);
+ if (r < 0 || isempty(name)) /* Same here... */
+ return ret;
+
+ return sd_bus_error_set_errnof(error, ret, "Failed to remove image %s: %m", name);
+ }
+
+ assert(success);
+
+ r = sd_bus_message_new_method_return(operation->message, &reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(reply, 'a', "(st)");
+ if (r < 0)
+ return r;
+
+ /* On success the resulting temporary file will contain a list of image names that were removed followed by
+ * their size on disk. Let's read that and turn it into a bus message. */
+ for (;;) {
+ _cleanup_free_ char *name = NULL;
+ uint64_t size;
+
+ r = read_nul_string(f, &name);
+ if (r < 0)
+ return r;
+ if (isempty(name)) /* reached the end */
+ break;
+
+ errno = 0;
+ n = fread(&size, 1, sizeof(size), f);
+ if (n != sizeof(size))
+ return errno != 0 ? -errno : -EIO;
+
+ r = sd_bus_message_append(reply, "(st)", name, size);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ return r;
+
+ return sd_bus_send(NULL, reply, NULL);
+}
+
+static int method_clean_pool(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ enum {
+ REMOVE_ALL,
+ REMOVE_HIDDEN,
+ } mode;
+
+ _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 };
+ _cleanup_close_ int result_fd = -1;
+ Manager *m = userdata;
+ Operation *operation;
+ const char *mm;
+ pid_t child;
+ int r;
+
+ assert(message);
+
+ if (m->n_operations >= OPERATIONS_MAX)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing operations.");
+
+ r = sd_bus_message_read(message, "s", &mm);
+ if (r < 0)
+ return r;
+
+ if (streq(mm, "all"))
+ mode = REMOVE_ALL;
+ else if (streq(mm, "hidden"))
+ mode = REMOVE_HIDDEN;
+ else
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown mode '%s'.", mm);
+
+ r = bus_verify_polkit_async(
+ message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.machine1.manage-machines",
+ NULL,
+ false,
+ UID_INVALID,
+ &m->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0)
+ return sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m");
+
+ /* Create a temporary file we can dump information about deleted images into. We use a temporary file for this
+ * instead of a pipe or so, since this might grow quit large in theory and we don't want to process this
+ * continuously */
+ result_fd = open_tmpfile_unlinkable("/tmp/", O_RDWR|O_CLOEXEC);
+ if (result_fd < 0)
+ return -errno;
+
+ /* This might be a slow operation, run it asynchronously in a background process */
+ child = fork();
+ if (child < 0)
+ return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m");
+
+ if (child == 0) {
+ _cleanup_(image_hashmap_freep) Hashmap *images = NULL;
+ bool success = true;
+ Image *image;
+ Iterator i;
+ ssize_t l;
+
+ errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
+
+ images = hashmap_new(&string_hash_ops);
+ if (!images) {
+ r = -ENOMEM;
+ goto child_fail;
+ }
+
+ r = image_discover(images);
+ if (r < 0)
+ goto child_fail;
+
+ l = write(result_fd, &success, sizeof(success));
+ if (l < 0) {
+ r = -errno;
+ goto child_fail;
+ }
+
+ HASHMAP_FOREACH(image, images, i) {
+
+ /* We can't remove vendor images (i.e. those in /usr) */
+ if (IMAGE_IS_VENDOR(image))
+ continue;
+
+ if (IMAGE_IS_HOST(image))
+ continue;
+
+ if (mode == REMOVE_HIDDEN && !IMAGE_IS_HIDDEN(image))
+ continue;
+
+ r = image_remove(image);
+ if (r == -EBUSY) /* keep images that are currently being used. */
+ continue;
+ if (r < 0) {
+ /* If the operation failed, let's override everything we wrote, and instead write there at which image we failed. */
+ success = false;
+ (void) ftruncate(result_fd, 0);
+ (void) lseek(result_fd, 0, SEEK_SET);
+ (void) write(result_fd, &success, sizeof(success));
+ (void) write(result_fd, image->name, strlen(image->name)+1);
+ goto child_fail;
+ }
+
+ l = write(result_fd, image->name, strlen(image->name)+1);
+ if (l < 0) {
+ r = -errno;
+ goto child_fail;
+ }
+
+ l = write(result_fd, &image->usage_exclusive, sizeof(image->usage_exclusive));
+ if (l < 0) {
+ r = -errno;
+ goto child_fail;
+ }
+ }
+
+ result_fd = safe_close(result_fd);
+ _exit(EXIT_SUCCESS);
+
+ child_fail:
+ (void) write(errno_pipe_fd[1], &r, sizeof(r));
+ _exit(EXIT_FAILURE);
+ }
+
+ errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
+
+ /* The clean-up might take a while, hence install a watch on the child and return */
+
+ r = operation_new(m, NULL, child, message, errno_pipe_fd[0], &operation);
+ if (r < 0) {
+ (void) sigkill_wait(child);
+ return r;
+ }
+
+ operation->extra_fd = result_fd;
+ operation->done = clean_pool_done;
+
+ result_fd = -1;
+ errno_pipe_fd[0] = -1;
+
+ return 1;
+}
+
+static int method_set_pool_limit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = userdata;
+ uint64_t limit;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "t", &limit);
+ if (r < 0)
+ return r;
+ if (!FILE_SIZE_VALID_OR_INFINITY(limit))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "New limit out of range");
+
+ r = bus_verify_polkit_async(
+ message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.machine1.manage-machines",
+ NULL,
+ false,
+ UID_INVALID,
+ &m->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* Will call us back */
+
+ /* Set up the machine directory if necessary */
+ r = setup_machine_directory(limit, error);
+ if (r < 0)
+ return r;
+
+ /* Resize the backing loopback device, if there is one, except if we asked to drop any limit */
+ if (limit != (uint64_t) -1) {
+ r = btrfs_resize_loopback("/var/lib/machines", limit, false);
+ if (r == -ENOTTY)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Quota is only supported on btrfs.");
+ if (r < 0 && r != -ENODEV) /* ignore ENODEV, as that's what is returned if the file system is not on loopback */
+ return sd_bus_error_set_errnof(error, r, "Failed to adjust loopback limit: %m");
+ }
+
+ (void) btrfs_qgroup_set_limit("/var/lib/machines", 0, limit);
+
+ r = btrfs_subvol_set_subtree_quota_limit("/var/lib/machines", 0, limit);
+ if (r == -ENOTTY)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Quota is only supported on btrfs.");
+ if (r < 0)
+ return sd_bus_error_set_errnof(error, r, "Failed to adjust quota limit: %m");
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_set_image_limit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_(image_unrefp) Image *i = NULL;
+ const char *name;
+ int r;
+
+ assert(message);
+
+ r = sd_bus_message_read(message, "s", &name);
+ if (r < 0)
+ return r;
+
+ if (!image_name_is_valid(name))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name);
+
+ r = image_find(name, &i);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name);
+
+ i->userdata = userdata;
+ return bus_image_method_set_limit(message, i, error);
+}
+
+static int method_map_from_machine_user(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_fclose_ FILE *f = NULL;
+ Manager *m = userdata;
+ const char *name, *p;
+ Machine *machine;
+ uint32_t uid;
+ int r;
+
+ r = sd_bus_message_read(message, "su", &name, &uid);
+ if (r < 0)
+ return r;
+
+ if (!uid_is_valid(uid))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid);
+
+ machine = hashmap_get(m->machines, name);
+ if (!machine)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name);
+
+ if (machine->class != MACHINE_CONTAINER)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Not supported for non-container machines.");
+
+ p = procfs_file_alloca(machine->leader, "uid_map");
+ f = fopen(p, "re");
+ if (!f)
+ return -errno;
+
+ for (;;) {
+ uid_t uid_base, uid_shift, uid_range, converted;
+ int k;
+
+ errno = 0;
+ k = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT, &uid_base, &uid_shift, &uid_range);
+ if (k < 0 && feof(f))
+ break;
+ if (k != 3) {
+ if (ferror(f) && errno > 0)
+ return -errno;
+
+ return -EIO;
+ }
+
+ if (uid < uid_base || uid >= uid_base + uid_range)
+ continue;
+
+ converted = uid - uid_base + uid_shift;
+ if (!uid_is_valid(converted))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid);
+
+ return sd_bus_reply_method_return(message, "u", (uint32_t) converted);
+ }
+
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_USER_MAPPING, "Machine '%s' has no matching user mappings.", name);
+}
+
+static int method_map_to_machine_user(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = userdata;
+ Machine *machine;
+ uid_t uid;
+ Iterator i;
+ int r;
+
+ r = sd_bus_message_read(message, "u", &uid);
+ if (r < 0)
+ return r;
+ if (!uid_is_valid(uid))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid);
+ if (uid < 0x10000)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_USER_MAPPING, "User " UID_FMT " belongs to host UID range", uid);
+
+ HASHMAP_FOREACH(machine, m->machines, i) {
+ _cleanup_fclose_ FILE *f = NULL;
+ char p[strlen("/proc//uid_map") + DECIMAL_STR_MAX(pid_t) + 1];
+
+ if (machine->class != MACHINE_CONTAINER)
+ continue;
+
+ xsprintf(p, "/proc/" UID_FMT "/uid_map", machine->leader);
+ f = fopen(p, "re");
+ if (!f) {
+ log_warning_errno(errno, "Failed top open %s, ignoring,", p);
+ continue;
+ }
+
+ for (;;) {
+ _cleanup_free_ char *o = NULL;
+ uid_t uid_base, uid_shift, uid_range, converted;
+ int k;
+
+ errno = 0;
+ k = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT, &uid_base, &uid_shift, &uid_range);
+ if (k < 0 && feof(f))
+ break;
+ if (k != 3) {
+ if (ferror(f) && errno > 0)
+ return -errno;
+
+ return -EIO;
+ }
+
+ if (uid < uid_shift || uid >= uid_shift + uid_range)
+ continue;
+
+ converted = (uid - uid_shift + uid_base);
+ if (!uid_is_valid(converted))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid);
+
+ o = machine_bus_path(machine);
+ if (!o)
+ return -ENOMEM;
+
+ return sd_bus_reply_method_return(message, "sou", machine->name, o, (uint32_t) converted);
+ }
+ }
+
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_USER_MAPPING, "No matching user mapping for " UID_FMT ".", uid);
+}
+
+static int method_map_from_machine_group(sd_bus_message *message, void *groupdata, sd_bus_error *error) {
+ _cleanup_fclose_ FILE *f = NULL;
+ Manager *m = groupdata;
+ const char *name, *p;
+ Machine *machine;
+ uint32_t gid;
+ int r;
+
+ r = sd_bus_message_read(message, "su", &name, &gid);
+ if (r < 0)
+ return r;
+
+ if (!gid_is_valid(gid))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid);
+
+ machine = hashmap_get(m->machines, name);
+ if (!machine)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name);
+
+ if (machine->class != MACHINE_CONTAINER)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Not supported for non-container machines.");
+
+ p = procfs_file_alloca(machine->leader, "gid_map");
+ f = fopen(p, "re");
+ if (!f)
+ return -errno;
+
+ for (;;) {
+ gid_t gid_base, gid_shift, gid_range, converted;
+ int k;
+
+ errno = 0;
+ k = fscanf(f, GID_FMT " " GID_FMT " " GID_FMT, &gid_base, &gid_shift, &gid_range);
+ if (k < 0 && feof(f))
+ break;
+ if (k != 3) {
+ if (ferror(f) && errno > 0)
+ return -errno;
+
+ return -EIO;
+ }
+
+ if (gid < gid_base || gid >= gid_base + gid_range)
+ continue;
+
+ converted = gid - gid_base + gid_shift;
+ if (!gid_is_valid(converted))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid);
+
+ return sd_bus_reply_method_return(message, "u", (uint32_t) converted);
+ }
+
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_GROUP_MAPPING, "Machine '%s' has no matching group mappings.", name);
+}
+
+static int method_map_to_machine_group(sd_bus_message *message, void *groupdata, sd_bus_error *error) {
+ Manager *m = groupdata;
+ Machine *machine;
+ gid_t gid;
+ Iterator i;
+ int r;
+
+ r = sd_bus_message_read(message, "u", &gid);
+ if (r < 0)
+ return r;
+ if (!gid_is_valid(gid))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid);
+ if (gid < 0x10000)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_GROUP_MAPPING, "Group " GID_FMT " belongs to host GID range", gid);
+
+ HASHMAP_FOREACH(machine, m->machines, i) {
+ _cleanup_fclose_ FILE *f = NULL;
+ char p[strlen("/proc//gid_map") + DECIMAL_STR_MAX(pid_t) + 1];
+
+ if (machine->class != MACHINE_CONTAINER)
+ continue;
+
+ xsprintf(p, "/proc/" GID_FMT "/gid_map", machine->leader);
+ f = fopen(p, "re");
+ if (!f) {
+ log_warning_errno(errno, "Failed top open %s, ignoring,", p);
+ continue;
+ }
+
+ for (;;) {
+ _cleanup_free_ char *o = NULL;
+ gid_t gid_base, gid_shift, gid_range, converted;
+ int k;
+
+ errno = 0;
+ k = fscanf(f, GID_FMT " " GID_FMT " " GID_FMT, &gid_base, &gid_shift, &gid_range);
+ if (k < 0 && feof(f))
+ break;
+ if (k != 3) {
+ if (ferror(f) && errno > 0)
+ return -errno;
+
+ return -EIO;
+ }
+
+ if (gid < gid_shift || gid >= gid_shift + gid_range)
+ continue;
+
+ converted = (gid - gid_shift + gid_base);
+ if (!gid_is_valid(converted))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid);
+
+ o = machine_bus_path(machine);
+ if (!o)
+ return -ENOMEM;
+
+ return sd_bus_reply_method_return(message, "sou", machine->name, o, (uint32_t) converted);
+ }
+ }
+
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_GROUP_MAPPING, "No matching group mapping for " GID_FMT ".", gid);
+}
+
+const sd_bus_vtable manager_vtable[] = {
+ SD_BUS_VTABLE_START(0),
+ SD_BUS_PROPERTY("PoolPath", "s", property_get_pool_path, 0, 0),
+ SD_BUS_PROPERTY("PoolUsage", "t", property_get_pool_usage, 0, 0),
+ SD_BUS_PROPERTY("PoolLimit", "t", property_get_pool_limit, 0, 0),
+ SD_BUS_METHOD("GetMachine", "s", "o", method_get_machine, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("GetImage", "s", "o", method_get_image, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("GetMachineByPID", "u", "o", method_get_machine_by_pid, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("ListMachines", NULL, "a(ssso)", method_list_machines, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("ListImages", NULL, "a(ssbttto)", method_list_images, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("CreateMachine", "sayssusa(sv)", "o", method_create_machine, 0),
+ SD_BUS_METHOD("CreateMachineWithNetwork", "sayssusaia(sv)", "o", method_create_machine_with_network, 0),
+ SD_BUS_METHOD("RegisterMachine", "sayssus", "o", method_register_machine, 0),
+ SD_BUS_METHOD("RegisterMachineWithNetwork", "sayssusai", "o", method_register_machine_with_network, 0),
+ SD_BUS_METHOD("TerminateMachine", "s", NULL, method_terminate_machine, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("KillMachine", "ssi", NULL, method_kill_machine, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("GetMachineAddresses", "s", "a(iay)", method_get_machine_addresses, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("GetMachineOSRelease", "s", "a{ss}", method_get_machine_os_release, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("OpenMachinePTY", "s", "hs", method_open_machine_pty, 0),
+ SD_BUS_METHOD("OpenMachineLogin", "s", "hs", method_open_machine_login, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("OpenMachineShell", "sssasas", "hs", method_open_machine_shell, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("BindMountMachine", "sssbb", NULL, method_bind_mount_machine, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("CopyFromMachine", "sss", NULL, method_copy_machine, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("CopyToMachine", "sss", NULL, method_copy_machine, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("OpenMachineRootDirectory", "s", "h", method_open_machine_root_directory, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("RemoveImage", "s", NULL, method_remove_image, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("RenameImage", "ss", NULL, method_rename_image, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("CloneImage", "ssb", NULL, method_clone_image, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("MarkImageReadOnly", "sb", NULL, method_mark_image_read_only, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("SetPoolLimit", "t", NULL, method_set_pool_limit, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("SetImageLimit", "st", NULL, method_set_image_limit, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("CleanPool", "s", "a(st)", method_clean_pool, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("MapFromMachineUser", "su", "u", method_map_from_machine_user, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("MapToMachineUser", "u", "sou", method_map_to_machine_user, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("MapFromMachineGroup", "su", "u", method_map_from_machine_group, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("MapToMachineGroup", "u", "sou", method_map_to_machine_group, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_SIGNAL("MachineNew", "so", 0),
+ SD_BUS_SIGNAL("MachineRemoved", "so", 0),
+ SD_BUS_VTABLE_END
+};
+
+int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ const char *path, *result, *unit;
+ Manager *m = userdata;
+ Machine *machine;
+ uint32_t id;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = sd_bus_message_read(message, "uoss", &id, &path, &unit, &result);
+ if (r < 0) {
+ bus_log_parse_error(r);
+ return 0;
+ }
+
+ machine = hashmap_get(m->machine_units, unit);
+ if (!machine)
+ return 0;
+
+ if (streq_ptr(path, machine->scope_job)) {
+ machine->scope_job = mfree(machine->scope_job);
+
+ if (machine->started) {
+ if (streq(result, "done"))
+ machine_send_create_reply(machine, NULL);
+ else {
+ _cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL;
+
+ sd_bus_error_setf(&e, BUS_ERROR_JOB_FAILED, "Start job for unit %s failed with '%s'", unit, result);
+
+ machine_send_create_reply(machine, &e);
+ }
+ }
+
+ machine_save(machine);
+ }
+
+ machine_add_to_gc_queue(machine);
+ return 0;
+}
+
+int match_properties_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_free_ char *unit = NULL;
+ const char *path;
+ Manager *m = userdata;
+ Machine *machine;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ path = sd_bus_message_get_path(message);
+ if (!path)
+ return 0;
+
+ r = unit_name_from_dbus_path(path, &unit);
+ if (r == -EINVAL) /* not for a unit */
+ return 0;
+ if (r < 0) {
+ log_oom();
+ return 0;
+ }
+
+ machine = hashmap_get(m->machine_units, unit);
+ if (!machine)
+ return 0;
+
+ machine_add_to_gc_queue(machine);
+ return 0;
+}
+
+int match_unit_removed(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ const char *path, *unit;
+ Manager *m = userdata;
+ Machine *machine;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = sd_bus_message_read(message, "so", &unit, &path);
+ if (r < 0) {
+ bus_log_parse_error(r);
+ return 0;
+ }
+
+ machine = hashmap_get(m->machine_units, unit);
+ if (!machine)
+ return 0;
+
+ machine_add_to_gc_queue(machine);
+ return 0;
+}
+
+int match_reloading(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = userdata;
+ Machine *machine;
+ Iterator i;
+ int b, r;
+
+ assert(message);
+ assert(m);
+
+ r = sd_bus_message_read(message, "b", &b);
+ if (r < 0) {
+ bus_log_parse_error(r);
+ return 0;
+ }
+ if (b)
+ return 0;
+
+ /* systemd finished reloading, let's recheck all our machines */
+ log_debug("System manager has been reloaded, rechecking machines...");
+
+ HASHMAP_FOREACH(machine, m->machines, i)
+ machine_add_to_gc_queue(machine);
+
+ return 0;
+}
+
+int manager_start_scope(
+ Manager *manager,
+ const char *scope,
+ pid_t pid,
+ const char *slice,
+ const char *description,
+ sd_bus_message *more_properties,
+ sd_bus_error *error,
+ char **job) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
+ int r;
+
+ assert(manager);
+ assert(scope);
+ assert(pid > 1);
+
+ r = sd_bus_message_new_method_call(
+ manager->bus,
+ &m,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "StartTransientUnit");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(m, "ss", strempty(scope), "fail");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(m, 'a', "(sv)");
+ if (r < 0)
+ return r;
+
+ if (!isempty(slice)) {
+ r = sd_bus_message_append(m, "(sv)", "Slice", "s", slice);
+ if (r < 0)
+ return r;
+ }
+
+ if (!isempty(description)) {
+ r = sd_bus_message_append(m, "(sv)", "Description", "s", description);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, pid);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(m, "(sv)", "Delegate", "b", 1);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(m, "(sv)", "TasksMax", "t", UINT64_C(16384));
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ if (more_properties) {
+ r = sd_bus_message_copy(m, more_properties, true);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(m, "a(sa(sv))", 0);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_call(manager->bus, m, 0, error, &reply);
+ if (r < 0)
+ return r;
+
+ if (job) {
+ const char *j;
+ char *copy;
+
+ r = sd_bus_message_read(reply, "o", &j);
+ if (r < 0)
+ return r;
+
+ copy = strdup(j);
+ if (!copy)
+ return -ENOMEM;
+
+ *job = copy;
+ }
+
+ return 1;
+}
+
+int manager_stop_unit(Manager *manager, const char *unit, sd_bus_error *error, char **job) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ int r;
+
+ assert(manager);
+ assert(unit);
+
+ r = sd_bus_call_method(
+ manager->bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "StopUnit",
+ error,
+ &reply,
+ "ss", unit, "fail");
+ if (r < 0) {
+ if (sd_bus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT) ||
+ sd_bus_error_has_name(error, BUS_ERROR_LOAD_FAILED)) {
+
+ if (job)
+ *job = NULL;
+
+ sd_bus_error_free(error);
+ return 0;
+ }
+
+ return r;
+ }
+
+ if (job) {
+ const char *j;
+ char *copy;
+
+ r = sd_bus_message_read(reply, "o", &j);
+ if (r < 0)
+ return r;
+
+ copy = strdup(j);
+ if (!copy)
+ return -ENOMEM;
+
+ *job = copy;
+ }
+
+ return 1;
+}
+
+int manager_kill_unit(Manager *manager, const char *unit, int signo, sd_bus_error *error) {
+ assert(manager);
+ assert(unit);
+
+ return sd_bus_call_method(
+ manager->bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "KillUnit",
+ error,
+ NULL,
+ "ssi", unit, "all", signo);
+}
+
+int manager_unit_is_active(Manager *manager, const char *unit) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_free_ char *path = NULL;
+ const char *state;
+ int r;
+
+ assert(manager);
+ assert(unit);
+
+ path = unit_dbus_path_from_name(unit);
+ if (!path)
+ return -ENOMEM;
+
+ r = sd_bus_get_property(
+ manager->bus,
+ "org.freedesktop.systemd1",
+ path,
+ "org.freedesktop.systemd1.Unit",
+ "ActiveState",
+ &error,
+ &reply,
+ "s");
+ if (r < 0) {
+ if (sd_bus_error_has_name(&error, SD_BUS_ERROR_NO_REPLY) ||
+ sd_bus_error_has_name(&error, SD_BUS_ERROR_DISCONNECTED))
+ return true;
+
+ if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_UNIT) ||
+ sd_bus_error_has_name(&error, BUS_ERROR_LOAD_FAILED))
+ return false;
+
+ return r;
+ }
+
+ r = sd_bus_message_read(reply, "s", &state);
+ if (r < 0)
+ return -EINVAL;
+
+ return !STR_IN_SET(state, "inactive", "failed");
+}
+
+int manager_job_is_active(Manager *manager, const char *path) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ int r;
+
+ assert(manager);
+ assert(path);
+
+ r = sd_bus_get_property(
+ manager->bus,
+ "org.freedesktop.systemd1",
+ path,
+ "org.freedesktop.systemd1.Job",
+ "State",
+ &error,
+ &reply,
+ "s");
+ if (r < 0) {
+ if (sd_bus_error_has_name(&error, SD_BUS_ERROR_NO_REPLY) ||
+ sd_bus_error_has_name(&error, SD_BUS_ERROR_DISCONNECTED))
+ return true;
+
+ if (sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_OBJECT))
+ return false;
+
+ return r;
+ }
+
+ /* We don't actually care about the state really. The fact
+ * that we could read the job state is enough for us */
+
+ return true;
+}
+
+int manager_get_machine_by_pid(Manager *m, pid_t pid, Machine **machine) {
+ Machine *mm;
+ int r;
+
+ assert(m);
+ assert(pid >= 1);
+ assert(machine);
+
+ mm = hashmap_get(m->machine_leaders, PID_TO_PTR(pid));
+ if (!mm) {
+ _cleanup_free_ char *unit = NULL;
+
+ r = cg_pid_get_unit(pid, &unit);
+ if (r >= 0)
+ mm = hashmap_get(m->machine_units, unit);
+ }
+ if (!mm)
+ return 0;
+
+ *machine = mm;
+ return 1;
+}
+
+int manager_add_machine(Manager *m, const char *name, Machine **_machine) {
+ Machine *machine;
+
+ assert(m);
+ assert(name);
+
+ machine = hashmap_get(m->machines, name);
+ if (!machine) {
+ machine = machine_new(m, _MACHINE_CLASS_INVALID, name);
+ if (!machine)
+ return -ENOMEM;
+ }
+
+ if (_machine)
+ *_machine = machine;
+
+ return 0;
+}
diff --git a/src/grp-machine/libmachine-core/src/operation.c b/src/grp-machine/libmachine-core/src/operation.c
new file mode 100644
index 0000000000..715e867e4f
--- /dev/null
+++ b/src/grp-machine/libmachine-core/src/operation.c
@@ -0,0 +1,152 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2016 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "machine-core/operation.h"
+#include "systemd-basic/alloc-util.h"
+#include "systemd-basic/fd-util.h"
+#include "systemd-basic/process-util.h"
+
+static int operation_done(sd_event_source *s, const siginfo_t *si, void *userdata) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ Operation *o = userdata;
+ int r;
+
+ assert(o);
+ assert(si);
+
+ log_debug("Operating " PID_FMT " is now complete with code=%s status=%i",
+ o->pid,
+ sigchld_code_to_string(si->si_code), si->si_status);
+
+ o->pid = 0;
+
+ if (si->si_code != CLD_EXITED) {
+ r = sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED, "Child died abnormally.");
+ goto fail;
+ }
+
+ if (si->si_status == EXIT_SUCCESS)
+ r = 0;
+ else if (read(o->errno_fd, &r, sizeof(r)) != sizeof(r)) { /* Try to acquire error code for failed operation */
+ r = sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED, "Child failed.");
+ goto fail;
+ }
+
+ if (o->done) {
+ /* A completion routine is set for this operation, call it. */
+ r = o->done(o, r, &error);
+ if (r < 0) {
+ if (!sd_bus_error_is_set(&error))
+ sd_bus_error_set_errno(&error, r);
+
+ goto fail;
+ }
+
+ } else {
+ /* The default operation when done is to simply return an error on failure or an empty success
+ * message on success. */
+ if (r < 0)
+ goto fail;
+
+ r = sd_bus_reply_method_return(o->message, NULL);
+ if (r < 0)
+ log_error_errno(r, "Failed to reply to message: %m");
+ }
+
+ operation_free(o);
+ return 0;
+
+fail:
+ r = sd_bus_reply_method_error(o->message, &error);
+ if (r < 0)
+ log_error_errno(r, "Failed to reply to message: %m");
+
+ operation_free(o);
+ return 0;
+}
+
+int operation_new(Manager *manager, Machine *machine, pid_t child, sd_bus_message *message, int errno_fd, Operation **ret) {
+ Operation *o;
+ int r;
+
+ assert(manager);
+ assert(child > 1);
+ assert(message);
+ assert(errno_fd >= 0);
+
+ o = new0(Operation, 1);
+ if (!o)
+ return -ENOMEM;
+
+ o->extra_fd = -1;
+
+ r = sd_event_add_child(manager->event, &o->event_source, child, WEXITED, operation_done, o);
+ if (r < 0) {
+ free(o);
+ return r;
+ }
+
+ o->pid = child;
+ o->message = sd_bus_message_ref(message);
+ o->errno_fd = errno_fd;
+
+ LIST_PREPEND(operations, manager->operations, o);
+ manager->n_operations++;
+ o->manager = manager;
+
+ if (machine) {
+ LIST_PREPEND(operations_by_machine, machine->operations, o);
+ o->machine = machine;
+ }
+
+ log_debug("Started new operation " PID_FMT ".", child);
+
+ /* At this point we took ownership of both the child and the errno file descriptor! */
+
+ if (ret)
+ *ret = o;
+
+ return 0;
+}
+
+Operation *operation_free(Operation *o) {
+ if (!o)
+ return NULL;
+
+ sd_event_source_unref(o->event_source);
+
+ safe_close(o->errno_fd);
+ safe_close(o->extra_fd);
+
+ if (o->pid > 1)
+ (void) sigkill_wait(o->pid);
+
+ sd_bus_message_unref(o->message);
+
+ if (o->manager) {
+ LIST_REMOVE(operations, o->manager->operations, o);
+ o->manager->n_operations--;
+ }
+
+ if (o->machine)
+ LIST_REMOVE(operations_by_machine, o->machine->operations, o);
+
+ free(o);
+ return NULL;
+}
diff --git a/src/grp-machine/libmachine-core/test/test-machine-tables.c b/src/grp-machine/libmachine-core/test/test-machine-tables.c
new file mode 100644
index 0000000000..5f50c3856b
--- /dev/null
+++ b/src/grp-machine/libmachine-core/test/test-machine-tables.c
@@ -0,0 +1,29 @@
+/***
+ This file is part of systemd
+
+ Copyright 2013 Zbigniew Jędrzejewski-Szmek
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "machine-core/machine.h"
+#include "systemd-shared/test-tables.h"
+
+int main(int argc, char **argv) {
+ test_table(machine_class, MACHINE_CLASS);
+ test_table(machine_state, MACHINE_STATE);
+ test_table(kill_who, KILL_WHO);
+
+ return EXIT_SUCCESS;
+}