~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/request.py

  • Committer: Matt Nordhoff
  • Date: 2009-04-04 02:50:01 UTC
  • mfrom: (4253 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4256.
  • Revision ID: mnordhoff@mattnordhoff.com-20090404025001-z1403k0tatmc8l91
Merge bzr.dev, fixing conflicts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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 threading
30
36
 
31
37
from bzrlib import (
32
38
    bzrdir,
33
39
    errors,
34
40
    registry,
35
41
    revision,
 
42
    trace,
36
43
    urlutils,
37
44
    )
38
45
from bzrlib.lazy_import import lazy_import
41
48
""")
42
49
 
43
50
 
 
51
jail_info = threading.local()
 
52
jail_info.transports = None
 
53
 
 
54
 
 
55
def _install_hook():
 
56
    bzrdir.BzrDir.hooks.install_named_hook(
 
57
        'pre_open', _pre_open_hook, 'checking server jail')
 
58
 
 
59
 
 
60
def _pre_open_hook(transport):
 
61
    allowed_transports = getattr(jail_info, 'transports', None)
 
62
    if allowed_transports is None:
 
63
        return
 
64
    abspath = transport.base
 
65
    for allowed_transport in allowed_transports:
 
66
        try:
 
67
            allowed_transport.relpath(abspath)
 
68
        except errors.PathNotChild:
 
69
            continue
 
70
        else:
 
71
            return
 
72
    raise errors.BzrError('jail break: %r' % (abspath,))
 
73
 
 
74
 
 
75
_install_hook()
 
76
 
 
77
 
44
78
class SmartServerRequest(object):
45
79
    """Base class for request handlers.
46
 
    
 
80
 
47
81
    To define a new request, subclass this class and override the `do` method
48
82
    (and if appropriate, `do_body` as well).  Request implementors should take
49
83
    care to call `translate_client_path` and `transport_from_client_path` as
78
112
 
79
113
    def do(self, *args):
80
114
        """Mandatory extension point for SmartServerRequest subclasses.
81
 
        
 
115
 
82
116
        Subclasses must implement this.
83
 
        
 
117
 
84
118
        This should return a SmartServerResponse if this command expects to
85
119
        receive no body.
86
120
        """
101
135
        """Called if the client sends a body with the request.
102
136
 
103
137
        The do() method is still called, and must have returned None.
104
 
        
 
138
 
105
139
        Must return a SmartServerResponse.
106
140
        """
107
141
        if body_bytes != '':
119
153
        body_bytes = ''.join(self._body_chunks)
120
154
        self._body_chunks = None
121
155
        return self.do_body(body_bytes)
122
 
    
 
156
 
 
157
    def setup_jail(self):
 
158
        jail_info.transports = [self._backing_transport]
 
159
 
 
160
    def teardown_jail(self):
 
161
        jail_info.transports = None
 
162
 
123
163
    def translate_client_path(self, client_path):
124
164
        """Translate a path received from a network client into a local
125
165
        relpath.
158
198
 
159
199
class SmartServerResponse(object):
160
200
    """A response to a client request.
161
 
    
 
201
 
162
202
    This base class should not be used. Instead use
163
203
    SuccessfulSmartServerResponse and FailedSmartServerResponse as appropriate.
164
204
    """
208
248
 
209
249
class SmartServerRequestHandler(object):
210
250
    """Protocol logic for smart server.
211
 
    
 
251
 
212
252
    This doesn't handle serialization at all, it just processes requests and
213
253
    creates responses.
214
254
    """
240
280
    def accept_body(self, bytes):
241
281
        """Accept body data."""
242
282
        self._run_handler_code(self._command.do_chunk, (bytes,), {})
243
 
        
 
283
 
244
284
    def end_of_body(self):
245
285
        """No more body data will be received."""
246
286
        self._run_handler_code(self._command.do_end, (), {})
276
316
        # XXX: most of this error conversion is VFS-related, and thus ought to
277
317
        # be in SmartServerVFSRequestHandler somewhere.
278
318
        try:
279
 
            return callable(*args, **kwargs)
280
 
        except errors.NoSuchFile, e:
281
 
            return FailedSmartServerResponse(('NoSuchFile', e.path))
282
 
        except errors.FileExists, e:
283
 
            return FailedSmartServerResponse(('FileExists', e.path))
284
 
        except errors.DirectoryNotEmpty, e:
285
 
            return FailedSmartServerResponse(('DirectoryNotEmpty', e.path))
286
 
        except errors.ShortReadvError, e:
287
 
            return FailedSmartServerResponse(('ShortReadvError',
288
 
                e.path, str(e.offset), str(e.length), str(e.actual)))
289
 
        except errors.UnstackableRepositoryFormat, e:
290
 
            return FailedSmartServerResponse(('UnstackableRepositoryFormat',
291
 
                str(e.format), e.url))
292
 
        except errors.UnstackableBranchFormat, e:
293
 
            return FailedSmartServerResponse(('UnstackableBranchFormat',
294
 
                str(e.format), e.url))
295
 
        except errors.NotStacked, e:
296
 
            return FailedSmartServerResponse(('NotStacked',))
297
 
        except UnicodeError, e:
298
 
            # If it is a DecodeError, than most likely we are starting
299
 
            # with a plain string
300
 
            str_or_unicode = e.object
301
 
            if isinstance(str_or_unicode, unicode):
302
 
                # XXX: UTF-8 might have \x01 (our protocol v1 and v2 seperator
303
 
                # byte) in it, so this encoding could cause broken responses.
304
 
                # Newer clients use protocol v3, so will be fine.
305
 
                val = 'u:' + str_or_unicode.encode('utf-8')
306
 
            else:
307
 
                val = 's:' + str_or_unicode.encode('base64')
308
 
            # This handles UnicodeEncodeError or UnicodeDecodeError
309
 
            return FailedSmartServerResponse((e.__class__.__name__,
310
 
                    e.encoding, val, str(e.start), str(e.end), e.reason))
311
 
        except errors.TransportNotPossible, e:
312
 
            if e.msg == "readonly transport":
313
 
                return FailedSmartServerResponse(('ReadOnlyError', ))
314
 
            else:
315
 
                raise
316
 
        except errors.ReadError, e:
317
 
            # cannot read the file
318
 
            return FailedSmartServerResponse(('ReadError', e.path))
319
 
        except errors.PermissionDenied, e:
320
 
            return FailedSmartServerResponse(
321
 
                ('PermissionDenied', e.path, e.extra))
 
319
            self._command.setup_jail()
 
320
            try:
 
321
                return callable(*args, **kwargs)
 
322
            finally:
 
323
                self._command.teardown_jail()
 
324
        except (KeyboardInterrupt, SystemExit):
 
325
            raise
 
326
        except Exception, err:
 
327
            err_struct = _translate_error(err)
 
328
            return FailedSmartServerResponse(err_struct)
322
329
 
323
330
    def headers_received(self, headers):
324
331
        # Just a no-op at the moment.
342
349
        pass
343
350
 
344
351
 
 
352
def _translate_error(err):
 
353
    if isinstance(err, errors.NoSuchFile):
 
354
        return ('NoSuchFile', err.path)
 
355
    elif isinstance(err, errors.FileExists):
 
356
        return ('FileExists', err.path)
 
357
    elif isinstance(err, errors.DirectoryNotEmpty):
 
358
        return ('DirectoryNotEmpty', err.path)
 
359
    elif isinstance(err, errors.ShortReadvError):
 
360
        return ('ShortReadvError', err.path, str(err.offset), str(err.length),
 
361
                str(err.actual))
 
362
    elif isinstance(err, errors.UnstackableRepositoryFormat):
 
363
        return (('UnstackableRepositoryFormat', str(err.format), err.url))
 
364
    elif isinstance(err, errors.UnstackableBranchFormat):
 
365
        return ('UnstackableBranchFormat', str(err.format), err.url)
 
366
    elif isinstance(err, errors.NotStacked):
 
367
        return ('NotStacked',)
 
368
    elif isinstance(err, UnicodeError):
 
369
        # If it is a DecodeError, than most likely we are starting
 
370
        # with a plain string
 
371
        str_or_unicode = err.object
 
372
        if isinstance(str_or_unicode, unicode):
 
373
            # XXX: UTF-8 might have \x01 (our protocol v1 and v2 seperator
 
374
            # byte) in it, so this encoding could cause broken responses.
 
375
            # Newer clients use protocol v3, so will be fine.
 
376
            val = 'u:' + str_or_unicode.encode('utf-8')
 
377
        else:
 
378
            val = 's:' + str_or_unicode.encode('base64')
 
379
        # This handles UnicodeEncodeError or UnicodeDecodeError
 
380
        return (err.__class__.__name__, err.encoding, val, str(err.start),
 
381
                str(err.end), err.reason)
 
382
    elif isinstance(err, errors.TransportNotPossible):
 
383
        if err.msg == "readonly transport":
 
384
            return ('ReadOnlyError', )
 
385
    elif isinstance(err, errors.ReadError):
 
386
        # cannot read the file
 
387
        return ('ReadError', err.path)
 
388
    elif isinstance(err, errors.PermissionDenied):
 
389
        return ('PermissionDenied', err.path, err.extra)
 
390
    elif isinstance(err, errors.TokenMismatch):
 
391
        return ('TokenMismatch', err.given_token, err.lock_token)
 
392
    elif isinstance(err, errors.LockContention):
 
393
        return ('LockContention', err.lock, err.msg)
 
394
    # Unserialisable error.  Log it, and return a generic error
 
395
    trace.log_exception_quietly()
 
396
    return ('error', str(err))
 
397
 
 
398
 
345
399
class HelloRequest(SmartServerRequest):
346
400
    """Answer a version request with the highest protocol version this server
347
401
    supports.
381
435
request_handlers.register_lazy(
382
436
    'append', 'bzrlib.smart.vfs', 'AppendRequest')
383
437
request_handlers.register_lazy(
384
 
    'Branch.get_config_file', 'bzrlib.smart.branch', 'SmartServerBranchGetConfigFile')
 
438
    'Branch.get_config_file', 'bzrlib.smart.branch',
 
439
    'SmartServerBranchGetConfigFile')
 
