[10/10] cr-service: add support for configuration files in RPC mode

Submitted by Adrian Reber on Aug. 1, 2018, 4:31 p.m.

Details

Message ID 20180801163122.19872-11-adrian@lisas.de
State Accepted
Series "Series without cover letter"
Headers show

Commit Message

Adrian Reber Aug. 1, 2018, 4:31 p.m.
From: Adrian Reber <areber@redhat.com>

With this commit it is possible to specify a configuration file via
RPC. In python this would look like this:

 req.opts.config_file = 'path/to/config_file'

With this commit CRIU's configuration file handling works like this:

 * apply_config(global_conf)
 * apply_config(user_conf)
 * apply_config(environment variable)
 * apply_config(config file via CLI)
 * apply_rpc_options() or apply_cli_options()
 * apply_config(rpc_conf) (only for RPC)

This is at least (probably) the third iteration of the RPC configuration
file code and it still is complicated.

Most CRIU options are correctly used by just writing the new values to
the corresponding fields of the opts structure. For the RPC case there
are, however, a few options (output, work_dir, imgs_dir) which need
special handling.

So the RPC configuration file is parsed twice. First time to get output,
work_dir and imgs_dir. Once those are read and correctly used, the RPC
code overwrites all options again by values set by the RPC interface. At
the end the RPC configuration file is read a second time and finally
overwrites the values set via RPC.

Signed-off-by: Adrian Reber <areber@redhat.com>
---
 criu/config.c             | 18 ++++----
 criu/cr-service.c         | 92 ++++++++++++++++++++++++++++++++++++---
 criu/crtools.c            |  3 +-
 criu/include/cr_options.h | 12 ++++-
 images/rpc.proto          |  1 +
 5 files changed, 108 insertions(+), 18 deletions(-)

Patch hide | download patch | download mbox

diff --git a/criu/config.c b/criu/config.c
index c2729a92d..84d6aad14 100644
--- a/criu/config.c
+++ b/criu/config.c
@@ -149,12 +149,6 @@  static char ** parse_config(char *filepath)
 	return configuration;
 }
 
