diff options
Diffstat (limited to 'sandboxlib/chroot.py')
-rw-r--r-- | sandboxlib/chroot.py | 70 |
1 files changed, 48 insertions, 22 deletions
diff --git a/sandboxlib/chroot.py b/sandboxlib/chroot.py index 8862b13..986c7c2 100644 --- a/sandboxlib/chroot.py +++ b/sandboxlib/chroot.py @@ -32,9 +32,8 @@ that the sandbox contains a shell and we do some hack like running ''' +import multiprocessing import os -import subprocess -import sys import sandboxlib @@ -56,6 +55,39 @@ def process_network_config(network): "Network sharing cannot be be configured in this backend." % network +def _run_command_in_chroot(pipe, rootfs_path, command, cwd, env): + # This function should be run in a multiprocessing.Process() subprocess, + # because it calls os.chroot(). There's no 'unchroot()' function! After + # chrooting, it calls sandboxlib._run_command(), which uses the + # 'subprocess' module to exec 'command'. This means there are actually + # two subprocesses, which is not ideal, but it seems to be the simplest + # implementation. + # + # An alternative approach would be to use the 'preexec_fn' feature of + # subprocess.Popen() to call os.chroot(rootfs_path) and os.chdir(cwd). + # The Python 3 '_posixsubprocess' module hints in several places that + # deadlocks can occur when using preexec_fn, and it is very difficult to + # propagate exceptions from that function, so it seems best to avoid it. + + try: + # You have most likely got to be the 'root' user in order for this to + # work. + try: + os.chroot(rootfs_path) + except OSError as e: + raise RuntimeError("Unable to chroot: %s" % e) + + if cwd is not None: + os.chdir(cwd) + + exit, out, err = sandboxlib._run_command(command, env=env) + pipe.send([exit, out, err]) + os._exit(0) + except Exception as e: + pipe.send(e) + os._exit(1) + + def run_sandbox(rootfs_path, command, cwd=None, extra_env=None, network='undefined'): if type(command) == str: @@ -65,25 +97,19 @@ def run_sandbox(rootfs_path, command, cwd=None, extra_env=None, process_network_config(network) - pid = os.fork() - if pid == 0: - # Child process. It's a bit messy that we create a child process and - # then a second child process, but it saves duplicating stuff from the - # 'subprocess' module. + pipe_parent, pipe_child = multiprocessing.Pipe() - # FIXME: you gotta be root for this one. - try: - try: - os.chroot(rootfs_path) - except OSError as e: - raise RuntimeError("Unable to chroot: %s" % e) - - result = subprocess.call(command, cwd=cwd, env=env) - except Exception as e: - print("ERROR: %s" % e) - result = 255 - finally: - os._exit(result) + process = multiprocessing.Process( + target=_run_command_in_chroot, + args=(pipe_child, rootfs_path, command, cwd, env)) + process.start() + process.join() + + if process.exitcode == 0: + exit, out, err = pipe_parent.recv() + return exit, out, err else: - # Parent process. Wait for child to exit. - os.waitpid(pid, 0) + # Note that no effort is made to pass on the original traceback, which + # will be within the _run_command_in_chroot() function somewhere. + exception = pipe_parent.recv() + raise exception |