mirror of
https://github.com/facebook/sapling.git
synced 2024-10-16 11:52:02 +03:00
584656dff3
Summary: Turned on the auto formatter. Ran `arc lint --apply-patches --take BLACK **/*.py`. Then run `arc lint` again so some other autofixers like spellchecker etc. looked at the code base. Manually accept the changes whenever they make sense, or use a workaround (ex. changing "dict()" to "dict constructor") where autofix is false positive. Disabled linters on files that are hard (i18n/polib.py) to fix, or less interesting to fix (hgsubversion tests), or cannot be fixed without breaking OSS build (FBPYTHON4). Conflicted linters (test-check-module-imports.t, part of test-check-code.t, test-check-pyflakes.t) are removed or disabled. Duplicated linters (test-check-pyflakes.t, test-check-pylint.t) are removed. An issue of the auto-formatter is lines are no longer guarnateed to be <= 80 chars. But that seems less important comparing with the benefit auto-formatter provides. As we're here, also remove test-check-py3-compat.t, as it is currently broken if `PYTHON3=/bin/python3` is set. Reviewed By: wez, phillco, simpkins, pkaush, singhsrb Differential Revision: D8173629 fbshipit-source-id: 90e248ae0c5e6eaadbe25520a6ee42d32005621b
273 lines
7.6 KiB
Python
Executable File
273 lines
7.6 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
# sshaskpass.py
|
|
#
|
|
# Copyright 2016 Facebook, Inc.
|
|
#
|
|
# This software may be used and distributed according to the terms of the
|
|
# GNU General Public License version 2 or any later version.
|
|
|
|
"""ssh-askpass implementation that works with chg
|
|
|
|
chg runs ssh at server side, and ssh does not have access to /dev/tty thus
|
|
unable to ask for password interactively when its output is being piped (ex.
|
|
during hg push or pull).
|
|
|
|
When ssh is unable to use /dev/tty, it will try to run SSH_ASKPASS if DISPLAY
|
|
is set, which is usually a GUI program. Here we set it to a special program
|
|
receiving fds from a simple unix socket server.
|
|
|
|
This file is both a mercurial extension to start that tty server and a
|
|
standalone ssh-askpass script.
|
|
"""
|
|
|
|
# Attention: Do NOT import anything inside mercurial here. This file also runs
|
|
# standalone without the mercurial environment, in which case it cannot import
|
|
# mercurial modules correctly.
|
|
|
|
import contextlib
|
|
import os
|
|
import signal
|
|
import socket
|
|
import sys
|
|
import tempfile
|
|
from multiprocessing.reduction import recv_handle, send_handle
|
|
|
|
|
|
try:
|
|
from mercurial import encoding
|
|
|
|
environ = encoding.environ
|
|
except ImportError:
|
|
environ = getattr(os, "environ")
|
|
|
|
|
|
# backup tty fds. useful if we lose them later, like chg starting the pager
|
|
_ttyfds = []
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def _silentexception(terminate=False):
|
|
"""silent common exceptions
|
|
|
|
useful if we don't want to pollute the terminal
|
|
exit if terminal is True
|
|
"""
|
|
exitcode = 0
|
|
try:
|
|
yield
|
|
except KeyboardInterrupt:
|
|
exitcode = 1
|
|
except Exception:
|
|
exitcode = 2
|
|
if terminate:
|
|
os._exit(exitcode)
|
|
|
|
|
|
def _sockbind(sock, addr):
|
|
"""shim for util.bindunixsocket"""
|
|
sock.bind(addr)
|
|
|
|
|
|
def _isttyserverneeded():
|
|
# respect user's setting, SSH_ASKPASS is likely a gui program
|
|
if "SSH_ASKPASS" in environ:
|
|
return False
|
|
|
|
# the tty server is not needed if /dev/tty can be opened
|
|
try:
|
|
with open("/dev/tty"):
|
|
return False
|
|
except Exception:
|
|
pass
|
|
|
|
# if no backup tty fds, and neither stdin nor stderr are tty, give up
|
|
if not _ttyfds and not all(f.isatty() for f in [sys.stdin, sys.stderr]):
|
|
return False
|
|
|
|
# tty server is needed
|
|
return True
|
|
|
|
|
|
def _startttyserver():
|
|
"""start a tty fd server
|
|
|
|
the server will send tty read and write fds via unix socket
|
|
|
|
listens at sockpath: $TMPDIR/ttysrv$UID/$PID
|
|
returns (pid, sockpath)
|
|
"""
|
|
sockpath = os.path.join(tempfile.mkdtemp("ttysrv"), str(os.getpid()))
|
|
pipes = os.pipe()
|
|
pid = os.fork()
|
|
if pid:
|
|
# parent, wait for the child to start listening
|
|
os.close(pipes[1])
|
|
os.read(pipes[0], 1)
|
|
os.close(pipes[0])
|
|
return pid, sockpath
|
|
|
|
# child, starts the server
|
|
ttyrfd, ttywfd = _ttyfds or [sys.stdin.fileno(), sys.stderr.fileno()]
|
|
|
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
getattr(util, "bindunixsocket", _sockbind)(sock, sockpath)
|
|
sock.listen(1)
|
|
|
|
# unblock parent
|
|
os.close(pipes[0])
|
|
os.write(pipes[1], " ")
|
|
os.close(pipes[1])
|
|
with _silentexception(terminate=True):
|
|
while True:
|
|
conn, addr = sock.accept()
|
|
# 0: a dummy destination_pid, is ignored on posix systems
|
|
send_handle(conn, ttyrfd, 0)
|
|
send_handle(conn, ttywfd, 0)
|
|
conn.close()
|
|
|
|
|
|
def _killprocess(pid):
|
|
"""kill and reap a child process"""
|
|
os.kill(pid, signal.SIGTERM)
|
|
try:
|
|
os.waitpid(pid, 0)
|
|
except KeyboardInterrupt:
|
|
pass
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def _receivefds(sockpath):
|
|
"""get fds from the tty server listening at sockpath
|
|
|
|
returns (readfd, writefd)
|
|
"""
|
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
# use chdir to handle long sockpath
|
|
os.chdir(os.path.dirname(sockpath) or ".")
|
|
sock.connect(os.path.basename(sockpath))
|
|
rfd = recv_handle(sock)
|
|
wfd = recv_handle(sock)
|
|
return (rfd, wfd)
|
|
|
|
|
|
def _validaterepo(orig, self, sshcmd, args, remotecmd, sshenv=None):
|
|
if not _isttyserverneeded():
|
|
return orig(self, sshcmd, args, remotecmd, sshenv=sshenv)
|
|
|
|
pid = sockpath = scriptpath = None
|
|
with _silentexception(terminate=False):
|
|
pid, sockpath = _startttyserver()
|
|
scriptpath = sockpath + ".sh"
|
|
with open(scriptpath, "w") as f:
|
|
f.write(
|
|
'#!/bin/bash\nexec %s %s "$@"'
|
|
% (util.shellquote(sys.executable), util.shellquote(__file__))
|
|
)
|
|
os.chmod(scriptpath, 0o755)
|
|
env = {
|
|
# ssh will not use SSH_ASKPASS if DISPLAY is not set
|
|
"DISPLAY": environ.get("DISPLAY", ""),
|
|
"SSH_ASKPASS": util.shellquote(scriptpath),
|
|
"TTYSOCK": util.shellquote(sockpath),
|
|
}
|
|
prefix = " ".join("%s=%s" % (k, v) for k, v in env.items())
|
|
# modify sshcmd to include new environ
|
|
sshcmd = "%s %s" % (prefix, sshcmd)
|
|
|
|
try:
|
|
return orig(self, sshcmd, args, remotecmd, sshenv=sshenv)
|
|
finally:
|
|
if pid:
|
|
_killprocess(pid)
|
|
for path in [scriptpath, sockpath]:
|
|
if path and os.path.exists(path):
|
|
util.unlinkpath(path, ignoremissing=True)
|
|
|
|
|
|
def _attachio(orig, self):
|
|
orig(self)
|
|
# backup read, write tty fds to _ttyfds
|
|
if _ttyfds:
|
|
return
|
|
ui = self.ui
|
|
if ui.fin.isatty() and ui.ferr.isatty():
|
|
rfd = os.dup(ui.fin.fileno())
|
|
wfd = os.dup(ui.ferr.fileno())
|
|
_ttyfds[:] = [rfd, wfd]
|
|
|
|
|
|
def _patchchgserver():
|
|
"""patch chgserver so we can backup tty fds before they are replaced if
|
|
chg starts the pager.
|
|
"""
|
|
chgserver = None
|
|
try:
|
|
from mercurial import chgserver
|
|
except ImportError:
|
|
try:
|
|
chgserver = extensions.find("chgserver")
|
|
except KeyError:
|
|
pass
|
|
server = getattr(chgserver, "chgcmdserver", None)
|
|
if server and "attachio" in server.capabilities:
|
|
orig = server.attachio
|
|
server.capabilities["attachio"] = extensions.bind(_attachio, orig)
|
|
|
|
|
|
def uisetup(ui):
|
|
# _validaterepo runs ssh and needs to be wrapped
|
|
extensions.wrapfunction(sshpeer.sshpeer, "_validaterepo", _validaterepo)
|
|
_patchchgserver()
|
|
|
|
|
|
def _setecho(ttyr, enableecho):
|
|
import termios
|
|
|
|
attrs = termios.tcgetattr(ttyr)
|
|
if bool(enableecho) == bool(attrs[3] & termios.ECHO):
|
|
return
|
|
attrs[3] ^= termios.ECHO
|
|
termios.tcsetattr(ttyr, termios.TCSANOW, attrs)
|
|
|
|
|
|
def _shoulddisableecho(prompt):
|
|
# we don't have the "flag" information from openssh's
|
|
# read_passphrase(const char *prompt, int flags).
|
|
# guess from the prompt string.
|
|
# do not match "Passcode or option"
|
|
if "Passcode or option" in prompt:
|
|
return False
|
|
# match "password", "Password", "passphrase", "Passphrase".
|
|
return prompt.find("ass") >= 0
|
|
|
|
|
|
def _sshaskpassmain(prompt):
|
|
"""the ssh-askpass client"""
|
|
rfd, wfd = _receivefds(environ["TTYSOCK"])
|
|
r, w = os.fdopen(rfd, "r"), os.fdopen(wfd, "a")
|
|
w.write("\033[31;1m==== AUTHENTICATING FOR SSH ====\033[0m\n")
|
|
w.write(prompt)
|
|
w.flush()
|
|
shouldecho = not _shoulddisableecho(prompt)
|
|
_setecho(r, shouldecho)
|
|
try:
|
|
line = r.readline()
|
|
finally:
|
|
if not shouldecho:
|
|
w.write("\n")
|
|
_setecho(r, True)
|
|
sys.stdout.write(line)
|
|
sys.stdout.flush()
|
|
w.write("\033[31;1m==== AUTHENTICATION COMPLETE ====\033[0m\n")
|
|
|
|
|
|
if __name__ == "__main__" and all(n in environ for n in ["SSH_ASKPASS", "TTYSOCK"]):
|
|
# started by ssh as ssh-askpass
|
|
with _silentexception(terminate=True):
|
|
_sshaskpassmain(" ".join(sys.argv[1:]))
|
|
else:
|
|
# imported as a mercurial extension
|
|
from mercurial import extensions, sshpeer, util
|