/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/transport/sftp.py

  • Committer: Marius Kruger
  • Date: 2010-07-10 21:28:56 UTC
  • mto: (5384.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5385.
  • Revision ID: marius.kruger@enerweb.co.za-20100710212856-uq4ji3go0u5se7hx
* Update documentation
* add NEWS

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011, 2016, 2017 Canonical Ltd
 
1
# Copyright (C) 2005-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
16
16
 
17
17
"""Implementation of Transport over SFTP, using paramiko."""
18
18
 
19
 
from __future__ import absolute_import
20
 
 
21
19
# TODO: Remove the transport-based lock_read and lock_write methods.  They'll
22
20
# then raise TransportNotPossible, which will break remote access to any
23
21
# formats which rely on OS-level locks.  That should be fine as those formats
33
31
import stat
34
32
import sys
35
33
import time
 
34
import urllib
 
35
import urlparse
36
36
import warnings
37
37
 
38
 
from .. import (
 
38
from bzrlib import (
39
39
    config,
40
40
    debug,
41
41
    errors,
42
42
    urlutils,
43
43
    )
44
 
from ..errors import (FileExists,
45
 
                      NoSuchFile,
46
 
                      TransportError,
47
 
                      LockError,
48
 
                      PathError,
49
 
                      ParamikoNotPresent,
50
 
                      )
51
 
from ..osutils import fancy_rename
52
 
from ..sixish import (
53
 
    zip,
54
 
    )
55
 
from ..trace import mutter, warning
56
 
from ..transport import (
 
44
from bzrlib.errors import (FileExists,
 
45
                           NoSuchFile, PathNotChild,
 
46
                           TransportError,
 
47
                           LockError,
 
48
                           PathError,
 
49
                           ParamikoNotPresent,
 
50
                           )
 
51
from bzrlib.osutils import pathjoin, fancy_rename, getcwd
 
52
from bzrlib.symbol_versioning import (
 
53
        deprecated_function,
 
54
        )
 
55
from bzrlib.trace import mutter, warning
 
56
from bzrlib.transport import (
57
57
    FileFileStream,
58
58
    _file_streams,
 
59
    local,
 
60
    Server,
59
61
    ssh,
60
62
    ConnectedTransport,
61
63
    )
69
71
# /var/lib/python-support/python2.5/paramiko/message.py:226: DeprecationWarning: integer argument expected, got float
70
72
#  self.packet.write(struct.pack('>I', n))
71
73
warnings.filterwarnings('ignore',
72
 
                        'integer argument expected, got float',
73
 
                        category=DeprecationWarning,
74
 
                        module='paramiko.message')
 
74
        'integer argument expected, got float',
 
75
        category=DeprecationWarning,
 
76
        module='paramiko.message')
75
77
 
76
78
try:
77
79
    import paramiko
78
 
except ImportError as e:
 
80
except ImportError, e:
79
81
    raise ParamikoNotPresent(e)
80
82
else:
81
83
    from paramiko.sftp import (SFTP_FLAG_WRITE, SFTP_FLAG_CREATE,
82
84
                               SFTP_FLAG_EXCL, SFTP_FLAG_TRUNC,
83
 
                               CMD_HANDLE, CMD_OPEN)
 
85
                               SFTP_OK, CMD_HANDLE, CMD_OPEN)
84
86
    from paramiko.sftp_attr import SFTPAttributes
85
87
    from paramiko.sftp_file import SFTPFile
86
88
 
87
89
 
88
 
# GZ 2017-05-25: Some dark hackery to monkeypatch out issues with paramiko's
89
 
# Python 3 compatibility code. Replace broken b() and asbytes() code.
90
 
try:
91
 
    from paramiko.py3compat import b as _bad
92
 
    from paramiko.common import asbytes as _bad_asbytes
93
 
except ImportError:
94
 
    pass
95
 
else:
96
 
    def _b_for_broken_paramiko(s, encoding='utf8'):
97
 
        """Hacked b() that does not raise TypeError."""
98
 
        # https://github.com/paramiko/paramiko/issues/967
99
 
        if not isinstance(s, bytes):
100
 
            encode = getattr(s, 'encode', None)
101
 
            if encode is not None:
102
 
                return encode(encoding)
103
 
            # Would like to pass buffer objects along, but have to realise.
104
 
            tobytes = getattr(s, 'tobytes', None)
105
 
            if tobytes is not None:
106
 
                return tobytes()
107
 
        return s
108
 
 
109
 
    def _asbytes_for_broken_paramiko(s):
110
 
        """Hacked asbytes() that does not raise Exception."""
111
 
        # https://github.com/paramiko/paramiko/issues/968
112
 
        if not isinstance(s, bytes):
113
 
            encode = getattr(s, 'encode', None)
114
 
            if encode is not None:
115
 
                return encode('utf8')
116
 
            asbytes = getattr(s, 'asbytes', None)
117
 
            if asbytes is not None:
118
 
                return asbytes()
119
 
        return s
120
 
 
121
 
    _bad.__code__ = _b_for_broken_paramiko.__code__
122
 
    _bad_asbytes.__code__ = _asbytes_for_broken_paramiko.__code__
 
90
_paramiko_version = getattr(paramiko, '__version_info__', (0, 0, 0))
 
91
# don't use prefetch unless paramiko version >= 1.5.5 (there were bugs earlier)
 
92
_default_do_prefetch = (_paramiko_version >= (1, 5, 5))
123
93
 
124
94
 
125
95
class SFTPLock(object):
144
114
        except FileExists:
145
115
            raise LockError('File %r already locked' % (self.path,))
146
116
 
 
117
    def __del__(self):
 
118
        """Should this warn, or actually try to cleanup?"""
 
119
        if self.lock_file:
 
120
            warning("SFTPLock %r not explicitly unlocked" % (self.path,))
 
121
            self.unlock()
 
122
 
147
123
    def unlock(self):
148
124
        if not self.lock_file:
149
125
            return
193
169
        # as possible, so we don't issues requests <32kB
194
170
        sorted_offsets = sorted(self.original_offsets)
195
171
        coalesced = list(ConnectedTransport._coalesce_offsets(sorted_offsets,
196
 
                                                              limit=0, fudge_factor=0))
 
172
                                                        limit=0, fudge_factor=0))
