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