This is a complete reimplementation of lxc-clone and lxc-start-ephemeral. lxc-copy merges the functionalities of lxc-clone + lxc-start-ephemeral into one executable.
Ephemeral containers can be started with: lxc-copy -n aa -e additional mounts can be of type {bind,aufs,overlay} and are specified e.g. lxc-copy -n aa -e -m bind=/src:/dest:ro,aufs=/src:/dest:rw,overlay=/src:/dest Signed-off-by: Christian Brauner <christianvanbrau...@gmail.com> --- src/lxc/Makefile.am | 2 + src/lxc/arguments.h | 6 +- src/lxc/lxc_copy.c | 729 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 736 insertions(+), 1 deletion(-) create mode 100644 src/lxc/lxc_copy.c diff --git a/src/lxc/Makefile.am b/src/lxc/Makefile.am index ce46495..72f1e9b 100644 --- a/src/lxc/Makefile.am +++ b/src/lxc/Makefile.am @@ -183,6 +183,7 @@ bin_PROGRAMS = \ lxc-cgroup \ lxc-checkpoint \ lxc-clone \ + lxc-copy \ lxc-config \ lxc-console \ lxc-create \ @@ -226,6 +227,7 @@ init_lxc_SOURCES = lxc_init.c lxc_monitor_SOURCES = lxc_monitor.c lxc_monitord_SOURCES = lxc_monitord.c lxc_clone_SOURCES = lxc_clone.c +lxc_copy_SOURCES = lxc_copy.c lxc_start_SOURCES = lxc_start.c lxc_stop_SOURCES = lxc_stop.c lxc_top_SOURCES = lxc_top.c diff --git a/src/lxc/arguments.h b/src/lxc/arguments.h index bf03dc5..a2f4b93 100644 --- a/src/lxc/arguments.h +++ b/src/lxc/arguments.h @@ -98,8 +98,9 @@ struct lxc_arguments { int list; char *groups; - /* lxc-snapshot and lxc-clone */ + /* lxc-snapshot and lxc-copy */ enum task { + CLONE, DESTROY, LIST, RESTORE, @@ -111,6 +112,9 @@ struct lxc_arguments { char *newname; char *newpath; char *snapname; + char **mntlist; + char **mnttype; + int keepdata; int keepname; int keepmac; diff --git a/src/lxc/lxc_copy.c b/src/lxc/lxc_copy.c new file mode 100644 index 0000000..a37a992 --- /dev/null +++ b/src/lxc/lxc_copy.c @@ -0,0 +1,729 @@ +/* + * + * Copyright © 2015 Christian Brauner <christianvanbrau...@gmail.com>. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#define _GNU_SOURCE +#include <unistd.h> +#include <getopt.h> +#include <signal.h> +#include <stdio.h> +#include <sys/types.h> +#include <stdint.h> +#include <sys/wait.h> +#include <stdlib.h> +#include <errno.h> +#include <ctype.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <time.h> + +#include <lxc/lxccontainer.h> + +#include "attach.h" +#include "log.h" +#include "confile.h" +#include "arguments.h" +#include "lxc.h" +#include "conf.h" +#include "state.h" +#include "utils.h" +#include "bdev.h" + +#define DOUBLE_INFO(...) { \ + printf(__VA_ARGS__); \ + INFO(__VA_ARGS__); \ + } + +lxc_log_define(lxc_copy_ui, lxc); + +static int my_parser(struct lxc_arguments *args, int c, char *arg); + +static const struct option my_longopts[] = { + { "newname", required_argument, 0, 'N'}, + { "newpath", required_argument, 0, 'p'}, + { "rename", no_argument, 0, 'R'}, + { "snapshot", no_argument, 0, 's'}, + { "daemonize", no_argument, 0, 'd'}, + { "ephemeral", no_argument, 0, 'e'}, + { "mount", required_argument, 0, 'm'}, + { "backingstore", required_argument, 0, 'B'}, + { "fssize", required_argument, 0, 'L'}, + { "keepdata", no_argument, 0, 'D'}, + { "keepname", no_argument, 0, 'K'}, + { "keepmac", no_argument, 0, 'M'}, + LXC_COMMON_OPTIONS +}; + +/* mount keys */ +static char *const keys[] = { + [0] = "bind", + [1] = "overlay", + [2] = "aufs", + NULL +}; + +static struct lxc_arguments my_args = { + .progname = "lxc-copy", + .help = "\ +--name=NAME [-P lxcpath] -N newname [-p newpath] [-B backingstorage] [-s] [-K] [-M] [-L size [unit]]\n\ +--name=NAME [-P lxcpath] [-N newname] [-p newpath] [-B backingstorage] -e [-d] [-D] [-K] [-M] [-m {bind,aufs,overlay}=/src:/dest]\n\ +--name=NAME [-P lxcpath] -N newname -R\n\ +\n\ +lxc-copy clone a container\n\ +\n\ +Options :\n\ + -n, --name=NAME NAME of the container\n\ + -N, --newname=NEWNAME NEWNAME for the restored container\n\ + -p, --newpath=NEWPATH NEWPATH for the container to be stored\n\ + -R, --rename rename container\n\ + -s, --snapshot create snapshot instead of clone\n\ + -d, --daemonize start container in background\n\ + -e, --ephemeral start ephemeral container\n\ + -m, --mount directory to mount into container, either \n\ + {bind,aufs,overlay}=/src-path or {bind,aufs,overlay}=/src-path:/dst-path\n\ + -B, --backingstorage=TYPE backingstorage type for the container\n\ + -L, --fssize size of the new block device for block device containers\n\ + -D, --keedata pass together with -e start a persistent snapshot \n\ + -K, --keepname keep the hostname of the original container\n\ + -M, --keepmac keep the MAC address of the original container\n", + .options = my_longopts, + .parser = my_parser, + .task = CLONE, +}; + +static int mntindex = 0; + +static char *construct_path(char *path, bool as_prefix); +static int create_mntlist(struct lxc_arguments *args, char *mntparameters, + char *mnttype); +static int do_clone(struct lxc_container *c, char *newname, char *newpath, + int flags, char *bdevtype, uint64_t fssize, enum task task, + char **args); +static int do_clone_ephemeral(struct lxc_container *c, char *newname, + char *newpath, int flags, char *bdevtype, + uint64_t fssize, char **args); +static int do_clone_rename(struct lxc_container *c, char *newname); +static int do_clone_task(struct lxc_container *c, enum task task, int flags, + char **args); +static char *generate_random_name(const char *name, const char *path); +static uint64_t get_fssize(char *s); +static int mkdir_userns_wrapper(void *arg); +static int mkdir_wrapper(struct lxc_container *c, char *arg); +static int parse_mntsubopts(struct lxc_arguments *args, char *subopts, + char *const *keys, char *mntparameters); +static int set_bind_mount(struct lxc_container *c, char *mntstring); +static int set_union_mount(struct lxc_container *c, char *newpath, + char *mntstring, int index, char *uniontype); + +int main(int argc, char *argv[]) +{ + struct lxc_container *c; + int flags = 0; + int ret; + + if (lxc_arguments_parse(&my_args, argc, argv)) + exit(EXIT_FAILURE); + + if (!my_args.log_file) + my_args.log_file = "none"; + + if (lxc_log_init(my_args.name, my_args.log_file, my_args.log_priority, + my_args.progname, my_args.quiet, my_args.lxcpath[0])) + exit(EXIT_FAILURE); + lxc_log_options_no_override(); + + if (geteuid()) { + if (access(my_args.lxcpath[0], O_RDWR) < 0) { + fprintf(stderr, "You lack access to %s\n", + my_args.lxcpath[0]); + exit(EXIT_FAILURE); + } + } + + if (!my_args.newname && !(my_args.task == DESTROY)) { + printf("Error: You must provide a NEWNAME for the clone.\n"); + exit(EXIT_FAILURE); + } + + if (my_args.task == SNAP || my_args.task == DESTROY) + flags |= LXC_CLONE_SNAPSHOT; + if (my_args.keepname) + flags |= LXC_CLONE_KEEPNAME; + if (my_args.keepmac) + flags |= LXC_CLONE_KEEPMACADDR; + + c = lxc_container_new(my_args.name, my_args.lxcpath[0]); + if (!c) + exit(EXIT_FAILURE); + + if (!c->may_control(c)) { + fprintf(stderr, "Insufficent privileges to control %s\n", + c->name); + lxc_container_put(c); + exit(EXIT_FAILURE); + } + + if (!c->is_defined(c)) { + fprintf(stderr, "Error: container %s is not defined\n", + c->name); + lxc_container_put(c); + exit(EXIT_FAILURE); + } + + ret = do_clone_task(c, my_args.task, flags, &argv[optind]); + + lxc_container_put(c); + + if (ret == 0) + exit(EXIT_SUCCESS); + exit(EXIT_FAILURE); +} + +static int my_parser(struct lxc_arguments *args, int c, char *arg) +{ + char *subopts = NULL; + char *mntparameters = NULL; + switch (c) { + case 'N': + args->newname = arg; + break; + case 'p': + args->newpath = arg; + break; + case 'R': + args->task = RENAME; + break; + case 's': + args->task = SNAP; + break; + case 'd': + args->daemonize = 1; + break; + case 'e': + args->task = DESTROY; + break; + case 'm': + subopts = optarg; + if (parse_mntsubopts(args, subopts, keys, mntparameters) < 0) + return -1; + break; + case 'B': + args->bdevtype = arg; + break; + case 'L': + args->fssize = get_fssize(optarg); + break; + case 'D': + args->keepdata = 1; + break; + case 'K': + args->keepname = 1; + break; + case 'M': + args->keepmac = 1; + break; + } + + return 0; +} + +static int do_clone(struct lxc_container *c, char *newname, char *newpath, + int flags, char *bdevtype, uint64_t fssize, enum task task, + char **args) +{ + struct lxc_container *clone; + + clone = c->clone(c, newname, newpath, flags, bdevtype, NULL, fssize, + args); + if (!clone) { + fprintf(stderr, "clone failed\n"); + return -1; + } + + DOUBLE_INFO("Created container %s as %s of %s\n", newname, + task ? "snapshot" : "copy", c->name); + + lxc_container_put(clone); + + return 0; +} + +static int do_clone_ephemeral(struct lxc_container *c, char *newname, + char *newpath, int flags, char *bdevtype, + uint64_t fssize, char **args) +{ + int i; + int index = 0; + int ret = 0; + struct lxc_container *clone; + char *randname = NULL; + lxc_attach_options_t attach_options = LXC_ATTACH_OPTIONS_DEFAULT; + attach_options.env_policy = LXC_ATTACH_CLEAR_ENV; + + if (!newname) { + randname = generate_random_name(c->name, newpath ? newpath : my_args.lxcpath[0]); + if (randname) + clone = c->clone(c, randname, newpath, flags, bdevtype, + NULL, fssize, args); + else + return -1; + } else { + clone = c->clone(c, newname, newpath, flags, bdevtype, NULL, + fssize, args); + } + + if (!clone) { + fprintf(stderr, "Creating clone of %s failed\n", c->name); + return -1; + } + + if (!my_args.keepdata) { + if (!clone->set_config_item(clone, "lxc.ephemeral", "1")) { + clone->destroy(clone); + lxc_container_put(clone); + fprintf(stderr, "Error setting config item\n"); + return -1; + } + + if (!clone->save_config(clone, NULL)) { + clone->destroy(clone); + lxc_container_put(clone); + fprintf(stderr, "Error saving config item\n"); + return -1; + } + } + + for (i = 0; i < mntindex; i++) { + if (strncmp(my_args.mnttype[i], "bind", 4) == 0) { + if (set_bind_mount(clone, my_args.mntlist[i]) < 0) { + clone->destroy(clone); + lxc_container_put(clone); + return -1; + } + } else { + if (set_union_mount(clone, newpath, my_args.mntlist[i], + index, my_args.mnttype[i]) < 0) { + clone->destroy(clone); + lxc_container_put(clone); + return -1; + } + index++; + } + if (!clone->save_config(clone, NULL)) { + clone->destroy(clone); + lxc_container_put(clone); + fprintf(stderr, "Error saving config item\n"); + return -1; + } + } + + DOUBLE_INFO("Created %s as %s of %s\n", newname ? newname : randname, + my_args.keepdata ? "clone" : "ephemeral clone", c->name); + + if (!my_args.daemonize && my_args.argc) { + clone->want_daemonize(clone, true); + my_args.daemonize = 1; + } else if (!my_args.daemonize) { + clone->want_daemonize(clone, false); + } + + if (!clone->start(clone, 0, NULL)) { + if (!(clone->lxc_conf->ephemeral == 1)) + clone->destroy(clone); + lxc_container_put(clone); + fprintf(stderr, "Error starting container\n"); + return -1; + } + + if (my_args.daemonize && my_args.argc) { + ret = clone->attach_run_wait(clone, &attach_options, + my_args.argv[0], + (const char *const *)my_args.argv); + if (ret < 0) { + lxc_container_put(clone); + return -1; + } else { + clone->shutdown(clone, true); + } + } + + lxc_container_put(clone); + + return 0; +} + +static int do_clone_rename(struct lxc_container *c, char *newname) +{ + if (!c->rename(c, newname)) { + ERROR("Error: Renaming container %s to %s failed\n", c->name, newname); + return -1; + } + + INFO("Renamed container %s to %s\n", c->name, newname); + + return 0; +} + +static int do_clone_task(struct lxc_container *c, enum task task, int flags, + char **args) +{ + int ret = 0; + + switch (task) { + case DESTROY: + ret = do_clone_ephemeral(c, my_args.newname, my_args.newpath, + flags, my_args.bdevtype, + my_args.fssize, args); + break; + case RENAME: + ret = do_clone_rename(c, my_args.newname); + break; + default: + ret = do_clone(c, my_args.newname, my_args.newpath, flags, + my_args.bdevtype, my_args.fssize, my_args.task, + args); + break; + } + + return ret; +} + +static char *construct_path(char *path, bool as_prefix) +{ + char **components = NULL; + char *cleanpath = NULL; + + components = lxc_normalize_path(path); + if (!components) + return NULL; + + cleanpath = lxc_string_join("/", (const char **)components, as_prefix); + lxc_free_array((void **)components, free); + + return cleanpath; +} + +static int create_mntlist(struct lxc_arguments *args, char *mntparameters, + char *mnttype) +{ + char **tmpchar1; + char **tmpchar2; + tmpchar1 = realloc(args->mntlist, (mntindex + 1) * sizeof(args->mntlist[0])); + if (!tmpchar1) + return -1; + args->mntlist = tmpchar1; + args->mntlist[mntindex] = mntparameters; + tmpchar2 = realloc(args->mnttype, (mntindex + 1) * sizeof(args->mnttype[0])); + if (!tmpchar2) + return -1; + args->mnttype = tmpchar2; + args->mnttype[mntindex] = mnttype; + mntindex++; + + return 0; +} + +static char *generate_random_name(const char *name, const char *path) +{ + char testpath[MAXPATHLEN]; + static char randname[MAXPATHLEN]; + int ret; + int suffix; + unsigned int seed; + + do { +#ifndef HAVE_RAND_R + seed = randseed(true); +#endif + +#ifdef HAVE_RAND_R + seed = randseed(false); + suffix = rand_r(&seed); +#else + suffix = rand(); +#endif + + ret = snprintf(randname, MAXPATHLEN, "%s_%08x", name, suffix); + if (ret < 0 || ret >= MAXPATHLEN) { + ERROR("Generating a random name for the clone of %s " "failed", name); + return NULL; + } + + ret = snprintf(testpath, MAXPATHLEN, "%s/%s", path, randname); + if (ret < 0 || ret >= MAXPATHLEN) { + ERROR("Generating a random name for the clone of %s " "failed", name); + return NULL; + } + } while (dir_exists(testpath)); + + return randname; +} + +/* we pass fssize in bytes */ +static uint64_t get_fssize(char *s) +{ + uint64_t ret; + char *end; + + ret = strtoull(s, &end, 0); + if (end == s) { + fprintf(stderr, "Invalid blockdev size '%s', using default size\n", s); + return 0; + } + while (isblank(*end)) + end++; + if (*end == '\0') { + ret *= 1024ULL * 1024ULL; // MB by default + } else if (*end == 'b' || *end == 'B') { + ret *= 1ULL; + } else if (*end == 'k' || *end == 'K') { + ret *= 1024ULL; + } else if (*end == 'm' || *end == 'M') { + ret *= 1024ULL * 1024ULL; + } else if (*end == 'g' || *end == 'G') { + ret *= 1024ULL * 1024ULL * 1024ULL; + } else if (*end == 't' || *end == 'T') { + ret *= 1024ULL * 1024ULL * 1024ULL * 1024ULL; + } else { + fprintf(stderr, "Invalid blockdev unit size '%c' in '%s', " "using default size\n", *end, s); + return 0; + } + + return ret; +} + +static int mkdir_userns_wrapper(void *arg) +{ + const char *dir = (const char *)arg; + return mkdir_p(dir, 0755); +} + +static int mkdir_wrapper(struct lxc_container *c, char *arg) +{ + if (am_unpriv()) { + if (userns_exec_1(c->lxc_conf, mkdir_userns_wrapper, (void *)arg) < 0) + return -1; + if (chown_mapped_root(arg, c->lxc_conf) < 0) + return -1; + } else { + if (mkdir_p(arg, 0755) < 0) + return -1; + } + return 0; +} + +static int parse_mntsubopts(struct lxc_arguments *args, char *subopts, + char *const *keys, char *mntparameters) +{ + while (*subopts != '\0') { + switch (getsubopt(&subopts, keys, &mntparameters)) { + case 0: + if (create_mntlist(args, mntparameters, "bind") < 0) + return -1; + break; + case 1: + if (create_mntlist(args, mntparameters, "overlay") < 0) + return -1; + break; + case 2: + if (create_mntlist(args, mntparameters, "aufs") < 0) + return -1; + break; + default: + break; + } + } + return 0; +} + +static int set_bind_mount(struct lxc_container *c, char *mntstring) +{ + int len = 0; + int ret = 0; + char *mntentry = NULL; + char *options = NULL; + char *src = NULL; + char *dest = NULL; + char **mntarray = NULL; + + mntarray = lxc_string_split(mntstring, ':'); + if (!mntarray) + goto err; + + src = construct_path(mntarray[0], true); + if (!src) + goto err; + + len = lxc_array_len((void **)mntarray); + if (len == 1) { /* bind=src */ + dest = construct_path(mntarray[0], false); + } else if (len == 2) { /* bind=src:option or bind=src:dest */ + if (strncmp(mntarray[1], "rw", strlen(mntarray[1])) == 0) + options = "rw"; + + if (strncmp(mntarray[1], "ro", strlen(mntarray[1])) == 0) + options = "ro"; + + if (options) + dest = construct_path(mntarray[0], false); + else + dest = construct_path(mntarray[1], false); + } else if (len == 3) { /* bind=src:dest:option */ + dest = construct_path(mntarray[1], false); + options = mntarray[2]; + } else { + INFO("Excess elements in mount specification"); + goto err; + } + if (!dest) + goto err; + + if (!options) + options = "rw"; + + len = strlen(src) + strlen(dest) + strlen(options) + + strlen(" none bind,optional,, 0 0") + + strlen(is_dir(src) ? "create=dir" : "create=file") + 1; + mntentry = malloc(len); + if (!mntentry) + goto err; + + ret = snprintf(mntentry, MAXPATHLEN, + "%s %s none bind,optional,%s,%s 0 0", src, dest, options, + is_dir(src) ? "create=dir" : "create=file"); + if (ret < 0 || ret >= MAXPATHLEN) + goto err; + + if (!c->set_config_item(c, "lxc.mount.entry", mntentry)) { + fprintf(stderr, "Error setting config item\n"); + goto err; + } + + return 0; + +err: + free(src); + free(dest); + free(mntentry); + lxc_free_array((void **)mntarray, free); + return -1; +} + +static int set_union_mount(struct lxc_container *c, char *newpath, + char *mntstring, int index, char *uniontype) +{ + int len = 0; + int ret = 0; + char *mntentry = NULL; + char *src = NULL; + char *dest = NULL; + const char *xinopath = "/dev/shm/aufs.xino"; + char **mntarray = NULL; + char tmpfs[MAXPATHLEN]; + char upperdir[MAXPATHLEN]; + char workdir[MAXPATHLEN]; + + mntarray = lxc_string_split(mntstring, ':'); + if (!mntarray) + goto err; + + src = construct_path(mntarray[0], true); + if (!src) + goto err; + + len = lxc_array_len((void **)mntarray); + if (len == 1) { /* aufs=src or overlay=src */ + dest = construct_path(mntarray[0], false); + } else if (len == 2) { /* aufs=src:dest or overlay=src:dest */ + dest = construct_path(mntarray[1], false); + } else { + INFO("Excess elements in mount specification"); + goto err; + } + if (!dest) + goto err; + + /* Create tmpfs folder under which we create the delta and workdir + * directories */ + ret = snprintf(tmpfs, MAXPATHLEN, "%s/%s/tmpfs", + newpath ? newpath : my_args.lxcpath[0], c->name); + if (ret < 0 || ret >= MAXPATHLEN) + goto err; + + if (mkdir_wrapper(c, tmpfs) < 0) + goto err; + + /* Create upperdir for both aufs and overlay */ + ret = snprintf(upperdir, MAXPATHLEN, "%s/%s/tmpfs/delta%d", + newpath ? newpath : my_args.lxcpath[0], c->name, index); + if (ret < 0 || ret >= MAXPATHLEN) + goto err; + + if (mkdir_wrapper(c, upperdir) < 0) + goto err; + + if (strncmp(uniontype, "overlay", 7) == 0) { + /* Create workdir */ + ret = snprintf(workdir, MAXPATHLEN, "%s/%s/tmpfs/workdir%d", + newpath ? newpath : my_args.lxcpath[0], c->name, index); + if (ret < 0 || ret >= MAXPATHLEN) + goto err; + + if (mkdir_wrapper(c, workdir) < 0) + goto err; + + len = 2 * strlen(src) + strlen(dest) + strlen(upperdir) + + strlen(workdir) + + strlen(" overlay lowerdir=,upperdir=,workdir=,create=dir") + 1; + mntentry = malloc(len); + if (!mntentry) + goto err; + + ret = snprintf(mntentry, len, "%s %s overlay lowerdir=%s,upperdir=%s,workdir=%s,create=dir", + src, dest, src, upperdir, workdir); + if (ret < 0 || ret >= len) + goto err; + } else if (strncmp(uniontype, "aufs", 4) == 0) { + len = 2 * strlen(src) + strlen(dest) + strlen(upperdir) + + strlen(xinopath) + + strlen(" aufs br==rw:=ro,xino=,create=dir") + 1; + + mntentry = malloc(len); + if (!mntentry) + goto err; + + ret = snprintf(mntentry, len, "%s %s aufs br=%s=rw:%s=ro,xino=%s,create=dir", + src, dest, upperdir, src, xinopath); + if (ret < 0 || ret >= len) + goto err; + } + + if (!c->set_config_item(c, "lxc.mount.entry", mntentry)) { + fprintf(stderr, "Error setting config item\n"); + goto err; + } + + return 0; + +err: + free(src); + free(dest); + free(mntentry); + lxc_free_array((void **)mntarray, free); + return -1; +} + -- 2.5.3 _______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel