345
def put(self, relpath, f):
399
def put(self, relpath, f, mode=None):
347
401
Copy the file-like or string object into the location.
349
403
:param relpath: Location to put the contents, relative to base.
350
404
:param f: File-like or string object.
405
:param mode: The final mode for the file
352
final_path = self._abspath(relpath)
353
tmp_relpath = '%s.tmp.%.9f.%d.%d' % (relpath, time.time(),
407
final_path = self._remote_path(relpath)
408
self._put(final_path, f, mode=mode)
410
def _put(self, abspath, f, mode=None):
411
"""Helper function so both put() and copy_abspaths can reuse the code"""
412
tmp_abspath = '%s.tmp.%.9f.%d.%d' % (abspath, time.time(),
354
413
os.getpid(), random.randint(0,0x7FFFFFFF))
355
tmp_abspath = self._abspath(tmp_relpath)
356
fout = self._sftp_open_exclusive(tmp_relpath)
414
fout = self._sftp_open_exclusive(tmp_abspath, mode=mode)
418
fout.set_pipelined(True)
360
419
self._pump(f, fout)
361
except (paramiko.SSHException, IOError), e:
362
self._translate_io_exception(e, relpath, ': unable to write')
420
except (IOError, paramiko.SSHException), e:
421
self._translate_io_exception(e, tmp_abspath)
423
self._sftp.chmod(tmp_abspath, mode)
426
self._rename_and_overwrite(tmp_abspath, abspath)
363
427
except Exception, e:
364
428
# If we fail, try to clean up the temporary file
365
429
# before we throw the exception
366
430
# but don't let another exception mess things up
431
# Write out the traceback, because otherwise
432
# the catch and throw destroys it
434
mutter(traceback.format_exc())
369
438
self._sftp.remove(tmp_abspath)
374
# sftp rename doesn't allow overwriting, so play tricks:
375
tmp_safety = 'bzr.tmp.%.9f.%d.%d' % (time.time(), os.getpid(), random.randint(0, 0x7FFFFFFF))
376
tmp_safety = self._abspath(tmp_safety)
378
self._sftp.rename(final_path, tmp_safety)
385
self._sftp.rename(tmp_abspath, final_path)
386
except (paramiko.SSHException, IOError), e:
387
self._translate_io_exception(e, relpath, ': unable to rename')
393
self._sftp.unlink(tmp_safety)
395
self._sftp.rename(tmp_safety, final_path)
440
# raise the saved except
442
# raise the original with its traceback if we can.
397
445
def iter_files_recursive(self):
398
446
"""Walk the relative paths of all files in this transport."""
450
path = self._abspath(relpath)
508
path = self._remote_path(relpath)
451
509
fout = self._sftp.file(path, 'ab')
452
511
self._pump(f, fout)
453
513
except (IOError, paramiko.SSHException), e:
454
514
self._translate_io_exception(e, relpath, ': unable to append')
456
def copy(self, rel_from, rel_to):
457
"""Copy the item at rel_from to the location at rel_to"""
458
path_from = self._abspath(rel_from)
459
path_to = self._abspath(rel_to)
460
self._copy_abspaths(path_from, path_to)
462
def _copy_abspaths(self, path_from, path_to):
463
"""Copy files given an absolute path
465
:param path_from: Path on remote server to read
466
:param path_to: Path on remote server to write
469
TODO: Should the destination location be atomically created?
470
This has not been specified
471
TODO: This should use some sort of remote copy, rather than
472
pulling the data locally, and then writing it remotely
475
fin = self._sftp.file(path_from, 'rb')
477
fout = self._sftp.file(path_to, 'wb')
479
fout.set_pipelined(True)
480
self._pump(fin, fout)
485
except (IOError, paramiko.SSHException), e:
486
self._translate_io_exception(e, path_from, ': unable copy to: %r' % path_to)
488
def copy_to(self, relpaths, other, pb=None):
489
"""Copy a set of entries from self into another Transport.
491
:param relpaths: A list/generator of entries to be copied.
493
if isinstance(other, SFTPTransport) and other._sftp is self._sftp:
494
# Both from & to are on the same remote filesystem
495
# We can use a remote copy, instead of pulling locally, and pushing
497
total = self._get_total(relpaths)
499
for path in relpaths:
500
path_from = self._abspath(relpath)
501
path_to = other._abspath(relpath)
502
self._update_pb(pb, 'copy-to', count, total)
503
self._copy_abspaths(path_from, path_to)
507
return super(SFTPTransport, self).copy_to(relpaths, other, pb=pb)
509
# The dummy implementation just does a simple get + put
510
def copy_entry(path):
511
other.put(path, self.get(path))
513
return self._iterate_over(relpaths, copy_entry, pb, 'copy_to', expand=False)
516
def rename(self, rel_from, rel_to):
517
"""Rename without special overwriting"""
519
self._sftp.rename(self._remote_path(rel_from),
520
self._remote_path(rel_to))
521
except (IOError, paramiko.SSHException), e:
522
self._translate_io_exception(e, rel_from,
523
': unable to rename to %r' % (rel_to))
525
def _rename_and_overwrite(self, abs_from, abs_to):
526
"""Do a fancy rename on the remote server.
528
Using the implementation provided by osutils.
531
fancy_rename(abs_from, abs_to,
532
rename_func=self._sftp.rename,
533
unlink_func=self._sftp.remove)
534
except (IOError, paramiko.SSHException), e:
535
self._translate_io_exception(e, abs_from, ': unable to rename to %r' % (abs_to))
515
537
def move(self, rel_from, rel_to):
516
538
"""Move the item at rel_from to the location at rel_to"""
517
path_from = self._abspath(rel_from)
518
path_to = self._abspath(rel_to)
520
self._sftp.rename(path_from, path_to)
521
except (IOError, paramiko.SSHException), e:
522
self._translate_io_exception(e, path_from, ': unable to move to: %r' % path_to)
539
path_from = self._remote_path(rel_from)
540
path_to = self._remote_path(rel_to)
541
self._rename_and_overwrite(path_from, path_to)
524
543
def delete(self, relpath):
525
544
"""Delete the item at relpath"""
526
path = self._abspath(relpath)
545
path = self._remote_path(relpath)
528
547
self._sftp.remove(path)
529
548
except (IOError, paramiko.SSHException), e:
787
820
WARNING: This breaks the SFTPClient abstraction, so it
788
821
could easily break against an updated version of paramiko.
790
:param relpath: The relative path, where the file should be opened
823
:param abspath: The remote absolute path where the file should be opened
824
:param mode: The mode permissions bits for the new file
792
path = self._sftp._adjust_cwd(self._abspath(relpath))
826
path = self._sftp._adjust_cwd(abspath)
793
827
attr = SFTPAttributes()
794
mode = (SFTP_FLAG_WRITE | SFTP_FLAG_CREATE
830
omode = (SFTP_FLAG_WRITE | SFTP_FLAG_CREATE
795
831
| SFTP_FLAG_TRUNC | SFTP_FLAG_EXCL)
797
t, msg = self._sftp._request(CMD_OPEN, path, mode, attr)
833
t, msg = self._sftp._request(CMD_OPEN, path, omode, attr)
798
834
if t != CMD_HANDLE:
799
835
raise TransportError('Expected an SFTP handle')
800
836
handle = msg.get_string()
801
return SFTPFile(self._sftp, handle, 'w', -1)
837
return SFTPFile(self._sftp, handle, 'wb', -1)
802
838
except (paramiko.SSHException, IOError), e:
803
self._translate_io_exception(e, relpath, ': unable to open',
839
self._translate_io_exception(e, abspath, ': unable to open',
804
840
failure_exc=FileExists)
843
# ------------- server test implementation --------------
847
from bzrlib.tests.stub_sftp import StubServer, StubSFTPServer
849
STUB_SERVER_KEY = """
850
-----BEGIN RSA PRIVATE KEY-----
851
MIICWgIBAAKBgQDTj1bqB4WmayWNPB+8jVSYpZYk80Ujvj680pOTh2bORBjbIAyz
852
oWGW+GUjzKxTiiPvVmxFgx5wdsFvF03v34lEVVhMpouqPAYQ15N37K/ir5XY+9m/
853
d8ufMCkjeXsQkKqFbAlQcnWMCRnOoPHS3I4vi6hmnDDeeYTSRvfLbW0fhwIBIwKB
854
gBIiOqZYaoqbeD9OS9z2K9KR2atlTxGxOJPXiP4ESqP3NVScWNwyZ3NXHpyrJLa0
855
EbVtzsQhLn6rF+TzXnOlcipFvjsem3iYzCpuChfGQ6SovTcOjHV9z+hnpXvQ/fon
856
soVRZY65wKnF7IAoUwTmJS9opqgrN6kRgCd3DASAMd1bAkEA96SBVWFt/fJBNJ9H
857
tYnBKZGw0VeHOYmVYbvMSstssn8un+pQpUm9vlG/bp7Oxd/m+b9KWEh2xPfv6zqU
858
avNwHwJBANqzGZa/EpzF4J8pGti7oIAPUIDGMtfIcmqNXVMckrmzQ2vTfqtkEZsA
859
4rE1IERRyiJQx6EJsz21wJmGV9WJQ5kCQQDwkS0uXqVdFzgHO6S++tjmjYcxwr3g
860
H0CoFYSgbddOT6miqRskOQF3DZVkJT3kyuBgU2zKygz52ukQZMqxCb1fAkASvuTv
861
qfpH87Qq5kQhNKdbbwbmd2NxlNabazPijWuphGTdW0VfJdWfklyS2Kr+iqrs/5wV
862
HhathJt636Eg7oIjAkA8ht3MQ+XSl9yIJIS8gVpbPxSw5OMfw0PjVE7tBdQruiSc
863
nvuQES5C9BMHjF39LZiGH1iLQy7FgdHyoP+eodI7
864
-----END RSA PRIVATE KEY-----
868
class SingleListener(threading.Thread):
870
def __init__(self, callback):
871
threading.Thread.__init__(self)
872
self._callback = callback
873
self._socket = socket.socket()
874
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
875
self._socket.bind(('localhost', 0))
876
self._socket.listen(1)
877
self.port = self._socket.getsockname()[1]
878
self.stop_event = threading.Event()
881
s, _ = self._socket.accept()
882
# now close the listen socket
885
self._callback(s, self.stop_event)
887
pass #Ignore socket errors
889
# probably a failed test
890
warning('Exception from within unit test server thread: %r' % x)
893
self.stop_event.set()
894
# use a timeout here, because if the test fails, the server thread may
895
# never notice the stop_event.
899
class SFTPServer(Server):
900
"""Common code for SFTP server facilities."""
903
self._original_vendor = None
905
self._server_homedir = None
906
self._listener = None
908
self._vendor = 'none'
912
def _get_sftp_url(self, path):
913
"""Calculate an sftp url to this server for path."""
914
return 'sftp://foo:bar@localhost:%d/%s' % (self._listener.port, path)
916
def log(self, message):
917
"""StubServer uses this to log when a new server is created."""
918
self.logs.append(message)
920
def _run_server(self, s, stop_event):
921
ssh_server = paramiko.Transport(s)
922
key_file = os.path.join(self._homedir, 'test_rsa.key')
923
file(key_file, 'w').write(STUB_SERVER_KEY)
924
host_key = paramiko.RSAKey.from_private_key_file(key_file)
925
ssh_server.add_server_key(host_key)
926
server = StubServer(self)
927
ssh_server.set_subsystem_handler('sftp', paramiko.SFTPServer,
928
StubSFTPServer, root=self._root,
929
home=self._server_homedir)
930
event = threading.Event()
931
ssh_server.start_server(event, server)
933
stop_event.wait(30.0)
937
self._original_vendor = _ssh_vendor
938
_ssh_vendor = self._vendor
939
self._homedir = os.getcwdu()
940
if self._server_homedir is None:
941
self._server_homedir = self._homedir
943
# FIXME WINDOWS: _root should be _server_homedir[0]:/
944
self._listener = SingleListener(self._run_server)
945
self._listener.setDaemon(True)
946
self._listener.start()
949
"""See bzrlib.transport.Server.tearDown."""
951
self._listener.stop()
952
_ssh_vendor = self._original_vendor
955
class SFTPFullAbsoluteServer(SFTPServer):
956
"""A test server for sftp transports, using absolute urls and ssh."""
959
"""See bzrlib.transport.Server.get_url."""
960
return self._get_sftp_url(urlescape(self._homedir[1:]))
963
class SFTPServerWithoutSSH(SFTPServer):
964
"""An SFTP server that uses a simple TCP socket pair rather than SSH."""
967
super(SFTPServerWithoutSSH, self).__init__()
968
self._vendor = 'loopback'
970
def _run_server(self, sock, stop_event):
971
class FakeChannel(object):
972
def get_transport(self):
974
def get_log_channel(self):
978
def get_hexdump(self):
981
server = paramiko.SFTPServer(FakeChannel(), 'sftp', StubServer(self), StubSFTPServer,
982
root=self._root, home=self._server_homedir)
983
server.start_subsystem('sftp', None, sock)
984
server.finish_subsystem()
987
class SFTPAbsoluteServer(SFTPServerWithoutSSH):
988
"""A test server for sftp transports, using absolute urls."""
991
"""See bzrlib.transport.Server.get_url."""
992
return self._get_sftp_url(urlescape(self._homedir[1:]))
995
class SFTPHomeDirServer(SFTPServerWithoutSSH):
996
"""A test server for sftp transports, using homedir relative urls."""
999
"""See bzrlib.transport.Server.get_url."""
1000
return self._get_sftp_url("~/")
1003
class SFTPSiblingAbsoluteServer(SFTPAbsoluteServer):
1004
"""A test servere for sftp transports, using absolute urls to non-home."""
1007
self._server_homedir = '/dev/noone/runs/tests/here'
1008
super(SFTPSiblingAbsoluteServer, self).setUp()
1011
def get_test_permutations():
1012
"""Return the permutations to be used in testing."""
1013
return [(SFTPTransport, SFTPAbsoluteServer),
1014
(SFTPTransport, SFTPHomeDirServer),
1015
(SFTPTransport, SFTPSiblingAbsoluteServer),