197
173
        requests = []
198
174
        for c_offset in coalesced:
199
175
            start = c_offset.start
207
183
                start += next_size
208
184
        if 'sftp' in debug.debug_flags:
209
185
            mutter('SFTP.readv(%s) %s offsets => %s coalesced => %s requests',
210
 
                   self.relpath, len(sorted_offsets), len(coalesced),
211
 
                   len(requests))
 
186
                self.relpath, len(sorted_offsets), len(coalesced),
 
187
                len(requests))
212
188
        return requests
213
189
 
214
190
    def request_and_yield_offsets(self, fp):
220
196
        """
221
197
        requests = self._get_requests()
222
198
        offset_iter = iter(self.original_offsets)
223
 
        cur_offset, cur_size = next(offset_iter)
 
199
        cur_offset, cur_size = offset_iter.next()
224
200
        # paramiko .readv() yields strings that are in the order of the requests
225
201
        # So we track the current request to know where the next data is
226
202
        # being returned from.
237
213
        # short readv.
238
214
        data_stream = itertools.chain(fp.readv(requests),
239
215
                                      itertools.repeat(None))
240
 
        for (start, length), data in zip(requests, data_stream):
 
216
        for (start, length), data in itertools.izip(requests, data_stream):
241
217
            if data is None:
242
218
                if cur_coalesced is not None:
243
219
                    raise errors.ShortReadvError(self.relpath,
244
 
                                                 start, length, len(data))
 
220
                        start, length, len(data))
245
221
            if len(data) != length:
246
222
                raise errors.ShortReadvError(self.relpath,
247
 
                                             start, length, len(data))
 
223
                    start, length, len(data))
248
224
            self._report_activity(length, 'read')
249
225
            if last_end is None:
250
226
                # This is the first request, just buffer it
262
238
                if buffered_len > 0:
263
239
                    # We haven't consumed the buffer so far, so put it into
264
240
                    # data_chunks, and continue.
265
 
                    buffered = b''.join(buffered_data)
 
241
                    buffered = ''.join(buffered_data)
266
242
                    data_chunks.append((input_start, buffered))
267
243
                input_start = start
268
244
                buffered_data = [data]
273
249
                # into a single string. We also have the nice property that
274
250
                # when there is only one string ''.join([x]) == x, so there is
275
251
                # no data copying.
276
 
                buffered = b''.join(buffered_data)
 
252
                buffered = ''.join(buffered_data)
277
253
                # Clean out buffered data so that we keep memory
278
254
                # consumption low
279
255
                del buffered_data[:]
294
270
                    input_start += cur_size
295
271
                    # Yield the requested data
296
272
                    yield cur_offset, cur_data
297
 
                    try:
298
 
                        cur_offset, cur_size = next(offset_iter)
299
 
                    except StopIteration:
300
 
                        return
 
273
                    cur_offset, cur_size = offset_iter.next()
301
274
                # at this point, we've consumed as much of buffered as we can,
302
275
                # so break off the portion that we consumed
303
276
                if buffered_offset == len(buffered_data):
308
281
                    buffered = buffered[buffered_offset:]
309
282
                    buffered_data = [buffered]
310
283
                    buffered_len = len(buffered)
311
 
        # now that the data stream is done, close the handle
312
 
        fp.close()
313
284
        if buffered_len:
314
 
            buffered = b''.join(buffered_data)
 
285
            buffered = ''.join(buffered_data)
315
286
            del buffered_data[:]
316
287
            data_chunks.append((input_start, buffered))
317
288
        if data_chunks:
318
289
            if 'sftp' in debug.debug_flags:
319
290
                mutter('SFTP readv left with %d out-of-order bytes',
320
 
                       sum(len(x[1]) for x in data_chunks))
 
291
                    sum(map(lambda x: len(x[1]), data_chunks)))
321
292
            # We've processed all the readv data, at this point, anything we
322
293
            # couldn't process is in data_chunks. This doesn't happen often, so
323
294
            # this code path isn't optimized
343
314
                    data = ''
344
315
                if len(data) != cur_size:
345
316
                    raise AssertionError('We must have miscalulated.'
346
 
                                         ' We expected %d bytes, but only found %d'
347
 
                                         % (cur_size, len(data)))
 
317
                        ' We expected %d bytes, but only found %d'
 
318
                        % (cur_size, len(data)))
348
319
                yield cur_offset, data
349
 
                try:
350
 
                    cur_offset, cur_size = next(offset_iter)
351
 
                except StopIteration:
352
 
                    return
 
320
                cur_offset, cur_size = offset_iter.next()
353
321
 
354
322
 
355
323
class SFTPTransport(ConnectedTransport):
356
324
    """Transport implementation for SFTP access."""
357
325
 
 
326
    _do_prefetch = _default_do_prefetch
358
327
    # TODO: jam 20060717 Conceivably these could be configurable, either
359
328
    #       by auto-tuning at run-time, or by a configuration (per host??)
360
329
    #       but the performance curve is pretty flat, so just going with
372
341
    # up the request itself, rather than us having to worry about it
373
342
    _max_request_size = 32768
374
343
 
 
344
    def __init__(self, base, _from_transport=None):
 
345
        super(SFTPTransport, self).__init__(base,
 
346
                                            _from_transport=_from_transport)
 
347
 
375
348
    def _remote_path(self, relpath):
376
349
        """Return the path to be passed along the sftp protocol for relpath.
