/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
1
# Copyright (C) 2006 Canonical Ltd
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
"""Smart-server protocol, client and server.
18
19
Requests are sent as a command and list of arguments, followed by optional
20
bulk body data.  Responses are similarly a response and list of arguments,
21
followed by bulk body data. ::
22
23
  SEP := '\001'
24
    Fields are separated by Ctrl-A.
25
  BULK_DATA := CHUNK+ TRAILER
26
    Chunks can be repeated as many times as necessary.
27
  CHUNK := CHUNK_LEN CHUNK_BODY
28
  CHUNK_LEN := DIGIT+ NEWLINE
29
    Gives the number of bytes in the following chunk.
30
  CHUNK_BODY := BYTE[chunk_len]
31
  TRAILER := SUCCESS_TRAILER | ERROR_TRAILER
32
  SUCCESS_TRAILER := 'done' NEWLINE
33
  ERROR_TRAILER := 
34
35
Paths are passed across the network.  The client needs to see a namespace that
36
includes any repository that might need to be referenced, and the client needs
37
to know about a root directory beyond which it cannot ascend.
38
39
Servers run over ssh will typically want to be able to access any path the user 
40
can access.  Public servers on the other hand (which might be over http, ssh
41
or tcp) will typically want to restrict access to only a particular directory 
42
and its children, so will want to do a software virtual root at that level.
43
In other words they'll want to rewrite incoming paths to be under that level
44
(and prevent escaping using ../ tricks.)
45
46
URLs that include ~ should probably be passed across to the server verbatim
47
and the server can expand them.  This will proably not be meaningful when 
48
limited to a directory?
49
"""
50
51
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
52
# TODO: _translate_error should be on the client, not the transport because
53
#     error coding is wire protocol specific.
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
54
55
# TODO: A plain integer from query_version is too simple; should give some
56
# capabilities too?
57
58
# TODO: Server should probably catch exceptions within itself and send them
59
# back across the network.  (But shouldn't catch KeyboardInterrupt etc)
60
# Also needs to somehow report protocol errors like bad requests.  Need to
61
# consider how we'll handle error reporting, e.g. if we get halfway through a
62
# bulk transfer and then something goes wrong.
63
64
# TODO: Standard marker at start of request/response lines?
65
1910.19.13 by Andrew Bennetts
Address various review comments.
66
# TODO: Make each request and response self-validatable, e.g. with checksums.
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
67
#
68
# TODO: get/put objects could be changed to gradually read back the data as it
69
# comes across the network
70
#
71
# TODO: What should the server do if it hits an error and has to terminate?
72
#
73
# TODO: is it useful to allow multiple chunks in the bulk data?
74
#
75
# TODO: If we get an exception during transmission of bulk data we can't just
76
# emit the exception because it won't be seen.
1910.19.13 by Andrew Bennetts
Address various review comments.
77
#   John proposes:  I think it would be worthwhile to have a header on each
78
#   chunk, that indicates it is another chunk. Then you can send an 'error'
79
#   chunk as long as you finish the previous chunk.
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
80
#
81
# TODO: Clone method on Transport; should work up towards parent directory;
82
# unclear how this should be stored or communicated to the server... maybe
83
# just pass it on all relevant requests?
84
#
85
# TODO: Better name than clone() for changing between directories.  How about
86
# open_dir or change_dir or chdir?
87
#
88
# TODO: Is it really good to have the notion of current directory within the
89
# connection?  Perhaps all Transports should factor out a common connection
90
# from the thing that has the directory context?
91
#
92
# TODO: Pull more things common to sftp and ssh to a higher level.
93
#
94
# TODO: The server that manages a connection should be quite small and retain
95
# minimum state because each of the requests are supposed to be stateless.
96
# Then we can write another implementation that maps to http.
97
#
98
# TODO: What to do when a client connection is garbage collected?  Maybe just
99
# abruptly drop the connection?
100
#
101
# TODO: Server in some cases will need to restrict access to files outside of
102
# a particular root directory.  LocalTransport doesn't do anything to stop you
103
# ascending above the base directory, so we need to prevent paths
104
# containing '..' in either the server or transport layers.  (Also need to
105
# consider what happens if someone creates a symlink pointing outside the 
106
# directory tree...)
107
#
108
# TODO: Server should rebase absolute paths coming across the network to put
109
# them under the virtual root, if one is in use.  LocalTransport currently
110
# doesn't do that; if you give it an absolute path it just uses it.
111
# 
112
# XXX: Arguments can't contain newlines or ascii; possibly we should e.g.
113
# urlescape them instead.  Indeed possibly this should just literally be
114
# http-over-ssh.
115
#
116
# FIXME: This transport, with several others, has imperfect handling of paths
117
# within urls.  It'd probably be better for ".." from a root to raise an error
118
# rather than return the same directory as we do at present.
119
#
120
# TODO: Rather than working at the Transport layer we want a Branch,
121
# Repository or BzrDir objects that talk to a server.
122
#
123
# TODO: Probably want some way for server commands to gradually produce body
124
# data rather than passing it as a string; they could perhaps pass an
125
# iterator-like callback that will gradually yield data; it probably needs a
126
# close() method that will always be closed to do any necessary cleanup.
127
#
128
# TODO: Split the actual smart server from the ssh encoding of it.
129
#
130
# TODO: Perhaps support file-level readwrite operations over the transport
131
# too.
132
#
133
# TODO: SmartBzrDir class, proxying all Branch etc methods across to another
134
# branch doing file-level operations.
1910.19.16 by John Arbash Meinel
Translate UnicodeErrors across the smart server
135
#
136
# TODO: jam 20060915 _decode_tuple is acting directly on input over
137
#       the socket, and it assumes everything is UTF8 sections separated
138
#       by \001. Which means a request like '\002' Will abort the connection
139
#       because of a UnicodeDecodeError. It does look like invalid data will
140
#       kill the SmartStreamServer, but only with an abort + exception, and 
141
#       the overall server shouldn't die.
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
142
143
from cStringIO import StringIO
144
import errno
145
import os
146
import socket
147
import sys
148
import tempfile
149
import threading
150
import urllib
151
import urlparse
152
1910.19.13 by Andrew Bennetts
Address various review comments.
153
from bzrlib import (
154
    bzrdir,
155
    errors,
156
    revision,
157
    transport,
158
    trace,
159
    urlutils,
160
    )
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
161
from bzrlib.bundle.serializer import write_bundle
162
from bzrlib.trace import mutter
1910.19.14 by Robert Collins
Fix up all tests to pass, remove a couple more deprecated function calls, and break the dependency on sftp for the smart transport.
163
from bzrlib.transport import local
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
164
165
# must do this otherwise urllib can't parse the urls properly :(
1910.19.3 by Andrew Bennetts
Add SSH support.
166
for scheme in ['ssh', 'bzr', 'bzr+loopback', 'bzr+ssh']:
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
167
    transport.register_urlparse_netloc_protocol(scheme)
