1
# Copyright (C) 2005-2011, 2016, 2017 Canonical Ltd
1
# Copyright (C) 2005-2011, 2016 Canonical Ltd
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
44
from ..errors import (FileExists,
44
from bzrlib.errors import (FileExists,
49
49
ParamikoNotPresent,
51
from ..osutils import fancy_rename
52
from ..sixish import (
55
from ..trace import mutter, warning
56
from ..transport import (
51
from bzrlib.osutils import fancy_rename
52
from bzrlib.trace import mutter, warning
53
from bzrlib.transport import (
85
82
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__
85
_paramiko_version = getattr(paramiko, '__version_info__', (0, 0, 0))
86
# don't use prefetch unless paramiko version >= 1.5.5 (there were bugs earlier)
87
_default_do_prefetch = (_paramiko_version >= (1, 5, 5))
125
90
class SFTPLock(object):
221
186
requests = self._get_requests()
222
187
offset_iter = iter(self.original_offsets)
223
cur_offset, cur_size = next(offset_iter)
188
cur_offset, cur_size = offset_iter.next()
224
189
# paramiko .readv() yields strings that are in the order of the requests
225
190
# So we track the current request to know where the next data is
226
191
# being returned from.
238
203
data_stream = itertools.chain(fp.readv(requests),
239
204
itertools.repeat(None))
240
for (start, length), data in zip(requests, data_stream):
205
for (start, length), data in itertools.izip(requests, data_stream):
242
207
if cur_coalesced is not None:
243
208
raise errors.ShortReadvError(self.relpath,
294
259
input_start += cur_size
295
260
# Yield the requested data
296
261
yield cur_offset, cur_data
297
cur_offset, cur_size = next(offset_iter)
262
cur_offset, cur_size = offset_iter.next()
298
263
# at this point, we've consumed as much of buffered as we can,
299
264
# so break off the portion that we consumed
300
265
if buffered_offset == len(buffered_data):
315
280
if 'sftp' in debug.debug_flags:
316
281
mutter('SFTP readv left with %d out-of-order bytes',
317
sum(len(x[1]) for x in data_chunks))
282
sum(map(lambda x: len(x[1]), data_chunks)))
318
283
# We've processed all the readv data, at this point, anything we
319
284
# couldn't process is in data_chunks. This doesn't happen often, so
320
285
# this code path isn't optimized
343
308
' We expected %d bytes, but only found %d'
344
309
% (cur_size, len(data)))
345
310
yield cur_offset, data
346
cur_offset, cur_size = next(offset_iter)
311
cur_offset, cur_size = offset_iter.next()
349
314
class SFTPTransport(ConnectedTransport):
350
315
"""Transport implementation for SFTP access."""
317
_do_prefetch = _default_do_prefetch
352
318
# TODO: jam 20060717 Conceivably these could be configurable, either
353
319
# by auto-tuning at run-time, or by a configuration (per host??)
354
320
# but the performance curve is pretty flat, so just going with
445
411
path = self._remote_path(relpath)
446
412
f = self._get_sftp().file(path, mode='rb')
447
size = f.stat().st_size
448
if getattr(f, 'prefetch', None) is not None:
413
if self._do_prefetch and (getattr(f, 'prefetch', None) is not None):
451
except (IOError, paramiko.SSHException) as e:
416
except (IOError, paramiko.SSHException), e:
452
417
self._translate_io_exception(e, path, ': error retrieving',
453
418
failure_exc=errors.ReadError)
455
420
def get_bytes(self, relpath):
456
421
# reimplement this here so that we can report how many bytes came back
457
with self.get(relpath) as f:
422
f = self.get(relpath)
459
425
self._report_activity(len(bytes), 'read')
462
430
def _readv(self, relpath, offsets):
463
431
"""See Transport.readv()"""
476
444
if 'sftp' in debug.debug_flags:
477
445
mutter('seek and read %s offsets', len(offsets))
478
446
return self._seek_and_read(fp, offsets, relpath)
479
except (IOError, paramiko.SSHException) as e:
447
except (IOError, paramiko.SSHException), e:
480
448
self._translate_io_exception(e, path, ': error retrieving')
482
450
def recommended_page_size(self):
511
479
def _put(self, abspath, f, mode=None):
512
480
"""Helper function so both put() and copy_abspaths can reuse the code"""
513
481
tmp_abspath = '%s.tmp.%.9f.%d.%d' % (abspath, time.time(),
514
os.getpid(), random.randint(0, 0x7FFFFFFF))
482
os.getpid(), random.randint(0,0x7FFFFFFF))
515
483
fout = self._sftp_open_exclusive(tmp_abspath, mode=mode)
519
487
fout.set_pipelined(True)
520
488
length = self._pump(f, fout)
521
except (IOError, paramiko.SSHException) as e:
489
except (IOError, paramiko.SSHException), e:
522
490
self._translate_io_exception(e, tmp_abspath)
523
491
# XXX: This doesn't truly help like we would like it to.
524
492
# The problem is that openssh strips sticky bits. So while we
540
508
self._rename_and_overwrite(tmp_abspath, abspath)
542
except Exception as e:
543
511
# If we fail, try to clean up the temporary file
544
512
# before we throw the exception
545
513
# but don't let another exception mess things up
574
542
fout = self._get_sftp().file(abspath, mode='wb')
575
543
fout.set_pipelined(True)
577
except (paramiko.SSHException, IOError) as e:
545
except (paramiko.SSHException, IOError), e:
578
546
self._translate_io_exception(e, abspath,
579
547
': unable to open')
667
635
# the sgid bit is set, report a warning to the user
668
636
# with the umask fix.
669
637
stat = self._get_sftp().lstat(abspath)
670
mode = mode & 0o777 # can't set special bits anyway
671
if mode != stat.st_mode & 0o777:
672
if stat.st_mode & 0o6000:
638
mode = mode & 0777 # can't set special bits anyway
639
if mode != stat.st_mode & 0777:
640
if stat.st_mode & 06000:
673
641
warning('About to chmod %s over sftp, which will result'
674
642
' in its suid or sgid bits being cleared. If'
675
643
' you want to preserve those bits, change your '
676
644
' environment on the server to use umask 0%03o.'
677
% (abspath, 0o777 - mode))
645
% (abspath, 0777 - mode))
678
646
self._get_sftp().chmod(abspath, mode=mode)
679
except (paramiko.SSHException, IOError) as e:
647
except (paramiko.SSHException, IOError), e:
680
648
self._translate_io_exception(e, abspath, ': unable to mkdir',
681
649
failure_exc=FileExists)
701
669
handle = self._get_sftp().file(abspath, mode='wb')
702
670
handle.set_pipelined(True)
703
except (paramiko.SSHException, IOError) as e:
671
except (paramiko.SSHException, IOError), e:
704
672
self._translate_io_exception(e, abspath,
705
673
': unable to open')
706
674
_file_streams[self.abspath(relpath)] = handle
758
726
result = fout.tell()
759
727
self._pump(f, fout)
761
except (IOError, paramiko.SSHException) as e:
729
except (IOError, paramiko.SSHException), e:
762
730
self._translate_io_exception(e, relpath, ': unable to append')
764
732
def rename(self, rel_from, rel_to):
767
735
self._get_sftp().rename(self._remote_path(rel_from),
768
736
self._remote_path(rel_to))
769
except (IOError, paramiko.SSHException) as e:
737
except (IOError, paramiko.SSHException), e:
770
738
self._translate_io_exception(e, rel_from,
771
739
': unable to rename to %r' % (rel_to))
780
748
fancy_rename(abs_from, abs_to,
781
749
rename_func=sftp.rename,
782
750
unlink_func=sftp.remove)
783
except (IOError, paramiko.SSHException) as e:
751
except (IOError, paramiko.SSHException), e:
784
752
self._translate_io_exception(e, abs_from,
785
753
': unable to rename to %r' % (abs_to))
795
763
path = self._remote_path(relpath)
797
765
self._get_sftp().remove(path)
798
except (IOError, paramiko.SSHException) as e:
766
except (IOError, paramiko.SSHException), e:
799
767
self._translate_io_exception(e, path, ': unable to delete')
801
769
def external_url(self):
802
"""See breezy.transport.Transport.external_url."""
770
"""See bzrlib.transport.Transport.external_url."""
803
771
# the external path for SFTP is the base
820
788
entries = self._get_sftp().listdir(path)
821
789
self._report_activity(sum(map(len, entries)), 'read')
822
except (IOError, paramiko.SSHException) as e:
790
except (IOError, paramiko.SSHException), e:
823
791
self._translate_io_exception(e, path, ': failed to list_dir')
824
792
return [urlutils.escape(entry) for entry in entries]
828
796
path = self._remote_path(relpath)
830
798
return self._get_sftp().rmdir(path)
831
except (IOError, paramiko.SSHException) as e:
799
except (IOError, paramiko.SSHException), e:
832
800
self._translate_io_exception(e, path, ': failed to rmdir')
834
802
def stat(self, relpath):
836
804
path = self._remote_path(relpath)
838
806
return self._get_sftp().lstat(path)
839
except (IOError, paramiko.SSHException) as e:
807
except (IOError, paramiko.SSHException), e:
840
808
self._translate_io_exception(e, path, ': unable to stat')
842
810
def readlink(self, relpath):
844
812
path = self._remote_path(relpath)
846
814
return self._get_sftp().readlink(path)
847
except (IOError, paramiko.SSHException) as e:
815
except (IOError, paramiko.SSHException), e:
848
816
self._translate_io_exception(e, path, ': unable to readlink')
850
818
def symlink(self, source, link_name):
857
825
'%r: unable to create symlink to %r' % (link_name, source),
860
except (IOError, paramiko.SSHException) as e:
828
except (IOError, paramiko.SSHException), e:
861
829
self._translate_io_exception(e, link_name,
862
830
': unable to create symlink to %r' % (source))
873
841
def unlock(self):
875
def __exit__(self, exc_type, exc_val, exc_tb):
879
843
return BogusLock(relpath)
881
845
def lock_write(self, relpath):
922
886
raise TransportError('Expected an SFTP handle')
923
887
handle = msg.get_string()
924
888
return SFTPFile(self._get_sftp(), handle, 'wb', -1)
925
except (paramiko.SSHException, IOError) as e:
889
except (paramiko.SSHException, IOError), e:
926
890
self._translate_io_exception(e, abspath, ': unable to open',
927
891
failure_exc=FileExists)
937
901
def get_test_permutations():
938
902
"""Return the permutations to be used in testing."""
939
from ..tests import stub_sftp
903
from bzrlib.tests import stub_sftp
940
904
return [(SFTPTransport, stub_sftp.SFTPAbsoluteServer),
941
905
(SFTPTransport, stub_sftp.SFTPHomeDirServer),
942
906
(SFTPTransport, stub_sftp.SFTPSiblingAbsoluteServer),