2
0
mirror of https://github.com/checkpoint-restore/criu synced 2025-08-25 19:38:38 +00:00
criu/scripts/criu-ns
Pavel Emelyanov 4c50326ed6 crns.py: New attempt to have --unshare option
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).

Signed-off-by: Pavel Emelyanov <xemul@virtuozzo.com>
Looks-good-to: Andrey Vagin <avagin@openvz.org>
2017-11-30 01:18:53 +03:00

241 lines
5.0 KiB
Python
Executable File

#!/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()
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)