168
del scheme
169
170
171
def _recv_tuple(from_file):
172
    req_line = from_file.readline()
173
    return _decode_tuple(req_line)
174
175
176
def _decode_tuple(req_line):
177
    if req_line == None or req_line == '':
178
        return None
179
    if req_line[-1] != '\n':
1910.19.13 by Andrew Bennetts
Address various review comments.
180
        raise errors.SmartProtocolError("request %r not terminated" % req_line)
1910.19.11 by Andrew Bennetts
General code cleanup based on review comments and other observations.
181
    return tuple((a.decode('utf-8') for a in req_line[:-1].split('\x01')))
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
182
183
1910.19.22 by Robert Collins
Rearrange the readv patch to put the serialise offsets method into the correct class, and document the structure of the classes somewhat better to hint to people writing patches where code should go. Also alter the test so that the client and server components are tested in one place preventing possible encoding skew from occuring.
184
def _encode_tuple(args):
185
    """Encode the tuple args to a bytestream."""
186
    return '\x01'.join((a.encode('utf-8') for a in args)) + '\n'
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
187
188
189
class SmartProtocolBase(object):
190
    """Methods common to client and server"""
191
192
    def _send_bulk_data(self, body):
193
        """Send chunked body data"""
194
        assert isinstance(body, str)
1910.19.22 by Robert Collins
Rearrange the readv patch to put the serialise offsets method into the correct class, and document the structure of the classes somewhat better to hint to people writing patches where code should go. Also alter the test so that the client and server components are tested in one place preventing possible encoding skew from occuring.
195
        bytes = ''.join(('%d\n' % len(body), body, 'done\n'))
196
        self._write_and_flush(bytes)
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
197
198
    # TODO: this only actually accomodates a single block; possibly should support
199
    # multiple chunks?
200
    def _recv_bulk(self):
201
        chunk_len = self._in.readline()
202
        try:
203
            chunk_len = int(chunk_len)
204
        except ValueError:
1910.19.13 by Andrew Bennetts
Address various review comments.
205
            raise errors.SmartProtocolError("bad chunk length line %r" % chunk_len)
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
206
        bulk = self._in.read(chunk_len)
207
        if len(bulk) != chunk_len:
1910.19.13 by Andrew Bennetts
Address various review comments.
208
            raise errors.SmartProtocolError("short read fetching bulk data chunk")
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
209
        self._recv_trailer()
210
        return bulk
211
212
    def _recv_tuple(self):
213
        return _recv_tuple(self._in)
214
215
    def _recv_trailer(self):
216
        resp = self._recv_tuple()
217
        if resp == ('done', ):
218
            return
219
        else:
220
            self._translate_error(resp)
221
1910.19.22 by Robert Collins
Rearrange the readv patch to put the serialise offsets method into the correct class, and document the structure of the classes somewhat better to hint to people writing patches where code should go. Also alter the test so that the client and server components are tested in one place preventing possible encoding skew from occuring.
222
    def _serialise_offsets(self, offsets):
223
        """Serialise a readv offset list."""
224
        txt = []
225
        for start, length in offsets:
226
            txt.append('%d,%d' % (start, length))
227
        return '\n'.join(txt)
228
229
    def _write_and_flush(self, bytes):
230
        """Write bytes to self._out and flush it."""
231
        # XXX: this will be inefficient.  Just ask Robert.
232
        self._out.write(bytes)
233
        self._out.flush()
234
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
235
236
class SmartStreamServer(SmartProtocolBase):
237
    """Handles smart commands coming over a stream.
238
239
    The stream may be a pipe connected to sshd, or a tcp socket, or an
240
    in-process fifo for testing.
241
242
    One instance is created for each connected client; it can serve multiple
243
    requests in the lifetime of the connection.
244
245
    The server passes requests through to an underlying backing transport, 
246
    which will typically be a LocalTransport looking at the server's filesystem.
247
    """
248
249
    def __init__(self, in_file, out_file, backing_transport):
250
        """Construct new server.
251
252
        :param in_file: Python file from which requests can be read.
253
        :param out_file: Python file to write responses.
254
        :param backing_transport: Transport for the directory served.
255
        """
256
        self._in = in_file
257
        self._out = out_file
258
        self.smart_server = SmartServer(backing_transport)
259
        # server can call back to us to get bulk data - this is not really
260
        # ideal, they should get it per request instead
261
        self.smart_server._recv_body = self._recv_bulk
262
263
    def _recv_tuple(self):
264
        """Read a request from the client and return as a tuple.
265
        
266
        Returns None at end of file (if the client closed the connection.)
267
        """
268
        return _recv_tuple(self._in)
269
270
    def _send_tuple(self, args):
271
        """Send response header"""
1910.19.22 by Robert Collins
Rearrange the readv patch to put the serialise offsets method into the correct class, and document the structure of the classes somewhat better to hint to people writing patches where code should go. Also alter the test so that the client and server components are tested in one place preventing possible encoding skew from occuring.
272
        return self._write_and_flush(_encode_tuple(args))
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
273
274
    def _send_error_and_disconnect(self, exception):
275
        self._send_tuple(('error', str(exception)))
276
        ## self._out.close()
277
        ## self._in.close()
278
279
    def _serve_one_request(self):
280
        """Read one request from input, process, send back a response.
281
        
282
        :return: False if the server should terminate, otherwise None.
283
        """
284
        req_args = self._recv_tuple()
285
        if req_args == None:
286
            # client closed connection
287
            return False  # shutdown server
288
        try:
289
            response = self.smart_server.dispatch_command(req_args[0], req_args[1:])
290
            self._send_tuple(response.args)
291
            if response.body is not None:
292
                self._send_bulk_data(response.body)
293
        except KeyboardInterrupt:
294
            raise
295
        except Exception, e:
296
            # everything else: pass to client, flush, and quit
297
            self._send_error_and_disconnect(e)
298
            return False
299
300
    def serve(self):
301
        """Serve requests until the client disconnects."""
