[2/4,v4] Add support for configuration files

Submitted by Veronika Kabatova on June 16, 2017, 3:17 p.m.

Details

Message ID 1497626241-21695-3-git-send-email-vkabatov@redhat.com
State New
Series "Implementation of configuration files"
Headers show

Commit Message

Veronika Kabatova June 16, 2017, 3:17 p.m.
From: Veronika Kabatova <vkabatov@redhat.com>

Implementation changes for usage of simple configuration files. Before
parsing the command line options, either default configuration files
(/etc/criu/default.conf, $HOME/.criu/default.conf; in this order) are
parsed, or a specific config file passed by the user. Two new options are
introduced: "--config FILEPATH" option allows users to specify a single
configuration file they want to use; and "--no-default-config" option to
forbid the parsing of default configuration files. Both options are to be
passed only via the command line.

Usage of configuration files is not mandatory to keep backwards
compatibility. The implementation of this feature tries to be compatible
with command line usage -- the user should get the same results whether
he passes the options (in the right order of parsing) on command line or
writes them in config files. This allows the user to:

1) Override boolean options if needed
2) Specify partial configuration for options that are possible to pass
   several times (e.g. "--external"), and pass the rest of the options
   based on process runtime by command line

Configuration file syntax allows comments marked with '#' sign, the rest
of the line after '#' is ignored. The user can use one option per line
(with argument supplied on the same line if needed, divided with whitespace
characters), the options are the same as long options (without the "--"
prefix used on command line).