-#define PARSING_GLOBAL_CONF	1
-#define PARSING_USER_CONF	2
-#define PARSING_ENV_CONF	3
-#define PARSING_CMDLINE_CONF	4
-#define PARSING_ARGV		5
-
 static int next_config(char **argv, char ***_argv, bool no_default_config,
 		int state, char *cfg_file)
 {
@@ -162,7 +156,7 @@  static int next_config(char **argv, char ***_argv, bool no_default_config,
 	char *home_dir = NULL;
 	char *cfg_from_env = NULL;
 
-	if (state > PARSING_ARGV)
+	if (state >= PARSING_LAST)
 		return 0;
 
 	switch(state) {
@@ -197,6 +191,11 @@  static int next_config(char **argv, char ***_argv, bool no_default_config,
 		case PARSING_ARGV:
 			*_argv = argv;
 			break;
+		case PARSING_RPC_CONF:
+			if (!rpc_cfg_file)
+				break;
+			*_argv = parse_config(rpc_cfg_file);
+			break;
 		default:
 			break;
 	}
@@ -403,9 +402,9 @@  static int parse_join_ns(const char *ptr)
  * correct, '1' if something failed and '2' if the CRIU help text should
  * be displayed.
  */
-int parse_options(int argc, char **argv, bool *usage_error, bool *has_exec_cmd)
+int parse_options(int argc, char **argv, bool *usage_error,
+		bool *has_exec_cmd, int state)
 {
-	int state = PARSING_GLOBAL_CONF;
 	int ret;
 	int opt = -1;
 	int idx;
@@ -517,7 +516,6 @@  int parse_options(int argc, char **argv, bool *usage_error, bool *has_exec_cmd)
 					free(_argv[i]);
 				}
 				free(_argv);
-				;
 			}
 			/* This needs to be reset for a new getopt() run */
 			_argc = 0;
diff --git a/criu/cr-service.c b/criu/cr-service.c
index c7c28857e..dc2ff05f7 100644
--- a/criu/cr-service.c
+++ b/criu/cr-service.c
@@ -242,7 +242,11 @@  static int setup_opts_from_req(int sk, CriuOpts *req)
 	char images_dir_path[PATH_MAX];
 	char work_dir_path[PATH_MAX];
 	char status_fd[PATH_MAX];
+	bool output_changed_by_rpc_conf = false;
+	bool work_changed_by_rpc_conf = false;
+	bool imgs_changed_by_rpc_conf = false;
 	int i;
+	bool dummy = false;
 
 	if (getsockopt(sk, SOL_SOCKET, SO_PEERCRED, &ids, &ids_len)) {
 		pr_perror("Can't get socket options");
@@ -257,8 +261,68 @@  static int setup_opts_from_req(int sk, CriuOpts *req)
 	BUG_ON(st.st_ino == -1);
 	service_sk_ino = st.st_ino;
 
-	/* open images_dir */
-	sprintf(images_dir_path, "/proc/%d/fd/%d", ids.pid, req->images_dir_fd);
+	/*
+	 * Evaluate an additional configuration file if specified.
+	 * This needs to happen twice, because it is needed early to detect
+	 * things like work_dir, imgs_dir and logfile. The second parsing
+	 * of the optional RPC configuration file happens at the end and
+	 * overwrites all options set via RPC.
+	 */
+	if (req->config_file) {
+		char *tmp_output = NULL;
+		char *tmp_work = NULL;
+		char *tmp_imgs = NULL;
+
+		if (opts.output)
+			tmp_output = xstrdup(opts.output);
+		if (opts.work_dir)
+			tmp_work = xstrdup(opts.work_dir);
+		if (opts.imgs_dir)
+			tmp_imgs = xstrdup(opts.imgs_dir);
+		xfree(opts.output);
+		xfree(opts.work_dir);
+		xfree(opts.imgs_dir);
+		opts.output = NULL;
+		opts.work_dir = NULL;
+		opts.imgs_dir = NULL;
+		rpc_cfg_file = req->config_file;
+		i = parse_options(0, NULL, &dummy, &dummy, PARSING_RPC_CONF);
+		pr_warn("parse_options returns %d\n", i);
+		if (i) {
+			xfree(tmp_output);
+			xfree(tmp_work);
+			xfree(tmp_imgs);
+			goto err;
+		}
+		if (tmp_output && opts.output && !strncmp(tmp_output, opts.output, PATH_MAX))
+			output_changed_by_rpc_conf = true;
+		if (tmp_work && opts.work_dir && !strncmp(tmp_work, opts.work_dir, PATH_MAX))
+			work_changed_by_rpc_conf = true;
+		if (tmp_imgs && opts.imgs_dir && !strncmp(tmp_imgs, opts.imgs_dir, PATH_MAX))
+			imgs_changed_by_rpc_conf = true;
+		xfree(tmp_output);
+		xfree(tmp_work);
+		xfree(tmp_imgs);
+	}
+
+	/*
+	 * open images_dir
+	 * This assumes that if opts.imgs_dir is set we have a value
+	 * from the configuration file parser. The test to see that
+	 * imgs_changed_by_rpc_conf is true is used to make sure the value
+	 * is not the same as from one of the other configuration files.
+	 * The idea is that only the RPC configuration file is able to
+	 * overwrite RPC settings:
+	 *  * apply_config(global_conf)
+	 *  * apply_config(user_conf)
+	 *  * apply_config(environment variable)
+	 *  * apply_rpc_options()
+	 *  * apply_config(rpc_conf)
+	 */
+	if (opts.imgs_dir && imgs_changed_by_rpc_conf)
+		strncpy(images_dir_path, opts.imgs_dir, PATH_MAX);
+	else
+		sprintf(images_dir_path, "/proc/%d/fd/%d", ids.pid, req->images_dir_fd);
 
 	if (req->parent_img)
 		SET_CHAR_OPTS(img_parent, req->parent_img);
@@ -275,7 +339,9 @@  static int setup_opts_from_req(int sk, CriuOpts *req)
 	}
 
 	/* chdir to work dir */
-	if (req->has_work_dir_fd)
+	if (opts.work_dir && work_changed_by_rpc_conf)
+		strncpy(work_dir_path, opts.work_dir, PATH_MAX);
+	else if (req->has_work_dir_fd)
 		sprintf(work_dir_path, "/proc/%d/fd/%d", ids.pid, req->work_dir_fd);
 	else
 		strcpy(work_dir_path, images_dir_path);
@@ -286,15 +352,16 @@  static int setup_opts_from_req(int sk, CriuOpts *req)
 	}
 
 	/* initiate log file in work dir */
-	if (req->log_file) {
+	if (req->log_file && !(opts.output  && output_changed_by_rpc_conf)) {
 		if (strchr(req->log_file, '/')) {
 			pr_perror("No subdirs are allowed in log_file name");
 			goto err;
 		}
 
 		SET_CHAR_OPTS(output, req->log_file);
-	} else
+	} else if (!opts.output) {
 		SET_CHAR_OPTS(output, DEFAULT_LOG_FILENAME);
+	}
 
 	log_set_loglevel(req->log_level);
 	if (log_init(opts.output) == -1) {
@@ -381,7 +448,10 @@  static int setup_opts_from_req(int sk, CriuOpts *req)
 
 		if (!opts.lazy_pages) {
 			opts.use_page_server = true;
-			SET_CHAR_OPTS(addr, req->ps->address);
+			if (req->ps->address)
+				SET_CHAR_OPTS(addr, req->ps->address);
+			else
+				opts.addr = NULL;
 
 			if (req->ps->has_fd) {
 				if (!opts.swrk_restore)
@@ -539,6 +609,16 @@  static int setup_opts_from_req(int sk, CriuOpts *req)
 	if (check_namespace_opts())
 		goto err;
 
+	/* Evaluate additional configuration file a second time to overwrite
+	 * all RPC settings. */
+	if (req->config_file) {
+		rpc_cfg_file = req->config_file;
+		i = parse_options(0, NULL, &dummy, &dummy, PARSING_RPC_CONF);
+		if (i)
+			goto err;
+	}
+	log_set_loglevel(opts.log_level);
+
 	return 0;
 
 err:
diff --git a/criu/crtools.c b/criu/crtools.c
index 8b9327d19..3122ac6c8 100644
--- a/criu/crtools.c
+++ b/criu/crtools.c
@@ -102,6 +102,7 @@  int main(int argc, char *argv[], char *envp[])
 	bool usage_error = true;
 	bool has_exec_cmd = false;
 	bool has_sub_command;
+	int state = PARSING_GLOBAL_CONF;
 
 	BUILD_BUG_ON(CTL_32 != SYSCTL_TYPE__CTL_32);
 	BUILD_BUG_ON(__CTL_STR != SYSCTL_TYPE__CTL_STR);
@@ -121,7 +122,7 @@  int main(int argc, char *argv[], char *envp[])
 	init_opts();
 
 
-	ret = parse_options(argc, argv, &usage_error, &has_exec_cmd);
+	ret = parse_options(argc, argv, &usage_error, &has_exec_cmd, state);
 
 	if (ret == 1)
 		return 1;
diff --git a/criu/include/cr_options.h b/criu/include/cr_options.h
index 95dc5ade5..44588fce7 100644
--- a/criu/include/cr_options.h
+++ b/criu/include/cr_options.h
@@ -6,6 +6,15 @@ 
 #include "common/config.h"
 #include "common/list.h"
 
+/* Configuration and CLI parsing order defines */
+#define PARSING_GLOBAL_CONF	1
+#define PARSING_USER_CONF	2
+#define PARSING_ENV_CONF	3
+#define PARSING_CMDLINE_CONF	4
+#define PARSING_ARGV		5
+#define PARSING_RPC_CONF	6
+#define PARSING_LAST		7
+
 #define SET_CHAR_OPTS(__dest, __src) \
 	do { \
 		free(opts.__dest); \
@@ -133,8 +142,9 @@  struct cr_options {
 };
 
 extern struct cr_options opts;
+char *rpc_cfg_file;
 
-extern int parse_options(int argc, char **argv, bool *usage_error, bool *has_exec_cmd);
+extern int parse_options(int argc, char **argv, bool *usage_error, bool *has_exec_cmd, int state);
 extern void init_opts();
 
 #endif /* __CR_OPTIONS_H__ */
diff --git a/images/rpc.proto b/images/rpc.proto
index 71f47d594..abf2f5d79 100644
--- a/images/rpc.proto
+++ b/images/rpc.proto
@@ -111,6 +111,7 @@  message criu_opts {
 	optional bool			lazy_pages		= 48;
 	optional int32			status_fd		= 49;
 	optional bool			orphan_pts_master	= 50;
+	optional string			config_file		= 51;
 }
 
 message criu_dump_resp {