1910.19.3 by Andrew Bennetts
Add SSH support.
302
        # Keep a reference to stderr because the sys module's globals get set to
303
        # None during interpreter shutdown.
304
        from sys import stderr
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
305
        try:
306
            while self._serve_one_request() != False:
307
                pass
308
        except Exception, e:
1910.19.3 by Andrew Bennetts
Add SSH support.
309
            stderr.write("%s terminating on exception %s\n" % (self, e))
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
310
            raise
311
312
313
class SmartServerResponse(object):
314
    """Response generated by SmartServer."""
315
316
    def __init__(self, args, body=None):
317
        self.args = args
318
        self.body = body
319
1910.19.22 by Robert Collins
Rearrange the readv patch to put the serialise offsets method into the correct class, and document the structure of the classes somewhat better to hint to people writing patches where code should go. Also alter the test so that the client and server components are tested in one place preventing possible encoding skew from occuring.
320
# XXX: TODO: Create a SmartServerRequest which will take the responsibility
321
# for delivering the data for a request. This could be done with as the
322
# StreamServer, though that would create conflation between request and response
323
# which may be undesirable.
324
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
325
326
class SmartServer(object):
1910.19.13 by Andrew Bennetts
Address various review comments.
327
    """Protocol logic for smart server.
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
328
    
329
    This doesn't handle serialization at all, it just processes requests and
330
    creates responses.
331
    """
332
1910.19.22 by Robert Collins
Rearrange the readv patch to put the serialise offsets method into the correct class, and document the structure of the classes somewhat better to hint to people writing patches where code should go. Also alter the test so that the client and server components are tested in one place preventing possible encoding skew from occuring.
333
    # IMPORTANT FOR IMPLEMENTORS: It is important that SmartServer not contain
334
    # encoding or decoding logic to allow the wire protocol to vary from the
335
    # object protocol: we will want to tweak the wire protocol separate from
336
    # the object model, and ideally we will be able to do that without having
337
    # a SmartServer subclass for each wire protocol, rather just a Protocol
338
    # subclass.
339
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
340
    # TODO: Better way of representing the body for commands that take it,
341
    # and allow it to be streamed into the server.
342
    
343
    def __init__(self, backing_transport):
344
        self._backing_transport = backing_transport
345
        
346
    def do_hello(self):
347
        """Answer a version request with my version."""
348
        return SmartServerResponse(('ok', '1'))
349
350
    def do_has(self, relpath):
351
        r = self._backing_transport.has(relpath) and 'yes' or 'no'
352
        return SmartServerResponse((r,))
353
354
    def do_get(self, relpath):
1910.19.13 by Andrew Bennetts
Address various review comments.
355
        backing_bytes = self._backing_transport.get_bytes(relpath)
356
        return SmartServerResponse(('ok',), backing_bytes)
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
357
1910.19.13 by Andrew Bennetts
Address various review comments.
358
    def _deserialise_optional_mode(self, mode):
1910.19.22 by Robert Collins
Rearrange the readv patch to put the serialise offsets method into the correct class, and document the structure of the classes somewhat better to hint to people writing patches where code should go. Also alter the test so that the client and server components are tested in one place preventing possible encoding skew from occuring.
359
        # XXX: FIXME this should be on the protocol object.
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
360
        if mode == '':
361
            return None
362
        else:
363
            return int(mode)
364
365
    def do_append(self, relpath, mode):
1910.19.14 by Robert Collins
Fix up all tests to pass, remove a couple more deprecated function calls, and break the dependency on sftp for the smart transport.
366
        old_length = self._backing_transport.append_bytes(
367
            relpath, self._recv_body(), self._deserialise_optional_mode(mode))
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
368
        return SmartServerResponse(('appended', '%d' % old_length))
369
370
    def do_delete(self, relpath):
371
        self._backing_transport.delete(relpath)
372
1910.19.3 by Andrew Bennetts
Add SSH support.
373
    def do_iter_files_recursive(self, abspath):
374
        # XXX: the path handling needs some thought.
375
        #relpath = self._backing_transport.relpath(abspath)
376
        transport = self._backing_transport.clone(abspath)
377
        filenames = transport.iter_files_recursive()
378
        return SmartServerResponse(('names',) + tuple(filenames))
379
380
    def do_list_dir(self, relpath):
381
        filenames = self._backing_transport.list_dir(relpath)
382
        return SmartServerResponse(('names',) + tuple(filenames))
383
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
384
    def do_mkdir(self, relpath, mode):
1910.19.13 by Andrew Bennetts
Address various review comments.
385
        self._backing_transport.mkdir(relpath,
386
                                      self._deserialise_optional_mode(mode))
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
387
388
    def do_move(self, rel_from, rel_to):
389
        self._backing_transport.move(rel_from, rel_to)
390
391
    def do_put(self, relpath, mode):
1910.19.14 by Robert Collins
Fix up all tests to pass, remove a couple more deprecated function calls, and break the dependency on sftp for the smart transport.
392
        self._backing_transport.put_bytes(relpath,
393
                self._recv_body(),
1910.19.13 by Andrew Bennetts
Address various review comments.
394
                self._deserialise_optional_mode(mode))
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
395
1910.19.22 by Robert Collins
Rearrange the readv patch to put the serialise offsets method into the correct class, and document the structure of the classes somewhat better to hint to people writing patches where code should go. Also alter the test so that the client and server components are tested in one place preventing possible encoding skew from occuring.
396
    def _deserialise_offsets(self, text):
397
        # XXX: FIXME this should be on the protocol object.
1910.19.17 by John Arbash Meinel
Implement readv() for the SmartServer
398
        offsets = []
399
        for line in text.split('\n'):
400
            if not line:
401
                continue
402
            start, length = line.split(',')
403
            offsets.append((int(start), int(length)))
404
        return offsets
405
2017.2.1 by John Arbash Meinel
Implement put_bytes_non_atomic for the smart transport
406
    def do_put_non_atomic(self, relpath, mode, create_parent, dir_mode):
407
        create_parent_dir = (create_parent == 'T')
408
        self._backing_transport.put_bytes_non_atomic(relpath,
409
                self._recv_body(),
410
                mode=self._deserialise_optional_mode(mode),
411
                create_parent_dir=create_parent_dir,
412
                dir_mode=self._deserialise_optional_mode(dir_mode))
413
1910.19.17 by John Arbash Meinel
Implement readv() for the SmartServer
414
    def do_readv(self, relpath):
415
        offsets = self._deserialise_offsets(self._recv_body())