440
request_handlers.register_lazy(
 
441
    'Branch.get_parent', 'bzrlib.smart.branch', 'SmartServerBranchGetParent')
 
442
request_handlers.register_lazy(
 
443
    'Branch.get_tags_bytes', 'bzrlib.smart.branch',
 
444
    'SmartServerBranchGetTagsBytes')
385
445
request_handlers.register_lazy(
386
446
    'Branch.get_stacked_on_url', 'bzrlib.smart.branch', 'SmartServerBranchRequestGetStackedOnURL')
387
447
request_handlers.register_lazy(
388
448
    'Branch.last_revision_info', 'bzrlib.smart.branch', 'SmartServerBranchRequestLastRevisionInfo')
389
449
request_handlers.register_lazy(
390
450
    'Branch.lock_write', 'bzrlib.smart.branch', 'SmartServerBranchRequestLockWrite')
391
 
request_handlers.register_lazy(
392
 
    'Branch.revision_history', 'bzrlib.smart.branch', 'SmartServerRequestRevisionHistory')
393
 
request_handlers.register_lazy(
394
 
    'Branch.set_last_revision', 'bzrlib.smart.branch', 'SmartServerBranchRequestSetLastRevision')
 
451
request_handlers.register_lazy( 'Branch.revision_history',
 
452
    'bzrlib.smart.branch', 'SmartServerRequestRevisionHistory')
 
