~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/request.py

  • Committer: Robert Collins
  • Date: 2009-03-16 07:44:05 UTC
  • mto: This revision was merged to the branch mainline in revision 4149.
  • Revision ID: robertc@robertcollins.net-20090316074405-t9guvf13rj4mlhuk
More test fallout, but all caught now.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
 
 
17
 
"""Infrastructure for server-side request handlers.
18
 
 
19
 
Interesting module attributes:
20
 
    * The request_handlers registry maps verb names to SmartServerRequest
21
 
      classes.
22
 
    * The jail_info threading.local() object is used to prevent accidental
23
 
      opening of BzrDirs outside of the backing transport, or any other
24
 
      transports placed in jail_info.transports.  The jail_info is reset on
25
 
      every call into a request handler (which can happen an arbitrary number
26
 
      of times during a request).
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""Basic server-side logic for dealing with requests.
 
18
 
 
19
**XXX**:
 
20
 
 
21
The class names are a little confusing: the protocol will instantiate a
 
22
SmartServerRequestHandler, whose dispatch_command method creates an instance of
 
23
a SmartServerRequest subclass.
 
24
 
 
25
The request_handlers registry tracks SmartServerRequest classes (rather than
 
26
SmartServerRequestHandler).
27
27
"""
28
28
 
29
 
# XXX: The class names are a little confusing: the protocol will instantiate a
30
 
# SmartServerRequestHandler, whose dispatch_command method creates an instance
31
 
# of a SmartServerRequest subclass.
32
 
 
33
 
 
34
29
import tempfile
35
 
import thread
36
 
import threading
37
30
 
