diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c index 2ed779d117..2d05c52608 100644 --- a/src/machine/machine-dbus.c +++ b/src/machine/machine-dbus.c @@ -492,15 +492,11 @@ int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bu } int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *host_basename = NULL, *container_basename = NULL; const char *src, *dest, *host_path, *container_path; - _cleanup_close_pair_ int errno_pipe_fd[2] = EBADF_PAIR; CopyFlags copy_flags = COPY_REFLINK|COPY_MERGE|COPY_HARDLINKS; - _cleanup_close_ int hostfd = -EBADF; Machine *m = ASSERT_PTR(userdata); + Manager *manager = m->manager; bool copy_from; - pid_t child; - uid_t uid_shift; int r; assert(message); @@ -549,17 +545,13 @@ int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_erro message, "org.freedesktop.machine1.manage-machines", details, - &m->manager->polkit_registry, + &manager->polkit_registry, error); if (r < 0) return r; if (r == 0) return 1; /* Will call us back */ - r = machine_get_uid_shift(m, &uid_shift); - if (r < 0) - return r; - copy_from = strstr(sd_bus_message_get_member(message), "CopyFrom"); if (copy_from) { @@ -570,83 +562,12 @@ int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_erro container_path = dest; } - r = path_extract_filename(host_path, &host_basename); + Operation *op; + r = machine_copy_from_to(manager, m, host_path, container_path, copy_from, copy_flags, &op); if (r < 0) - return sd_bus_error_set_errnof(error, r, "Failed to extract file name of '%s' path: %m", host_path); - - r = path_extract_filename(container_path, &container_basename); - if (r < 0) - return sd_bus_error_set_errnof(error, r, "Failed to extract file name of '%s' path: %m", container_path); - - hostfd = open_parent(host_path, O_CLOEXEC, 0); - if (hostfd < 0) - return sd_bus_error_set_errnof(error, hostfd, "Failed to open host directory %s: %m", host_path); - - if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0) - return sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m"); - - r = safe_fork("(sd-copy)", FORK_RESET_SIGNALS, &child); - if (r < 0) - return sd_bus_error_set_errnof(error, r, "Failed to fork(): %m"); - if (r == 0) { - int containerfd; - const char *q; - int mntfd; - - errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); - - q = procfs_file_alloca(m->leader.pid, "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_parent(container_path, O_CLOEXEC, 0); - if (containerfd < 0) { - r = log_error_errno(containerfd, "Failed to open destination directory: %m"); - goto child_fail; - } - - /* Run the actual copy operation. Note that when a UID shift is set we'll either clamp the UID/GID to - * 0 or to the actual UID shift depending on the direction we copy. If no UID shift is set we'll copy - * the UID/GIDs as they are. */ - if (copy_from) - r = copy_tree_at(containerfd, container_basename, hostfd, host_basename, uid_shift == 0 ? UID_INVALID : 0, uid_shift == 0 ? GID_INVALID : 0, copy_flags, NULL, NULL); - else - r = copy_tree_at(hostfd, host_basename, containerfd, container_basename, uid_shift == 0 ? UID_INVALID : uid_shift, uid_shift == 0 ? GID_INVALID : uid_shift, copy_flags, NULL, NULL); - - hostfd = safe_close(hostfd); - containerfd = safe_close(containerfd); - - if (r < 0) { - r = log_error_errno(r, "Failed to copy tree: %m"); - goto child_fail; - } - - _exit(EXIT_SUCCESS); - - child_fail: - (void) write(errno_pipe_fd[1], &r, sizeof(r)); - _exit(EXIT_FAILURE); - } - - errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); - - /* Copying might take a while, hence install a watch on the child, and return */ - - r = operation_new_with_bus_reply(m->manager, m, child, message, errno_pipe_fd[0], /* ret= */ NULL); - if (r < 0) { - (void) sigkill_wait(child); - return r; - } - errno_pipe_fd[0] = -EBADF; + return sd_bus_error_set_errnof(error, r, "Failed to copy from/to machine '%s': %m", m->name); + operation_attach_bus_reply(op, message); return 1; } diff --git a/src/machine/machine-varlink.c b/src/machine/machine-varlink.c index 4e5362783b..e932711311 100644 --- a/src/machine/machine-varlink.c +++ b/src/machine/machine-varlink.c @@ -7,6 +7,7 @@ #include "sd-varlink.h" #include "bus-polkit.h" +#include "copy.h" #include "fd-util.h" #include "hostname-util.h" #include "json-util.h" @@ -825,3 +826,96 @@ int vl_method_bind_mount(sd_varlink *link, sd_json_variant *parameters, sd_varli return sd_varlink_reply(link, NULL); } + +typedef struct MachineCopyParameters { + const char *name; + PidRef pidref; + const char *src; + const char *dest; + bool replace; +} MachineCopyParameters; + +static void machine_copy_paramaters_done(MachineCopyParameters *p) { + assert(p); + + pidref_done(&p->pidref); +} + +static int copy_done(Operation *operation, int ret, sd_bus_error *error) { + assert(operation); + assert(operation->link); + + if (ERRNO_IS_PRIVILEGE(ret)) + return sd_varlink_error(operation->link, SD_VARLINK_ERROR_PERMISSION_DENIED, NULL); + if (ERRNO_IS_NEG_NOT_SUPPORTED(ret)) + return sd_varlink_error(operation->link, "io.systemd.Machine.NotSupported", NULL); + if (ret < 0) + return sd_varlink_error_errno(operation->link, ret); + + return sd_varlink_reply(operation->link, NULL); +} + +int vl_method_copy_internal(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata, bool copy_from) { + static const sd_json_dispatch_field dispatch_table[] = { + VARLINK_DISPATCH_MACHINE_LOOKUP_FIELDS(MachineCopyParameters), + { "source", SD_JSON_VARIANT_STRING, json_dispatch_const_path, offsetof(MachineCopyParameters, src), SD_JSON_MANDATORY }, + { "destination", SD_JSON_VARIANT_STRING, json_dispatch_const_path, offsetof(MachineCopyParameters, dest), 0 }, + { "replace", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(MachineCopyParameters, replace), 0 }, + VARLINK_DISPATCH_POLKIT_FIELD, + {} + }; + + int r; + Manager *manager = ASSERT_PTR(userdata); + _cleanup_(machine_copy_paramaters_done) MachineCopyParameters p = { + .pidref = PIDREF_NULL + }; + + assert(link); + assert(parameters); + + if (manager->n_operations >= OPERATIONS_MAX) + return sd_varlink_error(link, "io.systemd.MachineImage.TooManyOperations", NULL); + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + /* There is no need for extra validation since json_dispatch_path() does path_is_valid() and path_is_absolute().*/ + const char *dest = p.dest ?: p.src; + const char *container_path = copy_from ? p.src : dest; + const char *host_path = copy_from ? dest : p.src; + CopyFlags copy_flags = COPY_REFLINK|COPY_MERGE|COPY_HARDLINKS; + copy_flags |= p.replace ? COPY_REPLACE : 0; + + Machine *machine; + r = lookup_machine_by_name_or_pidref(link, manager, p.name, &p.pidref, &machine); + if (r == -ESRCH) + return sd_varlink_error(link, "io.systemd.Machine.NoSuchMachine", NULL); + if (r != 0) + return r; + + if (machine->class != MACHINE_CONTAINER) + return sd_varlink_error(link, "io.systemd.Machine.NotSupported", NULL); + + r = varlink_verify_polkit_async( + link, + manager->bus, + "org.freedesktop.machine1.manage-machines", + (const char**) STRV_MAKE("name", machine->name, + "verb", "copy", + "src", p.src, + "dest", dest), + &manager->polkit_registry); + if (r <= 0) + return r; + + Operation *op; + r = machine_copy_from_to(manager, machine, host_path, container_path, copy_from, copy_flags, &op); + if (r < 0) + return r; + + operation_attach_varlink_reply(op, link); + op->done = copy_done; + return 1; +} diff --git a/src/machine/machine-varlink.h b/src/machine/machine-varlink.h index 401d8f5c68..bd80cb2653 100644 --- a/src/machine/machine-varlink.h +++ b/src/machine/machine-varlink.h @@ -28,3 +28,4 @@ int vl_method_open(sd_varlink *link, sd_json_variant *parameters, sd_varlink_met int vl_method_map_from(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_map_to(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_bind_mount(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_copy_internal(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata, bool copy_from); diff --git a/src/machine/machine.c b/src/machine/machine.c index d94805c418..1e6a5e0606 100644 --- a/src/machine/machine.c +++ b/src/machine/machine.c @@ -24,6 +24,7 @@ #include "machine-dbus.h" #include "machine.h" #include "mkdir-label.h" +#include "namespace-util.h" #include "parse-util.h" #include "path-util.h" #include "process-util.h" @@ -978,6 +979,125 @@ char** machine_default_shell_args(const char *user) { return TAKE_PTR(args); } +int machine_copy_from_to( + Manager *manager, + Machine *machine, + const char *host_path, + const char *container_path, + bool copy_from_container, + CopyFlags copy_flags, + Operation **ret) { + + _cleanup_close_ int host_fd = -EBADF, mntns_fd = -EBADF; + _cleanup_close_pair_ int errno_pipe_fd[2] = EBADF_PAIR; + _cleanup_free_ char *host_basename = NULL, *container_basename = NULL; + uid_t uid_shift; + pid_t child; + int r; + + assert(manager); + assert(machine); + assert(ret); + + if (isempty(host_path) || isempty(container_path)) + return -EINVAL; + + r = path_extract_filename(host_path, &host_basename); + if (r < 0) + return log_debug_errno(r, "Failed to extract file name of '%s' path: %m", host_path); + + r = path_extract_filename(container_path, &container_basename); + if (r < 0) + return log_debug_errno(r, "Failed to extract file name of '%s' path: %m", container_path); + + host_fd = open_parent(host_path, O_CLOEXEC, 0); + if (host_fd < 0) + return log_debug_errno(host_fd, "Failed to open host directory '%s': %m", host_path); + + r = machine_get_uid_shift(machine, &uid_shift); + if (r < 0) + return log_debug_errno(r, "Failed to get UID shift of machine '%s': %m", machine->name); + + r = pidref_namespace_open(&machine->leader, + /* ret_pidns_fd = */ NULL, + &mntns_fd, + /* ret_netns_fd = */ NULL, + /* ret_userns_fd = */ NULL, + /* ret_root_fd = */ NULL); + if (r < 0) + return log_debug_errno(r, "Failed to open mount namespace of machine '%s': %m", machine->name); + + if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0) + return log_debug_errno(errno, "Failed to create pipe: %m"); + + r = namespace_fork("(sd-copyns)", + "(sd-copy)", + /* except_fds = */ NULL, + /* n_except_fds = */ 0, + FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL, + /* pidns_fd = */ -EBADF, + mntns_fd, + /* netns_fd = */ -EBADF, + /* userns_fd = */ -EBADF, + /* root_fd = */ -EBADF, + &child); + if (r < 0) + return log_debug_errno(r, "Failed to fork into mount namespace of machine '%s': %m", machine->name); + if (r == 0) { + errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); + + _cleanup_close_ int container_fd = -EBADF; + container_fd = open_parent(container_path, O_CLOEXEC, 0); + if (container_fd < 0) { + log_debug_errno(container_fd, "Failed to open destination directory: %m"); + report_errno_and_exit(errno_pipe_fd[1], container_fd); + } + + /* Run the actual copy operation. Note that when a UID shift is set we'll either clamp the UID/GID to + * 0 or to the actual UID shift depending on the direction we copy. If no UID shift is set we'll copy + * the UID/GIDs as they are. */ + if (copy_from_container) + r = copy_tree_at( + container_fd, + container_basename, + host_fd, + host_basename, + uid_shift == 0 ? UID_INVALID : 0, + uid_shift == 0 ? GID_INVALID : 0, + copy_flags, + /* denylist = */ NULL, + /* subvolumes = */ NULL); + else + r = copy_tree_at( + host_fd, + host_basename, + container_fd, + container_basename, + uid_shift == 0 ? UID_INVALID : uid_shift, + uid_shift == 0 ? GID_INVALID : uid_shift, + copy_flags, + /* denylist = */ NULL, + /* subvolumes = */ NULL); + if (r < 0) + log_debug_errno(r, "Failed to copy tree: %m"); + + report_errno_and_exit(errno_pipe_fd[1], r); + } + + errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); + + Operation *operation; + r = operation_new(manager, machine, child, errno_pipe_fd[0], &operation); + if (r < 0) { + sigkill_wait(child); + return r; + } + + TAKE_FD(errno_pipe_fd[0]); + *ret = operation; + return 0; +} + void machine_release_unit(Machine *m) { assert(m); diff --git a/src/machine/machine.h b/src/machine/machine.h index 30e4a6a466..c538c671ff 100644 --- a/src/machine/machine.h +++ b/src/machine/machine.h @@ -4,6 +4,7 @@ typedef struct Machine Machine; typedef enum KillWhom KillWhom; +#include "copy.h" #include "list.h" #include "machined.h" #include "operation.h" @@ -107,6 +108,15 @@ int machine_start_shell(Machine *m, int ptmx_fd, const char *ptmx_name, const ch #define machine_default_shell_path() ("/bin/sh") char** machine_default_shell_args(const char *user); +int machine_copy_from_to( + Manager *manager, + Machine *machine, + const char *host_path, + const char *container_path, + bool copy_from_container, + CopyFlags copy_flags, + Operation **ret); + int machine_get_uid_shift(Machine *m, uid_t *ret); int machine_owns_uid(Machine *m, uid_t host_uid, uid_t *ret_internal_uid); diff --git a/src/machine/machined-varlink.c b/src/machine/machined-varlink.c index 68b31cf262..e0de3aca01 100644 --- a/src/machine/machined-varlink.c +++ b/src/machine/machined-varlink.c @@ -591,6 +591,14 @@ static int vl_method_terminate(sd_varlink *link, sd_json_variant *parameters, sd return lookup_machine_and_call_method(link, parameters, flags, userdata, vl_method_terminate_internal); } +static int vl_method_copy_from(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return vl_method_copy_internal(link, parameters, flags, userdata, /* copy_from = */ true); +} + +static int vl_method_copy_to(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return vl_method_copy_internal(link, parameters, flags, userdata, /* copy_from = */ false); +} + static int list_image_one_and_maybe_read_metadata(sd_varlink *link, Image *image, bool more, AcquireMetadata am) { int r; @@ -775,6 +783,8 @@ static int manager_varlink_init_machine(Manager *m) { "io.systemd.Machine.MapFrom", vl_method_map_from, "io.systemd.Machine.MapTo", vl_method_map_to, "io.systemd.Machine.BindMount", vl_method_bind_mount, + "io.systemd.Machine.CopyFrom", vl_method_copy_from, + "io.systemd.Machine.CopyTo", vl_method_copy_to, "io.systemd.MachineImage.List", vl_method_list_images, "io.systemd.MachineImage.Update", vl_method_update_image, "io.systemd.MachineImage.Clone", vl_method_clone_image, diff --git a/src/machine/operation.c b/src/machine/operation.c index 7d7bfa5062..9e4e7e3dce 100644 --- a/src/machine/operation.c +++ b/src/machine/operation.c @@ -46,10 +46,12 @@ static int operation_done(sd_event_source *s, const siginfo_t *si, void *userdat if (r < 0) log_debug_errno(r, "Operation failed: %m"); - /* If a completion routine (o->done) is set for this operation, call it. It sends a response, but can return an error in which case it expect us to reply. - * Otherwise, the default action is to simply return an error on failure or an empty success message on success. */ - if (o->message) { + /* If o->done set, call it. It sends a response, but can return + * an error in which case it expect this code to reply. + * If o->done is not set, the default action is to simply return + * an error on failure or an empty success message on success.*/ + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; if (o->done) r = o->done(o, r, &error); @@ -68,13 +70,16 @@ static int operation_done(sd_event_source *s, const siginfo_t *si, void *userdat log_error_errno(r, "Failed to reply to dbus message: %m"); } } else if (o->link) { - if (o->done) - r = o->done(o, r, /* error = */ NULL); + /* If o->done set, call it. Unlike o->message case above, this + * code expect o->done to reply in all cases. + * If o->done is not set, the default action is to simply return + * an error on failure or an empty success message on success.*/ - if (r < 0) + if (o->done) + (void) o->done(o, r, /* error = */ NULL); + else if (r < 0) (void) sd_varlink_error_errno(o->link, r); - else if (!o->done) - /* when o->done set it's responsible for sending reply in a happy-path case */ + else (void) sd_varlink_reply(o->link, NULL); } else assert_not_reached(); @@ -83,21 +88,24 @@ static int operation_done(sd_event_source *s, const siginfo_t *si, void *userdat return 0; } -int operation_new(Manager *manager, Machine *machine, pid_t child, sd_bus_message *message, sd_varlink *link, int errno_fd, Operation **ret) { +int operation_new(Manager *manager, Machine *machine, pid_t child, int errno_fd, Operation **ret) { Operation *o; int r; assert(manager); assert(child > 1); assert(errno_fd >= 0); - assert(message || link); - assert(!(message && link)); + assert(ret); - o = new0(Operation, 1); + o = new(Operation, 1); if (!o) return -ENOMEM; - o->extra_fd = -EBADF; + *o = (Operation) { + .pid = child, + .errno_fd = errno_fd, + .extra_fd = -EBADF + }; r = sd_event_add_child(manager->event, &o->event_source, child, WEXITED, operation_done, o); if (r < 0) { @@ -105,11 +113,6 @@ int operation_new(Manager *manager, Machine *machine, pid_t child, sd_bus_messag return r; } - o->pid = child; - o->message = sd_bus_message_ref(message); - o->link = sd_varlink_ref(link); - o->errno_fd = errno_fd; - LIST_PREPEND(operations, manager->operations, o); manager->n_operations++; o->manager = manager; @@ -123,9 +126,7 @@ int operation_new(Manager *manager, Machine *machine, pid_t child, sd_bus_messag /* At this point we took ownership of both the child and the errno file descriptor! */ - if (ret) - *ret = o; - + *ret = o; return 0; } diff --git a/src/machine/operation.h b/src/machine/operation.h index 75bf918c2b..1a92847b80 100644 --- a/src/machine/operation.h +++ b/src/machine/operation.h @@ -31,11 +31,69 @@ struct Operation { LIST_FIELDS(Operation, operations_by_machine); }; -int operation_new(Manager *manager, Machine *machine, pid_t child, sd_bus_message *message, sd_varlink *link, int errno_fd, Operation **ret); +int operation_new(Manager *manager, Machine *machine, pid_t child, int errno_fd, Operation **ret); Operation *operation_free(Operation *o); -static inline int operation_new_with_bus_reply(Manager *manager, Machine *machine, pid_t child, sd_bus_message *message, int errno_fd, Operation **ret) { - return operation_new(manager, machine, child, message, /* link = */ NULL, errno_fd, ret); + +static inline void operation_attach_bus_reply(Operation *op, sd_bus_message *message) { + assert(op); + assert(!op->message); + assert(!op->link); + assert(message); + + op->message = sd_bus_message_ref(message); } -static inline int operation_new_with_varlink_reply(Manager *manager, Machine *machine, pid_t child, sd_varlink *link, int errno_fd, Operation **ret) { - return operation_new(manager, machine, child, /* message = */ NULL, link, errno_fd, ret); + +static inline void operation_attach_varlink_reply(Operation *op, sd_varlink *link) { + assert(op); + assert(!op->message); + assert(!op->link); + assert(link); + + op->link = sd_varlink_ref(link); +} + +static inline int operation_new_with_bus_reply( + Manager *manager, + Machine *machine, + pid_t child, + sd_bus_message *message, + int errno_fd, + Operation **ret) { + + Operation *op; + int r; + + r = operation_new(manager, machine, child, errno_fd, &op); + if (r < 0) + return r; + + operation_attach_bus_reply(op, message); + + if (ret) + *ret = op; + + return 0; +} + +static inline int operation_new_with_varlink_reply( + Manager *manager, + Machine *machine, + pid_t child, + sd_varlink *link, + int errno_fd, + Operation **ret) { + + Operation *op; + int r; + + r = operation_new(manager, machine, child, errno_fd, &op); + if (r < 0) + return r; + + operation_attach_varlink_reply(op, link); + + if (ret) + *ret = op; + + return 0; } diff --git a/src/shared/varlink-io.systemd.Machine.c b/src/shared/varlink-io.systemd.Machine.c index 05a20bc428..1953f2fc61 100644 --- a/src/shared/varlink-io.systemd.Machine.c +++ b/src/shared/varlink-io.systemd.Machine.c @@ -159,6 +159,26 @@ static SD_VARLINK_DEFINE_METHOD( SD_VARLINK_FIELD_COMMENT("The destination mount point shall be created first, if it is missing"), SD_VARLINK_DEFINE_INPUT(mkdir, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_METHOD( + CopyFrom, + VARLINK_DEFINE_MACHINE_LOOKUP_AND_POLKIT_INPUT_FIELDS, + SD_VARLINK_FIELD_COMMENT("The absolute path to source directory/file in the container"), + SD_VARLINK_DEFINE_INPUT(source, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("The absolute path to destination directory/file on the host. If null, it's equal to 'source'."), + SD_VARLINK_DEFINE_INPUT(destination, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("If true the destination will be replaced"), + SD_VARLINK_DEFINE_INPUT(replace, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD( + CopyTo, + VARLINK_DEFINE_MACHINE_LOOKUP_AND_POLKIT_INPUT_FIELDS, + SD_VARLINK_FIELD_COMMENT("The absolute path to source directory/file on the host"), + SD_VARLINK_DEFINE_INPUT(source, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("The absolute path to destination directory/file in the container. If null, it's equal to 'source'"), + SD_VARLINK_DEFINE_INPUT(destination, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("If true the destination will be replaced"), + SD_VARLINK_DEFINE_INPUT(replace, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); + static SD_VARLINK_DEFINE_ERROR(NoSuchMachine); static SD_VARLINK_DEFINE_ERROR(MachineExists); static SD_VARLINK_DEFINE_ERROR(NoPrivateNetworking); @@ -201,6 +221,10 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_method_MapTo, SD_VARLINK_SYMBOL_COMMENT("Bind mounts a file or directory from the host into the container"), &vl_method_BindMount, + SD_VARLINK_SYMBOL_COMMENT("Copy files or directories from a container into the host"), + &vl_method_CopyFrom, + SD_VARLINK_SYMBOL_COMMENT("Copy files or directories from the host into a container"), + &vl_method_CopyTo, SD_VARLINK_SYMBOL_COMMENT("No matching machine currently running"), &vl_error_NoSuchMachine, &vl_error_MachineExists, diff --git a/test/units/TEST-13-NSPAWN.machined.sh b/test/units/TEST-13-NSPAWN.machined.sh index 11f6a6f483..f3d59722ea 100755 --- a/test/units/TEST-13-NSPAWN.machined.sh +++ b/test/units/TEST-13-NSPAWN.machined.sh @@ -259,7 +259,7 @@ done #################### # varlinkctl tests # -# ################## +#################### long_running_machine_start @@ -387,6 +387,11 @@ varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": "login"}' varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": "shell"}' +rm -f /tmp/none-existent-file +varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": "shell", "user": "root", "path": "/bin/sh", "args": ["/bin/sh", "-c", "echo $FOO > /tmp/none-existent-file"], "environment": ["FOO=BAR"]}' +timeout 30 bash -c "until test -e /tmp/none-existent-file; do sleep .5; done" +grep -q "BAR" /tmp/none-existent-file + # test io.systemd.Machine.MapFrom varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.MapFrom '{"name": "long-running", "uid":0, "gid": 0}' container_uid=$(varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.MapFrom '{"name": "long-running", "uid":0}' | jq '.uid') @@ -396,14 +401,28 @@ varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.MapTo (! varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.MapTo '{"uid": 0}') (! varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.MapTo '{"gid": 0}') -rm -f /tmp/none-existent-file -varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": "shell", "user": "root", "path": "/bin/sh", "args": ["/bin/sh", "-c", "echo $FOO > /tmp/none-existent-file"], "environment": ["FOO=BAR"]}' -timeout 30 bash -c "until test -e /tmp/none-existent-file; do sleep .5; done" -grep -q "BAR" /tmp/none-existent-file - # io.systemd.Machine.BindMount is covered by testcase_check_machinectl_bind() in nspawn tests -# terminate machines +# test io.systemd.Machine.CopyTo +rm -f /tmp/foo /var/lib/machines/long-running/root/foo +cp /etc/machine-id /tmp/foo +varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.CopyTo '{"name": "long-running", "source": "/tmp/foo", "destination": "/root/foo"}' +diff /tmp/foo /var/lib/machines/long-running/root/foo +(! varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.CopyTo '{"name": "long-running", "source": "/tmp/foo", "destination": "/root/foo"}') # FileExists +varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.CopyTo '{"name": "long-running", "source": "/tmp/foo", "destination": "/root/foo", "replace": true}' + +echo "sample-test-output" > /tmp/foo +varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.CopyTo '{"name": "long-running", "source": "/tmp/foo", "destination": "/root/foo", "replace": true}' +diff /tmp/foo /var/lib/machines/long-running/root/foo +rm -f /tmp/foo /var/lib/machines/long-running/root/foo + +# test io.systemd.Machine.CopyFrom +cp /etc/machine-id /var/lib/machines/long-running/foo +varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.CopyFrom '{"name": "long-running", "source": "/foo"}' +diff /var/lib/machines/long-running/foo /foo +rm -f /var/lib/machines/long-running/root/foo /foo + +# Terminating machine, otherwise acquiring image metadata by io.systemd.MachineImage.List may fail in the below. machinectl terminate long-running # wait for the container being stopped, otherwise acquiring image metadata by io.systemd.MachineImage.List may fail in the below. timeout 10 bash -c "while machinectl status long-running &>/dev/null; do sleep .5; done"