##// END OF EJS Templates
py3: use native string as fallback value for __file__ for consistency...
Martin von Zweigbergk -
r44054:ef43765c default
parent child Browse files
Show More
@@ -1,640 +1,640
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(
292 pycompat.fsencode(getattr(mainmod, '__file__', b''))
292 pycompat.fsencode(getattr(mainmod, '__file__', ''))
293 )
293 )
294 == b'hg'
294 == b'hg'
295 ):
295 ):
296 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
296 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
297 else:
297 else:
298 _sethgexecutable(
298 _sethgexecutable(
299 findexe(b'hg') or os.path.basename(pycompat.sysargv[0])
299 findexe(b'hg') or os.path.basename(pycompat.sysargv[0])
300 )
300 )
301 return _hgexecutable
301 return _hgexecutable
302
302
303
303
304 def _sethgexecutable(path):
304 def _sethgexecutable(path):
305 """set location of the 'hg' executable"""
305 """set location of the 'hg' executable"""
306 global _hgexecutable
306 global _hgexecutable
307 _hgexecutable = path
307 _hgexecutable = path
308
308
309
309
310 def _testfileno(f, stdf):
310 def _testfileno(f, stdf):
311 fileno = getattr(f, 'fileno', None)
311 fileno = getattr(f, 'fileno', None)
312 try:
312 try:
313 return fileno and fileno() == stdf.fileno()
313 return fileno and fileno() == stdf.fileno()
314 except io.UnsupportedOperation:
314 except io.UnsupportedOperation:
315 return False # fileno() raised UnsupportedOperation
315 return False # fileno() raised UnsupportedOperation
316
316
317
317
318 def isstdin(f):
318 def isstdin(f):
319 return _testfileno(f, sys.__stdin__)
319 return _testfileno(f, sys.__stdin__)
320
320
321
321
322 def isstdout(f):
322 def isstdout(f):
323 return _testfileno(f, sys.__stdout__)
323 return _testfileno(f, sys.__stdout__)
324
324
325
325
326 def protectstdio(uin, uout):
326 def protectstdio(uin, uout):
327 """Duplicate streams and redirect original if (uin, uout) are stdio
327 """Duplicate streams and redirect original if (uin, uout) are stdio
328
328
329 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
330 redirected to stderr so the output is still readable.
330 redirected to stderr so the output is still readable.
331
331
332 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
333 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
334 "owned" in that print(), exec(), etc. never reach to them.
334 "owned" in that print(), exec(), etc. never reach to them.
335 """
335 """
336 uout.flush()
336 uout.flush()
337 fin, fout = uin, uout
337 fin, fout = uin, uout
338 if _testfileno(uin, stdin):
338 if _testfileno(uin, stdin):
339 newfd = os.dup(uin.fileno())
339 newfd = os.dup(uin.fileno())
340 nullfd = os.open(os.devnull, os.O_RDONLY)
340 nullfd = os.open(os.devnull, os.O_RDONLY)
341 os.dup2(nullfd, uin.fileno())
341 os.dup2(nullfd, uin.fileno())
342 os.close(nullfd)
342 os.close(nullfd)
343 fin = os.fdopen(newfd, 'rb')
343 fin = os.fdopen(newfd, 'rb')
344 if _testfileno(uout, stdout):
344 if _testfileno(uout, stdout):
345 newfd = os.dup(uout.fileno())
345 newfd = os.dup(uout.fileno())
346 os.dup2(stderr.fileno(), uout.fileno())
346 os.dup2(stderr.fileno(), uout.fileno())
347 fout = os.fdopen(newfd, 'wb')
347 fout = os.fdopen(newfd, 'wb')
348 return fin, fout
348 return fin, fout
349
349
350
350
351 def restorestdio(uin, uout, fin, fout):
351 def restorestdio(uin, uout, fin, fout):
352 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
352 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
353 uout.flush()
353 uout.flush()
354 for f, uif in [(fin, uin), (fout, uout)]:
354 for f, uif in [(fin, uin), (fout, uout)]:
355 if f is not uif:
355 if f is not uif:
356 os.dup2(f.fileno(), uif.fileno())
356 os.dup2(f.fileno(), uif.fileno())
357 f.close()
357 f.close()
358
358
359
359
360 def shellenviron(environ=None):
360 def shellenviron(environ=None):
361 """return environ with optional override, useful for shelling out"""
361 """return environ with optional override, useful for shelling out"""
362
362
363 def py2shell(val):
363 def py2shell(val):
364 b'convert python object into string that is useful to shell'
364 b'convert python object into string that is useful to shell'
365 if val is None or val is False:
365 if val is None or val is False:
366 return b'0'
366 return b'0'
367 if val is True:
367 if val is True:
368 return b'1'
368 return b'1'
369 return pycompat.bytestr(val)
369 return pycompat.bytestr(val)
370
370
371 env = dict(encoding.environ)
371 env = dict(encoding.environ)
372 if environ:
372 if environ:
373 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))
374 env[b'HG'] = hgexecutable()
374 env[b'HG'] = hgexecutable()
375 return env
375 return env
376
376
377
377
378 if pycompat.iswindows:
378 if pycompat.iswindows:
379
379
380 def shelltonative(cmd, env):
380 def shelltonative(cmd, env):
381 return platform.shelltocmdexe( # pytype: disable=module-attr
381 return platform.shelltocmdexe( # pytype: disable=module-attr
382 cmd, shellenviron(env)
382 cmd, shellenviron(env)
383 )
383 )
384
384
385 tonativestr = encoding.strfromlocal
385 tonativestr = encoding.strfromlocal
386 else:
386 else:
387
387
388 def shelltonative(cmd, env):
388 def shelltonative(cmd, env):
389 return cmd
389 return cmd
390
390
391 tonativestr = pycompat.identity
391 tonativestr = pycompat.identity
392
392
393
393
394 def tonativeenv(env):
394 def tonativeenv(env):
395 '''convert the environment from bytes to strings suitable for Popen(), etc.
395 '''convert the environment from bytes to strings suitable for Popen(), etc.
396 '''
396 '''
397 return pycompat.rapply(tonativestr, env)
397 return pycompat.rapply(tonativestr, env)
398
398
399
399
400 def system(cmd, environ=None, cwd=None, out=None):
400 def system(cmd, environ=None, cwd=None, out=None):
401 '''enhanced shell command execution.
401 '''enhanced shell command execution.
402 run with environment maybe modified, maybe in different dir.
402 run with environment maybe modified, maybe in different dir.
403
403
404 if out is specified, it is assumed to be a file-like object that has a
404 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.'''
405 write() method. stdout and stderr will be redirected to out.'''
406 try:
406 try:
407 stdout.flush()
407 stdout.flush()
408 except Exception:
408 except Exception:
409 pass
409 pass
410 cmd = quotecommand(cmd)
410 cmd = quotecommand(cmd)
411 env = shellenviron(environ)
411 env = shellenviron(environ)
412 if out is None or isstdout(out):
412 if out is None or isstdout(out):
413 rc = subprocess.call(
413 rc = subprocess.call(
414 tonativestr(cmd),
414 tonativestr(cmd),
415 shell=True,
415 shell=True,
416 close_fds=closefds,
416 close_fds=closefds,
417 env=tonativeenv(env),
417 env=tonativeenv(env),
418 cwd=pycompat.rapply(tonativestr, cwd),
418 cwd=pycompat.rapply(tonativestr, cwd),
419 )
419 )
420 else:
420 else:
421 proc = subprocess.Popen(
421 proc = subprocess.Popen(
422 tonativestr(cmd),
422 tonativestr(cmd),
423 shell=True,
423 shell=True,
424 close_fds=closefds,
424 close_fds=closefds,
425 env=tonativeenv(env),
425 env=tonativeenv(env),
426 cwd=pycompat.rapply(tonativestr, cwd),
426 cwd=pycompat.rapply(tonativestr, cwd),
427 stdout=subprocess.PIPE,
427 stdout=subprocess.PIPE,
428 stderr=subprocess.STDOUT,
428 stderr=subprocess.STDOUT,
429 )
429 )
430 for line in iter(proc.stdout.readline, b''):
430 for line in iter(proc.stdout.readline, b''):
431 out.write(line)
431 out.write(line)
432 proc.wait()
432 proc.wait()
433 rc = proc.returncode
433 rc = proc.returncode
434 if pycompat.sysplatform == b'OpenVMS' and rc & 1:
434 if pycompat.sysplatform == b'OpenVMS' and rc & 1:
435 rc = 0
435 rc = 0
436 return rc
436 return rc
437
437
438
438
439 def gui():
439 def gui():
440 '''Are we running in a GUI?'''
440 '''Are we running in a GUI?'''
441 if pycompat.isdarwin:
441 if pycompat.isdarwin:
442 if b'SSH_CONNECTION' in encoding.environ:
442 if b'SSH_CONNECTION' in encoding.environ:
443 # handle SSH access to a box where the user is logged in
443 # handle SSH access to a box where the user is logged in
444 return False
444 return False
445 elif getattr(osutil, 'isgui', None):
445 elif getattr(osutil, 'isgui', None):
446 # check if a CoreGraphics session is available
446 # check if a CoreGraphics session is available
447 return osutil.isgui()
447 return osutil.isgui()
448 else:
448 else:
449 # pure build; use a safe default
449 # pure build; use a safe default
450 return True
450 return True
451 else:
451 else:
452 return pycompat.iswindows or encoding.environ.get(b"DISPLAY")
452 return pycompat.iswindows or encoding.environ.get(b"DISPLAY")
453
453
454
454
455 def hgcmd():
455 def hgcmd():
456 """Return the command used to execute current hg
456 """Return the command used to execute current hg
457
457
458 This is different from hgexecutable() because on Windows we want
458 This is different from hgexecutable() because on Windows we want
459 to avoid things opening new shell windows like batch files, so we
459 to avoid things opening new shell windows like batch files, so we
460 get either the python call or current executable.
460 get either the python call or current executable.
461 """
461 """
462 if mainfrozen():
462 if mainfrozen():
463 if getattr(sys, 'frozen', None) == b'macosx_app':
463 if getattr(sys, 'frozen', None) == b'macosx_app':
464 # Env variable set by py2app
464 # Env variable set by py2app
465 return [encoding.environ[b'EXECUTABLEPATH']]
465 return [encoding.environ[b'EXECUTABLEPATH']]
466 else:
466 else:
467 return [pycompat.sysexecutable]
467 return [pycompat.sysexecutable]
468 return _gethgcmd()
468 return _gethgcmd()
469
469
470
470
471 def rundetached(args, condfn):
471 def rundetached(args, condfn):
472 """Execute the argument list in a detached process.
472 """Execute the argument list in a detached process.
473
473
474 condfn is a callable which is called repeatedly and should return
474 condfn is a callable which is called repeatedly and should return
475 True once the child process is known to have started successfully.
475 True once the child process is known to have started successfully.
476 At this point, the child process PID is returned. If the child
476 At this point, the child process PID is returned. If the child
477 process fails to start or finishes before condfn() evaluates to
477 process fails to start or finishes before condfn() evaluates to
478 True, return -1.
478 True, return -1.
479 """
479 """
480 # Windows case is easier because the child process is either
480 # Windows case is easier because the child process is either
481 # successfully starting and validating the condition or exiting
481 # successfully starting and validating the condition or exiting
482 # on failure. We just poll on its PID. On Unix, if the child
482 # 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
483 # 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
484 # 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
485 # running process on success. Instead we listen for SIGCHLD telling
486 # us our child process terminated.
486 # us our child process terminated.
487 terminated = set()
487 terminated = set()
488
488
489 def handler(signum, frame):
489 def handler(signum, frame):
490 terminated.add(os.wait())
490 terminated.add(os.wait())
491
491
492 prevhandler = None
492 prevhandler = None
493 SIGCHLD = getattr(signal, 'SIGCHLD', None)
493 SIGCHLD = getattr(signal, 'SIGCHLD', None)
494 if SIGCHLD is not None:
494 if SIGCHLD is not None:
495 prevhandler = signal.signal(SIGCHLD, handler)
495 prevhandler = signal.signal(SIGCHLD, handler)
496 try:
496 try:
497 pid = spawndetached(args)
497 pid = spawndetached(args)
498 while not condfn():
498 while not condfn():
499 if (pid in terminated or not testpid(pid)) and not condfn():
499 if (pid in terminated or not testpid(pid)) and not condfn():
500 return -1
500 return -1
501 time.sleep(0.1)
501 time.sleep(0.1)
502 return pid
502 return pid
503 finally:
503 finally:
504 if prevhandler is not None:
504 if prevhandler is not None:
505 signal.signal(signal.SIGCHLD, prevhandler)
505 signal.signal(signal.SIGCHLD, prevhandler)
506
506
507
507
508 @contextlib.contextmanager
508 @contextlib.contextmanager
509 def uninterruptible(warn):
509 def uninterruptible(warn):
510 """Inhibit SIGINT handling on a region of code.
510 """Inhibit SIGINT handling on a region of code.
511
511
512 Note that if this is called in a non-main thread, it turns into a no-op.
512 Note that if this is called in a non-main thread, it turns into a no-op.
513
513
514 Args:
514 Args:
515 warn: A callable which takes no arguments, and returns True if the
515 warn: A callable which takes no arguments, and returns True if the
516 previous signal handling should be restored.
516 previous signal handling should be restored.
517 """
517 """
518
518
519 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
519 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
520 shouldbail = []
520 shouldbail = []
521
521
522 def disabledsiginthandler(*args):
522 def disabledsiginthandler(*args):
523 if warn():
523 if warn():
524 signal.signal(signal.SIGINT, oldsiginthandler[0])
524 signal.signal(signal.SIGINT, oldsiginthandler[0])
525 del oldsiginthandler[0]
525 del oldsiginthandler[0]
526 shouldbail.append(True)
526 shouldbail.append(True)
527
527
528 try:
528 try:
529 try:
529 try:
530 signal.signal(signal.SIGINT, disabledsiginthandler)
530 signal.signal(signal.SIGINT, disabledsiginthandler)
531 except ValueError:
531 except ValueError:
532 # wrong thread, oh well, we tried
532 # wrong thread, oh well, we tried
533 del oldsiginthandler[0]
533 del oldsiginthandler[0]
534 yield
534 yield
535 finally:
535 finally:
536 if oldsiginthandler:
536 if oldsiginthandler:
537 signal.signal(signal.SIGINT, oldsiginthandler[0])
537 signal.signal(signal.SIGINT, oldsiginthandler[0])
538 if shouldbail:
538 if shouldbail:
539 raise KeyboardInterrupt
539 raise KeyboardInterrupt
540
540
541
541
542 if pycompat.iswindows:
542 if pycompat.iswindows:
543 # no fork on Windows, but we can create a detached process
543 # no fork on Windows, but we can create a detached process
544 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
544 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
545 # No stdlib constant exists for this value
545 # No stdlib constant exists for this value
546 DETACHED_PROCESS = 0x00000008
546 DETACHED_PROCESS = 0x00000008
547 # Following creation flags might create a console GUI window.
547 # Following creation flags might create a console GUI window.
548 # Using subprocess.CREATE_NEW_CONSOLE might helps.
548 # Using subprocess.CREATE_NEW_CONSOLE might helps.
549 # See https://phab.mercurial-scm.org/D1701 for discussion
549 # See https://phab.mercurial-scm.org/D1701 for discussion
550 _creationflags = (
550 _creationflags = (
551 DETACHED_PROCESS
551 DETACHED_PROCESS
552 | subprocess.CREATE_NEW_PROCESS_GROUP # pytype: disable=module-attr
552 | subprocess.CREATE_NEW_PROCESS_GROUP # pytype: disable=module-attr
553 )
553 )
554
554
555 def runbgcommand(
555 def runbgcommand(
556 script, env, shell=False, stdout=None, stderr=None, ensurestart=True
556 script, env, shell=False, stdout=None, stderr=None, ensurestart=True
557 ):
557 ):
558 '''Spawn a command without waiting for it to finish.'''
558 '''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
559 # 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.
560 # need to because the detached process has no console connection.
561 subprocess.Popen(
561 subprocess.Popen(
562 tonativestr(script),
562 tonativestr(script),
563 shell=shell,
563 shell=shell,
564 env=tonativeenv(env),
564 env=tonativeenv(env),
565 close_fds=True,
565 close_fds=True,
566 creationflags=_creationflags,
566 creationflags=_creationflags,
567 stdout=stdout,
567 stdout=stdout,
568 stderr=stderr,
568 stderr=stderr,
569 )
569 )
570
570
571
571
572 else:
572 else:
573
573
574 def runbgcommand(
574 def runbgcommand(
575 cmd, env, shell=False, stdout=None, stderr=None, ensurestart=True
575 cmd, env, shell=False, stdout=None, stderr=None, ensurestart=True
576 ):
576 ):
577 '''Spawn a command without waiting for it to finish.'''
577 '''Spawn a command without waiting for it to finish.'''
578 # double-fork to completely detach from the parent process
578 # double-fork to completely detach from the parent process
579 # based on http://code.activestate.com/recipes/278731
579 # based on http://code.activestate.com/recipes/278731
580 pid = os.fork()
580 pid = os.fork()
581 if pid:
581 if pid:
582 if not ensurestart:
582 if not ensurestart:
583 return
583 return
584 # Parent process
584 # Parent process
585 (_pid, status) = os.waitpid(pid, 0)
585 (_pid, status) = os.waitpid(pid, 0)
586 if os.WIFEXITED(status):
586 if os.WIFEXITED(status):
587 returncode = os.WEXITSTATUS(status)
587 returncode = os.WEXITSTATUS(status)
588 else:
588 else:
589 returncode = -(os.WTERMSIG(status))
589 returncode = -(os.WTERMSIG(status))
590 if returncode != 0:
590 if returncode != 0:
591 # The child process's return code is 0 on success, an errno
591 # 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
592 # value on failure, or 255 if we don't have a valid errno
593 # value.
593 # value.
594 #
594 #
595 # (It would be slightly nicer to return the full exception info
595 # (It would be slightly nicer to return the full exception info
596 # over a pipe as the subprocess module does. For now it
596 # over a pipe as the subprocess module does. For now it
597 # doesn't seem worth adding that complexity here, though.)
597 # doesn't seem worth adding that complexity here, though.)
598 if returncode == 255:
598 if returncode == 255:
599 returncode = errno.EINVAL
599 returncode = errno.EINVAL
600 raise OSError(
600 raise OSError(
601 returncode,
601 returncode,
602 b'error running %r: %s' % (cmd, os.strerror(returncode)),
602 b'error running %r: %s' % (cmd, os.strerror(returncode)),
603 )
603 )
604 return
604 return
605
605
606 returncode = 255
606 returncode = 255
607 try:
607 try:
608 # Start a new session
608 # Start a new session
609 os.setsid()
609 os.setsid()
610
610
611 stdin = open(os.devnull, b'r')
611 stdin = open(os.devnull, b'r')
612 if stdout is None:
612 if stdout is None:
613 stdout = open(os.devnull, b'w')
613 stdout = open(os.devnull, b'w')
614 if stderr is None:
614 if stderr is None:
615 stderr = open(os.devnull, b'w')
615 stderr = open(os.devnull, b'w')
616
616
617 # connect stdin to devnull to make sure the subprocess can't
617 # connect stdin to devnull to make sure the subprocess can't
618 # muck up that stream for mercurial.
618 # muck up that stream for mercurial.
619 subprocess.Popen(
619 subprocess.Popen(
620 cmd,
620 cmd,
621 shell=shell,
621 shell=shell,
622 env=env,
622 env=env,
623 close_fds=True,
623 close_fds=True,
624 stdin=stdin,
624 stdin=stdin,
625 stdout=stdout,
625 stdout=stdout,
626 stderr=stderr,
626 stderr=stderr,
627 )
627 )
628 returncode = 0
628 returncode = 0
629 except EnvironmentError as ex:
629 except EnvironmentError as ex:
630 returncode = ex.errno & 0xFF
630 returncode = ex.errno & 0xFF
631 if returncode == 0:
631 if returncode == 0:
632 # This shouldn't happen, but just in case make sure the
632 # This shouldn't happen, but just in case make sure the
633 # return code is never 0 here.
633 # return code is never 0 here.
634 returncode = 255
634 returncode = 255
635 except Exception:
635 except Exception:
636 returncode = 255
636 returncode = 255
637 finally:
637 finally:
638 # mission accomplished, this child needs to exit and not
638 # mission accomplished, this child needs to exit and not
639 # continue the hg process here.
639 # continue the hg process here.
640 os._exit(returncode)
640 os._exit(returncode)
General Comments 0
You need to be logged in to leave comments. Login now