416
        backing_bytes = ''.join(bytes for offset, bytes in
417
                             self._backing_transport.readv(relpath, offsets))
418
        return SmartServerResponse(('readv',), backing_bytes)
419
        
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
420
    def do_rename(self, rel_from, rel_to):
421
        self._backing_transport.rename(rel_from, rel_to)
422
423
    def do_rmdir(self, relpath):
424
        self._backing_transport.rmdir(relpath)
425
1910.19.3 by Andrew Bennetts
Add SSH support.
426
    def do_stat(self, relpath):
427
        stat = self._backing_transport.stat(relpath)
428
        return SmartServerResponse(('stat', str(stat.st_size), oct(stat.st_mode)))
429
        
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
430
    def do_get_bundle(self, path, revision_id):
431
        # open transport relative to our base
432
        t = self._backing_transport.clone(path)
433
        control, extra_path = bzrdir.BzrDir.open_containing_from_transport(t)
434
        repo = control.open_repository()
435
        tmpf = tempfile.TemporaryFile()
436
        base_revision = revision.NULL_REVISION
437
        write_bundle(repo, revision_id, base_revision, tmpf)
438
        tmpf.seek(0)
439
        return SmartServerResponse((), tmpf.read())
440
441
    def dispatch_command(self, cmd, args):
442
        func = getattr(self, 'do_' + cmd, None)
443
        if func is None:
1910.19.13 by Andrew Bennetts
Address various review comments.
444
            raise errors.SmartProtocolError("bad request %r" % (cmd,))
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
445
        try:
446
            result = func(*args)
447
            if result is None: 
448
                result = SmartServerResponse(('ok',))
449
            return result
450
        except errors.NoSuchFile, e:
451
            return SmartServerResponse(('NoSuchFile', e.path))
452
        except errors.FileExists, e:
453
            return SmartServerResponse(('FileExists', e.path))
454
        except errors.DirectoryNotEmpty, e:
455
            return SmartServerResponse(('DirectoryNotEmpty', e.path))
1910.19.18 by John Arbash Meinel
[merge] short-read code, implement for Smart readv
456
        except errors.ShortReadvError, e:
457
            return SmartServerResponse(('ShortReadvError',
458
                e.path, str(e.offset), str(e.length), str(e.actual)))
1910.19.16 by John Arbash Meinel
Translate UnicodeErrors across the smart server
459
        except UnicodeError, e:
460
            # If it is a DecodeError, than most likely we are starting
461
            # with a plain string
462
            str_or_unicode = e.object
463
            if isinstance(str_or_unicode, unicode):
464
                val = u'u:' + str_or_unicode
465
            else:
466
                val = u's:' + str_or_unicode.encode('base64')
467
            # This handles UnicodeEncodeError or UnicodeDecodeError
468
            return SmartServerResponse((e.__class__.__name__,
469
                    e.encoding, val, str(e.start), str(e.end), e.reason))
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
470
        except errors.TransportNotPossible, e:
471
            if e.msg == "readonly transport":
472
                return SmartServerResponse(('ReadOnlyError', ))
473
            else:
474
                raise
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
475
476
477
class SmartTCPServer(object):
478
    """Listens on a TCP socket and accepts connections from smart clients"""
479
1910.19.7 by Andrew Bennetts
Allow specifying the host/interface to bzr serve, and use the new test
480
    def __init__(self, backing_transport=None, host='127.0.0.1', port=0):
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
481
        """Construct a new server.
482
483
        To actually start it running, call either start_background_thread or
484
        serve.
485
1910.19.7 by Andrew Bennetts
Allow specifying the host/interface to bzr serve, and use the new test
486
        :param host: Name of the interface to listen on.
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
487
        :param port: TCP port to listen on, or 0 to allocate a transient port.
488
        """
489
        if backing_transport is None:
490
            backing_transport = memory.MemoryTransport()
491
        self._server_socket = socket.socket()
1910.19.7 by Andrew Bennetts
Allow specifying the host/interface to bzr serve, and use the new test
492
        self._server_socket.bind((host, port))
493
        self.port = self._server_socket.getsockname()[1]
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
494
        self._server_socket.listen(1)
495
        self._server_socket.settimeout(1)
496
        self.backing_transport = backing_transport
497
498
    def serve(self):
499
        # let connections timeout so that we get a chance to terminate
1910.19.3 by Andrew Bennetts
Add SSH support.
500
        # Keep a reference to the exceptions we want to catch because the socket
501
        # module's globals get set to None during interpreter shutdown.
502
        from socket import timeout as socket_timeout
503
        from socket import error as socket_error
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
504
        self._should_terminate = False
505
        while not self._should_terminate:
506
            try:
507
                self.accept_and_serve()
1910.19.3 by Andrew Bennetts
Add SSH support.
508
            except socket_timeout:
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
509
                # just check if we're asked to stop
510
                pass
1910.19.3 by Andrew Bennetts
Add SSH support.
511
            except socket_error, e:
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
512
                trace.warning("client disconnected: %s", e)
513
                pass
514
515
    def get_url(self):
516
        """Return the url of the server"""
517
        return "bzr://%s:%d/" % self._server_socket.getsockname()
518
519
    def accept_and_serve(self):
520
        conn, client_addr = self._server_socket.accept()
2037.1.3 by Robert Collins
(Andrew Bennetts, Robert Collins) Set smart server test sockets to be blocking
521
        # For WIN32, where the timeout value from the listening socket
522
        # propogates to the newly accepted socket.
523
        conn.setblocking(True)
1910.19.3 by Andrew Bennetts
Add SSH support.
524
        conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
525
        from_client = conn.makefile('r')
526
        to_client = conn.makefile('w')
527
        handler = SmartStreamServer(from_client, to_client,
528
                self.backing_transport)
529
        connection_thread = threading.Thread(None, handler.serve, name='smart-server-child')
530
        connection_thread.setDaemon(True)
531
        connection_thread.start()
532
533
    def start_background_thread(self):
534
        self._server_thread = threading.Thread(None,
535
                self.serve,
536
                name='server-' + self.get_url())
537
        self._server_thread.setDaemon(True)
538
        self._server_thread.start()
539
540
    def stop_background_thread(self):
541
        self._should_terminate = True
542
        # self._server_socket.close()
543
        # we used to join the thread, but it's not really necessary; it will
544
        # terminate in time
545
        ## self._server_thread.join()