377
350
 
378
351
        :param relpath: is a urlencoded string.
379
352
        """
380
 
        remote_path = self._parsed_url.clone(relpath).path
 
353
        relative = urlutils.unescape(relpath).encode('utf-8')
 
354
        remote_path = self._combine_paths(self._path, relative)
381
355
        # the initial slash should be removed from the path, and treated as a
382
356
        # homedir relative path (the path begins with a double slash if it is
383
357
        # absolute).  see draft-ietf-secsh-scp-sftp-ssh-uri-03.txt
402
376
        in base url at transport creation time.
403
377
        """
404
378
        if credentials is None:
405
 
            password = self._parsed_url.password
 
379
            password = self._password
406
380
        else:
407
381
            password = credentials
408
382
 
409
383
        vendor = ssh._get_ssh_vendor()
410
 
        user = self._parsed_url.user
 
384
        user = self._user
411
385
        if user is None:
412
386
            auth = config.AuthenticationConfig()
413
 
            user = auth.get_user('ssh', self._parsed_url.host,
414
 
                                 self._parsed_url.port)
415
 
        connection = vendor.connect_sftp(self._parsed_url.user, password,
416
 
                                         self._parsed_url.host, self._parsed_url.port)
 
387
            user = auth.get_user('ssh', self._host, self._port)
 