38
31
from bzrlib import (
39
32
    bzrdir,
40
 
    debug,
41
33
    errors,
42
 
    osutils,
43
34
    registry,
44
35
    revision,
45
36
    trace,
51
42
""")
52
43
 
53
44
 
54
 
jail_info = threading.local()
55
 
jail_info.transports = None
56
 
 
57
 
 
58
 
def _install_hook():
59
 
    bzrdir.BzrDir.hooks.install_named_hook(
60
 
        'pre_open', _pre_open_hook, 'checking server jail')
61
 
 
62
 
 
63
 
def _pre_open_hook(transport):
64
 
    allowed_transports = getattr(jail_info, 'transports', None)
65
 
    if allowed_transports is None:
66
 
        return
67
 
    abspath = transport.base
68
 
    for allowed_transport in allowed_transports:
69
 
        try:
70
 
            allowed_transport.relpath(abspath)
71
 
        except errors.PathNotChild:
72
 
            continue
73
 
        else:
74
 
            return
75
 
    raise errors.JailBreak(abspath)
76
 
 
77
 
 
78
 
_install_hook()
79
 
 
80
 
 
81
45
class SmartServerRequest(object):
82
46
    """Base class for request handlers.
83
47
 
89
53
    # XXX: rename this class to BaseSmartServerRequestHandler ?  A request
90
54
    # *handler* is a different concept to the request.
91
55
 
92
 
    def __init__(self, backing_transport, root_client_path='/', jail_root=None):
 
56
    def __init__(self, backing_transport, root_client_path='/'):
93
57
        """Constructor.
94
58
 
95
59
        :param backing_transport: the base transport to be used when performing
99
63
            from the client.  Clients will not be able to refer to paths above
100
64
            this root.  If root_client_path is None, then no translation will
101
65
            be performed on client paths.  Default is '/'.
102
 
        :param jail_root: if specified, the root of the BzrDir.open jail to use
103
 
            instead of backing_transport.
104
66
        """
105
67
        self._backing_transport = backing_transport
106
 
        if jail_root is None:
107
 
            jail_root = backing_transport
108
 
        self._jail_root = jail_root
109
68
        if root_client_path is not None:
110
69
            if not root_client_path.startswith('/'):
111
70
                root_client_path = '/' + root_client_path
162
121
        self._body_chunks = None
163
122
        return self.do_body(body_bytes)
164
123
 
165
 
    def setup_jail(self):
166
 
        jail_info.transports = [self._jail_root]
167
 
 
168
 
    def teardown_jail(self):
169
 
        jail_info.transports = None
170
 
 
171
124
    def translate_client_path(self, client_path):
172
125
        """Translate a path received from a network client into a local
173
126
        relpath.
184
137
            return client_path
185
138
        if not client_path.startswith('/'):
186
139
            client_path = '/' + client_path
187
 
        if client_path + '/' == self._root_client_path:
188
 
            return '.'
189
140
        if client_path.startswith(self._root_client_path):
190
141
            path = client_path[len(self._root_client_path):]
191
142
            relpath = urlutils.joinpath('/', path)
192
143
            if not relpath.startswith('/'):
193
144
                raise ValueError(relpath)
194
 
            return urlutils.escape('.' + relpath)
 
145
            return '.' + relpath
195
146
        else:
196
147
            raise errors.PathNotChild(client_path, self._root_client_path)
197
148
 
273
224
    # TODO: Better way of representing the body for commands that take it,
274
225
    # and allow it to be streamed into the server.
275
226
 
276
 
    def __init__(self, backing_transport, commands, root_client_path,
277
 
        jail_root=None):
 
227
    def __init__(self, backing_transport, commands, root_client_path):
278
228
        """Constructor.
279
229
 
280
230
        :param backing_transport: a Transport to handle requests for.
284
234
        self._backing_transport = backing_transport
285
235
        self._root_client_path = root_client_path
286
236
        self._commands = commands
287
 
        if jail_root is None:
288
 
            jail_root = backing_transport
289
 
        self._jail_root = jail_root
290
237
        self.response = None
291
238
        self.finished_reading = False
292
239
        self._command = None
293
 
        if 'hpss' in debug.debug_flags:
294
 
            self._request_start_time = osutils.timer_func()
295
 
            self._thread_id = thread.get_ident()
296
 
 
297
 
    def _trace(self, action, message, extra_bytes=None, include_time=False):
298
 
        # It is a bit of a shame that this functionality overlaps with that of 
299
 
        # ProtocolThreeRequester._trace. However, there is enough difference
300
 
        # that just putting it in a helper doesn't help a lot. And some state
301
 
        # is taken from the instance.
302
 
        if include_time:
303
 
            t = '%5.3fs ' % (osutils.timer_func() - self._request_start_time)
304
 
        else:
305
 
            t = ''
306
 
        if extra_bytes is None:
307
 
            extra = ''
308
 
        else:
309
 
            extra = ' ' + repr(extra_bytes[:40])
310
 
            if len(extra) > 33:
311
 
                extra = extra[:29] + extra[-1] + '...'
312
 
        trace.mutter('%12s: [%s] %s%s%s'
313
 
                     % (action, self._thread_id, t, message, extra))
314
240
 
315
241
    def accept_body(self, bytes):
316
242
        """Accept body data."""
317
 
        if self._command is None:
318
 
            # no active command object, so ignore the event.
319
 
            return
320
243
        self._run_handler_code(self._command.do_chunk, (bytes,), {})
321
 
        if 'hpss' in debug.debug_flags:
322
 
            self._trace('accept body',
323
 
                        '%d bytes' % (len(bytes),), bytes)
324
244
 
325
245
    def end_of_body(self):
326
246
        """No more body data will be received."""
327
247
        self._run_handler_code(self._command.do_end, (), {})
328
248
        # cannot read after this.
329
249
        self.finished_reading = True
330
 
        if 'hpss' in debug.debug_flags:
331
 
            self._trace('end of body', '', include_time=True)
 
250
 
 
251
    def dispatch_command(self, cmd, args):
 
252
        """Deprecated compatibility method.""" # XXX XXX
 
253
        try:
 
254
            command = self._commands.get(cmd)
 
255
        except LookupError:
 
256
            raise errors.UnknownSmartMethod(cmd)
 
257
        self._command = command(self._backing_transport, self._root_client_path)
 
258
        self._run_handler_code(self._command.execute, args, {})
332
259
 
333
260
    def _run_handler_code(self, callable, args, kwargs):
334
261
        """Run some handler specific code 'callable'.
350
277
        # XXX: most of this error conversion is VFS-related, and thus ought to
351
278
        # be in SmartServerVFSRequestHandler somewhere.
352
279
        try:
353
 
            self._command.setup_jail()
354
 
            try:
355
 
                return callable(*args, **kwargs)
356
 
            finally:
357
 
                self._command.teardown_jail()
 
280
            return callable(*args, **kwargs)
358
281
        except (KeyboardInterrupt, SystemExit):
359
282
            raise
360
283
        except Exception, err:
363
286
 
364
287
    def headers_received(self, headers):
365
288
        # Just a no-op at the moment.
366
 
        if 'hpss' in debug.debug_flags:
367
 
            self._trace('headers', repr(headers))
 
289
        pass
368
290
 
369
291
    def args_received(self, args):
370
292
        cmd = args[0]
372
294
        try:
373
295
            command = self._commands.get(cmd)
374
296
        except LookupError:
375
 
            if 'hpss' in debug.debug_flags:
376
 
                self._trace('hpss unknown request', 
377
 
                            cmd, repr(args)[1:-1])
378
297
            raise errors.UnknownSmartMethod(cmd)
379
 
        if 'hpss' in debug.debug_flags:
380
 
            from bzrlib.smart import vfs
381
 
            if issubclass(command, vfs.VfsRequest):
382
 
                action = 'hpss vfs req'
383
 
            else:
384
 
                action = 'hpss request'
385
 
            self._trace(action, 
386
 
                        '%s %s' % (cmd, repr(args)[1:-1]))
387
 
        self._command = command(
388
 
            self._backing_transport, self._root_client_path, self._jail_root)
 
298
        self._command = command(self._backing_transport)
389
299
        self._run_handler_code(self._command.execute, args, {})
390
300
 
391
301
    def end_received(self):
392
 
        if self._command is None:
393
 
            # no active command object, so ignore the event.
394
 
            return
395
302
        self._run_handler_code(self._command.do_end, (), {})
396
 
        if 'hpss' in debug.debug_flags:
397
 
            self._trace('end', '', include_time=True)
398
303
 
399
304
    def post_body_error_received(self, error_args):
400
305
        # Just a no-op at the moment.
408
313
        return ('FileExists', err.path)
409
314
    elif isinstance(err, errors.DirectoryNotEmpty):
410
315
        return ('DirectoryNotEmpty', err.path)
411
 
    elif isinstance(err, errors.IncompatibleRepositories):
412
 
        return ('IncompatibleRepositories', str(err.source), str(err.target),
413
 
            str(err.details))
414
316
    elif isinstance(err, errors.ShortReadvError):
415
317
        return ('ShortReadvError', err.path, str(err.offset), str(err.length),
416
318
                str(err.actual))
442
344
        return ('ReadError', err.path)
443
345
    elif isinstance(err, errors.PermissionDenied):
444
346
        return ('PermissionDenied', err.path, err.extra)
445
 
    elif isinstance(err, errors.TokenMismatch):
446
 
        return ('TokenMismatch', err.given_token, err.lock_token)
447
 
    elif isinstance(err, errors.LockContention):
448
 
        return ('LockContention',)
449
347
    # Unserialisable error.  Log it, and return a generic error
450
348
    trace.log_exception_quietly()
451
349
    return ('error', str(err))
498
396
    'Branch.get_tags_bytes', 'bzrlib.smart.branch',
499
397
    'SmartServerBranchGetTagsBytes')
500
398
request_handlers.register_lazy(
501
 
    'Branch.set_tags_bytes', 'bzrlib.smart.branch',
502
 
    'SmartServerBranchSetTagsBytes')
503
 
request_handlers.register_lazy(
504
399
    'Branch.get_stacked_on_url', 'bzrlib.smart.branch', 'SmartServerBranchRequestGetStackedOnURL')
505
400
request_handlers.register_lazy(
506
401
    'Branch.last_revision_info', 'bzrlib.smart.branch', 'SmartServerBranchRequestLastRevisionInfo')
507
402
request_handlers.register_lazy(
508
403
    'Branch.lock_write', 'bzrlib.smart.branch', 'SmartServerBranchRequestLockWrite')
509
 
request_handlers.register_lazy( 'Branch.revision_history',
510
 
    'bzrlib.smart.branch', 'SmartServerRequestRevisionHistory')
511
 
request_handlers.register_lazy( 'Branch.set_config_option',
512
 
    'bzrlib.smart.branch', 'SmartServerBranchRequestSetConfigOption')
513
 
request_handlers.register_lazy( 'Branch.set_last_revision',
514
 
    'bzrlib.smart.branch', 'SmartServerBranchRequestSetLastRevision')
 
404
request_handlers.register_lazy(
 
405
    'Branch.revision_history', 'bzrlib.smart.branch', 'SmartServerRequestRevisionHistory')
 
406
request_handlers.register_lazy(
 
407
    'Branch.set_last_revision', 'bzrlib.smart.branch', 'SmartServerBranchRequestSetLastRevision')
515
408
request_handlers.register_lazy(
516
409
    'Branch.set_last_revision_info', 'bzrlib.smart.branch',
517
410
    'SmartServerBranchRequestSetLastRevisionInfo')
519
412
    'Branch.set_last_revision_ex', 'bzrlib.smart.branch',
520
413
    'SmartServerBranchRequestSetLastRevisionEx')
521
414
request_handlers.register_lazy(
522
 
    'Branch.set_parent_location', 'bzrlib.smart.branch',
523
 
    'SmartServerBranchRequestSetParentLocation')
524
 
request_handlers.register_lazy(
525
415
    'Branch.unlock', 'bzrlib.smart.branch', 'SmartServerBranchRequestUnlock')
526
416
request_handlers.register_lazy(
527
417
    'BzrDir.cloning_metadir', 'bzrlib.smart.bzrdir',
542
432
    'BzrDir.find_repositoryV3', 'bzrlib.smart.bzrdir',
543
433
    'SmartServerRequestFindRepositoryV3')
544
434
request_handlers.register_lazy(
545
 
    'BzrDir.get_config_file', 'bzrlib.smart.bzrdir',
546
 
    'SmartServerBzrDirRequestConfigFile')
547
 
request_handlers.register_lazy(
548
435
    'BzrDirFormat.initialize', 'bzrlib.smart.bzrdir',
549
436
    'SmartServerRequestInitializeBzrDir')
550
437
request_handlers.register_lazy(
551
 
    'BzrDirFormat.initialize_ex_1.16', 'bzrlib.smart.bzrdir',
552
 
    'SmartServerRequestBzrDirInitializeEx')
553
 
request_handlers.register_lazy(
554
 
    'BzrDir.open', 'bzrlib.smart.bzrdir', 'SmartServerRequestOpenBzrDir')
555
 
request_handlers.register_lazy(
556
 
    'BzrDir.open_2.1', 'bzrlib.smart.bzrdir', 'SmartServerRequestOpenBzrDir_2_1')
557
 
request_handlers.register_lazy(
558
438
    'BzrDir.open_branch', 'bzrlib.smart.bzrdir',
559
439
    'SmartServerRequestOpenBranch')
560
440
request_handlers.register_lazy(
561
441
    'BzrDir.open_branchV2', 'bzrlib.smart.bzrdir',
562
442
    'SmartServerRequestOpenBranchV2')
563
443
request_handlers.register_lazy(
564
 
    'BzrDir.open_branchV3', 'bzrlib.smart.bzrdir',
565
 
    'SmartServerRequestOpenBranchV3')
566
 
request_handlers.register_lazy(
567
444
    'delete', 'bzrlib.smart.vfs', 'DeleteRequest')
568
445
request_handlers.register_lazy(
569
446
    'get', 'bzrlib.smart.vfs', 'GetRequest')
605
482
request_handlers.register_lazy(
606
483
    'Repository.insert_stream', 'bzrlib.smart.repository', 'SmartServerRepositoryInsertStream')
607
484
request_handlers.register_lazy(
608
 
    'Repository.insert_stream_1.19', 'bzrlib.smart.repository', 'SmartServerRepositoryInsertStream_1_19')
609
 
request_handlers.register_lazy(
610
 
    'Repository.insert_stream_locked', 'bzrlib.smart.repository', 'SmartServerRepositoryInsertStreamLocked')
611
 
request_handlers.register_lazy(
612
485
    'Repository.is_shared', 'bzrlib.smart.repository', 'SmartServerRepositoryIsShared')
613
486
request_handlers.register_lazy(
614
487
    'Repository.lock_write', 'bzrlib.smart.repository', 'SmartServerRepositoryLockWrite')
618
491
request_handlers.register_lazy(
619
492
    'Repository.unlock', 'bzrlib.smart.repository', 'SmartServerRepositoryUnlock')
620
493
request_handlers.register_lazy(
621
 
    'Repository.get_rev_id_for_revno', 'bzrlib.smart.repository',
622
 
    'SmartServerRepositoryGetRevIdForRevno')
623
 
request_handlers.register_lazy(
624
494
    'Repository.get_stream', 'bzrlib.smart.repository',
625
495
    'SmartServerRepositoryGetStream')
626
496
request_handlers.register_lazy(
627
 
    'Repository.get_stream_1.19', 'bzrlib.smart.repository',
628
 
    'SmartServerRepositoryGetStream_1_19')
629
 
request_handlers.register_lazy(
630
497
    'Repository.tarball', 'bzrlib.smart.repository',
631
498
    'SmartServerRepositoryTarball')
632
499
request_handlers.register_lazy(
635
502
    'stat', 'bzrlib.smart.vfs', 'StatRequest')
636
503
request_handlers.register_lazy(
637
504
    'Transport.is_readonly', 'bzrlib.smart.request', 'SmartServerIsReadonly')
 
505
request_handlers.register_lazy(
 
506
    'BzrDir.open', 'bzrlib.smart.bzrdir', 'SmartServerRequestOpenBzrDir')