diff --git a/mercurial/utils/procutil.py b/mercurial/utils/procutil.py --- a/mercurial/utils/procutil.py +++ b/mercurial/utils/procutil.py @@ -691,90 +691,60 @@ # calls gui() while we're calling fork(). gui() - # double-fork to completely detach from the parent process - # based on http://code.activestate.com/recipes/278731 + if shell: + script = cmd + else: + if isinstance(cmd, bytes): + cmd = [cmd] + script = b' '.join(shellquote(x) for x in cmd) if record_wait is None: - pid = os.fork() - if pid: - if not ensurestart: - # Even though we're not waiting on the child process, - # we still must call waitpid() on it at some point so - # it's not a zombie/defunct. This is especially relevant for - # chg since the parent process won't die anytime soon. - # We use a thread to make the overhead tiny. - def _do_wait(): - os.waitpid(pid, 0) + # double-fork to completely detach from the parent process + script = b'( %s ) &' % script + start_new_session = True + else: + start_new_session = False + ensurestart = True - t = threading.Thread(target=_do_wait) - t.daemon = True - t.start() - return - # Parent process - (_pid, status) = os.waitpid(pid, 0) - if os.WIFEXITED(status): - returncode = os.WEXITSTATUS(status) - else: - returncode = -(os.WTERMSIG(status)) - if returncode != 0: - # The child process's return code is 0 on success, an errno - # value on failure, or 255 if we don't have a valid errno - # value. - # - # (It would be slightly nicer to return the full exception info - # over a pipe as the subprocess module does. For now it - # doesn't seem worth adding that complexity here, though.) - if returncode == 255: - returncode = errno.EINVAL - raise OSError( - returncode, - b'error running %r: %s' - % (cmd, os.strerror(returncode)), - ) - return - - returncode = 255 try: - if record_wait is None: - # Start a new session - os.setsid() - # connect stdin to devnull to make sure the subprocess can't - # muck up that stream for mercurial. if stdin_bytes is None: - stdin = open(os.devnull, b'r') + stdin = subprocess.DEVNULL else: stdin = pycompat.unnamedtempfile() stdin.write(stdin_bytes) stdin.flush() stdin.seek(0) - if stdout is None: - stdout = open(os.devnull, b'w') + stdout = subprocess.DEVNULL if stderr is None: - stderr = open(os.devnull, b'w') + stderr = subprocess.DEVNULL p = subprocess.Popen( - cmd, - shell=shell, + script, + shell=True, env=env, close_fds=True, stdin=stdin, stdout=stdout, stderr=stderr, + start_new_session=start_new_session, ) + except Exception: if record_wait is not None: - record_wait(p.wait) - returncode = 0 - except EnvironmentError as ex: - returncode = ex.errno & 0xFF - if returncode == 0: - # This shouldn't happen, but just in case make sure the - # return code is never 0 here. - returncode = 255 - except Exception: - returncode = 255 + record_wait(255) + raise finally: - # mission accomplished, this child needs to exit and not - # continue the hg process here. - stdin.close() - if record_wait is None: - os._exit(returncode) + if stdin_bytes is not None: + stdin.close() + if not ensurestart: + # Even though we're not waiting on the child process, + # we still must call waitpid() on it at some point so + # it's not a zombie/defunct. This is especially relevant for + # chg since the parent process won't die anytime soon. + # We use a thread to make the overhead tiny. + t = threading.Thread(target=lambda: p.wait) + t.daemon = True + t.start() + else: + returncode = p.wait + if record_wait is not None: + record_wait(returncode)