From 60a77c182229855703f859d884ef9584c80fb987 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Sat, 16 Jul 2016 11:00:17 +0200 Subject: [PATCH] lxc-copy: allow snapshots to be placed on tmpfs Place an ephemeral container started with -e flag on a tmpfs. Restrictions are that you cannot request the data to be kept while placing the container on a tmpfs, that either overlay or aufs backing storage must be used, and that the storage backend of the original container must be a directory. For ephemeral snapshots backed by overlay or aufs filesystems, a fresh tmpfs is mounted over the containers directory if the user requests it. This should be the easiest options. Anything else would require us to change the current mount-layout of overlay and aufs snapshots. (A standard overlay or aufs snapshot clone currently has the layout: /var/lib/lxc/CLONE_SNAPSHOT/delta0 <-- upperdir /var/lib/lxc/CLONE_SNAPSHOT/rootfs /var/lib/lxc/CLONE_SNAPSHOT/olwork /var/lib/lxc/CLONE_SNAPSHOT/olwork/work <-- workdir with the lowerdir being /var/lib/lxc/CLONE_PARENT/rootfs The fact that upperdir and workdir are not placed in a common subfolder under the container directory has the consequence that we cannot simply mount a fresh tmpfs under upperdir and workdir because overlay expects them to be on the same filesystem.) Because we mount a fresh tmpfs over the directory of the container the updated /etc/hostname file created during the clone residing in the upperdir (currently named "delta0" by default) will be hidden. Hence, if the user requests that the old name is not to be kept for the clone, we recreate this file on the tmpfs. This should be all that is required to restore the exact behaviour we would get with a normal clone. NOTE: If the container is rebooted all changes made to it are lost. This is not easy to prevent since each reboot remounts the rootfs again. Signed-off-by: Christian Brauner --- src/lxc/arguments.h | 3 ++ src/lxc/lxc_copy.c | 110 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 112 insertions(+), 1 deletion(-) diff --git a/src/lxc/arguments.h b/src/lxc/arguments.h index 6bc6fcd5c..f68f8ab90 100644 --- a/src/lxc/arguments.h +++ b/src/lxc/arguments.h @@ -131,6 +131,9 @@ struct lxc_arguments { bool ls_running; bool ls_stopped; + /* lxc-copy */ + bool tmpfs; + /* remaining arguments */ char *const *argv; int argc; diff --git a/src/lxc/lxc_copy.c b/src/lxc/lxc_copy.c index e424e6518..cb73471df 100644 --- a/src/lxc/lxc_copy.c +++ b/src/lxc/lxc_copy.c @@ -87,6 +87,7 @@ static const struct option my_longopts[] = { { "keepdata", no_argument, 0, 'D'}, { "keepname", no_argument, 0, 'K'}, { "keepmac", no_argument, 0, 'M'}, + { "tmpfs", no_argument, 0, 't'}, LXC_COMMON_OPTIONS }; @@ -119,6 +120,8 @@ Options :\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\ + -t, --tmpfs place ephemeral container on a tmpfs\n\ + (WARNING: On reboot all changes made to the container will be lost.)\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\ @@ -129,6 +132,7 @@ Options :\n\ .task = CLONE, .daemonize = 1, .quiet = false, + .tmpfs = false, }; static struct mnts *add_mnt(struct mnts **mnts, unsigned int *num, @@ -148,6 +152,13 @@ static int do_clone_task(struct lxc_container *c, enum task task, int flags, char **args); static void free_mnts(void); static uint64_t get_fssize(char *s); + +/* Place an ephemeral container started with -e flag on a tmpfs. Restrictions + * are that you cannot request the data to be kept while placing the container + * on a tmpfs and that either overlay or aufs backing storage must be used. + */ +static char *mount_tmpfs(const char *oldname, const char *newname, + const char *path, struct lxc_arguments *arg); static int parse_mntsubopts(char *subopts, char *const *keys, char *mntparameters); static int parse_aufs_mnt(char *mntstring, enum mnttype type); @@ -369,12 +380,13 @@ static int do_clone(struct lxc_container *c, char *newname, char *newpath, static int do_clone_ephemeral(struct lxc_container *c, struct lxc_arguments *arg, char **args, int flags) { + char *bdev; + char *premount; char randname[MAXPATHLEN]; unsigned int i; int ret = 0; bool bret = true, started = false; struct lxc_container *clone; - lxc_attach_options_t attach_options = LXC_ATTACH_OPTIONS_DEFAULT; attach_options.env_policy = LXC_ATTACH_CLEAR_ENV; @@ -396,6 +408,23 @@ static int do_clone_ephemeral(struct lxc_container *c, if (!clone) return -1; + if (arg->tmpfs) { + bdev = c->lxc_conf->rootfs.bdev_type; + if (bdev && strcmp(bdev, "dir")) { + fprintf(stderr, "Cannot currently use tmpfs with %s storage backend.\n", bdev); + goto destroy_and_put; + } + + premount = mount_tmpfs(arg->name, arg->newname, arg->newpath, arg); + if (!premount) + goto destroy_and_put; + + bret = clone->set_config_item(clone, "lxc.hook.pre-mount", premount); + free(premount); + if (!bret) + goto destroy_and_put; + } + if (!arg->keepdata) if (!clone->set_config_item(clone, "lxc.ephemeral", "1")) goto destroy_and_put; @@ -423,6 +452,10 @@ static int do_clone_ephemeral(struct lxc_container *c, if (!my_args.quiet) printf("Created %s as clone of %s\n", arg->newname, arg->name); + if (arg->tmpfs && !my_args.quiet) + printf("Container is placed on tmpfs.\nRebooting will cause " + "all changes made to it to be lost!"); + if (!arg->daemonize && arg->argc) { clone->want_daemonize(clone, true); arg->daemonize = 1; @@ -575,6 +608,9 @@ static int my_parser(struct lxc_arguments *args, int c, char *arg) case 'B': args->bdevtype = arg; break; + case 't': + args->tmpfs = true; + break; case 'L': args->fssize = get_fssize(optarg); break; @@ -753,3 +789,75 @@ err: lxc_free_array((void **)mntarray, free); return -1; } + +/* For ephemeral snapshots backed by overlay or aufs filesystems, this function + * mounts a fresh tmpfs over the containers directory if the user requests it. + * Because we mount a fresh tmpfs over the directory of the container the + * updated /etc/hostname file created during the clone residing in the upperdir + * (currently named "delta0" by default) will be hidden. Hence, if the user + * requests that the old name is not to be kept for the clone, we recreate this + * file on the tmpfs. This should be all that is required to restore the exact + * behaviour we would get with a normal clone. + */ +static char *mount_tmpfs(const char *oldname, const char *newname, + const char *path, struct lxc_arguments *arg) +{ + int ret, fd; + size_t len; + char *premount = NULL; + + if (arg->tmpfs && arg->keepdata) { + fprintf(stderr, "%s\n", "A container can only be placed on a " + "tmpfs when storage backend is overlay " + "or aufs."); + goto err_free; + } + + if (arg->tmpfs && !arg->bdevtype) { + arg->bdevtype = "overlayfs"; + } else if (arg->tmpfs && arg->bdevtype && strcmp(arg->bdevtype, "overlayfs") && strcmp(arg->bdevtype, "aufs")) { + fprintf(stderr, "%s\n", "A container can only be placed on a " + "tmpfs when storage backend is overlay " + "or aufs."); + goto err_free; + } + + len = strlen(path) + strlen(newname) + strlen("pre-start-XXXXXX") + /* //\0 */ 3; + premount = malloc(len); + if (!premount) + goto err_free; + + ret = snprintf(premount, len, "%s/%s/pre-start-XXXXXX", path, newname); + if (ret < 0 || (size_t)ret >= len) + goto err_free; + + fd = mkostemp(premount, O_CLOEXEC); + if (fd < 0) + goto err_free; + + if (chmod(premount, 0755) < 0) + goto err_close; + + ret = dprintf(fd, "#! /bin/sh\n" + "mount -n -t tmpfs -o mode=0755 none %s/%s\n", + path, newname); + if (ret < 0) + goto err_close; + + if (!arg->keepname) { + ret = dprintf(fd, "mkdir -p %s/%s/delta0/etc\n" + "echo %s > %s/%s/delta0/etc/hostname\n", + path, newname, newname, path, newname); + if (ret < 0) + goto err_close; + } + + close(fd); + return premount; + +err_close: + close(fd); +err_free: + free(premount); + return NULL; +}