##// END OF EJS Templates
win32: close the handles associated with a spawned child process...
win32: close the handles associated with a spawned child process Probably not a big deal because at this point, the call is only used when spawning a daemonized server. In that case, the parent process goes away first, so it won't prevent the child from being cleaned up.

File last commit:

r39585:089fc0db default
r40964:576474ba default
Show More
sshpeer.py
654 lines | 22.4 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 _
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
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
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 if re.match('[a-zA-Z0-9@%_+=:,./-]*$', s):
return s
return "'%s'" % s.replace("'", "'\\''")
Pierre-Yves David
sshpeer: extract the forward output logic...
r25244 def _forwardoutput(ui, pipe):
"""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:
for l in s.splitlines():
ui.status(_("remote: "), l, '\n')
Pierre-Yves David
sshpeer: extract the forward output logic...
r25244
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`)
"""
Gregory Szorc
sshpeer: make pipe polling code more explicit...
r36387 if (isinstance(self._main, util.bufferedinputpipe) and
self._main.hasbuffer):
# 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):
return self._call('write', data)
Pierre-Yves David
sshpeer: introduce a "doublepipe" class...
r25421 def read(self, size):
Augie Fackler
sshpeer: try harder to snag stderr when stdout closes unexpectedly...
r32062 r = self._call('read', size)
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):
r = self._call('unbufferedread', size)
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):
return self._call('readline')
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)
return ''
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()
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:
ui.status(_('remote: '), l)
except (IOError, ValueError):
pass
pipee.close()
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.
"""
cmd = '%s %s %s' % (
sshcmd,
args,
Yuya Nishihara
procutil: bulk-replace function calls to point to new module
r37138 procutil.shellquote('%s -R %s serve --stdio' % (
Gregory Szorc
sshpeer: establish SSH connection before class instantiation...
r35953 _serverquote(remotecmd), _serverquote(path))))
ui.debug('running %s\n' % cmd)
Yuya Nishihara
procutil: bulk-replace function calls to point to new module
r37138 cmd = procutil.quotecommand(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
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.
"""
Joerg Sonnenberger
wireproto: support for pullbundles...
r37516 protoparams = {'partial-pull'}
Joerg Sonnenberger
wireproto: provide accessors for client capabilities...
r37411 comps = [e.wireprotosupport().name for e in
util.compengines.supportedwireengines(util.CLIENTROLE)]
Joerg Sonnenberger
wireproto: turn client capabilities into sets, sorted on the wire...
r37429 protoparams.add('comp=%s' % ','.join(comps))
Joerg Sonnenberger
wireproto: provide accessors for client capabilities...
r37411 return protoparams
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 def _performhandshake(ui, stdin, stdout, stderr):
def badresponse():
Gregory Szorc
sshpeer: defer pipe buffering and stderr sidechannel binding...
r36388 # Flush any output on stderr.
_forwardoutput(ui, stderr)
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 msg = _('no suitable response from remote hg')
hint = ui.config('ui', 'ssherrorhint')
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.
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 requestlog = ui.configbool('devel', 'debug.peer-request')
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 = [
('proto', wireprotoserver.SSHV2),
]
upgradecaps = util.urlreq.urlencode(upgradecaps)
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 try:
pairsarg = '%s-%s' % ('0' * 40, '0' * 40)
handshake = [
'hello\n',
'between\n',
'pairs %d\n' % len(pairsarg),
pairsarg,
]
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 # Request upgrade to version 2 if configured.
if ui.configbool('experimental', 'sshpeer.advertise-v2'):
ui.debug('sending upgrade request: %s %s\n' % (token, upgradecaps))
handshake.insert(0, 'upgrade %s %s\n' % (token, upgradecaps))
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 if requestlog:
Boris Feld
sshpeer: reflect actual command activity one handshake...
r37831 ui.debug('devel-peer-request: hello+between\n')
ui.debug('devel-peer-request: pairs: %d bytes\n' % len(pairsarg))
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 ui.debug('sending hello command\n')
ui.debug('sending between command\n')
stdin.write(''.join(handshake))
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
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 lines = ['', 'dummy']
max_noise = 500
while lines[-1] and max_noise:
try:
l = stdout.readline()
_forwardoutput(ui, stderr)
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)
ui.debug('protocol upgraded to %s\n' % protoname)
# 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.
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956 if lines[-1] == '1\n' and l == '\n':
break
if l:
ui.debug('remote: ', l)
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.
if l.startswith('capabilities:'):
caps.update(l[:-1].split(':')[1].split())
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)
if not capsline.startswith('capabilities: '):
badresponse()
Gregory Szorc
sshpeer: log remote capabilities after protocol upgrade...
r36234 ui.debug('remote: %s\n' % capsline)
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 caps.update(capsline.split(':')[1].split())
# 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.
_forwardoutput(ui, stderr)
Gregory Szorc
sshpeer: implement peer for version 2 of wire protocol...
r35996 return protoname, caps
Gregory Szorc
sshpeer: move handshake outside of sshpeer...
r35956
Gregory Szorc
wireproto: move version 1 peer functionality to standalone module (API)...
r37632 class sshv1peer(wireprotov1peer.wirepeer):
Gregory Szorc
sshpeer: support not reading and forwarding stderr...
r36550 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 = {
'batch',
}
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):
Boris Feld
sshpeer: add support for request tracing...
r35717 if (self.ui.debugflag
and self.ui.configbool('devel', 'debug.peer-request')):
dbg = self.ui.debug
line = 'devel-peer-request: %s\n'
dbg(line % cmd)
for key, value in sorted(args.items()):
if not isinstance(value, dict):
dbg(line % ' %s: %d bytes' % (key, len(value)))
else:
for dk, dv in sorted(value.items()):
dbg(line % ' %s-%s: %d' % (key, dk, len(dv)))
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 self.ui.debug("sending %s command\n" % cmd)
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 self._pipeo.write("%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:
if k == '*':
wireargs['*'] = args
break
else:
wireargs[k] = args[k]
del args[k]
for k, v in sorted(wireargs.iteritems()):
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 self._pipeo.write("%s %d\n" % (k, len(v)))
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 if isinstance(v, dict):
for dk, dv in v.iteritems():
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 self._pipeo.write("%s %d\n" % (dk, len(dv)))
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:
return '', 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
sshpeer: use `iter(callable, sentinel)` instead of while True...
r29727 for d in iter(lambda: fp.read(4096), ''):
Gregory Szorc
sshpeer: rename _recv and _send to _readframed and _writeframed...
r36383 self._writeframed(d)
self._writeframed("", 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:
return '', r
Gregory Szorc
sshpeer: rename _recv and _send to _readframed and _writeframed...
r36383 return self._readframed(), ''
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
liscju
i18n: translate abort messages...
r29389 raise error.Abort(_('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
sshpeer: use `iter(callable, sentinel)` instead of while True...
r29727 for d in iter(lambda: fp.read(4096), ''):
Gregory Szorc
sshpeer: rename _recv and _send to _readframed and _writeframed...
r36383 self._writeframed(d)
self._writeframed("", 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()
Peter Arrenbrecht
peer: introduce real peer classes...
r17192 if l == '\n':
Gregory Szorc
sshpeer: don't read from stderr when that behavior is disabled...
r36626 if self._autoreadstderr:
self._readerr()
Pierre-Yves David
sshpeer: break "OutOfBandError" feature for ssh (BC)...
r25243 msg = _('check previous remote output')
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:
self._abort(error.ResponseError(_("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):
Gregory Szorc
sshpeer: make instance attributes and methods internal...
r33763 self._pipeo.write("%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
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."""
# 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.
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:
Gregory Szorc
sshpeer: support not reading and forwarding stderr...
r36550 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:
Gregory Szorc
sshpeer: support not reading and forwarding stderr...
r36550 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)
raise error.RepoError(_('unknown version of SSH protocol: %s') %
protoname)
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)
if u.scheme != 'ssh' or not u.host or u.path is None:
raise error.RepoError(_("couldn't parse location %s") % path)
util.checksafessh(path)
if u.passwd is not None:
raise error.RepoError(_('password in URL not supported'))
Gregory Szorc
sshpeer: move ssh command and repo creation logic out of __init__...
r35950 sshcmd = ui.config('ui', 'ssh')
remotecmd = ui.config('ui', 'remotecmd')
sshaddenv = dict(ui.configitems('sshenv'))
Yuya Nishihara
procutil: bulk-replace function calls to point to new module
r37138 sshenv = procutil.shellenviron(sshaddenv)
Gregory Szorc
sshpeer: move ssh command and repo creation logic out of __init__...
r35950 remotepath = u.path or '.'
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:
raise error.RepoError(_('cannot create remote SSH repositories '
'with extra options'))
Gregory Szorc
sshpeer: move ssh command and repo creation logic out of __init__...
r35950 cmd = '%s %s %s' % (sshcmd, args,
Yuya Nishihara
procutil: bulk-replace function calls to point to new module
r37138 procutil.shellquote('%s init %s' %
Gregory Szorc
sshpeer: move ssh command and repo creation logic out of __init__...
r35950 (_serverquote(remotecmd), _serverquote(remotepath))))
ui.debug('running %s\n' % cmd)
res = ui.system(cmd, blockedtag='sshpeer', environ=sshenv)
if res != 0:
raise error.RepoError(_('could not create remote repo'))
Gregory Szorc
sshpeer: establish SSH connection before class instantiation...
r35953 proc, stdin, stdout, stderr = _makeconnection(ui, sshcmd, args, remotecmd,
remotepath, sshenv)
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.
if 'protocaps' in peer.capabilities():
try:
Joerg Sonnenberger
wireproto: turn client capabilities into sets, sorted on the wire...
r37429 peer._call("protocaps",
caps=' '.join(sorted(_clientcapabilities())))
Joerg Sonnenberger
wireproto: provide accessors for client capabilities...
r37411 except IOError:
peer._cleanup()
raise error.RepoError(_('capability exchange failed'))
return peer