From f2cbe59e113f08549949a76ac5b9b3972df4cc30 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 18 Dec 2014 01:35:58 +0100 Subject: [PATCH] machinectl: add new commands for copying files from/to containers --- man/machinectl.xml | 45 +++++++++--- src/machine/machinectl.c | 144 ++++++++++++++++++++++++++++++++------- src/shared/btrfs-util.c | 2 +- src/shared/copy.c | 24 ++++--- src/shared/copy.h | 3 +- 5 files changed, 175 insertions(+), 43 deletions(-) diff --git a/man/machinectl.xml b/man/machinectl.xml index 9d8a94aab..eef1740f9 100644 --- a/man/machinectl.xml +++ b/man/machinectl.xml @@ -185,7 +185,7 @@ - status ID... + status NAME... Show terse runtime status information about one or more @@ -198,14 +198,14 @@ - show ID... + show NAME... Show properties of one or more registered virtual machines or containers or the manager itself. If no argument is specified, properties of the manager will be shown. If an - ID is specified, properties of this + NAME is specified, properties of this virtual machine or container are shown. By default, empty properties are suppressed. Use @@ -222,7 +222,7 @@ - login ID + login NAME Open a terminal login session to a container. This will @@ -235,7 +235,7 @@ - reboot ID... + reboot NAME... Reboot one or more containers. This will trigger a reboot @@ -248,7 +248,7 @@ - poweroff ID... + poweroff NAME... Power off one or more containers. This will trigger a reboot @@ -264,7 +264,7 @@ - kill ID... + kill NAME... Send a signal to one or more processes of the virtual @@ -279,7 +279,7 @@ - terminate ID... + terminate NAME... Terminates a virtual machine or container. This kills all @@ -290,7 +290,7 @@ - bind ID DIRECTORY [DIRECTORY] + bind NAME PATH [PATH] Bind mounts a directory from the host into the @@ -314,6 +314,33 @@ containers. + + copy-to NAME PATH [PATH] + + Copies files or + directories from the host system into + a running container. Takes a container + name, followed by the source path on + the host and the destination path in + the container. If the destination path + is omitted the same as the source path + is used. + + + + + copy-from NAME PATH [PATH] + + Copies files or + directories from a container into the + host system. Takes a container name, + followed by the source path in the + container the destination path on the + host. If the destination path is + omitted the same as the source path is + used. + + diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index 2571fc0c7..a62ffe30a 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -31,6 +31,7 @@ #include #include #include +#include #include "sd-bus.h" #include "log.h" @@ -48,6 +49,7 @@ #include "event-util.h" #include "path-util.h" #include "mkdir.h" +#include "copy.h" static char **arg_property = NULL; static bool arg_all = false; @@ -626,6 +628,97 @@ static int machine_get_leader(sd_bus *bus, const char *name, pid_t *ret) { return 0; } +static int copy_files(sd_bus *bus, char **args, unsigned n) { + char *dest, *host_path, *container_path, *host_dirname, *host_basename, *container_dirname, *container_basename, *t; + _cleanup_close_ int hostfd = -1; + pid_t child, leader; + bool copy_from; + siginfo_t si; + int r; + + if (n > 4) { + log_error("Too many arguments."); + return -EINVAL; + } + + copy_from = streq(args[0], "copy-from"); + dest = args[3] ?: args[2]; + host_path = strdupa(copy_from ? dest : args[2]); + container_path = strdupa(copy_from ? args[2] : dest); + + if (!path_is_absolute(container_path)) { + log_error("Container path not absolute."); + return -EINVAL; + } + + t = strdup(host_path); + host_basename = basename(t); + host_dirname = dirname(host_path); + + t = strdup(container_path); + container_basename = basename(t); + container_dirname = dirname(container_path); + + r = machine_get_leader(bus, args[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; +} + static int bind_mount(sd_bus *bus, char **args, unsigned n) { char mount_slave[] = "/tmp/propagate.XXXXXX", *mount_tmp, *mount_outside, *p; pid_t child, leader; @@ -998,30 +1091,33 @@ static int login_machine(sd_bus *bus, char **args, unsigned n) { static void help(void) { printf("%s [OPTIONS...] {COMMAND} ...\n\n" - "Send control commands to or query the virtual machine and container 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" - " -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" - " -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" - " --read-only Create read-only bind mount\n" - " --mkdir Create directory before bind mounting, if missing\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" + " -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" + " -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" + " --read-only Create read-only bind mount\n" + " --mkdir Create directory before bind mounting, if missing\n\n" "Commands:\n" - " list List running VMs and containers\n" - " status NAME... Show VM/container status\n" - " show NAME... Show properties of one or more VMs/containers\n" - " login NAME Get a login prompt on a container\n" - " poweroff NAME... Power off one or more containers\n" - " reboot NAME... Reboot one or more containers\n" - " kill NAME... Send signal to processes of a VM/container\n" - " terminate NAME... Terminate one or more VMs/containers\n" - " bind NAME PATH [PATH] Bind mount a path from the host into a container\n", + " list List running VMs and containers\n" + " status NAME... Show VM/container status\n" + " show NAME... Show properties of one or more VMs/containers\n" + " login NAME Get a login prompt on a container\n" + " poweroff NAME... Power off one or more containers\n" + " reboot NAME... Reboot one or more containers\n" + " kill NAME... Send signal to processes of a VM/container\n" + " terminate NAME... Terminate one or more VMs/containers\n" + " bind NAME PATH [PATH] Bind mount a path from the host into a 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", program_invocation_short_name); } @@ -1159,6 +1255,8 @@ static int machinectl_main(sd_bus *bus, int argc, char *argv[]) { { "kill", MORE, 2, kill_machine }, { "login", MORE, 2, login_machine }, { "bind", MORE, 3, bind_mount }, + { "copy-to", MORE, 3, copy_files }, + { "copy-from", MORE, 3, copy_files }, }; int left; diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c index fcf543a46..492d7fc77 100644 --- a/src/shared/btrfs-util.c +++ b/src/shared/btrfs-util.c @@ -122,7 +122,7 @@ int btrfs_subvol_snapshot(const char *old_path, const char *new_path, bool read_ if (r < 0) return r; - r = copy_tree_fd(old_fd, new_path, true); + r = copy_directory_fd(old_fd, new_path, true); if (r < 0) { btrfs_subvol_remove(new_path); return r; diff --git a/src/shared/copy.c b/src/shared/copy.c index b4a85c7bf..0c2cdc8d9 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -25,6 +25,8 @@ #include "btrfs-util.h" #include "copy.h" +#define COPY_BUFFER_SIZE (16*1024) + int copy_bytes(int fdf, int fdt, off_t max_bytes, bool try_reflink) { bool try_sendfile = true; int r; @@ -40,7 +42,7 @@ int copy_bytes(int fdf, int fdt, off_t max_bytes, bool try_reflink) { } for (;;) { - size_t m = PIPE_BUF; + size_t m = COPY_BUFFER_SIZE; ssize_t n; if (max_bytes != (off_t) -1) { @@ -279,30 +281,34 @@ static int fd_copy_directory( return r; } -int copy_tree(const char *from, const char *to, bool merge) { +int copy_tree_at(int fdf, const char *from, int fdt, const char *to, bool merge) { struct stat st; assert(from); assert(to); - if (lstat(from, &st) < 0) + if (fstatat(fdf, from, &st, AT_SYMLINK_NOFOLLOW) < 0) return -errno; if (S_ISREG(st.st_mode)) - return fd_copy_regular(AT_FDCWD, from, &st, AT_FDCWD, to); + return fd_copy_regular(fdf, from, &st, fdt, to); else if (S_ISDIR(st.st_mode)) - return fd_copy_directory(AT_FDCWD, from, &st, AT_FDCWD, to, st.st_dev, merge); + return fd_copy_directory(fdf, from, &st, fdt, to, st.st_dev, merge); else if (S_ISLNK(st.st_mode)) - return fd_copy_symlink(AT_FDCWD, from, &st, AT_FDCWD, to); + return fd_copy_symlink(fdf, from, &st, fdt, to); else if (S_ISFIFO(st.st_mode)) - return fd_copy_fifo(AT_FDCWD, from, &st, AT_FDCWD, to); + return fd_copy_fifo(fdf, from, &st, fdt, to); else if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)) - return fd_copy_node(AT_FDCWD, from, &st, AT_FDCWD, to); + return fd_copy_node(fdf, from, &st, fdt, to); else return -ENOTSUP; } -int copy_tree_fd(int dirfd, const char *to, bool merge) { +int copy_tree(const char *from, const char *to, bool merge) { + return copy_tree_at(AT_FDCWD, from, AT_FDCWD, to, merge); +} + +int copy_directory_fd(int dirfd, const char *to, bool merge) { struct stat st; diff --git a/src/shared/copy.h b/src/shared/copy.h index 201fe692c..714addf4c 100644 --- a/src/shared/copy.h +++ b/src/shared/copy.h @@ -27,5 +27,6 @@ int copy_file_fd(const char *from, int to, bool try_reflink); int copy_file(const char *from, const char *to, int flags, mode_t mode); int copy_tree(const char *from, const char *to, bool merge); -int copy_tree_fd(int dirfd, const char *to, bool merge); +int copy_tree_at(int fdf, const char *from, int fdt, const char *to, bool merge); +int copy_directory_fd(int dirfd, const char *to, bool merge); int copy_bytes(int fdf, int fdt, off_t max_bytes, bool try_reflink); -- 2.30.2