453
request_handlers.register_lazy( 'Branch.set_config_option',
 
454
    'bzrlib.smart.branch', 'SmartServerBranchRequestSetConfigOption')
 
455
request_handlers.register_lazy( 'Branch.set_last_revision',
 
456
    'bzrlib.smart.branch', 'SmartServerBranchRequestSetLastRevision')
395
457
request_handlers.register_lazy(
396
458
    'Branch.set_last_revision_info', 'bzrlib.smart.branch',
397
459
    'SmartServerBranchRequestSetLastRevisionInfo')
401
463
request_handlers.register_lazy(
402
464
    'Branch.unlock', 'bzrlib.smart.branch', 'SmartServerBranchRequestUnlock')
403
465
request_handlers.register_lazy(
404
 
    'BzrDir.create_repository', 'bzrlib.smart.bzrdir', 'SmartServerRequestCreateRepository')
405
 
request_handlers.register_lazy(
406
 
    'BzrDir.find_repository', 'bzrlib.smart.bzrdir', 'SmartServerRequestFindRepositoryV1')
407
 
request_handlers.register_lazy(
408
 
    'BzrDir.find_repositoryV2', 'bzrlib.smart.bzrdir', 'SmartServerRequestFindRepositoryV2')
409
 
request_handlers.register_lazy(
410
 
    'BzrDirFormat.initialize', 'bzrlib.smart.bzrdir', 'SmartServerRequestInitializeBzrDir')
