[CRIU] [PATCH RFC v2] crns.py: New attempt to have --unshare option

Andrew Vagin avagin at virtuozzo.com
Fri Aug 5 13:44:23 PDT 2016


On Wed, Aug 03, 2016 at 06:14:39PM +0300, Pavel Emelyanov wrote:
> So, here's the enhanced version of the first try.
> 
> Changes are:
> 
> 1. The wrapper name is criu-ns instead of crns.py
> 2. The CLI is absolutely the same as for criu, since the script
>    re-execl-s criu binary. E.g.
> 	   scripts/criu-ns dump -t 1234 ...
>    just works
> 3. Caller doesn't need to care about substituting CLI options,
>    instead, the scripts analyzes the command line and
>    a) replaces -t|--tree argument with virtual pid __if__ the
>       target task lives in another pidns
>    b) keeps the current cwd (and root) __if__ switches to another 
>       mntns. A limitation applies here -- cwd path should be the 
>       same in target ns, no "smart path mapping" is performed. So
>       this script is for now only useful for mntns clones (which
>       is our main goal at the moment).
>

This script looks good to me. Only one comment is inline.

Thanks!

> Signed-off-by: Pavel Emelyanov <xemul at virtuozzo.com>
> 
> ---
> 
> diff --git a/scripts/criu-ns b/scripts/criu-ns
> new file mode 100755
> index 0000000..e7ebbf0
> --- /dev/null
> +++ b/scripts/criu-ns
> @@ -0,0 +1,240 @@
> +#!/usr/bin/env python
> +import ctypes
> +import ctypes.util
> +import errno
> +import sys
> +import os
> +
> +# <sched.h> constants for unshare
> +CLONE_NEWNS = 0x00020000
> +CLONE_NEWPID = 0x20000000
> +
> +# <sys/mount.h> - constants for mount
> +MS_REC = 16384
> +MS_PRIVATE = 1 << 18
> +MS_SLAVE = 1 << 19
> +
> +# Load libc bindings
> +_libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)
> +
> +try:
> +	_unshare = _libc.unshare
> +except AttributeError:
> +	raise OSError(errno.EINVAL, "unshare is not supported on this platform")
> +else:
> +	_unshare.argtypes = [ ctypes.c_int ]
> +	_unshare.restype = ctypes.c_int
> +
> +try:
> +	_setns = _libc.setns
> +except AttributeError:
> +	raise OSError(errno.EINVAL, "setns is not supported on this platform")
> +else:
> +	_setns.argtypes = [ ctypes.c_int, ctypes.c_int ]
> +	_setns.restype = ctypes.c_int
> +
> +try:
> +	_mount = _libc.mount
> +except AttributeError:
> +	raise OSError(errno.EINVAL, "mount is not supported on this platform")
> +else:
> +	_mount.argtypes = [
> +		ctypes.c_char_p,
> +		ctypes.c_char_p,
> +		ctypes.c_char_p,
> +		ctypes.c_ulong,
> +		ctypes.c_void_p
> +	]
> +	_mount.restype = ctypes.c_int
> +
> +try:
> +	_umount = _libc.umount
> +except AttributeError:
> +	raise OSError(errno.EINVAL, "umount is not supported on this platform")
> +else:
> +	_umount.argtypes = [ctypes.c_char]
> +	_umount.restype = ctypes.c_int
> +
> +
> +def run_criu():
> +	print sys.argv
> +	os.execlp('criu', *['criu'] + sys.argv[1:])
> +
> +
> +def wrap_restore():
> +	# Unshare pid and mount namespaces
> +	if _unshare(CLONE_NEWNS | CLONE_NEWPID) != 0:
> +		_errno = ctypes.get_errno()
> +		raise OSError(_errno, errno.errorcode[_errno])
> +
> +	(r_pipe, w_pipe) = os.pipe()
> +
> +	# Spawn the init
> +	if os.fork() == 0:
> +		os.close(r_pipe)
> +
> +		# Mount new /proc
> +		if _mount(None, "/", None, MS_SLAVE|MS_REC, None) != 0:
> +			_errno = ctypes.get_errno()
> +			raise OSError(_errno, errno.errorcode[_errno])
> +
> +		if _mount('proc', '/proc', 'proc', 0, None) != 0:
> +			_errno = ctypes.get_errno()
> +			raise OSError(_errno, errno.errorcode[_errno])
> +
> +		# Spawn CRIU binary
> +		criu_pid = os.fork()

criu_pid can conflict with pid-s of restored tasks

