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 (
42
from ..errors import (FileExists,
49
from ..osutils import fancy_rename
50
from ..trace import mutter, warning
51
from ..transport import (
62
55
ConnectedTransport,
71
64
# /var/lib/python-support/python2.5/paramiko/message.py:226: DeprecationWarning: integer argument expected, got float
72
65
# self.packet.write(struct.pack('>I', n))
73
66
warnings.filterwarnings('ignore',
74
'integer argument expected, got float',
75
category=DeprecationWarning,
76
module='paramiko.message')
67
'integer argument expected, got float',
68
category=DeprecationWarning,
69
module='paramiko.message')
80
except ImportError, e:
73
except ImportError as e:
81
74
raise ParamikoNotPresent(e)
83
76
from paramiko.sftp import (SFTP_FLAG_WRITE, SFTP_FLAG_CREATE,
84
77
SFTP_FLAG_EXCL, SFTP_FLAG_TRUNC,
85
SFTP_OK, CMD_HANDLE, CMD_OPEN)
86
79
from paramiko.sftp_attr import SFTPAttributes
87
80
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))
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.
86
from paramiko.py3compat import b as _bad
87
from paramiko.common import asbytes as _bad_asbytes
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:
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:
116
_bad.__code__ = _b_for_broken_paramiko.__code__
117
_bad_asbytes.__code__ = _asbytes_for_broken_paramiko.__code__
95
120
class SFTPLock(object):
169
188
# as possible, so we don't issues requests <32kB
170
189
sorted_offsets = sorted(self.original_offsets)
171
190
coalesced = list(ConnectedTransport._coalesce_offsets(sorted_offsets,
172
limit=0, fudge_factor=0))
191
limit=0, fudge_factor=0))
174
193
for c_offset in coalesced:
175
194
start = c_offset.start
183
202
start += next_size
184
203
if 'sftp' in debug.debug_flags:
185
204
mutter('SFTP.readv(%s) %s offsets => %s coalesced => %s requests',
186
self.relpath, len(sorted_offsets), len(coalesced),
205
self.relpath, len(sorted_offsets), len(coalesced),
190
209
def request_and_yield_offsets(self, fp):
197
216
requests = self._get_requests()
198
217
offset_iter = iter(self.original_offsets)
199
cur_offset, cur_size = offset_iter.next()
218
cur_offset, cur_size = next(offset_iter)
200
219
# paramiko .readv() yields strings that are in the order of the requests
201
220
# So we track the current request to know where the next data is
202
221
# being returned from.
214
233
data_stream = itertools.chain(fp.readv(requests),
215
234
itertools.repeat(None))
216
for (start, length), data in itertools.izip(requests, data_stream):
235
for (start, length), data in zip(requests, data_stream):
218
237
if cur_coalesced is not None:
219
238
raise errors.ShortReadvError(self.relpath,
220
start, length, len(data))
239
start, length, len(data))
221
240
if len(data) != length:
222
241
raise errors.ShortReadvError(self.relpath,
223
start, length, len(data))
242
start, length, len(data))
224
243
self._report_activity(length, 'read')
225
244
if last_end is None:
226
245
# This is the first request, just buffer it
270
289
input_start += cur_size
271
290
# Yield the requested data
272
291
yield cur_offset, cur_data
273
cur_offset, cur_size = offset_iter.next()
293
cur_offset, cur_size = next(offset_iter)
294
except StopIteration:
274
296
# at this point, we've consumed as much of buffered as we can,
275
297
# so break off the portion that we consumed
276
298
if buffered_offset == len(buffered_data):
281
303
buffered = buffered[buffered_offset:]
282
304
buffered_data = [buffered]
283
305
buffered_len = len(buffered)
306
# now that the data stream is done, close the handle
285
buffered = ''.join(buffered_data)
309
buffered = b''.join(buffered_data)
286
310
del buffered_data[:]
287
311
data_chunks.append((input_start, buffered))
289
313
if 'sftp' in debug.debug_flags:
290
314
mutter('SFTP readv left with %d out-of-order bytes',
291
sum(map(lambda x: len(x[1]), data_chunks)))
315
sum(len(x[1]) for x in data_chunks))
292
316
# We've processed all the readv data, at this point, anything we
293
317
# couldn't process is in data_chunks. This doesn't happen often, so
294
318
# this code path isn't optimized
315
339
if len(data) != cur_size:
316
340
raise AssertionError('We must have miscalulated.'
317
' We expected %d bytes, but only found %d'
318
% (cur_size, len(data)))
341
' We expected %d bytes, but only found %d'
342
% (cur_size, len(data)))
319
343
yield cur_offset, data
320
cur_offset, cur_size = offset_iter.next()
345
cur_offset, cur_size = next(offset_iter)
346
except StopIteration:
323
350
class SFTPTransport(ConnectedTransport):
324
351
"""Transport implementation for SFTP access."""
326
_do_prefetch = _default_do_prefetch
327
353
# TODO: jam 20060717 Conceivably these could be configurable, either
328
354
# by auto-tuning at run-time, or by a configuration (per host??)
329
355
# but the performance curve is pretty flat, so just going with
341
367
# up the request itself, rather than us having to worry about it
342
368
_max_request_size = 32768
344
def __init__(self, base, _from_transport=None):
345
super(SFTPTransport, self).__init__(base,
346
_from_transport=_from_transport)
348
370
def _remote_path(self, relpath):
349
371
"""Return the path to be passed along the sftp protocol for relpath.
351
373
:param relpath: is a urlencoded string.
353
relative = urlutils.unescape(relpath).encode('utf-8')
354
remote_path = self._combine_paths(self._path, relative)
375
remote_path = self._parsed_url.clone(relpath).path
355
376
# the initial slash should be removed from the path, and treated as a
356
377
# homedir relative path (the path begins with a double slash if it is
357
378
# absolute). see draft-ietf-secsh-scp-sftp-ssh-uri-03.txt
376
397
in base url at transport creation time.
378
399
if credentials is None:
379
password = self._password
400
password = self._parsed_url.password
381
402
password = credentials
383
404
vendor = ssh._get_ssh_vendor()
405
user = self._parsed_url.user
386
407
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)
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)
390
412
return connection, (user, password)
414
def disconnect(self):
415
connection = self._get_connection()
416
if connection is not None:
392
419
def _get_sftp(self):
393
420
"""Ensures that a connection is established"""
394
421
connection = self._get_connection()
416
443
: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
446
path = self._remote_path(relpath)
425
447
f = self._get_sftp().file(path, mode='rb')
426
if self._do_prefetch and (getattr(f, 'prefetch', None) is not None):
448
size = f.stat().st_size
449
if getattr(f, 'prefetch', None) is not None:
429
except (IOError, paramiko.SSHException), e:
452
except (IOError, paramiko.SSHException) as e:
430
453
self._translate_io_exception(e, path, ': error retrieving',
431
failure_exc=errors.ReadError)
454
failure_exc=errors.ReadError)
433
456
def get_bytes(self, relpath):
434
457
# reimplement this here so that we can report how many bytes came back
435
f = self.get(relpath)
458
with self.get(relpath) as f:
438
460
self._report_activity(len(bytes), 'read')
443
463
def _readv(self, relpath, offsets):
444
464
"""See Transport.readv()"""
457
477
if 'sftp' in debug.debug_flags:
458
478
mutter('seek and read %s offsets', len(offsets))
459
479
return self._seek_and_read(fp, offsets, relpath)
460
except (IOError, paramiko.SSHException), e:
480
except (IOError, paramiko.SSHException) as e:
461
481
self._translate_io_exception(e, path, ': error retrieving')
463
483
def recommended_page_size(self):
492
512
def _put(self, abspath, f, mode=None):
493
513
"""Helper function so both put() and copy_abspaths can reuse the code"""
494
514
tmp_abspath = '%s.tmp.%.9f.%d.%d' % (abspath, time.time(),
495
os.getpid(), random.randint(0,0x7FFFFFFF))
515
os.getpid(), random.randint(0, 0x7FFFFFFF))
496
516
fout = self._sftp_open_exclusive(tmp_abspath, mode=mode)
500
520
fout.set_pipelined(True)
501
521
length = self._pump(f, fout)
502
except (IOError, paramiko.SSHException), e:
522
except (IOError, paramiko.SSHException) as e:
503
523
self._translate_io_exception(e, tmp_abspath)
504
524
# XXX: This doesn't truly help like we would like it to.
505
525
# The problem is that openssh strips sticky bits. So while we
606
626
create_parent_dir=create_parent_dir,
607
627
dir_mode=dir_mode)
609
def put_bytes_non_atomic(self, relpath, bytes, mode=None,
629
def put_bytes_non_atomic(self, relpath, raw_bytes, mode=None,
610
630
create_parent_dir=False,
632
if not isinstance(raw_bytes, bytes):
634
'raw_bytes must be a plain string, not %s' % type(raw_bytes))
612
636
def writer(fout):
637
fout.write(raw_bytes)
614
638
self._put_non_atomic_helper(relpath, writer, mode=mode,
615
639
create_parent_dir=create_parent_dir,
616
640
dir_mode=dir_mode)
624
648
st = self.stat(relpath)
625
649
if stat.S_ISDIR(st.st_mode):
626
650
for i, basename in enumerate(self.list_dir(relpath)):
627
queue.insert(i, relpath+'/'+basename)
651
queue.insert(i, relpath + '/' + basename)
631
655
def _mkdir(self, abspath, mode=None):
635
659
local_mode = mode
644
668
# the sgid bit is set, report a warning to the user
645
669
# with the umask fix.
646
670
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:
671
mode = mode & 0o777 # can't set special bits anyway
672
if mode != stat.st_mode & 0o777:
673
if stat.st_mode & 0o6000:
650
674
warning('About to chmod %s over sftp, which will result'
651
675
' in its suid or sgid bits being cleared. If'
652
676
' you want to preserve those bits, change your '
653
677
' environment on the server to use umask 0%03o.'
654
% (abspath, 0777 - mode))
678
% (abspath, 0o777 - mode))
655
679
self._get_sftp().chmod(abspath, mode=mode)
656
except (paramiko.SSHException, IOError), e:
680
except (paramiko.SSHException, IOError) as e:
657
681
self._translate_io_exception(e, abspath, ': unable to mkdir',
658
failure_exc=FileExists)
682
failure_exc=FileExists)
660
684
def mkdir(self, relpath, mode=None):
661
685
"""Create a directory at the given path."""
668
692
# api more than once per write_group at the moment so
669
693
# it is a tolerable overhead. Better would be to truncate
670
694
# the file after opening. RBC 20070805
671
self.put_bytes_non_atomic(relpath, "", mode)
695
self.put_bytes_non_atomic(relpath, b"", mode)
672
696
abspath = self._remote_path(relpath)
673
697
# TODO: jam 20060816 paramiko doesn't publicly expose a way to
674
698
# set the file mode at create time. If it does, use it.
678
702
handle = self._get_sftp().file(abspath, mode='wb')
679
703
handle.set_pipelined(True)
680
except (paramiko.SSHException, IOError), e:
704
except (paramiko.SSHException, IOError) as e:
681
705
self._translate_io_exception(e, abspath,
682
706
': unable to open')
683
707
_file_streams[self.abspath(relpath)] = handle
701
725
self._translate_error(e, path, raise_generic=False)
702
726
if getattr(e, 'args', None) is not None:
703
727
if (e.args == ('No such file or directory',) or
704
e.args == ('No such file',)):
728
e.args == ('No such file',)):
705
729
raise NoSuchFile(path, str(e) + more_info)
706
730
if (e.args == ('mkdir failed',) or
707
e.args[0].startswith('syserr: File exists')):
731
e.args[0].startswith('syserr: File exists')):
708
732
raise FileExists(path, str(e) + more_info)
709
733
# strange but true, for the paramiko server.
710
734
if (e.args == ('Failure',)):
713
737
# '/srv/bazaar.launchpad.net/blah...: '
714
738
# [Errno 39] Directory not empty',)
715
739
if (e.args[0].startswith('Directory not empty: ')
716
or getattr(e, 'errno', None) == errno.ENOTEMPTY):
740
or getattr(e, 'errno', None) == errno.ENOTEMPTY):
717
741
raise errors.DirectoryNotEmpty(path, str(e))
742
if e.args == ('Operation unsupported',):
743
raise errors.TransportNotPossible()
718
744
mutter('Raising exception with args %s', e.args)
719
745
if getattr(e, 'errno', None) is not None:
720
746
mutter('Raising exception with errno %s', e.errno)
733
759
result = fout.tell()
734
760
self._pump(f, fout)
736
except (IOError, paramiko.SSHException), e:
762
except (IOError, paramiko.SSHException) as e:
737
763
self._translate_io_exception(e, relpath, ': unable to append')
739
765
def rename(self, rel_from, rel_to):
740
766
"""Rename without special overwriting"""
742
768
self._get_sftp().rename(self._remote_path(rel_from),
743
self._remote_path(rel_to))
744
except (IOError, paramiko.SSHException), e:
769
self._remote_path(rel_to))
770
except (IOError, paramiko.SSHException) as e:
745
771
self._translate_io_exception(e, rel_from,
746
': unable to rename to %r' % (rel_to))
772
': unable to rename to %r' % (rel_to))
748
774
def _rename_and_overwrite(self, abs_from, abs_to):
749
775
"""Do a fancy rename on the remote server.
755
781
fancy_rename(abs_from, abs_to,
756
782
rename_func=sftp.rename,
757
783
unlink_func=sftp.remove)
758
except (IOError, paramiko.SSHException), e:
784
except (IOError, paramiko.SSHException) as e:
759
785
self._translate_io_exception(e, abs_from,
760
786
': unable to rename to %r' % (abs_to))
770
796
path = self._remote_path(relpath)
772
798
self._get_sftp().remove(path)
773
except (IOError, paramiko.SSHException), e:
799
except (IOError, paramiko.SSHException) as e:
774
800
self._translate_io_exception(e, path, ': unable to delete')
776
802
def external_url(self):
777
"""See bzrlib.transport.Transport.external_url."""
803
"""See breezy.transport.Transport.external_url."""
778
804
# the external path for SFTP is the base
795
821
entries = self._get_sftp().listdir(path)
796
822
self._report_activity(sum(map(len, entries)), 'read')
797
except (IOError, paramiko.SSHException), e:
823
except (IOError, paramiko.SSHException) as e:
798
824
self._translate_io_exception(e, path, ': failed to list_dir')
799
825
return [urlutils.escape(entry) for entry in entries]
803
829
path = self._remote_path(relpath)
805
831
return self._get_sftp().rmdir(path)
806
except (IOError, paramiko.SSHException), e:
832
except (IOError, paramiko.SSHException) as e:
807
833
self._translate_io_exception(e, path, ': failed to rmdir')
809
835
def stat(self, relpath):
811
837
path = self._remote_path(relpath)
813
839
return self._get_sftp().lstat(path)
814
except (IOError, paramiko.SSHException), e:
840
except (IOError, paramiko.SSHException) as e:
815
841
self._translate_io_exception(e, path, ': unable to stat')
817
843
def readlink(self, relpath):
818
844
"""See Transport.readlink."""
819
845
path = self._remote_path(relpath)
821
return self._get_sftp().readlink(path)
822
except (IOError, paramiko.SSHException), e:
847
return self._get_sftp().readlink(self._remote_path(path))
848
except (IOError, paramiko.SSHException) as e:
823
849
self._translate_io_exception(e, path, ': unable to readlink')
825
851
def symlink(self, source, link_name):
826
852
"""See Transport.symlink."""
828
854
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:
855
sftp_retval = conn.symlink(source, self._remote_path(link_name))
856
except (IOError, paramiko.SSHException) as e:
836
857
self._translate_io_exception(e, link_name,
837
858
': unable to create symlink to %r' % (source))
886
914
if mode is not None:
887
915
attr.st_mode = mode
888
916
omode = (SFTP_FLAG_WRITE | SFTP_FLAG_CREATE
889
| SFTP_FLAG_TRUNC | SFTP_FLAG_EXCL)
917
| SFTP_FLAG_TRUNC | SFTP_FLAG_EXCL)
891
919
t, msg = self._get_sftp()._request(CMD_OPEN, path, omode, attr)
892
920
if t != CMD_HANDLE:
893
921
raise TransportError('Expected an SFTP handle')
894
922
handle = msg.get_string()
895
923
return SFTPFile(self._get_sftp(), handle, 'wb', -1)
896
except (paramiko.SSHException, IOError), e:
924
except (paramiko.SSHException, IOError) as e:
897
925
self._translate_io_exception(e, abspath, ': unable to open',
898
failure_exc=FileExists)
926
failure_exc=FileExists)
900
928
def _can_roundtrip_unix_modebits(self):
901
929
if sys.platform == 'win32':
908
936
def get_test_permutations():
909
937
"""Return the permutations to be used in testing."""
910
from bzrlib.tests import stub_sftp
938
from ..tests import stub_sftp
911
939
return [(SFTPTransport, stub_sftp.SFTPAbsoluteServer),
912
940
(SFTPTransport, stub_sftp.SFTPHomeDirServer),
913
941
(SFTPTransport, stub_sftp.SFTPSiblingAbsoluteServer),