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" <