388
        connection = vendor.connect_sftp(self._user, password,
 
389
                                         self._host, self._port)
417
390
        return connection, (user, password)
418
391
 
419
 
    def disconnect(self):
420
 
        connection = self._get_connection()
421
 
        if connection is not None:
422
 
            connection.close()
423
 
 
424
392
    def _get_sftp(self):
425
393
        """Ensures that a connection is established"""
426
394
        connection = self._get_connection()
448
416
        :param relpath: The relative path to the file
449
417
        """
450
418
        try:
 
419
            # FIXME: by returning the file directly, we don't pass this
 
420
            # through to report_activity.  We could try wrapping the object
 
421
            # before it's returned.  For readv and get_bytes it's handled in
 
422
            # the higher-level function.
 
423
            # -- mbp 20090126
451
424
            path = self._remote_path(relpath)
452
425
            f = self._get_sftp().file(path, mode='rb')
453
 
            size = f.stat().st_size
454
 
            if getattr(f, 'prefetch', None) is not None:
455
 
                f.prefetch(size)
 
426
            if self._do_prefetch and (getattr(f, 'prefetch', None) is not None):
 
427
                f.prefetch()
456
428
            return f
457
 
        except (IOError, paramiko.SSHException) as e:
 
429
        except (IOError, paramiko.SSHException), e:
458
430
            self._translate_io_exception(e, path, ': error retrieving',
459
 
                                         failure_exc=errors.ReadError)
 
431
                failure_exc=errors.ReadError)
460
432
 
461
433
    def get_bytes(self, relpath):
462
434
        # reimplement this here so that we can report how many bytes came back
463
 
        with self.get(relpath) as f:
 
435
        f = self.get(relpath)
 
436
        try:
464
437
            bytes = f.read()
465
438
            self._report_activity(len(bytes), 'read')
466
439
            return bytes
 
440
        finally:
 
441
            f.close()
467
442
 
468
443
    def _readv(self, relpath, offsets):
469
444
        """See Transport.readv()"""
482
457
            if 'sftp' in debug.debug_flags:
483
458
                mutter('seek and read %s offsets', len(offsets))
484
459
            return self._seek_and_read(fp, offsets, relpath)
485
 
        except (IOError, paramiko.SSHException) as e:
 
460
        except (IOError, paramiko.SSHException), e:
486
461
            self._translate_io_exception(e, path, ': error retrieving')
487
462
 
488
463
    def recommended_page_size(self):
517
492
    def _put(self, abspath, f, mode=None):
518
493
        """Helper function so both put() and copy_abspaths can reuse the code"""
519
494
        tmp_abspath = '%s.tmp.%.9f.%d.%d' % (abspath, time.time(),
520
 
                                             os.getpid(), random.randint(0, 0x7FFFFFFF))
 
495
                        os.getpid(), random.randint(0,0x7FFFFFFF))
521
496
        fout = self._sftp_open_exclusive(tmp_abspath, mode=mode)
522
497
        closed = False
523
498
        try:
524
499
            try:
525
500
                fout.set_pipelined(True)
526
501
                length = self._pump(f, fout)
527
 
            except (IOError, paramiko.SSHException) as e:
 
502
            except (IOError, paramiko.SSHException), e:
528
503
                self._translate_io_exception(e, tmp_abspath)
529
504
            # XXX: This doesn't truly help like we would like it to.
530
505
            #      The problem is that openssh strips sticky bits. So while we
545
520
            closed = True
546
521
            self._rename_and_overwrite(tmp_abspath, abspath)
547
522
            return length
548
 
        except Exception as e:
 
523
        except Exception, e:
549
524
            # If we fail, try to clean up the temporary file
550
525
            # before we throw the exception
551
526
            # but don't let another exception mess things up
580
555
                    fout = self._get_sftp().file(abspath, mode='wb')
581
556
                    fout.set_pipelined(True)
582
557
                    writer(fout)
583
 
                except (paramiko.SSHException, IOError) as e:
 
558
                except (paramiko.SSHException, IOError), e:
584
559
                    self._translate_io_exception(e, abspath,
585
560
                                                 ': unable to open')
586
561
 
631
606
                                    create_parent_dir=create_parent_dir,
632
607
                                    dir_mode=dir_mode)
633
608
 
634
 
    def put_bytes_non_atomic(self, relpath, raw_bytes, mode=None,
 
609
    def put_bytes_non_atomic(self, relpath, bytes, mode=None,
635
610
                             create_parent_dir=False,
636
611
                             dir_mode=None):
637
 
        if not isinstance(raw_bytes, bytes):
638
 
            raise TypeError(
639
 
                'raw_bytes must be a plain string, not %s' % type(raw_bytes))
640
 
 
641
612
        def writer(fout):
642
 
            fout.write(raw_bytes)
 
613
            fout.write(bytes)
643
614
        self._put_non_atomic_helper(relpath, writer, mode=mode,
644
615
                                    create_parent_dir=create_parent_dir,
645
616
                                    dir_mode=dir_mode)
653
624
            st = self.stat(relpath)
654
625
            if stat.S_ISDIR(st.st_mode):
655
626
                for i, basename in enumerate(self.list_dir(relpath)):
656
 
                    queue.insert(i, relpath + '/' + basename)
 
627
                    queue.insert(i, relpath+'/'+basename)
657
628
            else:
658
629
                yield relpath
659
630
 
660
631
    def _mkdir(self, abspath, mode=None):
661
632
        if mode is None:
662
 
            local_mode = 0o777
 
633
            local_mode = 0777
663
634
        else:
664
635
            local_mode = mode
665
636
        try:
673
644
                # the sgid bit is set, report a warning to the user
674
645
                # with the umask fix.
675
646
                stat = self._get_sftp().lstat(abspath)
676
 
                mode = mode & 0o777  # can't set special bits anyway
677
 
                if mode != stat.st_mode & 0o777:
678
 
                    if stat.st_mode & 0o6000:
 
647
                mode = mode & 0777 # can't set special bits anyway
 
648
                if mode != stat.st_mode & 0777:
 
649
                    if stat.st_mode & 06000:
679
650
                        warning('About to chmod %s over sftp, which will result'
680
651
                                ' in its suid or sgid bits being cleared.  If'
681
652
                                ' you want to preserve those bits, change your '
682
653
                                ' environment on the server to use umask 0%03o.'
683
 
                                % (abspath, 0o777 - mode))
 
654
                                % (abspath, 0777 - mode))
684
655
                    self._get_sftp().chmod(abspath, mode=mode)
685
 
        except (paramiko.SSHException, IOError) as e:
 
656
        except (paramiko.SSHException, IOError), e:
686
657
            self._translate_io_exception(e, abspath, ': unable to mkdir',
687
 
                                         failure_exc=FileExists)
 
658
                failure_exc=FileExists)
688
659
 
689
660
    def mkdir(self, relpath, mode=None):
690
661
        """Create a directory at the given path."""
697
668
        # api more than once per write_group at the moment so
698
669
        # it is a tolerable overhead. Better would be to truncate
699
670
        # the file after opening. RBC 20070805
700
 
        self.put_bytes_non_atomic(relpath, b"", mode)
 
671
        self.put_bytes_non_atomic(relpath, "", mode)
701
672
        abspath = self._remote_path(relpath)
702
673
        # TODO: jam 20060816 paramiko doesn't publicly expose a way to
703
674
        #       set the file mode at create time. If it does, use it.
706
677
        try:
707
678
            handle = self._get_sftp().file(abspath, mode='wb')
708
679
            handle.set_pipelined(True)
709
 
        except (paramiko.SSHException, IOError) as e:
 
680
        except (paramiko.SSHException, IOError), e:
710
681
            self._translate_io_exception(e, abspath,
711
682
                                         ': unable to open')
712
683
        _file_streams[self.abspath(relpath)] = handle
730
701
        self._translate_error(e, path, raise_generic=False)
731
702
        if getattr(e, 'args', None) is not None:
732
703
            if (e.args == ('No such file or directory',) or
733
 
                    e.args == ('No such file',)):
 
704
                e.args == ('No such file',)):
734
705
                raise NoSuchFile(path, str(e) + more_info)
735
706
            if (e.args == ('mkdir failed',) or
736
 
                    e.args[0].startswith('syserr: File exists')):
 
707
                e.args[0].startswith('syserr: File exists')):
737
708
                raise FileExists(path, str(e) + more_info)
738
709
            # strange but true, for the paramiko server.
739
710
            if (e.args == ('Failure',)):
742
713
            # '/srv/bazaar.launchpad.net/blah...: '
743
714
            # [Errno 39] Directory not empty',)
744
715
            if (e.args[0].startswith('Directory not empty: ')
745
 
                    or getattr(e, 'errno', None) == errno.ENOTEMPTY):
 
716
                or getattr(e, 'errno', None) == errno.ENOTEMPTY):
746
717
                raise errors.DirectoryNotEmpty(path, str(e))
747
 
            if e.args == ('Operation unsupported',):
748
 
                raise errors.TransportNotPossible()
749
718
            mutter('Raising exception with args %s', e.args)
750
719
        if getattr(e, 'errno', None) is not None:
751
720
            mutter('Raising exception with errno %s', e.errno)
764
733
            result = fout.tell()
765
734
            self._pump(f, fout)
766
735
            return result
767
 
        except (IOError, paramiko.SSHException) as e:
 
736
        except (IOError, paramiko.SSHException), e:
768
737
            self._translate_io_exception(e, relpath, ': unable to append')
769
738
 
770
739
    def rename(self, rel_from, rel_to):
771
740
        """Rename without special overwriting"""
772
741
        try:
773
742
            self._get_sftp().rename(self._remote_path(rel_from),
774
 
                                    self._remote_path(rel_to))
775
 
        except (IOError, paramiko.SSHException) as e:
 
743
                              self._remote_path(rel_to))
 
744
        except (IOError, paramiko.SSHException), e:
776
745
            self._translate_io_exception(e, rel_from,
777
 
                                         ': unable to rename to %r' % (rel_to))
 
746
                    ': unable to rename to %r' % (rel_to))
778
747
 
779
748
    def _rename_and_overwrite(self, abs_from, abs_to):
780
749
        """Do a fancy rename on the remote server.