546
547
548
class SmartTCPServer_for_testing(SmartTCPServer):
549
    """Server suitable for use by transport tests.
550
    
551
    This server is backed by the process's cwd.
552
    """
553
554
    def __init__(self):
2049.1.1 by Lukáš Lalinský
Windows-speficic smart server transport selftest fixes.
555
        self._homedir = urlutils.local_path_to_url(os.getcwd())[7:]
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
556
        # The server is set up by default like for ssh access: the client
557
        # passes filesystem-absolute paths; therefore the server must look
558
        # them up relative to the root directory.  it might be better to act
559
        # a public server and have the server rewrite paths into the test
560
        # directory.
2049.1.1 by Lukáš Lalinský
Windows-speficic smart server transport selftest fixes.
561
        SmartTCPServer.__init__(self,
562
            transport.get_transport(urlutils.local_path_to_url('/')))
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
563
        
564
    def setUp(self):
565
        """Set up server for testing"""
566
        self.start_background_thread()
567
568
    def tearDown(self):
569
        self.stop_background_thread()
570
571
    def get_url(self):
572
        """Return the url of the server"""
573
        host, port = self._server_socket.getsockname()
574
        return "bzr://%s:%d%s" % (host, port, urlutils.escape(self._homedir))
575
576
    def get_bogus_url(self):
577
        """Return a URL which will fail to connect"""
578
        return 'bzr://127.0.0.1:1/'
579
580
1910.19.3 by Andrew Bennetts
Add SSH support.
581
class SmartStat(object):
582
583
    def __init__(self, size, mode):
584
        self.st_size = size
585
        self.st_mode = mode
586
587
1910.19.14 by Robert Collins
Fix up all tests to pass, remove a couple more deprecated function calls, and break the dependency on sftp for the smart transport.
588
class SmartTransport(transport.Transport):
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
589
    """Connection to a smart server.
590
591
    The connection holds references to pipes that can be used to send requests
592
    to the server.
593
594
    The connection has a notion of the current directory to which it's
595
    connected; this is incorporated in filenames passed to the server.
596
    
597
    This supports some higher-level RPC operations and can also be treated 
598
    like a Transport to do file-like operations.
599
600
    The connection can be made over a tcp socket, or (in future) an ssh pipe
601
    or a series of http requests.  There are concrete subclasses for each
602
    type: SmartTCPTransport, etc.
603
    """
604
1910.19.22 by Robert Collins
Rearrange the readv patch to put the serialise offsets method into the correct class, and document the structure of the classes somewhat better to hint to people writing patches where code should go. Also alter the test so that the client and server components are tested in one place preventing possible encoding skew from occuring.
605
    # IMPORTANT FOR IMPLEMENTORS: SmartTransport MUST NOT be given encoding
606
    # responsibilities: Put those on SmartClient or similar. This is vital for
607
    # the ability to support multiple versions of the smart protocol over time:
608
    # SmartTransport is an adapter from the Transport object model to the 
609
    # SmartClient model, not an encoder.
610
1910.19.14 by Robert Collins
Fix up all tests to pass, remove a couple more deprecated function calls, and break the dependency on sftp for the smart transport.
611
    def __init__(self, url, clone_from=None, client=None):
1910.19.11 by Andrew Bennetts
General code cleanup based on review comments and other observations.
612
        """Constructor.
613
614
        :param client: ignored when clone_from is not None.
615
        """
1910.19.6 by Andrew Bennetts
Merge increased transport root tests and fix the smart transport accordingly.
616
        ### Technically super() here is faulty because Transport's __init__
617
        ### fails to take 2 parameters, and if super were to choose a silly
618
        ### initialisation order things would blow up. 
1910.19.14 by Robert Collins
Fix up all tests to pass, remove a couple more deprecated function calls, and break the dependency on sftp for the smart transport.
619
        if not url.endswith('/'):
620
            url += '/'
621
        super(SmartTransport, self).__init__(url)
622
        self._scheme, self._username, self._password, self._host, self._port, self._path = \
623
                transport.split_url(url)
1910.19.6 by Andrew Bennetts
Merge increased transport root tests and fix the smart transport accordingly.
624
        if clone_from is None:
1910.19.11 by Andrew Bennetts
General code cleanup based on review comments and other observations.
625
            if client is None:
626
                self._client = SmartStreamClient(self._connect_to_server)
627
            else:
628
                self._client = client
1910.19.6 by Andrew Bennetts
Merge increased transport root tests and fix the smart transport accordingly.
629
        else:
630
            # credentials may be stripped from the base in some circumstances
631
            # as yet to be clearly defined or documented, so copy them.
632
            self._username = clone_from._username
633
            # reuse same connection
634
            self._client = clone_from._client
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
635
1910.19.14 by Robert Collins
Fix up all tests to pass, remove a couple more deprecated function calls, and break the dependency on sftp for the smart transport.
636
    def abspath(self, relpath):
637
        """Return the full url to the given relative path.
638
        
639
        @param relpath: the relative path or path components
640
        @type relpath: str or list
641
        """
642
        return self._unparse_url(self._remote_path(relpath))
643
    
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
644
    def clone(self, relative_url):
645
        """Make a new SmartTransport related to me, sharing the same connection.
646
647
        This essentially opens a handle on a different remote directory.
648
        """
1910.19.6 by Andrew Bennetts
Merge increased transport root tests and fix the smart transport accordingly.
649
        if relative_url is None:
650
            return self.__class__(self.base, self)
651
        else:
652
            return self.__class__(self.abspath(relative_url), self)
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
653
654
    def is_readonly(self):
655
        """Smart server transport can do read/write file operations."""
656
        return False
657
                                                   
658
    def get_smart_client(self):
659
        return self._client
660
                                                   
661
    def _unparse_url(self, path):
662
        """Return URL for a path.
663
664
        :see: SFTPUrlHandling._unparse_url
665
        """
666
        # TODO: Eventually it should be possible to unify this with
667
        # SFTPUrlHandling._unparse_url?
668
        if path == '':
669
            path = '/'
670
        path = urllib.quote(path)
671
        netloc = urllib.quote(self._host)
672
        if self._username is not None:
673
            netloc = '%s@%s' % (urllib.quote(self._username), netloc)
674
        if self._port is not None:
675
            netloc = '%s:%d' % (netloc, self._port)
676
        return urlparse.urlunparse((self._scheme, netloc, path, '', '', ''))
677
678
    def _remote_path(self, relpath):
