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