786
755
            fancy_rename(abs_from, abs_to,
787
756
                         rename_func=sftp.rename,
788
757
                         unlink_func=sftp.remove)
789
 
        except (IOError, paramiko.SSHException) as e:
 
758
        except (IOError, paramiko.SSHException), e:
790
759
            self._translate_io_exception(e, abs_from,
791
760
                                         ': unable to rename to %r' % (abs_to))
792
761
 
801
770
        path = self._remote_path(relpath)
802
771
        try:
803
772
            self._get_sftp().remove(path)
804
 
        except (IOError, paramiko.SSHException) as e:
 
773
        except (IOError, paramiko.SSHException), e:
805
774
            self._translate_io_exception(e, path, ': unable to delete')
806
775
 
807
776
    def external_url(self):
808
 
        """See breezy.transport.Transport.external_url."""
 
777
        """See bzrlib.transport.Transport.external_url."""
809
778
        # the external path for SFTP is the base
810
779
        return self.base
811
780
 
825
794
        try:
826
795
            entries = self._get_sftp().listdir(path)
827
796
            self._report_activity(sum(map(len, entries)), 'read')
828
 
        except (IOError, paramiko.SSHException) as e:
 
797
        except (IOError, paramiko.SSHException), e:
829
798
            self._translate_io_exception(e, path, ': failed to list_dir')