> +		if criu_pid == 0:
> +			run_criu()
> +			raise OSError(errno.ENOENT, "No such command")
> +
> +		while True:
> +			try:
> +				(pid, status) = os.wait()
> +				if pid == criu_pid:
> +					status = os.WEXITSTATUS(status)
> +					break
> +			except OSError:
> +				status = -251
> +				break
> +
> +		os.write(w_pipe, "%d" % status)
> +		os.close(w_pipe)
> +
> +		if status != 0:
> +			sys.exit(status)
> +
> +		while True:
> +			try:
> +				os.wait()
> +			except OSError:
> +				break
> +
> +		sys.exit(0)
> +
> +	# Wait for CRIU to exit and report the status back
> +	os.close(w_pipe)
> +	status = os.read(r_pipe, 1024)
> +	if not status.isdigit():
> +		status_i = -252
> +	else:
> +		status_i = int(status)
> +
> +	return status_i
> +
> +
> +def get_varg(args):
> +	for i in xrange(1, len(sys.argv)):
> +		if not sys.argv[i] in args:
> +			continue
> +
> +		if i + 1 >= len(sys.argv):
> +			break
> +
> +		return (sys.argv[i + 1], i + 1)
> +
> +	return (None, None)
> +
> +
> +
> +def set_pidns(tpid, pid_idx):
> +	# Joind pid namespace. Note, that the given pid should
> +	# be changed in -t option, as task lives in different
> +	# pid namespace.
> +
> +	myns = os.stat('/proc/self/ns/pid').st_ino
> +
> +	ns_fd = os.open('/proc/%s/ns/pid' % tpid, os.O_RDONLY)
> +	if myns != os.fstat(ns_fd).st_ino:
> +
> +		for l in open('/proc/%s/status' % tpid):
> +			if not l.startswith('NSpid:'):
> +				continue
> +
> +			ls = l.split()
> +			if ls[1] != tpid:
> +				raise OSError(errno.ESRCH, 'No such pid')
> +
> +			print 'Replace pid %s with %s' % (tpid, ls[2])
> +			sys.argv[pid_idx] = ls[2]
> +			break
> +		else:
> +			raise OSError(errno.ENOENT, 'Cannot find NSpid field in proc')
> +
> +		if _setns(ns_fd, 0) != 0:
> +			_errno = ctypes.get_errno()
> +			raise OSError(_errno, errno.errorcode[_errno])
> +
> +	os.close(ns_fd)
> +
> +
> +def set_mntns(tpid):
> +	# Join mount namespace. Trick here too -- check / and .
> +	# will be the same in target mntns.
> +
> +	myns = os.stat('/proc/self/ns/mnt').st_ino
> +	ns_fd = os.open('/proc/%s/ns/mnt' % tpid, os.O_RDONLY)
> +	if myns != os.fstat(ns_fd).st_ino:
> +		root_st = os.stat('/')
> +		cwd_st = os.stat('.')
> +		cwd_path = os.path.realpath('.')
> +
> +		if _setns(ns_fd, 0) != 0:
> +			_errno = ctypes.get_errno()
> +			raise OSError(_errno, errno.errorcode[_errno])
> +
> +		os.chdir(cwd_path)
> +		root_nst = os.stat('/')
> +		cwd_nst = os.stat('.')
> +
> +		def steq(st, nst):
> +			return (st.st_dev, st.st_ino) == (nst.st_dev, nst.st_ino)
> +
> +		if not steq(root_st, root_nst):
> +			raise OSError(errno.EXDEV, 'Target ns / is not as current')
> +		if not steq(cwd_st, cwd_nst):
> +			raise OSError(errno.EXDEV, 'Target ns . is not as current')
> +
> +
> +	os.close(ns_fd)
> +
> +
> +def wrap_dump():
> +	(pid, pid_idx) = get_varg(('-t', '--tree'))
> +	if pid is None:
> +		raise OSError(errno.EINVAL, 'No --tree option given')
> +
> +	set_pidns(pid, pid_idx)
> +	set_mntns(pid)
> +
> +	# Spawn CRIU binary
> +	criu_pid = os.fork()
> +	if criu_pid == 0:
> +		run_criu()
> +		raise OSError(errno.ENOENT, "No such command")
> +
> +	# Wait for CRIU to exit and report the status back
> +	while True:
> +		try:
> +			(pid, status) = os.wait()
> +			if pid == criu_pid:
> +				status = os.WEXITSTATUS(status)
> +				break
> +		except OSError:
> +			status = -251
> +			break
> +
> +	return status
> +
> +
> +action = sys.argv[1]
> +
> +if action == 'restore':
> +	res = wrap_restore()
> +elif action == 'dump' or action == 'pre-dump':
> +	res = wrap_dump()
> +else:
> +	print 'Unsupported action %s for nswrap' % action
> +	res = -1
> +
> +sys.exit(res)
> 
> _______________________________________________
> CRIU mailing list
> CRIU at openvz.org
> https://lists.openvz.org/mailman/listinfo/criu


More information about the CRIU mailing list