diff --git a/.gitignore b/.gitignore
index e6de18f60..0b6ec69bc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,6 +49,7 @@ src/lxc/lxc-attach
src/lxc/lxc-autostart
src/lxc/lxc-cgroup
src/lxc/lxc-checkconfig
+src/lxc/lxc-checkpoint
src/lxc/lxc-clone
src/lxc/lxc-console
src/lxc/lxc-config
diff --git a/configure.ac b/configure.ac
index 1a55521ce..882e759a1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -644,6 +644,7 @@ AC_CONFIG_FILES([
doc/lxc-autostart.sgml
doc/lxc-cgroup.sgml
doc/lxc-checkconfig.sgml
+ doc/lxc-checkpoint.sgml
doc/lxc-clone.sgml
doc/lxc-config.sgml
doc/lxc-console.sgml
diff --git a/doc/Makefile.am b/doc/Makefile.am
index bfe887ed6..767ee3861 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -20,6 +20,7 @@ man_MANS = \
lxc-autostart.1 \
lxc-cgroup.1 \
lxc-checkconfig.1 \
+ lxc-checkpoint.1 \
lxc-clone.1 \
lxc-config.1 \
lxc-console.1 \
diff --git a/doc/lxc-checkpoint.sgml.in b/doc/lxc-checkpoint.sgml.in
new file mode 100644
index 000000000..cb58074f2
--- /dev/null
+++ b/doc/lxc-checkpoint.sgml.in
@@ -0,0 +1,194 @@
+
+
+
+
+]>
+
+
+
+ @LXC_GENERATE_DATE@
+
+
+ lxc-checkpoint
+ 1
+
+
+
+ lxc-checkpoint
+
+
+ checkpoint a container
+
+
+
+
+
+ lxc-checkpoint
+ -n name
+ -D PATH
+ -r
+ -s
+ -v
+ -d
+ -F
+
+
+
+
+ Description
+
+ lxc-checkpoint checkpoints and restores containers.
+
+
+
+
+ Options
+
+
+
+
+
+
+
+
+ Restore the checkpoint for the container, instead of dumping it.
+ This option is incompatible with .
+
+
+
+
+
+
+
+
+
+
+ The directory to dump the checkpoint metadata.
+
+
+
+
+
+
+
+
+
+
+ Optionally stop the container after dumping. This option is
+ incompatible with .
+
+
+
+
+
+
+
+
+
+
+ Enable verbose criu logging.
+
+
+
+
+
+
+
+
+
+
+ Restore the container in the background (this is the default).
+ Only available when providing .
+
+
+
+
+
+
+
+
+
+
+ Restore the container in the foreground. Only available when
+ providing .
+
+
+
+
+
+
+
+ &commonoptions;
+
+
+ Examples
+
+
+
+ lxc-checkpoint -n foo -D /tmp/checkpoint
+
+
+ Checkpoint the container foo into the directory /tmp/checkpoint.
+
+
+
+
+
+ lxc-checkpoint -r -n foo -D /tmp/checkpoint
+
+
+ Restore the checkpoint from the directory /tmp/checkpoint.
+
+
+
+
+
+
+
+ &seealso;
+
+
+ Author
+ Tycho Andersen tycho.andersen@canonical.com
+
+
+
+
diff --git a/src/lxc/Makefile.am b/src/lxc/Makefile.am
index c1a67d64d..26bb005f6 100644
--- a/src/lxc/Makefile.am
+++ b/src/lxc/Makefile.am
@@ -184,6 +184,7 @@ bin_PROGRAMS = \
lxc-attach \
lxc-autostart \
lxc-cgroup \
+ lxc-checkpoint \
lxc-clone \
lxc-config \
lxc-console \
@@ -205,6 +206,8 @@ sbin_PROGRAMS = init.lxc
pkglibexec_PROGRAMS = \
lxc-monitord \
lxc-user-nic
+pkglibexec_SCRIPTS = \
+ lxc-restore-net
AM_LDFLAGS = -Wl,-E
if ENABLE_RPATH
@@ -234,6 +237,7 @@ lxc_create_SOURCES = lxc_create.c
lxc_snapshot_SOURCES = lxc_snapshot.c
lxc_usernsexec_SOURCES = lxc_usernsexec.c
lxc_user_nic_SOURCES = lxc_user_nic.c network.c network.h
+lxc_checkpoint_SOURCES = lxc_checkpoint.c
if HAVE_STATIC_LIBCAP
sbin_PROGRAMS += init.lxc.static
diff --git a/src/lxc/lxc-restore-net b/src/lxc/lxc-restore-net
new file mode 100755
index 000000000..7d4558399
--- /dev/null
+++ b/src/lxc/lxc-restore-net
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+[ -z "$CRTOOLS_IMAGE_DIR" ] && exit 1
+
+set -e
+
+dir="$CRTOOLS_IMAGE_DIR"
+
+i=0
+while [ -f "$dir/eth$i" ] && [ -f "$dir/veth$i" ] && [ -f "$dir/bridge$i" ]; do
+ veth=$(cat "$dir/veth$i")
+ bridge=$(cat "$dir/bridge$i")
+
+ if [ "$CRTOOLS_SCRIPT_ACTION" = "network-lock" ]; then
+ brctl delif $bridge $veth
+ fi
+
+ if [ "$CRTOOLS_SCRIPT_ACTION" = "network-unlock" ]; then
+ brctl addif $bridge $veth
+ ifconfig $veth 0.0.0.0 up
+ fi
+
+ i=$((i+1))
+done
diff --git a/src/lxc/lxc_checkpoint.c b/src/lxc/lxc_checkpoint.c
new file mode 100644
index 000000000..8dc2c1792
--- /dev/null
+++ b/src/lxc/lxc_checkpoint.c
@@ -0,0 +1,202 @@
+/*
+ *
+ * Copyright © 2014 Tycho Andersen .
+ * Copyright © 2014 Canonical Ltd.
+ *
+ * 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.
+ */
+
+#include
+#include
+#include
+
+#include
+
+#include "log.h"
+#include "config.h"
+#include "lxc.h"
+#include "arguments.h"
+
+static char *checkpoint_dir = NULL;
+static bool stop = false;
+static bool verbose = false;
+static bool do_restore = false;
+static bool daemonize_set = false;
+
+static const struct option my_longopts[] = {
+ {"checkpoint-dir", required_argument, 0, 'D'},
+ {"stop", no_argument, 0, 's'},
+ {"verbose", no_argument, 0, 'v'},
+ {"restore", no_argument, 0, 'r'},
+ {"daemon", no_argument, 0, 'd'},
+ {"foreground", no_argument, 0, 'F'},
+ LXC_COMMON_OPTIONS
+};
+
+static int my_checker(const struct lxc_arguments *args)
+{
+ if (do_restore && stop) {
+ lxc_error(args, "-s not compatible with -r.");
+ return -1;
+
+ } else if (!do_restore && daemonize_set) {
+ lxc_error(args, "-d/-F not compatible with -r.");
+ return -1;
+ }
+
+ if (checkpoint_dir == NULL) {
+ lxc_error(args, "-D is required.");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int my_parser(struct lxc_arguments *args, int c, char *arg)
+{
+ switch (c) {
+ case 'D':
+ checkpoint_dir = strdup(arg);
+ if (!checkpoint_dir)
+ return -1;
+ break;
+ case 's':
+ stop = true;
+ break;
+ case 'v':
+ verbose = true;
+ break;
+ case 'r':
+ do_restore = true;
+ break;
+ case 'd':
+ args->daemonize = 1;
+ daemonize_set = true;
+ break;
+ case 'F':
+ args->daemonize = 0;
+ daemonize_set = true;
+ break;
+ }
+ return 0;
+}
+
+static struct lxc_arguments my_args = {
+ .progname = "lxc-checkpoint",
+ .help = "\
+--name=NAME\n\
+\n\
+lxc-checkpoint checkpoints and restores a container\n\
+ Serializes a container's running state to disk to allow restoring it in\n\
+ its running state at a later time.\n\
+\n\
+Options :\n\
+ -n, --name=NAME NAME for name of the container\n\
+ -r, --restore Restore container\n\
+ -D, --checkpoint-dir=DIR directory to save the checkpoint in\n\
+ -v, --verbose Enable verbose criu logs\n\
+ Checkpoint options:\n\
+ -s, --stop Stop the container after checkpointing.\n\
+ Restore options:\n\
+ -d, --daemon Daemonize the container (default)\n\
+ -F, --foreground Start with the current tty attached to /dev/console\n\
+",
+ .options = my_longopts,
+ .parser = my_parser,
+ .daemonize = 1,
+ .checker = my_checker,
+};
+
+bool checkpoint(struct lxc_container *c)
+{
+ bool ret;
+
+ if (!c->is_running(c)) {
+ fprintf(stderr, "%s not running, not checkpointing.\n", my_args.name);
+ lxc_container_put(c);
+ return false;
+ }
+
+ ret = c->checkpoint(c, checkpoint_dir, stop, verbose);
+ lxc_container_put(c);
+
+ if (!ret) {
+ fprintf(stderr, "Checkpointing %s failed.\n", my_args.name);
+ return false;
+ }
+
+ return true;
+}
+
+bool restore(struct lxc_container *c)
+{
+ pid_t pid = 0;
+ bool ret = true;
+
+ if (c->is_running(c)) {
+ fprintf(stderr, "%s is running, not restoring.\n", my_args.name);
+ lxc_container_put(c);
+ return false;
+ }
+
+ if (my_args.daemonize)
+ pid = fork();
+
+ if (pid == 0) {
+ ret = c->restore(c, checkpoint_dir, verbose);
+
+ if (!ret) {
+ fprintf(stderr, "Restoring %s failed.\n", my_args.name);
+ }
+ }
+
+ lxc_container_put(c);
+
+ return ret;
+}
+
+int main(int argc, char *argv[])
+{
+ struct lxc_container *c;
+ bool ret;
+
+ if (lxc_arguments_parse(&my_args, argc, argv))
+ exit(1);
+
+ c = lxc_container_new(my_args.name, my_args.lxcpath[0]);
+ if (!c) {
+ fprintf(stderr, "System error loading %s\n", my_args.name);
+ exit(1);
+ }
+
+ if (!c->may_control(c)) {
+ fprintf(stderr, "Insufficent privileges to control %s\n", my_args.name);
+ lxc_container_put(c);
+ exit(1);
+ }
+
+ if (!c->is_defined(c)) {
+ fprintf(stderr, "%s is not defined\n", my_args.name);
+ lxc_container_put(c);
+ exit(1);
+ }
+
+
+ if (do_restore)
+ ret = restore(c);
+ else
+ ret = checkpoint(c);
+
+ return !ret;
+}
diff --git a/src/lxc/lxccontainer.c b/src/lxc/lxccontainer.c
index 172e667e2..ed6f8de97 100644
--- a/src/lxc/lxccontainer.c
+++ b/src/lxc/lxccontainer.c
@@ -55,6 +55,7 @@
#include "monitor.h"
#include "namespace.h"
#include "lxclock.h"
+#include "sync.h"
#if HAVE_IFADDRS_H
#include
@@ -3495,6 +3496,469 @@ static bool lxcapi_remove_device_node(struct lxc_container *c, const char *src_p
return add_remove_device_node(c, src_path, dest_path, false);
}
+struct criu_opts {
+ /* The type of criu invocation, one of "dump" or "restore" */
+ char *action;
+
+ /* The directory to pass to criu */
+ char *directory;
+
+ /* The container to dump */
+ struct lxc_container *c;
+
+ /* Enable criu verbose mode? */
+ bool verbose;
+
+ /* dump: stop the container or not after dumping? */
+ bool stop;
+
+ /* restore: the file to write the init process' pid into */
+ char *pidfile;
+};
+
+/*
+ * @out must be 128 bytes long
+ */
+static int read_criu_file(const char *directory, const char *file, int netnr, char *out)
+{
+ char path[PATH_MAX];
+ int ret;
+ FILE *f;
+
+ ret = snprintf(path, PATH_MAX, "%s/%s%d", directory, file, netnr);
+ if (ret < 0 || ret >= PATH_MAX) {
+ ERROR("%s: path too long", __func__);
+ return -1;
+ }
+
+ f = fopen(path, "r");
+ if (!f)
+ return -1;
+
+ ret = fscanf(f, "%127s", out);
+ fclose(f);
+ if (ret <= 0)
+ return -1;
+
+ return 0;
+}
+
+static void exec_criu(struct criu_opts *opts)
+{
+ char **argv, log[PATH_MAX];
+ int static_args = 13, argc = 0, i, ret;
+
+ /* The command line always looks like:
+ * criu $(action) --tcp-established --file-locks --link-remap --manage-cgroups \
+ * --action-script foo.sh -D $(directory) -o $(directory)/$(action).log
+ * +1 for final NULL */
+
+ if (strcmp(opts->action, "dump") == 0) {
+ /* -t pid */
+ static_args += 2;
+
+ /* --leave-running */
+ if (!opts->stop)
+ static_args++;
+ } else if (strcmp(opts->action, "restore") == 0) {
+ /* --root $(lxc_mount_point) --restore-detached --pidfile $foo */
+ static_args += 5;
+ } else {
+ return;
+ }
+
+ if (opts->verbose)
+ static_args++;
+
+ ret = snprintf(log, PATH_MAX, "%s/%s.log", opts->directory, opts->action);
+ if (ret < 0 || ret >= PATH_MAX) {
+ ERROR("logfile name too long\n");
+ return;
+ }
+
+ argv = malloc(static_args * sizeof(*argv));
+ if (!argv)
+ return;
+
+ memset(argv, 0, static_args * sizeof(*argv));
+
+#define DECLARE_ARG(arg) \
+ do { \
+ argv[argc++] = strdup(arg); \
+ if (!argv[argc-1]) \
+ goto err; \
+ } while (0)
+
+ argv[argc++] = on_path("criu", NULL);
+ if (!argv[argc-1]) {
+ ERROR("Couldn't find criu binary\n");
+ goto err;
+ }
+
+ DECLARE_ARG(opts->action);
+ DECLARE_ARG("--tcp-established");
+ DECLARE_ARG("--file-locks");
+ DECLARE_ARG("--link-remap");
+ DECLARE_ARG("--manage-cgroups");
+ DECLARE_ARG("--action-script");
+ DECLARE_ARG(LIBEXECDIR "/lxc/lxc-restore-net");
+ DECLARE_ARG("-D");
+ DECLARE_ARG(opts->directory);
+ DECLARE_ARG("-o");
+ DECLARE_ARG(log);
+
+ if (opts->verbose)
+ DECLARE_ARG("-vvvvvv");
+
+ if (strcmp(opts->action, "dump") == 0) {
+ char pid[32];
+
+ if (sprintf(pid, "%d", lxcapi_init_pid(opts->c)) < 0)
+ goto err;
+
+ DECLARE_ARG("-t");
+ DECLARE_ARG(pid);
+ if (!opts->stop)
+ DECLARE_ARG("--leave-running");
+ } else if (strcmp(opts->action, "restore") == 0) {
+ int netnr = 0;
+ struct lxc_list *it;
+
+ DECLARE_ARG("--root");
+ DECLARE_ARG(opts->c->lxc_conf->rootfs.mount);
+ DECLARE_ARG("--restore-detached");
+ DECLARE_ARG("--pidfile");
+ DECLARE_ARG(opts->pidfile);
+
+ lxc_list_for_each(it, &opts->c->lxc_conf->network) {
+ char eth[128], veth[128], buf[257];
+ void *m;
+
+ if (read_criu_file(opts->directory, "veth", netnr, veth))
+ goto err;
+ if (read_criu_file(opts->directory, "eth", netnr, eth))
+ goto err;
+ ret = snprintf(buf, 257, "%s=%s", eth, veth);
+ if (ret < 0 || ret >= 257)
+ goto err;
+
+ /* final NULL and --veth-pair eth0:vethASDF */
+ m = realloc(argv, (argc + 1 + 2) * sizeof(*argv));
+ if (!m)
+ goto err;
+ argv = m;
+
+ DECLARE_ARG("--veth-pair");
+ DECLARE_ARG(buf);
+ argv[argc] = NULL;
+
+ netnr++;
+ }
+ }
+
+#undef DECLARE_ARG
+
+ execv(argv[0], argv);
+err:
+ for (i = 0; argv[i]; i++)
+ free(argv[i]);
+ free(argv);
+}
+
+/* Check and make sure the container has a configuration that we know CRIU can
+ * dump. */
+static bool criu_ok(struct lxc_container *c)
+{
+ struct lxc_list *it;
+ bool found_deny_rule = false;
+
+ if (geteuid()) {
+ ERROR("Must be root to checkpoint\n");
+ return false;
+ }
+
+ /* We only know how to restore containers with veth networks. */
+ lxc_list_for_each(it, &c->lxc_conf->network) {
+ struct lxc_netdev *n = it->elem;
+ if (n->type != LXC_NET_VETH && n->type != LXC_NET_NONE) {
+ ERROR("Found network that is not VETH or NONE\n");
+ return false;
+ }
+ }
+
+ // These requirements come from http://criu.org/LXC
+ if (c->lxc_conf->console.path &&
+ strcmp(c->lxc_conf->console.path, "none") != 0) {
+ ERROR("lxc.console must be none\n");
+ return false;
+ }
+
+ if (c->lxc_conf->tty != 0) {
+ ERROR("lxc.tty must be 0\n");
+ return false;
+ }
+
+ lxc_list_for_each(it, &c->lxc_conf->cgroup) {
+ struct lxc_cgroup *cg = it->elem;
+ if (strcmp(cg->subsystem, "devices.deny") == 0 &&
+ strcmp(cg->value, "c 5:1 rwm") == 0) {
+
+ found_deny_rule = true;
+ break;
+ }
+ }
+
+ if (!found_deny_rule) {
+ ERROR("couldn't find devices.deny = c 5:1 rwm");
+ return false;
+ }
+
+ return true;
+}
+
+static bool lxcapi_checkpoint(struct lxc_container *c, char *directory, bool stop, bool verbose)
+{
+ int netnr, status;
+ struct lxc_list *it;
+ bool error = false;
+ pid_t pid;
+
+ if (!criu_ok(c))
+ return false;
+
+ if (mkdir(directory, 0700) < 0 && errno != EEXIST)
+ return false;
+
+ netnr = 0;
+ lxc_list_for_each(it, &c->lxc_conf->network) {
+ char *veth = NULL, *bridge = NULL, veth_path[PATH_MAX], eth[128];
+ struct lxc_netdev *n = it->elem;
+ int pret;
+
+ pret = snprintf(veth_path, PATH_MAX, "lxc.network.%d.veth.pair", netnr);
+ if (pret < 0 || pret >= PATH_MAX) {
+ error = true;
+ goto out;
+ }
+
+ veth = lxcapi_get_running_config_item(c, veth_path);
+ if (!veth) {
+ /* criu_ok() checks that all interfaces are
+ * LXC_NET{VETH,NONE}, and VETHs should have this
+ * config */
+ assert(n->type == LXC_NET_NONE);
+ break;
+ }
+
+ pret = snprintf(veth_path, PATH_MAX, "lxc.network.%d.link", netnr);
+ if (pret < 0 || pret >= PATH_MAX) {
+ error = true;
+ goto out;
+ }
+
+ bridge = lxcapi_get_running_config_item(c, veth_path);
+ if (!bridge) {
+ error = true;
+ goto out;
+ }
+
+ pret = snprintf(veth_path, PATH_MAX, "%s/veth%d", directory, netnr);
+ if (pret < 0 || pret >= PATH_MAX || print_to_file(veth_path, veth) < 0) {
+ error = true;
+ goto out;
+ }
+
+ pret = snprintf(veth_path, PATH_MAX, "%s/bridge%d", directory, netnr);
+ if (pret < 0 || pret >= PATH_MAX || print_to_file(veth_path, bridge) < 0) {
+ error = true;
+ goto out;
+ }
+
+ if (n->name) {
+ if (strlen(n->name) >= 128) {
+ error = true;
+ goto out;
+ }
+ strncpy(eth, n->name, 128);
+ } else
+ sprintf(eth, "eth%d", netnr);
+
+ pret = snprintf(veth_path, PATH_MAX, "%s/eth%d", directory, netnr);
+ if (pret < 0 || pret >= PATH_MAX || print_to_file(veth_path, eth) < 0)
+ error = true;
+
+out:
+ free(veth);
+ free(bridge);
+ if (error)
+ return false;
+ }
+
+ pid = fork();
+ if (pid < 0)
+ return false;
+
+ if (pid == 0) {
+ struct criu_opts os;
+
+ os.action = "dump";
+ os.directory = directory;
+ os.c = c;
+ os.stop = stop;
+ os.verbose = verbose;
+
+ /* exec_criu() returning is an error */
+ exec_criu(&os);
+ exit(1);
+ } else {
+ pid_t w = waitpid(pid, &status, 0);
+ if (w == -1) {
+ perror("waitpid");
+ return false;
+ }
+
+ if (WIFEXITED(status)) {
+ return !WEXITSTATUS(status);
+ }
+
+ return false;
+ }
+}
+
+static bool lxcapi_restore(struct lxc_container *c, char *directory, bool verbose)
+{
+ pid_t pid;
+ struct lxc_list *it;
+ struct lxc_rootfs *rootfs;
+ char pidfile[L_tmpnam];
+
+ if (!criu_ok(c))
+ return false;
+
+ if (geteuid()) {
+ ERROR("Must be root to restore\n");
+ return false;
+ }
+
+ if (!tmpnam(pidfile))
+ return false;
+
+ struct lxc_handler *handler;
+
+ handler = lxc_init(c->name, c->lxc_conf, c->config_path);
+ if (!handler)
+ return false;
+
+ pid = fork();
+ if (pid < 0)
+ return false;
+
+ if (pid == 0) {
+ struct criu_opts os;
+
+ if (unshare(CLONE_NEWNS))
+ return false;
+
+ /* CRIU needs the lxc root bind mounted so that it is the root of some
+ * mount. */
+ rootfs = &c->lxc_conf->rootfs;
+
+ if (rootfs_is_blockdev(c->lxc_conf)) {
+ if (do_rootfs_setup(c->lxc_conf, c->name, c->config_path) < 0)
+ return false;
+ }
+ else {
+ if (mkdir(rootfs->mount, 0755) < 0 && errno != EEXIST)
+ return false;
+
+ if (mount(rootfs->path, rootfs->mount, NULL, MS_BIND, NULL) < 0) {
+ rmdir(rootfs->mount);
+ return false;
+ }
+ }
+
+ os.action = "restore";
+ os.directory = directory;
+ os.c = c;
+ os.pidfile = pidfile;
+ os.verbose = verbose;
+
+ /* exec_criu() returning is an error */
+ exec_criu(&os);
+ umount(rootfs->mount);
+ rmdir(rootfs->mount);
+ exit(1);
+ } else {
+ int status;
+ pid_t w = waitpid(pid, &status, 0);
+
+ if (w == -1) {
+ perror("waitpid");
+ return false;
+ }
+
+ if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status)) {
+ return false;
+ }
+ else {
+ int netnr = 0, ret;
+ bool error = false;
+ FILE *f = fopen(pidfile, "r");
+ if (!f) {
+ perror("reading pidfile");
+ ERROR("couldn't read restore's init pidfile %s\n", pidfile);
+ return false;
+ }
+
+ ret = fscanf(f, "%d", (int*) &handler->pid);
+ fclose(f);
+ if (ret != 1) {
+ ERROR("reading restore pid failed");
+ return false;
+ }
+
+ if (container_mem_lock(c))
+ return false;
+
+ lxc_list_for_each(it, &c->lxc_conf->network) {
+ char eth[128], veth[128];
+ struct lxc_netdev *netdev = it->elem;
+
+ if (read_criu_file(directory, "veth", netnr, veth)) {
+ error = true;
+ goto out_unlock;
+ }
+ if (read_criu_file(directory, "eth", netnr, eth)) {
+ error = true;
+ goto out_unlock;
+ }
+ netdev->priv.veth_attr.pair = strdup(veth);
+ if (!netdev->priv.veth_attr.pair) {
+ error = true;
+ goto out_unlock;
+ }
+ netnr++;
+ }
+out_unlock:
+ container_mem_unlock(c);
+ if (error)
+ return false;
+
+ if (lxc_set_state(c->name, handler, RUNNING))
+ return false;
+ }
+ }
+
+ if (lxc_poll(c->name, handler)) {
+ lxc_abort(c->name, handler);
+ return false;
+ }
+ }
+
+ return true;
+}
+
static int lxcapi_attach_run_waitl(struct lxc_container *c, lxc_attach_options_t *options, const char *program, const char *arg, ...)
{
va_list ap;
@@ -3627,6 +4091,8 @@ struct lxc_container *lxc_container_new(const char *name, const char *configpath
c->may_control = lxcapi_may_control;
c->add_device_node = lxcapi_add_device_node;
c->remove_device_node = lxcapi_remove_device_node;
+ c->checkpoint = lxcapi_checkpoint;
+ c->restore = lxcapi_restore;
/* we'll allow the caller to update these later */
if (lxc_log_init(NULL, "none", NULL, "lxc_container", 0, c->config_path)) {
diff --git a/src/lxc/lxccontainer.h b/src/lxc/lxccontainer.h
index 5085c43ef..6344f3d71 100644
--- a/src/lxc/lxccontainer.h
+++ b/src/lxc/lxccontainer.h
@@ -760,6 +760,31 @@ struct lxc_container {
* \return \c true on success, else \c false.
*/
bool (*remove_device_node)(struct lxc_container *c, const char *src_path, const char *dest_path);
+
+ /*!
+ * \brief Checkpoint a container.
+ *
+ * \param c Container.
+ * \param directory The directory to dump the container to.
+ * \param stop Whether or not to stop the container after checkpointing.
+ * \param verbose Enable criu's verbose logs.
+ *
+ * \return \c true on success, else \c false.
+ * present at compile time).
+ */
+ bool (*checkpoint)(struct lxc_container *c, char *directory, bool stop, bool verbose);
+
+ /*!
+ * \brief Restore a container from a checkpoint.
+ *
+ * \param c Container.
+ * \param directory The directory to restore the container from.
+ * \param verbose Enable criu's verbose logs.
+ *
+ * \return \c true on success, else \c false.
+ *
+ */
+ bool (*restore)(struct lxc_container *c, char *directory, bool verbose);
};
/*!
diff --git a/src/lxc/start.c b/src/lxc/start.c
index f282b93cf..98849e10e 100644
--- a/src/lxc/start.c
+++ b/src/lxc/start.c
@@ -300,14 +300,14 @@ static int signal_handler(int fd, uint32_t events, void *data,
return 1;
}
-static int lxc_set_state(const char *name, struct lxc_handler *handler, lxc_state_t state)
+int lxc_set_state(const char *name, struct lxc_handler *handler, lxc_state_t state)
{
handler->state = state;
lxc_monitor_send_state(name, state, handler->lxcpath);
return 0;
}
-static int lxc_poll(const char *name, struct lxc_handler *handler)
+int lxc_poll(const char *name, struct lxc_handler *handler)
{
int sigfd = handler->sigfd;
int pid = handler->pid;
@@ -485,7 +485,7 @@ static void lxc_fini(const char *name, struct lxc_handler *handler)
free(handler);
}
-static void lxc_abort(const char *name, struct lxc_handler *handler)
+void lxc_abort(const char *name, struct lxc_handler *handler)
{
int ret, status;
diff --git a/src/lxc/start.h b/src/lxc/start.h
index ca7891cdd..8af0a0607 100644
--- a/src/lxc/start.h
+++ b/src/lxc/start.h
@@ -74,6 +74,10 @@ struct lxc_handler {
void *cgroup_data;
};
+
+extern int lxc_poll(const char *name, struct lxc_handler *handler);
+extern int lxc_set_state(const char *name, struct lxc_handler *handler, lxc_state_t state);
+extern void lxc_abort(const char *name, struct lxc_handler *handler);
extern struct lxc_handler *lxc_init(const char *name, struct lxc_conf *, const char *);
extern int lxc_check_inherited(struct lxc_conf *conf, int fd_to_ignore);
diff --git a/src/lxc/utils.c b/src/lxc/utils.c
index a32829d11..ed34706bf 100644
--- a/src/lxc/utils.c
+++ b/src/lxc/utils.c
@@ -1446,3 +1446,17 @@ out1:
free(retv);
return NULL;
}
+
+int print_to_file(const char *file, const char *content)
+{
+ FILE *f;
+ int ret = 0;
+
+ f = fopen(file, "w");
+ if (!f)
+ return -1;
+ if (fprintf(f, "%s", content) != strlen(content))
+ ret = -1;
+ fclose(f);
+ return ret;
+}
diff --git a/src/lxc/utils.h b/src/lxc/utils.h
index a84b4897d..cdfe56aef 100644
--- a/src/lxc/utils.h
+++ b/src/lxc/utils.h
@@ -282,3 +282,4 @@ int detect_ramfs_rootfs(void);
char *on_path(char *cmd, const char *rootfs);
bool file_exists(const char *f);
char *choose_init(const char *rootfs);
+int print_to_file(const char *file, const char *content);
diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am
index 6b4fceb74..85f6cea99 100644
--- a/src/tests/Makefile.am
+++ b/src/tests/Makefile.am
@@ -51,7 +51,7 @@ bin_PROGRAMS = lxc-test-containertests lxc-test-locktests lxc-test-startone \
bin_SCRIPTS = lxc-test-autostart
if DISTRO_UBUNTU
-bin_SCRIPTS += lxc-test-usernic lxc-test-ubuntu lxc-test-unpriv
+bin_SCRIPTS += lxc-test-usernic lxc-test-ubuntu lxc-test-unpriv lxc-test-checkpoint
endif
endif
diff --git a/src/tests/lxc-test-checkpoint-restore b/src/tests/lxc-test-checkpoint-restore
new file mode 100755
index 000000000..43068ef44
--- /dev/null
+++ b/src/tests/lxc-test-checkpoint-restore
@@ -0,0 +1,46 @@
+#!/bin/sh
+
+# Do an end to end checkpoint and restore with criu.
+
+set -e
+
+FAIL() {
+ echo -n "Failed " >&2
+ echo "$*" >&2
+ exit 1
+}
+
+if [ "$(id -u)" != "0" ]; then
+ echo "ERROR: Must run as root."
+ exit 1
+fi
+
+if [ "$(criu --version | head -n1 | cut -d' ' -f 2)" != "1.3-rc2" ]; then
+ echo "SKIP: skipping test because no (or wrong) criu installed."
+ exit 0
+fi
+
+name=lxc-test-criu
+lxc-create -t ubuntu -n $name || FAIL "creating container"
+
+cat >> "$(lxc-config lxc.lxcpath)/$name/config" <