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