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