mirror of
https://github.com/checkpoint-restore/criu
synced 2025-08-22 01:51:51 +00:00
Brought to you by codespell -w (using codespell v2.1.0). Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
1125 lines
28 KiB
C
1125 lines
28 KiB
C
#include <ctype.h>
|
|
#include <getopt.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
|
|
#include "log.h"
|
|
#include "common/list.h"
|
|
|
|
#include "action-scripts.h"
|
|
#include "cgroup.h"
|
|
#include "cgroup-props.h"
|
|
#include "common/bug.h"
|
|
#include "cpu.h"
|
|
#include "crtools.h"
|
|
#include "cr_options.h"
|
|
#include "filesystems.h"
|
|
#include "file-lock.h"
|
|
#include "irmap.h"
|
|
#include "mount.h"
|
|
#include "mount-v2.h"
|
|
#include "namespaces.h"
|
|
#include "net.h"
|
|
#include "sk-inet.h"
|
|
#include "sockets.h"
|
|
#include "tty.h"
|
|
#include "version.h"
|
|
|
|
#include "common/xmalloc.h"
|
|
|
|
struct cr_options opts;
|
|
char *rpc_cfg_file;
|
|
|
|
static int count_elements(char **to_count)
|
|
{
|
|
int count = 0;
|
|
if (to_count != NULL)
|
|
while (to_count[count] != NULL)
|
|
count++;
|
|
return count;
|
|
}
|
|
|
|
/* Parse one statement in configuration file */
|
|
int parse_statement(int i, char *line, char **configuration)
|
|
{
|
|
cleanup_free char *input = NULL;
|
|
int offset = 0, len = 0;
|
|
char *tmp_string;
|
|
|
|
/*
|
|
* A line from the configuration file can be:
|
|
* - empty
|
|
* - a boolean option (tcp-close)
|
|
* - an option with one parameter (verbosity 4)
|
|
* - a parameter can be in quotes (lsm-profile "selinux:something")
|
|
* - a parameter can contain escaped quotes
|
|
*
|
|
* Whenever a '#' is found we ignore everything after as a comment.
|
|
*
|
|
* This function adds none, one (boolean option) or two entries
|
|
* in **configuration and returns i + (the number of entries).
|
|
*/
|
|
|
|
if (strlen(line) == 0)
|
|
return i;
|
|
|
|
/* Ignore leading white-space */
|
|
while ((isspace(*(line + offset)) && (*(line + offset) != '\n')))
|
|
offset++;
|
|
|
|
/* Ignore empty line */
|
|
if (line[offset] == '\n')
|
|
return i;
|
|
|
|
/* Ignore line starting with a comment */
|
|
if (line[offset] == '#')
|
|
return i;
|
|
|
|
input = xstrdup(line + offset);
|
|
if (unlikely(!input))
|
|
return -1;
|
|
|
|
offset = 0;
|
|
|
|
/* Remove trailing '\n' */
|
|
if ((tmp_string = strchr(input, '\n')))
|
|
tmp_string[0] = 0;
|
|
|
|
if ((tmp_string = strchr(input, ' ')) || (tmp_string = strchr(input, '\t'))) {
|
|
configuration[i] = xzalloc(tmp_string - input + strlen("--") + 1);
|
|
if (unlikely(!configuration[i]))
|
|
return -1;
|
|
memcpy(configuration[i], "--", strlen("--"));
|
|
memcpy(configuration[i] + strlen("--"), input, tmp_string - input);
|
|
configuration[i][tmp_string - input + strlen("--")] = 0;
|
|
/* Go to the next character */
|
|
offset += tmp_string - input + 1;
|
|
i++;
|
|
} else {
|
|
if (unlikely(asprintf(&configuration[i], "--%s", input) == -1))
|
|
return -1;
|
|
return i + 1;
|
|
}
|
|
|
|
while ((isspace(*(input + offset))))
|
|
offset++;
|
|
|
|
/* Check if the next token is a comment */
|
|
if (input[offset] == '#')
|
|
return i;
|
|
|
|
if (input[offset] == '"') {
|
|
bool found_second_quote = false;
|
|
char *quote_start;
|
|
int quote_offset;
|
|
|
|
/* Move by one to skip the leading quote. */
|
|
offset++;
|
|
quote_start = input + offset;
|
|
quote_offset = offset;
|
|
|
|
if (input[offset] == 0) {
|
|
/* The value for the parameter was a single quote, this is not supported. */
|
|
xfree(configuration[i - 1]);
|
|
pr_err("Unsupported configuration file format. Please consult man page criu(8)\n");
|
|
return -1;
|
|
}
|
|
|
|
if (input[offset] == '"') {
|
|
/* We got "" as value */
|
|
configuration[i] = xstrdup("");
|
|
if (unlikely(!configuration[i])) {
|
|
xfree(configuration[i - 1]);
|
|
return -1;
|
|
}
|
|
offset = 0;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* If it starts with a quote everything until the
|
|
* next unescaped quote needs to be looked at.
|
|
*/
|
|
while ((tmp_string = strchr(input + quote_offset + 1, '"'))) {
|
|
quote_offset = tmp_string - input;
|
|
/* Check if it is escaped */
|
|
if (*(tmp_string - 1) == '\\')
|
|
continue;
|
|
|
|
/* Not escaped. That is the end of the quoted string. */
|
|
found_second_quote = true;
|
|
configuration[i] = xzalloc(quote_offset - offset + 1);
|
|
if (unlikely(!configuration[i])) {
|
|
xfree(configuration[i - 1]);
|
|
return -1;
|
|
}
|
|
memcpy(configuration[i], quote_start, quote_offset - offset);
|
|
configuration[i][quote_offset - offset] = 0;
|
|
/* We skipped one additional quote */
|
|
offset++;
|
|
/* Check for excessive parameters on the original line. */
|
|
tmp_string++;
|
|
if (tmp_string != 0 && strchr(tmp_string, ' ')) {
|
|
int j;
|
|
len = strlen(tmp_string);
|
|
for (j = 0; j < len - 1; j++) {
|
|
if (tmp_string[j] == '#')
|
|
break;
|
|
if (!isspace(tmp_string[j])) {
|
|
pr_err("Unsupported configuration file format. Please consult man page criu(8)\n");
|
|
xfree(configuration[i - 1]);
|
|
xfree(configuration[i]);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
if (!found_second_quote) {
|
|
pr_err("Unsupported configuration file format. Please consult man page criu(8)\n");
|
|
xfree(configuration[i - 1]);
|
|
return -1;
|
|
}
|
|
} else {
|
|
/* Does not start with a quote. */
|
|
if (unlikely(asprintf(&configuration[i], "%s", input + offset) == -1)) {
|
|
xfree(configuration[i - 1]);
|
|
return -1;
|
|
}
|
|
|
|
if ((tmp_string = strchr(input + offset, ' ')))
|
|
offset = tmp_string - (input + offset);
|
|
else
|
|
offset = 0;
|
|
}
|
|
|
|
len = strlen(configuration[i]);
|
|
if (strstr(configuration[i], "\\\"")) {
|
|
/* We found an escaped quote. Skip the backslash. */
|
|
cleanup_free char *tmp = NULL;
|
|
int skipped = 0;
|
|
int start = 0;
|
|
int dest = 0;
|
|
int j;
|
|
|
|
tmp = xzalloc(len);
|
|
if (tmp == NULL)
|
|
return -1;
|
|
|
|
for (j = start; j < len; j++) {
|
|
if (configuration[i][j] == '\\' && j + 1 < len && configuration[i][j + 1] == '"') {
|
|
skipped++;
|
|
continue;
|
|
}
|
|
tmp[dest++] = configuration[i][j];
|
|
}
|
|
memcpy(configuration[i], tmp, strlen(tmp));
|
|
configuration[i][strlen(tmp)] = 0;
|
|
|
|
/* Account for skipped backslashes. */
|
|
offset += skipped + 1;
|
|
len -= skipped;
|
|
}
|
|
|
|
out:
|
|
/* Remove potential comments at the end */
|
|
if ((tmp_string = strstr(configuration[i], "#")) || (tmp_string = strstr(configuration[i], " #")))
|
|
tmp_string[0] = 0;
|
|
|
|
/* Check for unsupported configuration file entries */
|
|
if (strchr(configuration[i] + offset, ' ')) {
|
|
int j;
|
|
len = strlen(configuration[i] + offset);
|
|
for (j = 0; j < len - 1; j++) {
|
|
if (!isspace(configuration[i][offset + j])) {
|
|
pr_err("Unsupported configuration file format. Please consult man page criu(8)\n");
|
|
xfree(configuration[i - 1]);
|
|
xfree(configuration[i]);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((tmp_string = strchr(configuration[i] + offset, ' ')))
|
|
tmp_string[0] = 0;
|
|
|
|
return i + 1;
|
|
}
|
|
|
|
/* Parse a configuration file */
|
|
static char **parse_config(char *filepath)
|
|
{
|
|
#define DEFAULT_CONFIG_SIZE 10
|
|
FILE *configfile = fopen(filepath, "r");
|
|
int config_size = DEFAULT_CONFIG_SIZE;
|
|
int i = 1;
|
|
size_t line_size = 0;
|
|
char *line = NULL;
|
|
char **configuration;
|
|
|
|
if (!configfile)
|
|
return NULL;
|
|
|
|
pr_debug("Parsing config file %s\n", filepath);
|
|
|
|
configuration = xmalloc(config_size * sizeof(char *));
|
|
if (configuration == NULL) {
|
|
fclose(configfile);
|
|
exit(1);
|
|
}
|
|
/*
|
|
* Initialize first element, getopt ignores it.
|
|
*/
|
|
configuration[0] = "criu";
|
|
|
|
while (getline(&line, &line_size, configfile) != -1) {
|
|
int spaces = 1;
|
|
int j;
|
|
/*
|
|
* The statement parser 'parse_statement()' needs as many
|
|
* elements in 'configuration' as spaces + 1, because it splits
|
|
* each line at a space to return a result that can used as
|
|
* input for getopt. So, let's count spaces to determine the
|
|
* memory requirements.
|
|
*/
|
|
for (j = 0; j < strlen(line); j++)
|
|
if (line[j] == ' ')
|
|
spaces++;
|
|
|
|
/* Extend configuration buffer if necessary */
|
|
if (i + spaces >= config_size - 1) {
|
|
config_size += spaces;
|
|
configuration = xrealloc(configuration, config_size * sizeof(char *));
|
|
if (configuration == NULL) {
|
|
fclose(configfile);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
i = parse_statement(i, line, configuration);
|
|
if (i < 0) {
|
|
fclose(configfile);
|
|
exit(1);
|
|
}
|
|
|
|
free(line);
|
|
line = NULL;
|
|
}
|
|
/* Initialize the last element */
|
|
configuration[i] = NULL;
|
|
|
|
free(line);
|
|
fclose(configfile);
|
|
return configuration;
|
|
}
|
|
|
|
static int next_config(char **argv, char ***_argv, bool no_default_config, int state, char *cfg_file)
|
|
{
|
|
char local_filepath[PATH_MAX + 1];
|
|
char *home_dir = NULL;
|
|
char *cfg_from_env = NULL;
|
|
|
|
if (state >= PARSING_LAST)
|
|
return 0;
|
|
|
|
switch (state) {
|
|
case PARSING_GLOBAL_CONF:
|
|
if (no_default_config)
|
|
break;
|
|
*_argv = parse_config(GLOBAL_CONFIG_DIR DEFAULT_CONFIG_FILENAME);
|
|
break;
|
|
case PARSING_USER_CONF:
|
|
if (no_default_config)
|
|
break;
|
|
home_dir = getenv("HOME");
|
|
if (!home_dir) {
|
|
pr_info("Unable to get $HOME directory, local configuration file will not be used.\n");
|
|
} else {
|
|
snprintf(local_filepath, PATH_MAX, "%s/%s%s", home_dir, USER_CONFIG_DIR,
|
|
DEFAULT_CONFIG_FILENAME);
|
|
*_argv = parse_config(local_filepath);
|
|
}
|
|
break;
|
|
case PARSING_ENV_CONF:
|
|
cfg_from_env = getenv("CRIU_CONFIG_FILE");
|
|
if (!cfg_from_env)
|
|
break;
|
|
*_argv = parse_config(cfg_from_env);
|
|
break;
|
|
case PARSING_CMDLINE_CONF:
|
|
if (!cfg_file)
|
|
break;
|
|
*_argv = parse_config(cfg_file);
|
|
break;
|
|
case PARSING_ARGV:
|
|
*_argv = argv;
|
|
break;
|
|
case PARSING_RPC_CONF:
|
|
if (!rpc_cfg_file)
|
|
break;
|
|
*_argv = parse_config(rpc_cfg_file);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ++state;
|
|
}
|
|
|
|
static int pre_parse(int argc, char **argv, bool *usage_error, bool *no_default_config, char **cfg_file)
|
|
{
|
|
int i;
|
|
/*
|
|
* We are running before getopt(), so we need to pre-parse
|
|
* the command line.
|
|
*
|
|
* 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.
|
|
*/
|
|
for (i = 0; i < argc; i++) {
|
|
if ((!strcmp(argv[i], "--help")) || (!strcmp(argv[i], "-h"))) {
|
|
*usage_error = false;
|
|
return 1;
|
|
} else if (!strcmp(argv[i], "--no-default-config")) {
|
|
*no_default_config = true;
|
|
} else if (!strcmp(argv[i], "--config")) {
|
|
/*
|
|
* getopt takes next string as required
|
|
* argument automatically, we do the same
|
|
*/
|
|
*cfg_file = argv[i + 1];
|
|
*no_default_config = true;
|
|
} else if (strstr(argv[i], "--config=") != NULL) {
|
|
*cfg_file = argv[i] + strlen("--config=");
|
|
*no_default_config = true;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void init_opts(void)
|
|
{
|
|
memset(&opts, 0, sizeof(opts));
|
|
|
|
/* Default options */
|
|
opts.final_state = TASK_DEAD;
|
|
INIT_LIST_HEAD(&opts.ext_mounts);
|
|
INIT_LIST_HEAD(&opts.inherit_fds);
|
|
INIT_LIST_HEAD(&opts.external);
|
|
INIT_LIST_HEAD(&opts.join_ns);
|
|
INIT_LIST_HEAD(&opts.new_cgroup_roots);
|
|
INIT_LIST_HEAD(&opts.irmap_scan_paths);
|
|
|
|
opts.cpu_cap = CPU_CAP_DEFAULT;
|
|
opts.manage_cgroups = CG_MODE_DEFAULT;
|
|
opts.ps_socket = -1;
|
|
opts.ghost_limit = DEFAULT_GHOST_LIMIT;
|
|
opts.timeout = DEFAULT_TIMEOUT;
|
|
opts.empty_ns = 0;
|
|
opts.status_fd = -1;
|
|
opts.log_level = DEFAULT_LOGLEVEL;
|
|
opts.pre_dump_mode = PRE_DUMP_SPLICE;
|
|
opts.file_validation_method = FILE_VALIDATION_DEFAULT;
|
|
opts.network_lock_method = NETWORK_LOCK_DEFAULT;
|
|
}
|
|
|
|
bool deprecated_ok(char *what)
|
|
{
|
|
if (opts.deprecated_ok)
|
|
return true;
|
|
|
|
pr_err("Deprecated functionality (%s) rejected.\n", what);
|
|
pr_err("Use the --deprecated option or set CRIU_DEPRECATED environment.\n");
|
|
pr_err("For details visit https://criu.org/Deprecation\n");
|
|
return false;
|
|
}
|
|
|
|
static int parse_cpu_cap(struct cr_options *opts, const char *optarg)
|
|
{
|
|
bool inverse = false;
|
|
|
|
#define ____cpu_set_cap(__opts, __cap, __inverse) \
|
|
do { \
|
|
if ((__inverse)) \
|
|
(__opts)->cpu_cap &= ~(__cap); \
|
|
else \
|
|
(__opts)->cpu_cap |= (__cap); \
|
|
} while (0)
|
|
|
|
if (!optarg) {
|
|
____cpu_set_cap(opts, CPU_CAP_ALL, false);
|
|
____cpu_set_cap(opts, CPU_CAP_IMAGE, false);
|
|
return 0;
|
|
}
|
|
|
|
while (*optarg) {
|
|
if (optarg[0] == '^') {
|
|
inverse = !inverse;
|
|
optarg++;
|
|
continue;
|
|
} else if (optarg[0] == ',') {
|
|
inverse = false;
|
|
optarg++;
|
|
continue;
|
|
}
|
|
|
|
if (!strncmp(optarg, "fpu", 3)) {
|
|
____cpu_set_cap(opts, CPU_CAP_FPU, inverse);
|
|
optarg += 3;
|
|
} else if (!strncmp(optarg, "all", 3)) {
|
|
____cpu_set_cap(opts, CPU_CAP_ALL, inverse);
|
|
optarg += 3;
|
|
} else if (!strncmp(optarg, "none", 4)) {
|
|
if (inverse)
|
|
opts->cpu_cap = CPU_CAP_ALL;
|
|
else
|
|
opts->cpu_cap = CPU_CAP_NONE;
|
|
optarg += 4;
|
|
} else if (!strncmp(optarg, "cpu", 3)) {
|
|
____cpu_set_cap(opts, CPU_CAP_CPU, inverse);
|
|
optarg += 3;
|
|
} else if (!strncmp(optarg, "ins", 3)) {
|
|
____cpu_set_cap(opts, CPU_CAP_INS, inverse);
|
|
optarg += 3;
|
|
} else
|
|
goto Esyntax;
|
|
}
|
|
|
|
if (opts->cpu_cap != CPU_CAP_NONE)
|
|
____cpu_set_cap(opts, CPU_CAP_IMAGE, false);
|
|
#undef ____cpu_set_cap
|
|
|
|
return 0;
|
|
|
|
Esyntax:
|
|
pr_err("Unknown FPU mode `%s' selected\n", optarg);
|
|
return -1;
|
|
}
|
|
|
|
static int parse_manage_cgroups(struct cr_options *opts, const char *optarg)
|
|
{
|
|
if (!optarg) {
|
|
opts->manage_cgroups = CG_MODE_SOFT;
|
|
return 0;
|
|
}
|
|
|
|
if (!strcmp(optarg, "none")) {
|
|
opts->manage_cgroups = CG_MODE_NONE;
|
|
} else if (!strcmp(optarg, "props")) {
|
|
opts->manage_cgroups = CG_MODE_PROPS;
|
|
} else if (!strcmp(optarg, "soft")) {
|
|
opts->manage_cgroups = CG_MODE_SOFT;
|
|
} else if (!strcmp(optarg, "full")) {
|
|
opts->manage_cgroups = CG_MODE_FULL;
|
|
} else if (!strcmp(optarg, "strict")) {
|
|
opts->manage_cgroups = CG_MODE_STRICT;
|
|
} else if (!strcmp(optarg, "ignore")) {
|
|
opts->manage_cgroups = CG_MODE_IGNORE;
|
|
} else
|
|
goto Esyntax;
|
|
|
|
return 0;
|
|
|
|
Esyntax:
|
|
pr_err("Unknown cgroups mode `%s' selected\n", optarg);
|
|
return -1;
|
|
}
|
|
|
|
extern char *index(const char *s, int c);
|
|
|
|
static size_t parse_size(char *optarg)
|
|
{
|
|
if (index(optarg, 'K'))
|
|
return (size_t)KILO(atol(optarg));
|
|
else if (index(optarg, 'M'))
|
|
return (size_t)MEGA(atol(optarg));
|
|
else if (index(optarg, 'G'))
|
|
return (size_t)GIGA(atol(optarg));
|
|
return (size_t)atol(optarg);
|
|
}
|
|
|
|
static int parse_join_ns(const char *ptr)
|
|
{
|
|
char *aux, *ns_file, *extra_opts = NULL;
|
|
cleanup_free char *ns = NULL;
|
|
|
|
ns = xstrdup(ptr);
|
|
if (ns == NULL)
|
|
return -1;
|
|
|
|
aux = strchr(ns, ':');
|
|
if (aux == NULL)
|
|
return -1;
|
|
*aux = '\0';
|
|
|
|
ns_file = aux + 1;
|
|
aux = strchr(ns_file, ',');
|
|
if (aux != NULL) {
|
|
*aux = '\0';
|
|
extra_opts = aux + 1;
|
|
} else {
|
|
extra_opts = NULL;
|
|
}
|
|
if (join_ns_add(ns, ns_file, extra_opts))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_file_validation_method(struct cr_options *opts, const char *optarg)
|
|
{
|
|
if (!strcmp(optarg, "filesize"))
|
|
opts->file_validation_method = FILE_VALIDATION_FILE_SIZE;
|
|
else if (!strcmp(optarg, "buildid"))
|
|
opts->file_validation_method = FILE_VALIDATION_BUILD_ID;
|
|
else
|
|
goto Esyntax;
|
|
|
|
return 0;
|
|
|
|
Esyntax:
|
|
pr_err("Unknown file validation method `%s' selected\n", optarg);
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* parse_options() is the point where the getopt parsing happens. The CLI
|
|
* parsing as well as the configuration file parsing happens here.
|
|
* This used to be all part of main() and to integrate the new code flow
|
|
* in main() this function (parse_options()) returns '0' if everything is
|
|
* 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 state)
|
|
{
|
|
int ret;
|
|
int opt = -1;
|
|
int idx;
|
|
bool no_default_config = false;
|
|
char *cfg_file = NULL;
|
|
char **_argv = NULL;
|
|
int _argc = 0;
|
|
bool has_network_lock_opt = false;
|
|
|
|
#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[] = "dSsRt:hD:o:v::x::Vr:jJ:lW:L:M:";
|
|
static struct option long_opts[] = {
|
|
{ "tree", required_argument, 0, 't' },
|
|
{ "leave-stopped", no_argument, 0, 's' },
|
|
{ "leave-running", no_argument, 0, 'R' },
|
|
BOOL_OPT("restore-detached", &opts.restore_detach),
|
|
BOOL_OPT("restore-sibling", &opts.restore_sibling),
|
|
BOOL_OPT("daemon", &opts.restore_detach),
|
|
{ "images-dir", required_argument, 0, 'D' },
|
|
{ "work-dir", required_argument, 0, 'W' },
|
|
{ "log-file", required_argument, 0, 'o' },
|
|
{ "join-ns", required_argument, 0, 'J' },
|
|
{ "root", required_argument, 0, 'r' },
|
|
{ USK_EXT_PARAM, optional_argument, 0, 'x' },
|
|
{ "help", no_argument, 0, 'h' },
|
|
BOOL_OPT(SK_EST_PARAM, &opts.tcp_established_ok),
|
|
{ "close", required_argument, 0, 1043 },
|
|
BOOL_OPT("log-pid", &opts.log_file_per_pid),
|
|
{ "version", no_argument, 0, 'V' },
|
|
BOOL_OPT("evasive-devices", &opts.evasive_devices),
|
|
{ "pidfile", required_argument, 0, 1046 },
|
|
{ "veth-pair", required_argument, 0, 1047 },
|
|
{ "action-script", required_argument, 0, 1049 },
|
|
BOOL_OPT(LREMAP_PARAM, &opts.link_remap_ok),
|
|
BOOL_OPT(OPT_SHELL_JOB, &opts.shell_job),
|
|
BOOL_OPT(OPT_FILE_LOCKS, &opts.handle_file_locks),
|
|
BOOL_OPT("page-server", &opts.use_page_server),
|
|
{ "address", required_argument, 0, 1051 },
|
|
{ "port", required_argument, 0, 1052 },
|
|
{ "prev-images-dir", required_argument, 0, 1053 },
|
|
{ "ms", no_argument, 0, 1054 },
|
|
BOOL_OPT("track-mem", &opts.track_mem),
|
|
BOOL_OPT("auto-dedup", &opts.auto_dedup),
|
|
{ "libdir", required_argument, 0, 'L' },
|
|
{ "cpu-cap", optional_argument, 0, 1057 },
|
|
BOOL_OPT("force-irmap", &opts.force_irmap),
|
|
{ "ext-mount-map", required_argument, 0, 'M' },
|
|
{ "exec-cmd", no_argument, 0, 1059 },
|
|
{ "manage-cgroups", optional_argument, 0, 1060 },
|
|
{ "cgroup-root", required_argument, 0, 1061 },
|
|
{ "inherit-fd", required_argument, 0, 1062 },
|
|
{ "feature", required_argument, 0, 1063 },
|
|
{ "skip-mnt", required_argument, 0, 1064 },
|
|
{ "enable-fs", required_argument, 0, 1065 },
|
|
{ "enable-external-sharing", no_argument, &opts.enable_external_sharing, true },
|
|
{ "enable-external-masters", no_argument, &opts.enable_external_masters, true },
|
|
{ "freeze-cgroup", required_argument, 0, 1068 },
|
|
{ "ghost-limit", required_argument, 0, 1069 },
|
|
{ "irmap-scan-path", required_argument, 0, 1070 },
|
|
{ "lsm-profile", required_argument, 0, 1071 },
|
|
{ "timeout", required_argument, 0, 1072 },
|
|
{ "external", required_argument, 0, 1073 },
|
|
{ "empty-ns", required_argument, 0, 1074 },
|
|
{ "lazy-pages", no_argument, 0, 1076 },
|
|
BOOL_OPT("extra", &opts.check_extra_features),
|
|
BOOL_OPT("experimental", &opts.check_experimental_features),
|
|
{ "all", no_argument, 0, 1079 },
|
|
{ "cgroup-props", required_argument, 0, 1080 },
|
|
{ "cgroup-props-file", required_argument, 0, 1081 },
|
|
{ "cgroup-dump-controller", required_argument, 0, 1082 },
|
|
BOOL_OPT(SK_INFLIGHT_PARAM, &opts.tcp_skip_in_flight),
|
|
BOOL_OPT("deprecated", &opts.deprecated_ok),
|
|
BOOL_OPT("display-stats", &opts.display_stats),
|
|
BOOL_OPT("weak-sysctls", &opts.weak_sysctls),
|
|
{ "status-fd", required_argument, 0, 1088 },
|
|
BOOL_OPT(SK_CLOSE_PARAM, &opts.tcp_close),
|
|
{ "verbosity", optional_argument, 0, 'v' },
|
|
{ "ps-socket", required_argument, 0, 1091 },
|
|
BOOL_OPT("stream", &opts.stream),
|
|
{ "config", required_argument, 0, 1089 },
|
|
{ "no-default-config", no_argument, 0, 1090 },
|
|
{ "tls-cacert", required_argument, 0, 1092 },
|
|
{ "tls-cacrl", required_argument, 0, 1093 },
|
|
{ "tls-cert", required_argument, 0, 1094 },
|
|
{ "tls-key", required_argument, 0, 1095 },
|
|
BOOL_OPT("tls", &opts.tls),
|
|
{ "tls-no-cn-verify", no_argument, &opts.tls_no_cn_verify, true },
|
|
{ "cgroup-yard", required_argument, 0, 1096 },
|
|
{ "pre-dump-mode", required_argument, 0, 1097 },
|
|
{ "file-validation", required_argument, 0, 1098 },
|
|
{ "lsm-mount-context", required_argument, 0, 1099 },
|
|
{ "network-lock", required_argument, 0, 1100 },
|
|
BOOL_OPT("mntns-compat-mode", &opts.mntns_compat_mode),
|
|
{},
|
|
};
|
|
|
|
#undef BOOL_OPT
|
|
|
|
ret = pre_parse(argc, argv, usage_error, &no_default_config, &cfg_file);
|
|
|
|
if (ret)
|
|
return 2;
|
|
|
|
while (1) {
|
|
idx = -1;
|
|
/* Only if opt is -1 we are going to the next configuration input */
|
|
if (opt == -1) {
|
|
/* Do not free any memory if it points to argv */
|
|
if (state != PARSING_ARGV + 1) {
|
|
int i;
|
|
for (i = 1; i < _argc; i++) {
|
|
free(_argv[i]);
|
|
}
|
|
free(_argv);
|
|
}
|
|
/* This needs to be reset for a new getopt() run */
|
|
_argc = 0;
|
|
_argv = NULL;
|
|
|
|
state = next_config(argv, &_argv, no_default_config, state, cfg_file);
|
|
|
|
/* if next_config() returns 0 it means no more configs found */
|
|
if (state == 0)
|
|
break;
|
|
|
|
if (!_argv)
|
|
continue;
|
|
|
|
_argc = count_elements(_argv);
|
|
optind = 0;
|
|
}
|
|
|
|
opt = getopt_long(_argc, _argv, short_opts, long_opts, &idx);
|
|
|
|
/*
|
|
* The end of the current _argv has been reached,
|
|
* let's go to the next _argv
|
|
*/
|
|
if (opt == -1)
|
|
continue;
|
|
|
|
/*
|
|
* If opt == 0 then getopt will directly fill out the corresponding
|
|
* field in CRIU's opts structure.
|
|
*/
|
|
if (!opt)
|
|
continue;
|
|
|
|
switch (opt) {
|
|
case 's':
|
|
opts.final_state = TASK_STOPPED;
|
|
break;
|
|
case 'R':
|
|
opts.final_state = TASK_ALIVE;
|
|
break;
|
|
case 'x':
|
|
if (optarg && unix_sk_ids_parse(optarg) < 0) {
|
|
pr_err("Failed to parse unix socket inode from optarg: %s\n", optarg);
|
|
return 1;
|
|
}
|
|
opts.ext_unix_sk = true;
|
|
break;
|
|
case 't':
|
|
opts.tree_id = atoi(optarg);
|
|
if (opts.tree_id <= 0)
|
|
goto bad_arg;
|
|
break;
|
|
case 'r':
|
|
SET_CHAR_OPTS(root, optarg);
|
|
break;
|
|
case 'd':
|
|
opts.restore_detach = true;
|
|
break;
|
|
case 'S':
|
|
opts.restore_sibling = true;
|
|
break;
|
|
case 'D':
|
|
SET_CHAR_OPTS(imgs_dir, optarg);
|
|
break;
|
|
case 'W':
|
|
SET_CHAR_OPTS(work_dir, optarg);
|
|
break;
|
|
case 'o':
|
|
SET_CHAR_OPTS(output, optarg);
|
|
break;
|
|
case 'J':
|
|
if (parse_join_ns(optarg))
|
|
goto bad_arg;
|
|
break;
|
|
case 'v':
|
|
if (optarg) {
|
|
if (optarg[0] == 'v')
|
|
/* handle -vvvvv */
|
|
opts.log_level += strlen(optarg) + 1;
|
|
else
|
|
opts.log_level = atoi(optarg);
|
|
} else
|
|
opts.log_level++;
|
|
break;
|
|
case 1043: {
|
|
int fd;
|
|
|
|
fd = atoi(optarg);
|
|
pr_info("Closing fd %d\n", fd);
|
|
close(fd);
|
|
break;
|
|
}
|
|
case 1046:
|
|
SET_CHAR_OPTS(pidfile, optarg);
|
|
break;
|
|
case 1047: {
|
|
char *aux;
|
|
|
|
aux = strchr(optarg, '=');
|
|
if (aux == NULL)
|
|
goto bad_arg;
|
|
|
|
*aux = '\0';
|
|
if (veth_pair_add(optarg, aux + 1)) {
|
|
pr_err("Failed to add veth pair: %s, %s.\n", optarg, aux + 1);
|
|
return 1;
|
|
}
|
|
} break;
|
|
case 1049:
|
|
if (add_script(optarg)) {
|
|
pr_err("Failed to add action-script: %s.\n", optarg);
|
|
return 1;
|
|
}
|
|
break;
|
|
case 1051:
|
|
SET_CHAR_OPTS(addr, optarg);
|
|
break;
|
|
case 1052:
|
|
opts.port = atoi(optarg);
|
|
if (!opts.port)
|
|
goto bad_arg;
|
|
break;
|
|
case 'j':
|
|
opts.shell_job = true;
|
|
break;
|
|
case 'l':
|
|
opts.handle_file_locks = true;
|
|
break;
|
|
case 1053:
|
|
SET_CHAR_OPTS(img_parent, optarg);
|
|
break;
|
|
case 1057:
|
|
if (parse_cpu_cap(&opts, optarg))
|
|
return 2;
|
|
break;
|
|
case 1058:
|
|
opts.force_irmap = true;
|
|
break;
|
|
case 1054:
|
|
pr_err("--ms is deprecated; see \"Check options\" of criu --help\n");
|
|
return 1;
|
|
case 'L':
|
|
SET_CHAR_OPTS(libdir, optarg);
|
|
break;
|
|
case 1059:
|
|
*has_exec_cmd = true;
|
|
break;
|
|
case 1060:
|
|
if (parse_manage_cgroups(&opts, optarg))
|
|
return 2;
|
|
break;
|
|
case 1061: {
|
|
char *path, *ctl;
|
|
|
|
path = strchr(optarg, ':');
|
|
if (path) {
|
|
*path = '\0';
|
|
path++;
|
|
ctl = optarg;
|
|
} else {
|
|
path = optarg;
|
|
ctl = NULL;
|
|
}
|
|
|
|
if (new_cg_root_add(ctl, path))
|
|
return -1;
|
|
} break;
|
|
case 1062:
|
|
if (inherit_fd_parse(optarg) < 0)
|
|
return 1;
|
|
break;
|
|
case 1063:
|
|
ret = check_add_feature(optarg);
|
|
if (ret < 0) /* invalid kernel feature name */
|
|
return 1;
|
|
if (ret > 0) /* list kernel features and exit */
|
|
return 0;
|
|
break;
|
|
case 1064:
|
|
if (!add_skip_mount(optarg)) {
|
|
pr_err("Failed to add skip-mnt: %s\n", optarg);
|
|
return 1;
|
|
}
|
|
break;
|
|
case 1065:
|
|
if (!add_fsname_auto(optarg)) {
|
|
pr_err("Failed while parsing --enable-fs option: %s\n", optarg);
|
|
return 1;
|
|
}
|
|
break;
|
|
case 1068:
|
|
SET_CHAR_OPTS(freeze_cgroup, optarg);
|
|
break;
|
|
case 1069:
|
|
opts.ghost_limit = parse_size(optarg);
|
|
break;
|
|
case 1070:
|
|
if (irmap_scan_path_add(optarg)) {
|
|
pr_err("Failed while parsing --irmap-scan-path option: %s\n", optarg);
|
|
return -1;
|
|
}
|
|
break;
|
|
case 1071:
|
|
SET_CHAR_OPTS(lsm_profile, optarg);
|
|
opts.lsm_supplied = true;
|
|
break;
|
|
case 1072:
|
|
opts.timeout = atoi(optarg);
|
|
break;
|
|
case 1076:
|
|
opts.lazy_pages = true;
|
|
break;
|
|
case 'M': {
|
|
char *aux;
|
|
|
|
if (strcmp(optarg, "auto") == 0) {
|
|
opts.autodetect_ext_mounts = true;
|
|
break;
|
|
}
|
|
|
|
aux = strchr(optarg, ':');
|
|
if (aux == NULL)
|
|
goto bad_arg;
|
|
|
|
*aux = '\0';
|
|
if (ext_mount_add(optarg, aux + 1)) {
|
|
pr_err("Could not add external mount when initializing config: %s, %s\n", optarg,
|
|
aux + 1);
|
|
return 1;
|
|
}
|
|
} break;
|
|
case 1073:
|
|
if (add_external(optarg)) {
|
|
pr_err("Could not add external resource when initializing config: %s\n", optarg);
|
|
return 1;
|
|
}
|
|
break;
|
|
case 1074:
|
|
if (!strcmp("net", optarg))
|
|
opts.empty_ns |= CLONE_NEWNET;
|
|
else {
|
|
pr_err("Unsupported empty namespace: %s\n", optarg);
|
|
return 1;
|
|
}
|
|
break;
|
|
case 1079:
|
|
opts.check_extra_features = true;
|
|
opts.check_experimental_features = true;
|
|
break;
|
|
case 1080:
|
|
SET_CHAR_OPTS(cgroup_props, optarg);
|
|
break;
|
|
case 1081:
|
|
SET_CHAR_OPTS(cgroup_props_file, optarg);
|
|
break;
|
|
case 1082:
|
|
if (!cgp_add_dump_controller(optarg))
|
|
return 1;
|
|
break;
|
|
case 1088:
|
|
if (sscanf(optarg, "%d", &opts.status_fd) != 1) {
|
|
pr_err("Unable to parse a value of --status-fd\n");
|
|
return 1;
|
|
}
|
|
break;
|
|
case 1089:
|
|
break;
|
|
case 1090:
|
|
break;
|
|
case 1091:
|
|
opts.ps_socket = atoi(optarg);
|
|
break;
|
|
case 1092:
|
|
SET_CHAR_OPTS(tls_cacert, optarg);
|
|
break;
|
|
case 1093:
|
|
SET_CHAR_OPTS(tls_cacrl, optarg);
|
|
break;
|
|
case 1094:
|
|
SET_CHAR_OPTS(tls_cert, optarg);
|
|
break;
|
|
case 1095:
|
|
SET_CHAR_OPTS(tls_key, optarg);
|
|
break;
|
|
case 1096:
|
|
SET_CHAR_OPTS(cgroup_yard, optarg);
|
|
break;
|
|
case 1097:
|
|
if (!strcmp("read", optarg)) {
|
|
opts.pre_dump_mode = PRE_DUMP_READ;
|
|
} else if (strcmp("splice", optarg)) {
|
|
pr_err("Unable to parse value of --pre-dump-mode\n");
|
|
return 1;
|
|
}
|
|
break;
|
|
case 1098:
|
|
if (parse_file_validation_method(&opts, optarg))
|
|
return 2;
|
|
break;
|
|
case 1099:
|
|
SET_CHAR_OPTS(lsm_mount_context, optarg);
|
|
break;
|
|
case 1100:
|
|
has_network_lock_opt = true;
|
|
if (!strcmp("iptables", optarg)) {
|
|
opts.network_lock_method = NETWORK_LOCK_IPTABLES;
|
|
} else if (!strcmp("nftables", optarg)) {
|
|
opts.network_lock_method = NETWORK_LOCK_NFTABLES;
|
|
} else {
|
|
pr_err("Invalid value for --network-lock: %s\n", optarg);
|
|
return 1;
|
|
}
|
|
break;
|
|
case 'V':
|
|
pr_msg("Version: %s\n", CRIU_VERSION);
|
|
if (strcmp(CRIU_GITID, "0"))
|
|
pr_msg("GitID: %s\n", CRIU_GITID);
|
|
exit(0);
|
|
case 'h':
|
|
*usage_error = false;
|
|
return 2;
|
|
default:
|
|
return 2;
|
|
}
|
|
}
|
|
|
|
if (has_network_lock_opt && !strcmp(argv[optind], "restore")) {
|
|
pr_warn("--network-lock will be ignored in restore command\n");
|
|
pr_info("Network lock method from dump will be used in restore\n");
|
|
}
|
|
|
|
return 0;
|
|
|
|
bad_arg:
|
|
if (idx < 0) /* short option */
|
|
pr_err("invalid argument for -%c: %s\n", opt, optarg);
|
|
else /* long option */
|
|
pr_err("invalid argument for --%s: %s\n", long_opts[idx].name, optarg);
|
|
return 1;
|
|
}
|
|
|
|
int check_options(void)
|
|
{
|
|
if (opts.tcp_established_ok)
|
|
pr_info("Will dump/restore TCP connections\n");
|
|
if (opts.tcp_skip_in_flight)
|
|
pr_info("Will skip in-flight TCP connections\n");
|
|
if (opts.tcp_close)
|
|
pr_info("Will drop all TCP connections on restore\n");
|
|
if (opts.link_remap_ok)
|
|
pr_info("Will allow link remaps on FS\n");
|
|
if (opts.weak_sysctls)
|
|
pr_info("Will skip non-existent sysctls on restore\n");
|
|
|
|
if (opts.deprecated_ok)
|
|
pr_info("Turn deprecated stuff ON\n");
|
|
else if (getenv("CRIU_DEPRECATED")) {
|
|
pr_info("Turn deprecated stuff ON via env\n");
|
|
opts.deprecated_ok = true;
|
|
}
|
|
|
|
if (!opts.restore_detach && opts.restore_sibling) {
|
|
pr_err("--restore-sibling only makes sense with --restore-detached\n");
|
|
return 1;
|
|
}
|
|
|
|
if (opts.ps_socket != -1) {
|
|
if (opts.addr || opts.port)
|
|
pr_warn("Using --address or --port in "
|
|
"combination with --ps-socket is obsolete\n");
|
|
if (opts.ps_socket <= STDERR_FILENO && opts.daemon_mode) {
|
|
pr_err("Standard file descriptors will be closed"
|
|
" in daemon mode\n");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
#ifndef CONFIG_GNUTLS
|
|
if (opts.tls) {
|
|
pr_err("CRIU was built without TLS support\n");
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
if (opts.mntns_compat_mode && opts.mode != CR_RESTORE) {
|
|
pr_err("Option --mntns-compat-mode is only valid on restore\n");
|
|
return 1;
|
|
} else if (!opts.mntns_compat_mode && opts.mode == CR_RESTORE) {
|
|
if (check_mount_v2()) {
|
|
pr_debug("Mount engine fallback to --mntns-compat-mode mode\n");
|
|
opts.mntns_compat_mode = true;
|
|
}
|
|
}
|
|
|
|
if (check_namespace_opts()) {
|
|
pr_err("Error: namespace flags conflict\n");
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|