1910.19.14 by Robert Collins
Fix up all tests to pass, remove a couple more deprecated function calls, and break the dependency on sftp for the smart transport.
679
        """Returns the Unicode version of the absolute path for relpath."""
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
680
        return self._combine_paths(self._path, relpath)
681
682
    def has(self, relpath):
683
        """Indicate whether a remote file of the given name exists or not.
684
685
        :see: Transport.has()
686
        """
687
        resp = self._client._call('has', self._remote_path(relpath))
688
        if resp == ('yes', ):
689
            return True
690
        elif resp == ('no', ):
691
            return False
692
        else:
693
            self._translate_error(resp)
694
695
    def get(self, relpath):
696
        """Return file-like object reading the contents of a remote file.
697
        
1910.19.13 by Andrew Bennetts
Address various review comments.
698
        :see: Transport.get_bytes()/get_file()
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
699
        """
700
        remote = self._remote_path(relpath)
701
        resp = self._client._call('get', remote)
702
        if resp != ('ok', ):
1910.19.3 by Andrew Bennetts
Add SSH support.
703
            self._translate_error(resp, relpath)
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
704
        return StringIO(self._client._recv_bulk())
705
1910.19.13 by Andrew Bennetts
Address various review comments.
706
    def _serialise_optional_mode(self, mode):
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
707
        if mode is None:
708
            return ''
709
        else:
710
            return '%d' % mode
711
712
    def mkdir(self, relpath, mode=None):
713
        resp = self._client._call('mkdir', 
714
                                  self._remote_path(relpath), 
1910.19.13 by Andrew Bennetts
Address various review comments.
715
                                  self._serialise_optional_mode(mode))
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
716
        self._translate_error(resp)
717
1910.19.13 by Andrew Bennetts
Address various review comments.
718
    def put_bytes(self, relpath, upload_contents, mode=None):
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
719
        # FIXME: upload_file is probably not safe for non-ascii characters -
720
        # should probably just pass all parameters as length-delimited
721
        # strings?
1910.19.13 by Andrew Bennetts
Address various review comments.
722
        resp = self._client._call_with_upload(
723
            'put',
724
            (self._remote_path(relpath), self._serialise_optional_mode(mode)),
725
            upload_contents)
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
726
        self._translate_error(resp)
727
2017.2.1 by John Arbash Meinel
Implement put_bytes_non_atomic for the smart transport
728
    def put_bytes_non_atomic(self, relpath, bytes, mode=None,
729
                             create_parent_dir=False,
730
                             dir_mode=None):
1910.19.24 by Robert Collins
Touchup method order.
731
        """See Transport.put_bytes_non_atomic."""
732
        # FIXME: no encoding in the transport!
2017.2.1 by John Arbash Meinel
Implement put_bytes_non_atomic for the smart transport
733
        create_parent_str = 'F'
734
        if create_parent_dir:
735
            create_parent_str = 'T'
736
737
        resp = self._client._call_with_upload(
738
            'put_non_atomic',
739
            (self._remote_path(relpath), self._serialise_optional_mode(mode),
740
             create_parent_str, self._serialise_optional_mode(dir_mode)),
741
            bytes)
742
        self._translate_error(resp)
743
1910.19.24 by Robert Collins
Touchup method order.
744
    def put_file(self, relpath, upload_file, mode=None):
745
        # its not ideal to seek back, but currently put_non_atomic_file depends
746
        # on transports not reading before failing - which is a faulty
747
        # assumption I think - RBC 20060915
748
        pos = upload_file.tell()
749
        try:
750
            return self.put_bytes(relpath, upload_file.read(), mode)
751
        except:
752
            upload_file.seek(pos)
753
            raise
754
755
    def put_file_non_atomic(self, relpath, f, mode=None,
756
                            create_parent_dir=False,
757
                            dir_mode=None):
758
        return self.put_bytes_non_atomic(relpath, f.read(), mode=mode,
759
                                         create_parent_dir=create_parent_dir,
760
                                         dir_mode=dir_mode)
761
1910.19.13 by Andrew Bennetts
Address various review comments.
762
    def append_file(self, relpath, from_file, mode=None):
1910.19.14 by Robert Collins
Fix up all tests to pass, remove a couple more deprecated function calls, and break the dependency on sftp for the smart transport.
763
        return self.append_bytes(relpath, from_file.read(), mode)
1910.19.13 by Andrew Bennetts
Address various review comments.
764
        
765
    def append_bytes(self, relpath, bytes, mode=None):
766
        resp = self._client._call_with_upload(
767
            'append',
768
            (self._remote_path(relpath), self._serialise_optional_mode(mode)),
769
            bytes)
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
770
        if resp[0] == 'appended':
771
            return int(resp[1])
772
        self._translate_error(resp)
773
774
    def delete(self, relpath):
775
        resp = self._client._call('delete', self._remote_path(relpath))
776
        self._translate_error(resp)
777
1910.19.17 by John Arbash Meinel
Implement readv() for the SmartServer
778
    def readv(self, relpath, offsets):
779
        if not offsets:
780
            return
781
782
        offsets = list(offsets)
1910.19.20 by John Arbash Meinel
Update the client-side requests, so that we collapse nearby ranges, rather than requesting lots of small chunks
783
784
        sorted_offsets = sorted(offsets)
785
        # turn the list of offsets into a stack
786
        offset_stack = iter(offsets)
787
        cur_offset_and_size = offset_stack.next()
788
        coalesced = list(self._coalesce_offsets(sorted_offsets,
789
                               limit=self._max_readv_combine,
790
                               fudge_factor=self._bytes_to_read_before_seek))
791
792
1910.19.17 by John Arbash Meinel
Implement readv() for the SmartServer
793
        resp = self._client._call_with_upload(
794
            'readv',
795
            (self._remote_path(relpath),),
1910.19.22 by Robert Collins
Rearrange the readv patch to put the serialise offsets method into the correct class, and document the structure of the classes somewhat better to hint to people writing patches where code should go. Also alter the test so that the client and server components are tested in one place preventing possible encoding skew from occuring.
796
            self._client._serialise_offsets((c.start, c.length) for c in coalesced))
1910.19.17 by John Arbash Meinel
Implement readv() for the SmartServer
797
798
        if resp[0] != 'readv':
1910.19.20 by John Arbash Meinel
Update the client-side requests, so that we collapse nearby ranges, rather than requesting lots of small chunks
799
            # This should raise an exception
1910.19.17 by John Arbash Meinel
Implement readv() for the SmartServer
800
            self._translate_error(resp)
