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