44
from ..errors import (FileExists,
51
from ..osutils import fancy_rename
52
from ..sixish import (
55
from ..trace import mutter, warning
56
from ..transport import (
44
from bzrlib.errors import (FileExists,
45
NoSuchFile, PathNotChild,
51
from bzrlib.osutils import pathjoin, fancy_rename, getcwd
52
from bzrlib.symbol_versioning import (
55
from bzrlib.trace import mutter, warning
56
from bzrlib.transport import (
60
62
ConnectedTransport,
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')
78
except ImportError as e:
80
except ImportError, e:
79
81
raise ParamikoNotPresent(e)
81
83
from paramiko.sftp import (SFTP_FLAG_WRITE, SFTP_FLAG_CREATE,
82
84
SFTP_FLAG_EXCL, SFTP_FLAG_TRUNC,
85
SFTP_OK, CMD_HANDLE, CMD_OPEN)
84
86
from paramiko.sftp_attr import SFTPAttributes
85
87
from paramiko.sftp_file import SFTPFile
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.
91
from paramiko.py3compat import b as _bad
92
from paramiko.common import asbytes as _bad_asbytes
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:
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:
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))
125
95
class SFTPLock(object):
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))
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),
186
self.relpath, len(sorted_offsets), len(coalesced),
214
190
def request_and_yield_offsets(self, fp):
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.
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):
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
294
270
input_start += cur_size
295
271
# Yield the requested data
296
272
yield cur_offset, cur_data
298
cur_offset, cur_size = next(offset_iter)
299
except StopIteration:
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
314
buffered = b''.join(buffered_data)
285
buffered = ''.join(buffered_data)
315
286
del buffered_data[:]
316
287
data_chunks.append((input_start, buffered))
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
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
350
cur_offset, cur_size = next(offset_iter)
351
except StopIteration:
320
cur_offset, cur_size = offset_iter.next()
355
323
class SFTPTransport(ConnectedTransport):
356
324
"""Transport implementation for SFTP access."""
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
344
def __init__(self, base, _from_transport=None):
345
super(SFTPTransport, self).__init__(base,
346
_from_transport=_from_transport)
375
348
def _remote_path(self, relpath):
376
349
"""Return the path to be passed along the sftp protocol for relpath.
378
351
:param relpath: is a urlencoded string.
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.
404
378
if credentials is None:
405
password = self._parsed_url.password
379
password = self._password
407
381
password = credentials
409
383
vendor = ssh._get_ssh_vendor()
410
user = self._parsed_url.user
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)
419
def disconnect(self):
420
connection = self._get_connection()
421
if connection is not None:
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
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.
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:
426
if self._do_prefetch and (getattr(f, 'prefetch', None) is not None):
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)
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)
465
438
self._report_activity(len(bytes), 'read')
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')
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)
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
631
606
create_parent_dir=create_parent_dir,
632
607
dir_mode=dir_mode)
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,
637
if not isinstance(raw_bytes, bytes):
639
'raw_bytes must be a plain string, not %s' % type(raw_bytes))
641
612
def writer(fout):
642
fout.write(raw_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)
660
631
def _mkdir(self, abspath, mode=None):
664
635
local_mode = mode
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)
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.
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)
767
except (IOError, paramiko.SSHException) as e:
736
except (IOError, paramiko.SSHException), e:
768
737
self._translate_io_exception(e, relpath, ': unable to append')
770
739
def rename(self, rel_from, rel_to):
771
740
"""Rename without special overwriting"""
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))
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))
801
770
path = self._remote_path(relpath)
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')
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
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]
834
803
path = self._remote_path(relpath)
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')
840
809
def stat(self, relpath):
842
811
path = self._remote_path(relpath)
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')
848
817
def readlink(self, relpath):
849
818
"""See Transport.readlink."""
850
819
path = self._remote_path(relpath)
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')
856
825
def symlink(self, source, link_name):
857
826
"""See Transport.symlink."""
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),
835
except (IOError, paramiko.SSHException), e:
862
836
self._translate_io_exception(e, link_name,
863
837
': unable to create symlink to %r' % (source))
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)
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)
933
900
def _can_roundtrip_unix_modebits(self):
934
901
if sys.platform == 'win32':
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),