diff --git a/criu/cr-service.c b/criu/cr-service.c index f9844570a..219a98624 100644 --- a/criu/cr-service.c +++ b/criu/cr-service.c @@ -37,6 +37,7 @@ #include "setproctitle.h" #include "cr-errno.h" +#include "namespaces.h" unsigned int service_sk_ino = -1; @@ -373,6 +374,11 @@ static int setup_opts_from_req(int sk, CriuOpts *req) goto err; } + for (i = 0; i < req->n_join_ns; i++) { + if (join_ns_add(req->join_ns[i]->ns, req->join_ns[i]->ns_file, req->join_ns[i]->extra_opt)) + goto err; + } + if (req->n_inherit_fd && !opts.swrk_restore) { pr_err("inherit_fd is not allowed in standalone service\n"); goto err; @@ -472,6 +478,9 @@ static int setup_opts_from_req(int sk, CriuOpts *req) } } + if (check_namespace_opts()) + goto err; + return 0; err: diff --git a/criu/crtools.c b/criu/crtools.c index 4a6dbffb4..f2043ce16 100644 --- a/criu/crtools.c +++ b/criu/crtools.c @@ -36,6 +36,7 @@ #include "cr-service.h" #include "plugin.h" #include "mount.h" +#include "namespaces.h" #include "cgroup.h" #include "cpu.h" #include "action-scripts.h" @@ -58,6 +59,7 @@ void init_opts(void) INIT_LIST_HEAD(&opts.ext_unixsk_ids); INIT_LIST_HEAD(&opts.veth_pairs); INIT_LIST_HEAD(&opts.ext_mounts); + INIT_LIST_HEAD(&opts.join_ns); INIT_LIST_HEAD(&opts.inherit_fds); INIT_LIST_HEAD(&opts.external); INIT_LIST_HEAD(&opts.new_cgroup_roots); @@ -71,6 +73,29 @@ void init_opts(void) opts.empty_ns = 0; } +static int parse_join_ns(const char *ptr) +{ + char *aux, *ns_file, *extra_opts = NULL; + + aux = strchr(ptr, ':'); + 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(ptr, ns_file, extra_opts)) + return -1; + + return 0; +} + static int parse_cpu_cap(struct cr_options *opts, const char *optarg) { bool inverse = false; @@ -190,7 +215,7 @@ int main(int argc, char *argv[], char *envp[]) int log_level = LOG_UNSET; char *imgs_dir = "."; char *work_dir = NULL; - static const char short_opts[] = "dSsRf:F:t:p:hcD:o:v::x::Vr:jlW:L:M:"; + 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' }, { "pid", required_argument, 0, 'p' }, @@ -205,6 +230,7 @@ int main(int argc, char *argv[], char *envp[]) { "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' }, @@ -338,6 +364,10 @@ int main(int argc, char *argv[], char *envp[]) case 'o': opts.output = optarg; break; + case 'J': + if (parse_join_ns(optarg)) + goto bad_arg; + break; case 'v': if (log_level == LOG_UNSET) log_level = 0; @@ -552,6 +582,11 @@ int main(int argc, char *argv[], char *envp[]) } } + if (check_namespace_opts()) { + pr_msg("Error: namespace flags confict\n"); + return 1; + } + if (!opts.restore_detach && opts.restore_sibling) { pr_msg("--restore-sibling only makes sense with --restore-detach\n"); return 1; @@ -793,6 +828,12 @@ usage: " --empty-ns {net}\n" " Create a namespace, but don't restore its properies.\n" " An user will retore them from action scripts.\n" +" -J|--join-ns NS:PID|NS_FILE[,EXTRA_OPTS]\n" +" Join exist namespace and restore process in it.\n" +" Namespace can be specified in pid or file path format.\n" +" --join-ns net:12345 or --join-ns net:/foo/bar.\n" +" Extra_opts is optional, for now only user namespace support:\n" +" --join-ns user:PID,UID,GID to specify uid and gid.\n" "Check options:\n" " without any arguments, \"criu check\" checks availability of absolutely required\n" " kernel features; if any of these features is missing dump and restore will fail\n" diff --git a/criu/include/cr_options.h b/criu/include/cr_options.h index 253aadb3f..4fa709425 100644 --- a/criu/include/cr_options.h +++ b/criu/include/cr_options.h @@ -81,6 +81,7 @@ struct cr_options { struct list_head ext_mounts; struct list_head inherit_fds; struct list_head external; + struct list_head join_ns; char *libdir; bool use_page_server; unsigned short port; diff --git a/criu/include/namespaces.h b/criu/include/namespaces.h index e7931ec89..15a514a98 100644 --- a/criu/include/namespaces.h +++ b/criu/include/namespaces.h @@ -3,6 +3,7 @@ #include "compiler.h" #include "files.h" +#include "list.h" #ifndef CLONE_NEWNS #define CLONE_NEWNS 0x00020000 @@ -36,6 +37,7 @@ /* Nested namespaces are supported only for these types */ #define CLONE_SUBNS (CLONE_NEWNS) +#define EXTRA_SIZE 20 struct ns_desc { unsigned int cflag; @@ -43,6 +45,24 @@ struct ns_desc { size_t len; }; +struct user_ns_extra { + char *uid; + char *gid; +}; + +/* struct join_ns is used for storing parameters specified by --join-ns */ +struct join_ns { + struct list_head list; + char *ns_file; + struct ns_desc *nd; /* namespace descriptor */ + int ns_fd; + /* extra options of --join-ns, like uid&gid in user namespace */ + union { + struct user_ns_extra user_extra; + char *common_extra; + } extra_opts; +}; + enum ns_type { NS_UNKNOWN = 0, NS_CRIU, @@ -124,6 +144,8 @@ extern gid_t userns_gid(gid_t gid); extern int dump_user_ns(pid_t pid, int ns_id); extern void free_userns_maps(void); +extern int join_ns_add(const char *type, char *ns_file, char *extra_opts); +extern int check_namespace_opts(void); typedef int (*uns_call_t)(void *arg, int fd, pid_t pid); /* diff --git a/criu/namespaces.c b/criu/namespaces.c index 260137c19..ce68d4d62 100644 --- a/criu/namespaces.c +++ b/criu/namespaces.c @@ -10,8 +10,12 @@ #include #include #include +#include +#include +#include #include "rst-malloc.h" +#include "cr_options.h" #include "imgset.h" #include "uts_ns.h" #include "ipc_ns.h" @@ -35,6 +39,147 @@ static struct ns_desc *ns_desc_array[] = { &cgroup_ns_desc, }; +static unsigned int join_ns_flags; + +int check_namespace_opts(void) +{ + errno = 22; + if (join_ns_flags & opts.rst_namespaces_flags) { + pr_perror("Conflict flags: -join-ns and -namespace"); + return -1; + } + if (join_ns_flags & opts.empty_ns) { + pr_perror("Conflict flags: -join-ns and -empty-ns"); + return -1; + } + errno = 0; + return 0; +} + +static int check_int_str(char *str) +{ + char *endptr; + long val; + + if (str == NULL) + return 0; + + if (*str == '\0') { + str = NULL; + return 0; + } + + errno = 22; + val = strtol(str, &endptr, 10); + if ((errno == ERANGE) || (endptr == str) + || (*endptr != '\0') + || (val < 0) || (val > 65535)) { + str = NULL; + return -1; + } + + errno = 0; + return 0; +} + +static int check_ns_file(char *ns_file) +{ + int pid, ret, proc_dir; + + if (!check_int_str(ns_file)) { + pid = atoi(ns_file); + if (pid <= 0) { + pr_perror("Invalid join_ns pid %s", ns_file); + return -1; + } + proc_dir = open_pid_proc(pid); + if (proc_dir < 0) { + pr_perror("Invalid join_ns pid: /proc/%s not found", ns_file); + return -1; + } + return 0; + } + + ret = access(ns_file, 0); + if (ret < 0) { + pr_perror("Can't access join-ns file: %s", ns_file); + return -1; + } + return 0; +} + +static int set_user_extra_opts(struct join_ns *jn, char *extra_opts) +{ + char *uid, *gid, *aux; + + if (extra_opts == NULL) { + jn->extra_opts.user_extra.uid = NULL; + jn->extra_opts.user_extra.gid = NULL; + return 0; + } + + uid = extra_opts; + aux = strchr(extra_opts, ','); + if (aux == NULL) { + gid = NULL; + } else { + *aux = '\0'; + gid = aux + 1; + } + + if (check_int_str(uid) || check_int_str(gid)) + return -1; + + jn->extra_opts.user_extra.uid = uid; + jn->extra_opts.user_extra.gid = gid; + + return 0; +} + +int join_ns_add(const char *type, char *ns_file, char *extra_opts) +{ + struct join_ns *jn; + + jn = xmalloc(sizeof(*jn)); + if (!jn) + return -1; + + if (check_ns_file(ns_file)) + return -1; + + jn->ns_file = ns_file; + if (!strncmp(type, "net", 4)) { + jn->nd = &net_ns_desc; + join_ns_flags |= CLONE_NEWNET; + } else if (!strncmp(type, "uts", 4)) { + jn->nd = &uts_ns_desc; + join_ns_flags |= CLONE_NEWUTS; + } else if (!strncmp(type, "ipc", 4)) { + jn->nd = &ipc_ns_desc; + join_ns_flags |= CLONE_NEWIPC; + } else if (!strncmp(type, "pid", 4)) { + pr_perror("join-ns pid namespace not supported\n"); + return -1; + } else if (!strncmp(type, "user", 5)) { + jn->nd = &user_ns_desc; + if (set_user_extra_opts(jn, extra_opts)) { + pr_perror("invalid user namespace extra_opts %s\n", extra_opts); + return -1; + } + join_ns_flags |= CLONE_NEWUSER; + } else if (!strncmp(type, "mnt", 4)) { + jn->nd = &mnt_ns_desc; + join_ns_flags |= CLONE_NEWNS; + } else { + pr_perror("invalid namespace type %s\n", type); + return -1; + } + + list_add_tail(&jn->list, &opts.join_ns); + pr_info("Added %s:%s join namespace\n", type, ns_file); + return 0; +} + static unsigned int parse_ns_link(char *link, size_t len, struct ns_desc *d) { unsigned long kid = 0; diff --git a/images/rpc.proto b/images/rpc.proto index fac4b9fa0..9ece4dabb 100644 --- a/images/rpc.proto +++ b/images/rpc.proto @@ -15,6 +15,12 @@ message ext_mount_map { required string val = 2; }; +message join_namespace { + required string ns = 1; + required string ns_file = 2; + optional string extra_opt = 3; +} + message inherit_fd { required string key = 1; required int32 fd = 2; @@ -90,6 +96,7 @@ message criu_opts { repeated string irmap_scan_paths = 36; repeated string external = 37; optional uint32 empty_ns = 38; + repeated join_namespace join_ns = 39; } message criu_dump_resp {