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