1910.19.20 by John Arbash Meinel
Update the client-side requests, so that we collapse nearby ranges, rather than requesting lots of small chunks
801
            return
802
803
        data = self._client._recv_bulk()
804
        # Cache the results, but only until they have been fulfilled
805
        data_map = {}
806
        for c_offset in coalesced:
807
            if len(data) < c_offset.length:
808
                raise errors.ShortReadvError(relpath, c_offset.start,
809
                            c_offset.length, actual=len(data))
810
            for suboffset, subsize in c_offset.ranges:
811
                key = (c_offset.start+suboffset, subsize)
812
                data_map[key] = data[suboffset:suboffset+subsize]
813
            data = data[c_offset.length:]
814
815
            # Now that we've read some data, see if we can yield anything back
816
            while cur_offset_and_size in data_map:
817
                this_data = data_map.pop(cur_offset_and_size)
818
                yield cur_offset_and_size[0], this_data
819
                cur_offset_and_size = offset_stack.next()
1910.19.17 by John Arbash Meinel
Implement readv() for the SmartServer
820
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
821
    def rename(self, rel_from, rel_to):
822
        self._call('rename', 
823
                   self._remote_path(rel_from),
824
                   self._remote_path(rel_to))
825
826
    def move(self, rel_from, rel_to):
827
        self._call('move', 
828
                   self._remote_path(rel_from),
829
                   self._remote_path(rel_to))
830
831
    def rmdir(self, relpath):
832
        resp = self._call('rmdir', self._remote_path(relpath))
833
834
    def _call(self, method, *args):
835
        resp = self._client._call(method, *args)
836
        self._translate_error(resp)
837
1910.19.3 by Andrew Bennetts
Add SSH support.
838
    def _translate_error(self, resp, orig_path=None):
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
839
        """Raise an exception from a response"""
2018.1.1 by Andrew Bennetts
Make bzr+ssh:// actually work (at least with absolute paths).
840
        if resp is None:
841
            what = None
842
        else:
843
            what = resp[0]
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
844
        if what == 'ok':
845
            return
846
        elif what == 'NoSuchFile':
1910.19.3 by Andrew Bennetts
Add SSH support.
847
            if orig_path is not None:
848
                error_path = orig_path
849
            else:
850
                error_path = resp[1]
851
            raise errors.NoSuchFile(error_path)
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
852
        elif what == 'error':
1910.19.13 by Andrew Bennetts
Address various review comments.
853
            raise errors.SmartProtocolError(unicode(resp[1]))
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
854
        elif what == 'FileExists':
855
            raise errors.FileExists(resp[1])
856
        elif what == 'DirectoryNotEmpty':
857
            raise errors.DirectoryNotEmpty(resp[1])
1910.19.18 by John Arbash Meinel
[merge] short-read code, implement for Smart readv
858
        elif what == 'ShortReadvError':
859
            raise errors.ShortReadvError(resp[1], int(resp[2]),
860
                                         int(resp[3]), int(resp[4]))
1910.19.16 by John Arbash Meinel
Translate UnicodeErrors across the smart server
861
        elif what in ('UnicodeEncodeError', 'UnicodeDecodeError'):
862
            encoding = str(resp[1]) # encoding must always be a string
863
            val = resp[2]
864
            start = int(resp[3])
865
            end = int(resp[4])
866
            reason = str(resp[5]) # reason must always be a string
867
            if val.startswith('u:'):
868
                val = val[2:]
869
            elif val.startswith('s:'):
870
                val = val[2:].decode('base64')
871
            if what == 'UnicodeDecodeError':
872
                raise UnicodeDecodeError(encoding, val, start, end, reason)
873
            elif what == 'UnicodeEncodeError':
874
                raise UnicodeEncodeError(encoding, val, start, end, reason)
2020.1.1 by Robert Collins
Add readonly support to the smart server, enabled by default via `bzr server`.
875
        elif what == "ReadOnlyError":
876
            raise errors.TransportNotPossible('readonly transport')
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
877
        else:
1910.19.13 by Andrew Bennetts
Address various review comments.
878
            raise errors.SmartProtocolError('unexpected smart server error: %r' % (resp,))
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
879
880
    def _send_tuple(self, args):
881
        self._client._send_tuple(args)
882
883
    def _recv_tuple(self):
884
        return self._client._recv_tuple()
885
886
    def disconnect(self):
887
        self._client.disconnect()
888
889
    def delete_tree(self, relpath):
890
        raise errors.TransportNotPossible('readonly transport')
891
892
    def stat(self, relpath):
1910.19.3 by Andrew Bennetts
Add SSH support.
893
        resp = self._client._call('stat', self._remote_path(relpath))
894
        if resp[0] == 'stat':
895
            return SmartStat(int(resp[1]), int(resp[2], 8))
896
        else:
897
            self._translate_error(resp)
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
898
899
    ## def lock_read(self, relpath):
900
    ##     """Lock the given file for shared (read) access.
901
    ##     :return: A lock object, which should be passed to Transport.unlock()
902
    ##     """
903
    ##     # The old RemoteBranch ignore lock for reading, so we will
904
    ##     # continue that tradition and return a bogus lock object.
905
    ##     class BogusLock(object):
906
    ##         def __init__(self, path):
907
    ##             self.path = path
908
    ##         def unlock(self):
909
    ##             pass
910
    ##     return BogusLock(relpath)
911
1910.19.3 by Andrew Bennetts
Add SSH support.
912
    def listable(self):
913
        return True
914
915
    def list_dir(self, relpath):
916
        resp = self._client._call('list_dir',
917
                                  self._remote_path(relpath))
918
        if resp[0] == 'names':
919
            return [name.encode('ascii') for name in resp[1:]]
920
        else:
921
            self._translate_error(resp)
922
923
    def iter_files_recursive(self):
924
        resp = self._client._call('iter_files_recursive',
925
                                  self._remote_path(''))
926
        if resp[0] == 'names':
927
            return resp[1:]
928
        else:
929
            self._translate_error(resp)
930
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
931
932
class SmartStreamClient(SmartProtocolBase):
933
    """Connection to smart server over two streams"""
934
1910.19.3 by Andrew Bennetts
Add SSH support.
935
    def __init__(self, connect_func):
936
        self._connect_func = connect_func
937
        self._connected = False
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
938
939
    def __del__(self):
940
        self.disconnect()
941
1910.19.3 by Andrew Bennetts
Add SSH support.
942
    def _ensure_connection(self):
