##// END OF EJS Templates
py3: keep stdout as defined by pycompat in procutil...
Denis Laxalde -
r43435:227ba1af default
parent child Browse files
Show More
@@ -1,633 +1,635 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 imp
14 import imp
15 import io
15 import io
16 import os
16 import os
17 import signal
17 import signal
18 import subprocess
18 import subprocess
19 import sys
19 import sys
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 osutil = policy.importmod(r'osutil')
35 osutil = policy.importmod(r'osutil')
36
36
37 stderr = pycompat.stderr
37 stderr = pycompat.stderr
38 stdin = pycompat.stdin
38 stdin = pycompat.stdin
39 stdout = pycompat.stdout
39 stdout = pycompat.stdout
40
40
41
41
42 def isatty(fp):
42 def isatty(fp):
43 try:
43 try:
44 return fp.isatty()
44 return fp.isatty()
45 except AttributeError:
45 except AttributeError:
46 return False
46 return False
47
47
48
48
49 # glibc determines buffering on first write to stdout - if we replace a TTY
49 # glibc determines buffering on first write to stdout - if we replace a TTY
50 # destined stdout with a pipe destined stdout (e.g. pager), we want line
50 # destined stdout with a pipe destined stdout (e.g. pager), we want line
51 # buffering (or unbuffered, on Windows)
51 # buffering (or unbuffered, on Windows)
52 if isatty(stdout):
52 if isatty(stdout):
53 if pycompat.iswindows:
53 if pycompat.iswindows:
54 # Windows doesn't support line buffering
54 # Windows doesn't support line buffering
55 stdout = os.fdopen(stdout.fileno(), r'wb', 0)
55 stdout = os.fdopen(stdout.fileno(), r'wb', 0)
56 else:
56 elif not pycompat.ispy3:
57 # on Python 3, stdout (sys.stdout.buffer) is already line buffered and
58 # buffering=1 is not handled in binary mode
57 stdout = os.fdopen(stdout.fileno(), r'wb', 1)
59 stdout = os.fdopen(stdout.fileno(), r'wb', 1)
58
60
59 if pycompat.iswindows:
61 if pycompat.iswindows:
60 from .. import windows as platform
62 from .. import windows as platform
61
63
62 stdout = platform.winstdout(stdout)
64 stdout = platform.winstdout(stdout)
63 else:
65 else:
64 from .. import posix as platform
66 from .. import posix as platform
65
67
66 findexe = platform.findexe
68 findexe = platform.findexe
67 _gethgcmd = platform.gethgcmd
69 _gethgcmd = platform.gethgcmd
68 getuser = platform.getuser
70 getuser = platform.getuser
69 getpid = os.getpid
71 getpid = os.getpid
70 hidewindow = platform.hidewindow
72 hidewindow = platform.hidewindow
71 quotecommand = platform.quotecommand
73 quotecommand = platform.quotecommand
72 readpipe = platform.readpipe
74 readpipe = platform.readpipe
73 setbinary = platform.setbinary
75 setbinary = platform.setbinary
74 setsignalhandler = platform.setsignalhandler
76 setsignalhandler = platform.setsignalhandler
75 shellquote = platform.shellquote
77 shellquote = platform.shellquote
76 shellsplit = platform.shellsplit
78 shellsplit = platform.shellsplit
77 spawndetached = platform.spawndetached
79 spawndetached = platform.spawndetached
78 sshargs = platform.sshargs
80 sshargs = platform.sshargs
79 testpid = platform.testpid
81 testpid = platform.testpid
80
82
81 try:
83 try:
82 setprocname = osutil.setprocname
84 setprocname = osutil.setprocname
83 except AttributeError:
85 except AttributeError:
84 pass
86 pass
85 try:
87 try:
86 unblocksignal = osutil.unblocksignal
88 unblocksignal = osutil.unblocksignal
87 except AttributeError:
89 except AttributeError:
88 pass
90 pass
89
91
90 closefds = pycompat.isposix
92 closefds = pycompat.isposix
91
93
92
94
93 def explainexit(code):
95 def explainexit(code):
94 """return a message describing a subprocess status
96 """return a message describing a subprocess status
95 (codes from kill are negative - not os.system/wait encoding)"""
97 (codes from kill are negative - not os.system/wait encoding)"""
96 if code >= 0:
98 if code >= 0:
97 return _(b"exited with status %d") % code
99 return _(b"exited with status %d") % code
98 return _(b"killed by signal %d") % -code
100 return _(b"killed by signal %d") % -code
99
101
100
102
101 class _pfile(object):
103 class _pfile(object):
102 """File-like wrapper for a stream opened by subprocess.Popen()"""
104 """File-like wrapper for a stream opened by subprocess.Popen()"""
103
105
104 def __init__(self, proc, fp):
106 def __init__(self, proc, fp):
105 self._proc = proc
107 self._proc = proc
106 self._fp = fp
108 self._fp = fp
107
109
108 def close(self):
110 def close(self):
109 # unlike os.popen(), this returns an integer in subprocess coding
111 # unlike os.popen(), this returns an integer in subprocess coding
110 self._fp.close()
112 self._fp.close()
111 return self._proc.wait()
113 return self._proc.wait()
112
114
113 def __iter__(self):
115 def __iter__(self):
114 return iter(self._fp)
116 return iter(self._fp)
115
117
116 def __getattr__(self, attr):
118 def __getattr__(self, attr):
117 return getattr(self._fp, attr)
119 return getattr(self._fp, attr)
118
120
119 def __enter__(self):
121 def __enter__(self):
120 return self
122 return self
121
123
122 def __exit__(self, exc_type, exc_value, exc_tb):
124 def __exit__(self, exc_type, exc_value, exc_tb):
123 self.close()
125 self.close()
124
126
125
127
126 def popen(cmd, mode=b'rb', bufsize=-1):
128 def popen(cmd, mode=b'rb', bufsize=-1):
127 if mode == b'rb':
129 if mode == b'rb':
128 return _popenreader(cmd, bufsize)
130 return _popenreader(cmd, bufsize)
129 elif mode == b'wb':
131 elif mode == b'wb':
130 return _popenwriter(cmd, bufsize)
132 return _popenwriter(cmd, bufsize)
131 raise error.ProgrammingError(b'unsupported mode: %r' % mode)
133 raise error.ProgrammingError(b'unsupported mode: %r' % mode)
132
134
133
135
134 def _popenreader(cmd, bufsize):
136 def _popenreader(cmd, bufsize):
135 p = subprocess.Popen(
137 p = subprocess.Popen(
136 tonativestr(quotecommand(cmd)),
138 tonativestr(quotecommand(cmd)),
137 shell=True,
139 shell=True,
138 bufsize=bufsize,
140 bufsize=bufsize,
139 close_fds=closefds,
141 close_fds=closefds,
140 stdout=subprocess.PIPE,
142 stdout=subprocess.PIPE,
141 )
143 )
142 return _pfile(p, p.stdout)
144 return _pfile(p, p.stdout)
143
145
144
146
145 def _popenwriter(cmd, bufsize):
147 def _popenwriter(cmd, bufsize):
146 p = subprocess.Popen(
148 p = subprocess.Popen(
147 tonativestr(quotecommand(cmd)),
149 tonativestr(quotecommand(cmd)),
148 shell=True,
150 shell=True,
149 bufsize=bufsize,
151 bufsize=bufsize,
150 close_fds=closefds,
152 close_fds=closefds,
151 stdin=subprocess.PIPE,
153 stdin=subprocess.PIPE,
152 )
154 )
153 return _pfile(p, p.stdin)
155 return _pfile(p, p.stdin)
154
156
155
157
156 def popen2(cmd, env=None):
158 def popen2(cmd, env=None):
157 # Setting bufsize to -1 lets the system decide the buffer size.
159 # Setting bufsize to -1 lets the system decide the buffer size.
158 # The default for bufsize is 0, meaning unbuffered. This leads to
160 # The default for bufsize is 0, meaning unbuffered. This leads to
159 # poor performance on Mac OS X: http://bugs.python.org/issue4194
161 # poor performance on Mac OS X: http://bugs.python.org/issue4194
160 p = subprocess.Popen(
162 p = subprocess.Popen(
161 tonativestr(cmd),
163 tonativestr(cmd),
162 shell=True,
164 shell=True,
163 bufsize=-1,
165 bufsize=-1,
164 close_fds=closefds,
166 close_fds=closefds,
165 stdin=subprocess.PIPE,
167 stdin=subprocess.PIPE,
166 stdout=subprocess.PIPE,
168 stdout=subprocess.PIPE,
167 env=tonativeenv(env),
169 env=tonativeenv(env),
168 )
170 )
169 return p.stdin, p.stdout
171 return p.stdin, p.stdout
170
172
171
173
172 def popen3(cmd, env=None):
174 def popen3(cmd, env=None):
173 stdin, stdout, stderr, p = popen4(cmd, env)
175 stdin, stdout, stderr, p = popen4(cmd, env)
174 return stdin, stdout, stderr
176 return stdin, stdout, stderr
175
177
176
178
177 def popen4(cmd, env=None, bufsize=-1):
179 def popen4(cmd, env=None, bufsize=-1):
178 p = subprocess.Popen(
180 p = subprocess.Popen(
179 tonativestr(cmd),
181 tonativestr(cmd),
180 shell=True,
182 shell=True,
181 bufsize=bufsize,
183 bufsize=bufsize,
182 close_fds=closefds,
184 close_fds=closefds,
183 stdin=subprocess.PIPE,
185 stdin=subprocess.PIPE,
184 stdout=subprocess.PIPE,
186 stdout=subprocess.PIPE,
185 stderr=subprocess.PIPE,
187 stderr=subprocess.PIPE,
186 env=tonativeenv(env),
188 env=tonativeenv(env),
187 )
189 )
188 return p.stdin, p.stdout, p.stderr, p
190 return p.stdin, p.stdout, p.stderr, p
189
191
190
192
191 def pipefilter(s, cmd):
193 def pipefilter(s, cmd):
192 '''filter string S through command CMD, returning its output'''
194 '''filter string S through command CMD, returning its output'''
193 p = subprocess.Popen(
195 p = subprocess.Popen(
194 tonativestr(cmd),
196 tonativestr(cmd),
195 shell=True,
197 shell=True,
196 close_fds=closefds,
198 close_fds=closefds,
197 stdin=subprocess.PIPE,
199 stdin=subprocess.PIPE,
198 stdout=subprocess.PIPE,
200 stdout=subprocess.PIPE,
199 )
201 )
200 pout, perr = p.communicate(s)
202 pout, perr = p.communicate(s)
201 return pout
203 return pout
202
204
203
205
204 def tempfilter(s, cmd):
206 def tempfilter(s, cmd):
205 '''filter string S through a pair of temporary files with CMD.
207 '''filter string S through a pair of temporary files with CMD.
206 CMD is used as a template to create the real command to be run,
208 CMD is used as a template to create the real command to be run,
207 with the strings INFILE and OUTFILE replaced by the real names of
209 with the strings INFILE and OUTFILE replaced by the real names of
208 the temporary files generated.'''
210 the temporary files generated.'''
209 inname, outname = None, None
211 inname, outname = None, None
210 try:
212 try:
211 infd, inname = pycompat.mkstemp(prefix=b'hg-filter-in-')
213 infd, inname = pycompat.mkstemp(prefix=b'hg-filter-in-')
212 fp = os.fdopen(infd, r'wb')
214 fp = os.fdopen(infd, r'wb')
213 fp.write(s)
215 fp.write(s)
214 fp.close()
216 fp.close()
215 outfd, outname = pycompat.mkstemp(prefix=b'hg-filter-out-')
217 outfd, outname = pycompat.mkstemp(prefix=b'hg-filter-out-')
216 os.close(outfd)
218 os.close(outfd)
217 cmd = cmd.replace(b'INFILE', inname)
219 cmd = cmd.replace(b'INFILE', inname)
218 cmd = cmd.replace(b'OUTFILE', outname)
220 cmd = cmd.replace(b'OUTFILE', outname)
219 code = system(cmd)
221 code = system(cmd)
220 if pycompat.sysplatform == b'OpenVMS' and code & 1:
222 if pycompat.sysplatform == b'OpenVMS' and code & 1:
221 code = 0
223 code = 0
222 if code:
224 if code:
223 raise error.Abort(
225 raise error.Abort(
224 _(b"command '%s' failed: %s") % (cmd, explainexit(code))
226 _(b"command '%s' failed: %s") % (cmd, explainexit(code))
225 )
227 )
226 with open(outname, b'rb') as fp:
228 with open(outname, b'rb') as fp:
227 return fp.read()
229 return fp.read()
228 finally:
230 finally:
229 try:
231 try:
230 if inname:
232 if inname:
231 os.unlink(inname)
233 os.unlink(inname)
232 except OSError:
234 except OSError:
233 pass
235 pass
234 try:
236 try:
235 if outname:
237 if outname:
236 os.unlink(outname)
238 os.unlink(outname)
237 except OSError:
239 except OSError:
238 pass
240 pass
239
241
240
242
241 _filtertable = {
243 _filtertable = {
242 b'tempfile:': tempfilter,
244 b'tempfile:': tempfilter,
243 b'pipe:': pipefilter,
245 b'pipe:': pipefilter,
244 }
246 }
245
247
246
248
247 def filter(s, cmd):
249 def filter(s, cmd):
248 b"filter a string through a command that transforms its input to its output"
250 b"filter a string through a command that transforms its input to its output"
249 for name, fn in pycompat.iteritems(_filtertable):
251 for name, fn in pycompat.iteritems(_filtertable):
250 if cmd.startswith(name):
252 if cmd.startswith(name):
251 return fn(s, cmd[len(name) :].lstrip())
253 return fn(s, cmd[len(name) :].lstrip())
252 return pipefilter(s, cmd)
254 return pipefilter(s, cmd)
253
255
254
256
255 def mainfrozen():
257 def mainfrozen():
256 """return True if we are a frozen executable.
258 """return True if we are a frozen executable.
257
259
258 The code supports py2exe (most common, Windows only) and tools/freeze
260 The code supports py2exe (most common, Windows only) and tools/freeze
259 (portable, not much used).
261 (portable, not much used).
260 """
262 """
261 return (
263 return (
262 pycompat.safehasattr(sys, "frozen")
264 pycompat.safehasattr(sys, "frozen")
263 or pycompat.safehasattr(sys, "importers") # new py2exe
265 or pycompat.safehasattr(sys, "importers") # new py2exe
264 or imp.is_frozen(r"__main__") # old py2exe
266 or imp.is_frozen(r"__main__") # old py2exe
265 ) # tools/freeze
267 ) # tools/freeze
266
268
267
269
268 _hgexecutable = None
270 _hgexecutable = None
269
271
270
272
271 def hgexecutable():
273 def hgexecutable():
272 """return location of the 'hg' executable.
274 """return location of the 'hg' executable.
273
275
274 Defaults to $HG or 'hg' in the search path.
276 Defaults to $HG or 'hg' in the search path.
275 """
277 """
276 if _hgexecutable is None:
278 if _hgexecutable is None:
277 hg = encoding.environ.get(b'HG')
279 hg = encoding.environ.get(b'HG')
278 mainmod = sys.modules[r'__main__']
280 mainmod = sys.modules[r'__main__']
279 if hg:
281 if hg:
280 _sethgexecutable(hg)
282 _sethgexecutable(hg)
281 elif mainfrozen():
283 elif mainfrozen():
282 if getattr(sys, 'frozen', None) == b'macosx_app':
284 if getattr(sys, 'frozen', None) == b'macosx_app':
283 # Env variable set by py2app
285 # Env variable set by py2app
284 _sethgexecutable(encoding.environ[b'EXECUTABLEPATH'])
286 _sethgexecutable(encoding.environ[b'EXECUTABLEPATH'])
285 else:
287 else:
286 _sethgexecutable(pycompat.sysexecutable)
288 _sethgexecutable(pycompat.sysexecutable)
287 elif (
289 elif (
288 not pycompat.iswindows
290 not pycompat.iswindows
289 and os.path.basename(
291 and os.path.basename(
290 pycompat.fsencode(getattr(mainmod, '__file__', b''))
292 pycompat.fsencode(getattr(mainmod, '__file__', b''))
291 )
293 )
292 == b'hg'
294 == b'hg'
293 ):
295 ):
294 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
296 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
295 else:
297 else:
296 _sethgexecutable(
298 _sethgexecutable(
297 findexe(b'hg') or os.path.basename(pycompat.sysargv[0])
299 findexe(b'hg') or os.path.basename(pycompat.sysargv[0])
298 )
300 )
299 return _hgexecutable
301 return _hgexecutable
300
302
301
303
302 def _sethgexecutable(path):
304 def _sethgexecutable(path):
303 """set location of the 'hg' executable"""
305 """set location of the 'hg' executable"""
304 global _hgexecutable
306 global _hgexecutable
305 _hgexecutable = path
307 _hgexecutable = path
306
308
307
309
308 def _testfileno(f, stdf):
310 def _testfileno(f, stdf):
309 fileno = getattr(f, 'fileno', None)
311 fileno = getattr(f, 'fileno', None)
310 try:
312 try:
311 return fileno and fileno() == stdf.fileno()
313 return fileno and fileno() == stdf.fileno()
312 except io.UnsupportedOperation:
314 except io.UnsupportedOperation:
313 return False # fileno() raised UnsupportedOperation
315 return False # fileno() raised UnsupportedOperation
314
316
315
317
316 def isstdin(f):
318 def isstdin(f):
317 return _testfileno(f, sys.__stdin__)
319 return _testfileno(f, sys.__stdin__)
318
320
319
321
320 def isstdout(f):
322 def isstdout(f):
321 return _testfileno(f, sys.__stdout__)
323 return _testfileno(f, sys.__stdout__)
322
324
323
325
324 def protectstdio(uin, uout):
326 def protectstdio(uin, uout):
325 """Duplicate streams and redirect original if (uin, uout) are stdio
327 """Duplicate streams and redirect original if (uin, uout) are stdio
326
328
327 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
329 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
328 redirected to stderr so the output is still readable.
330 redirected to stderr so the output is still readable.
329
331
330 Returns (fin, fout) which point to the original (uin, uout) fds, but
332 Returns (fin, fout) which point to the original (uin, uout) fds, but
331 may be copy of (uin, uout). The returned streams can be considered
333 may be copy of (uin, uout). The returned streams can be considered
332 "owned" in that print(), exec(), etc. never reach to them.
334 "owned" in that print(), exec(), etc. never reach to them.
333 """
335 """
334 uout.flush()
336 uout.flush()
335 fin, fout = uin, uout
337 fin, fout = uin, uout
336 if _testfileno(uin, stdin):
338 if _testfileno(uin, stdin):
337 newfd = os.dup(uin.fileno())
339 newfd = os.dup(uin.fileno())
338 nullfd = os.open(os.devnull, os.O_RDONLY)
340 nullfd = os.open(os.devnull, os.O_RDONLY)
339 os.dup2(nullfd, uin.fileno())
341 os.dup2(nullfd, uin.fileno())
340 os.close(nullfd)
342 os.close(nullfd)
341 fin = os.fdopen(newfd, r'rb')
343 fin = os.fdopen(newfd, r'rb')
342 if _testfileno(uout, stdout):
344 if _testfileno(uout, stdout):
343 newfd = os.dup(uout.fileno())
345 newfd = os.dup(uout.fileno())
344 os.dup2(stderr.fileno(), uout.fileno())
346 os.dup2(stderr.fileno(), uout.fileno())
345 fout = os.fdopen(newfd, r'wb')
347 fout = os.fdopen(newfd, r'wb')
346 return fin, fout
348 return fin, fout
347
349
348
350
349 def restorestdio(uin, uout, fin, fout):
351 def restorestdio(uin, uout, fin, fout):
350 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
352 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
351 uout.flush()
353 uout.flush()
352 for f, uif in [(fin, uin), (fout, uout)]:
354 for f, uif in [(fin, uin), (fout, uout)]:
353 if f is not uif:
355 if f is not uif:
354 os.dup2(f.fileno(), uif.fileno())
356 os.dup2(f.fileno(), uif.fileno())
355 f.close()
357 f.close()
356
358
357
359
358 def shellenviron(environ=None):
360 def shellenviron(environ=None):
359 """return environ with optional override, useful for shelling out"""
361 """return environ with optional override, useful for shelling out"""
360
362
361 def py2shell(val):
363 def py2shell(val):
362 b'convert python object into string that is useful to shell'
364 b'convert python object into string that is useful to shell'
363 if val is None or val is False:
365 if val is None or val is False:
364 return b'0'
366 return b'0'
365 if val is True:
367 if val is True:
366 return b'1'
368 return b'1'
367 return pycompat.bytestr(val)
369 return pycompat.bytestr(val)
368
370
369 env = dict(encoding.environ)
371 env = dict(encoding.environ)
370 if environ:
372 if environ:
371 env.update((k, py2shell(v)) for k, v in pycompat.iteritems(environ))
373 env.update((k, py2shell(v)) for k, v in pycompat.iteritems(environ))
372 env[b'HG'] = hgexecutable()
374 env[b'HG'] = hgexecutable()
373 return env
375 return env
374
376
375
377
376 if pycompat.iswindows:
378 if pycompat.iswindows:
377
379
378 def shelltonative(cmd, env):
380 def shelltonative(cmd, env):
379 return platform.shelltocmdexe(cmd, shellenviron(env))
381 return platform.shelltocmdexe(cmd, shellenviron(env))
380
382
381 tonativestr = encoding.strfromlocal
383 tonativestr = encoding.strfromlocal
382 else:
384 else:
383
385
384 def shelltonative(cmd, env):
386 def shelltonative(cmd, env):
385 return cmd
387 return cmd
386
388
387 tonativestr = pycompat.identity
389 tonativestr = pycompat.identity
388
390
389
391
390 def tonativeenv(env):
392 def tonativeenv(env):
391 '''convert the environment from bytes to strings suitable for Popen(), etc.
393 '''convert the environment from bytes to strings suitable for Popen(), etc.
392 '''
394 '''
393 return pycompat.rapply(tonativestr, env)
395 return pycompat.rapply(tonativestr, env)
394
396
395
397
396 def system(cmd, environ=None, cwd=None, out=None):
398 def system(cmd, environ=None, cwd=None, out=None):
397 '''enhanced shell command execution.
399 '''enhanced shell command execution.
398 run with environment maybe modified, maybe in different dir.
400 run with environment maybe modified, maybe in different dir.
399
401
400 if out is specified, it is assumed to be a file-like object that has a
402 if out is specified, it is assumed to be a file-like object that has a
401 write() method. stdout and stderr will be redirected to out.'''
403 write() method. stdout and stderr will be redirected to out.'''
402 try:
404 try:
403 stdout.flush()
405 stdout.flush()
404 except Exception:
406 except Exception:
405 pass
407 pass
406 cmd = quotecommand(cmd)
408 cmd = quotecommand(cmd)
407 env = shellenviron(environ)
409 env = shellenviron(environ)
408 if out is None or isstdout(out):
410 if out is None or isstdout(out):
409 rc = subprocess.call(
411 rc = subprocess.call(
410 tonativestr(cmd),
412 tonativestr(cmd),
411 shell=True,
413 shell=True,
412 close_fds=closefds,
414 close_fds=closefds,
413 env=tonativeenv(env),
415 env=tonativeenv(env),
414 cwd=pycompat.rapply(tonativestr, cwd),
416 cwd=pycompat.rapply(tonativestr, cwd),
415 )
417 )
416 else:
418 else:
417 proc = subprocess.Popen(
419 proc = subprocess.Popen(
418 tonativestr(cmd),
420 tonativestr(cmd),
419 shell=True,
421 shell=True,
420 close_fds=closefds,
422 close_fds=closefds,
421 env=tonativeenv(env),
423 env=tonativeenv(env),
422 cwd=pycompat.rapply(tonativestr, cwd),
424 cwd=pycompat.rapply(tonativestr, cwd),
423 stdout=subprocess.PIPE,
425 stdout=subprocess.PIPE,
424 stderr=subprocess.STDOUT,
426 stderr=subprocess.STDOUT,
425 )
427 )
426 for line in iter(proc.stdout.readline, b''):
428 for line in iter(proc.stdout.readline, b''):
427 out.write(line)
429 out.write(line)
428 proc.wait()
430 proc.wait()
429 rc = proc.returncode
431 rc = proc.returncode
430 if pycompat.sysplatform == b'OpenVMS' and rc & 1:
432 if pycompat.sysplatform == b'OpenVMS' and rc & 1:
431 rc = 0
433 rc = 0
432 return rc
434 return rc
433
435
434
436
435 def gui():
437 def gui():
436 '''Are we running in a GUI?'''
438 '''Are we running in a GUI?'''
437 if pycompat.isdarwin:
439 if pycompat.isdarwin:
438 if b'SSH_CONNECTION' in encoding.environ:
440 if b'SSH_CONNECTION' in encoding.environ:
439 # handle SSH access to a box where the user is logged in
441 # handle SSH access to a box where the user is logged in
440 return False
442 return False
441 elif getattr(osutil, 'isgui', None):
443 elif getattr(osutil, 'isgui', None):
442 # check if a CoreGraphics session is available
444 # check if a CoreGraphics session is available
443 return osutil.isgui()
445 return osutil.isgui()
444 else:
446 else:
445 # pure build; use a safe default
447 # pure build; use a safe default
446 return True
448 return True
447 else:
449 else:
448 return pycompat.iswindows or encoding.environ.get(b"DISPLAY")
450 return pycompat.iswindows or encoding.environ.get(b"DISPLAY")
449
451
450
452
451 def hgcmd():
453 def hgcmd():
452 """Return the command used to execute current hg
454 """Return the command used to execute current hg
453
455
454 This is different from hgexecutable() because on Windows we want
456 This is different from hgexecutable() because on Windows we want
455 to avoid things opening new shell windows like batch files, so we
457 to avoid things opening new shell windows like batch files, so we
456 get either the python call or current executable.
458 get either the python call or current executable.
457 """
459 """
458 if mainfrozen():
460 if mainfrozen():
459 if getattr(sys, 'frozen', None) == b'macosx_app':
461 if getattr(sys, 'frozen', None) == b'macosx_app':
460 # Env variable set by py2app
462 # Env variable set by py2app
461 return [encoding.environ[b'EXECUTABLEPATH']]
463 return [encoding.environ[b'EXECUTABLEPATH']]
462 else:
464 else:
463 return [pycompat.sysexecutable]
465 return [pycompat.sysexecutable]
464 return _gethgcmd()
466 return _gethgcmd()
465
467
466
468
467 def rundetached(args, condfn):
469 def rundetached(args, condfn):
468 """Execute the argument list in a detached process.
470 """Execute the argument list in a detached process.
469
471
470 condfn is a callable which is called repeatedly and should return
472 condfn is a callable which is called repeatedly and should return
471 True once the child process is known to have started successfully.
473 True once the child process is known to have started successfully.
472 At this point, the child process PID is returned. If the child
474 At this point, the child process PID is returned. If the child
473 process fails to start or finishes before condfn() evaluates to
475 process fails to start or finishes before condfn() evaluates to
474 True, return -1.
476 True, return -1.
475 """
477 """
476 # Windows case is easier because the child process is either
478 # Windows case is easier because the child process is either
477 # successfully starting and validating the condition or exiting
479 # successfully starting and validating the condition or exiting
478 # on failure. We just poll on its PID. On Unix, if the child
480 # on failure. We just poll on its PID. On Unix, if the child
479 # process fails to start, it will be left in a zombie state until
481 # process fails to start, it will be left in a zombie state until
480 # the parent wait on it, which we cannot do since we expect a long
482 # the parent wait on it, which we cannot do since we expect a long
481 # running process on success. Instead we listen for SIGCHLD telling
483 # running process on success. Instead we listen for SIGCHLD telling
482 # us our child process terminated.
484 # us our child process terminated.
483 terminated = set()
485 terminated = set()
484
486
485 def handler(signum, frame):
487 def handler(signum, frame):
486 terminated.add(os.wait())
488 terminated.add(os.wait())
487
489
488 prevhandler = None
490 prevhandler = None
489 SIGCHLD = getattr(signal, 'SIGCHLD', None)
491 SIGCHLD = getattr(signal, 'SIGCHLD', None)
490 if SIGCHLD is not None:
492 if SIGCHLD is not None:
491 prevhandler = signal.signal(SIGCHLD, handler)
493 prevhandler = signal.signal(SIGCHLD, handler)
492 try:
494 try:
493 pid = spawndetached(args)
495 pid = spawndetached(args)
494 while not condfn():
496 while not condfn():
495 if (pid in terminated or not testpid(pid)) and not condfn():
497 if (pid in terminated or not testpid(pid)) and not condfn():
496 return -1
498 return -1
497 time.sleep(0.1)
499 time.sleep(0.1)
498 return pid
500 return pid
499 finally:
501 finally:
500 if prevhandler is not None:
502 if prevhandler is not None:
501 signal.signal(signal.SIGCHLD, prevhandler)
503 signal.signal(signal.SIGCHLD, prevhandler)
502
504
503
505
504 @contextlib.contextmanager
506 @contextlib.contextmanager
505 def uninterruptible(warn):
507 def uninterruptible(warn):
506 """Inhibit SIGINT handling on a region of code.
508 """Inhibit SIGINT handling on a region of code.
507
509
508 Note that if this is called in a non-main thread, it turns into a no-op.
510 Note that if this is called in a non-main thread, it turns into a no-op.
509
511
510 Args:
512 Args:
511 warn: A callable which takes no arguments, and returns True if the
513 warn: A callable which takes no arguments, and returns True if the
512 previous signal handling should be restored.
514 previous signal handling should be restored.
513 """
515 """
514
516
515 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
517 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
516 shouldbail = []
518 shouldbail = []
517
519
518 def disabledsiginthandler(*args):
520 def disabledsiginthandler(*args):
519 if warn():
521 if warn():
520 signal.signal(signal.SIGINT, oldsiginthandler[0])
522 signal.signal(signal.SIGINT, oldsiginthandler[0])
521 del oldsiginthandler[0]
523 del oldsiginthandler[0]
522 shouldbail.append(True)
524 shouldbail.append(True)
523
525
524 try:
526 try:
525 try:
527 try:
526 signal.signal(signal.SIGINT, disabledsiginthandler)
528 signal.signal(signal.SIGINT, disabledsiginthandler)
527 except ValueError:
529 except ValueError:
528 # wrong thread, oh well, we tried
530 # wrong thread, oh well, we tried
529 del oldsiginthandler[0]
531 del oldsiginthandler[0]
530 yield
532 yield
531 finally:
533 finally:
532 if oldsiginthandler:
534 if oldsiginthandler:
533 signal.signal(signal.SIGINT, oldsiginthandler[0])
535 signal.signal(signal.SIGINT, oldsiginthandler[0])
534 if shouldbail:
536 if shouldbail:
535 raise KeyboardInterrupt
537 raise KeyboardInterrupt
536
538
537
539
538 if pycompat.iswindows:
540 if pycompat.iswindows:
539 # no fork on Windows, but we can create a detached process
541 # no fork on Windows, but we can create a detached process
540 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
542 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
541 # No stdlib constant exists for this value
543 # No stdlib constant exists for this value
542 DETACHED_PROCESS = 0x00000008
544 DETACHED_PROCESS = 0x00000008
543 # Following creation flags might create a console GUI window.
545 # Following creation flags might create a console GUI window.
544 # Using subprocess.CREATE_NEW_CONSOLE might helps.
546 # Using subprocess.CREATE_NEW_CONSOLE might helps.
545 # See https://phab.mercurial-scm.org/D1701 for discussion
547 # See https://phab.mercurial-scm.org/D1701 for discussion
546 _creationflags = DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP
548 _creationflags = DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP
547
549
548 def runbgcommand(
550 def runbgcommand(
549 script, env, shell=False, stdout=None, stderr=None, ensurestart=True
551 script, env, shell=False, stdout=None, stderr=None, ensurestart=True
550 ):
552 ):
551 '''Spawn a command without waiting for it to finish.'''
553 '''Spawn a command without waiting for it to finish.'''
552 # we can't use close_fds *and* redirect stdin. I'm not sure that we
554 # we can't use close_fds *and* redirect stdin. I'm not sure that we
553 # need to because the detached process has no console connection.
555 # need to because the detached process has no console connection.
554 subprocess.Popen(
556 subprocess.Popen(
555 tonativestr(script),
557 tonativestr(script),
556 shell=shell,
558 shell=shell,
557 env=tonativeenv(env),
559 env=tonativeenv(env),
558 close_fds=True,
560 close_fds=True,
559 creationflags=_creationflags,
561 creationflags=_creationflags,
560 stdout=stdout,
562 stdout=stdout,
561 stderr=stderr,
563 stderr=stderr,
562 )
564 )
563
565
564
566
565 else:
567 else:
566
568
567 def runbgcommand(
569 def runbgcommand(
568 cmd, env, shell=False, stdout=None, stderr=None, ensurestart=True
570 cmd, env, shell=False, stdout=None, stderr=None, ensurestart=True
569 ):
571 ):
570 '''Spawn a command without waiting for it to finish.'''
572 '''Spawn a command without waiting for it to finish.'''
571 # double-fork to completely detach from the parent process
573 # double-fork to completely detach from the parent process
572 # based on http://code.activestate.com/recipes/278731
574 # based on http://code.activestate.com/recipes/278731
573 pid = os.fork()
575 pid = os.fork()
574 if pid:
576 if pid:
575 if not ensurestart:
577 if not ensurestart:
576 return
578 return
577 # Parent process
579 # Parent process
578 (_pid, status) = os.waitpid(pid, 0)
580 (_pid, status) = os.waitpid(pid, 0)
579 if os.WIFEXITED(status):
581 if os.WIFEXITED(status):
580 returncode = os.WEXITSTATUS(status)
582 returncode = os.WEXITSTATUS(status)
581 else:
583 else:
582 returncode = -(os.WTERMSIG(status))
584 returncode = -(os.WTERMSIG(status))
583 if returncode != 0:
585 if returncode != 0:
584 # The child process's return code is 0 on success, an errno
586 # The child process's return code is 0 on success, an errno
585 # value on failure, or 255 if we don't have a valid errno
587 # value on failure, or 255 if we don't have a valid errno
586 # value.
588 # value.
587 #
589 #
588 # (It would be slightly nicer to return the full exception info
590 # (It would be slightly nicer to return the full exception info
589 # over a pipe as the subprocess module does. For now it
591 # over a pipe as the subprocess module does. For now it
590 # doesn't seem worth adding that complexity here, though.)
592 # doesn't seem worth adding that complexity here, though.)
591 if returncode == 255:
593 if returncode == 255:
592 returncode = errno.EINVAL
594 returncode = errno.EINVAL
593 raise OSError(
595 raise OSError(
594 returncode,
596 returncode,
595 b'error running %r: %s' % (cmd, os.strerror(returncode)),
597 b'error running %r: %s' % (cmd, os.strerror(returncode)),
596 )
598 )
597 return
599 return
598
600
599 returncode = 255
601 returncode = 255
600 try:
602 try:
601 # Start a new session
603 # Start a new session
602 os.setsid()
604 os.setsid()
603
605
604 stdin = open(os.devnull, b'r')
606 stdin = open(os.devnull, b'r')
605 if stdout is None:
607 if stdout is None:
606 stdout = open(os.devnull, b'w')
608 stdout = open(os.devnull, b'w')
607 if stderr is None:
609 if stderr is None:
608 stderr = open(os.devnull, b'w')
610 stderr = open(os.devnull, b'w')
609
611
610 # connect stdin to devnull to make sure the subprocess can't
612 # connect stdin to devnull to make sure the subprocess can't
611 # muck up that stream for mercurial.
613 # muck up that stream for mercurial.
612 subprocess.Popen(
614 subprocess.Popen(
613 cmd,
615 cmd,
614 shell=shell,
616 shell=shell,
615 env=env,
617 env=env,
616 close_fds=True,
618 close_fds=True,
617 stdin=stdin,
619 stdin=stdin,
618 stdout=stdout,
620 stdout=stdout,
619 stderr=stderr,
621 stderr=stderr,
620 )
622 )
621 returncode = 0
623 returncode = 0
622 except EnvironmentError as ex:
624 except EnvironmentError as ex:
623 returncode = ex.errno & 0xFF
625 returncode = ex.errno & 0xFF
624 if returncode == 0:
626 if returncode == 0:
625 # This shouldn't happen, but just in case make sure the
627 # This shouldn't happen, but just in case make sure the
626 # return code is never 0 here.
628 # return code is never 0 here.
627 returncode = 255
629 returncode = 255
628 except Exception:
630 except Exception:
629 returncode = 255
631 returncode = 255
630 finally:
632 finally:
631 # mission accomplished, this child needs to exit and not
633 # mission accomplished, this child needs to exit and not
632 # continue the hg process here.
634 # continue the hg process here.
633 os._exit(returncode)
635 os._exit(returncode)
General Comments 0
You need to be logged in to leave comments. Login now