-
Notifications
You must be signed in to change notification settings - Fork 54
Expand file tree
/
Copy pathchild.py
More file actions
187 lines (153 loc) · 4.6 KB
/
child.py
File metadata and controls
187 lines (153 loc) · 4.6 KB
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
"""
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, listdir, stat)
from sys import exc_info
from traceback import format_exception
from ptrace.os_tools import RUNNING_WINDOWS, RUNNING_FREEBSD, HAS_PROC
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:
MAXFD = 256
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 _closeFdsExcept(fds, ignore_fds):
for fd in fds:
if fd not in ignore_fds:
try:
close(fd)
except OSError:
pass
def _closeFds(ignore_fds):
path = None
if HAS_PROC:
path = '/proc/self/fd'
elif _hasDevFd():
path = '/dev/fd'
if path:
try:
_closeFdsExcept([int(i) for i in listdir(path)], ignore_fds)
return
except OSError:
pass
_closeFdsExcept(range(0, MAXFD), ignore_fds)
def _hasDevFd():
try:
# have fdescfs if /dev/fd is on different device
# see https://github.com/python/cpython/blob/master/Modules/_posixsubprocess.c
return RUNNING_FREEBSD and stat("/dev/fd/").st_dev != stat("/dev/").st_dev
except OSError:
return False
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
_closeFds([0, 1, 2, errpipe_write])
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:
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):
"""
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
"""
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)