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