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