1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
|
"""
Error pipe and serialization code comes from Python 2.5 subprocess module.
"""
from os import (
fork, execv, execve, waitpid,
close, dup2, pipe,
read, write, devnull)
from sys import exc_info
from traceback import format_exception
from ptrace.os_tools import RUNNING_WINDOWS
from subprocess import MAXFD
from ptrace.binding import ptrace_traceme
from ptrace import PtraceError
from sys import exit
from errno import EINTR
import fcntl
import pickle
class ChildError(RuntimeError):
pass
class ChildPtraceError(ChildError):
pass
def _set_cloexec_flag(fd):
if RUNNING_WINDOWS:
return
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):
# Child code
try:
ptrace_traceme()
except PtraceError as err:
raise ChildError(str(err))
# Close all files except 0, 1, 2 and errpipe_write
for fd in range(3, MAXFD):
if fd == errpipe_write:
continue
try:
close(fd)
except OSError:
pass
try:
_execChild(arguments, no_stdout, env)
except:
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 as err:
close(2)
close(1)
try:
if env is not None:
execve(arguments[0], arguments, env)
else:
execv(arguments[0], arguments)
except Exception as err:
raise ChildError(str(err))
def createChild(arguments, no_stdout, env=None):
"""
Create a child process:
- arguments: list of string where (eg. ['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
"""
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)
|