411
 
request_handlers.register_lazy(
412
 
    'BzrDir.open_branch', 'bzrlib.smart.bzrdir', 'SmartServerRequestOpenBranch')
 
466
    'BzrDir.cloning_metadir', 'bzrlib.smart.bzrdir',
 
467
    'SmartServerBzrDirRequestCloningMetaDir')
 
468
request_handlers.register_lazy(
 
469
    'BzrDir.create_branch', 'bzrlib.smart.bzrdir',
 
470
    'SmartServerRequestCreateBranch')
 
471
request_handlers.register_lazy(
 
472
    'BzrDir.create_repository', 'bzrlib.smart.bzrdir',
 
473
    'SmartServerRequestCreateRepository')
 
474
request_handlers.register_lazy(
 
475
    'BzrDir.find_repository', 'bzrlib.smart.bzrdir',
 
476
    'SmartServerRequestFindRepositoryV1')
 
477
request_handlers.register_lazy(
 
478
    'BzrDir.find_repositoryV2', 'bzrlib.smart.bzrdir',
 
479
    'SmartServerRequestFindRepositoryV2')
 
480
request_handlers.register_lazy(
 
481
    'BzrDir.find_repositoryV3', 'bzrlib.smart.bzrdir',
 
482
    'SmartServerRequestFindRepositoryV3')
 
483
request_handlers.register_lazy(
 
484
    'BzrDirFormat.initialize', 'bzrlib.smart.bzrdir',
 
485
    'SmartServerRequestInitializeBzrDir')
 
486
request_handlers.register_lazy(
 
487
    'BzrDir.open_branch', 'bzrlib.smart.bzrdir',
 
488
    'SmartServerRequestOpenBranch')
 
489
request_handlers.register_lazy(
 
490
    'BzrDir.open_branchV2', 'bzrlib.smart.bzrdir',
 
491
    'SmartServerRequestOpenBranchV2')
413
492
request_handlers.register_lazy(
414
493
    'delete', 'bzrlib.smart.vfs', 'DeleteRequest')
415
494
request_handlers.register_lazy(
452
531
request_handlers.register_lazy(
453
532
    'Repository.insert_stream', 'bzrlib.smart.repository', 'SmartServerRepositoryInsertStream')
454
533
request_handlers.register_lazy(
 
534
    'Repository.insert_stream_locked', 'bzrlib.smart.repository', 'SmartServerRepositoryInsertStreamLocked')
 
535
request_handlers.register_lazy(
455
536
    'Repository.is_shared', 'bzrlib.smart.repository', 'SmartServerRepositoryIsShared')
456
537
request_handlers.register_lazy(
457
538
    'Repository.lock_write', 'bzrlib.smart.repository', 'SmartServerRepositoryLockWrite')
461
542
request_handlers.register_lazy(
462
543
    'Repository.unlock', 'bzrlib.smart.repository', 'SmartServerRepositoryUnlock')
463
544
request_handlers.register_lazy(
 
545
    'Repository.get_stream', 'bzrlib.smart.repository',
 
546
    'SmartServerRepositoryGetStream')
 
547
request_handlers.register_lazy(
464
548
    'Repository.tarball', 'bzrlib.smart.repository',
465
549
    'SmartServerRepositoryTarball')
466
550
request_handlers.register_lazy(