summaryrefslogtreecommitdiff
path: root/sandboxlib/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'sandboxlib/__init__.py')
-rw-r--r--sandboxlib/__init__.py105
1 files changed, 71 insertions, 34 deletions
diff --git a/sandboxlib/__init__.py b/sandboxlib/__init__.py
index 3aeaf1c..d620777 100644
--- a/sandboxlib/__init__.py
+++ b/sandboxlib/__init__.py
@@ -23,6 +23,7 @@ docstrings that describe the different parameters.
import logging
+import os
import platform
import shutil
import subprocess
@@ -58,8 +59,20 @@ def maximum_possible_isolation():
raise NotImplementedError()
-def run_sandbox(rootfs_path, command, cwd=None, extra_env=None,
- network='undefined'):
+
+# Special value for 'stderr' and 'stdout' parameters to indicate 'capture
+# and return the data'.
+CAPTURE = subprocess.PIPE
+
+# Special value for 'stderr' parameter to indicate 'forward to stdout'.
+STDOUT = subprocess.STDOUT
+
+
+def run_sandbox(command, cwd=None, extra_env=None,
+ filesystem_root='/', filesystem_writable_paths='all',
+ mounts='undefined', extra_mounts=None,
+ network='undefined',
+ stderr=CAPTURE, stdout=CAPTURE):
'''Run 'command' in a sandboxed environment.
Parameters:
@@ -89,29 +102,28 @@ def run_sandbox(rootfs_path, command, cwd=None, extra_env=None,
no attempt is made to either prevent or provide networking
inside the sandbox. Backends may support 'isolated' and/or other
values as well.
+ - stdout: whether to capture stdout, or redirect stdout to a file handle.
+ If set to sandboxlib.CAPTURE, the function will return the stdout
+ data, if not, it will return None for that. If stdout=None, the
+ data will be discarded -- it will NOT inherit the parent process's
+ stdout, unlike with subprocess.Popen(). Set 'stdout=sys.stdout' if
+ you want that.
+ - stderr: same as stdout
+
+ Returns:
+ a tuple of (exit code, stdout output, stderr output).
'''
raise NotImplementedError()
-def Popen(command, stderr=None, stdout=None, **sandbox_config):
- '''Start a subprocess in a sandbox and return straight away.
+def run_sandbox_with_redirection(command, **sandbox_config):
+ '''Start a subprocess in a sandbox, redirecting stderr and/or stdout.
- This function aims to function like subprocess.Popen(), but with the
- subprocess running inside a sandbox. It returns a subprocess.Popen
- instance as soon as 'command' starts executing.
+ The sandbox_config arguments are the same as the run_command() function.
- The 'stderr' and 'stdout' parameters accept None, a file-like object, a
- file descriptor (integer), or subprocess.PIPE. The only difference from
- the subprocess.Popen() function is that 'None' means 'ignore all output'
- rather than 'inherit parent's stdout': if you want to forward output from
- the subprocess to stdout, you must pass `stdout=sys.stdout`.
-
- The sandbox_config arguments are the same as the run_command() function. In
- most cases you should use run_command() instead of this, but there are
- certain cases where Popen() could be useful. The run_command() function
- buffers all data from stdout and stderr of the subprocess in memory, which
- is impractical if there is a huge amount of data.
+ This returns just the exit code, because if stdout or stderr are redirected
+ those values will be None in any case.
'''
raise NotImplementedError()
@@ -205,26 +217,51 @@ def validate_extra_mounts(extra_mounts):
return new_extra_mounts
-def _run_command(argv, cwd=None, env=None, preexec_fn=None):
+def _run_command(argv, stdout, stderr, cwd=None, env=None):
'''Wrapper around subprocess.Popen() with common settings.
- This function blocks until the subprocesses has terminated. It then
- returns a tuple of (exit code, stdout output, stderr output).
+ This function blocks until the subprocess has terminated.
+
+ Unlike the subprocess.Popen() function, if stdout or stderr are None then
+ output is discarded.
+
+ It then returns a tuple of (exit code, stdout output, stderr output).
+ If stdout was not equal to subprocess.PIPE, stdout will be None. Same for
+ stderr.
'''
- process = subprocess.Popen(
- argv,
- # The default is to share file descriptors from the parent process
- # to the subprocess, which is rarely good for sandboxing.
- close_fds=True,
- cwd=cwd,
- env=env,
- preexec_fn=preexec_fn,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE
- )
- process.wait()
- return process.returncode, process.stdout.read(), process.stderr.read()
+ if stdout is None or stderr is None:
+ dev_null = open(os.devnull, 'w')
+ stdout = stdout or dev_null
+ stderr = stderr or dev_null
+ else:
+ dev_null = None
+
+ try:
+ process = subprocess.Popen(
+ argv,
+ # The default is to share file descriptors from the parent process
+ # to the subprocess, which is rarely good for sandboxing.
+ close_fds=True,
+ cwd=cwd,
+ env=env,
+ stdout=stdout,
+ stderr=stderr,
+ )
+
+ # The 'out' variable will be None unless subprocess.PIPE was passed as
+ # 'stdout' to subprocess.Popen(). Same for 'err' and 'stderr'. If
+ # subprocess.PIPE wasn't passed for either it'd be safe to use .wait()
+ # instead of .communicate(), but if they were then we must use
+ # .communicate() to avoid blocking the subprocess if one of the pipes
+ # becomes full. It's safe to use .communicate() in all cases.
+
+ out, err = process.communicate()
+ finally:
+ if dev_null is not None:
+ dev_null.close()
+
+ return process.returncode, out, err
# Executors