"""
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)
|