/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/smart/request.py

  • Committer: Robert Collins
  • Date: 2010-05-06 11:08:10 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506110810-h3j07fh5gmw54s25
Cleaner matcher matching revised unlocking protocol.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2006-2010 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., 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).
 
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).
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
 
29
34
import tempfile
 
35
import thread
 
36
import threading
30
37
 
31
38
from bzrlib import (
32
39
    bzrdir,
 
40
    debug,
33
41
    errors,
 
42
    osutils,
34
43
    registry,
35
44
    revision,
36
45
    trace,
42
51
""")
43
52
 
44
53
 
 
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
 
45
81
class SmartServerRequest(object):
46
82
    """Base class for request handlers.
47
83
 
53
89
    # XXX: rename this class to BaseSmartServerRequestHandler ?  A request
54
90
    # *handler* is a different concept to the request.
55
91
 
56
 
    def __init__(self, backing_transport, root_client_path='/'):
 
92
    def __init__(self, backing_transport, root_client_path='/', jail_root=None):
57
93
        """Constructor.
58
94
 
59
95
        :param backing_transport: the base transport to be used when performing
63
99
            from the client.  Clients will not be able to refer to paths above
64
100
            this root.  If root_client_path is None, then no translation will
65
101
            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.
66
104
        """
67
105
        self._backing_transport = backing_transport
 
106
        if jail_root is None:
 
107
            jail_root = backing_transport
 
108
        self._jail_root = jail_root
68
109
        if root_client_path is not None:
69
110
            if not root_client_path.startswith('/'):
70
111
                root_client_path = '/' + root_client_path
121
162
        self._body_chunks = None
122
163
        return self.do_body(body_bytes)
123
164
 
 
165
    def setup_jail(self):
 
166
        jail_info.transports = [self._jail_root]
 
167
 
 
168
    def teardown_jail(self):
 
169
        jail_info.transports = None
 
170
 
124
171
    def translate_client_path(self, client_path):
125
172
        """Translate a path received from a network client into a local
126
173
        relpath.
137
184
            return client_path
138
185
        if not client_path.startswith('/'):
139
186
            client_path = '/' + client_path
 
187
        if client_path + '/' == self._root_client_path:
 
188
            return '.'
140
189
        if client_path.startswith(self._root_client_path):
141
190
            path = client_path[len(self._root_client_path):]
142
191
            relpath = urlutils.joinpath('/', path)
143
192
            if not relpath.startswith('/'):
144
193
                raise ValueError(relpath)
145
 
            return '.' + relpath
 
194
            return urlutils.escape('.' + relpath)
146
195
        else:
147
196
            raise errors.PathNotChild(client_path, self._root_client_path)
148
197
 
224
273
    # TODO: Better way of representing the body for commands that take it,
225
274
    # and allow it to be streamed into the server.
226
275
 
227
 
    def __init__(self, backing_transport, commands, root_client_path):
 
276
    def __init__(self, backing_transport, commands, root_client_path,
 
277
        jail_root=None):
228
278
        """Constructor.
229
279
 
230
280
        :param backing_transport: a Transport to handle requests for.
234
284
        self._backing_transport = backing_transport
235
285
        self._root_client_path = root_client_path
236
286
        self._commands = commands
 
287
        if jail_root is None:
 
288
            jail_root = backing_transport
 
289
        self._jail_root = jail_root
237
290
        self.response = None
238
291
        self.finished_reading = False
239
292
        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))
240
314
 
241
315
    def accept_body(self, bytes):
242
316
        """Accept body data."""
 
317
        if self._command is None:
 
318
            # no active command object, so ignore the event.
 
319
            return
243
320
        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)
244
324
 
245
325
    def end_of_body(self):
246
326
        """No more body data will be received."""
247
327
        self._run_handler_code(self._command.do_end, (), {})
248
328
        # cannot read after this.
249
329
        self.finished_reading = 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, {})
 
