/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/transport/sftp.py

merge from bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
34
34
                           FileExists, 
35
35
                           TransportNotPossible, NoSuchFile, PathNotChild,
36
36
                           TransportError,
37
 
                           LockError
 
37
                           LockError, ParamikoNotPresent
38
38
                           )
39
39
from bzrlib.osutils import pathjoin, fancy_rename
40
40
from bzrlib.trace import mutter, warning, error
43
43
 
44
44
try:
45
45
    import paramiko
46
 
except ImportError:
47
 
    error('The SFTP transport requires paramiko.')
48
 
    raise
 
46
except ImportError, e:
 
47
    raise ParamikoNotPresent(e)
49
48
else:
50
49
    from paramiko.sftp import (SFTP_FLAG_WRITE, SFTP_FLAG_CREATE,
51
50
                               SFTP_FLAG_EXCL, SFTP_FLAG_TRUNC,
144
143
        self.proc.wait()
145
144
 
146
145
 
 
146
class LoopbackSFTP(object):
 
147
    """Simple wrapper for a socket that pretends to be a paramiko Channel."""
 
148
 
 
149
    def __init__(self, sock):
 
150
        self.__socket = sock
 
151
 
 
152
    def send(self, data):
 
153
        return self.__socket.send(data)
 
154
 
 
155
    def recv(self, n):
 
156
        return self.__socket.recv(n)
 
157
 
 
158
    def recv_ready(self):
 
159
        return True
 
160
 
 
161
    def close(self):
 
162
        self.__socket.close()
 
163
 
 
164
 
147
165
SYSTEM_HOSTKEYS = {}
148
166
BZR_HOSTKEYS = {}
149
167
 
153
171
# X seconds. But that requires a lot more fanciness.
154
172
_connected_hosts = weakref.WeakValueDictionary()
155
173
 
 
174
 
156
175
def load_host_keys():
157
176
    """
158
177
    Load system host keys (probably doesn't work on windows) and any
170
189
        mutter('failed to load bzr host keys: ' + str(e))
171
190
        save_host_keys()
172
191
 
 
192
 
173
193
def save_host_keys():
174
194
    """
175
195
    Save "discovered" host keys in $(config)/ssh_host_keys/.
209
229
    def __del__(self):
210
230
        """Should this warn, or actually try to cleanup?"""
211
231
        if self.lock_file:
212
 
            warn("SFTPLock %r not explicitly unlocked" % (self.path,))
 
232
            warning("SFTPLock %r not explicitly unlocked" % (self.path,))
213
233
            self.unlock()
214
234
 
215
235
    def unlock(self):
224
244
            pass
225
245
 
226
246
 
227
 
 
228
247
class SFTPTransport (Transport):
229
248
    """
230
249
    Transport implementation for SFTP access.
474
493
        except (IOError, paramiko.SSHException), e:
475
494
            self._translate_io_exception(e, relpath, ': unable to append')
476
495
 
477
 
    def copy(self, rel_from, rel_to):
478
 
        """Copy the item at rel_from to the location at rel_to"""
479
 
        path_from = self._remote_path(rel_from)
480
 
        path_to = self._remote_path(rel_to)
481
 
        self._copy_abspaths(path_from, path_to)
482
 
 
483
 
    def _copy_abspaths(self, path_from, path_to, mode=None):
484
 
        """Copy files given an absolute path
485
 
 
486
 
        :param path_from: Path on remote server to read
487
 
        :param path_to: Path on remote server to write
488
 
        :return: None
489
 
 
490
 
        TODO: Should the destination location be atomically created?
491
 
              This has not been specified
492
 
        TODO: This should use some sort of remote copy, rather than
493
 
              pulling the data locally, and then writing it remotely
494
 
        """
495
 
        try:
496
 
            fin = self._sftp.file(path_from, 'rb')
497
 
            try:
498
 
                self._put(path_to, fin, mode=mode)
499
 
            finally:
500
 
                fin.close()
501
 
        except (IOError, paramiko.SSHException), e:
502
 
            self._translate_io_exception(e, path_from, ': unable copy to: %r' % path_to)
503
 
 
504
 
    def copy_to(self, relpaths, other, mode=None, pb=None):
505
 
        """Copy a set of entries from self into another Transport.
506
 
 
507
 
        :param relpaths: A list/generator of entries to be copied.
508
 
        """
509
 
        if isinstance(other, SFTPTransport) and other._sftp is self._sftp:
510
 
            # Both from & to are on the same remote filesystem
511
 
            # We can use a remote copy, instead of pulling locally, and pushing
512
 
 
513
 
            total = self._get_total(relpaths)
514
 
            count = 0
515
 
            for path in relpaths:
516
 
                path_from = self._remote_path(relpath)
517
 
                path_to = other._remote_path(relpath)
518
 
                self._update_pb(pb, 'copy-to', count, total)
519
 
                self._copy_abspaths(path_from, path_to, mode=mode)
520
 
                count += 1
521
 
            return count
522
 
        else:
523
 
            return super(SFTPTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
524
 
 
525
496
    def _rename(self, abs_from, abs_to):
526
497
        """Do a fancy rename on the remote server.
527
498
        
563
534
        except (IOError, paramiko.SSHException), e:
564
535
            self._translate_io_exception(e, path, ': failed to list_dir')
565
536
 
 
537
    def rmdir(self, relpath):
 
538
        """See Transport.rmdir."""
 
539
        path = self._remote_path(relpath)
 
540
        try:
 
541
            return self._sftp.rmdir(path)
 
542
        except (IOError, paramiko.SSHException), e:
 
543
            self._translate_io_exception(e, path, ': failed to rmdir')
 
544
 
566
545
    def stat(self, relpath):
567
546
        """Return the stat information for a file."""
568
547
        path = self._remote_path(relpath)
597
576
        # that we have taken the lock.
598
577
        return SFTPLock(relpath, self)
599
578
 
600
 
 
601
579
    def _unparse_url(self, path=None):
602
580
        if path is None:
603
581
            path = self._path
604
582
        path = urllib.quote(path)
605
 
        if path.startswith('/'):
606
 
            path = '/%2F' + path[1:]
607
 
        else:
608
 
            path = '/' + path
 
583
        # handle homedir paths
 
584
        if not path.startswith('/'):
 
585
            path = "/~/" + path
609
586
        netloc = urllib.quote(self._host)
610
587
        if self._username is not None:
611
588
            netloc = '%s@%s' % (urllib.quote(self._username), netloc)
645
622
        # as a homedir relative path (the path begins with a double slash
646
623
        # if it is absolute).
647
624
        # see draft-ietf-secsh-scp-sftp-ssh-uri-03.txt
648
 
        if path.startswith('/'):
649
 
            path = path[1:]
650
 
 
 
625
        # RBC 20060118 we are not using this as its too user hostile. instead
 
626
        # we are following lftp and using /~/foo to mean '~/foo'.
 
627
        # handle homedir paths
 
628
        if path.startswith('/~/'):
 
629
            path = path[3:]
 
630
        elif path == '/~':
 
631
            path = ''
651
632
        return (username, password, host, port, path)
652
633
 
653
634
    def _parse_url(self, url):
671
652
            pass
672
653
        
673
654
        vendor = _get_ssh_vendor()
674
 
        if vendor != 'none':
 
655
        if vendor == 'loopback':
 
656
            sock = socket.socket()
 
657
            sock.connect((self._host, self._port))
 
658
            self._sftp = SFTPClient(LoopbackSFTP(sock))
 
659
        elif vendor != 'none':
675
660
            sock = SFTPSubprocess(self._host, vendor, self._port,
676
661
                                  self._username)
677
662
            self._sftp = SFTPClient(sock)
687
672
 
688
673
        try:
689
674
            t = paramiko.Transport((self._host, self._port or 22))
 
675
            t.set_log_channel('bzr.paramiko')
690
676
            t.start_client()
691
677
        except paramiko.SSHException, e:
692
678
            raise ConnectionError('Unable to reach SSH host %s:%d' %
867
853
        s, _ = self._socket.accept()
868
854
        # now close the listen socket
869
855
        self._socket.close()
870
 
        self._callback(s, self.stop_event)
871
 
    
 
856
        try:
 
857
            self._callback(s, self.stop_event)
 
858
        except socket.error:
 
859
            pass #Ignore socket errors
 
860
        except Exception, x:
 
861
            # probably a failed test
 
862
            warning('Exception from within unit test server thread: %r' % x)
 
863
 
872
864
    def stop(self):
873
865
        self.stop_event.set()
874
 
        # We should consider waiting for the other thread
875
 
        # to stop, because otherwise we get spurious
876
 
        #   bzr: ERROR: Socket exception: Connection reset by peer (54)
877
 
        # because the test suite finishes before the thread has a chance
878
 
        # to close. (Especially when only running a few tests)
879
 
        
880
 
        
 
866
        # use a timeout here, because if the test fails, the server thread may
 
867
        # never notice the stop_event.
 
868
        self.join(5.0)
 
869
 
 
870
 
881
871
class SFTPServer(Server):
882
872
    """Common code for SFTP server facilities."""
883
873
 
884
 
    def _get_sftp_url(self, path):
885
 
        """Calculate a sftp url to this server for path."""
886
 
        return 'sftp://foo:bar@localhost:%d/%s' % (self._listener.port, path)
887
 
 
888
874
    def __init__(self):
889
875
        self._original_vendor = None
890
876
        self._homedir = None
891
877
        self._server_homedir = None
892
878
        self._listener = None
893
879
        self._root = None
 
880
        self._vendor = 'none'
894
881
        # sftp server logs
895
882
        self.logs = []
896
883
 
 
884
    def _get_sftp_url(self, path):
 
885
        """Calculate an sftp url to this server for path."""
 
886
        return 'sftp://foo:bar@localhost:%d/%s' % (self._listener.port, path)
 
887
 
897
888
    def log(self, message):
898
 
        """What to do here? do we need this? Its for the StubServer.."""
 
889
        """StubServer uses this to log when a new server is created."""
899
890
        self.logs.append(message)
900
891
 
901
892
    def _run_server(self, s, stop_event):
912
903
        ssh_server.start_server(event, server)
913
904
        event.wait(5.0)
914
905
        stop_event.wait(30.0)
915
 
 
 
906
    
916
907
    def setUp(self):
917
 
        """See bzrlib.transport.Server.setUp."""
918
 
        # XXX: 20051124 jamesh
919
 
        # The tests currently pop up a password prompt when an external ssh
920
 
        # is used.  This forces the use of the paramiko implementation.
921
908
        global _ssh_vendor
922
909
        self._original_vendor = _ssh_vendor
923
 
        _ssh_vendor = 'none'
 
910
        _ssh_vendor = self._vendor
924
911
        self._homedir = os.getcwdu()
925
912
        if self._server_homedir is None:
926
913
            self._server_homedir = self._homedir
937
924
        _ssh_vendor = self._original_vendor
938
925
 
939
926
 
940
 
class SFTPAbsoluteServer(SFTPServer):
 
927
class SFTPServerWithoutSSH(SFTPServer):
 
928
    """
 
929
    Common code for an SFTP server over a clear TCP loopback socket,
 
930
    instead of over an SSH secured socket.
 
931
    """
 
932
 
 
933
    def __init__(self):
 
934
        super(SFTPServerWithoutSSH, self).__init__()
 
935
        self._vendor = 'loopback'
 
936
 
 
937
    def _run_server(self, sock, stop_event):
 
938
        class FakeChannel(object):
 
939
            def get_transport(self):
 
940
                return self
 
941
            def get_log_channel(self):
 
942
                return 'paramiko'
 
943
            def get_name(self):
 
944
                return '1'
 
945
            def get_hexdump(self):
 
946
                return False
 
947
 
 
948
        server = paramiko.SFTPServer(FakeChannel(), 'sftp', StubServer(self), StubSFTPServer,
 
949
                                     root=self._root, home=self._server_homedir)
 
950
        server.start_subsystem('sftp', None, sock)
 
951
        server.finish_subsystem()
 
952
 
 
953
 
 
954
class SFTPAbsoluteServer(SFTPServerWithoutSSH):
941
955
    """A test server for sftp transports, using absolute urls."""
942
956
 
943
957
    def get_url(self):
944
958
        """See bzrlib.transport.Server.get_url."""
945
 
        return self._get_sftp_url("%%2f%s" % 
946
 
                urlescape(self._homedir[1:]))
947
 
 
948
 
 
949
 
class SFTPHomeDirServer(SFTPServer):
 
959
        return self._get_sftp_url(urlescape(self._homedir[1:]))
 
960
 
 
961
 
 
962
class SFTPHomeDirServer(SFTPServerWithoutSSH):
950
963
    """A test server for sftp transports, using homedir relative urls."""
951
964
 
952
965
    def get_url(self):
953
966
        """See bzrlib.transport.Server.get_url."""
954
 
        return self._get_sftp_url("")
 
967
        return self._get_sftp_url("~/")
955
968
 
956
969
 
957
970
class SFTPSiblingAbsoluteServer(SFTPAbsoluteServer):