>From 9c4c750b9305ee81e11fa399057d6f8d1d7eb1e2 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen@canonical.com>
Date: Mon, 30 Nov 2015 15:14:22 -0700
Subject: [PATCH 3/3] c/r: add a new ->migrate API call

This patch adds a new ->migrate API call with three commands:

MIGRATE_DUMP: this is basically just ->checkpoint()
MIGRATE_RESTORE: this is just ->restore()
MIGRATE_PRE_DUMP: this can be used to invoke criu's pre-dump command on the
    container.

A small addition to the (pre-)dump commands is the ability to specify a
previous partial dump directory, so that one can use a pre-dump of a
container.

Finally, this new API call uses a structure to pass options so that it can
be easily extended in the future (e.g. to CRIU's --leave-frozen option in
the future, for potentially smarter failure handling on restore).

v2: remember to flip the return code for legacy ->checkpoint and ->restore
    calls

Signed-off-by: Tycho Andersen <tycho.andersen@canonical.com>
---
 src/lxc/criu.c         | 155 +++++++++++++++++++++++++++++++++++++++++++++++--
 src/lxc/criu.h         |   9 ++-
 src/lxc/lxccontainer.c | 140 ++++++++++++++++----------------------------
 src/lxc/lxccontainer.h |  33 +++++++++++
 4 files changed, 237 insertions(+), 100 deletions(-)

diff --git a/src/lxc/criu.c b/src/lxc/criu.c
index 695a763..c0ce965 100644
--- a/src/lxc/criu.c
+++ b/src/lxc/criu.c
@@ -64,12 +64,16 @@ void exec_criu(struct criu_opts *opts)
 	 * --enable-fs hugetlbfs --enable-fs tracefs
 	 * +1 for final NULL */
 
