File: child.py

package info (click to toggle)
python-ptrace 0.7-1
  • links: PTS
  • area: main
  • in suites: jessie, jessie-kfreebsd, stretch
  • size: 680 kB
  • ctags: 1,002
  • sloc: python: 6,659; ansic: 263; makefile: 13; sh: 1
file content (144 lines) | stat: -rw-r--r-- 3,897 bytes parent folder | download
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)