330
        if 'hpss' in debug.debug_flags:
 
331
            self._trace('end of body', '', include_time=True)
259
332
 
260
333
    def _run_handler_code(self, callable, args, kwargs):
261
334
        """Run some handler specific code 'callable'.
277
350
        # XXX: most of this error conversion is VFS-related, and thus ought to
278
351
        # be in SmartServerVFSRequestHandler somewhere.
279
352
        try:
280
 
            return callable(*args, **kwargs)
 
353
            self._command.setup_jail()
 
354
            try:
 
355
                return callable(*args, **kwargs)
 
356
            finally:
 
357
                self._command.teardown_jail()
281
358
        except (KeyboardInterrupt, SystemExit):
282
359
            raise
283
360
        except Exception, err:
286
363
 
287
364
    def headers_received(self, headers):
288
365
        # Just a no-op at the moment.
289
 
        pass
 
366
        if 'hpss' in debug.debug_flags:
 
367
            self._trace('headers', repr(headers))
290
368
 
291
369
    def args_received(self, args):
292
370
        cmd = args[0]
294
372
        try:
295
373
            command = self._commands.get(cmd)
296
374
        except LookupError:
 
375
            if 'hpss' in debug.debug_flags:
 
376
                self._trace('hpss unknown request', 
 
377
                            cmd, repr(args)[1:-1])
297
378
            raise errors.UnknownSmartMethod(cmd)
298
 
        self._command = command(self._backing_transport)
 
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)
299
389
        self._run_handler_code(self._command.execute, args, {})
300
390
 
301
391
    def end_received(self):
 
392
        if self._command is None:
 
393
            # no active command object, so ignore the event.
 
394
            return
302
395
        self._run_handler_code(self._command.do_end, (), {})
 
396
        if 'hpss' in debug.debug_flags:
 
397
            self._trace('end', '', include_time=True)
303
398
 
304
399
    def post_body_error_received(self, error_args):
305
400
        # Just a no-op at the moment.
313
408
        return ('FileExists', err.path)
314
409
    elif isinstance(err, errors.DirectoryNotEmpty):
315
410
        return ('DirectoryNotEmpty', err.path)
 
411
    elif isinstance(err, errors.IncompatibleRepositories):
 
412
        return ('IncompatibleRepositories', str(err.source), str(err.target),
 
413
            str(err.details))
316
414
    elif isinstance(err, errors.ShortReadvError):
317
415
        return ('ShortReadvError', err.path, str(err.offset), str(err.length),
318
416
                str(err.actual))
344
442
        return ('ReadError', err.path)
345
443
    elif isinstance(err, errors.PermissionDenied):
346
444
        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',)
347
449
    # Unserialisable error.  Log it, and return a generic error
348
450
    trace.log_exception_quietly()
349
451
    return ('error', str(err))
396
498
    'Branch.get_tags_bytes', 'bzrlib.smart.branch',
397
499
    'SmartServerBranchGetTagsBytes')
398
500
request_handlers.register_lazy(
 
501
    'Branch.set_tags_bytes', 'bzrlib.smart.branch',
 
502
    'SmartServerBranchSetTagsBytes')
 
503
request_handlers.register_lazy(
399
504
    'Branch.get_stacked_on_url', 'bzrlib.smart.branch', 'SmartServerBranchRequestGetStackedOnURL')
400
505
request_handlers.register_lazy(
401
506
    'Branch.last_revision_info', 'bzrlib.smart.branch', 'SmartServerBranchRequestLastRevisionInfo')
402
507
request_handlers.register_lazy(
403
508
    'Branch.lock_write', 'bzrlib.smart.branch', 'SmartServerBranchRequestLockWrite')
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')
 
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')
408
515
request_handlers.register_lazy(
409
516
    'Branch.set_last_revision_info', 'bzrlib.smart.branch',
410
517
    'SmartServerBranchRequestSetLastRevisionInfo')
412
519
    'Branch.set_last_revision_ex', 'bzrlib.smart.branch',
413
520
    'SmartServerBranchRequestSetLastRevisionEx')
414
521
request_handlers.register_lazy(
 
522
    'Branch.set_parent_location', 'bzrlib.smart.branch',
 
523
    'SmartServerBranchRequestSetParentLocation')
 
524
request_handlers.register_lazy(
415
525
    'Branch.unlock', 'bzrlib.smart.branch', 'SmartServerBranchRequestUnlock')
416
526
request_handlers.register_lazy(
417
527
    'BzrDir.cloning_metadir', 'bzrlib.smart.bzrdir',
432
542
    'BzrDir.find_repositoryV3', 'bzrlib.smart.bzrdir',
433
543
    'SmartServerRequestFindRepositoryV3')
434
544
request_handlers.register_lazy(
 
545
    'BzrDir.get_config_file', 'bzrlib.smart.bzrdir',
 
546
    'SmartServerBzrDirRequestConfigFile')
 
547
request_handlers.register_lazy(
435
548
    'BzrDirFormat.initialize', 'bzrlib.smart.bzrdir',
436
549
    'SmartServerRequestInitializeBzrDir')
437
550
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(
438
558
    'BzrDir.open_branch', 'bzrlib.smart.bzrdir',
439
559
    'SmartServerRequestOpenBranch')
440
560
request_handlers.register_lazy(
441
561
    'BzrDir.open_branchV2', 'bzrlib.smart.bzrdir',
442
562
    'SmartServerRequestOpenBranchV2')
443
563
request_handlers.register_lazy(
 
564
    'BzrDir.open_branchV3', 'bzrlib.smart.bzrdir',
 
565
    'SmartServerRequestOpenBranchV3')
 
566
request_handlers.register_lazy(
444
567
    'delete', 'bzrlib.smart.vfs', 'DeleteRequest')
445
568
request_handlers.register_lazy(
446
569
    'get', 'bzrlib.smart.vfs', 'GetRequest')
482
605
request_handlers.register_lazy(
483
606
    'Repository.insert_stream', 'bzrlib.smart.repository', 'SmartServerRepositoryInsertStream')
484
607
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(
485
612
    'Repository.is_shared', 'bzrlib.smart.repository', 'SmartServerRepositoryIsShared')
486
613
request_handlers.register_lazy(
487
614
    'Repository.lock_write', 'bzrlib.smart.repository', 'SmartServerRepositoryLockWrite')
491
618
request_handlers.register_lazy(
492
619
    'Repository.unlock', 'bzrlib.smart.repository', 'SmartServerRepositoryUnlock')
493
620
request_handlers.register_lazy(
 
621
    'Repository.get_rev_id_for_revno', 'bzrlib.smart.repository',
 
622
    'SmartServerRepositoryGetRevIdForRevno')
 
623
request_handlers.register_lazy(
494
624
    'Repository.get_stream', 'bzrlib.smart.repository',
495
625
    'SmartServerRepositoryGetStream')
496
626
request_handlers.register_lazy(
 
627
    'Repository.get_stream_1.19', 'bzrlib.smart.repository',
 
628
    'SmartServerRepositoryGetStream_1_19')
 
629
request_handlers.register_lazy(
497
630
    'Repository.tarball', 'bzrlib.smart.repository',
498
631
    'SmartServerRepositoryTarball')
499
632
request_handlers.register_lazy(
502
635
    'stat', 'bzrlib.smart.vfs', 'StatRequest')
503
636
request_handlers.register_lazy(
504
637
    'Transport.is_readonly', 'bzrlib.smart.request', 'SmartServerIsReadonly')
505
 
request_handlers.register_lazy(
506
 
    'BzrDir.open', 'bzrlib.smart.bzrdir', 'SmartServerRequestOpenBzrDir')