42
from ..errors import (FileExists,
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,
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 (
55
62
ConnectedTransport,
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')
73
except ImportError as e:
80
except ImportError, e:
74
81
raise ParamikoNotPresent(e)
76
83
from paramiko.sftp import (SFTP_FLAG_WRITE, SFTP_FLAG_CREATE,
77
84
SFTP_FLAG_EXCL, SFTP_FLAG_TRUNC,
85
SFTP_OK, CMD_HANDLE, CMD_OPEN)
79
86
from paramiko.sftp_attr import SFTPAttributes
80
87
from paramiko.sftp_file import SFTPFile
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__
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))
120
95
class SFTPLock(object):
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))
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),
186
self.relpath, len(sorted_offsets), len(coalesced),
209
190
def request_and_yield_offsets(self, fp):
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.
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):
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
289
270
input_start += cur_size
290
271
# Yield the requested data
291
272
yield cur_offset, cur_data
293
cur_offset, cur_size = next(offset_iter)
294
except StopIteration:
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
309
buffered = b''.join(buffered_data)
285
buffered = ''.join(buffered_data)
310
286
del buffered_data[:]
311
287
data_chunks.append((input_start, buffered))
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
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
345
cur_offset, cur_size = next(offset_iter)
346
except StopIteration:
320
cur_offset, cur_size = offset_iter.next()
350
323
class SFTPTransport(ConnectedTransport):
351
324
"""Transport implementation for SFTP access."""
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
344
def __init__(self, base, _from_transport=None):
345
super(SFTPTransport, self).__init__(base,
346
_from_transport=_from_transport)
370
348
def _remote_path(self, relpath):
371
349
"""Return the path to be passed along the sftp protocol for relpath.
373
351
:param relpath: is a urlencoded string.
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.
399
378
if credentials is None:
400
password = self._parsed_url.password
379
password = self._password
402
381
password = credentials
404
383
vendor = ssh._get_ssh_vendor()
405
user = self._parsed_url.user
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)
414
def disconnect(self):
415
connection = self._get_connection()
416
if connection is not None:
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
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.
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:
426
if self._do_prefetch and (getattr(f, 'prefetch', None) is not None):
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)
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)
460
438
self._report_activity(len(bytes), 'read')
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')
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)
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
626
606
create_parent_dir=create_parent_dir,
627
607
dir_mode=dir_mode)
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,
632
if not isinstance(raw_bytes, bytes):
634
'raw_bytes must be a plain string, not %s' % type(raw_bytes))
636
612
def writer(fout):
637
fout.write(raw_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)
655
631
def _mkdir(self, abspath, mode=None):
659
635
local_mode = mode
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)
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.
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)
762
except (IOError, paramiko.SSHException) as e:
736
except (IOError, paramiko.SSHException), e:
763
737
self._translate_io_exception(e, relpath, ': unable to append')
765
739
def rename(self, rel_from, rel_to):
766
740
"""Rename without special overwriting"""
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))
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))
796
770
path = self._remote_path(relpath)
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')
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
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]
829
803
path = self._remote_path(relpath)
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')
835
809
def stat(self, relpath):
837
811
path = self._remote_path(relpath)
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')
843
817
def readlink(self, relpath):
844
818
"""See Transport.readlink."""
845
819
path = self._remote_path(relpath)
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')
851
825
def symlink(self, source, link_name):
852
826
"""See Transport.symlink."""
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),
835
except (IOError, paramiko.SSHException), e:
857
836
self._translate_io_exception(e, link_name,
858
837
': unable to create symlink to %r' % (source))
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)
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)
928
900
def _can_roundtrip_unix_modebits(self):
929
901
if sys.platform == 'win32':
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),