830
799
        return [urlutils.escape(entry) for entry in entries]
831
800
 
834
803
        path = self._remote_path(relpath)
835
804
        try:
836
805
            return self._get_sftp().rmdir(path)
837
 
        except (IOError, paramiko.SSHException) as e:
 
806
        except (IOError, paramiko.SSHException), e:
838
807
            self._translate_io_exception(e, path, ': failed to rmdir')
839
808
 
840
809
    def stat(self, relpath):
842
811
        path = self._remote_path(relpath)
843
812
        try:
844
813
            return self._get_sftp().lstat(path)
845
 
        except (IOError, paramiko.SSHException) as e:
 
814
        except (IOError, paramiko.SSHException), e:
846
815
            self._translate_io_exception(e, path, ': unable to stat')
847
816
 
848
817
    def readlink(self, relpath):
849
818
        """See Transport.readlink."""
850
819
        path = self._remote_path(relpath)
851
820
        try:
852
 
            return self._get_sftp().readlink(self._remote_path(path))
853
 
        except (IOError, paramiko.SSHException) as e:
 
821
            return self._get_sftp().readlink(path)
 
822
        except (IOError, paramiko.SSHException), e:
854
823
            self._translate_io_exception(e, path, ': unable to readlink')
855
824
 
856
825
    def symlink(self, source, link_name):
