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