Configuration file example (syntax purposes only, doesn't make sense):

$ cat ~/.criu/default.conf
tcp-established
work-dir /home/<USERNAME>/criu/work_directory
extra # inline comment
no-restore-sibling
tree	111111

Signed-off-by: Veronika Kabatova <vkabatov@redhat.com>
---
 criu/Makefile  |   7 ++
 criu/crtools.c | 227 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 226 insertions(+), 8 deletions(-)

Patch hide | download patch | download mbox

diff --git a/criu/Makefile b/criu/Makefile
index b2c6633..b556a83 100644
--- a/criu/Makefile
+++ b/criu/Makefile
@@ -15,6 +15,12 @@  ifeq ($(filter clean mrproper,$(MAKECMDGOALS)),)
 endif
 
 #
+# Configuration file paths
+CONFIG-DEFINES		+= -DGLOBAL_CONFIG_DIR='"/etc/criu/"'
+CONFIG-DEFINES		+= -DDEFAULT_CONFIG_FILENAME='"default.conf"'
+CONFIG-DEFINES		+= -DUSER_CONFIG_DIR='".criu/"'
+
+#
 # General flags.
 ccflags-y		+= -fno-strict-aliasing
 ccflags-y		+= -iquote criu/include
@@ -24,6 +30,7 @@  ccflags-y		+= -iquote $(ARCH_DIR)/include
 ccflags-y		+= -iquote .
 ccflags-y		+= -I/usr/include/libnl3
 ccflags-y		+= $(COMPEL_UAPI_INCLUDES)
+ccflags-y		+= $(CONFIG-DEFINES)
 
 export ccflags-y
 
diff --git a/criu/crtools.c b/criu/crtools.c
index f8c97a3..34850b3 100644
--- a/criu/crtools.c
+++ b/criu/crtools.c
@@ -59,6 +59,8 @@ 
 #include "../soccr/soccr.h"
 
 struct cr_options opts;
+char **global_conf = NULL;
+char **user_conf = NULL;
 
 void init_opts(void)
 {
@@ -226,21 +228,198 @@  static void rlimit_unlimit_nofile_self(void)
 		pr_debug("rlimit: RLIMIT_NOFILE unlimited for self\n");
 }
 
-int main(int argc, char *argv[], char *envp[])
+#define HELP_PASSED			1
+#define DEFAULT_CONFIGS_FORBIDDEN	2
+int passed_help_or_defaults_forbidden(int argc, char *argv[])
 {
+	/*
+	 * Check for --help / -h on commandline before parsing, otherwise
+	 * the help message won't be displayed if there is an error in
+	 * configuration file syntax. Checks are kept in parser in case of
+	 * option being put in the configuration file itself.
+	 *
+	 * Check also whether default configfiles are forbidden to lower
+	 * number of argv iterations, but checks for help have higher priority.
+	 */
+	int i, ret = 0;
+	for (i = 0; i < argc; i++) {
+		if ((!strcmp(argv[i], "--help")) || (!strcmp(argv[i], "-h")))
+			return HELP_PASSED;
+		if (!strcmp(argv[i], "--no-default-config"))
+			ret = DEFAULT_CONFIGS_FORBIDDEN;
+	}
+	return ret;
+}
 
-#define BOOL_OPT(OPT_NAME, SAVE_TO) \
-		{OPT_NAME, no_argument, SAVE_TO, true},\
-		{"no-" OPT_NAME, no_argument, SAVE_TO, false}
+char * specific_config_passed(char *args[], int argc)
+{
+	int i;
+	for (i = 0; i < argc; i++) {
+		if (!strcmp(args[i], "--config")) {
+			/* getopt takes next string as required argument automatically */
+			return args[i + 1];
+		} else if (strstr(args[i], "--config=") != NULL) {
+			return args[i] + strlen("--config=");
+		}
+	}
+	return NULL;
+}
+
+static int count_elements(char **to_count)
+{
+	int count = 0;
+	if (to_count != NULL)
+		while (to_count[count] != NULL)
+			count++;
+	return count;
+}
+
+char ** parse_config(char *filepath)
+{
+#define DEFAULT_CONFIG_SIZE	10
+	FILE* configfile = fopen(filepath, "r");
+	int config_size = DEFAULT_CONFIG_SIZE;
+	int i = 1, len = 0, offset;
+	size_t limit = 0;
+	bool was_newline;
+	char *tmp_string, *line = NULL, *quoted, *quotedptr;
+	char **configuration, **tmp_conf;
+
+	if (!configfile) {
+		return NULL;
+	}
+	configuration = xmalloc(config_size * sizeof(char *));
+	if (configuration == NULL) {
+		fclose(configfile);
+		exit(1);
+	}
+	/*
+	 * Initialize first element, getopt ignores it.
+	 */
+	configuration[0] = "criu";
+
+	while ((len = getline(&line, &limit, configfile)) != -1) {
+		offset = 0;
+		was_newline = true;
+		if (i >= config_size - 1) {
+			config_size *= 2;
+			tmp_conf = xrealloc(configuration, config_size * sizeof(char *));
+			if (tmp_conf == NULL) {
+				fclose(configfile);
+				exit(1);
+			}
+			configuration = tmp_conf;
+		}
+		while (sscanf(line + offset, "%m[^ \t\n]s", &configuration[i]) == 1) {
+			if (configuration[i][0] == '#') {
+				if (sscanf(line, "%*[^\n]") != 0) {
+					pr_err("Error while reading configuration file %s\n", filepath);
+					fclose(configfile);
+					exit(1);
+				}
+				configuration[i] = NULL;
+				break;
+			}
+			if ((configuration[i][0] == '\"') && (strchr(line + offset + 1, '"'))) {
+				/*
+				 * Handle empty strings which strtok ignores
+				 */
+				if (!strcmp(configuration[i], "\"\"")) {
+					configuration[i] = "";
+					offset += strlen("\"\"");
+				} else if ((configuration[i] = strtok_r(line + offset, "\"", &quotedptr))) {
+					/*
+					 * Handle escaping of quotes in quoted string
+					 */
+					while (configuration[i][strlen(configuration[i]) - 1] == '\\') {
+						offset++;
+						len = strlen(configuration[i]);
+						configuration[i][len - 1] = '"';
+						if (*quotedptr == '"') {
+							quotedptr++;
+							break;
+						}
+						quoted = strtok_r(NULL, "\"", &quotedptr);
+						tmp_string = xmalloc(len + strlen(quoted) + 1);
+						if (tmp_string == NULL) {
+							fclose(configfile);
+							exit(1);
+						}
+						memmove(tmp_string, configuration[i], len);
+						memmove(tmp_string + len, quoted, strlen(quoted) + 1);
+						configuration[i] = tmp_string;
+					}
+					offset += 2;
+				}
+			}
+			offset += strlen(configuration[i]);
+			if (was_newline) {
+				was_newline = false;
+				len = strlen(configuration[i]);
+				tmp_string = xrealloc(configuration[i], len + strlen("--") + 1);
+				if (tmp_string == NULL) {
+					fclose(configfile);
+					exit(1);
+				}
+				memmove(tmp_string + strlen("--"), tmp_string, len + 1);
+				memmove(tmp_string, "--", strlen("--"));
+				configuration[i] = tmp_string;
+			}
+			i++;
+			while ((isspace(*(line + offset)) && (*(line + offset) != '\n'))) offset++;
+		}
+		line = NULL;
+	}
+
+	fclose(configfile);
+	return configuration;
+}
+
+static void init_configuration(int argc, char *argv[], int defaults_forbidden)
+{
+	char *specific_conf = specific_config_passed(argv, argc);
+	char local_filepath[PATH_MAX + 1];
+	char *home_dir = getenv("HOME");
+
+	if ((specific_conf == NULL) && (!defaults_forbidden)) {
+		global_conf = parse_config(GLOBAL_CONFIG_DIR DEFAULT_CONFIG_FILENAME);
+		if (!home_dir) {
+			pr_info("Unable to get $HOME directory, local configuration file will not be used.");
+		} else {
+			snprintf(local_filepath, PATH_MAX, "%s/%s%s",
+					home_dir, USER_CONFIG_DIR, DEFAULT_CONFIG_FILENAME);
+			user_conf = parse_config(local_filepath);
+		}
+	} else if (specific_conf != NULL) {
+		global_conf = parse_config(specific_conf);
+		if (global_conf == NULL) {
+			pr_err("Can't access configuration file %s.\n", specific_conf);
+			exit(1);
+		}
+	}
+}
+
+int main(int argc, char *argv[], char *envp[])
+{
+#define PARSING_GLOBAL_CONF	1
+#define PARSING_USER_CONF	2
+#define PARSING_ARGV		3
 
 	pid_t pid = 0, tree_id = 0;
 	int ret = -1;
 	bool usage_error = true;
 	bool has_exec_cmd = false;
 	bool has_sub_command;
-	int opt, idx;
+	int opt = 0, idx, help_or_configs;
+	int first_count = 1, second_count = 1;
+	int state = PARSING_GLOBAL_CONF;
 	int log_level = DEFAULT_LOGLEVEL;
 	char *imgs_dir = ".";
+
+#define BOOL_OPT(OPT_NAME, SAVE_TO) \
+		{OPT_NAME, no_argument, SAVE_TO, true},\
+		{"no-" OPT_NAME, no_argument, SAVE_TO, false}
+
 	static const char short_opts[] = "dSsRf:F:t:p:hcD:o:v::x::Vr:jJ:lW:L:M:";
 	static struct option long_opts[] = {
 		{ "tree",			required_argument,	0, 't'	},
@@ -313,6 +492,8 @@  int main(int argc, char *argv[], char *envp[])
 		{ "status-fd",			required_argument,	0, 1088 },
 		BOOL_OPT("remote", &opts.remote),
 		{ "verbosity",			optional_argument,	0, 'v'	},
+		{ "config",			required_argument,	0, 1089},
+		{ "no-default-config",		no_argument,		0, 1090},
 		{ },
 	};
 
@@ -367,11 +548,38 @@  int main(int argc, char *argv[], char *envp[])
 		return cr_service_work(atoi(argv[2]));
 	}
 
+	help_or_configs = passed_help_or_defaults_forbidden(argc, argv);
+	if (help_or_configs == 1) {
+		usage_error = false;
+		goto usage;
+	}
+	init_configuration(argc, argv, (help_or_configs == DEFAULT_CONFIGS_FORBIDDEN));
+	if (global_conf != NULL) first_count = count_elements(global_conf);
+	if (user_conf != NULL) second_count = count_elements(user_conf);
+
 	while (1) {
 		idx = -1;
-		opt = getopt_long(argc, argv, short_opts, long_opts, &idx);
-		if (opt == -1)
+
+		switch (state) {
+		case PARSING_GLOBAL_CONF:
+			opt = getopt_long(first_count, global_conf, short_opts, long_opts, &idx);
+			break;
+		case PARSING_USER_CONF:
+			opt = getopt_long(second_count, user_conf, short_opts, long_opts, &idx);
 			break;
+		case PARSING_ARGV:
+			opt = getopt_long(argc, argv, short_opts, long_opts, &idx);
+			break;
+		}
+		if (opt == -1) {
+			if (state < PARSING_ARGV) {
+				state++;
+				optind = 0;
+				continue;
+			} else {
+				break;
+			}
+		}
 		if (!opt)
 			continue;
 
@@ -465,7 +673,6 @@  int main(int argc, char *argv[], char *envp[])
 		case 1049:
 			if (add_script(optarg))
 				return 1;
-
 			break;
 		case 1051:
 			opts.addr = optarg;
@@ -612,6 +819,10 @@  int main(int argc, char *argv[], char *envp[])
 				return 1;
 			}
 			break;
+		case 1089:
+			break;
+		case 1090:
+			break;
 		case 'V':
 			pr_msg("Version: %s\n", CRIU_VERSION);
 			if (strcmp(CRIU_GITID, "0"))