857
826
        """See Transport.symlink."""
858
827
        try:
859
828
            conn = self._get_sftp()
860
 
            sftp_retval = conn.symlink(source, self._remote_path(link_name))
861
 
        except (IOError, paramiko.SSHException) as e:
 
829
            sftp_retval = conn.symlink(source, link_name)
 
830
            if SFTP_OK != sftp_retval:
 
831
                raise TransportError(
 
832
                    '%r: unable to create symlink to %r' % (link_name, source),
 
833
                    sftp_retval
 
834
                )
 
835
        except (IOError, paramiko.SSHException), e:
862
836
            self._translate_io_exception(e, link_name,
863
837
                                         ': unable to create symlink to %r' % (source))
864
838
 
871
845
        class BogusLock(object):
872
846
            def __init__(self, path):
873
847
                self.path = path
874
 
 
875
848
            def unlock(self):
876
849
                pass
877
 
 
878
 
            def __exit__(self, exc_type, exc_val, exc_tb):
879
 
                return False
880
 
 
881
 
            def __enter__(self):
882
 
                pass
883
850
        return BogusLock(relpath)
884
851
 
885
852
    def lock_write(self, relpath):
919
886
        if mode is not None:
920
887
            attr.st_mode = mode
921
888
        omode = (SFTP_FLAG_WRITE | SFTP_FLAG_CREATE
922
 
                 | SFTP_FLAG_TRUNC | SFTP_FLAG_EXCL)
 
889
                | SFTP_FLAG_TRUNC | SFTP_FLAG_EXCL)
923
890
        try:
924
891
            t, msg = self._get_sftp()._request(CMD_OPEN, path, omode, attr)
925
892
            if t != CMD_HANDLE:
926
893
                raise TransportError('Expected an SFTP handle')
927
894
            handle = msg.get_string()
928
895
            return SFTPFile(self._get_sftp(), handle, 'wb', -1)
929
 
        except (paramiko.SSHException, IOError) as e:
 
896
        except (paramiko.SSHException, IOError), e:
930
897
            self._translate_io_exception(e, abspath, ': unable to open',
931
 
                                         failure_exc=FileExists)
 
898
                failure_exc=FileExists)
932
899
 
933
900
    def _can_roundtrip_unix_modebits(self):
934
901
        if sys.platform == 'win32':
940
907
 
941
908
def get_test_permutations():
942
909
    """Return the permutations to be used in testing."""
943
 
    from ..tests import stub_sftp
 
910
    from bzrlib.tests import stub_sftp
944
911
    return [(SFTPTransport, stub_sftp.SFTPAbsoluteServer),
945
912
            (SFTPTransport, stub_sftp.SFTPHomeDirServer),
946
913
            (SFTPTransport, stub_sftp.SFTPSiblingAbsoluteServer),