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 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
|
"""
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)
|