##// END OF EJS Templates
py3: use pycompat.sysargv[0] for instead of fsencode(sys.argv[0])...
Martin von Zweigbergk -
r43120:db51a4ac default
parent child Browse files
Show More
@@ -1,541 +1,544 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(r"__main__")) # tools/freeze
224 imp.is_frozen(r"__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 (not pycompat.iswindows and 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')
249 _sethgexecutable(pycompat.fsencode(exe))
249 if exe:
250 _sethgexecutable(pycompat.fsencode(exe))
251 else:
252 _sethgexecutable(os.path.basename(pycompat.sysargv[0]))
250 return _hgexecutable
253 return _hgexecutable
251
254
252 def _sethgexecutable(path):
255 def _sethgexecutable(path):
253 """set location of the 'hg' executable"""
256 """set location of the 'hg' executable"""
254 global _hgexecutable
257 global _hgexecutable
255 _hgexecutable = path
258 _hgexecutable = path
256
259
257 def _testfileno(f, stdf):
260 def _testfileno(f, stdf):
258 fileno = getattr(f, 'fileno', None)
261 fileno = getattr(f, 'fileno', None)
259 try:
262 try:
260 return fileno and fileno() == stdf.fileno()
263 return fileno and fileno() == stdf.fileno()
261 except io.UnsupportedOperation:
264 except io.UnsupportedOperation:
262 return False # fileno() raised UnsupportedOperation
265 return False # fileno() raised UnsupportedOperation
263
266
264 def isstdin(f):
267 def isstdin(f):
265 return _testfileno(f, sys.__stdin__)
268 return _testfileno(f, sys.__stdin__)
266
269
267 def isstdout(f):
270 def isstdout(f):
268 return _testfileno(f, sys.__stdout__)
271 return _testfileno(f, sys.__stdout__)
269
272
270 def protectstdio(uin, uout):
273 def protectstdio(uin, uout):
271 """Duplicate streams and redirect original if (uin, uout) are stdio
274 """Duplicate streams and redirect original if (uin, uout) are stdio
272
275
273 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
276 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.
277 redirected to stderr so the output is still readable.
275
278
276 Returns (fin, fout) which point to the original (uin, uout) fds, but
279 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
280 may be copy of (uin, uout). The returned streams can be considered
278 "owned" in that print(), exec(), etc. never reach to them.
281 "owned" in that print(), exec(), etc. never reach to them.
279 """
282 """
280 uout.flush()
283 uout.flush()
281 fin, fout = uin, uout
284 fin, fout = uin, uout
282 if _testfileno(uin, stdin):
285 if _testfileno(uin, stdin):
283 newfd = os.dup(uin.fileno())
286 newfd = os.dup(uin.fileno())
284 nullfd = os.open(os.devnull, os.O_RDONLY)
287 nullfd = os.open(os.devnull, os.O_RDONLY)
285 os.dup2(nullfd, uin.fileno())
288 os.dup2(nullfd, uin.fileno())
286 os.close(nullfd)
289 os.close(nullfd)
287 fin = os.fdopen(newfd, r'rb')
290 fin = os.fdopen(newfd, r'rb')
288 if _testfileno(uout, stdout):
291 if _testfileno(uout, stdout):
289 newfd = os.dup(uout.fileno())
292 newfd = os.dup(uout.fileno())
290 os.dup2(stderr.fileno(), uout.fileno())
293 os.dup2(stderr.fileno(), uout.fileno())
291 fout = os.fdopen(newfd, r'wb')
294 fout = os.fdopen(newfd, r'wb')
292 return fin, fout
295 return fin, fout
293
296
294 def restorestdio(uin, uout, fin, fout):
297 def restorestdio(uin, uout, fin, fout):
295 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
298 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
296 uout.flush()
299 uout.flush()
297 for f, uif in [(fin, uin), (fout, uout)]:
300 for f, uif in [(fin, uin), (fout, uout)]:
298 if f is not uif:
301 if f is not uif:
299 os.dup2(f.fileno(), uif.fileno())
302 os.dup2(f.fileno(), uif.fileno())
300 f.close()
303 f.close()
301
304
302 def shellenviron(environ=None):
305 def shellenviron(environ=None):
303 """return environ with optional override, useful for shelling out"""
306 """return environ with optional override, useful for shelling out"""
304 def py2shell(val):
307 def py2shell(val):
305 'convert python object into string that is useful to shell'
308 'convert python object into string that is useful to shell'
306 if val is None or val is False:
309 if val is None or val is False:
307 return '0'
310 return '0'
308 if val is True:
311 if val is True:
309 return '1'
312 return '1'
310 return pycompat.bytestr(val)
313 return pycompat.bytestr(val)
311 env = dict(encoding.environ)
314 env = dict(encoding.environ)
312 if environ:
315 if environ:
313 env.update((k, py2shell(v)) for k, v in environ.iteritems())
316 env.update((k, py2shell(v)) for k, v in environ.iteritems())
314 env['HG'] = hgexecutable()
317 env['HG'] = hgexecutable()
315 return env
318 return env
316
319
317 if pycompat.iswindows:
320 if pycompat.iswindows:
318 def shelltonative(cmd, env):
321 def shelltonative(cmd, env):
319 return platform.shelltocmdexe(cmd, shellenviron(env))
322 return platform.shelltocmdexe(cmd, shellenviron(env))
320
323
321 tonativestr = encoding.strfromlocal
324 tonativestr = encoding.strfromlocal
322 else:
325 else:
323 def shelltonative(cmd, env):
326 def shelltonative(cmd, env):
324 return cmd
327 return cmd
325
328
326 tonativestr = pycompat.identity
329 tonativestr = pycompat.identity
327
330
328 def tonativeenv(env):
331 def tonativeenv(env):
329 '''convert the environment from bytes to strings suitable for Popen(), etc.
332 '''convert the environment from bytes to strings suitable for Popen(), etc.
330 '''
333 '''
331 return pycompat.rapply(tonativestr, env)
334 return pycompat.rapply(tonativestr, env)
332
335
333 def system(cmd, environ=None, cwd=None, out=None):
336 def system(cmd, environ=None, cwd=None, out=None):
334 '''enhanced shell command execution.
337 '''enhanced shell command execution.
335 run with environment maybe modified, maybe in different dir.
338 run with environment maybe modified, maybe in different dir.
336
339
337 if out is specified, it is assumed to be a file-like object that has a
340 if out is specified, it is assumed to be a file-like object that has a
338 write() method. stdout and stderr will be redirected to out.'''
341 write() method. stdout and stderr will be redirected to out.'''
339 try:
342 try:
340 stdout.flush()
343 stdout.flush()
341 except Exception:
344 except Exception:
342 pass
345 pass
343 cmd = quotecommand(cmd)
346 cmd = quotecommand(cmd)
344 env = shellenviron(environ)
347 env = shellenviron(environ)
345 if out is None or isstdout(out):
348 if out is None or isstdout(out):
346 rc = subprocess.call(tonativestr(cmd),
349 rc = subprocess.call(tonativestr(cmd),
347 shell=True, close_fds=closefds,
350 shell=True, close_fds=closefds,
348 env=tonativeenv(env),
351 env=tonativeenv(env),
349 cwd=pycompat.rapply(tonativestr, cwd))
352 cwd=pycompat.rapply(tonativestr, cwd))
350 else:
353 else:
351 proc = subprocess.Popen(tonativestr(cmd),
354 proc = subprocess.Popen(tonativestr(cmd),
352 shell=True, close_fds=closefds,
355 shell=True, close_fds=closefds,
353 env=tonativeenv(env),
356 env=tonativeenv(env),
354 cwd=pycompat.rapply(tonativestr, cwd),
357 cwd=pycompat.rapply(tonativestr, cwd),
355 stdout=subprocess.PIPE,
358 stdout=subprocess.PIPE,
356 stderr=subprocess.STDOUT)
359 stderr=subprocess.STDOUT)
357 for line in iter(proc.stdout.readline, ''):
360 for line in iter(proc.stdout.readline, ''):
358 out.write(line)
361 out.write(line)
359 proc.wait()
362 proc.wait()
360 rc = proc.returncode
363 rc = proc.returncode
361 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
364 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
362 rc = 0
365 rc = 0
363 return rc
366 return rc
364
367
365 def gui():
368 def gui():
366 '''Are we running in a GUI?'''
369 '''Are we running in a GUI?'''
367 if pycompat.isdarwin:
370 if pycompat.isdarwin:
368 if 'SSH_CONNECTION' in encoding.environ:
371 if 'SSH_CONNECTION' in encoding.environ:
369 # handle SSH access to a box where the user is logged in
372 # handle SSH access to a box where the user is logged in
370 return False
373 return False
371 elif getattr(osutil, 'isgui', None):
374 elif getattr(osutil, 'isgui', None):
372 # check if a CoreGraphics session is available
375 # check if a CoreGraphics session is available
373 return osutil.isgui()
376 return osutil.isgui()
374 else:
377 else:
375 # pure build; use a safe default
378 # pure build; use a safe default
376 return True
379 return True
377 else:
380 else:
378 return pycompat.iswindows or encoding.environ.get("DISPLAY")
381 return pycompat.iswindows or encoding.environ.get("DISPLAY")
379
382
380 def hgcmd():
383 def hgcmd():
381 """Return the command used to execute current hg
384 """Return the command used to execute current hg
382
385
383 This is different from hgexecutable() because on Windows we want
386 This is different from hgexecutable() because on Windows we want
384 to avoid things opening new shell windows like batch files, so we
387 to avoid things opening new shell windows like batch files, so we
385 get either the python call or current executable.
388 get either the python call or current executable.
386 """
389 """
387 if mainfrozen():
390 if mainfrozen():
388 if getattr(sys, 'frozen', None) == 'macosx_app':
391 if getattr(sys, 'frozen', None) == 'macosx_app':
389 # Env variable set by py2app
392 # Env variable set by py2app
390 return [encoding.environ['EXECUTABLEPATH']]
393 return [encoding.environ['EXECUTABLEPATH']]
391 else:
394 else:
392 return [pycompat.sysexecutable]
395 return [pycompat.sysexecutable]
393 return _gethgcmd()
396 return _gethgcmd()
394
397
395 def rundetached(args, condfn):
398 def rundetached(args, condfn):
396 """Execute the argument list in a detached process.
399 """Execute the argument list in a detached process.
397
400
398 condfn is a callable which is called repeatedly and should return
401 condfn is a callable which is called repeatedly and should return
399 True once the child process is known to have started successfully.
402 True once the child process is known to have started successfully.
400 At this point, the child process PID is returned. If the child
403 At this point, the child process PID is returned. If the child
401 process fails to start or finishes before condfn() evaluates to
404 process fails to start or finishes before condfn() evaluates to
402 True, return -1.
405 True, return -1.
403 """
406 """
404 # Windows case is easier because the child process is either
407 # Windows case is easier because the child process is either
405 # successfully starting and validating the condition or exiting
408 # successfully starting and validating the condition or exiting
406 # on failure. We just poll on its PID. On Unix, if the child
409 # on failure. We just poll on its PID. On Unix, if the child
407 # process fails to start, it will be left in a zombie state until
410 # process fails to start, it will be left in a zombie state until
408 # the parent wait on it, which we cannot do since we expect a long
411 # the parent wait on it, which we cannot do since we expect a long
409 # running process on success. Instead we listen for SIGCHLD telling
412 # running process on success. Instead we listen for SIGCHLD telling
410 # us our child process terminated.
413 # us our child process terminated.
411 terminated = set()
414 terminated = set()
412 def handler(signum, frame):
415 def handler(signum, frame):
413 terminated.add(os.wait())
416 terminated.add(os.wait())
414 prevhandler = None
417 prevhandler = None
415 SIGCHLD = getattr(signal, 'SIGCHLD', None)
418 SIGCHLD = getattr(signal, 'SIGCHLD', None)
416 if SIGCHLD is not None:
419 if SIGCHLD is not None:
417 prevhandler = signal.signal(SIGCHLD, handler)
420 prevhandler = signal.signal(SIGCHLD, handler)
418 try:
421 try:
419 pid = spawndetached(args)
422 pid = spawndetached(args)
420 while not condfn():
423 while not condfn():
421 if ((pid in terminated or not testpid(pid))
424 if ((pid in terminated or not testpid(pid))
422 and not condfn()):
425 and not condfn()):
423 return -1
426 return -1
424 time.sleep(0.1)
427 time.sleep(0.1)
425 return pid
428 return pid
426 finally:
429 finally:
427 if prevhandler is not None:
430 if prevhandler is not None:
428 signal.signal(signal.SIGCHLD, prevhandler)
431 signal.signal(signal.SIGCHLD, prevhandler)
429
432
430 @contextlib.contextmanager
433 @contextlib.contextmanager
431 def uninterruptible(warn):
434 def uninterruptible(warn):
432 """Inhibit SIGINT handling on a region of code.
435 """Inhibit SIGINT handling on a region of code.
433
436
434 Note that if this is called in a non-main thread, it turns into a no-op.
437 Note that if this is called in a non-main thread, it turns into a no-op.
435
438
436 Args:
439 Args:
437 warn: A callable which takes no arguments, and returns True if the
440 warn: A callable which takes no arguments, and returns True if the
438 previous signal handling should be restored.
441 previous signal handling should be restored.
439 """
442 """
440
443
441 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
444 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
442 shouldbail = []
445 shouldbail = []
443
446
444 def disabledsiginthandler(*args):
447 def disabledsiginthandler(*args):
445 if warn():
448 if warn():
446 signal.signal(signal.SIGINT, oldsiginthandler[0])
449 signal.signal(signal.SIGINT, oldsiginthandler[0])
447 del oldsiginthandler[0]
450 del oldsiginthandler[0]
448 shouldbail.append(True)
451 shouldbail.append(True)
449
452
450 try:
453 try:
451 try:
454 try:
452 signal.signal(signal.SIGINT, disabledsiginthandler)
455 signal.signal(signal.SIGINT, disabledsiginthandler)
453 except ValueError:
456 except ValueError:
454 # wrong thread, oh well, we tried
457 # wrong thread, oh well, we tried
455 del oldsiginthandler[0]
458 del oldsiginthandler[0]
456 yield
459 yield
457 finally:
460 finally:
458 if oldsiginthandler:
461 if oldsiginthandler:
459 signal.signal(signal.SIGINT, oldsiginthandler[0])
462 signal.signal(signal.SIGINT, oldsiginthandler[0])
460 if shouldbail:
463 if shouldbail:
461 raise KeyboardInterrupt
464 raise KeyboardInterrupt
462
465
463 if pycompat.iswindows:
466 if pycompat.iswindows:
464 # no fork on Windows, but we can create a detached process
467 # no fork on Windows, but we can create a detached process
465 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
468 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
466 # No stdlib constant exists for this value
469 # No stdlib constant exists for this value
467 DETACHED_PROCESS = 0x00000008
470 DETACHED_PROCESS = 0x00000008
468 # Following creation flags might create a console GUI window.
471 # Following creation flags might create a console GUI window.
469 # Using subprocess.CREATE_NEW_CONSOLE might helps.
472 # Using subprocess.CREATE_NEW_CONSOLE might helps.
470 # See https://phab.mercurial-scm.org/D1701 for discussion
473 # See https://phab.mercurial-scm.org/D1701 for discussion
471 _creationflags = DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP
474 _creationflags = DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP
472
475
473 def runbgcommand(
476 def runbgcommand(
474 script, env, shell=False, stdout=None, stderr=None, ensurestart=True):
477 script, env, shell=False, stdout=None, stderr=None, ensurestart=True):
475 '''Spawn a command without waiting for it to finish.'''
478 '''Spawn a command without waiting for it to finish.'''
476 # we can't use close_fds *and* redirect stdin. I'm not sure that we
479 # we can't use close_fds *and* redirect stdin. I'm not sure that we
477 # need to because the detached process has no console connection.
480 # need to because the detached process has no console connection.
478 subprocess.Popen(
481 subprocess.Popen(
479 tonativestr(script),
482 tonativestr(script),
480 shell=shell, env=tonativeenv(env), close_fds=True,
483 shell=shell, env=tonativeenv(env), close_fds=True,
481 creationflags=_creationflags, stdout=stdout,
484 creationflags=_creationflags, stdout=stdout,
482 stderr=stderr)
485 stderr=stderr)
483 else:
486 else:
484 def runbgcommand(
487 def runbgcommand(
485 cmd, env, shell=False, stdout=None, stderr=None, ensurestart=True):
488 cmd, env, shell=False, stdout=None, stderr=None, ensurestart=True):
486 '''Spawn a command without waiting for it to finish.'''
489 '''Spawn a command without waiting for it to finish.'''
487 # double-fork to completely detach from the parent process
490 # double-fork to completely detach from the parent process
488 # based on http://code.activestate.com/recipes/278731
491 # based on http://code.activestate.com/recipes/278731
489 pid = os.fork()
492 pid = os.fork()
490 if pid:
493 if pid:
491 if not ensurestart:
494 if not ensurestart:
492 return
495 return
493 # Parent process
496 # Parent process
494 (_pid, status) = os.waitpid(pid, 0)
497 (_pid, status) = os.waitpid(pid, 0)
495 if os.WIFEXITED(status):
498 if os.WIFEXITED(status):
496 returncode = os.WEXITSTATUS(status)
499 returncode = os.WEXITSTATUS(status)
497 else:
500 else:
498 returncode = -os.WTERMSIG(status)
501 returncode = -os.WTERMSIG(status)
499 if returncode != 0:
502 if returncode != 0:
500 # The child process's return code is 0 on success, an errno
503 # The child process's return code is 0 on success, an errno
501 # value on failure, or 255 if we don't have a valid errno
504 # value on failure, or 255 if we don't have a valid errno
502 # value.
505 # value.
503 #
506 #
504 # (It would be slightly nicer to return the full exception info
507 # (It would be slightly nicer to return the full exception info
505 # over a pipe as the subprocess module does. For now it
508 # over a pipe as the subprocess module does. For now it
506 # doesn't seem worth adding that complexity here, though.)
509 # doesn't seem worth adding that complexity here, though.)
507 if returncode == 255:
510 if returncode == 255:
508 returncode = errno.EINVAL
511 returncode = errno.EINVAL
509 raise OSError(returncode, 'error running %r: %s' %
512 raise OSError(returncode, 'error running %r: %s' %
510 (cmd, os.strerror(returncode)))
513 (cmd, os.strerror(returncode)))
511 return
514 return
512
515
513 returncode = 255
516 returncode = 255
514 try:
517 try:
515 # Start a new session
518 # Start a new session
516 os.setsid()
519 os.setsid()
517
520
518 stdin = open(os.devnull, 'r')
521 stdin = open(os.devnull, 'r')
519 if stdout is None:
522 if stdout is None:
520 stdout = open(os.devnull, 'w')
523 stdout = open(os.devnull, 'w')
521 if stderr is None:
524 if stderr is None:
522 stderr = open(os.devnull, 'w')
525 stderr = open(os.devnull, 'w')
523
526
524 # connect stdin to devnull to make sure the subprocess can't
527 # connect stdin to devnull to make sure the subprocess can't
525 # muck up that stream for mercurial.
528 # muck up that stream for mercurial.
526 subprocess.Popen(
529 subprocess.Popen(
527 cmd, shell=shell, env=env, close_fds=True,
530 cmd, shell=shell, env=env, close_fds=True,
528 stdin=stdin, stdout=stdout, stderr=stderr)
531 stdin=stdin, stdout=stdout, stderr=stderr)
529 returncode = 0
532 returncode = 0
530 except EnvironmentError as ex:
533 except EnvironmentError as ex:
531 returncode = (ex.errno & 0xff)
534 returncode = (ex.errno & 0xff)
532 if returncode == 0:
535 if returncode == 0:
533 # This shouldn't happen, but just in case make sure the
536 # This shouldn't happen, but just in case make sure the
534 # return code is never 0 here.
537 # return code is never 0 here.
535 returncode = 255
538 returncode = 255
536 except Exception:
539 except Exception:
537 returncode = 255
540 returncode = 255
538 finally:
541 finally:
539 # mission accomplished, this child needs to exit and not
542 # mission accomplished, this child needs to exit and not
540 # continue the hg process here.
543 # continue the hg process here.
541 os._exit(returncode)
544 os._exit(returncode)
General Comments 0
You need to be logged in to leave comments. Login now