-	if (strcmp(opts->action, "dump") == 0) {
+	if (strcmp(opts->action, "dump") == 0 || strcmp(opts->action, "pre-dump") == 0) {
 		/* -t pid --freeze-cgroup /lxc/ct */
 		static_args += 4;
 
-		/* --leave-running */
-		if (!opts->stop)
+		/* --prev-images-dir <path-to-directory-A-relative-to-B> */
+		if (opts->predump_dir)
+			static_args += 2;
+
+		/* --leave-running (only for final dump) */
+		if (strcmp(opts->action, "dump") == 0 && !opts->stop)
 			static_args++;
 	} else if (strcmp(opts->action, "restore") == 0) {
 		/* --root $(lxc_mount_point) --restore-detached
@@ -133,13 +137,12 @@ void exec_criu(struct criu_opts *opts)
 	if (opts->verbose)
 		DECLARE_ARG("-vvvvvv");
 
-	if (strcmp(opts->action, "dump") == 0) {
+	if (strcmp(opts->action, "dump") == 0 || strcmp(opts->action, "pre-dump") == 0) {
 		char pid[32], *freezer_relative;
 
 		if (sprintf(pid, "%d", opts->c->init_pid(opts->c)) < 0)
 			goto err;
 
-
 		DECLARE_ARG("-t");
 		DECLARE_ARG(pid);
 
@@ -158,7 +161,13 @@ void exec_criu(struct criu_opts *opts)
 		DECLARE_ARG("--freeze-cgroup");
 		DECLARE_ARG(log);
 
-		if (!opts->stop)
+		if (opts->predump_dir) {
+			DECLARE_ARG("--prev-images-dir");
+			DECLARE_ARG(opts->predump_dir);
+		}
+
+		/* only for final dump */
+		if (strcmp(opts->action, "dump") == 0 && !opts->stop)
 			DECLARE_ARG("--leave-running");
 	} else if (strcmp(opts->action, "restore") == 0) {
 		void *m;
@@ -402,6 +411,8 @@ out_unlock:
 	return !has_error;
 }
 
+// do_restore never returns, the calling process is used as the
+// monitor process. do_restore calls exit() if it fails.
 void do_restore(struct lxc_container *c, int pipe, char *directory, bool verbose)
 {
 	pid_t pid;
@@ -560,3 +571,135 @@ out:
 
 	exit(1);
 }
+
+/* do one of either predump or a regular dump */
+static bool do_dump(struct lxc_container *c, char *mode, char *directory,
+		    bool stop, bool verbose, char *predump_dir)
+{
+	pid_t pid;
+
+	if (!criu_ok(c))
+		return false;
+
+	if (mkdir_p(directory, 0700) < 0)
+		return false;
+
+	pid = fork();
+	if (pid < 0) {
+		SYSERROR("fork failed");
+		return false;
+	}
+
+	if (pid == 0) {
+		struct criu_opts os;
+
+		os.action = mode;
+		os.directory = directory;
+		os.c = c;
+		os.stop = stop;
+		os.verbose = verbose;
+		os.predump_dir = predump_dir;
+
+		/* exec_criu() returning is an error */
+		exec_criu(&os);
+		exit(1);
+	} else {
+		int status;
+		pid_t w = waitpid(pid, &status, 0);
+		if (w == -1) {
+			SYSERROR("waitpid");
+			return false;
+		}
+
+		if (WIFEXITED(status)) {
+			if (WEXITSTATUS(status)) {
+				ERROR("dump failed with %d\n", WEXITSTATUS(status));
+				return false;
+			}
+
+			return true;
+		} else if (WIFSIGNALED(status)) {
+			ERROR("dump signaled with %d\n", WTERMSIG(status));
+			return false;
+		} else {
+			ERROR("unknown dump exit %d\n", status);
+			return false;
+		}
+	}
+}
+
+bool pre_dump(struct lxc_container *c, char *directory, bool verbose, char *predump_dir)
+{
+	return do_dump(c, "pre-dump", directory, false, verbose, predump_dir);
+}
+
+bool dump(struct lxc_container *c, char *directory, bool stop, bool verbose, char *predump_dir)
+{
+	char path[PATH_MAX];
+	int ret;
+
+	ret = snprintf(path, sizeof(path), "%s/inventory.img", directory);
+	if (ret < 0 || ret >= sizeof(path))
+		return false;
+
+	if (access(path, F_OK) == 0) {
+		ERROR("please use a fresh directory for the dump directory\n");
+		return false;
+	}
+
+	return do_dump(c, "dump", directory, stop, verbose, predump_dir);
+}
+
+bool restore(struct lxc_container *c, char *directory, bool verbose)
+{
+	pid_t pid;
+	int status, nread;
+	int pipefd[2];
+
+	if (!criu_ok(c))
+		return false;
+
+	if (geteuid()) {
+		ERROR("Must be root to restore\n");
+		return false;
+	}
+
+	if (pipe(pipefd)) {
+		ERROR("failed to create pipe");
+		return false;
+	}
+
+	pid = fork();
+	if (pid < 0) {
+		close(pipefd[0]);
+		close(pipefd[1]);
+		return false;
+	}
+
+	if (pid == 0) {
+		close(pipefd[0]);
+		// this never returns
+		do_restore(c, pipefd[1], directory, verbose);
+	}
+
+	close(pipefd[1]);
+
+	nread = read(pipefd[0], &status, sizeof(status));
+	close(pipefd[0]);
+	if (sizeof(status) != nread) {
+		ERROR("reading status from pipe failed");
+		goto err_wait;
+	}
+
+	// If the criu process was killed or exited nonzero, wait() for the
+	// handler, since the restore process died. Otherwise, we don't need to
+	// wait, since the child becomes the monitor process.
+	if (!WIFEXITED(status) || WEXITSTATUS(status))
+		goto err_wait;
+	return true;
+
+err_wait:
+	if (wait_for_pid(pid))
+		ERROR("restore process died");
+	return false;
+}
diff --git a/src/lxc/criu.h b/src/lxc/criu.h
index 9714d17..b7d241b 100644
--- a/src/lxc/criu.h
+++ b/src/lxc/criu.h
@@ -47,6 +47,9 @@ struct criu_opts {
 	/* Enable criu verbose mode? */
 	bool verbose;
 
+	/* (pre-)dump: a directory for the previous dump's images */
+	char *predump_dir;
+
 	/* dump: stop the container or not after dumping? */
 	bool stop;
 
@@ -61,8 +64,8 @@ void exec_criu(struct criu_opts *opts);
  * dump. */
 bool criu_ok(struct lxc_container *c);
 
-// do_restore never returns, the calling process is used as the
-// monitor process. do_restore calls exit() if it fails.
-void do_restore(struct lxc_container *c, int pipe, char *directory, bool verbose);
+bool pre_dump(struct lxc_container *c, char *directory, bool verbose, char *predump_dir);
+bool dump(struct lxc_container *c, char *directory, bool stop, bool verbose, char *predump_dir);
+bool restore(struct lxc_container *c, char *directory, bool verbose);
 
 #endif
diff --git a/src/lxc/lxccontainer.c b/src/lxc/lxccontainer.c
index 613e894..c8edc45 100644
--- a/src/lxc/lxccontainer.c
+++ b/src/lxc/lxccontainer.c
@@ -3992,112 +3992,69 @@ static bool do_lxcapi_detach_interface(struct lxc_container *c, const char *ifna
 
 WRAP_API_2(bool, lxcapi_detach_interface, const char *, const char *)
 
-static bool do_lxcapi_checkpoint(struct lxc_container *c, char *directory, bool stop, bool verbose)
+static int do_lxcapi_migrate(struct lxc_container *c, unsigned int cmd,
+			     struct migrate_opts *opts, unsigned int size)
 {
-	pid_t pid;
-	int status;
-	char path[PATH_MAX];
-
-	if (!criu_ok(c))
-		return false;
-
-	if (mkdir(directory, 0700) < 0 && errno != EEXIST)
-		return false;
-
-	status = snprintf(path, sizeof(path), "%s/inventory.img", directory);
-	if (status < 0 || status >= sizeof(path))
-		return false;
+	int ret;
 
-	if (access(path, F_OK) == 0) {
-		ERROR("please use a fresh directory for the dump directory\n");
-		return false;
+	/* If the caller has a bigger (newer) struct migrate_opts, let's make
+	 * sure that the stuff on the end is zero, i.e. that they didn't ask us
+	 * to do anything special.
+	 */
+	if (size > sizeof(*opts)) {
+		unsigned char *addr;
+		unsigned char *end;
+
+		addr = (void *)opts + sizeof(*opts);
+		end  = (void *)opts + size;
+		for (; addr < end; addr++) {
+			if (*addr) {
+				return -E2BIG;
+			}
+		}
 	}
 
-	pid = fork();
-	if (pid < 0)
-		return false;
-
-	if (pid == 0) {
-		struct criu_opts os;
+	switch (cmd) {
+	case MIGRATE_PRE_DUMP:
+		ret = !pre_dump(c, opts->directory, opts->verbose, opts->predump_dir);
+		break;
+	case MIGRATE_DUMP:
+		ret = !dump(c, opts->directory, opts->stop, opts->verbose, opts->predump_dir);
+		break;
+	case MIGRATE_RESTORE:
+		ret = !restore(c, opts->directory, opts->verbose);
+		break;
+	default:
+		ERROR("invalid migrate command %u", cmd);
+		ret = -EINVAL;
+	}
 
-		os.action = "dump";
-		os.directory = directory;
-		os.c = c;
-		os.stop = stop;
-		os.verbose = verbose;
+	return ret;
+}
 
-		/* exec_criu() returning is an error */
-		exec_criu(&os);
-		exit(1);
-	} else {
-		pid_t w = waitpid(pid, &status, 0);
-		if (w == -1) {
-			SYSERROR("waitpid");
-			return false;
-		}
+WRAP_API_3(int, lxcapi_migrate, unsigned int, struct migrate_opts *, unsigned int)
 
-		if (WIFEXITED(status)) {
-			return !WEXITSTATUS(status);
-		}
+static bool do_lxcapi_checkpoint(struct lxc_container *c, char *directory, bool stop, bool verbose)
+{
+	struct migrate_opts opts = {
+		.directory = directory,
+		.stop = stop,
+		.verbose = verbose,
+	};
 
-		return false;
-	}
+	return !do_lxcapi_migrate(c, MIGRATE_DUMP, &opts, sizeof(opts));
 }
 
 WRAP_API_3(bool, lxcapi_checkpoint, char *, bool, bool)
 
 static bool do_lxcapi_restore(struct lxc_container *c, char *directory, bool verbose)
 {
-	pid_t pid;
-	int status, nread;
-	int pipefd[2];
-
-	if (!criu_ok(c))
-		return false;
-
-	if (geteuid()) {
-		ERROR("Must be root to restore\n");
-		return false;
-	}
-
-	if (pipe(pipefd)) {
-		ERROR("failed to create pipe");
-		return false;
-	}
-
-	pid = fork();
-	if (pid < 0) {
-		close(pipefd[0]);
-		close(pipefd[1]);
-		return false;
-	}
-
-	if (pid == 0) {
-		close(pipefd[0]);
-		// this never returns
-		do_restore(c, pipefd[1], directory, verbose);
-	}
-
-	close(pipefd[1]);
-
-	nread = read(pipefd[0], &status, sizeof(status));
-	close(pipefd[0]);
-	if (sizeof(status) != nread) {
-		ERROR("reading status from pipe failed");
-		goto err_wait;
-	}
-
-	// If the criu process was killed or exited nonzero, wait() for the
-	// handler, since the restore process died. Otherwise, we don't need to
-	// wait, since the child becomes the monitor process.
-	if (!WIFEXITED(status) || WEXITSTATUS(status))
-		goto err_wait;
-	return true;
+	struct migrate_opts opts = {
+		.directory = directory,
+		.verbose = verbose,
+	};
 
-err_wait:
-	if (wait_for_pid(pid))
-		ERROR("restore process died");
-	return false;
+	return !do_lxcapi_migrate(c, MIGRATE_RESTORE, &opts, sizeof(opts));
 }
 
 WRAP_API_2(bool, lxcapi_restore, char *, bool)
@@ -4243,6 +4200,7 @@ struct lxc_container *lxc_container_new(const char *name, const char *configpath
 	c->detach_interface = lxcapi_detach_interface;
 	c->checkpoint = lxcapi_checkpoint;
 	c->restore = lxcapi_restore;
+	c->migrate = lxcapi_migrate;
 
 	return c;
 
diff --git a/src/lxc/lxccontainer.h b/src/lxc/lxccontainer.h
index 38e1a8e..e909d81 100644
--- a/src/lxc/lxccontainer.h
+++ b/src/lxc/lxccontainer.h
@@ -49,6 +49,8 @@ struct lxc_snapshot;
 
 struct lxc_lock;
 
+struct migrate_opts;
+
 /*!
  * An LXC container.
  *
@@ -812,6 +814,16 @@ struct lxc_container {
 	bool (*snapshot_destroy_all)(struct lxc_container *c);
 
 	/* Post LXC-1.1 additions */
+	/*!
+	 * \brief An API call to perform various migration operations
+	 *
+	 * \param cmd One of the MIGRATE_ contstants.
+	 * \param opts A migrate_opts struct filled with relevant options.
+	 * \param size The size of the migrate_opts struct, i.e. sizeof(struct migrate_opts).
+	 *
+	 * \return \c 0 on success, nonzero on failure.
+	 */
+	int (*migrate)(struct lxc_container *c, unsigned int cmd, struct migrate_opts *opts, unsigned int size);
 };
 
 /*!
@@ -849,6 +861,27 @@ struct bdev_specs {
 };
 
 /*!
+ * \brief Commands for the migrate API call.
+ */
+enum {
+	MIGRATE_PRE_DUMP,
+	MIGRATE_DUMP,
+	MIGRATE_RESTORE,
+};
+
+/*!
+ * \brief Options for the migrate API call.
+ */
+struct migrate_opts {
+	/* new members should be added at the end */
+	char *directory;
+	bool verbose;
+
+	bool stop; /* stop the container after dump? */
+	char *predump_dir; /* relative to directory above */
+};
+
+/*!
  * \brief Create a new container.
  *
  * \param name Name to use for container.
-- 
2.5.0

