Edit File: child.py
""" Error pipe and serialization code comes from Python 2.5 subprocess module. """ from os import ( fork, execvp, execvpe, waitpid, close, dup2, pipe, read, write, devnull, sysconf, set_inheritable) from sys import exc_info from traceback import format_exception from ptrace.binding import ptrace_traceme from ptrace import PtraceError from sys import exit from errno import EINTR import fcntl import pickle try: MAXFD = sysconf("SC_OPEN_MAX") except Exception: MAXFD = 256 class ChildError(RuntimeError): pass class ChildPtraceError(ChildError): pass def _set_cloexec_flag(fd): try: cloexec_flag = fcntl.FD_CLOEXEC except AttributeError: cloexec_flag = 1 old = fcntl.fcntl(fd, fcntl.F_GETFD) fcntl.fcntl(fd, fcntl.F_SETFD, old | cloexec_flag) def _waitpid_no_intr(pid, options): """Like os.waitpid, but retries on EINTR""" while True: try: return waitpid(pid, options) except OSError as e: if e.errno == EINTR: continue else: raise def _read_no_intr(fd, buffersize): """Like os.read, but retries on EINTR""" while True: try: return read(fd, buffersize) except OSError as e: if e.errno == EINTR: continue else: raise def _write_no_intr(fd, s): """Like os.write, but retries on EINTR""" while True: try: return write(fd, s) except OSError as e: if e.errno == EINTR: continue else: raise def _createParent(pid, errpipe_read): # Wait for exec to fail or succeed; possibly raising exception data = _read_no_intr(errpipe_read, 1048576) # Exceptions limited to 1 MB close(errpipe_read) if data: _waitpid_no_intr(pid, 0) child_exception = pickle.loads(data) raise child_exception def _createChild(arguments, no_stdout, env, errpipe_write, close_fds=True, pass_fds=()): # Child code try: ptrace_traceme() except PtraceError as err: raise ChildError(str(err)) for fd in pass_fds: set_inheritable(fd, True) if close_fds: # Close all files except 0, 1, 2 and errpipe_write for fd in range(3, MAXFD): if fd == errpipe_write or (fd in pass_fds): continue try: close(fd) except OSError: pass try: _execChild(arguments, no_stdout, env) except: # noqa: E722 exc_type, exc_value, tb = exc_info() # Save the traceback and attach it to the exception object exc_lines = format_exception(exc_type, exc_value, tb) exc_value.child_traceback = ''.join(exc_lines) _write_no_intr(errpipe_write, pickle.dumps(exc_value)) exit(255) def _execChild(arguments, no_stdout, env): if no_stdout: try: null = open(devnull, 'wb') dup2(null.fileno(), 1) dup2(1, 2) null.close() except IOError: close(2) close(1) try: if env is not None: execvpe(arguments[0], arguments, env) else: execvp(arguments[0], arguments) except Exception as err: raise ChildError(str(err)) def createChild(arguments, no_stdout, env=None, close_fds=True, pass_fds=()): """ Create a child process: - arguments: list of string where (e.g. ['ls', '-la']) - no_stdout: if True, use null device for stdout/stderr - env: environment variables dictionary Use: - env={} to start with an empty environment - env=None (default) to copy the environment """ if pass_fds and not close_fds: close_fds = True errpipe_read, errpipe_write = pipe() _set_cloexec_flag(errpipe_write) # Fork process pid = fork() if pid: close(errpipe_write) _createParent(pid, errpipe_read) return pid else: close(errpipe_read) _createChild(arguments, no_stdout, env, errpipe_write, close_fds=close_fds, pass_fds=pass_fds)