##// END OF EJS Templates
errors: name arguments to Abort constructor...
errors: name arguments to Abort constructor Differential Revision: https://phab.mercurial-scm.org/D9179

File last commit:

r45403:8e8fd938 default
r46274:d2e1dcd4 default
Show More
sshpeer.py
708 lines | 23.2 KiB | text/x-python | PythonLexer
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 # sshpeer.py - ssh repository proxy class for mercurial
#
# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
Gregory Szorc
sshpeer: use absolute_import
r25975 from __future__ import absolute_import
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 import re
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 import uuid
Gregory Szorc
sshpeer: use absolute_import
r25975
from .i18n import _
Gregory Szorc
py3: manually import getattr where it is needed...
r43359 from .pycompat import getattr
Gregory Szorc
sshpeer: use absolute_import
r25975 from . import (
error,
Pulkit Goyal
py3: use pycompat.byteskwargs() to convert kwargs' keys to bytes...
r33100 pycompat,
Gregory Szorc
sshpeer: use absolute_import
r25975 util,
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 wireprotoserver,
Gregory Szorc
wireprotoserver: move SSHV1 and SSHV2 constants to wireprototypes...
r36553 wireprototypes,
Gregory Szorc
wireproto: move version 1 peer functionality to standalone module (API)...
r37632 wireprotov1peer,
Gregory Szorc
wireproto: rename wireproto to wireprotov1server (API)...
r37803 wireprotov1server,
Gregory Szorc
sshpeer: use absolute_import
r25975 )
Yuya Nishihara
procutil: bulk-replace function calls to point to new module
r37138 from .utils import (
procutil,
Augie Fackler
cleanup: migrate from re.escape to stringutil.reescape...
r38494 stringutil,
Yuya Nishihara
procutil: bulk-replace function calls to point to new module
r37138 )
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
Augie Fackler
formatting: blacken the codebase...
r43346
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 def _serverquote(s):
Yuya Nishihara
sshpeer: move docstring to top...
r35475 """quote a string for the remote shell ... which we assume is sh"""
Matt Mackall
sshpeer: more thorough shell quoting...
r23671 if not s:
return s
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if re.match(b'[a-zA-Z0-9@%_+=:,./-]*$', s):
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 return s
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return b"'%s'" % s.replace(b"'", b"'\\''")
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
Augie Fackler
formatting: blacken the codebase...
r43346
Valentin Gatien-Baron
sshpeer: make client print (likely) server errors on stderr (BC)...
r45387 def _forwardoutput(ui, pipe, warn=False):
Pierre-Yves David
sshpeer: extract the forward output logic...
r25244 """display all data currently available on pipe as remote output.
This is non blocking."""
Matt Harbison
sshpeer: check pipe validity before forwarding output from it...
r36851 if pipe:
Yuya Nishihara
procutil: bulk-replace function calls to point to new module
r37138 s = procutil.readpipe(pipe)
Matt Harbison
sshpeer: check pipe validity before forwarding output from it...
r36851 if s:
Valentin Gatien-Baron
sshpeer: make client print (likely) server errors on stderr (BC)...
r45387 display = ui.warn if warn else ui.status
Matt Harbison
sshpeer: check pipe validity before forwarding output from it...
r36851 for l in s.splitlines():
Valentin Gatien-Baron
sshpeer: make client print (likely) server errors on stderr (BC)...
r45387 display(_(b"remote: "), l, b'\n')
Pierre-Yves David
sshpeer: extract the forward output logic...
r25244
Augie Fackler
formatting: blacken the codebase...
r43346
Pierre-Yves David
sshpeer: introduce a "doublepipe" class...
r25421 class doublepipe(object):
"""Operate a side-channel pipe in addition of a main one
The side-channel pipe contains server output to be forwarded to the user
input. The double pipe will behave as the "main" pipe, but will ensure the
content of the "side" pipe is properly processed while we wait for blocking
call on the "main" pipe.
If large amounts of data are read from "main", the forward will cease after
the first bytes start to appear. This simplifies the implementation
without affecting actual output of sshpeer too much as we rarely issue
large read for data not yet emitted by the server.
The main pipe is expected to be a 'bufferedinputpipe' from the util module
Augie Fackler
sshpeer: fix docstring typo
r31953 that handle all the os specific bits. This class lives in this module
Mads Kiilerich
spelling: trivial spell checking
r26781 because it focus on behavior specific to the ssh protocol."""
Pierre-Yves David
sshpeer: introduce a "doublepipe" class...
r25421
def __init__(self, ui, main, side):
self._ui = ui
self._main = main
self._side = side
def _wait(self):
"""wait until some data are available on main or side
return a pair of boolean (ismainready, issideready)
(This will only wait for data if the setup is supported by `util.poll`)
"""
Augie Fackler
formatting: blacken the codebase...
r43346 if (
isinstance(self._main, util.bufferedinputpipe)
and self._main.hasbuffer
):
Gregory Szorc
sshpeer: make pipe polling code more explicit...
r36387 # Main has data. Assume side is worth poking at.
return True, True
Pierre-Yves David
sshpeer: introduce a "doublepipe" class...
r25421 fds = [self._main.fileno(), self._side.fileno()]
try:
act = util.poll(fds)
except NotImplementedError:
# non supported yet case, assume all have data.
act = fds
return (self._main.fileno() in act, self._side.fileno() in act)
Pierre-Yves David
sshpeer: allow write operations through double pipe...
r25456 def write(self, data):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return self._call(b'write', data)
Pierre-Yves David
sshpeer: allow write operations through double pipe...
r25456
Pierre-Yves David
sshpeer: introduce a "doublepipe" class...
r25421 def read(self, size):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 r = self._call(b'read', size)
Augie Fackler
sshpeer: try harder to snag stderr when stdout closes unexpectedly...
r32062 if size != 0 and not r:
# We've observed a condition that indicates the
# stdout closed unexpectedly. Check stderr one
# more time and snag anything that's there before
# letting anyone know the main part of the pipe
# closed prematurely.
_forwardoutput(self._ui, self._side)
return r
Pierre-Yves David
sshpeer: introduce a "doublepipe" class...
r25421
Joerg Sonnenberger
ssh: avoid reading beyond the end of stream when using compression...
r38735 def unbufferedread(self, size):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 r = self._call(b'unbufferedread', size)
Joerg Sonnenberger
ssh: avoid reading beyond the end of stream when using compression...
r38735 if size != 0 and not r:
# We've observed a condition that indicates the
# stdout closed unexpectedly. Check stderr one
# more time and snag anything that's there before
# letting anyone know the main part of the pipe
# closed prematurely.
_forwardoutput(self._ui, self._side)
return r
Pierre-Yves David
sshpeer: introduce a "doublepipe" class...
r25421 def readline(self):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return self._call(b'readline')
Pierre-Yves David
sshpeer: introduce a "doublepipe" class...
r25421
Pierre-Yves David
sshpeer: rename 'size' to 'data' in doublepipe...
r25455 def _call(self, methname, data=None):
Pierre-Yves David
sshpeer: introduce a "doublepipe" class...
r25421 """call <methname> on "main", forward output of "side" while blocking
"""
Pierre-Yves David
sshpeer: rename 'size' to 'data' in doublepipe...
r25455 # data can be '' or 0
if (data is not None and not data) or self._main.closed:
Pierre-Yves David
sshpeer: introduce a "doublepipe" class...
r25421 _forwardoutput(self._ui, self._side)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return b''
Pierre-Yves David
sshpeer: introduce a "doublepipe" class...
r25421 while True:
mainready, sideready = self._wait()
if sideready:
_forwardoutput(self._ui, self._side)
if mainready:
meth = getattr(self._main, methname)
Pierre-Yves David
sshpeer: rename 'size' to 'data' in doublepipe...
r25455 if data is None:
Pierre-Yves David
sshpeer: introduce a "doublepipe" class...
r25421 return meth()
else:
Pierre-Yves David
sshpeer: rename 'size' to 'data' in doublepipe...
r25455 return meth(data)
Pierre-Yves David
sshpeer: introduce a "doublepipe" class...
r25421
def close(self):
return self._main.close()
Pierre-Yves David
sshpeer: allow write operations through double pipe...
r25456 def flush(self):
return self._main.flush()
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
sshpeer: extract pipe cleanup logic to own function...
r35951 def _cleanuppipes(ui, pipei, pipeo, pipee):
"""Clean up pipes used by an SSH connection."""
if pipeo:
pipeo.close()
if pipei:
pipei.close()
if pipee:
# Try to read from the err descriptor until EOF.
try:
for l in pipee:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.status(_(b'remote: '), l)
Gregory Szorc
sshpeer: extract pipe cleanup logic to own function...
r35951 except (IOError, ValueError):
pass
pipee.close()
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
sshpeer: establish SSH connection before class instantiation...
r35953 def _makeconnection(ui, sshcmd, args, remotecmd, path, sshenv=None):
"""Create an SSH connection to a server.
Returns a tuple of (process, stdin, stdout, stderr) for the
spawned process.
"""
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 cmd = b'%s %s %s' % (
Gregory Szorc
sshpeer: establish SSH connection before class instantiation...
r35953 sshcmd,
args,
Augie Fackler
formatting: blacken the codebase...
r43346 procutil.shellquote(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'%s -R %s serve --stdio'
Augie Fackler
formatting: blacken the codebase...
r43346 % (_serverquote(remotecmd), _serverquote(path))
),
)
Gregory Szorc
sshpeer: establish SSH connection before class instantiation...
r35953
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.debug(b'running %s\n' % cmd)
Gregory Szorc
sshpeer: establish SSH connection before class instantiation...
r35953
# no buffer allow the use of 'select'
# feel free to remove buffering and select usage when we ultimately
# move to threading.
Yuya Nishihara
procutil: bulk-replace function calls to point to new module
r37138 stdin, stdout, stderr, proc = procutil.popen4(cmd, bufsize=0, env=sshenv)
Gregory Szorc
sshpeer: establish SSH connection before class instantiation...
r35953
return proc, stdin, stdout, stderr
Augie Fackler
formatting: blacken the codebase...
r43346
Joerg Sonnenberger
wireproto: provide accessors for client capabilities...
r37411 def _clientcapabilities():
"""Return list of capabilities of this client.
Returns a list of capabilities that are supported by this client.
"""
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 protoparams = {b'partial-pull'}
Augie Fackler
formatting: blacken the codebase...
r43346 comps = [
e.wireprotosupport().name
for e in util.compengines.supportedwireengines(util.CLIENTROLE)
]
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 protoparams.add(b'comp=%s' % b','.join(comps))
Joerg Sonnenberger
wireproto: provide accessors for client capabilities...
r37411 return protoparams
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 def _performhandshake(ui, stdin, stdout, stderr):
def badresponse():
Valentin Gatien-Baron
sshpeer: make client print (likely) server errors on stderr (BC)...
r45387 # Flush any output on stderr. In general, the stderr contains errors
# from the remote (ssh errors, some hg errors), and status indications
# (like "adding changes"), with no current way to tell them apart.
# Here we failed so early that it's almost certainly only errors, so
# use warn=True so -q doesn't hide them.
_forwardoutput(ui, stderr, warn=True)
Gregory Szorc
sshpeer: defer pipe buffering and stderr sidechannel binding...
r36388
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 msg = _(b'no suitable response from remote hg')
hint = ui.config(b'ui', b'ssherrorhint')
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 raise error.RepoError(msg, hint=hint)
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 # The handshake consists of sending wire protocol commands in reverse
# order of protocol implementation and then sniffing for a response
# to one of them.
#
# Those commands (from oldest to newest) are:
Gregory Szorc
sshpeer: document the handshake mechanism...
r35957 #
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 # ``between``
# Asks for the set of revisions between a pair of revisions. Command
# present in all Mercurial server implementations.
Gregory Szorc
sshpeer: document the handshake mechanism...
r35957 #
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 # ``hello``
# Instructs the server to advertise its capabilities. Introduced in
# Mercurial 0.9.1.
#
# ``upgrade``
# Requests upgrade from default transport protocol version 1 to
# a newer version. Introduced in Mercurial 4.6 as an experimental
# feature.
Gregory Szorc
sshpeer: document the handshake mechanism...
r35957 #
# The ``between`` command is issued with a request for the null
# range. If the remote is a Mercurial server, this request will
# generate a specific response: ``1\n\n``. This represents the
# wire protocol encoded value for ``\n``. We look for ``1\n\n``
# in the output stream and know this is the response to ``between``
# and we're at the end of our handshake reply.
#
# The response to the ``hello`` command will be a line with the
# length of the value returned by that command followed by that
# value. If the server doesn't support ``hello`` (which should be
# rare), that line will be ``0\n``. Otherwise, the value will contain
# RFC 822 like lines. Of these, the ``capabilities:`` line contains
# the capabilities of the server.
#
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 # The ``upgrade`` command isn't really a command in the traditional
# sense of version 1 of the transport because it isn't using the
# proper mechanism for formatting insteads: instead, it just encodes
# arguments on the line, delimited by spaces.
#
# The ``upgrade`` line looks like ``upgrade <token> <capabilities>``.
# If the server doesn't support protocol upgrades, it will reply to
# this line with ``0\n``. Otherwise, it emits an
# ``upgraded <token> <protocol>`` line to both stdout and stderr.
# Content immediately following this line describes additional
# protocol and server state.
#
Gregory Szorc
sshpeer: document the handshake mechanism...
r35957 # In addition to the responses to our command requests, the server
# may emit "banner" output on stdout. SSH servers are allowed to
# print messages to stdout on login. Issuing commands on connection
# allows us to flush this banner output from the server by scanning
# for output to our well-known ``between`` command. Of course, if
# the banner contains ``1\n\n``, this will throw off our detection.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 requestlog = ui.configbool(b'devel', b'debug.peer-request')
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 # Generate a random token to help identify responses to version 2
# upgrade request.
Gregory Szorc
py3: more robustly cast UUID to bytes...
r36060 token = pycompat.sysbytes(str(uuid.uuid4()))
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 upgradecaps = [
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 (b'proto', wireprotoserver.SSHV2),
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 ]
upgradecaps = util.urlreq.urlencode(upgradecaps)
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 try:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 pairsarg = b'%s-%s' % (b'0' * 40, b'0' * 40)
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 handshake = [
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'hello\n',
b'between\n',
b'pairs %d\n' % len(pairsarg),
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 pairsarg,
]
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 # Request upgrade to version 2 if configured.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if ui.configbool(b'experimental', b'sshpeer.advertise-v2'):
ui.debug(b'sending upgrade request: %s %s\n' % (token, upgradecaps))
handshake.insert(0, b'upgrade %s %s\n' % (token, upgradecaps))
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 if requestlog:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.debug(b'devel-peer-request: hello+between\n')
ui.debug(b'devel-peer-request: pairs: %d bytes\n' % len(pairsarg))
ui.debug(b'sending hello command\n')
ui.debug(b'sending between command\n')
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 stdin.write(b''.join(handshake))
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 stdin.flush()
except IOError:
badresponse()
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 # Assume version 1 of wire protocol by default.
Gregory Szorc
wireprotoserver: move SSHV1 and SSHV2 constants to wireprototypes...
r36553 protoname = wireprototypes.SSHV1
Augie Fackler
cleanup: migrate from re.escape to stringutil.reescape...
r38494 reupgraded = re.compile(b'^upgraded %s (.*)$' % stringutil.reescape(token))
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 lines = [b'', b'dummy']
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 max_noise = 500
while lines[-1] and max_noise:
try:
l = stdout.readline()
Valentin Gatien-Baron
sshpeer: make client print (likely) server errors on stderr (BC)...
r45387 _forwardoutput(ui, stderr, warn=True)
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994
# Look for reply to protocol upgrade request. It has a token
# in it, so there should be no false positives.
m = reupgraded.match(l)
if m:
protoname = m.group(1)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.debug(b'protocol upgraded to %s\n' % protoname)
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 # If an upgrade was handled, the ``hello`` and ``between``
# requests are ignored. The next output belongs to the
# protocol, so stop scanning lines.
break
# Otherwise it could be a banner, ``0\n`` response if server
# doesn't support upgrade.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if lines[-1] == b'1\n' and l == b'\n':
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 break
if l:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.debug(b'remote: ', l)
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 lines.append(l)
max_noise -= 1
except IOError:
badresponse()
else:
badresponse()
caps = set()
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 # For version 1, we should see a ``capabilities`` line in response to the
# ``hello`` command.
Gregory Szorc
wireprotoserver: move SSHV1 and SSHV2 constants to wireprototypes...
r36553 if protoname == wireprototypes.SSHV1:
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 for l in reversed(lines):
# Look for response to ``hello`` command. Scan from the back so
# we don't misinterpret banner output as the command reply.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if l.startswith(b'capabilities:'):
caps.update(l[:-1].split(b':')[1].split())
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 break
elif protoname == wireprotoserver.SSHV2:
# We see a line with number of bytes to follow and then a value
# looking like ``capabilities: *``.
line = stdout.readline()
try:
valuelen = int(line)
except ValueError:
badresponse()
capsline = stdout.read(valuelen)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if not capsline.startswith(b'capabilities: '):
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 badresponse()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.debug(b'remote: %s\n' % capsline)
Gregory Szorc
sshpeer: log remote capabilities after protocol upgrade...
r36234
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 caps.update(capsline.split(b':')[1].split())
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 # Trailing newline.
stdout.read(1)
# Error if we couldn't find capabilities, this means:
Gregory Szorc
sshpeer: remove support for connecting to <0.9.1 servers (BC)...
r35958 #
# 1. Remote isn't a Mercurial server
# 2. Remote is a <0.9.1 Mercurial server
# 3. Remote is a future Mercurial server that dropped ``hello``
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 # and other attempted handshake mechanisms.
Gregory Szorc
sshpeer: remove support for connecting to <0.9.1 servers (BC)...
r35958 if not caps:
badresponse()
Gregory Szorc
sshpeer: defer pipe buffering and stderr sidechannel binding...
r36388 # Flush any output on stderr before proceeding.
Valentin Gatien-Baron
sshpeer: make client print (likely) server errors on stderr (BC)...
r45387 _forwardoutput(ui, stderr, warn=True)
Gregory Szorc
sshpeer: defer pipe buffering and stderr sidechannel binding...
r36388
Gregory Szorc
sshpeer: implement peer for version 2 of wire protocol...
r35996 return protoname, caps
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
wireproto: move version 1 peer functionality to standalone module (API)...
r37632 class sshv1peer(wireprotov1peer.wirepeer):
Augie Fackler
formatting: blacken the codebase...
r43346 def __init__(
self, ui, url, proc, stdin, stdout, stderr, caps, autoreadstderr=True
):
Gregory Szorc
sshpeer: clean up API for sshpeer.__init__ (API)...
r35954 """Create a peer from an existing SSH connection.
``proc`` is a handle on the underlying SSH process.
``stdin``, ``stdout``, and ``stderr`` are handles on the stdio
pipes for that process.
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 ``caps`` is a set of capabilities supported by the remote.
Gregory Szorc
sshpeer: support not reading and forwarding stderr...
r36550 ``autoreadstderr`` denotes whether to automatically read from
stderr and to forward its output.
Gregory Szorc
sshpeer: clean up API for sshpeer.__init__ (API)...
r35954 """
self._url = url
Gregory Szorc
peer: make ui an attribute...
r37337 self.ui = ui
Gregory Szorc
sshpeer: establish SSH connection before class instantiation...
r35953 # self._subprocess is unused. Keeping a handle on the process
# holds a reference and prevents it from being garbage collected.
Gregory Szorc
sshpeer: clean up API for sshpeer.__init__ (API)...
r35954 self._subprocess = proc
Gregory Szorc
sshpeer: defer pipe buffering and stderr sidechannel binding...
r36388
# And we hook up our "doublepipe" wrapper to allow querying
# stderr any time we perform I/O.
Gregory Szorc
sshpeer: support not reading and forwarding stderr...
r36550 if autoreadstderr:
stdout = doublepipe(ui, util.bufferedinputpipe(stdout), stderr)
stdin = doublepipe(ui, stdin, stderr)
Gregory Szorc
sshpeer: defer pipe buffering and stderr sidechannel binding...
r36388
Gregory Szorc
sshpeer: clean up API for sshpeer.__init__ (API)...
r35954 self._pipeo = stdin
self._pipei = stdout
self._pipee = stderr
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 self._caps = caps
Gregory Szorc
sshpeer: don't read from stderr when that behavior is disabled...
r36626 self._autoreadstderr = autoreadstderr
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
Gregory Szorc
sshpeer: return framed file object when needed...
r36385 # Commands that have a "framed" response where the first line of the
# response contains the length of that response.
_FRAMED_COMMANDS = {
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'batch',
Gregory Szorc
sshpeer: return framed file object when needed...
r36385 }
Gregory Szorc
repository: port peer interfaces to zope.interface...
r37336 # Begin of ipeerconnection interface.
Gregory Szorc
sshpeer: use peer interface...
r33803
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 def url(self):
return self._url
Gregory Szorc
sshpeer: use peer interface...
r33803 def local(self):
return None
def peer(self):
return self
def canpush(self):
return True
def close(self):
pass
Gregory Szorc
repository: port peer interfaces to zope.interface...
r37336 # End of ipeerconnection interface.
Gregory Szorc
sshpeer: use peer interface...
r33803
Gregory Szorc
repository: port peer interfaces to zope.interface...
r37336 # Begin of ipeercommands interface.
Gregory Szorc
sshpeer: use peer interface...
r33803
def capabilities(self):
return self._caps
Gregory Szorc
repository: port peer interfaces to zope.interface...
r37336 # End of ipeercommands interface.
Gregory Szorc
sshpeer: use peer interface...
r33803
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 def _readerr(self):
_forwardoutput(self.ui, self._pipee)
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
def _abort(self, exception):
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 self._cleanup()
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 raise exception
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 def _cleanup(self):
Gregory Szorc
sshpeer: extract pipe cleanup logic to own function...
r35951 _cleanuppipes(self.ui, self._pipei, self._pipeo, self._pipee)
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 __del__ = _cleanup
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
Gregory Szorc
sshpeer: return framed file object when needed...
r36385 def _sendrequest(self, cmd, args, framed=False):
Augie Fackler
formatting: blacken the codebase...
r43346 if self.ui.debugflag and self.ui.configbool(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'devel', b'debug.peer-request'
Augie Fackler
formatting: blacken the codebase...
r43346 ):
Boris Feld
sshpeer: add support for request tracing...
r35717 dbg = self.ui.debug
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 line = b'devel-peer-request: %s\n'
Boris Feld
sshpeer: add support for request tracing...
r35717 dbg(line % cmd)
for key, value in sorted(args.items()):
if not isinstance(value, dict):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 dbg(line % b' %s: %d bytes' % (key, len(value)))
Boris Feld
sshpeer: add support for request tracing...
r35717 else:
for dk, dv in sorted(value.items()):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 dbg(line % b' %s-%s: %d' % (key, dk, len(dv)))
self.ui.debug(b"sending %s command\n" % cmd)
self._pipeo.write(b"%s\n" % cmd)
Gregory Szorc
wireproto: rename wireproto to wireprotov1server (API)...
r37803 _func, names = wireprotov1server.commands[cmd]
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 keys = names.split()
wireargs = {}
for k in keys:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if k == b'*':
wireargs[b'*'] = args
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 break
else:
wireargs[k] = args[k]
del args[k]
Gregory Szorc
py3: finish porting iteritems() to pycompat and remove source transformer...
r43376 for k, v in sorted(pycompat.iteritems(wireargs)):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 self._pipeo.write(b"%s %d\n" % (k, len(v)))
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 if isinstance(v, dict):
Gregory Szorc
py3: finish porting iteritems() to pycompat and remove source transformer...
r43376 for dk, dv in pycompat.iteritems(v):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 self._pipeo.write(b"%s %d\n" % (dk, len(dv)))
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 self._pipeo.write(dv)
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 else:
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 self._pipeo.write(v)
self._pipeo.flush()
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
Gregory Szorc
sshpeer: return framed file object when needed...
r36385 # We know exactly how many bytes are in the response. So return a proxy
# around the raw output stream that allows reading exactly this many
# bytes. Callers then can read() without fear of overrunning the
# response.
if framed:
amount = self._getamount()
return util.cappedreader(self._pipei, amount)
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 return self._pipei
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
Gregory Szorc
sshpeer: move logic for sending a request into a new function...
r36384 def _callstream(self, cmd, **args):
args = pycompat.byteskwargs(args)
Gregory Szorc
sshpeer: return framed file object when needed...
r36385 return self._sendrequest(cmd, args, framed=cmd in self._FRAMED_COMMANDS)
Gregory Szorc
sshpeer: move logic for sending a request into a new function...
r36384
Pierre-Yves David
wireproto: drop the _decompress method in favor a new call type...
r20905 def _callcompressable(self, cmd, **args):
Gregory Szorc
sshpeer: move logic for sending a request into a new function...
r36384 args = pycompat.byteskwargs(args)
Gregory Szorc
sshpeer: return framed file object when needed...
r36385 return self._sendrequest(cmd, args, framed=cmd in self._FRAMED_COMMANDS)
Pierre-Yves David
wireproto: drop the _decompress method in favor a new call type...
r20905
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 def _call(self, cmd, **args):
Gregory Szorc
sshpeer: move logic for sending a request into a new function...
r36384 args = pycompat.byteskwargs(args)
Gregory Szorc
sshpeer: return framed file object when needed...
r36385 return self._sendrequest(cmd, args, framed=True).read()
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
def _callpush(self, cmd, fp, **args):
Gregory Szorc
wireproto: document the wonky push protocol for SSH...
r36390 # The server responds with an empty frame if the client should
# continue submitting the payload.
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 r = self._call(cmd, **args)
if r:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return b'', r
Gregory Szorc
wireproto: document the wonky push protocol for SSH...
r36390
# The payload consists of frames with content followed by an empty
# frame.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 for d in iter(lambda: fp.read(4096), b''):
Gregory Szorc
sshpeer: rename _recv and _send to _readframed and _writeframed...
r36383 self._writeframed(d)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 self._writeframed(b"", flush=True)
Gregory Szorc
wireproto: document the wonky push protocol for SSH...
r36390
# In case of success, there is an empty frame and a frame containing
# the integer result (as a string).
# In case of error, there is a non-empty frame containing the error.
Gregory Szorc
sshpeer: rename _recv and _send to _readframed and _writeframed...
r36383 r = self._readframed()
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 if r:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return b'', r
return self._readframed(), b''
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
Pierre-Yves David
sshpeer: add implementation of _calltwowaystream...
r21073 def _calltwowaystream(self, cmd, fp, **args):
Gregory Szorc
wireproto: document the wonky push protocol for SSH...
r36390 # The server responds with an empty frame if the client should
# continue submitting the payload.
Pierre-Yves David
sshpeer: add implementation of _calltwowaystream...
r21073 r = self._call(cmd, **args)
if r:
# XXX needs to be made better
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.Abort(_(b'unexpected remote reply: %s') % r)
Gregory Szorc
wireproto: document the wonky push protocol for SSH...
r36390
# The payload consists of frames with content followed by an empty
# frame.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 for d in iter(lambda: fp.read(4096), b''):
Gregory Szorc
sshpeer: rename _recv and _send to _readframed and _writeframed...
r36383 self._writeframed(d)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 self._writeframed(b"", flush=True)
Gregory Szorc
wireproto: document the wonky push protocol for SSH...
r36390
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 return self._pipei
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
Augie Fackler
wireproto: make iterbatcher behave streamily over http(s)...
r28438 def _getamount(self):
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 l = self._pipei.readline()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if l == b'\n':
Gregory Szorc
sshpeer: don't read from stderr when that behavior is disabled...
r36626 if self._autoreadstderr:
self._readerr()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 msg = _(b'check previous remote output')
Pierre-Yves David
sshpeer: break "OutOfBandError" feature for ssh (BC)...
r25243 self._abort(error.OutOfBandError(hint=msg))
Gregory Szorc
sshpeer: don't read from stderr when that behavior is disabled...
r36626 if self._autoreadstderr:
self._readerr()
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 try:
Augie Fackler
wireproto: make iterbatcher behave streamily over http(s)...
r28438 return int(l)
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 except ValueError:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 self._abort(error.ResponseError(_(b"unexpected response:"), l))
Augie Fackler
wireproto: make iterbatcher behave streamily over http(s)...
r28438
Gregory Szorc
sshpeer: rename _recv and _send to _readframed and _writeframed...
r36383 def _readframed(self):
Gregory Szorc
sshpeer: don't read(0)...
r36646 size = self._getamount()
if not size:
return b''
return self._pipei.read(size)
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
Gregory Szorc
sshpeer: rename _recv and _send to _readframed and _writeframed...
r36383 def _writeframed(self, data, flush=False):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 self._pipeo.write(b"%d\n" % len(data))
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 if data:
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 self._pipeo.write(data)
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 if flush:
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 self._pipeo.flush()
Gregory Szorc
sshpeer: don't read from stderr when that behavior is disabled...
r36626 if self._autoreadstderr:
self._readerr()
Peter Arrenbrecht
peer: introduce real peer classes...
r17192
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
sshpeer: implement peer for version 2 of wire protocol...
r35996 class sshv2peer(sshv1peer):
"""A peer that speakers version 2 of the transport protocol."""
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
sshpeer: implement peer for version 2 of wire protocol...
r35996 # Currently version 2 is identical to version 1 post handshake.
# And handshake is performed before the peer is instantiated. So
# we need no custom code.
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
sshpeer: support not reading and forwarding stderr...
r36550 def makepeer(ui, path, proc, stdin, stdout, stderr, autoreadstderr=True):
Gregory Szorc
sshpeer: factor out code for creating peers from pipes...
r36505 """Make a peer instance from existing pipes.
``path`` and ``proc`` are stored on the eventual peer instance and may
not be used for anything meaningful.
``stdin``, ``stdout``, and ``stderr`` are the pipes connected to the
SSH server's stdio handles.
This function is factored out to allow creating peers that don't
actually spawn a new process. It is useful for starting SSH protocol
servers and clients via non-standard means, which can be useful for
testing.
"""
try:
protoname, caps = _performhandshake(ui, stdin, stdout, stderr)
except Exception:
_cleanuppipes(ui, stdout, stdin, stderr)
raise
Gregory Szorc
wireprotoserver: move SSHV1 and SSHV2 constants to wireprototypes...
r36553 if protoname == wireprototypes.SSHV1:
Augie Fackler
formatting: blacken the codebase...
r43346 return sshv1peer(
ui,
path,
proc,
stdin,
stdout,
stderr,
caps,
autoreadstderr=autoreadstderr,
)
Gregory Szorc
wireprotoserver: move SSHV1 and SSHV2 constants to wireprototypes...
r36553 elif protoname == wireprototypes.SSHV2:
Augie Fackler
formatting: blacken the codebase...
r43346 return sshv2peer(
ui,
path,
proc,
stdin,
stdout,
stderr,
caps,
autoreadstderr=autoreadstderr,
)
Gregory Szorc
sshpeer: factor out code for creating peers from pipes...
r36505 else:
_cleanuppipes(ui, stdout, stdin, stderr)
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.RepoError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _(b'unknown version of SSH protocol: %s') % protoname
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
sshpeer: factor out code for creating peers from pipes...
r36505
Gregory Szorc
hg: allow extra arguments to be passed to repo creation (API)...
r39585 def instance(ui, path, create, intents=None, createopts=None):
Gregory Szorc
sshpeer: move URL validation out of sshpeer.__init__...
r35949 """Create an SSH peer.
Gregory Szorc
wireproto: move version 1 peer functionality to standalone module (API)...
r37632 The returned object conforms to the ``wireprotov1peer.wirepeer`` interface.
Gregory Szorc
sshpeer: move URL validation out of sshpeer.__init__...
r35949 """
u = util.url(path, parsequery=False, parsefragment=False)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if u.scheme != b'ssh' or not u.host or u.path is None:
raise error.RepoError(_(b"couldn't parse location %s") % path)
Gregory Szorc
sshpeer: move URL validation out of sshpeer.__init__...
r35949
util.checksafessh(path)
if u.passwd is not None:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.RepoError(_(b'password in URL not supported'))
Gregory Szorc
sshpeer: move URL validation out of sshpeer.__init__...
r35949
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 sshcmd = ui.config(b'ui', b'ssh')
remotecmd = ui.config(b'ui', b'remotecmd')
sshaddenv = dict(ui.configitems(b'sshenv'))
Yuya Nishihara
procutil: bulk-replace function calls to point to new module
r37138 sshenv = procutil.shellenviron(sshaddenv)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 remotepath = u.path or b'.'
Gregory Szorc
sshpeer: move ssh command and repo creation logic out of __init__...
r35950
Yuya Nishihara
procutil: bulk-replace function calls to point to new module
r37138 args = procutil.sshargs(sshcmd, u.host, u.user, u.port)
Gregory Szorc
sshpeer: move ssh command and repo creation logic out of __init__...
r35950
if create:
Gregory Szorc
hg: allow extra arguments to be passed to repo creation (API)...
r39585 # We /could/ do this, but only if the remote init command knows how to
# handle them. We don't yet make any assumptions about that. And without
# querying the remote, there's no way of knowing if the remote even
# supports said requested feature.
if createopts:
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.RepoError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _(
b'cannot create remote SSH repositories '
b'with extra options'
)
Augie Fackler
formatting: blacken the codebase...
r43346 )
Gregory Szorc
hg: allow extra arguments to be passed to repo creation (API)...
r39585
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 cmd = b'%s %s %s' % (
Augie Fackler
formatting: blacken the codebase...
r43346 sshcmd,
args,
procutil.shellquote(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'%s init %s'
Augie Fackler
formatting: blacken the codebase...
r43346 % (_serverquote(remotecmd), _serverquote(remotepath))
),
)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ui.debug(b'running %s\n' % cmd)
res = ui.system(cmd, blockedtag=b'sshpeer', environ=sshenv)
Gregory Szorc
sshpeer: move ssh command and repo creation logic out of __init__...
r35950 if res != 0:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.RepoError(_(b'could not create remote repo'))
Gregory Szorc
sshpeer: move ssh command and repo creation logic out of __init__...
r35950
Augie Fackler
formatting: blacken the codebase...
r43346 proc, stdin, stdout, stderr = _makeconnection(
ui, sshcmd, args, remotecmd, remotepath, sshenv
)
Gregory Szorc
sshpeer: establish SSH connection before class instantiation...
r35953
Joerg Sonnenberger
wireproto: provide accessors for client capabilities...
r37411 peer = makepeer(ui, path, proc, stdin, stdout, stderr)
# Finally, if supported by the server, notify it about our own
# capabilities.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if b'protocaps' in peer.capabilities():
Joerg Sonnenberger
wireproto: provide accessors for client capabilities...
r37411 try:
Augie Fackler
formatting: blacken the codebase...
r43346 peer._call(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b"protocaps", caps=b' '.join(sorted(_clientcapabilities()))
Augie Fackler
formatting: blacken the codebase...
r43346 )
Joerg Sonnenberger
wireproto: provide accessors for client capabilities...
r37411 except IOError:
peer._cleanup()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.RepoError(_(b'capability exchange failed'))
Joerg Sonnenberger
wireproto: provide accessors for client capabilities...
r37411
return peer