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