diff options
Diffstat (limited to 'src/machine/src')
-rw-r--r-- | src/machine/src/.gitignore | 1 | ||||
-rw-r--r-- | src/machine/src/Makefile | 128 | ||||
-rw-r--r-- | src/machine/src/image-dbus.c | 361 | ||||
-rw-r--r-- | src/machine/src/image-dbus.h | 35 | ||||
-rw-r--r-- | src/machine/src/machine-dbus.c | 1435 | ||||
-rw-r--r-- | src/machine/src/machine-dbus.h | 43 | ||||
-rw-r--r-- | src/machine/src/machine.c | 659 | ||||
-rw-r--r-- | src/machine/src/machine.h | 124 | ||||
-rw-r--r-- | src/machine/src/machinectl.c | 2678 | ||||
-rw-r--r-- | src/machine/src/machined-dbus.c | 1551 | ||||
-rw-r--r-- | src/machine/src/machined.c | 407 | ||||
-rw-r--r-- | src/machine/src/machined.h | 78 | ||||
-rw-r--r-- | src/machine/src/org.freedesktop.machine1.conf | 194 | ||||
-rw-r--r-- | src/machine/src/org.freedesktop.machine1.policy.in | 102 | ||||
-rw-r--r-- | src/machine/src/org.freedesktop.machine1.service | 12 | ||||
-rw-r--r-- | src/machine/src/test-machine-tables.c | 29 |
16 files changed, 7837 insertions, 0 deletions
diff --git a/src/machine/src/.gitignore b/src/machine/src/.gitignore new file mode 100644 index 0000000000..e1065b5894 --- /dev/null +++ b/src/machine/src/.gitignore @@ -0,0 +1 @@ +/org.freedesktop.machine1.policy diff --git a/src/machine/src/Makefile b/src/machine/src/Makefile new file mode 100644 index 0000000000..43a799440d --- /dev/null +++ b/src/machine/src/Makefile @@ -0,0 +1,128 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_MACHINED),) +systemd_machined_SOURCES = \ + src/machine/machined.c \ + src/machine/machined.h + +systemd_machined_LDADD = \ + libmachine-core.la + +rootlibexec_PROGRAMS += \ + systemd-machined + +libmachine_core_la_SOURCES = \ + src/machine/machine.c \ + src/machine/machine.h \ + src/machine/machined-dbus.c \ + src/machine/machine-dbus.c \ + src/machine/machine-dbus.h \ + src/machine/image-dbus.c \ + src/machine/image-dbus.h + +libmachine_core_la_LIBADD = \ + libshared.la + +noinst_LTLIBRARIES += \ + libmachine-core.la + +machinectl_SOURCES = \ + src/machine/machinectl.c + +machinectl_LDADD = \ + libshared.la + +rootbin_PROGRAMS += \ + machinectl + +test_machine_tables_SOURCES = \ + src/machine/test-machine-tables.c + +test_machine_tables_LDADD = \ + libmachine-core.la + +tests += \ + test-machine-tables + +nodist_systemunit_DATA += \ + units/systemd-machined.service + +dist_systemunit_DATA += \ + units/machine.slice + +dist_systemunit_DATA_busnames += \ + units/org.freedesktop.machine1.busname + +dist_dbussystemservice_DATA += \ + src/machine/org.freedesktop.machine1.service + +dist_dbuspolicy_DATA += \ + src/machine/org.freedesktop.machine1.conf + +polkitpolicy_files += \ + src/machine/org.freedesktop.machine1.policy + +dist_bashcompletion_data += \ + shell-completion/bash/machinectl + +dist_zshcompletion_data += \ + shell-completion/zsh/_machinectl \ + shell-completion/zsh/_sd_machines + +SYSTEM_UNIT_ALIASES += \ + systemd-machined.service dbus-org.freedesktop.machine1.service + +BUSNAMES_TARGET_WANTS += \ + org.freedesktop.machine1.busname + +libnss_mymachines_la_SOURCES = \ + src/nss-mymachines/nss-mymachines.sym \ + src/nss-mymachines/nss-mymachines.c + +libnss_mymachines_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -module \ + -export-dynamic \ + -avoid-version \ + -shared \ + -shrext .so.2 \ + -Wl,--version-script=$(top_srcdir)/src/nss-mymachines/nss-mymachines.sym + +libnss_mymachines_la_LIBADD = \ + libsystemd-internal.la + +lib_LTLIBRARIES += \ + libnss_mymachines.la + +endif + +polkitpolicy_in_files += \ + src/machine/org.freedesktop.machine1.policy.in + +EXTRA_DIST += \ + units/systemd-machined.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/machine/src/image-dbus.c b/src/machine/src/image-dbus.c new file mode 100644 index 0000000000..73f5112c4d --- /dev/null +++ b/src/machine/src/image-dbus.c @@ -0,0 +1,361 @@ +/*** + 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 "alloc-util.h" +#include "bus-label.h" +#include "bus-util.h" +#include "image-dbus.h" +#include "io-util.h" +#include "machine-image.h" +#include "strv.h" +#include "user-util.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) { + + Image *image = userdata; + Manager *m = image->userdata; + int r; + + assert(message); + assert(image); + + 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_remove(image); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +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) { + + Image *image = userdata; + Manager *m = image->userdata; + const char *new_name; + int r, read_only; + + assert(message); + assert(image); + + 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 */ + + r = image_clone(image, new_name, read_only); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +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/machine/src/image-dbus.h b/src/machine/src/image-dbus.h new file mode 100644 index 0000000000..b62da996c6 --- /dev/null +++ b/src/machine/src/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/machine/src/machine-dbus.c b/src/machine/src/machine-dbus.c new file mode 100644 index 0000000000..71f20b3f07 --- /dev/null +++ b/src/machine/src/machine-dbus.c @@ -0,0 +1,1435 @@ +/*** + 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> + +/* 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 "alloc-util.h" +#include "bus-common-errors.h" +#include "bus-internal.h" +#include "bus-label.h" +#include "bus-util.h" +#include "copy.h" +#include "env-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "fs-util.h" +#include "in-addr-util.h" +#include "local-addresses.h" +#include "machine-dbus.h" +#include "machine.h" +#include "mkdir.h" +#include "path-util.h" +#include "process-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "user-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 (signo <= 0 || signo >= _NSIG) + 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; + if (isempty(user)) + user = NULL; + 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", NULL); + 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; +} + +static int machine_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; + MachineOperation *o = userdata; + int r; + + assert(o); + assert(si); + + 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) { + if (read(o->errno_fd, &r, sizeof(r)) == sizeof(r)) + r = sd_bus_error_set_errnof(&error, r, "%m"); + else + r = sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED, "Child failed."); + + goto fail; + } + + r = sd_bus_reply_method_return(o->message, NULL); + if (r < 0) + log_error_errno(r, "Failed to reply to message: %m"); + + machine_operation_unref(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"); + + machine_operation_unref(o); + return 0; +} + +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; + MachineOperation *o; + bool copy_from; + pid_t child; + char *t; + int r; + + assert(message); + assert(m); + + if (m->n_operations >= MACHINE_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)); + errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); + + _exit(EXIT_FAILURE); + } + + errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); + + /* Copying might take a while, hence install a watch the + * child, and return */ + + o = new0(MachineOperation, 1); + if (!o) + return log_oom(); + + o->pid = child; + o->message = sd_bus_message_ref(message); + o->errno_fd = errno_pipe_fd[0]; + errno_pipe_fd[0] = -1; + + r = sd_event_add_child(m->manager->event, &o->event_source, child, WEXITED, machine_operation_done, o); + if (r < 0) { + machine_operation_unref(o); + return log_oom(); + } + + LIST_PREPEND(operations, m->operations, o); + m->n_operations++; + o->machine = m; + + return 1; +} + +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_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/machine/src/machine-dbus.h b/src/machine/src/machine-dbus.h new file mode 100644 index 0000000000..224f36529f --- /dev/null +++ b/src/machine/src/machine-dbus.h @@ -0,0 +1,43 @@ +#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 machine_send_signal(Machine *m, bool new_machine); +int machine_send_create_reply(Machine *m, sd_bus_error *error); diff --git a/src/machine/src/machine.c b/src/machine/src/machine.c new file mode 100644 index 0000000000..468fc1fecf --- /dev/null +++ b/src/machine/src/machine.c @@ -0,0 +1,659 @@ +/*** + 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 "alloc-util.h" +#include "bus-error.h" +#include "bus-util.h" +#include "escape.h" +#include "extract-word.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "hashmap.h" +#include "machine-dbus.h" +#include "machine.h" +#include "mkdir.h" +#include "parse-util.h" +#include "process-util.h" +#include "special.h" +#include "string-table.h" +#include "terminal-util.h" +#include "unit-name.h" +#include "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) + machine_operation_unref(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_equal(m->id, SD_ID128_NULL)) + 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) { + unsigned long long l; + if (sscanf(realtime, "%llu", &l) > 0) + m->timestamp.realtime = l; + } + + if (monotonic) { + unsigned long long l; + if (sscanf(monotonic, "%llu", &l) > 0) + m->timestamp.monotonic = l; + } + + 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; + } +} + +MachineOperation *machine_operation_unref(MachineOperation *o) { + if (!o) + return NULL; + + sd_event_source_unref(o->event_source); + + safe_close(o->errno_fd); + + if (o->pid > 1) + (void) kill(o->pid, SIGKILL); + + sd_bus_message_unref(o->message); + + if (o->machine) { + LIST_REMOVE(operations, o->machine->operations, o); + o->machine->n_operations--; + } + + free(o); + return NULL; +} + +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/machine/src/machine.h b/src/machine/src/machine.h new file mode 100644 index 0000000000..1d8cc5911a --- /dev/null +++ b/src/machine/src/machine.h @@ -0,0 +1,124 @@ +#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/>. +***/ + +typedef struct Machine Machine; +typedef struct MachineOperation MachineOperation; +typedef enum KillWho KillWho; + +#include "list.h" +#include "machined.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 +}; + +#define MACHINE_OPERATIONS_MAX 64 + +struct MachineOperation { + Machine *machine; + pid_t pid; + sd_bus_message *message; + int errno_fd; + sd_event_source *event_source; + LIST_FIELDS(MachineOperation, operations); +}; + +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_FIELDS(Machine, gc_queue); + + MachineOperation *operations; + unsigned n_operations; +}; + +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); + +MachineOperation *machine_operation_unref(MachineOperation *o); + +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/machine/src/machinectl.c b/src/machine/src/machinectl.c new file mode 100644 index 0000000000..fb743ab6cb --- /dev/null +++ b/src/machine/src/machinectl.c @@ -0,0 +1,2678 @@ +/*** + 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 <arpa/inet.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <locale.h> +#include <net/if.h> +#include <netinet/in.h> +#include <string.h> +#include <sys/mount.h> +#include <sys/socket.h> +#include <unistd.h> + +#include <systemd/sd-bus.h> + +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-util.h" +#include "cgroup-show.h" +#include "cgroup-util.h" +#include "copy.h" +#include "env-util.h" +#include "fd-util.h" +#include "hostname-util.h" +#include "import-util.h" +#include "log.h" +#include "logs-show.h" +#include "macro.h" +#include "mkdir.h" +#include "pager.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "ptyfwd.h" +#include "signal-util.h" +#include "spawn-polkit-agent.h" +#include "strv.h" +#include "terminal-util.h" +#include "unit-name.h" +#include "util.h" +#include "verbs.h" +#include "web-util.h" + +static char **arg_property = NULL; +static bool arg_all = false; +static bool arg_full = false; +static bool arg_no_pager = false; +static bool arg_legend = true; +static const char *arg_kill_who = NULL; +static int arg_signal = SIGTERM; +static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; +static char *arg_host = NULL; +static bool arg_read_only = false; +static bool arg_mkdir = false; +static bool arg_quiet = false; +static bool arg_ask_password = true; +static unsigned arg_lines = 10; +static OutputMode arg_output = OUTPUT_SHORT; +static bool arg_force = false; +static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE; +static const char* arg_format = NULL; +static const char *arg_uid = NULL; +static char **arg_setenv = NULL; + +static void pager_open_if_enabled(void) { + + if (arg_no_pager) + return; + + pager_open(false); +} + +static void polkit_agent_open_if_enabled(void) { + + /* Open the polkit agent as a child process if necessary */ + + if (!arg_ask_password) + return; + + if (arg_transport != BUS_TRANSPORT_LOCAL) + return; + + polkit_agent_open(); +} + +static OutputFlags get_output_flags(void) { + return + arg_all * OUTPUT_SHOW_ALL | + arg_full * OUTPUT_FULL_WIDTH | + (!on_tty() || pager_have()) * OUTPUT_FULL_WIDTH | + colors_enabled() * OUTPUT_COLOR | + !arg_quiet * OUTPUT_WARN_CUTOFF; +} + +typedef struct MachineInfo { + const char *name; + const char *class; + const char *service; +} MachineInfo; + +static int compare_machine_info(const void *a, const void *b) { + const MachineInfo *x = a, *y = b; + + return strcmp(x->name, y->name); +} + +static int list_machines(int argc, char *argv[], void *userdata) { + + size_t max_name = strlen("MACHINE"), max_class = strlen("CLASS"), max_service = strlen("SERVICE"); + _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_ MachineInfo *machines = NULL; + const char *name, *class, *service, *object; + size_t n_machines = 0, n_allocated = 0, j; + sd_bus *bus = userdata; + int r; + + assert(bus); + + pager_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "ListMachines", + &error, + &reply, + NULL); + if (r < 0) { + log_error("Could not get machines: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_enter_container(reply, 'a', "(ssso)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "(ssso)", &name, &class, &service, &object)) > 0) { + size_t l; + + if (name[0] == '.' && !arg_all) + continue; + + if (!GREEDY_REALLOC(machines, n_allocated, n_machines + 1)) + return log_oom(); + + machines[n_machines].name = name; + machines[n_machines].class = class; + machines[n_machines].service = service; + + l = strlen(name); + if (l > max_name) + max_name = l; + + l = strlen(class); + if (l > max_class) + max_class = l; + + l = strlen(service); + if (l > max_service) + max_service = l; + + n_machines ++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + qsort_safe(machines, n_machines, sizeof(MachineInfo), compare_machine_info); + + if (arg_legend) + printf("%-*s %-*s %-*s\n", + (int) max_name, "MACHINE", + (int) max_class, "CLASS", + (int) max_service, "SERVICE"); + + for (j = 0; j < n_machines; j++) + printf("%-*s %-*s %-*s\n", + (int) max_name, machines[j].name, + (int) max_class, machines[j].class, + (int) max_service, machines[j].service); + + if (arg_legend) + printf("\n%zu machines listed.\n", n_machines); + + return 0; +} + +typedef struct ImageInfo { + const char *name; + const char *type; + bool read_only; + usec_t crtime; + usec_t mtime; + uint64_t size; +} ImageInfo; + +static int compare_image_info(const void *a, const void *b) { + const ImageInfo *x = a, *y = b; + + return strcmp(x->name, y->name); +} + +static int list_images(int argc, char *argv[], void *userdata) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + size_t max_name = strlen("NAME"), max_type = strlen("TYPE"), max_size = strlen("USAGE"), max_crtime = strlen("CREATED"), max_mtime = strlen("MODIFIED"); + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ ImageInfo *images = NULL; + size_t n_images = 0, n_allocated = 0, j; + const char *name, *type, *object; + sd_bus *bus = userdata; + uint64_t crtime, mtime, size; + int read_only, r; + + assert(bus); + + pager_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "ListImages", + &error, + &reply, + ""); + if (r < 0) { + log_error("Could not get images: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssbttto)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "(ssbttto)", &name, &type, &read_only, &crtime, &mtime, &size, &object)) > 0) { + char buf[MAX(FORMAT_TIMESTAMP_MAX, FORMAT_BYTES_MAX)]; + size_t l; + + if (name[0] == '.' && !arg_all) + continue; + + if (!GREEDY_REALLOC(images, n_allocated, n_images + 1)) + return log_oom(); + + images[n_images].name = name; + images[n_images].type = type; + images[n_images].read_only = read_only; + images[n_images].crtime = crtime; + images[n_images].mtime = mtime; + images[n_images].size = size; + + l = strlen(name); + if (l > max_name) + max_name = l; + + l = strlen(type); + if (l > max_type) + max_type = l; + + if (crtime != 0) { + l = strlen(strna(format_timestamp(buf, sizeof(buf), crtime))); + if (l > max_crtime) + max_crtime = l; + } + + if (mtime != 0) { + l = strlen(strna(format_timestamp(buf, sizeof(buf), mtime))); + if (l > max_mtime) + max_mtime = l; + } + + if (size != (uint64_t) -1) { + l = strlen(strna(format_bytes(buf, sizeof(buf), size))); + if (l > max_size) + max_size = l; + } + + n_images++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + qsort_safe(images, n_images, sizeof(ImageInfo), compare_image_info); + + if (arg_legend) + printf("%-*s %-*s %-3s %-*s %-*s %-*s\n", + (int) max_name, "NAME", + (int) max_type, "TYPE", + "RO", + (int) max_size, "USAGE", + (int) max_crtime, "CREATED", + (int) max_mtime, "MODIFIED"); + + for (j = 0; j < n_images; j++) { + char crtime_buf[FORMAT_TIMESTAMP_MAX], mtime_buf[FORMAT_TIMESTAMP_MAX], size_buf[FORMAT_BYTES_MAX]; + + printf("%-*s %-*s %s%-3s%s %-*s %-*s %-*s\n", + (int) max_name, images[j].name, + (int) max_type, images[j].type, + images[j].read_only ? ansi_highlight_red() : "", yes_no(images[j].read_only), images[j].read_only ? ansi_normal() : "", + (int) max_size, strna(format_bytes(size_buf, sizeof(size_buf), images[j].size)), + (int) max_crtime, strna(format_timestamp(crtime_buf, sizeof(crtime_buf), images[j].crtime)), + (int) max_mtime, strna(format_timestamp(mtime_buf, sizeof(mtime_buf), images[j].mtime))); + } + + if (arg_legend) + printf("\n%zu images listed.\n", n_images); + + return 0; +} + +static int show_unit_cgroup(sd_bus *bus, const char *unit, pid_t leader) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *path = NULL; + const char *cgroup; + int r; + unsigned c; + + assert(bus); + assert(unit); + + if (arg_transport == BUS_TRANSPORT_REMOTE) + return 0; + + path = unit_dbus_path_from_name(unit); + if (!path) + return log_oom(); + + r = sd_bus_get_property( + bus, + "org.freedesktop.systemd1", + path, + unit_dbus_interface_from_name(unit), + "ControlGroup", + &error, + &reply, + "s"); + if (r < 0) { + log_error("Failed to query ControlGroup: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "s", &cgroup); + if (r < 0) + return bus_log_parse_error(r); + + if (cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, cgroup) != 0 && leader <= 0) + return 0; + + c = columns(); + if (c > 18) + c -= 18; + else + c = 0; + + show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, cgroup, "\t\t ", c, false, &leader, leader > 0, get_output_flags()); + return 0; +} + +static int print_addresses(sd_bus *bus, const char *name, int ifi, const char *prefix, const char *prefix2) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; + + assert(bus); + assert(name); + assert(prefix); + assert(prefix2); + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetMachineAddresses", + NULL, + &reply, + "s", name); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(reply, 'a', "(iay)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_enter_container(reply, 'r', "iay")) > 0) { + int family; + const void *a; + size_t sz; + char buffer[MAX(INET6_ADDRSTRLEN, INET_ADDRSTRLEN)]; + + r = sd_bus_message_read(reply, "i", &family); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read_array(reply, 'y', &a, &sz); + if (r < 0) + return bus_log_parse_error(r); + + fputs(prefix, stdout); + fputs(inet_ntop(family, a, buffer, sizeof(buffer)), stdout); + if (family == AF_INET6 && ifi > 0) + printf("%%%i", ifi); + fputc('\n', stdout); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + if (prefix != prefix2) + prefix = prefix2; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + return 0; +} + +static int print_os_release(sd_bus *bus, const char *name, const char *prefix) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + const char *k, *v, *pretty = NULL; + int r; + + assert(bus); + assert(name); + assert(prefix); + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetMachineOSRelease", + NULL, + &reply, + "s", name); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(reply, 'a', "{ss}"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "{ss}", &k, &v)) > 0) { + if (streq(k, "PRETTY_NAME")) + pretty = v; + + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + if (pretty) + printf("%s%s\n", prefix, pretty); + + return 0; +} + +typedef struct MachineStatusInfo { + char *name; + sd_id128_t id; + char *class; + char *service; + char *unit; + char *root_directory; + pid_t leader; + struct dual_timestamp timestamp; + int *netif; + unsigned n_netif; +} MachineStatusInfo; + +static void machine_status_info_clear(MachineStatusInfo *info) { + if (info) { + free(info->name); + free(info->class); + free(info->service); + free(info->unit); + free(info->root_directory); + free(info->netif); + zero(*info); + } +} + +static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) { + char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; + char since2[FORMAT_TIMESTAMP_MAX], *s2; + int ifi = -1; + + assert(bus); + assert(i); + + fputs(strna(i->name), stdout); + + if (!sd_id128_equal(i->id, SD_ID128_NULL)) + printf("(" SD_ID128_FORMAT_STR ")\n", SD_ID128_FORMAT_VAL(i->id)); + else + putchar('\n'); + + s1 = format_timestamp_relative(since1, sizeof(since1), i->timestamp.realtime); + s2 = format_timestamp(since2, sizeof(since2), i->timestamp.realtime); + + if (s1) + printf("\t Since: %s; %s\n", s2, s1); + else if (s2) + printf("\t Since: %s\n", s2); + + if (i->leader > 0) { + _cleanup_free_ char *t = NULL; + + printf("\t Leader: %u", (unsigned) i->leader); + + get_process_comm(i->leader, &t); + if (t) + printf(" (%s)", t); + + putchar('\n'); + } + + if (i->service) { + printf("\t Service: %s", i->service); + + if (i->class) + printf("; class %s", i->class); + + putchar('\n'); + } else if (i->class) + printf("\t Class: %s\n", i->class); + + if (i->root_directory) + printf("\t Root: %s\n", i->root_directory); + + if (i->n_netif > 0) { + unsigned c; + + fputs("\t Iface:", stdout); + + for (c = 0; c < i->n_netif; c++) { + char name[IF_NAMESIZE+1] = ""; + + if (if_indextoname(i->netif[c], name)) { + fputc(' ', stdout); + fputs(name, stdout); + + if (ifi < 0) + ifi = i->netif[c]; + else + ifi = 0; + } else + printf(" %i", i->netif[c]); + } + + fputc('\n', stdout); + } + + print_addresses(bus, i->name, ifi, + "\t Address: ", + "\t "); + + print_os_release(bus, i->name, "\t OS: "); + + if (i->unit) { + printf("\t Unit: %s\n", i->unit); + show_unit_cgroup(bus, i->unit, i->leader); + + if (arg_transport == BUS_TRANSPORT_LOCAL) + + show_journal_by_unit( + stdout, + i->unit, + arg_output, + 0, + i->timestamp.monotonic, + arg_lines, + 0, + get_output_flags() | OUTPUT_BEGIN_NEWLINE, + SD_JOURNAL_LOCAL_ONLY, + true, + NULL); + } +} + +static int map_netif(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { + MachineStatusInfo *i = userdata; + size_t l; + const void *v; + int r; + + assert_cc(sizeof(int32_t) == sizeof(int)); + r = sd_bus_message_read_array(m, SD_BUS_TYPE_INT32, &v, &l); + if (r < 0) + return r; + if (r == 0) + return -EBADMSG; + + i->n_netif = l / sizeof(int32_t); + i->netif = memdup(v, l); + if (!i->netif) + return -ENOMEM; + + return 0; +} + +static int show_machine_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) { + + static const struct bus_properties_map map[] = { + { "Name", "s", NULL, offsetof(MachineStatusInfo, name) }, + { "Class", "s", NULL, offsetof(MachineStatusInfo, class) }, + { "Service", "s", NULL, offsetof(MachineStatusInfo, service) }, + { "Unit", "s", NULL, offsetof(MachineStatusInfo, unit) }, + { "RootDirectory", "s", NULL, offsetof(MachineStatusInfo, root_directory) }, + { "Leader", "u", NULL, offsetof(MachineStatusInfo, leader) }, + { "Timestamp", "t", NULL, offsetof(MachineStatusInfo, timestamp.realtime) }, + { "TimestampMonotonic", "t", NULL, offsetof(MachineStatusInfo, timestamp.monotonic) }, + { "Id", "ay", bus_map_id128, offsetof(MachineStatusInfo, id) }, + { "NetworkInterfaces", "ai", map_netif, 0 }, + {} + }; + + _cleanup_(machine_status_info_clear) MachineStatusInfo info = {}; + int r; + + assert(verb); + assert(bus); + assert(path); + assert(new_line); + + r = bus_map_all_properties(bus, + "org.freedesktop.machine1", + path, + map, + &info); + if (r < 0) + return log_error_errno(r, "Could not get properties: %m"); + + if (*new_line) + printf("\n"); + *new_line = true; + + print_machine_status_info(bus, &info); + + return r; +} + +static int show_machine_properties(sd_bus *bus, const char *path, bool *new_line) { + int r; + + assert(bus); + assert(path); + assert(new_line); + + if (*new_line) + printf("\n"); + + *new_line = true; + + r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, arg_property, arg_all); + if (r < 0) + log_error_errno(r, "Could not get properties: %m"); + + return r; +} + +static int show_machine(int argc, char *argv[], void *userdata) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + bool properties, new_line = false; + sd_bus *bus = userdata; + int r = 0, i; + + assert(bus); + + properties = !strstr(argv[0], "status"); + + pager_open_if_enabled(); + + if (properties && argc <= 1) { + + /* If no argument is specified, inspect the manager + * itself */ + r = show_machine_properties(bus, "/org/freedesktop/machine1", &new_line); + if (r < 0) + return r; + } + + for (i = 1; i < argc; i++) { + const char *path = NULL; + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetMachine", + &error, + &reply, + "s", argv[i]); + if (r < 0) { + log_error("Could not get path to machine: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "o", &path); + if (r < 0) + return bus_log_parse_error(r); + + if (properties) + r = show_machine_properties(bus, path, &new_line); + else + r = show_machine_info(argv[0], bus, path, &new_line); + } + + return r; +} + +typedef struct ImageStatusInfo { + char *name; + char *path; + char *type; + int read_only; + usec_t crtime; + usec_t mtime; + uint64_t usage; + uint64_t limit; + uint64_t usage_exclusive; + uint64_t limit_exclusive; +} ImageStatusInfo; + +static void image_status_info_clear(ImageStatusInfo *info) { + if (info) { + free(info->name); + free(info->path); + free(info->type); + zero(*info); + } +} + +static void print_image_status_info(sd_bus *bus, ImageStatusInfo *i) { + char ts_relative[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; + char ts_absolute[FORMAT_TIMESTAMP_MAX], *s2; + char bs[FORMAT_BYTES_MAX], *s3; + char bs_exclusive[FORMAT_BYTES_MAX], *s4; + + assert(bus); + assert(i); + + if (i->name) { + fputs(i->name, stdout); + putchar('\n'); + } + + if (i->type) + printf("\t Type: %s\n", i->type); + + if (i->path) + printf("\t Path: %s\n", i->path); + + printf("\t RO: %s%s%s\n", + i->read_only ? ansi_highlight_red() : "", + i->read_only ? "read-only" : "writable", + i->read_only ? ansi_normal() : ""); + + s1 = format_timestamp_relative(ts_relative, sizeof(ts_relative), i->crtime); + s2 = format_timestamp(ts_absolute, sizeof(ts_absolute), i->crtime); + if (s1 && s2) + printf("\t Created: %s; %s\n", s2, s1); + else if (s2) + printf("\t Created: %s\n", s2); + + s1 = format_timestamp_relative(ts_relative, sizeof(ts_relative), i->mtime); + s2 = format_timestamp(ts_absolute, sizeof(ts_absolute), i->mtime); + if (s1 && s2) + printf("\tModified: %s; %s\n", s2, s1); + else if (s2) + printf("\tModified: %s\n", s2); + + s3 = format_bytes(bs, sizeof(bs), i->usage); + s4 = i->usage_exclusive != i->usage ? format_bytes(bs_exclusive, sizeof(bs_exclusive), i->usage_exclusive) : NULL; + if (s3 && s4) + printf("\t Usage: %s (exclusive: %s)\n", s3, s4); + else if (s3) + printf("\t Usage: %s\n", s3); + + s3 = format_bytes(bs, sizeof(bs), i->limit); + s4 = i->limit_exclusive != i->limit ? format_bytes(bs_exclusive, sizeof(bs_exclusive), i->limit_exclusive) : NULL; + if (s3 && s4) + printf("\t Limit: %s (exclusive: %s)\n", s3, s4); + else if (s3) + printf("\t Limit: %s\n", s3); +} + +static int show_image_info(sd_bus *bus, const char *path, bool *new_line) { + + static const struct bus_properties_map map[] = { + { "Name", "s", NULL, offsetof(ImageStatusInfo, name) }, + { "Path", "s", NULL, offsetof(ImageStatusInfo, path) }, + { "Type", "s", NULL, offsetof(ImageStatusInfo, type) }, + { "ReadOnly", "b", NULL, offsetof(ImageStatusInfo, read_only) }, + { "CreationTimestamp", "t", NULL, offsetof(ImageStatusInfo, crtime) }, + { "ModificationTimestamp", "t", NULL, offsetof(ImageStatusInfo, mtime) }, + { "Usage", "t", NULL, offsetof(ImageStatusInfo, usage) }, + { "Limit", "t", NULL, offsetof(ImageStatusInfo, limit) }, + { "UsageExclusive", "t", NULL, offsetof(ImageStatusInfo, usage_exclusive) }, + { "LimitExclusive", "t", NULL, offsetof(ImageStatusInfo, limit_exclusive) }, + {} + }; + + _cleanup_(image_status_info_clear) ImageStatusInfo info = {}; + int r; + + assert(bus); + assert(path); + assert(new_line); + + r = bus_map_all_properties(bus, + "org.freedesktop.machine1", + path, + map, + &info); + if (r < 0) + return log_error_errno(r, "Could not get properties: %m"); + + if (*new_line) + printf("\n"); + *new_line = true; + + print_image_status_info(bus, &info); + + return r; +} + +typedef struct PoolStatusInfo { + char *path; + uint64_t usage; + uint64_t limit; +} PoolStatusInfo; + +static void pool_status_info_clear(PoolStatusInfo *info) { + if (info) { + free(info->path); + zero(*info); + info->usage = -1; + info->limit = -1; + } +} + +static void print_pool_status_info(sd_bus *bus, PoolStatusInfo *i) { + char bs[FORMAT_BYTES_MAX], *s; + + if (i->path) + printf("\t Path: %s\n", i->path); + + s = format_bytes(bs, sizeof(bs), i->usage); + if (s) + printf("\t Usage: %s\n", s); + + s = format_bytes(bs, sizeof(bs), i->limit); + if (s) + printf("\t Limit: %s\n", s); +} + +static int show_pool_info(sd_bus *bus) { + + static const struct bus_properties_map map[] = { + { "PoolPath", "s", NULL, offsetof(PoolStatusInfo, path) }, + { "PoolUsage", "t", NULL, offsetof(PoolStatusInfo, usage) }, + { "PoolLimit", "t", NULL, offsetof(PoolStatusInfo, limit) }, + {} + }; + + _cleanup_(pool_status_info_clear) PoolStatusInfo info = { + .usage = (uint64_t) -1, + .limit = (uint64_t) -1, + }; + int r; + + assert(bus); + + r = bus_map_all_properties(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + map, + &info); + if (r < 0) + return log_error_errno(r, "Could not get properties: %m"); + + print_pool_status_info(bus, &info); + + return 0; +} + + +static int show_image_properties(sd_bus *bus, const char *path, bool *new_line) { + int r; + + assert(bus); + assert(path); + assert(new_line); + + if (*new_line) + printf("\n"); + + *new_line = true; + + r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, arg_property, arg_all); + if (r < 0) + log_error_errno(r, "Could not get properties: %m"); + + return r; +} + +static int show_image(int argc, char *argv[], void *userdata) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + bool properties, new_line = false; + sd_bus *bus = userdata; + int r = 0, i; + + assert(bus); + + properties = !strstr(argv[0], "status"); + + pager_open_if_enabled(); + + if (argc <= 1) { + + /* If no argument is specified, inspect the manager + * itself */ + + if (properties) + r = show_image_properties(bus, "/org/freedesktop/machine1", &new_line); + else + r = show_pool_info(bus); + if (r < 0) + return r; + } + + for (i = 1; i < argc; i++) { + const char *path = NULL; + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetImage", + &error, + &reply, + "s", argv[i]); + if (r < 0) { + log_error("Could not get path to image: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "o", &path); + if (r < 0) + return bus_log_parse_error(r); + + if (properties) + r = show_image_properties(bus, path, &new_line); + else + r = show_image_info(bus, path, &new_line); + } + + return r; +} + +static int kill_machine(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r, i; + + assert(bus); + + polkit_agent_open_if_enabled(); + + if (!arg_kill_who) + arg_kill_who = "all"; + + for (i = 1; i < argc; i++) { + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "KillMachine", + &error, + NULL, + "ssi", argv[i], arg_kill_who, arg_signal); + if (r < 0) { + log_error("Could not kill machine: %s", bus_error_message(&error, -r)); + return r; + } + } + + return 0; +} + +static int reboot_machine(int argc, char *argv[], void *userdata) { + arg_kill_who = "leader"; + arg_signal = SIGINT; /* sysvinit + systemd */ + + return kill_machine(argc, argv, userdata); +} + +static int poweroff_machine(int argc, char *argv[], void *userdata) { + arg_kill_who = "leader"; + arg_signal = SIGRTMIN+4; /* only systemd */ + + return kill_machine(argc, argv, userdata); +} + +static int terminate_machine(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r, i; + + assert(bus); + + polkit_agent_open_if_enabled(); + + for (i = 1; i < argc; i++) { + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "TerminateMachine", + &error, + NULL, + "s", argv[i]); + if (r < 0) { + log_error("Could not terminate machine: %s", bus_error_message(&error, -r)); + return r; + } + } + + return 0; +} + +static int copy_files(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *abs_host_path = NULL; + char *dest, *host_path, *container_path; + sd_bus *bus = userdata; + bool copy_from; + int r; + + assert(bus); + + polkit_agent_open_if_enabled(); + + copy_from = streq(argv[0], "copy-from"); + dest = argv[3] ?: argv[2]; + host_path = copy_from ? dest : argv[2]; + container_path = copy_from ? argv[2] : dest; + + if (!path_is_absolute(host_path)) { + r = path_make_absolute_cwd(host_path, &abs_host_path); + if (r < 0) + return log_error_errno(r, "Failed to make path absolute: %m"); + + host_path = abs_host_path; + } + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + copy_from ? "CopyFromMachine" : "CopyToMachine", + &error, + NULL, + "sss", + argv[1], + copy_from ? container_path : host_path, + copy_from ? host_path : container_path); + if (r < 0) + return log_error_errno(r, "Failed to copy: %s", bus_error_message(&error, r)); + + return 0; +} + +static int bind_mount(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r; + + assert(bus); + + polkit_agent_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "BindMountMachine", + &error, + NULL, + "sssbb", + argv[1], + argv[2], + argv[3], + arg_read_only, + arg_mkdir); + if (r < 0) { + log_error("Failed to bind mount: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int on_machine_removed(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + PTYForward ** forward = (PTYForward**) userdata; + int r; + + assert(m); + assert(forward); + + if (*forward) { + /* If the forwarder is already initialized, tell it to + * exit on the next vhangup(), so that we still flush + * out what might be queued and exit then. */ + + r = pty_forward_set_ignore_vhangup(*forward, false); + if (r >= 0) + return 0; + + log_error_errno(r, "Failed to set ignore_vhangup flag: %m"); + } + + /* On error, or when the forwarder is not initialized yet, quit immediately */ + sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), EXIT_FAILURE); + return 0; +} + +static int process_forward(sd_event *event, PTYForward **forward, int master, PTYForwardFlags flags, const char *name) { + char last_char = 0; + bool machine_died; + int ret = 0, r; + + assert(event); + assert(master >= 0); + assert(name); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGWINCH, SIGTERM, SIGINT, -1) >= 0); + + if (streq(name, ".host")) + log_info("Connected to the local host. Press ^] three times within 1s to exit session."); + else + log_info("Connected to machine %s. Press ^] three times within 1s to exit session.", name); + + sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); + sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); + + r = pty_forward_new(event, master, flags, forward); + if (r < 0) + return log_error_errno(r, "Failed to create PTY forwarder: %m"); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + pty_forward_get_last_char(*forward, &last_char); + + machine_died = + (flags & PTY_FORWARD_IGNORE_VHANGUP) && + pty_forward_get_ignore_vhangup(*forward) == 0; + + *forward = pty_forward_free(*forward); + + if (last_char != '\n') + fputc('\n', stdout); + + if (machine_died) + log_info("Machine %s terminated.", name); + else if (streq(name, ".host")) + log_info("Connection to the local host terminated."); + else + log_info("Connection to machine %s terminated.", name); + + sd_event_get_exit_code(event, &ret); + return ret; +} + +static int login_machine(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(pty_forward_freep) PTYForward *forward = NULL; + _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + int master = -1, r; + sd_bus *bus = userdata; + const char *pty, *match, *machine; + + assert(bus); + + if (!strv_isempty(arg_setenv) || arg_uid) { + log_error("--setenv= and --uid= are not supported for 'login'. Use 'shell' instead."); + return -EINVAL; + } + + if (arg_transport != BUS_TRANSPORT_LOCAL && + arg_transport != BUS_TRANSPORT_MACHINE) { + log_error("Login only supported on local machines."); + return -EOPNOTSUPP; + } + + polkit_agent_open_if_enabled(); + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop: %m"); + + r = sd_bus_attach_event(bus, event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + machine = argc < 2 || isempty(argv[1]) ? ".host" : argv[1]; + + match = strjoina("type='signal'," + "sender='org.freedesktop.machine1'," + "path='/org/freedesktop/machine1',", + "interface='org.freedesktop.machine1.Manager'," + "member='MachineRemoved'," + "arg0='", machine, "'"); + + r = sd_bus_add_match(bus, &slot, match, on_machine_removed, &forward); + if (r < 0) + return log_error_errno(r, "Failed to add machine removal match: %m"); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "OpenMachineLogin", + &error, + &reply, + "s", machine); + if (r < 0) { + log_error("Failed to get login PTY: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "hs", &master, &pty); + if (r < 0) + return bus_log_parse_error(r); + + return process_forward(event, &forward, master, PTY_FORWARD_IGNORE_VHANGUP, machine); +} + +static int shell_machine(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(pty_forward_freep) PTYForward *forward = NULL; + _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + int master = -1, r; + sd_bus *bus = userdata; + const char *pty, *match, *machine, *path, *uid = NULL; + + assert(bus); + + if (arg_transport != BUS_TRANSPORT_LOCAL && + arg_transport != BUS_TRANSPORT_MACHINE) { + log_error("Shell only supported on local machines."); + return -EOPNOTSUPP; + } + + /* Pass $TERM to shell session, if not explicitly specified. */ + if (!strv_find_prefix(arg_setenv, "TERM=")) { + const char *t; + + t = strv_find_prefix(environ, "TERM="); + if (t) { + if (strv_extend(&arg_setenv, t) < 0) + return log_oom(); + } + } + + polkit_agent_open_if_enabled(); + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop: %m"); + + r = sd_bus_attach_event(bus, event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + machine = argc < 2 || isempty(argv[1]) ? NULL : argv[1]; + + if (arg_uid) + uid = arg_uid; + else if (machine) { + const char *at; + + at = strchr(machine, '@'); + if (at) { + uid = strndupa(machine, at - machine); + machine = at + 1; + } + } + + if (isempty(machine)) + machine = ".host"; + + match = strjoina("type='signal'," + "sender='org.freedesktop.machine1'," + "path='/org/freedesktop/machine1',", + "interface='org.freedesktop.machine1.Manager'," + "member='MachineRemoved'," + "arg0='", machine, "'"); + + r = sd_bus_add_match(bus, &slot, match, on_machine_removed, &forward); + if (r < 0) + return log_error_errno(r, "Failed to add machine removal match: %m"); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "OpenMachineShell"); + if (r < 0) + return bus_log_create_error(r); + + path = argc < 3 || isempty(argv[2]) ? NULL : argv[2]; + + r = sd_bus_message_append(m, "sss", machine, uid, path); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, strv_length(argv) <= 3 ? NULL : argv + 2); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, arg_setenv); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) { + log_error("Failed to get shell PTY: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "hs", &master, &pty); + if (r < 0) + return bus_log_parse_error(r); + + return process_forward(event, &forward, master, 0, machine); +} + +static int remove_image(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r, i; + + assert(bus); + + polkit_agent_open_if_enabled(); + + for (i = 1; i < argc; i++) { + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "RemoveImage", + &error, + NULL, + "s", argv[i]); + if (r < 0) { + log_error("Could not remove image: %s", bus_error_message(&error, -r)); + return r; + } + } + + return 0; +} + +static int rename_image(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r; + + polkit_agent_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "RenameImage", + &error, + NULL, + "ss", argv[1], argv[2]); + if (r < 0) { + log_error("Could not rename image: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int clone_image(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r; + + polkit_agent_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "CloneImage", + &error, + NULL, + "ssb", argv[1], argv[2], arg_read_only); + if (r < 0) { + log_error("Could not clone image: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int read_only_image(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int b = true, r; + + if (argc > 2) { + b = parse_boolean(argv[2]); + if (b < 0) { + log_error("Failed to parse boolean argument: %s", argv[2]); + return -EINVAL; + } + } + + polkit_agent_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "MarkImageReadOnly", + &error, + NULL, + "sb", argv[1], b); + if (r < 0) { + log_error("Could not mark image read-only: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int make_service_name(const char *name, char **ret) { + _cleanup_free_ char *e = NULL; + int r; + + assert(name); + assert(ret); + + if (!machine_name_is_valid(name)) { + log_error("Invalid machine name %s.", name); + return -EINVAL; + } + + e = unit_name_escape(name); + if (!e) + return log_oom(); + + r = unit_name_build("systemd-nspawn", e, ".service", ret); + if (r < 0) + return log_error_errno(r, "Failed to build unit name: %m"); + + return 0; +} + +static int start_machine(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + sd_bus *bus = userdata; + int r, i; + + assert(bus); + + polkit_agent_open_if_enabled(); + + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_oom(); + + for (i = 1; i < argc; i++) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *unit = NULL; + const char *object; + + r = make_service_name(argv[i], &unit); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartUnit", + &error, + &reply, + "ss", unit, "fail"); + if (r < 0) { + log_error("Failed to start unit: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "o", &object); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_wait_for_jobs_add(w, object); + if (r < 0) + return log_oom(); + } + + r = bus_wait_for_jobs(w, arg_quiet, NULL); + if (r < 0) + return r; + + return 0; +} + +static int enable_machine(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int carries_install_info = 0; + const char *method = NULL; + sd_bus *bus = userdata; + int r, i; + + assert(bus); + + polkit_agent_open_if_enabled(); + + method = streq(argv[0], "enable") ? "EnableUnitFiles" : "DisableUnitFiles"; + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + method); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "s"); + if (r < 0) + return bus_log_create_error(r); + + for (i = 1; i < argc; i++) { + _cleanup_free_ char *unit = NULL; + + r = make_service_name(argv[i], &unit); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", unit); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + if (streq(argv[0], "enable")) + r = sd_bus_message_append(m, "bb", false, false); + else + r = sd_bus_message_append(m, "b", false); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) { + log_error("Failed to enable or disable unit: %s", bus_error_message(&error, -r)); + return r; + } + + if (streq(argv[0], "enable")) { + r = sd_bus_message_read(reply, "b", carries_install_info); + if (r < 0) + return bus_log_parse_error(r); + } + + r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, NULL, NULL); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "Reload", + &error, + NULL, + NULL); + if (r < 0) { + log_error("Failed to reload daemon: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int match_log_message(sd_bus_message *m, void *userdata, sd_bus_error *error) { + const char **our_path = userdata, *line; + unsigned priority; + int r; + + assert(m); + assert(our_path); + + r = sd_bus_message_read(m, "us", &priority, &line); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + if (!streq_ptr(*our_path, sd_bus_message_get_path(m))) + return 0; + + if (arg_quiet && LOG_PRI(priority) >= LOG_INFO) + return 0; + + log_full(priority, "%s", line); + return 0; +} + +static int match_transfer_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { + const char **our_path = userdata, *path, *result; + uint32_t id; + int r; + + assert(m); + assert(our_path); + + r = sd_bus_message_read(m, "uos", &id, &path, &result); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + if (!streq_ptr(*our_path, path)) + return 0; + + sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), !streq_ptr(result, "done")); + return 0; +} + +static int transfer_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + assert(s); + assert(si); + + if (!arg_quiet) + log_info("Continuing download in the background. Use \"machinectl cancel-transfer %" PRIu32 "\" to abort transfer.", PTR_TO_UINT32(userdata)); + + sd_event_exit(sd_event_source_get_event(s), EINTR); + return 0; +} + +static int transfer_image_common(sd_bus *bus, sd_bus_message *m) { + _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot_job_removed = NULL, *slot_log_message = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_event_unrefp) sd_event* event = NULL; + const char *path = NULL; + uint32_t id; + int r; + + assert(bus); + assert(m); + + polkit_agent_open_if_enabled(); + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop: %m"); + + r = sd_bus_attach_event(bus, event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + r = sd_bus_add_match( + bus, + &slot_job_removed, + "type='signal'," + "sender='org.freedesktop.import1'," + "interface='org.freedesktop.import1.Manager'," + "member='TransferRemoved'," + "path='/org/freedesktop/import1'", + match_transfer_removed, &path); + if (r < 0) + return log_error_errno(r, "Failed to install match: %m"); + + r = sd_bus_add_match( + bus, + &slot_log_message, + "type='signal'," + "sender='org.freedesktop.import1'," + "interface='org.freedesktop.import1.Transfer'," + "member='LogMessage'", + match_log_message, &path); + if (r < 0) + return log_error_errno(r, "Failed to install match: %m"); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) { + log_error("Failed transfer image: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "uo", &id, &path); + if (r < 0) + return bus_log_parse_error(r); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); + + if (!arg_quiet) + log_info("Enqueued transfer job %u. Press C-c to continue download in background.", id); + + sd_event_add_signal(event, NULL, SIGINT, transfer_signal_handler, UINT32_TO_PTR(id)); + sd_event_add_signal(event, NULL, SIGTERM, transfer_signal_handler, UINT32_TO_PTR(id)); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + return -r; +} + +static int import_tar(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *ll = NULL; + _cleanup_close_ int fd = -1; + const char *local = NULL, *path = NULL; + sd_bus *bus = userdata; + int r; + + assert(bus); + + if (argc >= 2) + path = argv[1]; + if (isempty(path) || streq(path, "-")) + path = NULL; + + if (argc >= 3) + local = argv[2]; + else if (path) + local = basename(path); + if (isempty(local) || streq(local, "-")) + local = NULL; + + if (!local) { + log_error("Need either path or local name."); + return -EINVAL; + } + + r = tar_strip_suffixes(local, &ll); + if (r < 0) + return log_oom(); + + local = ll; + + if (!machine_name_is_valid(local)) { + log_error("Local name %s is not a suitable machine name.", local); + return -EINVAL; + } + + if (path) { + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "ImportTar"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "hsbb", + fd >= 0 ? fd : STDIN_FILENO, + local, + arg_force, + arg_read_only); + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +static int import_raw(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *ll = NULL; + _cleanup_close_ int fd = -1; + const char *local = NULL, *path = NULL; + sd_bus *bus = userdata; + int r; + + assert(bus); + + if (argc >= 2) + path = argv[1]; + if (isempty(path) || streq(path, "-")) + path = NULL; + + if (argc >= 3) + local = argv[2]; + else if (path) + local = basename(path); + if (isempty(local) || streq(local, "-")) + local = NULL; + + if (!local) { + log_error("Need either path or local name."); + return -EINVAL; + } + + r = raw_strip_suffixes(local, &ll); + if (r < 0) + return log_oom(); + + local = ll; + + if (!machine_name_is_valid(local)) { + log_error("Local name %s is not a suitable machine name.", local); + return -EINVAL; + } + + if (path) { + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "ImportRaw"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "hsbb", + fd >= 0 ? fd : STDIN_FILENO, + local, + arg_force, + arg_read_only); + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +static void determine_compression_from_filename(const char *p) { + if (arg_format) + return; + + if (!p) + return; + + if (endswith(p, ".xz")) + arg_format = "xz"; + else if (endswith(p, ".gz")) + arg_format = "gzip"; + else if (endswith(p, ".bz2")) + arg_format = "bzip2"; +} + +static int export_tar(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_close_ int fd = -1; + const char *local = NULL, *path = NULL; + sd_bus *bus = userdata; + int r; + + assert(bus); + + local = argv[1]; + if (!machine_name_is_valid(local)) { + log_error("Machine name %s is not valid.", local); + return -EINVAL; + } + + if (argc >= 3) + path = argv[2]; + if (isempty(path) || streq(path, "-")) + path = NULL; + + if (path) { + determine_compression_from_filename(path); + + fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "ExportTar"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "shs", + local, + fd >= 0 ? fd : STDOUT_FILENO, + arg_format); + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +static int export_raw(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_close_ int fd = -1; + const char *local = NULL, *path = NULL; + sd_bus *bus = userdata; + int r; + + assert(bus); + + local = argv[1]; + if (!machine_name_is_valid(local)) { + log_error("Machine name %s is not valid.", local); + return -EINVAL; + } + + if (argc >= 3) + path = argv[2]; + if (isempty(path) || streq(path, "-")) + path = NULL; + + if (path) { + determine_compression_from_filename(path); + + fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "ExportRaw"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "shs", + local, + fd >= 0 ? fd : STDOUT_FILENO, + arg_format); + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +static int pull_tar(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *l = NULL, *ll = NULL; + const char *local, *remote; + sd_bus *bus = userdata; + int r; + + assert(bus); + + remote = argv[1]; + if (!http_url_is_valid(remote)) { + log_error("URL '%s' is not valid.", remote); + return -EINVAL; + } + + if (argc >= 3) + local = argv[2]; + else { + r = import_url_last_component(remote, &l); + if (r < 0) + return log_error_errno(r, "Failed to get final component of URL: %m"); + + local = l; + } + + if (isempty(local) || streq(local, "-")) + local = NULL; + + if (local) { + r = tar_strip_suffixes(local, &ll); + if (r < 0) + return log_oom(); + + local = ll; + + if (!machine_name_is_valid(local)) { + log_error("Local name %s is not a suitable machine name.", local); + return -EINVAL; + } + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "PullTar"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sssb", + remote, + local, + import_verify_to_string(arg_verify), + arg_force); + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +static int pull_raw(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *l = NULL, *ll = NULL; + const char *local, *remote; + sd_bus *bus = userdata; + int r; + + assert(bus); + + remote = argv[1]; + if (!http_url_is_valid(remote)) { + log_error("URL '%s' is not valid.", remote); + return -EINVAL; + } + + if (argc >= 3) + local = argv[2]; + else { + r = import_url_last_component(remote, &l); + if (r < 0) + return log_error_errno(r, "Failed to get final component of URL: %m"); + + local = l; + } + + if (isempty(local) || streq(local, "-")) + local = NULL; + + if (local) { + r = raw_strip_suffixes(local, &ll); + if (r < 0) + return log_oom(); + + local = ll; + + if (!machine_name_is_valid(local)) { + log_error("Local name %s is not a suitable machine name.", local); + return -EINVAL; + } + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "PullRaw"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sssb", + remote, + local, + import_verify_to_string(arg_verify), + arg_force); + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +typedef struct TransferInfo { + uint32_t id; + const char *type; + const char *remote; + const char *local; + double progress; +} TransferInfo; + +static int compare_transfer_info(const void *a, const void *b) { + const TransferInfo *x = a, *y = b; + + return strcmp(x->local, y->local); +} + +static int list_transfers(int argc, char *argv[], void *userdata) { + size_t max_type = strlen("TYPE"), max_local = strlen("LOCAL"), max_remote = strlen("REMOTE"); + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ TransferInfo *transfers = NULL; + size_t n_transfers = 0, n_allocated = 0, j; + const char *type, *remote, *local, *object; + sd_bus *bus = userdata; + uint32_t id, max_id = 0; + double progress; + int r; + + pager_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "ListTransfers", + &error, + &reply, + NULL); + if (r < 0) { + log_error("Could not get transfers: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_enter_container(reply, 'a', "(usssdo)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "(usssdo)", &id, &type, &remote, &local, &progress, &object)) > 0) { + size_t l; + + if (!GREEDY_REALLOC(transfers, n_allocated, n_transfers + 1)) + return log_oom(); + + transfers[n_transfers].id = id; + transfers[n_transfers].type = type; + transfers[n_transfers].remote = remote; + transfers[n_transfers].local = local; + transfers[n_transfers].progress = progress; + + l = strlen(type); + if (l > max_type) + max_type = l; + + l = strlen(remote); + if (l > max_remote) + max_remote = l; + + l = strlen(local); + if (l > max_local) + max_local = l; + + if (id > max_id) + max_id = id; + + n_transfers ++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + qsort_safe(transfers, n_transfers, sizeof(TransferInfo), compare_transfer_info); + + if (arg_legend) + printf("%-*s %-*s %-*s %-*s %-*s\n", + (int) MAX(2U, DECIMAL_STR_WIDTH(max_id)), "ID", + (int) 7, "PERCENT", + (int) max_type, "TYPE", + (int) max_local, "LOCAL", + (int) max_remote, "REMOTE"); + + for (j = 0; j < n_transfers; j++) + printf("%*" PRIu32 " %*u%% %-*s %-*s %-*s\n", + (int) MAX(2U, DECIMAL_STR_WIDTH(max_id)), transfers[j].id, + (int) 6, (unsigned) (transfers[j].progress * 100), + (int) max_type, transfers[j].type, + (int) max_local, transfers[j].local, + (int) max_remote, transfers[j].remote); + + if (arg_legend) + printf("\n%zu transfers listed.\n", n_transfers); + + return 0; +} + +static int cancel_transfer(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r, i; + + assert(bus); + + polkit_agent_open_if_enabled(); + + for (i = 1; i < argc; i++) { + uint32_t id; + + r = safe_atou32(argv[i], &id); + if (r < 0) + return log_error_errno(r, "Failed to parse transfer id: %s", argv[i]); + + r = sd_bus_call_method( + bus, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "CancelTransfer", + &error, + NULL, + "u", id); + if (r < 0) { + log_error("Could not cancel transfer: %s", bus_error_message(&error, -r)); + return r; + } + } + + return 0; +} + +static int set_limit(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + uint64_t limit; + int r; + + if (STR_IN_SET(argv[argc-1], "-", "none", "infinity")) + limit = (uint64_t) -1; + else { + r = parse_size(argv[argc-1], 1024, &limit); + if (r < 0) + return log_error("Failed to parse size: %s", argv[argc-1]); + } + + if (argc > 2) + /* With two arguments changes the quota limit of the + * specified image */ + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "SetImageLimit", + &error, + NULL, + "st", argv[1], limit); + else + /* With one argument changes the pool quota limit */ + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "SetPoolLimit", + &error, + NULL, + "t", limit); + + if (r < 0) { + log_error("Could not set limit: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int help(int argc, char *argv[], void *userdata) { + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Send control commands to or query the virtual machine and container\n" + "registration manager.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " --no-legend Do not show the headers and footers\n" + " --no-ask-password Do not ask for system passwords\n" + " -H --host=[USER@]HOST Operate on remote host\n" + " -M --machine=CONTAINER Operate on local container\n" + " -p --property=NAME Show only properties by this name\n" + " -q --quiet Suppress output\n" + " -a --all Show all properties, including empty ones\n" + " -l --full Do not ellipsize output\n" + " --kill-who=WHO Who to send signal to\n" + " -s --signal=SIGNAL Which signal to send\n" + " --uid=USER Specify user ID to invoke shell as\n" + " --setenv=VAR=VALUE Add an environment variable for shell\n" + " --read-only Create read-only bind mount\n" + " --mkdir Create directory before bind mounting, if missing\n" + " -n --lines=INTEGER Number of journal entries to show\n" + " -o --output=STRING Change journal output mode (short,\n" + " short-monotonic, verbose, export, json,\n" + " json-pretty, json-sse, cat)\n" + " --verify=MODE Verification mode for downloaded images (no,\n" + " checksum, signature)\n" + " --force Download image even if already exists\n\n" + "Machine Commands:\n" + " list List running VMs and containers\n" + " status NAME... Show VM/container details\n" + " show [NAME...] Show properties of one or more VMs/containers\n" + " start NAME... Start container as a service\n" + " login [NAME] Get a login prompt in a container or on the\n" + " local host\n" + " shell [[USER@]NAME [COMMAND...]]\n" + " Invoke a shell (or other command) in a container\n" + " or on the local host\n" + " enable NAME... Enable automatic container start at boot\n" + " disable NAME... Disable automatic container start at boot\n" + " poweroff NAME... Power off one or more containers\n" + " reboot NAME... Reboot one or more containers\n" + " terminate NAME... Terminate one or more VMs/containers\n" + " kill NAME... Send signal to processes of a VM/container\n" + " copy-to NAME PATH [PATH] Copy files from the host to a container\n" + " copy-from NAME PATH [PATH] Copy files from a container to the host\n" + " bind NAME PATH [PATH] Bind mount a path from the host into a container\n\n" + "Image Commands:\n" + " list-images Show available container and VM images\n" + " image-status [NAME...] Show image details\n" + " show-image [NAME...] Show properties of image\n" + " clone NAME NAME Clone an image\n" + " rename NAME NAME Rename an image\n" + " read-only NAME [BOOL] Mark or unmark image read-only\n" + " remove NAME... Remove an image\n" + " set-limit [NAME] BYTES Set image or pool size limit (disk quota)\n\n" + "Image Transfer Commands:\n" + " pull-tar URL [NAME] Download a TAR container image\n" + " pull-raw URL [NAME] Download a RAW container or VM image\n" + " import-tar FILE [NAME] Import a local TAR container image\n" + " import-raw FILE [NAME] Import a local RAW container or VM image\n" + " export-tar NAME [FILE] Export a TAR container image locally\n" + " export-raw NAME [FILE] Export a RAW container or VM image locally\n" + " list-transfers Show list of downloads in progress\n" + " cancel-transfer Cancel a download\n" + , program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_NO_PAGER, + ARG_NO_LEGEND, + ARG_KILL_WHO, + ARG_READ_ONLY, + ARG_MKDIR, + ARG_NO_ASK_PASSWORD, + ARG_VERIFY, + ARG_FORCE, + ARG_FORMAT, + ARG_UID, + ARG_SETENV, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "property", required_argument, NULL, 'p' }, + { "all", no_argument, NULL, 'a' }, + { "full", no_argument, NULL, 'l' }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, + { "kill-who", required_argument, NULL, ARG_KILL_WHO }, + { "signal", required_argument, NULL, 's' }, + { "host", required_argument, NULL, 'H' }, + { "machine", required_argument, NULL, 'M' }, + { "read-only", no_argument, NULL, ARG_READ_ONLY }, + { "mkdir", no_argument, NULL, ARG_MKDIR }, + { "quiet", no_argument, NULL, 'q' }, + { "lines", required_argument, NULL, 'n' }, + { "output", required_argument, NULL, 'o' }, + { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, + { "verify", required_argument, NULL, ARG_VERIFY }, + { "force", no_argument, NULL, ARG_FORCE }, + { "format", required_argument, NULL, ARG_FORMAT }, + { "uid", required_argument, NULL, ARG_UID }, + { "setenv", required_argument, NULL, ARG_SETENV }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hp:als:H:M:qn:o:", options, NULL)) >= 0) + + switch (c) { + + case 'h': + return help(0, NULL, NULL); + + case ARG_VERSION: + return version(); + + case 'p': + r = strv_extend(&arg_property, optarg); + if (r < 0) + return log_oom(); + + /* If the user asked for a particular + * property, show it to him, even if it is + * empty. */ + arg_all = true; + break; + + case 'a': + arg_all = true; + break; + + case 'l': + arg_full = true; + break; + + case 'n': + if (safe_atou(optarg, &arg_lines) < 0) { + log_error("Failed to parse lines '%s'", optarg); + return -EINVAL; + } + break; + + case 'o': + arg_output = output_mode_from_string(optarg); + if (arg_output < 0) { + log_error("Unknown output '%s'.", optarg); + return -EINVAL; + } + break; + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case ARG_NO_LEGEND: + arg_legend = false; + break; + + case ARG_KILL_WHO: + arg_kill_who = optarg; + break; + + case 's': + arg_signal = signal_from_string_try_harder(optarg); + if (arg_signal < 0) { + log_error("Failed to parse signal string %s.", optarg); + return -EINVAL; + } + break; + + case ARG_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + case 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + arg_transport = BUS_TRANSPORT_MACHINE; + arg_host = optarg; + break; + + case ARG_READ_ONLY: + arg_read_only = true; + break; + + case ARG_MKDIR: + arg_mkdir = true; + break; + + case 'q': + arg_quiet = true; + break; + + case ARG_VERIFY: + arg_verify = import_verify_from_string(optarg); + if (arg_verify < 0) { + log_error("Failed to parse --verify= setting: %s", optarg); + return -EINVAL; + } + break; + + case ARG_FORCE: + arg_force = true; + break; + + case ARG_FORMAT: + if (!STR_IN_SET(optarg, "uncompressed", "xz", "gzip", "bzip2")) { + log_error("Unknown format: %s", optarg); + return -EINVAL; + } + + arg_format = optarg; + break; + + case ARG_UID: + arg_uid = optarg; + break; + + case ARG_SETENV: + if (!env_assignment_is_valid(optarg)) { + log_error("Environment assignment invalid: %s", optarg); + return -EINVAL; + } + + r = strv_extend(&arg_setenv, optarg); + if (r < 0) + return log_oom(); + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +static int machinectl_main(int argc, char *argv[], sd_bus *bus) { + + static const Verb verbs[] = { + { "help", VERB_ANY, VERB_ANY, 0, help }, + { "list", VERB_ANY, 1, VERB_DEFAULT, list_machines }, + { "list-images", VERB_ANY, 1, 0, list_images }, + { "status", 2, VERB_ANY, 0, show_machine }, + { "image-status", VERB_ANY, VERB_ANY, 0, show_image }, + { "show", VERB_ANY, VERB_ANY, 0, show_machine }, + { "show-image", VERB_ANY, VERB_ANY, 0, show_image }, + { "terminate", 2, VERB_ANY, 0, terminate_machine }, + { "reboot", 2, VERB_ANY, 0, reboot_machine }, + { "poweroff", 2, VERB_ANY, 0, poweroff_machine }, + { "kill", 2, VERB_ANY, 0, kill_machine }, + { "login", VERB_ANY, 2, 0, login_machine }, + { "shell", VERB_ANY, VERB_ANY, 0, shell_machine }, + { "bind", 3, 4, 0, bind_mount }, + { "copy-to", 3, 4, 0, copy_files }, + { "copy-from", 3, 4, 0, copy_files }, + { "remove", 2, VERB_ANY, 0, remove_image }, + { "rename", 3, 3, 0, rename_image }, + { "clone", 3, 3, 0, clone_image }, + { "read-only", 2, 3, 0, read_only_image }, + { "start", 2, VERB_ANY, 0, start_machine }, + { "enable", 2, VERB_ANY, 0, enable_machine }, + { "disable", 2, VERB_ANY, 0, enable_machine }, + { "import-tar", 2, 3, 0, import_tar }, + { "import-raw", 2, 3, 0, import_raw }, + { "export-tar", 2, 3, 0, export_tar }, + { "export-raw", 2, 3, 0, export_raw }, + { "pull-tar", 2, 3, 0, pull_tar }, + { "pull-raw", 2, 3, 0, pull_raw }, + { "list-transfers", VERB_ANY, 1, 0, list_transfers }, + { "cancel-transfer", 2, VERB_ANY, 0, cancel_transfer }, + { "set-limit", 2, 3, 0, set_limit }, + {} + }; + + return dispatch_verb(argc, argv, verbs, bus); +} + +int main(int argc, char*argv[]) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + setlocale(LC_ALL, ""); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = bus_connect_transport(arg_transport, arg_host, false, &bus); + if (r < 0) { + log_error_errno(r, "Failed to create bus connection: %m"); + goto finish; + } + + sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); + + r = machinectl_main(argc, argv, bus); + +finish: + pager_close(); + polkit_agent_close(); + + strv_free(arg_property); + strv_free(arg_setenv); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/machine/src/machined-dbus.c b/src/machine/src/machined-dbus.c new file mode 100644 index 0000000000..96f2c4769e --- /dev/null +++ b/src/machine/src/machined-dbus.c @@ -0,0 +1,1551 @@ +/*** + 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 "alloc-util.h" +#include "btrfs-util.h" +#include "bus-common-errors.h" +#include "bus-util.h" +#include "cgroup-util.h" +#include "fd-util.h" +#include "formats-util.h" +#include "hostname-util.h" +#include "image-dbus.h" +#include "io-util.h" +#include "machine-dbus.h" +#include "machine-image.h" +#include "machine-pool.h" +#include "machined.h" +#include "path-util.h" +#include "process-util.h" +#include "stdio-util.h" +#include "strv.h" +#include "unit-name.h" +#include "user-util.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_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 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("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("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/machine/src/machined.c b/src/machine/src/machined.c new file mode 100644 index 0000000000..6ada8671f8 --- /dev/null +++ b/src/machine/src/machined.c @@ -0,0 +1,407 @@ +/*** + 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 <errno.h> +#include <string.h> +#include <unistd.h> + +#include <systemd/sd-daemon.h> + +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-util.h" +#include "cgroup-util.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "formats-util.h" +#include "hostname-util.h" +#include "label.h" +#include "machine-image.h" +#include "machined.h" +#include "signal-util.h" + +Manager *manager_new(void) { + Manager *m; + int r; + + m = new0(Manager, 1); + if (!m) + return NULL; + + m->machines = hashmap_new(&string_hash_ops); + m->machine_units = hashmap_new(&string_hash_ops); + m->machine_leaders = hashmap_new(NULL); + + if (!m->machines || !m->machine_units || !m->machine_leaders) { + manager_free(m); + return NULL; + } + + r = sd_event_default(&m->event); + if (r < 0) { + manager_free(m); + return NULL; + } + + sd_event_set_watchdog(m->event, true); + + return m; +} + +void manager_free(Manager *m) { + Machine *machine; + Image *i; + + assert(m); + + while ((machine = hashmap_first(m->machines))) + machine_free(machine); + + hashmap_free(m->machines); + hashmap_free(m->machine_units); + hashmap_free(m->machine_leaders); + + while ((i = hashmap_steal_first(m->image_cache))) + image_unref(i); + + hashmap_free(m->image_cache); + + sd_event_source_unref(m->image_cache_defer_event); + + bus_verify_polkit_async_registry_free(m->polkit_registry); + + sd_bus_unref(m->bus); + sd_event_unref(m->event); + + free(m); +} + +static int manager_add_host_machine(Manager *m) { + _cleanup_free_ char *rd = NULL, *unit = NULL; + sd_id128_t mid; + Machine *t; + int r; + + if (m->host_machine) + return 0; + + r = sd_id128_get_machine(&mid); + if (r < 0) + return log_error_errno(r, "Failed to get machine ID: %m"); + + rd = strdup("/"); + if (!rd) + return log_oom(); + + unit = strdup("-.slice"); + if (!unit) + return log_oom(); + + t = machine_new(m, MACHINE_HOST, ".host"); + if (!t) + return log_oom(); + + t->leader = 1; + t->id = mid; + + t->root_directory = rd; + t->unit = unit; + rd = unit = NULL; + + dual_timestamp_from_boottime_or_monotonic(&t->timestamp, 0); + + m->host_machine = t; + + return 0; +} + +int manager_enumerate_machines(Manager *m) { + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + int r = 0; + + assert(m); + + r = manager_add_host_machine(m); + if (r < 0) + return r; + + /* Read in machine data stored on disk */ + d = opendir("/run/systemd/machines"); + if (!d) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to open /run/systemd/machines: %m"); + } + + FOREACH_DIRENT(de, d, return -errno) { + struct Machine *machine; + int k; + + if (!dirent_is_file(de)) + continue; + + /* Ignore symlinks that map the unit name to the machine */ + if (startswith(de->d_name, "unit:")) + continue; + + if (!machine_name_is_valid(de->d_name)) + continue; + + k = manager_add_machine(m, de->d_name, &machine); + if (k < 0) { + r = log_error_errno(k, "Failed to add machine by file name %s: %m", de->d_name); + continue; + } + + machine_add_to_gc_queue(machine); + + k = machine_load(machine); + if (k < 0) + r = k; + } + + return r; +} + +static int manager_connect_bus(Manager *m) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(m); + assert(!m->bus); + + r = sd_bus_default_system(&m->bus); + if (r < 0) + return log_error_errno(r, "Failed to connect to system bus: %m"); + + r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/machine1", "org.freedesktop.machine1.Manager", manager_vtable, m); + if (r < 0) + return log_error_errno(r, "Failed to add manager object vtable: %m"); + + r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/machine1/machine", "org.freedesktop.machine1.Machine", machine_vtable, machine_object_find, m); + if (r < 0) + return log_error_errno(r, "Failed to add machine object vtable: %m"); + + r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/machine1/machine", machine_node_enumerator, m); + if (r < 0) + return log_error_errno(r, "Failed to add machine enumerator: %m"); + + r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/machine1/image", "org.freedesktop.machine1.Image", image_vtable, image_object_find, m); + if (r < 0) + return log_error_errno(r, "Failed to add image object vtable: %m"); + + r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/machine1/image", image_node_enumerator, m); + if (r < 0) + return log_error_errno(r, "Failed to add image enumerator: %m"); + + r = sd_bus_add_match(m->bus, + NULL, + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.systemd1.Manager'," + "member='JobRemoved'," + "path='/org/freedesktop/systemd1'", + match_job_removed, + m); + if (r < 0) + return log_error_errno(r, "Failed to add match for JobRemoved: %m"); + + r = sd_bus_add_match(m->bus, + NULL, + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.systemd1.Manager'," + "member='UnitRemoved'," + "path='/org/freedesktop/systemd1'", + match_unit_removed, + m); + if (r < 0) + return log_error_errno(r, "Failed to add match for UnitRemoved: %m"); + + r = sd_bus_add_match(m->bus, + NULL, + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.DBus.Properties'," + "member='PropertiesChanged'," + "arg0='org.freedesktop.systemd1.Unit'", + match_properties_changed, + m); + if (r < 0) + return log_error_errno(r, "Failed to add match for PropertiesChanged: %m"); + + r = sd_bus_add_match(m->bus, + NULL, + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.systemd1.Manager'," + "member='Reloading'," + "path='/org/freedesktop/systemd1'", + match_reloading, + m); + if (r < 0) + return log_error_errno(r, "Failed to add match for Reloading: %m"); + + r = sd_bus_call_method( + m->bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "Subscribe", + &error, + NULL, NULL); + if (r < 0) { + log_error("Failed to enable subscription: %s", bus_error_message(&error, r)); + return r; + } + + r = sd_bus_request_name(m->bus, "org.freedesktop.machine1", 0); + if (r < 0) + return log_error_errno(r, "Failed to register name: %m"); + + r = sd_bus_attach_event(m->bus, m->event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + return 0; +} + +void manager_gc(Manager *m, bool drop_not_started) { + Machine *machine; + + assert(m); + + while ((machine = m->machine_gc_queue)) { + LIST_REMOVE(gc_queue, m->machine_gc_queue, machine); + machine->in_gc_queue = false; + + /* First, if we are not closing yet, initiate stopping */ + if (!machine_check_gc(machine, drop_not_started) && + machine_get_state(machine) != MACHINE_CLOSING) + machine_stop(machine); + + /* Now, the stop stop probably made this referenced + * again, but if it didn't, then it's time to let it + * go entirely. */ + if (!machine_check_gc(machine, drop_not_started)) { + machine_finalize(machine); + machine_free(machine); + } + } +} + +int manager_startup(Manager *m) { + Machine *machine; + Iterator i; + int r; + + assert(m); + + /* Connect to the bus */ + r = manager_connect_bus(m); + if (r < 0) + return r; + + /* Deserialize state */ + manager_enumerate_machines(m); + + /* Remove stale objects before we start them */ + manager_gc(m, false); + + /* And start everything */ + HASHMAP_FOREACH(machine, m->machines, i) + machine_start(machine, NULL, NULL); + + return 0; +} + +static bool check_idle(void *userdata) { + Manager *m = userdata; + + manager_gc(m, true); + + return hashmap_isempty(m->machines); +} + +int manager_run(Manager *m) { + assert(m); + + return bus_event_loop_with_idle( + m->event, + m->bus, + "org.freedesktop.machine1", + DEFAULT_EXIT_USEC, + check_idle, m); +} + +int main(int argc, char *argv[]) { + Manager *m = NULL; + int r; + + log_set_target(LOG_TARGET_AUTO); + log_set_facility(LOG_AUTH); + log_parse_environment(); + log_open(); + + umask(0022); + + if (argc != 1) { + log_error("This program takes no arguments."); + r = -EINVAL; + goto finish; + } + + /* Always create the directories people can create inotify + * watches in. Note that some applications might check for the + * existence of /run/systemd/machines/ to determine whether + * machined is available, so please always make sure this + * check stays in. */ + mkdir_label("/run/systemd/machines", 0755); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0); + + m = manager_new(); + if (!m) { + r = log_oom(); + goto finish; + } + + r = manager_startup(m); + if (r < 0) { + log_error_errno(r, "Failed to fully start up daemon: %m"); + goto finish; + } + + log_debug("systemd-machined running as pid "PID_FMT, getpid()); + + sd_notify(false, + "READY=1\n" + "STATUS=Processing requests..."); + + r = manager_run(m); + + log_debug("systemd-machined stopped as pid "PID_FMT, getpid()); + +finish: + manager_free(m); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/machine/src/machined.h b/src/machine/src/machined.h new file mode 100644 index 0000000000..0fe50aaa66 --- /dev/null +++ b/src/machine/src/machined.h @@ -0,0 +1,78 @@ +#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 "hashmap.h" +#include "list.h" + +typedef struct Manager Manager; + +#include "image-dbus.h" +#include "machine-dbus.h" +#include "machine.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; +}; + +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/machine/src/org.freedesktop.machine1.conf b/src/machine/src/org.freedesktop.machine1.conf new file mode 100644 index 0000000000..9d40b90151 --- /dev/null +++ b/src/machine/src/org.freedesktop.machine1.conf @@ -0,0 +1,194 @@ +<?xml version="1.0"?> <!--*-nxml-*--> +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> + +<!-- + This file is part of systemd. + + 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. +--> + +<busconfig> + + <policy user="root"> + <allow own="org.freedesktop.machine1"/> + <allow send_destination="org.freedesktop.machine1"/> + <allow receive_sender="org.freedesktop.machine1"/> + </policy> + + <policy context="default"> + <deny send_destination="org.freedesktop.machine1"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.DBus.Introspectable"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.DBus.Peer"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.DBus.Properties" + send_member="Get"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.DBus.Properties" + send_member="GetAll"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="ListMachines"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="ListImages"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="GetMachine"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="GetMachineByPID"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="GetImage"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="GetMachineAddresses"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="GetMachineOSRelease"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="OpenMachineLogin"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="OpenMachineShell"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="TerminateMachine"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="KillMachine"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="BindMountMachine"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="CopyFromMachine"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="CopyToMachine"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="RemoveImage"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="RenameImage"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="CloneImage"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="MarkImageReadOnly"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="SetPoolLimit"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="SetImageLimit"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="MapFromMachineUser"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="MapToMachineUser"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="MapFromMachineGroup"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="MapToMachineGroup"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Machine" + send_member="GetAddresses"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Machine" + send_member="GetOSRelease"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Machine" + send_member="OpenLogin"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Machine" + send_member="OpenShell"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Machine" + send_member="Terminate"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Machine" + send_member="Kill"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Machine" + send_member="BindMount"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Machine" + send_member="CopyFrom"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Machine" + send_member="CopyTo"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Image" + send_member="Remove"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Image" + send_member="Rename"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Image" + send_member="Clone"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Image" + send_member="SetLimit"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Image" + send_member="MarkReadOnly"/> + + <allow receive_sender="org.freedesktop.machine1"/> + </policy> + +</busconfig> diff --git a/src/machine/src/org.freedesktop.machine1.policy.in b/src/machine/src/org.freedesktop.machine1.policy.in new file mode 100644 index 0000000000..69f78a5c25 --- /dev/null +++ b/src/machine/src/org.freedesktop.machine1.policy.in @@ -0,0 +1,102 @@ +<?xml version="1.0" encoding="UTF-8"?> <!--*-nxml-*--> +<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" + "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd"> + +<!-- + This file is part of systemd. + + 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. +--> + +<policyconfig> + + <vendor>The systemd Project</vendor> + <vendor_url>http://www.freedesktop.org/wiki/Software/systemd</vendor_url> + + <action id="org.freedesktop.machine1.login"> + <_description>Log into a local container</_description> + <_message>Authentication is required to log into a local container.</_message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>auth_admin_keep</allow_active> + </defaults> + </action> + + <action id="org.freedesktop.machine1.host-login"> + <_description>Log into the local host</_description> + <_message>Authentication is required to log into the local host.</_message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>yes</allow_active> + </defaults> + </action> + + <action id="org.freedesktop.machine1.shell"> + <_description>Acquire a shell in a local container</_description> + <_message>Authentication is required to acquire a shell in a local container.</_message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>auth_admin_keep</allow_active> + </defaults> + <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.login</annotate> + </action> + + <action id="org.freedesktop.machine1.host-shell"> + <_description>Acquire a shell on the local host</_description> + <_message>Authentication is required to acquire a shell on the local host.</_message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>auth_admin_keep</allow_active> + </defaults> + <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.host-login</annotate> + </action> + + <action id="org.freedesktop.machine1.open-pty"> + <_description>Acquire a pseudo TTY in a local container</_description> + <_message>Authentication is required to acquire a pseudo TTY in a local container.</_message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>auth_admin_keep</allow_active> + </defaults> + </action> + + <action id="org.freedesktop.machine1.host-open-pty"> + <_description>Acquire a pseudo TTY on the local host</_description> + <_message>Authentication is required to acquire a pseudo TTY on the local host.</_message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>auth_admin_keep</allow_active> + </defaults> + </action> + + <action id="org.freedesktop.machine1.manage-machines"> + <_description>Manage local virtual machines and containers</_description> + <_message>Authentication is required to manage local virtual machines and containers.</_message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>auth_admin_keep</allow_active> + </defaults> + <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.shell org.freedesktop.login1.login</annotate> + </action> + + <action id="org.freedesktop.machine1.manage-images"> + <_description>Manage local virtual machine and container images</_description> + <_message>Authentication is required to manage local virtual machine and container images.</_message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>auth_admin_keep</allow_active> + </defaults> + </action> + +</policyconfig> diff --git a/src/machine/src/org.freedesktop.machine1.service b/src/machine/src/org.freedesktop.machine1.service new file mode 100644 index 0000000000..d3dc99852b --- /dev/null +++ b/src/machine/src/org.freedesktop.machine1.service @@ -0,0 +1,12 @@ +# This file is part of systemd. +# +# 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. + +[D-BUS Service] +Name=org.freedesktop.machine1 +Exec=/bin/false +User=root +SystemdService=dbus-org.freedesktop.machine1.service diff --git a/src/machine/src/test-machine-tables.c b/src/machine/src/test-machine-tables.c new file mode 100644 index 0000000000..f851a4d37d --- /dev/null +++ b/src/machine/src/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.h" +#include "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; +} |