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