[CRIU] [PATCH] add join-ns opt to criu restore

Dengguangxing dengguangxing at huawei.com
Fri Mar 18 02:50:54 PDT 2016


join-ns will restore process with specified existing namespace.
This opt can be used in this fomat:
	--join-ns NS:PID|NS_FILE
for example --join-ns net:12345 or --join-ns net:/foo/bar.

pid namespaces is not supported yet. As fork() is needed to make
new pid-namespace work. That makes it hard for criu to track the
child-process through pid because another child process has been
created after fork().

Signed-off-by: Deng Guangxing <dengguangxing at huawei.com>
---
 criu/cr-restore.c         |   5 +
 criu/cr-service.c         |  10 ++
 criu/crtools.c            |  55 ++++++++-
 criu/include/cr_options.h |   1 +
 criu/include/namespaces.h |  24 ++++
 criu/namespaces.c         | 308 +++++++++++++++++++++++++++++++++++++++++++++-
 criu/net.c                |   5 +-
 images/rpc.proto          |   7 ++
 8 files changed, 412 insertions(+), 3 deletions(-)

diff --git a/criu/cr-restore.c b/criu/cr-restore.c
index 30ddff9..e3f0add 100644
--- a/criu/cr-restore.c
+++ b/criu/cr-restore.c
@@ -1566,6 +1566,11 @@ static int restore_task_with_children(void *_arg)

 	/* Restore root task */
 	if (current->parent == NULL) {
+		if (join_namespaces()) {
+			pr_perror("Join namespaces failed");
+			goto err;
+		}
+
 		if (restore_finish_stage(CR_STATE_RESTORE_NS) < 0)
 			goto err;

diff --git a/criu/cr-service.c b/criu/cr-service.c
index 88d4af7..e2725d0 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;

@@ -374,6 +375,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;
@@ -473,6 +479,10 @@ static int setup_opts_from_req(int sk, CriuOpts *req)
 		}
 	}

+	/* check namespace flags here */
+	if (check_namespace_opts())
+		goto err;
+
 	return 0;

 err:
diff --git a/criu/crtools.c b/criu/crtools.c
index d6e8672..66a1d44 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.veth_pairs);
 	INIT_LIST_HEAD(&opts.scripts);
 	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);
@@ -99,6 +101,41 @@ bad_ns:
 	return -1;
 }

