3 * Copyright © 2015 Christian Brauner <christian.brauner@mailbox.org>.
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 2, as
7 * published by the Free Software Foundation.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
32 #include <sys/types.h>
37 #include <lxc/lxccontainer.h>
39 #include "arguments.h"
42 #include "storage_utils.h"
45 #ifndef HAVE_GETSUBOPT
46 #include "include/getsubopt.h"
49 lxc_log_define(lxc_copy
, lxc
);
57 enum mnttype mnt_type
;
66 static unsigned int mnt_table_size
= 0;
67 static struct mnts
*mnt_table
= NULL
;
69 static int my_parser(struct lxc_arguments
*args
, int c
, char *arg
);
71 static const struct option my_longopts
[] = {
72 { "newname", required_argument
, 0, 'N'},
73 { "newpath", required_argument
, 0, 'p'},
74 { "rename", no_argument
, 0, 'R'},
75 { "snapshot", no_argument
, 0, 's'},
76 { "allowrunning", no_argument
, 0, 'a'},
77 { "foreground", no_argument
, 0, 'F'},
78 { "daemon", no_argument
, 0, 'd'},
79 { "ephemeral", no_argument
, 0, 'e'},
80 { "mount", required_argument
, 0, 'm'},
81 { "backingstorage", required_argument
, 0, 'B'},
82 { "fssize", required_argument
, 0, 'L'},
83 { "keepdata", no_argument
, 0, 'D'},
84 { "keepname", no_argument
, 0, 'K'},
85 { "keepmac", no_argument
, 0, 'M'},
86 { "tmpfs", no_argument
, 0, 't'},
91 static char *const keys
[] = {
92 [LXC_MNT_BIND
] = "bind",
93 [LXC_MNT_OVL
] = "overlay",
97 static struct lxc_arguments my_args
= {
98 .progname
= "lxc-copy",
100 --name=NAME [-P lxcpath] -N newname [-p newpath] [-B backingstorage] [-s] [-K] [-M] [-L size [unit]] -- hook options\n\
101 --name=NAME [-P lxcpath] [-N newname] [-p newpath] [-B backingstorage] -e [-d] [-D] [-K] [-M] [-m {bind,overlay}=/src:/dest] -- hook options\n\
102 --name=NAME [-P lxcpath] -N newname -R\n\
104 lxc-copy clone a container\n\
107 -n, --name=NAME NAME of the container\n\
108 -N, --newname=NEWNAME NEWNAME for the restored container\n\
109 -p, --newpath=NEWPATH NEWPATH for the container to be stored\n\
110 -R, --rename rename container\n\
111 -s, --snapshot create snapshot instead of clone\n\
112 -a, --allowrunning allow snapshot creation even if source container is running\n\
113 -F, --foreground start with current tty attached to /dev/console\n\
114 -d, --daemon daemonize the container (default)\n\
115 -e, --ephemeral start ephemeral container\n\
116 -m, --mount directory to mount into container, either \n\
117 {bind,overlay}=/src-path or {bind,overlay}=/src-path:/dst-path\n\
118 -B, --backingstorage=TYPE backingstorage type for the container\n\
119 -t, --tmpfs place ephemeral container on a tmpfs\n\
120 (WARNING: On reboot all changes made to the container will be lost.)\n\
121 -L, --fssize size of the new block device for block device containers\n\
122 -D, --keedata pass together with -e start a persistent snapshot \n\
123 -K, --keepname keep the hostname of the original container\n\
124 -- hook options arguments passed to the hook program\n\
125 -M, --keepmac keep the MAC address of the original container\n\
126 --rcfile=FILE Load configuration file FILE\n",
127 .options
= my_longopts
,
135 static struct mnts
*add_mnt(struct mnts
**mnts
, unsigned int *num
,
137 static int mk_rand_ovl_dirs(struct mnts
*mnts
, unsigned int num
,
138 struct lxc_arguments
*arg
);
139 static char *construct_path(char *path
, bool as_prefix
);
140 static char *set_mnt_entry(struct mnts
*m
);
141 static int do_clone(struct lxc_container
*c
, char *newname
, char *newpath
,
142 int flags
, char *bdevtype
, uint64_t fssize
, enum task task
,
144 static int do_clone_ephemeral(struct lxc_container
*c
,
145 struct lxc_arguments
*arg
, char **args
,
147 static int do_clone_rename(struct lxc_container
*c
, char *newname
);
148 static int do_clone_task(struct lxc_container
*c
, enum task task
, int flags
,
150 static void free_mnts(void);
152 /* Place an ephemeral container started with -e flag on a tmpfs. Restrictions
153 * are that you cannot request the data to be kept while placing the container
154 * on a tmpfs and that either overlay storage driver must be used.
156 static char *mount_tmpfs(const char *oldname
, const char *newname
,
157 const char *path
, struct lxc_arguments
*arg
);
158 static int parse_mntsubopts(char *subopts
, char *const *keys
,
159 char *mntparameters
);
160 static int parse_bind_mnt(char *mntstring
, enum mnttype type
);
161 static int parse_ovl_mnt(char *mntstring
, enum mnttype type
);
163 int main(int argc
, char *argv
[])
165 struct lxc_container
*c
;
168 int ret
= EXIT_FAILURE
;
170 if (lxc_arguments_parse(&my_args
, argc
, argv
))
173 /* Only create log if explicitly instructed */
174 if (my_args
.log_file
|| my_args
.log_priority
) {
175 log
.name
= my_args
.name
;
176 log
.file
= my_args
.log_file
;
177 log
.level
= my_args
.log_priority
;
178 log
.prefix
= my_args
.progname
;
179 log
.quiet
= my_args
.quiet
;
180 log
.lxcpath
= my_args
.lxcpath
[0];
182 if (lxc_log_init(&log
))
187 if (access(my_args
.lxcpath
[0], O_RDONLY
) < 0) {
188 ERROR("You lack access to %s", my_args
.lxcpath
[0]);
193 if (!my_args
.newname
&& !(my_args
.task
== DESTROY
)) {
194 ERROR("You must provide a NEWNAME for the clone");
198 if (my_args
.task
== SNAP
|| my_args
.task
== DESTROY
)
199 flags
|= LXC_CLONE_SNAPSHOT
;
200 if (my_args
.allowrunning
)
201 flags
|= LXC_CLONE_ALLOW_RUNNING
;
202 if (my_args
.keepname
)
203 flags
|= LXC_CLONE_KEEPNAME
;
206 flags
|= LXC_CLONE_KEEPMACADDR
;
208 if (!my_args
.newpath
)
209 my_args
.newpath
= (char *)my_args
.lxcpath
[0];
211 c
= lxc_container_new(my_args
.name
, my_args
.lxcpath
[0]);
215 if (my_args
.rcfile
) {
218 if (!c
->load_config(c
, my_args
.rcfile
)) {
219 ERROR("Failed to load rcfile");
223 c
->configfile
= strdup(my_args
.rcfile
);
224 if (!c
->configfile
) {
225 ERROR("Out of memory setting new config filename");
230 if (!c
->may_control(c
)) {
231 ERROR("Insufficent privileges to control %s", c
->name
);
235 if (!c
->is_defined(c
)) {
236 ERROR("Container %s is not defined", c
->name
);
240 ret
= do_clone_task(c
, my_args
.task
, flags
, &argv
[optind
]);
243 lxc_container_put(c
);
250 static struct mnts
*add_mnt(struct mnts
**mnts
, unsigned int *num
, enum mnttype type
)
254 n
= realloc(*mnts
, (*num
+ 1) * sizeof(struct mnts
));
262 *m
= (struct mnts
) {.mnt_type
= type
};
267 static int mk_rand_ovl_dirs(struct mnts
*mnts
, unsigned int num
, struct lxc_arguments
*arg
)
269 char upperdir
[PATH_MAX
];
270 char workdir
[PATH_MAX
];
273 struct mnts
*m
= NULL
;
275 for (i
= 0, m
= mnts
; i
< num
; i
++, m
++) {
276 if (m
->mnt_type
== LXC_MNT_OVL
) {
277 ret
= snprintf(upperdir
, PATH_MAX
, "%s/%s/delta#XXXXXX",
278 arg
->newpath
, arg
->newname
);
279 if (ret
< 0 || ret
>= PATH_MAX
)
282 if (!mkdtemp(upperdir
))
285 m
->upper
= strdup(upperdir
);
290 if (m
->mnt_type
== LXC_MNT_OVL
) {
291 ret
= snprintf(workdir
, PATH_MAX
, "%s/%s/work#XXXXXX",
292 arg
->newpath
, arg
->newname
);
293 if (ret
< 0 || ret
>= PATH_MAX
)
296 if (!mkdtemp(workdir
))
299 m
->workdir
= strdup(workdir
);
308 static char *construct_path(char *path
, bool as_prefix
)
310 char **components
= NULL
;
311 char *cleanpath
= NULL
;
313 components
= lxc_normalize_path(path
);
317 cleanpath
= lxc_string_join("/", (const char **)components
, as_prefix
);
318 lxc_free_array((void **)components
, free
);
323 static char *set_mnt_entry(struct mnts
*m
)
325 char *mntentry
= NULL
;
329 if (m
->mnt_type
== LXC_MNT_OVL
) {
330 len
= strlen(" overlay lowerdir=,upperdir=,workdir=,create=dir") +
331 2 * strlen(m
->src
) + strlen(m
->dest
) + strlen(m
->upper
) +
332 strlen(m
->workdir
) + 1;
334 mntentry
= malloc(len
);
338 ret
= snprintf(mntentry
, len
, "%s %s overlay lowerdir=%s,upperdir=%s,workdir=%s,create=dir",
339 m
->src
, m
->dest
, m
->src
, m
->upper
, m
->workdir
);
340 if (ret
< 0 || (size_t)ret
>= len
)
342 } else if (m
->mnt_type
== LXC_MNT_BIND
) {
343 len
= strlen(" none bind,optional,, 0 0") +
344 strlen(is_dir(m
->src
) ? "create=dir" : "create=file") +
345 strlen(m
->src
) + strlen(m
->dest
) + strlen(m
->options
) + 1;
347 mntentry
= malloc(len
);
351 ret
= snprintf(mntentry
, len
, "%s %s none bind,optional,%s,%s 0 0",
352 m
->src
, m
->dest
, m
->options
,
353 is_dir(m
->src
) ? "create=dir" : "create=file");
354 if (ret
< 0 || (size_t)ret
>= len
)
365 static int do_clone(struct lxc_container
*c
, char *newname
, char *newpath
,
366 int flags
, char *bdevtype
, uint64_t fssize
, enum task task
,
369 struct lxc_container
*clone
;
371 clone
= c
->clone(c
, newname
, newpath
, flags
, bdevtype
, NULL
, fssize
,
374 ERROR("Failed to clone");
378 lxc_container_put(clone
);
383 static int do_clone_ephemeral(struct lxc_container
*c
,
384 struct lxc_arguments
*arg
, char **args
, int flags
)
387 char randname
[PATH_MAX
];
390 bool bret
= true, started
= false;
391 char *tmp_buf
= randname
;
392 struct lxc_container
*clone
;
393 lxc_attach_options_t attach_options
= LXC_ATTACH_OPTIONS_DEFAULT
;
394 attach_options
.env_policy
= LXC_ATTACH_CLEAR_ENV
;
397 ret
= snprintf(randname
, PATH_MAX
, "%s/%s_XXXXXX", arg
->newpath
, arg
->name
);
398 if (ret
< 0 || ret
>= PATH_MAX
)
401 if (!mkdtemp(randname
))
404 if (chmod(randname
, 0770) < 0) {
405 (void)remove(randname
);
409 arg
->newname
= randname
+ strlen(arg
->newpath
) + 1;
412 clone
= c
->clone(c
, arg
->newname
, arg
->newpath
, flags
,
413 arg
->bdevtype
, NULL
, arg
->fssize
, args
);
418 premount
= mount_tmpfs(arg
->name
, arg
->newname
, arg
->newpath
, arg
);
420 goto destroy_and_put
;
422 bret
= clone
->set_config_item(clone
, "lxc.hook.pre-mount", premount
);
425 goto destroy_and_put
;
429 if (!clone
->set_config_item(clone
, "lxc.ephemeral", "1"))
430 goto destroy_and_put
;
432 /* allocate and create random upper- and workdirs for overlay mounts */
433 if (mk_rand_ovl_dirs(mnt_table
, mnt_table_size
, arg
) < 0)
434 goto destroy_and_put
;
436 /* allocate and set mount entries */
437 struct mnts
*n
= NULL
;
438 for (i
= 0, n
= mnt_table
; i
< mnt_table_size
; i
++, n
++) {
439 char *mntentry
= NULL
;
441 mntentry
= set_mnt_entry(n
);
443 goto destroy_and_put
;
445 bret
= clone
->set_config_item(clone
, "lxc.mount.entry", mntentry
);
448 goto destroy_and_put
;
451 if (!clone
->save_config(clone
, NULL
))
452 goto destroy_and_put
;
455 printf("Created %s as clone of %s\n", arg
->newname
, arg
->name
);
457 if (arg
->tmpfs
&& !my_args
.quiet
)
458 printf("Container is placed on tmpfs.\nRebooting will cause "
459 "all changes made to it to be lost!\n");
461 if (!arg
->daemonize
&& arg
->argc
) {
462 clone
->want_daemonize(clone
, true);
464 } else if (!arg
->daemonize
) {
465 clone
->want_daemonize(clone
, false);
468 started
= clone
->start(clone
, 0, NULL
);
470 goto destroy_and_put
;
472 if (arg
->daemonize
&& arg
->argc
) {
473 ret
= clone
->attach_run_wait(clone
, &attach_options
, arg
->argv
[0], (const char *const *)arg
->argv
);
475 goto destroy_and_put
;
476 clone
->shutdown(clone
, -1);
480 lxc_container_put(clone
);
485 clone
->shutdown(clone
, -1);
487 ret
= clone
->get_config_item(clone
, "lxc.ephemeral", tmp_buf
, PATH_MAX
);
488 if (ret
> 0 && strcmp(tmp_buf
, "0"))
489 clone
->destroy(clone
);
492 lxc_container_put(clone
);
496 static int do_clone_rename(struct lxc_container
*c
, char *newname
)
498 if (!c
->rename(c
, newname
)) {
499 ERROR("Renaming container %s to %s failed", c
->name
, newname
);
506 static int do_clone_task(struct lxc_container
*c
, enum task task
, int flags
,
513 ret
= do_clone_ephemeral(c
, &my_args
, args
, flags
);
516 ret
= do_clone_rename(c
, my_args
.newname
);
519 ret
= do_clone(c
, my_args
.newname
, my_args
.newpath
, flags
,
520 my_args
.bdevtype
, my_args
.fssize
, my_args
.task
,
528 static void free_mnts()
531 struct mnts
*n
= NULL
;
533 for (i
= 0, n
= mnt_table
; i
< mnt_table_size
; i
++, n
++) {
546 static int my_parser(struct lxc_arguments
*args
, int c
, char *arg
)
548 char *subopts
= NULL
;
549 char *mntparameters
= NULL
;
564 args
->allowrunning
= 1;
573 args
->task
= DESTROY
;
577 if (parse_mntsubopts(subopts
, keys
, mntparameters
) < 0)
581 if (strncmp(arg
, "overlay", strlen(arg
)) == 0)
583 args
->bdevtype
= arg
;
589 args
->fssize
= get_fssize(optarg
);
605 static int parse_bind_mnt(char *mntstring
, enum mnttype type
)
608 char **mntarray
= NULL
;
609 struct mnts
*m
= NULL
;
611 m
= add_mnt(&mnt_table
, &mnt_table_size
, type
);
615 mntarray
= lxc_string_split(mntstring
, ':');
619 m
->src
= construct_path(mntarray
[0], true);
623 len
= lxc_array_len((void **)mntarray
);
624 if (len
== 1) { /* bind=src */
625 m
->dest
= construct_path(mntarray
[0], false);
626 } else if (len
== 2) { /* bind=src:option or bind=src:dest */
627 if (strncmp(mntarray
[1], "rw", strlen(mntarray
[1])) == 0)
628 m
->options
= strdup("rw");
630 if (strncmp(mntarray
[1], "ro", strlen(mntarray
[1])) == 0)
631 m
->options
= strdup("ro");
634 m
->dest
= construct_path(mntarray
[0], false);
636 m
->dest
= construct_path(mntarray
[1], false);
637 } else if (len
== 3) { /* bind=src:dest:option */
638 m
->dest
= construct_path(mntarray
[1], false);
639 m
->options
= strdup(mntarray
[2]);
641 printf("Excess elements in mount specification\n");
648 m
->options
= strdup("rw");
650 if (!m
->options
|| (strncmp(m
->options
, "rw", strlen(m
->options
)) != 0 &&
651 strncmp(m
->options
, "ro", strlen(m
->options
)) != 0))
654 lxc_free_array((void **)mntarray
, free
);
659 lxc_free_array((void **)mntarray
, free
);
663 static int parse_mntsubopts(char *subopts
, char *const *keys
, char *mntparameters
)
665 while (*subopts
!= '\0') {
666 switch (getsubopt(&subopts
, keys
, &mntparameters
)) {
668 if (parse_bind_mnt(mntparameters
, LXC_MNT_BIND
) < 0)
672 if (parse_ovl_mnt(mntparameters
, LXC_MNT_OVL
) < 0)
683 static int parse_ovl_mnt(char *mntstring
, enum mnttype type
)
686 char **mntarray
= NULL
;
689 m
= add_mnt(&mnt_table
, &mnt_table_size
, type
);
693 mntarray
= lxc_string_split(mntstring
, ':');
697 m
->src
= construct_path(mntarray
[0], true);
701 len
= lxc_array_len((void **)mntarray
);
702 if (len
== 1) /* overlay=src */
703 m
->dest
= construct_path(mntarray
[0], false);
704 else if (len
== 2) /* overlay=src:dest */
705 m
->dest
= construct_path(mntarray
[1], false);
707 printf("Excess elements in mount specification\n");
712 lxc_free_array((void **)mntarray
, free
);
717 lxc_free_array((void **)mntarray
, free
);
721 /* For ephemeral snapshots backed by the overlay filesystem, this function
722 * mounts a fresh tmpfs over the containers directory if the user requests it.
723 * Because we mount a fresh tmpfs over the directory of the container the
724 * updated /etc/hostname file created during the clone residing in the upperdir
725 * (currently named "delta0" by default) will be hidden. Hence, if the user
726 * requests that the old name is not to be kept for the clone, we recreate this
727 * file on the tmpfs. This should be all that is required to restore the exact
728 * behaviour we would get with a normal clone.
730 static char *mount_tmpfs(const char *oldname
, const char *newname
,
731 const char *path
, struct lxc_arguments
*arg
)
736 char *premount
= NULL
;
739 if (arg
->tmpfs
&& arg
->keepdata
) {
741 "A container can only be placed on a tmpfs when the "
742 "overlay storage driver is used");
746 if (arg
->tmpfs
&& !arg
->bdevtype
) {
747 arg
->bdevtype
= "overlayfs";
748 } else if (arg
->tmpfs
&& arg
->bdevtype
&&
749 strncmp(arg
->bdevtype
, "overlayfs", strlen(arg
->bdevtype
)) != 0) {
751 "A container can only be placed on a tmpfs when the "
752 "overlay storage driver is used");
756 len
= strlen(path
) + strlen(newname
) + strlen("pre-start-XXXXXX") + /* //\0 */ 3;
757 premount
= malloc(len
);
761 ret
= snprintf(premount
, len
, "%s/%s/pre-start-XXXXXX", path
, newname
);
762 if (ret
< 0 || (size_t)ret
>= len
)
766 fd
= mkstemp(premount
);
771 if (fcntl(fd
, F_SETFD
, FD_CLOEXEC
)) {
772 ERROR("Failed to set close-on-exec on file descriptor");
776 if (chmod(premount
, 0755) < 0)
779 fp
= fdopen(fd
, "r+");
784 ret
= fprintf(fp
, "#! /bin/sh\n"
785 "mount -n -t tmpfs -o mode=0755 none %s/%s\n",
790 if (!arg
->keepname
) {
791 ret
= fprintf(fp
, "mkdir -p %s/%s/delta0/etc\n"
792 "echo %s > %s/%s/delta0/etc/hostname\n",
793 path
, newname
, newname
, path
, newname
);