diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c index eb4b70a614..216c9c1d0d 100644 --- a/src/machine/machine-dbus.c +++ b/src/machine/machine-dbus.c @@ -24,6 +24,12 @@ #include #include +/* When we include libgen.h because we need dirname() we immediately + * undefine basename() since libgen.h defines it as a macro to the XDG + * version which is really broken. */ +#include +#undef basename + #include "bus-util.h" #include "bus-label.h" #include "strv.h" @@ -725,6 +731,178 @@ finish: return r; } +static int machine_operation_done(sd_event_source *s, const siginfo_t *si, void *userdata) { + _cleanup_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, "Client 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, "Client 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 *bus, 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; + + 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) || !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 ../."); + + 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 (r < 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), @@ -745,6 +923,8 @@ const sd_bus_vtable machine_vtable[] = { SD_BUS_METHOD("OpenPTY", NULL, "hs", bus_machine_method_open_pty, 0), SD_BUS_METHOD("OpenLogin", NULL, "hs", bus_machine_method_open_login, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("BindMount", "ssbb", NULL, bus_machine_method_bind_mount, 0), + SD_BUS_METHOD("CopyFrom", "ss", NULL, bus_machine_method_copy, 0), + SD_BUS_METHOD("CopyTo", "ss", NULL, bus_machine_method_copy, 0), SD_BUS_VTABLE_END }; diff --git a/src/machine/machine-dbus.h b/src/machine/machine-dbus.h index 474fec7b10..dc9115d39a 100644 --- a/src/machine/machine-dbus.h +++ b/src/machine/machine-dbus.h @@ -37,6 +37,7 @@ int bus_machine_method_get_os_release(sd_bus *bus, sd_bus_message *message, void int bus_machine_method_open_pty(sd_bus *bus, sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_machine_method_open_login(sd_bus *bus, sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_machine_method_bind_mount(sd_bus *bus, sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_copy(sd_bus *bus, 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/machine.c b/src/machine/machine.c index 223eb0f364..edd244f30d 100644 --- a/src/machine/machine.c +++ b/src/machine/machine.c @@ -74,6 +74,9 @@ fail: 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); @@ -501,6 +504,28 @@ int machine_kill(Machine *m, KillWho who, int signo) { return manager_kill_unit(m->manager, m->unit, signo, NULL); } +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; +} + static const char* const machine_class_table[_MACHINE_CLASS_MAX] = { [MACHINE_CONTAINER] = "container", [MACHINE_VM] = "vm" diff --git a/src/machine/machine.h b/src/machine/machine.h index 4827ba332f..94dbac0160 100644 --- a/src/machine/machine.h +++ b/src/machine/machine.h @@ -22,6 +22,7 @@ ***/ typedef struct Machine Machine; +typedef struct MachineOperation MachineOperation; typedef enum KillWho KillWho; #include "list.h" @@ -50,6 +51,17 @@ enum KillWho { _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; @@ -79,6 +91,9 @@ struct Machine { unsigned n_netif; LIST_FIELDS(Machine, gc_queue); + + MachineOperation *operations; + unsigned n_operations; }; Machine* machine_new(Manager *manager, const char *name); @@ -93,6 +108,8 @@ int machine_kill(Machine *m, KillWho who, int signo); 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_; diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index c1cc5e7eb5..ea6db4d18b 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -32,12 +32,6 @@ #include #include -/* When we include libgen.h because we need dirname() we immediately - * undefine basename() since libgen.h defines it as a macro to the XDG - * version which is really broken. */ -#include -#undef basename - #include "sd-bus.h" #include "log.h" #include "util.h" @@ -1002,141 +996,33 @@ static int terminate_machine(int argc, char *argv[], void *userdata) { return 0; } -static int machine_get_leader(sd_bus *bus, const char *name, pid_t *ret) { +static int copy_files(int argc, char *argv[], void *userdata) { _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL, *reply2 = NULL; - const char *object; - uint32_t leader; + sd_bus *bus = userdata; + bool copy_from; int r; assert(bus); - assert(name); - assert(ret); + + copy_from = streq(argv[0], "copy-from"); r = sd_bus_call_method( bus, "org.freedesktop.machine1", "/org/freedesktop/machine1", "org.freedesktop.machine1.Manager", - "GetMachine", + copy_from ? "CopyFromMachine" : "CopyToMachine", &error, - &reply, - "s", name); + NULL, + "sss", + argv[1], + argv[2], + argv[3]); if (r < 0) { - log_error("Could not get path to machine: %s", bus_error_message(&error, -r)); + log_error("Failed to copy: %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 = sd_bus_get_property( - bus, - "org.freedesktop.machine1", - object, - "org.freedesktop.machine1.Machine", - "Leader", - &error, - &reply2, - "u"); - if (r < 0) - return log_error_errno(r, "Failed to retrieve PID of leader: %m"); - - r = sd_bus_message_read(reply2, "u", &leader); - if (r < 0) - return bus_log_parse_error(r); - - *ret = leader; - return 0; -} - -static int copy_files(int argc, char *argv[], void *userdata) { - char *dest, *host_path, *container_path, *host_dirname, *host_basename, *container_dirname, *container_basename, *t; - _cleanup_close_ int hostfd = -1; - sd_bus *bus = userdata; - pid_t child, leader; - bool copy_from; - siginfo_t si; - int r; - - assert(bus); - - copy_from = streq(argv[0], "copy-from"); - dest = argv[3] ?: argv[2]; - host_path = strdupa(copy_from ? dest : argv[2]); - container_path = strdupa(copy_from ? argv[2] : dest); - - if (!path_is_absolute(container_path)) { - log_error("Container path not absolute."); - return -EINVAL; - } - - t = strdupa(host_path); - host_basename = basename(t); - host_dirname = dirname(host_path); - - t = strdupa(container_path); - container_basename = basename(t); - container_dirname = dirname(container_path); - - r = machine_get_leader(bus, argv[1], &leader); - if (r < 0) - return r; - - hostfd = open(host_dirname, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_DIRECTORY); - if (r < 0) - return log_error_errno(errno, "Failed to open source directory: %m"); - - child = fork(); - if (child < 0) - return log_error_errno(errno, "Failed to fork(): %m"); - - if (child == 0) { - int containerfd; - const char *q; - int mntfd; - - q = procfs_file_alloca(leader, "ns/mnt"); - mntfd = open(q, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (mntfd < 0) { - log_error_errno(errno, "Failed to open mount namespace of leader: %m"); - _exit(EXIT_FAILURE); - } - - if (setns(mntfd, CLONE_NEWNS) < 0) { - log_error_errno(errno, "Failed to join namespace of leader: %m"); - _exit(EXIT_FAILURE); - } - - containerfd = open(container_dirname, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_DIRECTORY); - if (containerfd < 0) { - log_error_errno(errno, "Failed top open destination directory: %m"); - _exit(EXIT_FAILURE); - } - - 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); - if (r < 0) { - log_error_errno(errno, "Failed to copy tree: %m"); - _exit(EXIT_FAILURE); - } - - _exit(EXIT_SUCCESS); - } - - r = wait_for_terminate(child, &si); - if (r < 0) - return log_error_errno(r, "Failed to wait for client: %m"); - if (si.si_code != CLD_EXITED) { - log_error("Client died abnormally."); - return -EIO; - } - if (si.si_status != EXIT_SUCCESS) - return -EIO; - return 0; } diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index 2b06d994a8..155146b938 100644 --- a/src/machine/machined-dbus.c +++ b/src/machine/machined-dbus.c @@ -580,6 +580,27 @@ static int method_bind_mount_machine(sd_bus *bus, sd_bus_message *message, void return bus_machine_method_bind_mount(bus, message, machine, error); } +static int method_copy_machine(sd_bus *bus, sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(bus); + 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(bus, message, machine, error); +} + static int method_remove_image(sd_bus *bus, sd_bus_message *message, void *userdata, sd_bus_error *error) { _cleanup_(image_unrefp) Image* i = NULL; const char *name; @@ -683,17 +704,19 @@ const sd_bus_vtable manager_vtable[] = { 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("KillMachine", "ssi", NULL, method_kill_machine, SD_BUS_VTABLE_CAPABILITY(CAP_KILL)), SD_BUS_METHOD("TerminateMachine", "s", NULL, method_terminate_machine, SD_BUS_VTABLE_CAPABILITY(CAP_KILL)), + SD_BUS_METHOD("KillMachine", "ssi", NULL, method_kill_machine, SD_BUS_VTABLE_CAPABILITY(CAP_KILL)), 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("BindMountMachine", "sssbb", NULL, method_bind_mount_machine, 0), + SD_BUS_METHOD("CopyFromMachine", "sss", NULL, method_copy_machine, 0), + SD_BUS_METHOD("CopyToMachine", "sss", NULL, method_copy_machine, 0), SD_BUS_METHOD("RemoveImage", "s", NULL, method_remove_image, 0), SD_BUS_METHOD("RenameImage", "ss", NULL, method_rename_image, 0), SD_BUS_METHOD("CloneImage", "ssb", NULL, method_clone_image, 0), SD_BUS_METHOD("MarkImageReadOnly", "sb", NULL, method_mark_image_read_only, 0), - SD_BUS_METHOD("BindMountMachine", "sssbb", NULL, method_bind_mount_machine, 0), SD_BUS_SIGNAL("MachineNew", "so", 0), SD_BUS_SIGNAL("MachineRemoved", "so", 0), SD_BUS_VTABLE_END diff --git a/src/machine/machined.c b/src/machine/machined.c index 6877c2b313..1373362e9d 100644 --- a/src/machine/machined.c +++ b/src/machine/machined.c @@ -325,6 +325,8 @@ int main(int argc, char *argv[]) { * check stays in. */ mkdir_label("/run/systemd/machines", 0755); + assert_se(sigprocmask_many(SIG_BLOCK, SIGCHLD, -1) >= 0); + m = manager_new(); if (!m) { r = log_oom();