##// END OF EJS Templates
procutil: introduce pseudo file object that just raises EBADF...
Yuya Nishihara -
r46790:a1601ff3 default
parent child Browse files
Show More
@@ -1,792 +1,801 b''
1 # procutil.py - utility for managing processes and executable environment
1 # procutil.py - utility for managing processes and executable environment
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 import contextlib
12 import contextlib
13 import errno
13 import errno
14 import io
14 import io
15 import os
15 import os
16 import signal
16 import signal
17 import subprocess
17 import subprocess
18 import sys
18 import sys
19 import threading
19 import threading
20 import time
20 import time
21
21
22 from ..i18n import _
22 from ..i18n import _
23 from ..pycompat import (
23 from ..pycompat import (
24 getattr,
24 getattr,
25 open,
25 open,
26 )
26 )
27
27
28 from .. import (
28 from .. import (
29 encoding,
29 encoding,
30 error,
30 error,
31 policy,
31 policy,
32 pycompat,
32 pycompat,
33 )
33 )
34
34
35 # Import like this to keep import-checker happy
35 # Import like this to keep import-checker happy
36 from ..utils import resourceutil
36 from ..utils import resourceutil
37
37
38 osutil = policy.importmod('osutil')
38 osutil = policy.importmod('osutil')
39
39
40 if pycompat.iswindows:
40 if pycompat.iswindows:
41 from .. import windows as platform
41 from .. import windows as platform
42 else:
42 else:
43 from .. import posix as platform
43 from .. import posix as platform
44
44
45
45
46 def isatty(fp):
46 def isatty(fp):
47 try:
47 try:
48 return fp.isatty()
48 return fp.isatty()
49 except AttributeError:
49 except AttributeError:
50 return False
50 return False
51
51
52
52
53 class BadFile(io.RawIOBase):
54 """Dummy file object to simulate closed stdio behavior"""
55
56 def readinto(self, b):
57 raise IOError(errno.EBADF, 'Bad file descriptor')
58
59 def write(self, b):
60 raise IOError(errno.EBADF, 'Bad file descriptor')
61
62
53 class LineBufferedWrapper(object):
63 class LineBufferedWrapper(object):
54 def __init__(self, orig):
64 def __init__(self, orig):
55 self.orig = orig
65 self.orig = orig
56
66
57 def __getattr__(self, attr):
67 def __getattr__(self, attr):
58 return getattr(self.orig, attr)
68 return getattr(self.orig, attr)
59
69
60 def write(self, s):
70 def write(self, s):
61 orig = self.orig
71 orig = self.orig
62 res = orig.write(s)
72 res = orig.write(s)
63 if s.endswith(b'\n'):
73 if s.endswith(b'\n'):
64 orig.flush()
74 orig.flush()
65 return res
75 return res
66
76
67
77
68 io.BufferedIOBase.register(LineBufferedWrapper)
78 io.BufferedIOBase.register(LineBufferedWrapper)
69
79
70
80
71 def make_line_buffered(stream):
81 def make_line_buffered(stream):
72 if pycompat.ispy3 and not isinstance(stream, io.BufferedIOBase):
82 if pycompat.ispy3 and not isinstance(stream, io.BufferedIOBase):
73 # On Python 3, buffered streams can be expected to subclass
83 # On Python 3, buffered streams can be expected to subclass
74 # BufferedIOBase. This is definitively the case for the streams
84 # BufferedIOBase. This is definitively the case for the streams
75 # initialized by the interpreter. For unbuffered streams, we don't need
85 # initialized by the interpreter. For unbuffered streams, we don't need
76 # to emulate line buffering.
86 # to emulate line buffering.
77 return stream
87 return stream
78 if isinstance(stream, LineBufferedWrapper):
88 if isinstance(stream, LineBufferedWrapper):
79 return stream
89 return stream
80 return LineBufferedWrapper(stream)
90 return LineBufferedWrapper(stream)
81
91
82
92
83 def unwrap_line_buffered(stream):
93 def unwrap_line_buffered(stream):
84 if isinstance(stream, LineBufferedWrapper):
94 if isinstance(stream, LineBufferedWrapper):
85 assert not isinstance(stream.orig, LineBufferedWrapper)
95 assert not isinstance(stream.orig, LineBufferedWrapper)
86 return stream.orig
96 return stream.orig
87 return stream
97 return stream
88
98
89
99
90 class WriteAllWrapper(object):
100 class WriteAllWrapper(object):
91 def __init__(self, orig):
101 def __init__(self, orig):
92 self.orig = orig
102 self.orig = orig
93
103
94 def __getattr__(self, attr):
104 def __getattr__(self, attr):
95 return getattr(self.orig, attr)
105 return getattr(self.orig, attr)
96
106
97 def write(self, s):
107 def write(self, s):
98 write1 = self.orig.write
108 write1 = self.orig.write
99 m = memoryview(s)
109 m = memoryview(s)
100 total_to_write = len(s)
110 total_to_write = len(s)
101 total_written = 0
111 total_written = 0
102 while total_written < total_to_write:
112 while total_written < total_to_write:
103 total_written += write1(m[total_written:])
113 total_written += write1(m[total_written:])
104 return total_written
114 return total_written
105
115
106
116
107 io.IOBase.register(WriteAllWrapper)
117 io.IOBase.register(WriteAllWrapper)
108
118
109
119
110 def _make_write_all(stream):
120 def _make_write_all(stream):
111 assert pycompat.ispy3
121 assert pycompat.ispy3
112 if isinstance(stream, WriteAllWrapper):
122 if isinstance(stream, WriteAllWrapper):
113 return stream
123 return stream
114 if isinstance(stream, io.BufferedIOBase):
124 if isinstance(stream, io.BufferedIOBase):
115 # The io.BufferedIOBase.write() contract guarantees that all data is
125 # The io.BufferedIOBase.write() contract guarantees that all data is
116 # written.
126 # written.
117 return stream
127 return stream
118 # In general, the write() method of streams is free to write only part of
128 # In general, the write() method of streams is free to write only part of
119 # the data.
129 # the data.
120 return WriteAllWrapper(stream)
130 return WriteAllWrapper(stream)
121
131
122
132
123 if pycompat.ispy3:
133 if pycompat.ispy3:
124 # Python 3 implements its own I/O streams.
134 # Python 3 implements its own I/O streams.
125 # TODO: .buffer might not exist if std streams were replaced; we'll need
135 # TODO: .buffer might not exist if std streams were replaced; we'll need
126 # a silly wrapper to make a bytes stream backed by a unicode one.
136 # a silly wrapper to make a bytes stream backed by a unicode one.
127
137
128 # sys.stdin can be None
138 # sys.stdin can be None
129 if sys.stdin:
139 if sys.stdin:
130 stdin = sys.stdin.buffer
140 stdin = sys.stdin.buffer
131 else:
141 else:
132 stdin = open(os.devnull, 'rb')
142 stdin = BadFile()
133 os.close(stdin.fileno())
134 stdout = _make_write_all(sys.stdout.buffer)
143 stdout = _make_write_all(sys.stdout.buffer)
135 stderr = _make_write_all(sys.stderr.buffer)
144 stderr = _make_write_all(sys.stderr.buffer)
136 if pycompat.iswindows:
145 if pycompat.iswindows:
137 # Work around Windows bugs.
146 # Work around Windows bugs.
138 stdout = platform.winstdout(stdout)
147 stdout = platform.winstdout(stdout)
139 stderr = platform.winstdout(stderr)
148 stderr = platform.winstdout(stderr)
140 if isatty(stdout):
149 if isatty(stdout):
141 # The standard library doesn't offer line-buffered binary streams.
150 # The standard library doesn't offer line-buffered binary streams.
142 stdout = make_line_buffered(stdout)
151 stdout = make_line_buffered(stdout)
143 else:
152 else:
144 # Python 2 uses the I/O streams provided by the C library.
153 # Python 2 uses the I/O streams provided by the C library.
145 stdin = sys.stdin
154 stdin = sys.stdin
146 stdout = sys.stdout
155 stdout = sys.stdout
147 stderr = sys.stderr
156 stderr = sys.stderr
148 if pycompat.iswindows:
157 if pycompat.iswindows:
149 # Work around Windows bugs.
158 # Work around Windows bugs.
150 stdout = platform.winstdout(stdout)
159 stdout = platform.winstdout(stdout)
151 stderr = platform.winstdout(stderr)
160 stderr = platform.winstdout(stderr)
152 if isatty(stdout):
161 if isatty(stdout):
153 if pycompat.iswindows:
162 if pycompat.iswindows:
154 # The Windows C runtime library doesn't support line buffering.
163 # The Windows C runtime library doesn't support line buffering.
155 stdout = make_line_buffered(stdout)
164 stdout = make_line_buffered(stdout)
156 else:
165 else:
157 # glibc determines buffering on first write to stdout - if we
166 # glibc determines buffering on first write to stdout - if we
158 # replace a TTY destined stdout with a pipe destined stdout (e.g.
167 # replace a TTY destined stdout with a pipe destined stdout (e.g.
159 # pager), we want line buffering.
168 # pager), we want line buffering.
160 stdout = os.fdopen(stdout.fileno(), 'wb', 1)
169 stdout = os.fdopen(stdout.fileno(), 'wb', 1)
161
170
162
171
163 findexe = platform.findexe
172 findexe = platform.findexe
164 _gethgcmd = platform.gethgcmd
173 _gethgcmd = platform.gethgcmd
165 getuser = platform.getuser
174 getuser = platform.getuser
166 getpid = os.getpid
175 getpid = os.getpid
167 hidewindow = platform.hidewindow
176 hidewindow = platform.hidewindow
168 readpipe = platform.readpipe
177 readpipe = platform.readpipe
169 setbinary = platform.setbinary
178 setbinary = platform.setbinary
170 setsignalhandler = platform.setsignalhandler
179 setsignalhandler = platform.setsignalhandler
171 shellquote = platform.shellquote
180 shellquote = platform.shellquote
172 shellsplit = platform.shellsplit
181 shellsplit = platform.shellsplit
173 spawndetached = platform.spawndetached
182 spawndetached = platform.spawndetached
174 sshargs = platform.sshargs
183 sshargs = platform.sshargs
175 testpid = platform.testpid
184 testpid = platform.testpid
176
185
177 try:
186 try:
178 setprocname = osutil.setprocname
187 setprocname = osutil.setprocname
179 except AttributeError:
188 except AttributeError:
180 pass
189 pass
181 try:
190 try:
182 unblocksignal = osutil.unblocksignal
191 unblocksignal = osutil.unblocksignal
183 except AttributeError:
192 except AttributeError:
184 pass
193 pass
185
194
186 closefds = pycompat.isposix
195 closefds = pycompat.isposix
187
196
188
197
189 def explainexit(code):
198 def explainexit(code):
190 """return a message describing a subprocess status
199 """return a message describing a subprocess status
191 (codes from kill are negative - not os.system/wait encoding)"""
200 (codes from kill are negative - not os.system/wait encoding)"""
192 if code >= 0:
201 if code >= 0:
193 return _(b"exited with status %d") % code
202 return _(b"exited with status %d") % code
194 return _(b"killed by signal %d") % -code
203 return _(b"killed by signal %d") % -code
195
204
196
205
197 class _pfile(object):
206 class _pfile(object):
198 """File-like wrapper for a stream opened by subprocess.Popen()"""
207 """File-like wrapper for a stream opened by subprocess.Popen()"""
199
208
200 def __init__(self, proc, fp):
209 def __init__(self, proc, fp):
201 self._proc = proc
210 self._proc = proc
202 self._fp = fp
211 self._fp = fp
203
212
204 def close(self):
213 def close(self):
205 # unlike os.popen(), this returns an integer in subprocess coding
214 # unlike os.popen(), this returns an integer in subprocess coding
206 self._fp.close()
215 self._fp.close()
207 return self._proc.wait()
216 return self._proc.wait()
208
217
209 def __iter__(self):
218 def __iter__(self):
210 return iter(self._fp)
219 return iter(self._fp)
211
220
212 def __getattr__(self, attr):
221 def __getattr__(self, attr):
213 return getattr(self._fp, attr)
222 return getattr(self._fp, attr)
214
223
215 def __enter__(self):
224 def __enter__(self):
216 return self
225 return self
217
226
218 def __exit__(self, exc_type, exc_value, exc_tb):
227 def __exit__(self, exc_type, exc_value, exc_tb):
219 self.close()
228 self.close()
220
229
221
230
222 def popen(cmd, mode=b'rb', bufsize=-1):
231 def popen(cmd, mode=b'rb', bufsize=-1):
223 if mode == b'rb':
232 if mode == b'rb':
224 return _popenreader(cmd, bufsize)
233 return _popenreader(cmd, bufsize)
225 elif mode == b'wb':
234 elif mode == b'wb':
226 return _popenwriter(cmd, bufsize)
235 return _popenwriter(cmd, bufsize)
227 raise error.ProgrammingError(b'unsupported mode: %r' % mode)
236 raise error.ProgrammingError(b'unsupported mode: %r' % mode)
228
237
229
238
230 def _popenreader(cmd, bufsize):
239 def _popenreader(cmd, bufsize):
231 p = subprocess.Popen(
240 p = subprocess.Popen(
232 tonativestr(cmd),
241 tonativestr(cmd),
233 shell=True,
242 shell=True,
234 bufsize=bufsize,
243 bufsize=bufsize,
235 close_fds=closefds,
244 close_fds=closefds,
236 stdout=subprocess.PIPE,
245 stdout=subprocess.PIPE,
237 )
246 )
238 return _pfile(p, p.stdout)
247 return _pfile(p, p.stdout)
239
248
240
249
241 def _popenwriter(cmd, bufsize):
250 def _popenwriter(cmd, bufsize):
242 p = subprocess.Popen(
251 p = subprocess.Popen(
243 tonativestr(cmd),
252 tonativestr(cmd),
244 shell=True,
253 shell=True,
245 bufsize=bufsize,
254 bufsize=bufsize,
246 close_fds=closefds,
255 close_fds=closefds,
247 stdin=subprocess.PIPE,
256 stdin=subprocess.PIPE,
248 )
257 )
249 return _pfile(p, p.stdin)
258 return _pfile(p, p.stdin)
250
259
251
260
252 def popen2(cmd, env=None):
261 def popen2(cmd, env=None):
253 # Setting bufsize to -1 lets the system decide the buffer size.
262 # Setting bufsize to -1 lets the system decide the buffer size.
254 # The default for bufsize is 0, meaning unbuffered. This leads to
263 # The default for bufsize is 0, meaning unbuffered. This leads to
255 # poor performance on Mac OS X: http://bugs.python.org/issue4194
264 # poor performance on Mac OS X: http://bugs.python.org/issue4194
256 p = subprocess.Popen(
265 p = subprocess.Popen(
257 tonativestr(cmd),
266 tonativestr(cmd),
258 shell=True,
267 shell=True,
259 bufsize=-1,
268 bufsize=-1,
260 close_fds=closefds,
269 close_fds=closefds,
261 stdin=subprocess.PIPE,
270 stdin=subprocess.PIPE,
262 stdout=subprocess.PIPE,
271 stdout=subprocess.PIPE,
263 env=tonativeenv(env),
272 env=tonativeenv(env),
264 )
273 )
265 return p.stdin, p.stdout
274 return p.stdin, p.stdout
266
275
267
276
268 def popen3(cmd, env=None):
277 def popen3(cmd, env=None):
269 stdin, stdout, stderr, p = popen4(cmd, env)
278 stdin, stdout, stderr, p = popen4(cmd, env)
270 return stdin, stdout, stderr
279 return stdin, stdout, stderr
271
280
272
281
273 def popen4(cmd, env=None, bufsize=-1):
282 def popen4(cmd, env=None, bufsize=-1):
274 p = subprocess.Popen(
283 p = subprocess.Popen(
275 tonativestr(cmd),
284 tonativestr(cmd),
276 shell=True,
285 shell=True,
277 bufsize=bufsize,
286 bufsize=bufsize,
278 close_fds=closefds,
287 close_fds=closefds,
279 stdin=subprocess.PIPE,
288 stdin=subprocess.PIPE,
280 stdout=subprocess.PIPE,
289 stdout=subprocess.PIPE,
281 stderr=subprocess.PIPE,
290 stderr=subprocess.PIPE,
282 env=tonativeenv(env),
291 env=tonativeenv(env),
283 )
292 )
284 return p.stdin, p.stdout, p.stderr, p
293 return p.stdin, p.stdout, p.stderr, p
285
294
286
295
287 def pipefilter(s, cmd):
296 def pipefilter(s, cmd):
288 '''filter string S through command CMD, returning its output'''
297 '''filter string S through command CMD, returning its output'''
289 p = subprocess.Popen(
298 p = subprocess.Popen(
290 tonativestr(cmd),
299 tonativestr(cmd),
291 shell=True,
300 shell=True,
292 close_fds=closefds,
301 close_fds=closefds,
293 stdin=subprocess.PIPE,
302 stdin=subprocess.PIPE,
294 stdout=subprocess.PIPE,
303 stdout=subprocess.PIPE,
295 )
304 )
296 pout, perr = p.communicate(s)
305 pout, perr = p.communicate(s)
297 return pout
306 return pout
298
307
299
308
300 def tempfilter(s, cmd):
309 def tempfilter(s, cmd):
301 """filter string S through a pair of temporary files with CMD.
310 """filter string S through a pair of temporary files with CMD.
302 CMD is used as a template to create the real command to be run,
311 CMD is used as a template to create the real command to be run,
303 with the strings INFILE and OUTFILE replaced by the real names of
312 with the strings INFILE and OUTFILE replaced by the real names of
304 the temporary files generated."""
313 the temporary files generated."""
305 inname, outname = None, None
314 inname, outname = None, None
306 try:
315 try:
307 infd, inname = pycompat.mkstemp(prefix=b'hg-filter-in-')
316 infd, inname = pycompat.mkstemp(prefix=b'hg-filter-in-')
308 fp = os.fdopen(infd, 'wb')
317 fp = os.fdopen(infd, 'wb')
309 fp.write(s)
318 fp.write(s)
310 fp.close()
319 fp.close()
311 outfd, outname = pycompat.mkstemp(prefix=b'hg-filter-out-')
320 outfd, outname = pycompat.mkstemp(prefix=b'hg-filter-out-')
312 os.close(outfd)
321 os.close(outfd)
313 cmd = cmd.replace(b'INFILE', inname)
322 cmd = cmd.replace(b'INFILE', inname)
314 cmd = cmd.replace(b'OUTFILE', outname)
323 cmd = cmd.replace(b'OUTFILE', outname)
315 code = system(cmd)
324 code = system(cmd)
316 if pycompat.sysplatform == b'OpenVMS' and code & 1:
325 if pycompat.sysplatform == b'OpenVMS' and code & 1:
317 code = 0
326 code = 0
318 if code:
327 if code:
319 raise error.Abort(
328 raise error.Abort(
320 _(b"command '%s' failed: %s") % (cmd, explainexit(code))
329 _(b"command '%s' failed: %s") % (cmd, explainexit(code))
321 )
330 )
322 with open(outname, b'rb') as fp:
331 with open(outname, b'rb') as fp:
323 return fp.read()
332 return fp.read()
324 finally:
333 finally:
325 try:
334 try:
326 if inname:
335 if inname:
327 os.unlink(inname)
336 os.unlink(inname)
328 except OSError:
337 except OSError:
329 pass
338 pass
330 try:
339 try:
331 if outname:
340 if outname:
332 os.unlink(outname)
341 os.unlink(outname)
333 except OSError:
342 except OSError:
334 pass
343 pass
335
344
336
345
337 _filtertable = {
346 _filtertable = {
338 b'tempfile:': tempfilter,
347 b'tempfile:': tempfilter,
339 b'pipe:': pipefilter,
348 b'pipe:': pipefilter,
340 }
349 }
341
350
342
351
343 def filter(s, cmd):
352 def filter(s, cmd):
344 """filter a string through a command that transforms its input to its
353 """filter a string through a command that transforms its input to its
345 output"""
354 output"""
346 for name, fn in pycompat.iteritems(_filtertable):
355 for name, fn in pycompat.iteritems(_filtertable):
347 if cmd.startswith(name):
356 if cmd.startswith(name):
348 return fn(s, cmd[len(name) :].lstrip())
357 return fn(s, cmd[len(name) :].lstrip())
349 return pipefilter(s, cmd)
358 return pipefilter(s, cmd)
350
359
351
360
352 _hgexecutable = None
361 _hgexecutable = None
353
362
354
363
355 def hgexecutable():
364 def hgexecutable():
356 """return location of the 'hg' executable.
365 """return location of the 'hg' executable.
357
366
358 Defaults to $HG or 'hg' in the search path.
367 Defaults to $HG or 'hg' in the search path.
359 """
368 """
360 if _hgexecutable is None:
369 if _hgexecutable is None:
361 hg = encoding.environ.get(b'HG')
370 hg = encoding.environ.get(b'HG')
362 mainmod = sys.modules['__main__']
371 mainmod = sys.modules['__main__']
363 if hg:
372 if hg:
364 _sethgexecutable(hg)
373 _sethgexecutable(hg)
365 elif resourceutil.mainfrozen():
374 elif resourceutil.mainfrozen():
366 if getattr(sys, 'frozen', None) == 'macosx_app':
375 if getattr(sys, 'frozen', None) == 'macosx_app':
367 # Env variable set by py2app
376 # Env variable set by py2app
368 _sethgexecutable(encoding.environ[b'EXECUTABLEPATH'])
377 _sethgexecutable(encoding.environ[b'EXECUTABLEPATH'])
369 else:
378 else:
370 _sethgexecutable(pycompat.sysexecutable)
379 _sethgexecutable(pycompat.sysexecutable)
371 elif (
380 elif (
372 not pycompat.iswindows
381 not pycompat.iswindows
373 and os.path.basename(getattr(mainmod, '__file__', '')) == 'hg'
382 and os.path.basename(getattr(mainmod, '__file__', '')) == 'hg'
374 ):
383 ):
375 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
384 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
376 else:
385 else:
377 _sethgexecutable(
386 _sethgexecutable(
378 findexe(b'hg') or os.path.basename(pycompat.sysargv[0])
387 findexe(b'hg') or os.path.basename(pycompat.sysargv[0])
379 )
388 )
380 return _hgexecutable
389 return _hgexecutable
381
390
382
391
383 def _sethgexecutable(path):
392 def _sethgexecutable(path):
384 """set location of the 'hg' executable"""
393 """set location of the 'hg' executable"""
385 global _hgexecutable
394 global _hgexecutable
386 _hgexecutable = path
395 _hgexecutable = path
387
396
388
397
389 def _testfileno(f, stdf):
398 def _testfileno(f, stdf):
390 fileno = getattr(f, 'fileno', None)
399 fileno = getattr(f, 'fileno', None)
391 try:
400 try:
392 return fileno and fileno() == stdf.fileno()
401 return fileno and fileno() == stdf.fileno()
393 except io.UnsupportedOperation:
402 except io.UnsupportedOperation:
394 return False # fileno() raised UnsupportedOperation
403 return False # fileno() raised UnsupportedOperation
395
404
396
405
397 def isstdin(f):
406 def isstdin(f):
398 return _testfileno(f, sys.__stdin__)
407 return _testfileno(f, sys.__stdin__)
399
408
400
409
401 def isstdout(f):
410 def isstdout(f):
402 return _testfileno(f, sys.__stdout__)
411 return _testfileno(f, sys.__stdout__)
403
412
404
413
405 def protectstdio(uin, uout):
414 def protectstdio(uin, uout):
406 """Duplicate streams and redirect original if (uin, uout) are stdio
415 """Duplicate streams and redirect original if (uin, uout) are stdio
407
416
408 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
417 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
409 redirected to stderr so the output is still readable.
418 redirected to stderr so the output is still readable.
410
419
411 Returns (fin, fout) which point to the original (uin, uout) fds, but
420 Returns (fin, fout) which point to the original (uin, uout) fds, but
412 may be copy of (uin, uout). The returned streams can be considered
421 may be copy of (uin, uout). The returned streams can be considered
413 "owned" in that print(), exec(), etc. never reach to them.
422 "owned" in that print(), exec(), etc. never reach to them.
414 """
423 """
415 uout.flush()
424 uout.flush()
416 fin, fout = uin, uout
425 fin, fout = uin, uout
417 if _testfileno(uin, stdin):
426 if _testfileno(uin, stdin):
418 newfd = os.dup(uin.fileno())
427 newfd = os.dup(uin.fileno())
419 nullfd = os.open(os.devnull, os.O_RDONLY)
428 nullfd = os.open(os.devnull, os.O_RDONLY)
420 os.dup2(nullfd, uin.fileno())
429 os.dup2(nullfd, uin.fileno())
421 os.close(nullfd)
430 os.close(nullfd)
422 fin = os.fdopen(newfd, 'rb')
431 fin = os.fdopen(newfd, 'rb')
423 if _testfileno(uout, stdout):
432 if _testfileno(uout, stdout):
424 newfd = os.dup(uout.fileno())
433 newfd = os.dup(uout.fileno())
425 os.dup2(stderr.fileno(), uout.fileno())
434 os.dup2(stderr.fileno(), uout.fileno())
426 fout = os.fdopen(newfd, 'wb')
435 fout = os.fdopen(newfd, 'wb')
427 return fin, fout
436 return fin, fout
428
437
429
438
430 def restorestdio(uin, uout, fin, fout):
439 def restorestdio(uin, uout, fin, fout):
431 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
440 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
432 uout.flush()
441 uout.flush()
433 for f, uif in [(fin, uin), (fout, uout)]:
442 for f, uif in [(fin, uin), (fout, uout)]:
434 if f is not uif:
443 if f is not uif:
435 os.dup2(f.fileno(), uif.fileno())
444 os.dup2(f.fileno(), uif.fileno())
436 f.close()
445 f.close()
437
446
438
447
439 def shellenviron(environ=None):
448 def shellenviron(environ=None):
440 """return environ with optional override, useful for shelling out"""
449 """return environ with optional override, useful for shelling out"""
441
450
442 def py2shell(val):
451 def py2shell(val):
443 """convert python object into string that is useful to shell"""
452 """convert python object into string that is useful to shell"""
444 if val is None or val is False:
453 if val is None or val is False:
445 return b'0'
454 return b'0'
446 if val is True:
455 if val is True:
447 return b'1'
456 return b'1'
448 return pycompat.bytestr(val)
457 return pycompat.bytestr(val)
449
458
450 env = dict(encoding.environ)
459 env = dict(encoding.environ)
451 if environ:
460 if environ:
452 env.update((k, py2shell(v)) for k, v in pycompat.iteritems(environ))
461 env.update((k, py2shell(v)) for k, v in pycompat.iteritems(environ))
453 env[b'HG'] = hgexecutable()
462 env[b'HG'] = hgexecutable()
454 return env
463 return env
455
464
456
465
457 if pycompat.iswindows:
466 if pycompat.iswindows:
458
467
459 def shelltonative(cmd, env):
468 def shelltonative(cmd, env):
460 return platform.shelltocmdexe( # pytype: disable=module-attr
469 return platform.shelltocmdexe( # pytype: disable=module-attr
461 cmd, shellenviron(env)
470 cmd, shellenviron(env)
462 )
471 )
463
472
464 tonativestr = encoding.strfromlocal
473 tonativestr = encoding.strfromlocal
465 else:
474 else:
466
475
467 def shelltonative(cmd, env):
476 def shelltonative(cmd, env):
468 return cmd
477 return cmd
469
478
470 tonativestr = pycompat.identity
479 tonativestr = pycompat.identity
471
480
472
481
473 def tonativeenv(env):
482 def tonativeenv(env):
474 """convert the environment from bytes to strings suitable for Popen(), etc."""
483 """convert the environment from bytes to strings suitable for Popen(), etc."""
475 return pycompat.rapply(tonativestr, env)
484 return pycompat.rapply(tonativestr, env)
476
485
477
486
478 def system(cmd, environ=None, cwd=None, out=None):
487 def system(cmd, environ=None, cwd=None, out=None):
479 """enhanced shell command execution.
488 """enhanced shell command execution.
480 run with environment maybe modified, maybe in different dir.
489 run with environment maybe modified, maybe in different dir.
481
490
482 if out is specified, it is assumed to be a file-like object that has a
491 if out is specified, it is assumed to be a file-like object that has a
483 write() method. stdout and stderr will be redirected to out."""
492 write() method. stdout and stderr will be redirected to out."""
484 try:
493 try:
485 stdout.flush()
494 stdout.flush()
486 except Exception:
495 except Exception:
487 pass
496 pass
488 env = shellenviron(environ)
497 env = shellenviron(environ)
489 if out is None or isstdout(out):
498 if out is None or isstdout(out):
490 rc = subprocess.call(
499 rc = subprocess.call(
491 tonativestr(cmd),
500 tonativestr(cmd),
492 shell=True,
501 shell=True,
493 close_fds=closefds,
502 close_fds=closefds,
494 env=tonativeenv(env),
503 env=tonativeenv(env),
495 cwd=pycompat.rapply(tonativestr, cwd),
504 cwd=pycompat.rapply(tonativestr, cwd),
496 )
505 )
497 else:
506 else:
498 proc = subprocess.Popen(
507 proc = subprocess.Popen(
499 tonativestr(cmd),
508 tonativestr(cmd),
500 shell=True,
509 shell=True,
501 close_fds=closefds,
510 close_fds=closefds,
502 env=tonativeenv(env),
511 env=tonativeenv(env),
503 cwd=pycompat.rapply(tonativestr, cwd),
512 cwd=pycompat.rapply(tonativestr, cwd),
504 stdout=subprocess.PIPE,
513 stdout=subprocess.PIPE,
505 stderr=subprocess.STDOUT,
514 stderr=subprocess.STDOUT,
506 )
515 )
507 for line in iter(proc.stdout.readline, b''):
516 for line in iter(proc.stdout.readline, b''):
508 out.write(line)
517 out.write(line)
509 proc.wait()
518 proc.wait()
510 rc = proc.returncode
519 rc = proc.returncode
511 if pycompat.sysplatform == b'OpenVMS' and rc & 1:
520 if pycompat.sysplatform == b'OpenVMS' and rc & 1:
512 rc = 0
521 rc = 0
513 return rc
522 return rc
514
523
515
524
516 _is_gui = None
525 _is_gui = None
517
526
518
527
519 def _gui():
528 def _gui():
520 '''Are we running in a GUI?'''
529 '''Are we running in a GUI?'''
521 if pycompat.isdarwin:
530 if pycompat.isdarwin:
522 if b'SSH_CONNECTION' in encoding.environ:
531 if b'SSH_CONNECTION' in encoding.environ:
523 # handle SSH access to a box where the user is logged in
532 # handle SSH access to a box where the user is logged in
524 return False
533 return False
525 elif getattr(osutil, 'isgui', None):
534 elif getattr(osutil, 'isgui', None):
526 # check if a CoreGraphics session is available
535 # check if a CoreGraphics session is available
527 return osutil.isgui()
536 return osutil.isgui()
528 else:
537 else:
529 # pure build; use a safe default
538 # pure build; use a safe default
530 return True
539 return True
531 else:
540 else:
532 return pycompat.iswindows or encoding.environ.get(b"DISPLAY")
541 return pycompat.iswindows or encoding.environ.get(b"DISPLAY")
533
542
534
543
535 def gui():
544 def gui():
536 global _is_gui
545 global _is_gui
537 if _is_gui is None:
546 if _is_gui is None:
538 _is_gui = _gui()
547 _is_gui = _gui()
539 return _is_gui
548 return _is_gui
540
549
541
550
542 def hgcmd():
551 def hgcmd():
543 """Return the command used to execute current hg
552 """Return the command used to execute current hg
544
553
545 This is different from hgexecutable() because on Windows we want
554 This is different from hgexecutable() because on Windows we want
546 to avoid things opening new shell windows like batch files, so we
555 to avoid things opening new shell windows like batch files, so we
547 get either the python call or current executable.
556 get either the python call or current executable.
548 """
557 """
549 if resourceutil.mainfrozen():
558 if resourceutil.mainfrozen():
550 if getattr(sys, 'frozen', None) == 'macosx_app':
559 if getattr(sys, 'frozen', None) == 'macosx_app':
551 # Env variable set by py2app
560 # Env variable set by py2app
552 return [encoding.environ[b'EXECUTABLEPATH']]
561 return [encoding.environ[b'EXECUTABLEPATH']]
553 else:
562 else:
554 return [pycompat.sysexecutable]
563 return [pycompat.sysexecutable]
555 return _gethgcmd()
564 return _gethgcmd()
556
565
557
566
558 def rundetached(args, condfn):
567 def rundetached(args, condfn):
559 """Execute the argument list in a detached process.
568 """Execute the argument list in a detached process.
560
569
561 condfn is a callable which is called repeatedly and should return
570 condfn is a callable which is called repeatedly and should return
562 True once the child process is known to have started successfully.
571 True once the child process is known to have started successfully.
563 At this point, the child process PID is returned. If the child
572 At this point, the child process PID is returned. If the child
564 process fails to start or finishes before condfn() evaluates to
573 process fails to start or finishes before condfn() evaluates to
565 True, return -1.
574 True, return -1.
566 """
575 """
567 # Windows case is easier because the child process is either
576 # Windows case is easier because the child process is either
568 # successfully starting and validating the condition or exiting
577 # successfully starting and validating the condition or exiting
569 # on failure. We just poll on its PID. On Unix, if the child
578 # on failure. We just poll on its PID. On Unix, if the child
570 # process fails to start, it will be left in a zombie state until
579 # process fails to start, it will be left in a zombie state until
571 # the parent wait on it, which we cannot do since we expect a long
580 # the parent wait on it, which we cannot do since we expect a long
572 # running process on success. Instead we listen for SIGCHLD telling
581 # running process on success. Instead we listen for SIGCHLD telling
573 # us our child process terminated.
582 # us our child process terminated.
574 terminated = set()
583 terminated = set()
575
584
576 def handler(signum, frame):
585 def handler(signum, frame):
577 terminated.add(os.wait())
586 terminated.add(os.wait())
578
587
579 prevhandler = None
588 prevhandler = None
580 SIGCHLD = getattr(signal, 'SIGCHLD', None)
589 SIGCHLD = getattr(signal, 'SIGCHLD', None)
581 if SIGCHLD is not None:
590 if SIGCHLD is not None:
582 prevhandler = signal.signal(SIGCHLD, handler)
591 prevhandler = signal.signal(SIGCHLD, handler)
583 try:
592 try:
584 pid = spawndetached(args)
593 pid = spawndetached(args)
585 while not condfn():
594 while not condfn():
586 if (pid in terminated or not testpid(pid)) and not condfn():
595 if (pid in terminated or not testpid(pid)) and not condfn():
587 return -1
596 return -1
588 time.sleep(0.1)
597 time.sleep(0.1)
589 return pid
598 return pid
590 finally:
599 finally:
591 if prevhandler is not None:
600 if prevhandler is not None:
592 signal.signal(signal.SIGCHLD, prevhandler)
601 signal.signal(signal.SIGCHLD, prevhandler)
593
602
594
603
595 @contextlib.contextmanager
604 @contextlib.contextmanager
596 def uninterruptible(warn):
605 def uninterruptible(warn):
597 """Inhibit SIGINT handling on a region of code.
606 """Inhibit SIGINT handling on a region of code.
598
607
599 Note that if this is called in a non-main thread, it turns into a no-op.
608 Note that if this is called in a non-main thread, it turns into a no-op.
600
609
601 Args:
610 Args:
602 warn: A callable which takes no arguments, and returns True if the
611 warn: A callable which takes no arguments, and returns True if the
603 previous signal handling should be restored.
612 previous signal handling should be restored.
604 """
613 """
605
614
606 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
615 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
607 shouldbail = []
616 shouldbail = []
608
617
609 def disabledsiginthandler(*args):
618 def disabledsiginthandler(*args):
610 if warn():
619 if warn():
611 signal.signal(signal.SIGINT, oldsiginthandler[0])
620 signal.signal(signal.SIGINT, oldsiginthandler[0])
612 del oldsiginthandler[0]
621 del oldsiginthandler[0]
613 shouldbail.append(True)
622 shouldbail.append(True)
614
623
615 try:
624 try:
616 try:
625 try:
617 signal.signal(signal.SIGINT, disabledsiginthandler)
626 signal.signal(signal.SIGINT, disabledsiginthandler)
618 except ValueError:
627 except ValueError:
619 # wrong thread, oh well, we tried
628 # wrong thread, oh well, we tried
620 del oldsiginthandler[0]
629 del oldsiginthandler[0]
621 yield
630 yield
622 finally:
631 finally:
623 if oldsiginthandler:
632 if oldsiginthandler:
624 signal.signal(signal.SIGINT, oldsiginthandler[0])
633 signal.signal(signal.SIGINT, oldsiginthandler[0])
625 if shouldbail:
634 if shouldbail:
626 raise KeyboardInterrupt
635 raise KeyboardInterrupt
627
636
628
637
629 if pycompat.iswindows:
638 if pycompat.iswindows:
630 # no fork on Windows, but we can create a detached process
639 # no fork on Windows, but we can create a detached process
631 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
640 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
632 # No stdlib constant exists for this value
641 # No stdlib constant exists for this value
633 DETACHED_PROCESS = 0x00000008
642 DETACHED_PROCESS = 0x00000008
634 # Following creation flags might create a console GUI window.
643 # Following creation flags might create a console GUI window.
635 # Using subprocess.CREATE_NEW_CONSOLE might helps.
644 # Using subprocess.CREATE_NEW_CONSOLE might helps.
636 # See https://phab.mercurial-scm.org/D1701 for discussion
645 # See https://phab.mercurial-scm.org/D1701 for discussion
637 _creationflags = (
646 _creationflags = (
638 DETACHED_PROCESS
647 DETACHED_PROCESS
639 | subprocess.CREATE_NEW_PROCESS_GROUP # pytype: disable=module-attr
648 | subprocess.CREATE_NEW_PROCESS_GROUP # pytype: disable=module-attr
640 )
649 )
641
650
642 def runbgcommand(
651 def runbgcommand(
643 script,
652 script,
644 env,
653 env,
645 shell=False,
654 shell=False,
646 stdout=None,
655 stdout=None,
647 stderr=None,
656 stderr=None,
648 ensurestart=True,
657 ensurestart=True,
649 record_wait=None,
658 record_wait=None,
650 stdin_bytes=None,
659 stdin_bytes=None,
651 ):
660 ):
652 '''Spawn a command without waiting for it to finish.'''
661 '''Spawn a command without waiting for it to finish.'''
653 # we can't use close_fds *and* redirect stdin. I'm not sure that we
662 # we can't use close_fds *and* redirect stdin. I'm not sure that we
654 # need to because the detached process has no console connection.
663 # need to because the detached process has no console connection.
655
664
656 try:
665 try:
657 stdin = None
666 stdin = None
658 if stdin_bytes is not None:
667 if stdin_bytes is not None:
659 stdin = pycompat.unnamedtempfile()
668 stdin = pycompat.unnamedtempfile()
660 stdin.write(stdin_bytes)
669 stdin.write(stdin_bytes)
661 stdin.flush()
670 stdin.flush()
662 stdin.seek(0)
671 stdin.seek(0)
663
672
664 p = subprocess.Popen(
673 p = subprocess.Popen(
665 pycompat.rapply(tonativestr, script),
674 pycompat.rapply(tonativestr, script),
666 shell=shell,
675 shell=shell,
667 env=tonativeenv(env),
676 env=tonativeenv(env),
668 close_fds=True,
677 close_fds=True,
669 creationflags=_creationflags,
678 creationflags=_creationflags,
670 stdin=stdin,
679 stdin=stdin,
671 stdout=stdout,
680 stdout=stdout,
672 stderr=stderr,
681 stderr=stderr,
673 )
682 )
674 if record_wait is not None:
683 if record_wait is not None:
675 record_wait(p.wait)
684 record_wait(p.wait)
676 finally:
685 finally:
677 if stdin is not None:
686 if stdin is not None:
678 stdin.close()
687 stdin.close()
679
688
680
689
681 else:
690 else:
682
691
683 def runbgcommand(
692 def runbgcommand(
684 cmd,
693 cmd,
685 env,
694 env,
686 shell=False,
695 shell=False,
687 stdout=None,
696 stdout=None,
688 stderr=None,
697 stderr=None,
689 ensurestart=True,
698 ensurestart=True,
690 record_wait=None,
699 record_wait=None,
691 stdin_bytes=None,
700 stdin_bytes=None,
692 ):
701 ):
693 """Spawn a command without waiting for it to finish.
702 """Spawn a command without waiting for it to finish.
694
703
695
704
696 When `record_wait` is not None, the spawned process will not be fully
705 When `record_wait` is not None, the spawned process will not be fully
697 detached and the `record_wait` argument will be called with a the
706 detached and the `record_wait` argument will be called with a the
698 `Subprocess.wait` function for the spawned process. This is mostly
707 `Subprocess.wait` function for the spawned process. This is mostly
699 useful for developers that need to make sure the spawned process
708 useful for developers that need to make sure the spawned process
700 finished before a certain point. (eg: writing test)"""
709 finished before a certain point. (eg: writing test)"""
701 if pycompat.isdarwin:
710 if pycompat.isdarwin:
702 # avoid crash in CoreFoundation in case another thread
711 # avoid crash in CoreFoundation in case another thread
703 # calls gui() while we're calling fork().
712 # calls gui() while we're calling fork().
704 gui()
713 gui()
705
714
706 # double-fork to completely detach from the parent process
715 # double-fork to completely detach from the parent process
707 # based on http://code.activestate.com/recipes/278731
716 # based on http://code.activestate.com/recipes/278731
708 if record_wait is None:
717 if record_wait is None:
709 pid = os.fork()
718 pid = os.fork()
710 if pid:
719 if pid:
711 if not ensurestart:
720 if not ensurestart:
712 # Even though we're not waiting on the child process,
721 # Even though we're not waiting on the child process,
713 # we still must call waitpid() on it at some point so
722 # we still must call waitpid() on it at some point so
714 # it's not a zombie/defunct. This is especially relevant for
723 # it's not a zombie/defunct. This is especially relevant for
715 # chg since the parent process won't die anytime soon.
724 # chg since the parent process won't die anytime soon.
716 # We use a thread to make the overhead tiny.
725 # We use a thread to make the overhead tiny.
717 def _do_wait():
726 def _do_wait():
718 os.waitpid(pid, 0)
727 os.waitpid(pid, 0)
719
728
720 t = threading.Thread(target=_do_wait)
729 t = threading.Thread(target=_do_wait)
721 t.daemon = True
730 t.daemon = True
722 t.start()
731 t.start()
723 return
732 return
724 # Parent process
733 # Parent process
725 (_pid, status) = os.waitpid(pid, 0)
734 (_pid, status) = os.waitpid(pid, 0)
726 if os.WIFEXITED(status):
735 if os.WIFEXITED(status):
727 returncode = os.WEXITSTATUS(status)
736 returncode = os.WEXITSTATUS(status)
728 else:
737 else:
729 returncode = -(os.WTERMSIG(status))
738 returncode = -(os.WTERMSIG(status))
730 if returncode != 0:
739 if returncode != 0:
731 # The child process's return code is 0 on success, an errno
740 # The child process's return code is 0 on success, an errno
732 # value on failure, or 255 if we don't have a valid errno
741 # value on failure, or 255 if we don't have a valid errno
733 # value.
742 # value.
734 #
743 #
735 # (It would be slightly nicer to return the full exception info
744 # (It would be slightly nicer to return the full exception info
736 # over a pipe as the subprocess module does. For now it
745 # over a pipe as the subprocess module does. For now it
737 # doesn't seem worth adding that complexity here, though.)
746 # doesn't seem worth adding that complexity here, though.)
738 if returncode == 255:
747 if returncode == 255:
739 returncode = errno.EINVAL
748 returncode = errno.EINVAL
740 raise OSError(
749 raise OSError(
741 returncode,
750 returncode,
742 b'error running %r: %s'
751 b'error running %r: %s'
743 % (cmd, os.strerror(returncode)),
752 % (cmd, os.strerror(returncode)),
744 )
753 )
745 return
754 return
746
755
747 returncode = 255
756 returncode = 255
748 try:
757 try:
749 if record_wait is None:
758 if record_wait is None:
750 # Start a new session
759 # Start a new session
751 os.setsid()
760 os.setsid()
752 # connect stdin to devnull to make sure the subprocess can't
761 # connect stdin to devnull to make sure the subprocess can't
753 # muck up that stream for mercurial.
762 # muck up that stream for mercurial.
754 if stdin_bytes is None:
763 if stdin_bytes is None:
755 stdin = open(os.devnull, b'r')
764 stdin = open(os.devnull, b'r')
756 else:
765 else:
757 stdin = pycompat.unnamedtempfile()
766 stdin = pycompat.unnamedtempfile()
758 stdin.write(stdin_bytes)
767 stdin.write(stdin_bytes)
759 stdin.flush()
768 stdin.flush()
760 stdin.seek(0)
769 stdin.seek(0)
761
770
762 if stdout is None:
771 if stdout is None:
763 stdout = open(os.devnull, b'w')
772 stdout = open(os.devnull, b'w')
764 if stderr is None:
773 if stderr is None:
765 stderr = open(os.devnull, b'w')
774 stderr = open(os.devnull, b'w')
766
775
767 p = subprocess.Popen(
776 p = subprocess.Popen(
768 cmd,
777 cmd,
769 shell=shell,
778 shell=shell,
770 env=env,
779 env=env,
771 close_fds=True,
780 close_fds=True,
772 stdin=stdin,
781 stdin=stdin,
773 stdout=stdout,
782 stdout=stdout,
774 stderr=stderr,
783 stderr=stderr,
775 )
784 )
776 if record_wait is not None:
785 if record_wait is not None:
777 record_wait(p.wait)
786 record_wait(p.wait)
778 returncode = 0
787 returncode = 0
779 except EnvironmentError as ex:
788 except EnvironmentError as ex:
780 returncode = ex.errno & 0xFF
789 returncode = ex.errno & 0xFF
781 if returncode == 0:
790 if returncode == 0:
782 # This shouldn't happen, but just in case make sure the
791 # This shouldn't happen, but just in case make sure the
783 # return code is never 0 here.
792 # return code is never 0 here.
784 returncode = 255
793 returncode = 255
785 except Exception:
794 except Exception:
786 returncode = 255
795 returncode = 255
787 finally:
796 finally:
788 # mission accomplished, this child needs to exit and not
797 # mission accomplished, this child needs to exit and not
789 # continue the hg process here.
798 # continue the hg process here.
790 stdin.close()
799 stdin.close()
791 if record_wait is None:
800 if record_wait is None:
792 os._exit(returncode)
801 os._exit(returncode)
General Comments 0
You need to be logged in to leave comments. Login now