943
        if not self._connected:
944
            self._in, self._out = self._connect_func()
945
            self._connected = True
946
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
947
    def _send_tuple(self, args):
1910.19.3 by Andrew Bennetts
Add SSH support.
948
        self._ensure_connection()
1910.19.22 by Robert Collins
Rearrange the readv patch to put the serialise offsets method into the correct class, and document the structure of the classes somewhat better to hint to people writing patches where code should go. Also alter the test so that the client and server components are tested in one place preventing possible encoding skew from occuring.
949
        return self._write_and_flush(_encode_tuple(args))
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
950
1910.19.3 by Andrew Bennetts
Add SSH support.
951
    def _send_bulk_data(self, body):
952
        self._ensure_connection()
953
        SmartProtocolBase._send_bulk_data(self, body)
954
        
955
    def _recv_bulk(self):
956
        self._ensure_connection()
957
        return SmartProtocolBase._recv_bulk(self)
958
959
    def _recv_tuple(self):
960
        self._ensure_connection()
961
        return SmartProtocolBase._recv_tuple(self)
962
963
    def _recv_trailer(self):
964
        self._ensure_connection()
965
        return SmartProtocolBase._recv_trailer(self)
966
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
967
    def disconnect(self):
968
        """Close connection to the server"""
1910.19.3 by Andrew Bennetts
Add SSH support.
969
        if self._connected:
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
970
            self._out.close()
971
            self._in.close()
972
973
    def _call(self, *args):
974
        self._send_tuple(args)
975
        return self._recv_tuple()
976
977
    def _call_with_upload(self, method, args, body):
978
        """Call an rpc, supplying bulk upload data.
979
980
        :param method: method name to call
981
        :param args: parameter args tuple
982
        :param body: upload body as a byte string
983
        """
984
        self._send_tuple((method,) + args)
985
        self._send_bulk_data(body)
986
        return self._recv_tuple()
987
988
    def query_version(self):
989
        """Return protocol version number of the server."""
990
        # XXX: should make sure it's empty
991
        self._send_tuple(('hello',))
992
        resp = self._recv_tuple()
993
        if resp == ('ok', '1'):
994
            return 1
995
        else:
1910.19.13 by Andrew Bennetts
Address various review comments.
996
            raise errors.SmartProtocolError("bad response %r" % (resp,))
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
997
998
999
class SmartTCPTransport(SmartTransport):
1000
    """Connection to smart server over plain tcp"""
1001
1910.19.6 by Andrew Bennetts
Merge increased transport root tests and fix the smart transport accordingly.
1002
    def __init__(self, url, clone_from=None):
1003
        super(SmartTCPTransport, self).__init__(url, clone_from)
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
1004
        try:
1005
            self._port = int(self._port)
1006
        except (ValueError, TypeError), e:
1007
            raise errors.InvalidURL(path=url, extra="invalid port %s" % self._port)
1910.19.3 by Andrew Bennetts
Add SSH support.
1008
        self._socket = None
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
1009
1010
    def _connect_to_server(self):
1011
        self._socket = socket.socket()
1910.19.3 by Andrew Bennetts
Add SSH support.
1012
        self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
1013
        result = self._socket.connect_ex((self._host, int(self._port)))
1014
        if result:
1015
            raise errors.ConnectionError("failed to connect to %s:%d: %s" %
1016
                    (self._host, self._port, os.strerror(result)))
1017
        # TODO: May be more efficient to just treat them as sockets
1018
        # throughout?  But what about pipes to ssh?...
1019
        to_server = self._socket.makefile('w')
1020
        from_server = self._socket.makefile('r')
1910.19.3 by Andrew Bennetts
Add SSH support.
1021
        return from_server, to_server
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
1022
1023
    def disconnect(self):
1024
        super(SmartTCPTransport, self).disconnect()
1025
        # XXX: Is closing the socket as well as closing the files really
1026
        # necessary?
1910.19.3 by Andrew Bennetts
Add SSH support.
1027
        if self._socket is not None:
1028
            self._socket.close()
1029
1910.19.14 by Robert Collins
Fix up all tests to pass, remove a couple more deprecated function calls, and break the dependency on sftp for the smart transport.
1030
try:
2018.1.1 by Andrew Bennetts
Make bzr+ssh:// actually work (at least with absolute paths).
1031
    from bzrlib.transport import sftp, ssh
1910.19.14 by Robert Collins
Fix up all tests to pass, remove a couple more deprecated function calls, and break the dependency on sftp for the smart transport.
1032
except errors.ParamikoNotPresent:
1033
    # no paramiko, no SSHTransport.
1034
    pass
1035
else:
1036
    class SmartSSHTransport(SmartTransport):
1037
        """Connection to smart server over SSH."""
1038
1039
        def __init__(self, url, clone_from=None):
1040
            # TODO: all this probably belongs in the parent class.
1041
            super(SmartSSHTransport, self).__init__(url, clone_from)
1042
            try:
1043
                if self._port is not None:
1044
                    self._port = int(self._port)
1045
            except (ValueError, TypeError), e:
1046
                raise errors.InvalidURL(path=url, extra="invalid port %s" % self._port)
1047
1048
        def _connect_to_server(self):
2018.1.1 by Andrew Bennetts
Make bzr+ssh:// actually work (at least with absolute paths).
1049
            executable = os.environ.get('BZR_REMOTE_PATH', 'bzr')
1050
            vendor = ssh._get_ssh_vendor()
2018.1.9 by Andrew Bennetts
Implement ParamikoVendor.connect_ssh
1051
            self._ssh_connection = vendor.connect_ssh(self._username,
1052
                    self._password, self._host, self._port,
2018.1.10 by Andrew Bennetts
Merge --allow-writes from Robert
1053
                    command=[executable, 'serve', '--inet', '--directory=/',
1054
                             '--allow-writes'])
1910.19.14 by Robert Collins
Fix up all tests to pass, remove a couple more deprecated function calls, and break the dependency on sftp for the smart transport.
1055
            return self._ssh_connection.get_filelike_channels()
1056
1057
        def disconnect(self):
1058
            super(SmartSSHTransport, self).disconnect()
1059
            self._ssh_connection.close()
1910.19.1 by Andrew Bennetts
Support bzr:// urls to work with the new RPC-based transport which will be used
1060
1061
1062
def get_test_permutations():
1063
    """Return (transport, server) permutations for testing"""
1064
    return [(SmartTCPTransport, SmartTCPServer_for_testing)]