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 (
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 (
62
60
ConnectedTransport,
71
69
# /var/lib/python-support/python2.5/paramiko/message.py:226: DeprecationWarning: integer argument expected, got float
72
70
# self.packet.write(struct.pack('>I', n))
73
71
warnings.filterwarnings('ignore',
74
'integer argument expected, got float',
75
category=DeprecationWarning,
76
module='paramiko.message')
72
'integer argument expected, got float',
73
category=DeprecationWarning,
74
module='paramiko.message')
80
except ImportError, e:
78
except ImportError as e:
81
79
raise ParamikoNotPresent(e)
83
81
from paramiko.sftp import (SFTP_FLAG_WRITE, SFTP_FLAG_CREATE,
84
82
SFTP_FLAG_EXCL, SFTP_FLAG_TRUNC,
85
SFTP_OK, CMD_HANDLE, CMD_OPEN)
86
84
from paramiko.sftp_attr import SFTPAttributes
87
85
from paramiko.sftp_file import SFTPFile
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))
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__
95
125
class SFTPLock(object):
169
193
# as possible, so we don't issues requests <32kB
170
194
sorted_offsets = sorted(self.original_offsets)
171
195
coalesced = list(ConnectedTransport._coalesce_offsets(sorted_offsets,
172
limit=0, fudge_factor=0))
196
limit=0, fudge_factor=0))
174
198
for c_offset in coalesced:
175
199
start = c_offset.start
183
207
start += next_size
184
208
if 'sftp' in debug.debug_flags:
185
209
mutter('SFTP.readv(%s) %s offsets => %s coalesced => %s requests',
186
self.relpath, len(sorted_offsets), len(coalesced),
210
self.relpath, len(sorted_offsets), len(coalesced),
190
214
def request_and_yield_offsets(self, fp):
197
221
requests = self._get_requests()
198
222
offset_iter = iter(self.original_offsets)
199
cur_offset, cur_size = offset_iter.next()
223
cur_offset, cur_size = next(offset_iter)
200
224
# paramiko .readv() yields strings that are in the order of the requests
201
225
# So we track the current request to know where the next data is
202
226
# being returned from.
214
238
data_stream = itertools.chain(fp.readv(requests),
215
239
itertools.repeat(None))
216
for (start, length), data in itertools.izip(requests, data_stream):
240
for (start, length), data in zip(requests, data_stream):
218
242
if cur_coalesced is not None:
219
243
raise errors.ShortReadvError(self.relpath,
220
start, length, len(data))
244
start, length, len(data))
221
245
if len(data) != length:
222
246
raise errors.ShortReadvError(self.relpath,
223
start, length, len(data))
247
start, length, len(data))
224
248
self._report_activity(length, 'read')
225
249
if last_end is None:
226
250
# This is the first request, just buffer it
270
294
input_start += cur_size
271
295
# Yield the requested data
272
296
yield cur_offset, cur_data
273
cur_offset, cur_size = offset_iter.next()
298
cur_offset, cur_size = next(offset_iter)
299
except StopIteration:
274
301
# at this point, we've consumed as much of buffered as we can,
275
302
# so break off the portion that we consumed
276
303
if buffered_offset == len(buffered_data):
281
308
buffered = buffered[buffered_offset:]
282
309
buffered_data = [buffered]
283
310
buffered_len = len(buffered)
311
# now that the data stream is done, close the handle
285
buffered = ''.join(buffered_data)
314
buffered = b''.join(buffered_data)
286
315
del buffered_data[:]
287
316
data_chunks.append((input_start, buffered))
289
318
if 'sftp' in debug.debug_flags:
290
319
mutter('SFTP readv left with %d out-of-order bytes',
291
sum(map(lambda x: len(x[1]), data_chunks)))
320
sum(len(x[1]) for x in data_chunks))
292
321
# We've processed all the readv data, at this point, anything we
293
322
# couldn't process is in data_chunks. This doesn't happen often, so
294
323
# this code path isn't optimized
315
344
if len(data) != cur_size:
316
345
raise AssertionError('We must have miscalulated.'
317
' We expected %d bytes, but only found %d'
318
% (cur_size, len(data)))
346
' We expected %d bytes, but only found %d'
347
% (cur_size, len(data)))
319
348
yield cur_offset, data
320
cur_offset, cur_size = offset_iter.next()
350
cur_offset, cur_size = next(offset_iter)
351
except StopIteration:
323
355
class SFTPTransport(ConnectedTransport):
324
356
"""Transport implementation for SFTP access."""
326
_do_prefetch = _default_do_prefetch
327
358
# TODO: jam 20060717 Conceivably these could be configurable, either
328
359
# by auto-tuning at run-time, or by a configuration (per host??)
329
360
# but the performance curve is pretty flat, so just going with
341
372
# up the request itself, rather than us having to worry about it
342
373
_max_request_size = 32768
344
def __init__(self, base, _from_transport=None):
345
super(SFTPTransport, self).__init__(base,
346
_from_transport=_from_transport)
348
375
def _remote_path(self, relpath):
349
376
"""Return the path to be passed along the sftp protocol for relpath.
351
378
:param relpath: is a urlencoded string.
353
relative = urlutils.unescape(relpath).encode('utf-8')
354
remote_path = self._combine_paths(self._path, relative)
380
remote_path = self._parsed_url.clone(relpath).path
355
381
# the initial slash should be removed from the path, and treated as a
356
382
# homedir relative path (the path begins with a double slash if it is
357
383
# absolute). see draft-ietf-secsh-scp-sftp-ssh-uri-03.txt
376
402
in base url at transport creation time.
378
404
if credentials is None:
379
password = self._password
405
password = self._parsed_url.password
381
407
password = credentials
383
409
vendor = ssh._get_ssh_vendor()
410
user = self._parsed_url.user
386
412
auth = config.AuthenticationConfig()
387
user = auth.get_user('ssh', self._host, self._port)
388
connection = vendor.connect_sftp(self._user, password,
389
self._host, self._port)
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)
390
417
return connection, (user, password)
419
def disconnect(self):
420
connection = self._get_connection()
421
if connection is not None:
392
424
def _get_sftp(self):
393
425
"""Ensures that a connection is established"""
394
426
connection = self._get_connection()
416
448
: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.
424
451
path = self._remote_path(relpath)
425
452
f = self._get_sftp().file(path, mode='rb')
426
if self._do_prefetch and (getattr(f, 'prefetch', None) is not None):
453
size = f.stat().st_size
454
if getattr(f, 'prefetch', None) is not None:
429
except (IOError, paramiko.SSHException), e:
457
except (IOError, paramiko.SSHException) as e:
430
458
self._translate_io_exception(e, path, ': error retrieving',
431
failure_exc=errors.ReadError)
459
failure_exc=errors.ReadError)
433
461
def get_bytes(self, relpath):
434
462
# reimplement this here so that we can report how many bytes came back
435
f = self.get(relpath)
463
with self.get(relpath) as f:
438
465
self._report_activity(len(bytes), 'read')
443
468
def _readv(self, relpath, offsets):
444
469
"""See Transport.readv()"""
457
482
if 'sftp' in debug.debug_flags:
458
483
mutter('seek and read %s offsets', len(offsets))
459
484
return self._seek_and_read(fp, offsets, relpath)
460
except (IOError, paramiko.SSHException), e:
485
except (IOError, paramiko.SSHException) as e:
461
486
self._translate_io_exception(e, path, ': error retrieving')
463
488
def recommended_page_size(self):
492
517
def _put(self, abspath, f, mode=None):
493
518
"""Helper function so both put() and copy_abspaths can reuse the code"""
494
519
tmp_abspath = '%s.tmp.%.9f.%d.%d' % (abspath, time.time(),
495
os.getpid(), random.randint(0,0x7FFFFFFF))
520
os.getpid(), random.randint(0, 0x7FFFFFFF))
496
521
fout = self._sftp_open_exclusive(tmp_abspath, mode=mode)
500
525
fout.set_pipelined(True)
501
526
length = self._pump(f, fout)
502
except (IOError, paramiko.SSHException), e:
527
except (IOError, paramiko.SSHException) as e:
503
528
self._translate_io_exception(e, tmp_abspath)
504
529
# XXX: This doesn't truly help like we would like it to.
505
530
# The problem is that openssh strips sticky bits. So while we
606
631
create_parent_dir=create_parent_dir,
607
632
dir_mode=dir_mode)
609
def put_bytes_non_atomic(self, relpath, bytes, mode=None,
634
def put_bytes_non_atomic(self, relpath, raw_bytes, mode=None,
610
635
create_parent_dir=False,
637
if not isinstance(raw_bytes, bytes):
639
'raw_bytes must be a plain string, not %s' % type(raw_bytes))
612
641
def writer(fout):
642
fout.write(raw_bytes)
614
643
self._put_non_atomic_helper(relpath, writer, mode=mode,
615
644
create_parent_dir=create_parent_dir,
616
645
dir_mode=dir_mode)
624
653
st = self.stat(relpath)
625
654
if stat.S_ISDIR(st.st_mode):
626
655
for i, basename in enumerate(self.list_dir(relpath)):
627
queue.insert(i, relpath+'/'+basename)
656
queue.insert(i, relpath + '/' + basename)
631
660
def _mkdir(self, abspath, mode=None):
635
664
local_mode = mode
644
673
# the sgid bit is set, report a warning to the user
645
674
# with the umask fix.
646
675
stat = self._get_sftp().lstat(abspath)
647
mode = mode & 0777 # can't set special bits anyway
648
if mode != stat.st_mode & 0777:
649
if stat.st_mode & 06000:
676
mode = mode & 0o777 # can't set special bits anyway
677
if mode != stat.st_mode & 0o777:
678
if stat.st_mode & 0o6000:
650
679
warning('About to chmod %s over sftp, which will result'
651
680
' in its suid or sgid bits being cleared. If'
652
681
' you want to preserve those bits, change your '
653
682
' environment on the server to use umask 0%03o.'
654
% (abspath, 0777 - mode))
683
% (abspath, 0o777 - mode))
655
684
self._get_sftp().chmod(abspath, mode=mode)
656
except (paramiko.SSHException, IOError), e:
685
except (paramiko.SSHException, IOError) as e:
657
686
self._translate_io_exception(e, abspath, ': unable to mkdir',
658
failure_exc=FileExists)
687
failure_exc=FileExists)
660
689
def mkdir(self, relpath, mode=None):
661
690
"""Create a directory at the given path."""
668
697
# api more than once per write_group at the moment so
669
698
# it is a tolerable overhead. Better would be to truncate
670
699
# the file after opening. RBC 20070805
671
self.put_bytes_non_atomic(relpath, "", mode)
700
self.put_bytes_non_atomic(relpath, b"", mode)
672
701
abspath = self._remote_path(relpath)
673
702
# TODO: jam 20060816 paramiko doesn't publicly expose a way to
674
703
# set the file mode at create time. If it does, use it.
678
707
handle = self._get_sftp().file(abspath, mode='wb')
679
708
handle.set_pipelined(True)
680
except (paramiko.SSHException, IOError), e:
709
except (paramiko.SSHException, IOError) as e:
681
710
self._translate_io_exception(e, abspath,
682
711
': unable to open')
683
712
_file_streams[self.abspath(relpath)] = handle
701
730
self._translate_error(e, path, raise_generic=False)
702
731
if getattr(e, 'args', None) is not None:
703
732
if (e.args == ('No such file or directory',) or
704
e.args == ('No such file',)):
733
e.args == ('No such file',)):
705
734
raise NoSuchFile(path, str(e) + more_info)
706
735
if (e.args == ('mkdir failed',) or
707
e.args[0].startswith('syserr: File exists')):
736
e.args[0].startswith('syserr: File exists')):
708
737
raise FileExists(path, str(e) + more_info)
709
738
# strange but true, for the paramiko server.
710
739
if (e.args == ('Failure',)):
713
742
# '/srv/bazaar.launchpad.net/blah...: '
714
743
# [Errno 39] Directory not empty',)
715
744
if (e.args[0].startswith('Directory not empty: ')
716
or getattr(e, 'errno', None) == errno.ENOTEMPTY):
745
or getattr(e, 'errno', None) == errno.ENOTEMPTY):
717
746
raise errors.DirectoryNotEmpty(path, str(e))
747
if e.args == ('Operation unsupported',):
748
raise errors.TransportNotPossible()
718
749
mutter('Raising exception with args %s', e.args)
719
750
if getattr(e, 'errno', None) is not None:
720
751
mutter('Raising exception with errno %s', e.errno)
733
764
result = fout.tell()
734
765
self._pump(f, fout)
736
except (IOError, paramiko.SSHException), e:
767
except (IOError, paramiko.SSHException) as e:
737
768
self._translate_io_exception(e, relpath, ': unable to append')
739
770
def rename(self, rel_from, rel_to):
740
771
"""Rename without special overwriting"""
742
773
self._get_sftp().rename(self._remote_path(rel_from),
743
self._remote_path(rel_to))
744
except (IOError, paramiko.SSHException), e:
774
self._remote_path(rel_to))
775
except (IOError, paramiko.SSHException) as e:
745
776
self._translate_io_exception(e, rel_from,
746
': unable to rename to %r' % (rel_to))
777
': unable to rename to %r' % (rel_to))
748
779
def _rename_and_overwrite(self, abs_from, abs_to):
749
780
"""Do a fancy rename on the remote server.
755
786
fancy_rename(abs_from, abs_to,
756
787
rename_func=sftp.rename,
757
788
unlink_func=sftp.remove)
758
except (IOError, paramiko.SSHException), e:
789
except (IOError, paramiko.SSHException) as e:
759
790
self._translate_io_exception(e, abs_from,
760
791
': unable to rename to %r' % (abs_to))
770
801
path = self._remote_path(relpath)
772
803
self._get_sftp().remove(path)
773
except (IOError, paramiko.SSHException), e:
804
except (IOError, paramiko.SSHException) as e:
774
805
self._translate_io_exception(e, path, ': unable to delete')
776
807
def external_url(self):
777
"""See bzrlib.transport.Transport.external_url."""
808
"""See breezy.transport.Transport.external_url."""
778
809
# the external path for SFTP is the base
795
826
entries = self._get_sftp().listdir(path)
796
827
self._report_activity(sum(map(len, entries)), 'read')
797
except (IOError, paramiko.SSHException), e:
828
except (IOError, paramiko.SSHException) as e:
798
829
self._translate_io_exception(e, path, ': failed to list_dir')
799
830
return [urlutils.escape(entry) for entry in entries]
803
834
path = self._remote_path(relpath)
805
836
return self._get_sftp().rmdir(path)
806
except (IOError, paramiko.SSHException), e:
837
except (IOError, paramiko.SSHException) as e:
807
838
self._translate_io_exception(e, path, ': failed to rmdir')
809
840
def stat(self, relpath):
811
842
path = self._remote_path(relpath)
813
844
return self._get_sftp().lstat(path)
814
except (IOError, paramiko.SSHException), e:
845
except (IOError, paramiko.SSHException) as e:
815
846
self._translate_io_exception(e, path, ': unable to stat')
817
848
def readlink(self, relpath):
818
849
"""See Transport.readlink."""
819
850
path = self._remote_path(relpath)
821
return self._get_sftp().readlink(path)
822
except (IOError, paramiko.SSHException), e:
852
return self._get_sftp().readlink(self._remote_path(path))
853
except (IOError, paramiko.SSHException) as e:
823
854
self._translate_io_exception(e, path, ': unable to readlink')
825
856
def symlink(self, source, link_name):
826
857
"""See Transport.symlink."""
828
859
conn = self._get_sftp()
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:
860
sftp_retval = conn.symlink(source, self._remote_path(link_name))
861
except (IOError, paramiko.SSHException) as e:
836
862
self._translate_io_exception(e, link_name,
837
863
': unable to create symlink to %r' % (source))
886
919
if mode is not None:
887
920
attr.st_mode = mode
888
921
omode = (SFTP_FLAG_WRITE | SFTP_FLAG_CREATE
889
| SFTP_FLAG_TRUNC | SFTP_FLAG_EXCL)
922
| SFTP_FLAG_TRUNC | SFTP_FLAG_EXCL)
891
924
t, msg = self._get_sftp()._request(CMD_OPEN, path, omode, attr)
892
925
if t != CMD_HANDLE:
893
926
raise TransportError('Expected an SFTP handle')
894
927
handle = msg.get_string()
895
928
return SFTPFile(self._get_sftp(), handle, 'wb', -1)
896
except (paramiko.SSHException, IOError), e:
929
except (paramiko.SSHException, IOError) as e:
897
930
self._translate_io_exception(e, abspath, ': unable to open',
898
failure_exc=FileExists)
931
failure_exc=FileExists)
900
933
def _can_roundtrip_unix_modebits(self):
901
934
if sys.platform == 'win32':
908
941
def get_test_permutations():
909
942
"""Return the permutations to be used in testing."""
910
from bzrlib.tests import stub_sftp
943
from ..tests import stub_sftp
911
944
return [(SFTPTransport, stub_sftp.SFTPAbsoluteServer),
912
945
(SFTPTransport, stub_sftp.SFTPHomeDirServer),
913
946
(SFTPTransport, stub_sftp.SFTPSiblingAbsoluteServer),