+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';
+
+	if (!strncmp(ptr, "pid", 4)) {
+		pr_perror("pid namespace not supported in join-ns\n");
+		return -1;
+	} else if (strncmp(ptr, "net", 4) &&
+		strncmp(ptr, "uts", 4) &&
+		strncmp(ptr, "ipc", 4) &&
+		strncmp(ptr, "user", 5) &&
+		strncmp(ptr, "mnt", 4)) {
+		pr_perror("Illegal namespace %s in join-ns\n", ptr);
+		return -1;
+	}
+
+	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;
@@ -218,7 +255,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:n:v::x::Vr:jlW:L:M:";
+	static const char short_opts[] = "dSsRf:F:t:p:hcD:o:n:v::x::Vr:jJ:lW:L:M:";
 	static struct option long_opts[] = {
 		{ "tree",			required_argument,	0, 't'	},
 		{ "pid",			required_argument,	0, 'p'	},
@@ -234,6 +271,7 @@ int main(int argc, char *argv[], char *envp[])
 		{ "work-dir",			required_argument,	0, 'W'	},
 		{ "log-file",			required_argument,	0, 'o'	},
 		{ "namespaces",			required_argument,	0, 'n'	},
+		{ "join-ns",			required_argument,	0, 'J'	},
 		{ "root",			required_argument,	0, 'r'	},
 		{ USK_EXT_PARAM,		optional_argument,	0, 'x'	},
 		{ "help",			no_argument,		0, 'h'	},
@@ -366,6 +404,10 @@ int main(int argc, char *argv[], char *envp[])
 			if (parse_ns_string(optarg))
 				goto bad_arg;
 			break;
+		case 'J':
+			if (parse_join_ns(optarg))
+				goto bad_arg;
+			break;
 		case 'v':
 			if (log_level == LOG_UNSET)
 				log_level = 0;
@@ -567,6 +609,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;
@@ -810,6 +857,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"
 "\n"
 "* Logging:\n"
 "  -o|--log-file FILE    log file name\n"
diff --git a/criu/include/cr_options.h b/criu/include/cr_options.h
index a6f0b3e..cf3cb0e 100644
--- a/criu/include/cr_options.h
+++ b/criu/include/cr_options.h
@@ -82,6 +82,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 303c9e6..3729aed 100644
--- a/criu/include/namespaces.h
+++ b/criu/include/namespaces.h
@@ -3,6 +3,7 @@

 #include "compiler.h"
 #include "files.h"
+#include "list.h"

 /* including syscall-types.h gives another weird error; do we really need to
  * define this twice? */
@@ -12,6 +13,7 @@

 /* Nested namespaces are supported only for these types */
 #define CLONE_SUBNS	(CLONE_NEWNS)
+#define EXTRA_SIZE	20

 struct ns_desc {
 	unsigned int	cflag;
@@ -19,6 +21,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;
+	int			ns_fd; 	/* namespace file descriptor */
+	struct ns_desc		nd; 	/* namespace descriptor */
+	/* 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,
@@ -100,6 +120,10 @@ 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 join_namespaces();
+extern unsigned int get_join_ns_flag();
+extern int check_namespace_opts();

 typedef int (*uns_call_t)(void *arg, int fd, pid_t pid);
 /*
diff --git a/criu/namespaces.c b/criu/namespaces.c
index d7f8a9f..99e420a 100644
--- a/criu/namespaces.c
+++ b/criu/namespaces.c
@@ -10,7 +10,11 @@
 #include <signal.h>
 #include <sched.h>
 #include <sys/capability.h>
+#include <sys/stat.h>
+#include <limits.h>
+#include <errno.h>

+#include "cr_options.h"
 #include "util.h"
 #include "imgset.h"
 #include "uts_ns.h"
@@ -35,6 +39,164 @@ static struct ns_desc *ns_desc_array[] = {
 	&cgroup_ns_desc,
 };

+unsigned int get_join_ns_flag() {
+	struct join_ns *jn;
+	unsigned int join_ns_flags = 0;
+
+	list_for_each_entry(jn, &opts.join_ns, list)
+		if (!strncmp(jn->nd.str, "uts", 4))
+			join_ns_flags |= CLONE_NEWUTS;
+		else if (!strncmp(jn->nd.str, "ipc", 4))
+			join_ns_flags |= CLONE_NEWIPC;
+		else if (!strncmp(jn->nd.str, "mnt", 4))
+			join_ns_flags |= CLONE_NEWNS;
+		else if (!strncmp(jn->nd.str, "pid", 4))
+			join_ns_flags |= CLONE_NEWPID;
+		else if (!strncmp(jn->nd.str, "net", 4))
+			join_ns_flags |= CLONE_NEWNET;
+		else if (!strncmp(jn->nd.str, "user", 5))
+			join_ns_flags |= CLONE_NEWNET;
+
+	return join_ns_flags;
+}
+
+int check_namespace_opts() {
+	unsigned int join_ns_flag;
+
+	join_ns_flag = get_join_ns_flag();
+
+	errno = 22;
+	if (join_ns_flag & opts.rst_namespaces_flags) {
+		pr_perror("Conflict flags: -join-ns and -namespace");
+		return -1;
+	}
+	if (join_ns_flag & opts.empty_ns) {
+		pr_perror("Conflict flags: -join-ns and -empty-ns");
+		return -1;
+	}
+	if (opts.empty_ns & opts.rst_namespaces_flags) {
+		pr_perror("Conflict flags: -empty-ns and -namespace");
+		return -1;
+	}
+	errno = 0;
+	return 0;
+}
+
+static int check_int_str(char *str) {
+	if (str == NULL)
+		return 0;
+
+	if (*str == '\0') {
+		str = NULL;
+		return 0;
+	}
+
+	char *endptr;
+	long val;
+	errno = 22;
+	val = strtol(str, &endptr, 10);
+	if ((errno == ERANGE) || (endptr == str)
+			|| (*endptr != '\0')
+			|| (val < 0) || (val > 65535)) {
+		pr_perror("Illegal input: %s", str);
+		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;
+	} else if (!strncmp(type, "uts", 4)) {
+			jn->nd = uts_ns_desc;
+	} else if (!strncmp(type, "ipc", 4)) {
+			jn->nd = ipc_ns_desc;
+	} else if (!strncmp(type, "pid", 4)) {
+			jn->nd = pid_ns_desc;
+	} 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;
+			}
+	} else if (!strncmp(type, "mnt", 4)) {
+			jn->nd = mnt_ns_desc;
+	} 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;
@@ -78,7 +240,7 @@ int switch_ns(int pid, struct ns_desc *nd, int *rst)

 	nsfd = open_proc(pid, "ns/%s", nd->str);
 	if (nsfd < 0) {
-		pr_perror("Can't open ipcns file");
+		pr_perror("Can't open ns file");
 		goto err_ns;
 	}

@@ -1371,6 +1533,150 @@ static int prepare_userns_creds()
 	return 0;
 }

+static int get_join_ns_fd(struct join_ns *jn) {
+	int pid, fd;
+	char nsf[32];
+	char *pnsf;
+
+	if (jn->ns_file == NULL) {
+		pr_perror("join-ns file is NULL");
+		return -1;
+	}
+
+	pid = atoi(jn->ns_file);
+	if (pid > 0) {
+		snprintf(nsf, sizeof(nsf), "/proc/%d/ns/%s", pid, jn->nd.str);
+		pnsf = nsf;
+	} else {
+		pnsf = jn->ns_file;
+	}
+
+	fd = open(pnsf, O_RDONLY);
+	if (fd < 0) {
+		pr_perror("Can't open ns file: %s", pnsf);
+		return -1;
+	}
+	jn->ns_fd = fd;
+	return 0;
+}
+
+static int close_join_ns_fd(struct join_ns *jn) {
+	if (jn->ns_fd < 0) {
+		return -1;
+	}
+
+	if (close_safe(&jn->ns_fd))
+		return -1;
+	return 0;
+}
+
+static int switch_join_ns(struct join_ns *jn) {
+	if (jn->ns_fd < 0) {
+		pr_perror("Invalid ns fd in join-ns");
+		return -1;
+	}
+
+	int ret = -1;
+	int self_fd;
+	struct stat st, self_st;
+	char buf[32];
+
+	if (fstat(jn->ns_fd, &st) == -1) {
+		pr_perror("Can't get ns file %s stat", jn->ns_file);
+		goto err_ns;
+	}
+
+	snprintf(buf, sizeof(buf), "/proc/self/ns/%s", jn->nd.str);
+	self_fd = open(buf, O_RDONLY);
+	if (self_fd < 0) {
+		pr_perror("Can't open ns file: %s", buf);
+		goto err_ns;
+	}
+	if (fstat(self_fd, &self_st) == -1) {
+		pr_perror("Can't get ns file %s stat", buf);
+		goto err_self;
+	}
+
+	if (st.st_ino != self_st.st_ino) {
+		ret = setns(jn->ns_fd, jn->nd.cflag);
+		if (ret < 0) {
+			pr_perror("Can't setns %s/%s", jn->ns_file, jn->nd.str);
+			goto err_self;
+		}
+	}
+	ret = 0;
+
+err_self:
+	close(self_fd);
+err_ns:
+	return ret;
+}
+
+static int switch_user_join_ns(struct join_ns *jn) {
+	if (jn == NULL) {
+		return 0;
+	}
+
+	uid_t uid;
+	gid_t gid;
+
+	if (switch_join_ns(jn))
+		return -1;
+
+	if (jn->extra_opts.user_extra.uid == NULL) {
+		uid = getuid();
+	} else {
+		uid = atoi(jn->extra_opts.user_extra.uid);
+	}
+	if (jn->extra_opts.user_extra.gid == NULL) {
+		gid = getgid();
+	} else {
+		gid = atoi(jn->extra_opts.user_extra.gid);
+	}
+
+	/* FIXME:
+	 * if err occurs in setuid/setgid, should we just alert or
+	 * return an error
+	 */
+	if (setuid(uid)) {
+		pr_perror("setuid failed while joining userns");
+		return -1;
+	}
+	if (setgid(gid)) {
+		pr_perror("setgid failed while joining userns");
+		return -1;
+	}
+	return 0;
+}
+
+int join_namespaces()
+{
+	struct join_ns *jn, *user_jn = NULL;
+	int ret = -1;
+
+	list_for_each_entry(jn, &opts.join_ns, list)
+		if (get_join_ns_fd(jn)) {
+			goto err_out;
+		}
+
+	list_for_each_entry(jn, &opts.join_ns, list)
+		if (!strncmp(jn->nd.str, "user", 5)) {
+			user_jn = jn;
+		} else {
+			if (switch_join_ns(jn))
+				goto err_out;
+		}
+
+	if (switch_user_join_ns(user_jn))
+		goto err_out;
+
+	ret = 0;
+err_out:
+	list_for_each_entry(jn, &opts.join_ns, list)
+		close_join_ns_fd(jn);
+	return ret;
+}
+
 int prepare_namespace(struct pstree_item *item, unsigned long clone_flags)
 {
 	pid_t pid = item->pid.virt;
diff --git a/criu/net.c b/criu/net.c
index a67cd6e..ba871c7 100644
--- a/criu/net.c
+++ b/criu/net.c
@@ -1094,8 +1094,11 @@ int prepare_net_ns(int pid)
 {
 	int ret = 0;
 	NetnsEntry *netns = NULL;
+	unsigned int join_ns_flag = 0;

-	if (!(opts.empty_ns & CLONE_NEWNET)) {
+	join_ns_flag = get_join_ns_flag();
+	if (!(opts.empty_ns & CLONE_NEWNET) ||
+			!(join_ns_flag & CLONE_NEWNET)) {
 		ret = restore_netns_conf(pid, &netns);
 		if (!ret)
 			ret = restore_links(pid, &netns);
diff --git a/images/rpc.proto b/images/rpc.proto
index fac4b9f..9ece4da 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 {
-- 
2.5.0join-ns will restore process with specified existing namespace.
This opt can be used in this fomat:
	--join-ns NS:PID|NS_FILE
for example --join-ns net:12345 or --join-ns net:/foo/bar.

pid namespaces is not supported yet. As fork() is needed to make
new pid-namespace work. That makes it hard for criu to track the
child-process through pid because another child process has been
created after fork().

Signed-off-by: Deng Guangxing <dengguangxing at huawei.com>
---
 criu/cr-restore.c         |   5 +
 criu/cr-service.c         |  10 ++
 criu/crtools.c            |  55 ++++++++-
 criu/include/cr_options.h |   1 +
 criu/include/namespaces.h |  24 ++++
 criu/namespaces.c         | 308 +++++++++++++++++++++++++++++++++++++++++++++-
 criu/net.c                |   5 +-
 images/rpc.proto          |   7 ++
 8 files changed, 412 insertions(+), 3 deletions(-)

diff --git a/criu/cr-restore.c b/criu/cr-restore.c
index 30ddff9..e3f0add 100644
--- a/criu/cr-restore.c
+++ b/criu/cr-restore.c
@@ -1566,6 +1566,11 @@ static int restore_task_with_children(void *_arg)

 	/* Restore root task */
 	if (current->parent == NULL) {
+		if (join_namespaces()) {
+			pr_perror("Join namespaces failed");
+			goto err;
+		}
+
 		if (restore_finish_stage(CR_STATE_RESTORE_NS) < 0)
 			goto err;

diff --git a/criu/cr-service.c b/criu/cr-service.c
index 88d4af7..e2725d0 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;

@@ -374,6 +375,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;
@@ -473,6 +479,10 @@ static int setup_opts_from_req(int sk, CriuOpts *req)
 		}
 	}

+	/* check namespace flags here */
+	if (check_namespace_opts())
+		goto err;
+
 	return 0;

 err:
diff --git a/criu/crtools.c b/criu/crtools.c
index d6e8672..66a1d44 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.veth_pairs);
 	INIT_LIST_HEAD(&opts.scripts);
 	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);
@@ -99,6 +101,41 @@ bad_ns:
 	return -1;
 }

+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';
+
+	if (!strncmp(ptr, "pid", 4)) {
+		pr_perror("pid namespace not supported in join-ns\n");
+		return -1;
+	} else if (strncmp(ptr, "net", 4) &&
+		strncmp(ptr, "uts", 4) &&
+		strncmp(ptr, "ipc", 4) &&
+		strncmp(ptr, "user", 5) &&
+		strncmp(ptr, "mnt", 4)) {
+		pr_perror("Illegal namespace %s in join-ns\n", ptr);
+		return -1;
+	}
+
+	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;
@@ -218,7 +255,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:n:v::x::Vr:jlW:L:M:";
+	static const char short_opts[] = "dSsRf:F:t:p:hcD:o:n:v::x::Vr:jJ:lW:L:M:";
 	static struct option long_opts[] = {
 		{ "tree",			required_argument,	0, 't'	},
 		{ "pid",			required_argument,	0, 'p'	},
@@ -234,6 +271,7 @@ int main(int argc, char *argv[], char *envp[])
 		{ "work-dir",			required_argument,	0, 'W'	},
 		{ "log-file",			required_argument,	0, 'o'	},
 		{ "namespaces",			required_argument,	0, 'n'	},
+		{ "join-ns",			required_argument,	0, 'J'	},
 		{ "root",			required_argument,	0, 'r'	},
 		{ USK_EXT_PARAM,		optional_argument,	0, 'x'	},
 		{ "help",			no_argument,		0, 'h'	},
@@ -366,6 +404,10 @@ int main(int argc, char *argv[], char *envp[])
 			if (parse_ns_string(optarg))
 				goto bad_arg;
 			break;
+		case 'J':
+			if (parse_join_ns(optarg))
+				goto bad_arg;
+			break;
 		case 'v':
 			if (log_level == LOG_UNSET)
 				log_level = 0;
@@ -567,6 +609,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;
@@ -810,6 +857,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"
 "\n"
 "* Logging:\n"
 "  -o|--log-file FILE    log file name\n"
diff --git a/criu/include/cr_options.h b/criu/include/cr_options.h
index a6f0b3e..cf3cb0e 100644
--- a/criu/include/cr_options.h
+++ b/criu/include/cr_options.h
@@ -82,6 +82,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 303c9e6..3729aed 100644
--- a/criu/include/namespaces.h
+++ b/criu/include/namespaces.h
@@ -3,6 +3,7 @@

 #include "compiler.h"
 #include "files.h"
+#include "list.h"

 /* including syscall-types.h gives another weird error; do we really need to
  * define this twice? */
@@ -12,6 +13,7 @@

 /* Nested namespaces are supported only for these types */
 #define CLONE_SUBNS	(CLONE_NEWNS)
+#define EXTRA_SIZE	20

 struct ns_desc {
 	unsigned int	cflag;
@@ -19,6 +21,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;
+	int			ns_fd; 	/* namespace file descriptor */
+	struct ns_desc		nd; 	/* namespace descriptor */
+	/* 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,
@@ -100,6 +120,10 @@ 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 join_namespaces();
+extern unsigned int get_join_ns_flag();
+extern int check_namespace_opts();

 typedef int (*uns_call_t)(void *arg, int fd, pid_t pid);
 /*
diff --git a/criu/namespaces.c b/criu/namespaces.c
index d7f8a9f..99e420a 100644
--- a/criu/namespaces.c
+++ b/criu/namespaces.c
@@ -10,7 +10,11 @@
 #include <signal.h>
 #include <sched.h>
 #include <sys/capability.h>
+#include <sys/stat.h>
+#include <limits.h>
+#include <errno.h>

+#include "cr_options.h"
 #include "util.h"
 #include "imgset.h"
 #include "uts_ns.h"
@@ -35,6 +39,164 @@ static struct ns_desc *ns_desc_array[] = {
 	&cgroup_ns_desc,
 };

+unsigned int get_join_ns_flag() {
+	struct join_ns *jn;
+	unsigned int join_ns_flags = 0;
+
+	list_for_each_entry(jn, &opts.join_ns, list)
+		if (!strncmp(jn->nd.str, "uts", 4))
+			join_ns_flags |= CLONE_NEWUTS;
+		else if (!strncmp(jn->nd.str, "ipc", 4))
+			join_ns_flags |= CLONE_NEWIPC;
+		else if (!strncmp(jn->nd.str, "mnt", 4))
+			join_ns_flags |= CLONE_NEWNS;
+		else if (!strncmp(jn->nd.str, "pid", 4))
+			join_ns_flags |= CLONE_NEWPID;
+		else if (!strncmp(jn->nd.str, "net", 4))
+			join_ns_flags |= CLONE_NEWNET;
+		else if (!strncmp(jn->nd.str, "user", 5))
+			join_ns_flags |= CLONE_NEWNET;
+
+	return join_ns_flags;
+}
+
+int check_namespace_opts() {
+	unsigned int join_ns_flag;
+
+	join_ns_flag = get_join_ns_flag();
+
+	errno = 22;
+	if (join_ns_flag & opts.rst_namespaces_flags) {
+		pr_perror("Conflict flags: -join-ns and -namespace");
+		return -1;
+	}
+	if (join_ns_flag & opts.empty_ns) {
+		pr_perror("Conflict flags: -join-ns and -empty-ns");
+		return -1;
+	}
+	if (opts.empty_ns & opts.rst_namespaces_flags) {
+		pr_perror("Conflict flags: -empty-ns and -namespace");
+		return -1;
+	}
+	errno = 0;
+	return 0;
+}
+
+static int check_int_str(char *str) {
+	if (str == NULL)
+		return 0;
+
+	if (*str == '\0') {
+		str = NULL;
+		return 0;
+	}
+
+	char *endptr;
+	long val;
+	errno = 22;
+	val = strtol(str, &endptr, 10);
+	if ((errno == ERANGE) || (endptr == str)
+			|| (*endptr != '\0')
+			|| (val < 0) || (val > 65535)) {
+		pr_perror("Illegal input: %s", str);
+		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;
+	} else if (!strncmp(type, "uts", 4)) {
+			jn->nd = uts_ns_desc;
+	} else if (!strncmp(type, "ipc", 4)) {
+			jn->nd = ipc_ns_desc;
+	} else if (!strncmp(type, "pid", 4)) {
+			jn->nd = pid_ns_desc;
+	} 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;
+			}
+	} else if (!strncmp(type, "mnt", 4)) {
+			jn->nd = mnt_ns_desc;
+	} 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;
@@ -78,7 +240,7 @@ int switch_ns(int pid, struct ns_desc *nd, int *rst)

 	nsfd = open_proc(pid, "ns/%s", nd->str);
 	if (nsfd < 0) {
-		pr_perror("Can't open ipcns file");
+		pr_perror("Can't open ns file");
 		goto err_ns;
 	}

@@ -1371,6 +1533,150 @@ static int prepare_userns_creds()
 	return 0;
 }

+static int get_join_ns_fd(struct join_ns *jn) {
+	int pid, fd;
+	char nsf[32];
+	char *pnsf;
+
+	if (jn->ns_file == NULL) {
+		pr_perror("join-ns file is NULL");
+		return -1;
+	}
+
+	pid = atoi(jn->ns_file);
+	if (pid > 0) {
+		snprintf(nsf, sizeof(nsf), "/proc/%d/ns/%s", pid, jn->nd.str);
+		pnsf = nsf;
+	} else {
+		pnsf = jn->ns_file;
+	}
+
+	fd = open(pnsf, O_RDONLY);
+	if (fd < 0) {
+		pr_perror("Can't open ns file: %s", pnsf);
+		return -1;
+	}
+	jn->ns_fd = fd;
+	return 0;
+}
+
+static int close_join_ns_fd(struct join_ns *jn) {
+	if (jn->ns_fd < 0) {
+		return -1;
+	}
+
+	if (close_safe(&jn->ns_fd))
+		return -1;
+	return 0;
+}
+
+static int switch_join_ns(struct join_ns *jn) {
+	if (jn->ns_fd < 0) {
+		pr_perror("Invalid ns fd in join-ns");
+		return -1;
+	}
+
+	int ret = -1;
+	int self_fd;
+	struct stat st, self_st;
+	char buf[32];
+
+	if (fstat(jn->ns_fd, &st) == -1) {
+		pr_perror("Can't get ns file %s stat", jn->ns_file);
+		goto err_ns;
+	}
+
+	snprintf(buf, sizeof(buf), "/proc/self/ns/%s", jn->nd.str);
+	self_fd = open(buf, O_RDONLY);
+	if (self_fd < 0) {
+		pr_perror("Can't open ns file: %s", buf);
+		goto err_ns;
+	}
+	if (fstat(self_fd, &self_st) == -1) {
+		pr_perror("Can't get ns file %s stat", buf);
+		goto err_self;
+	}
+
+	if (st.st_ino != self_st.st_ino) {
+		ret = setns(jn->ns_fd, jn->nd.cflag);
+		if (ret < 0) {
+			pr_perror("Can't setns %s/%s", jn->ns_file, jn->nd.str);
+			goto err_self;
+		}
+	}
+	ret = 0;
+
+err_self:
+	close(self_fd);
+err_ns:
+	return ret;
+}
+
+static int switch_user_join_ns(struct join_ns *jn) {
+	if (jn == NULL) {
+		return 0;
+	}
+
+	uid_t uid;
+	gid_t gid;
+
+	if (switch_join_ns(jn))
+		return -1;
+
+	if (jn->extra_opts.user_extra.uid == NULL) {
+		uid = getuid();
+	} else {
+		uid = atoi(jn->extra_opts.user_extra.uid);
+	}
+	if (jn->extra_opts.user_extra.gid == NULL) {
+		gid = getgid();
+	} else {
+		gid = atoi(jn->extra_opts.user_extra.gid);
+	}
+
+	/* FIXME:
+	 * if err occurs in setuid/setgid, should we just alert or
+	 * return an error
+	 */
+	if (setuid(uid)) {
+		pr_perror("setuid failed while joining userns");
+		return -1;
+	}
+	if (setgid(gid)) {
+		pr_perror("setgid failed while joining userns");
+		return -1;
+	}
+	return 0;
+}
+
+int join_namespaces()
+{
+	struct join_ns *jn, *user_jn = NULL;
+	int ret = -1;
+
+	list_for_each_entry(jn, &opts.join_ns, list)
+		if (get_join_ns_fd(jn)) {
+			goto err_out;
+		}
+
+	list_for_each_entry(jn, &opts.join_ns, list)
+		if (!strncmp(jn->nd.str, "user", 5)) {
+			user_jn = jn;
+		} else {
+			if (switch_join_ns(jn))
+				goto err_out;
+		}
+
+	if (switch_user_join_ns(user_jn))
+		goto err_out;
+
+	ret = 0;
+err_out:
+	list_for_each_entry(jn, &opts.join_ns, list)
+		close_join_ns_fd(jn);
+	return ret;
+}
+
 int prepare_namespace(struct pstree_item *item, unsigned long clone_flags)
 {
 	pid_t pid = item->pid.virt;
diff --git a/criu/net.c b/criu/net.c
index a67cd6e..ba871c7 100644
--- a/criu/net.c
+++ b/criu/net.c
@@ -1094,8 +1094,11 @@ int prepare_net_ns(int pid)
 {
 	int ret = 0;
 	NetnsEntry *netns = NULL;
+	unsigned int join_ns_flag = 0;

-	if (!(opts.empty_ns & CLONE_NEWNET)) {
+	join_ns_flag = get_join_ns_flag();
+	if (!(opts.empty_ns & CLONE_NEWNET) ||
+			!(join_ns_flag & CLONE_NEWNET)) {
 		ret = restore_netns_conf(pid, &netns);
 		if (!ret)
 			ret = restore_links(pid, &netns);
diff --git a/images/rpc.proto b/images/rpc.proto
index fac4b9f..9ece4da 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 {
-- 
2.5.0



More information about the CRIU mailing list