/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
1
# Copyright (C) 2005 Robey Pointer <robey@lag.net>, Canonical Ltd
2
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
"""Implementation of Transport over SFTP, using paramiko."""
18
1489 by Robert Collins
Make the paramiko tests pass. Nice huh.
19
import errno
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
20
import getpass
21
import os
22
import re
23
import stat
24
import sys
25
import urllib
1185.48.1 by James Henstridge
Use /usr/bin/ssh if we can.
26
import urlparse
1185.49.1 by John Arbash Meinel
Updating SftpTransport.put() so that it is atomic
27
import time
28
import random
1185.48.1 by James Henstridge
Use /usr/bin/ssh if we can.
29
import subprocess
1185.49.10 by John Arbash Meinel
Use a weakref dictionary to enable re-use of a connection (for sftp).
30
import weakref
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
31
1489 by Robert Collins
Make the paramiko tests pass. Nice huh.
32
from bzrlib.errors import (FileExists, 
1185.31.44 by John Arbash Meinel
Cleaned up Exceptions for all transports.
33
                           TransportNotPossible, NoSuchFile, PathNotChild,
1185.49.3 by John Arbash Meinel
Added a form of locking to sftp branches. Refactored _sftp_open_exclusive to take a relative path
34
                           TransportError,
35
                           LockError)
1185.31.43 by John Arbash Meinel
Reintroduced ensure_config_dir_exists() for sftp
36
from bzrlib.config import config_dir, ensure_config_dir_exists
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
37
from bzrlib.trace import mutter, warning, error
38
from bzrlib.transport import Transport, register_transport
1185.31.47 by John Arbash Meinel
Added a fancy footwork rename to osutils, made SftpTransport use it.
39
from bzrlib.osutils import pathjoin, fancy_rename
1185.50.10 by John Arbash Meinel
Don't import ui_factory directly, in case it gets changed later.
40
import bzrlib.ui
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
41
42
try:
43
    import paramiko
44
except ImportError:
45
    error('The SFTP transport requires paramiko.')
46
    raise
1185.49.2 by John Arbash Meinel
Adding a open_exclusive function since paramiko supports it, but doesn't expose it
47
else:
48
    from paramiko.sftp import (SFTP_FLAG_WRITE, SFTP_FLAG_CREATE,
49
                               SFTP_FLAG_EXCL, SFTP_FLAG_TRUNC,
50
                               CMD_HANDLE, CMD_OPEN)
51
    from paramiko.sftp_attr import SFTPAttributes
52
    from paramiko.sftp_file import SFTPFile
1185.48.1 by James Henstridge
Use /usr/bin/ssh if we can.
53
    from paramiko.sftp_client import SFTPClient
54
55
if 'sftp' not in urlparse.uses_netloc: urlparse.uses_netloc.append('sftp')
56
57
1185.50.2 by John Arbash Meinel
From Matt Lavin: close_fds is not supported on Windows platforms
58
_close_fds = True
59
if sys.platform == 'win32':
60
    # close_fds not supported on win32
61
    _close_fds = False
62
1185.48.1 by James Henstridge
Use /usr/bin/ssh if we can.
63
_ssh_vendor = None
64
def _get_ssh_vendor():
65
    """Find out what version of SSH is on the system."""
1185.48.2 by James Henstridge
Fix some bugs so it actually works
66
    global _ssh_vendor
67
    if _ssh_vendor is not None:
68
        return _ssh_vendor
1185.48.1 by James Henstridge
Use /usr/bin/ssh if we can.
69
1185.48.2 by James Henstridge
Fix some bugs so it actually works
70
    _ssh_vendor = 'none'
1185.48.1 by James Henstridge
Use /usr/bin/ssh if we can.
71
72
    try:
73
        p = subprocess.Popen(['ssh', '-V'],
1185.50.2 by John Arbash Meinel
From Matt Lavin: close_fds is not supported on Windows platforms
74
                             close_fds=_close_fds,
1185.48.1 by James Henstridge
Use /usr/bin/ssh if we can.
75
                             stdin=subprocess.PIPE,
76
                             stdout=subprocess.PIPE,
77
                             stderr=subprocess.PIPE)
78
        returncode = p.returncode
79
        stdout, stderr = p.communicate()
80
    except OSError:
81
        returncode = -1
82
        stdout = stderr = ''
83
    if 'OpenSSH' in stderr:
1185.48.2 by James Henstridge
Fix some bugs so it actually works
84
        mutter('ssh implementation is OpenSSH')
85
        _ssh_vendor = 'openssh'
1185.48.1 by James Henstridge
Use /usr/bin/ssh if we can.
86
    elif 'SSH Secure Shell' in stderr:
1185.48.2 by James Henstridge
Fix some bugs so it actually works
87
        mutter('ssh implementation is SSH Corp.')
88
        _ssh_vendor = 'ssh'
1185.48.1 by James Henstridge
Use /usr/bin/ssh if we can.
89
1185.48.2 by James Henstridge
Fix some bugs so it actually works
90
    if _ssh_vendor != 'none':
91
        return _ssh_vendor
1185.48.1 by James Henstridge
Use /usr/bin/ssh if we can.
92
93
    # XXX: 20051123 jamesh
94
    # A check for putty's plink or lsh would go here.
95
1185.48.2 by James Henstridge
Fix some bugs so it actually works
96
    mutter('falling back to paramiko implementation')
97
    return _ssh_vendor
1185.48.1 by James Henstridge
Use /usr/bin/ssh if we can.
98
99
100
class SFTPSubprocess:
101
    """A socket-like object that talks to an ssh subprocess via pipes."""
102
    def __init__(self, hostname, port=None, user=None):
103
        vendor = _get_ssh_vendor()
104
        assert vendor in ['openssh', 'ssh']
105
        if vendor == 'openssh':
106
            args = ['ssh',
107
                    '-oForwardX11=no', '-oForwardAgent=no',
108
                    '-oClearAllForwardings=yes', '-oProtocol=2',
109
                    '-oNoHostAuthenticationForLocalhost=yes']
110
            if port is not None:
111
                args.extend(['-p', str(port)])
112
            if user is not None:
113
                args.extend(['-l', user])
114
            args.extend(['-s', hostname, 'sftp'])
115
        elif vendor == 'ssh':
116
            args = ['ssh', '-x']
117
            if port is not None:
118
                args.extend(['-p', str(port)])
119
            if user is not None:
120
                args.extend(['-l', user])
121
            args.extend(['-s', 'sftp', hostname])
122
1185.50.2 by John Arbash Meinel
From Matt Lavin: close_fds is not supported on Windows platforms
123
        self.proc = subprocess.Popen(args, close_fds=_close_fds,
1185.48.1 by James Henstridge
Use /usr/bin/ssh if we can.
124
                                     stdin=subprocess.PIPE,
125
                                     stdout=subprocess.PIPE)
126
127
    def send(self, data):
128
        return os.write(self.proc.stdin.fileno(), data)
129
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
130
    def recv_ready(self):
131
        # TODO: jam 20051215 this function is necessary to support the
132
        # pipelined() function. In reality, it probably should use
133
        # poll() or select() to actually return if there is data
134
        # available, otherwise we probably don't get any benefit
135
        return True
136
1185.48.1 by James Henstridge
Use /usr/bin/ssh if we can.
137
    def recv(self, count):
138
        return os.read(self.proc.stdout.fileno(), count)
139
140
    def close(self):
141
        self.proc.stdin.close()
142
        self.proc.stdout.close()
143
        self.proc.wait()
144
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
145
146
SYSTEM_HOSTKEYS = {}
147
BZR_HOSTKEYS = {}
148
1185.49.10 by John Arbash Meinel
Use a weakref dictionary to enable re-use of a connection (for sftp).
149
# This is a weakref dictionary, so that we can reuse connections
150
# that are still active. Long term, it might be nice to have some
151
# sort of expiration policy, such as disconnect if inactive for
152
# X seconds. But that requires a lot more fanciness.
153
_connected_hosts = weakref.WeakValueDictionary()
154
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
155
def load_host_keys():
156
    """
157
    Load system host keys (probably doesn't work on windows) and any
158
    "discovered" keys from previous sessions.
159
    """
160
    global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
161
    try:
162
        SYSTEM_HOSTKEYS = paramiko.util.load_host_keys(os.path.expanduser('~/.ssh/known_hosts'))
163
    except Exception, e:
164
        mutter('failed to load system host keys: ' + str(e))
1185.31.33 by John Arbash Meinel
A couple more path.join statements needed changing.
165
    bzr_hostkey_path = pathjoin(config_dir(), 'ssh_host_keys')
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
166
    try:
167
        BZR_HOSTKEYS = paramiko.util.load_host_keys(bzr_hostkey_path)
168
    except Exception, e:
169
        mutter('failed to load bzr host keys: ' + str(e))
170
        save_host_keys()
171
172
def save_host_keys():
173
    """
174
    Save "discovered" host keys in $(config)/ssh_host_keys/.
175
    """
176
    global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
1185.31.33 by John Arbash Meinel
A couple more path.join statements needed changing.
177
    bzr_hostkey_path = pathjoin(config_dir(), 'ssh_host_keys')
1185.31.43 by John Arbash Meinel
Reintroduced ensure_config_dir_exists() for sftp
178
    ensure_config_dir_exists()
179
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
180
    try:
181
        f = open(bzr_hostkey_path, 'w')
182
        f.write('# SSH host keys collected by bzr\n')
183
        for hostname, keys in BZR_HOSTKEYS.iteritems():
184
            for keytype, key in keys.iteritems():
185
                f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
186
        f.close()
187
    except IOError, e:
188
        mutter('failed to save bzr host keys: ' + str(e))
189
190
1185.49.3 by John Arbash Meinel
Added a form of locking to sftp branches. Refactored _sftp_open_exclusive to take a relative path
191
class SFTPLock(object):
192
    """This fakes a lock in a remote location."""
193
    __slots__ = ['path', 'lock_path', 'lock_file', 'transport']
194
    def __init__(self, path, transport):
195
        assert isinstance(transport, SFTPTransport)
196
197
        self.lock_file = None
198
        self.path = path
199
        self.lock_path = path + '.write-lock'
200
        self.transport = transport
201
        try:
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
202
            abspath = transport._abspath(self.lock_path)
203
            self.lock_file = transport._sftp_open_exclusive(abspath)
1185.49.3 by John Arbash Meinel
Added a form of locking to sftp branches. Refactored _sftp_open_exclusive to take a relative path
204
        except FileExists:
205
            raise LockError('File %r already locked' % (self.path,))
206
207
    def __del__(self):
208
        """Should this warn, or actually try to cleanup?"""
209
        if self.lock_file:
210
            warn("SFTPLock %r not explicitly unlocked" % (self.path,))
211
            self.unlock()
212
213
    def unlock(self):
214
        if not self.lock_file:
215
            return
216
        self.lock_file.close()
217
        self.lock_file = None
218
        try:
219
            self.transport.delete(self.lock_path)
220
        except (NoSuchFile,):
221
            # What specific errors should we catch here?
222
            pass
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
223
224
class SFTPTransport (Transport):
225
    """
226
    Transport implementation for SFTP access.
227
    """
1185.49.16 by John Arbash Meinel
Disabling prefetch
228
    _do_prefetch = False # Right now Paramiko's prefetch support causes things to hang
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
229
230
    def __init__(self, base, clone_from=None):
231
        assert base.startswith('sftp://')
1185.49.6 by John Arbash Meinel
Fixed the double rename, to rename the safety in case of problem.
232
        self._parse_url(base)
233
        base = self._unparse_url()
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
234
        super(SFTPTransport, self).__init__(base)
235
        if clone_from is None:
236
            self._sftp_connect()
237
        else:
238
            # use the same ssh connection, etc
239
            self._sftp = clone_from._sftp
240
        # super saves 'self.base'
241
    
242
    def should_cache(self):
243
        """
244
        Return True if the data pulled across should be cached locally.
245
        """
246
        return True
247
248
    def clone(self, offset=None):
249
        """
250
        Return a new SFTPTransport with root at self.base + offset.
251
        We share the same SFTP session between such transports, because it's
252
        fairly expensive to set them up.
253
        """
254
        if offset is None:
255
            return SFTPTransport(self.base, self)
256
        else:
257
            return SFTPTransport(self.abspath(offset), self)
258
259
    def abspath(self, relpath):
260
        """
261
        Return the full url to the given relative path.
262
        
263
        @param relpath: the relative path or path components
264
        @type relpath: str or list
265
        """
266
        return self._unparse_url(self._abspath(relpath))
267
    
268
    def _abspath(self, relpath):
269
        """Return the absolute path segment without the SFTP URL."""
270
        # FIXME: share the common code across transports
271
        assert isinstance(relpath, basestring)
272
        relpath = [urllib.unquote(relpath)]
273
        basepath = self._path.split('/')
274
        if len(basepath) > 0 and basepath[-1] == '':
275
            basepath = basepath[:-1]
276
277
        for p in relpath:
278
            if p == '..':
279
                if len(basepath) == 0:
280
                    # In most filesystems, a request for the parent
281
                    # of root, just returns root.
282
                    continue
283
                basepath.pop()
284
            elif p == '.':
285
                continue # No-op
286
            else:
287
                basepath.append(p)
288
289
        path = '/'.join(basepath)
1185.40.4 by Robey Pointer
fix sftp urls to support the ietf draft url spec wrt relative vs absolute sftp urls (this will break existing branch urls); fix username/password parsing in sftp urls; add unit tests to make sure sftp url parsing is working
290
        # could still be a "relative" path here, but relative on the sftp server
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
291
        return path
292
293
    def relpath(self, abspath):
1185.48.8 by James Henstridge
More URL handling fixes
294
        username, password, host, port, path = self._split_url(abspath)
1185.49.23 by John Arbash Meinel
bugreport from Matthieu Moy: relpath was failing, but throwing an unhelpful exception.
295
        error = []
296
        if (username != self._username):
297
            error.append('username mismatch')
298
        if (host != self._host):
299
            error.append('host mismatch')
300
        if (port != self._port):
301
            error.append('port mismatch')
302
        if (not path.startswith(self._path)):
303
            error.append('path mismatch')
304
        if error:
1185.31.44 by John Arbash Meinel
Cleaned up Exceptions for all transports.
305
            extra = ': ' + ', '.join(error)
306
            raise PathNotChild(abspath, self.base, extra=extra)
1185.48.8 by James Henstridge
More URL handling fixes
307
        pl = len(self._path)
308
        return path[pl:].lstrip('/')
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
309
310
    def has(self, relpath):
311
        """
312
        Does the target location exist?
313
        """
314
        try:
315
            self._sftp.stat(self._abspath(relpath))
316
            return True
317
        except IOError:
318
            return False
319
1540.3.2 by Martin Pool
Remove obsolete (and no-op) `decode` parameter to `Transport.get`.
320
    def get(self, relpath):
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
321
        """
322
        Get the file at the given relative path.
323
324
        :param relpath: The relative path to the file
325
        """
326
        try:
327
            path = self._abspath(relpath)
1185.31.51 by John Arbash Meinel
Setting binary flags for sftp.
328
            f = self._sftp.file(path, mode='rb')
1185.49.16 by John Arbash Meinel
Disabling prefetch
329
            if self._do_prefetch and hasattr(f, 'prefetch'):
1185.40.1 by Robey Pointer
prefetch files under paramiko 1.5.1 for improved speed
330
                f.prefetch()
331
            return f
1185.50.13 by John Arbash Meinel
Expanded the Transport test suite. Including delete, copy, move, etc. Updated SftpTransport to conform.
332
        except (IOError, paramiko.SSHException), e:
333
            self._translate_io_exception(e, path, ': error retrieving')
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
334
335
    def get_partial(self, relpath, start, length=None):
336
        """
337
        Get just part of a file.
338
339
        :param relpath: Path to the file, relative to base
340
        :param start: The starting position to read from
341
        :param length: The length to read. A length of None indicates
342
                       read to the end of the file.
343
        :return: A file-like object containing at least the specified bytes.
344
                 Some implementations may return objects which can be read
345
                 past this length, but this is not guaranteed.
346
        """
1185.49.2 by John Arbash Meinel
Adding a open_exclusive function since paramiko supports it, but doesn't expose it
347
        # TODO: implement get_partial_multi to help with knit support
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
348
        f = self.get(relpath)
349
        f.seek(start)
1185.49.16 by John Arbash Meinel
Disabling prefetch
350
        if self._do_prefetch and hasattr(f, 'prefetch'):
1185.40.1 by Robey Pointer
prefetch files under paramiko 1.5.1 for improved speed
351
            f.prefetch()
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
352
        return f
353
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
354
    def put(self, relpath, f, mode=None):
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
355
        """
356
        Copy the file-like or string object into the location.
357
358
        :param relpath: Location to put the contents, relative to base.
359
        :param f:       File-like or string object.
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
360
        :param mode: The final mode for the file
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
361
        """
1185.49.3 by John Arbash Meinel
Added a form of locking to sftp branches. Refactored _sftp_open_exclusive to take a relative path
362
        final_path = self._abspath(relpath)
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
363
        self._put(final_path, f, mode=mode)
364
365
    def _put(self, abspath, f, mode=None):
366
        """Helper function so both put() and copy_abspaths can reuse the code"""
367
        tmp_abspath = '%s.tmp.%.9f.%d.%d' % (abspath, time.time(),
1185.49.3 by John Arbash Meinel
Added a form of locking to sftp branches. Refactored _sftp_open_exclusive to take a relative path
368
                        os.getpid(), random.randint(0,0x7FFFFFFF))
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
369
        fout = self._sftp_open_exclusive(tmp_abspath, mode=mode)
1185.31.47 by John Arbash Meinel
Added a fancy footwork rename to osutils, made SftpTransport use it.
370
        closed = False
1185.41.6 by Robey Pointer
modified version of john's patch to add atomic put and locking to the sftp transport
371
        try:
1185.49.1 by John Arbash Meinel
Updating SftpTransport.put() so that it is atomic
372
            try:
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
373
                fout.set_pipelined(True)
1185.49.1 by John Arbash Meinel
Updating SftpTransport.put() so that it is atomic
374
                self._pump(f, fout)
1185.31.47 by John Arbash Meinel
Added a fancy footwork rename to osutils, made SftpTransport use it.
375
            except (IOError, paramiko.SSHException), e:
376
                self._translate_io_exception(e, tmp_abspath)
1185.50.20 by John Arbash Meinel
merge permissions branch, also fixup tests so they are lined up with bzr.dev to help prevent conflicts.
377
            if mode is not None:
378
                self._sftp.chmod(tmp_abspath, mode)
1185.31.47 by John Arbash Meinel
Added a fancy footwork rename to osutils, made SftpTransport use it.
379
            fout.close()
380
            closed = True
1185.50.20 by John Arbash Meinel
merge permissions branch, also fixup tests so they are lined up with bzr.dev to help prevent conflicts.
381
            self._rename(tmp_abspath, abspath)
1185.49.1 by John Arbash Meinel
Updating SftpTransport.put() so that it is atomic
382
        except Exception, e:
383
            # If we fail, try to clean up the temporary file
384
            # before we throw the exception
385
            # but don't let another exception mess things up
1185.50.20 by John Arbash Meinel
merge permissions branch, also fixup tests so they are lined up with bzr.dev to help prevent conflicts.
386
            # Write out the traceback, because otherwise
387
            # the catch and throw destroys it
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
388
            import traceback
389
            mutter(traceback.format_exc())
1185.49.1 by John Arbash Meinel
Updating SftpTransport.put() so that it is atomic
390
            try:
1185.31.47 by John Arbash Meinel
Added a fancy footwork rename to osutils, made SftpTransport use it.
391
                if not closed:
392
                    fout.close()
1185.49.3 by John Arbash Meinel
Added a form of locking to sftp branches. Refactored _sftp_open_exclusive to take a relative path
393
                self._sftp.remove(tmp_abspath)
1185.49.1 by John Arbash Meinel
Updating SftpTransport.put() so that it is atomic
394
            except:
1532 by Robert Collins
Merge in John Meinels integration branch.
395
                # raise the saved except
396
                raise e
397
            # raise the original with its traceback if we can.
398
            raise
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
399
400
    def iter_files_recursive(self):
401
        """Walk the relative paths of all files in this transport."""
402
        queue = list(self.list_dir('.'))
403
        while queue:
1185.12.96 by Aaron Bentley
Merge from mpool
404
            relpath = urllib.quote(queue.pop(0))
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
405
            st = self.stat(relpath)
406
            if stat.S_ISDIR(st.st_mode):
407
                for i, basename in enumerate(self.list_dir(relpath)):
408
                    queue.insert(i, relpath+'/'+basename)
409
            else:
410
                yield relpath
411
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
412
    def mkdir(self, relpath, mode=None):
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
413
        """Create a directory at the given path."""
414
        try:
415
            path = self._abspath(relpath)
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
416
            # In the paramiko documentation, it says that passing a mode flag 
417
            # will filtered against the server umask.
418
            # StubSFTPServer does not do this, which would be nice, because it is
419
            # what we really want :)
420
            # However, real servers do use umask, so we really should do it that way
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
421
            self._sftp.mkdir(path)
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
422
            if mode is not None:
423
                self._sftp.chmod(path, mode=mode)
1185.31.44 by John Arbash Meinel
Cleaned up Exceptions for all transports.
424
        except (paramiko.SSHException, IOError), e:
1185.58.4 by John Arbash Meinel
Added permission checking to Branch, and propogated that change into the stores.
425
            self._translate_io_exception(e, path, ': unable to mkdir',
1185.50.13 by John Arbash Meinel
Expanded the Transport test suite. Including delete, copy, move, etc. Updated SftpTransport to conform.
426
                failure_exc=FileExists)
427
428
    def _translate_io_exception(self, e, path, more_info='', failure_exc=NoSuchFile):
429
        """Translate a paramiko or IOError into a friendlier exception.
430
431
        :param e: The original exception
432
        :param path: The path in question when the error is raised
433
        :param more_info: Extra information that can be included,
434
                          such as what was going on
435
        :param failure_exc: Paramiko has the super fun ability to raise completely
436
                           opaque errors that just set "e.args = ('Failure',)" with
437
                           no more information.
438
                           This sometimes means FileExists, but it also sometimes
439
                           means NoSuchFile
440
        """
1489 by Robert Collins
Make the paramiko tests pass. Nice huh.
441
        # paramiko seems to generate detailless errors.
1185.50.13 by John Arbash Meinel
Expanded the Transport test suite. Including delete, copy, move, etc. Updated SftpTransport to conform.
442
        self._translate_error(e, path, raise_generic=False)
1185.31.44 by John Arbash Meinel
Cleaned up Exceptions for all transports.
443
        if hasattr(e, 'args'):
444
            if (e.args == ('No such file or directory',) or
445
                e.args == ('No such file',)):
1185.50.13 by John Arbash Meinel
Expanded the Transport test suite. Including delete, copy, move, etc. Updated SftpTransport to conform.
446
                raise NoSuchFile(path, str(e) + more_info)
1185.31.44 by John Arbash Meinel
Cleaned up Exceptions for all transports.
447
            if (e.args == ('mkdir failed',)):
1185.50.13 by John Arbash Meinel
Expanded the Transport test suite. Including delete, copy, move, etc. Updated SftpTransport to conform.
448
                raise FileExists(path, str(e) + more_info)
1185.31.44 by John Arbash Meinel
Cleaned up Exceptions for all transports.
449
            # strange but true, for the paramiko server.
450
            if (e.args == ('Failure',)):
1185.50.13 by John Arbash Meinel
Expanded the Transport test suite. Including delete, copy, move, etc. Updated SftpTransport to conform.
451
                raise failure_exc(path, str(e) + more_info)
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
452
            mutter('Raising exception with args %s', e.args)
453
        if hasattr(e, 'errno'):
454
            mutter('Raising exception with errno %s', e.errno)
1185.31.44 by John Arbash Meinel
Cleaned up Exceptions for all transports.
455
        raise e
1489 by Robert Collins
Make the paramiko tests pass. Nice huh.
456
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
457
    def append(self, relpath, f):
458
        """
459
        Append the text in the file-like object into the final
460
        location.
461
        """
462
        try:
463
            path = self._abspath(relpath)
464
            fout = self._sftp.file(path, 'ab')
465
            self._pump(f, fout)
1185.50.13 by John Arbash Meinel
Expanded the Transport test suite. Including delete, copy, move, etc. Updated SftpTransport to conform.
466
        except (IOError, paramiko.SSHException), e:
467
            self._translate_io_exception(e, relpath, ': unable to append')
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
468
469
    def copy(self, rel_from, rel_to):
470
        """Copy the item at rel_from to the location at rel_to"""
471
        path_from = self._abspath(rel_from)
472
        path_to = self._abspath(rel_to)
1185.49.11 by John Arbash Meinel
Setting up framework for making sftp remote copy faster
473
        self._copy_abspaths(path_from, path_to)
474
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
475
    def _copy_abspaths(self, path_from, path_to, mode=None):
1185.49.11 by John Arbash Meinel
Setting up framework for making sftp remote copy faster
476
        """Copy files given an absolute path
477
478
        :param path_from: Path on remote server to read
479
        :param path_to: Path on remote server to write
480
        :return: None
481
482
        TODO: Should the destination location be atomically created?
483
              This has not been specified
484
        TODO: This should use some sort of remote copy, rather than
485
              pulling the data locally, and then writing it remotely
486
        """
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
487
        try:
488
            fin = self._sftp.file(path_from, 'rb')
489
            try:
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
490
                self._put(path_to, fin, mode=mode)
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
491
            finally:
492
                fin.close()
1185.50.13 by John Arbash Meinel
Expanded the Transport test suite. Including delete, copy, move, etc. Updated SftpTransport to conform.
493
        except (IOError, paramiko.SSHException), e:
494
            self._translate_io_exception(e, path_from, ': unable copy to: %r' % path_to)
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
495
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
496
    def copy_to(self, relpaths, other, mode=None, pb=None):
1185.49.11 by John Arbash Meinel
Setting up framework for making sftp remote copy faster
497
        """Copy a set of entries from self into another Transport.
498
499
        :param relpaths: A list/generator of entries to be copied.
500
        """
501
        if isinstance(other, SFTPTransport) and other._sftp is self._sftp:
502
            # Both from & to are on the same remote filesystem
503
            # We can use a remote copy, instead of pulling locally, and pushing
504
505
            total = self._get_total(relpaths)
506
            count = 0
507
            for path in relpaths:
508
                path_from = self._abspath(relpath)
509
                path_to = other._abspath(relpath)
510
                self._update_pb(pb, 'copy-to', count, total)
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
511
                self._copy_abspaths(path_from, path_to, mode=mode)
1185.49.11 by John Arbash Meinel
Setting up framework for making sftp remote copy faster
512
                count += 1
513
            return count
514
        else:
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
515
            return super(SFTPTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
1185.49.11 by John Arbash Meinel
Setting up framework for making sftp remote copy faster
516
1185.31.47 by John Arbash Meinel
Added a fancy footwork rename to osutils, made SftpTransport use it.
517
    def _rename(self, abs_from, abs_to):
518
        """Do a fancy rename on the remote server.
519
        
520
        Using the implementation provided by osutils.
521
        """
522
        try:
523
            fancy_rename(abs_from, abs_to,
524
                    rename_func=self._sftp.rename,
525
                    unlink_func=self._sftp.remove)
526
        except (IOError, paramiko.SSHException), e:
527
            self._translate_io_exception(e, abs_from, ': unable to rename to %r' % (abs_to))
528
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
529
    def move(self, rel_from, rel_to):
530
        """Move the item at rel_from to the location at rel_to"""
531
        path_from = self._abspath(rel_from)
532
        path_to = self._abspath(rel_to)
1185.31.47 by John Arbash Meinel
Added a fancy footwork rename to osutils, made SftpTransport use it.
533
        self._rename(path_from, path_to)
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
534
535
    def delete(self, relpath):
536
        """Delete the item at relpath"""
537
        path = self._abspath(relpath)
538
        try:
539
            self._sftp.remove(path)
1185.50.13 by John Arbash Meinel
Expanded the Transport test suite. Including delete, copy, move, etc. Updated SftpTransport to conform.
540
        except (IOError, paramiko.SSHException), e:
541
            self._translate_io_exception(e, path, ': unable to delete')
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
542
            
543
    def listable(self):
544
        """Return True if this store supports listing."""
545
        return True
546
547
    def list_dir(self, relpath):
548
        """
549
        Return a list of all files at the given location.
550
        """
551
        # does anything actually use this?
552
        path = self._abspath(relpath)
553
        try:
554
            return self._sftp.listdir(path)
1185.50.13 by John Arbash Meinel
Expanded the Transport test suite. Including delete, copy, move, etc. Updated SftpTransport to conform.
555
        except (IOError, paramiko.SSHException), e:
556
            self._translate_io_exception(e, path, ': failed to list_dir')
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
557
558
    def stat(self, relpath):
559
        """Return the stat information for a file."""
560
        path = self._abspath(relpath)
561
        try:
562
            return self._sftp.stat(path)
1185.50.13 by John Arbash Meinel
Expanded the Transport test suite. Including delete, copy, move, etc. Updated SftpTransport to conform.
563
        except (IOError, paramiko.SSHException), e:
564
            self._translate_io_exception(e, path, ': unable to stat')
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
565
566
    def lock_read(self, relpath):
567
        """
568
        Lock the given file for shared (read) access.
1185.49.3 by John Arbash Meinel
Added a form of locking to sftp branches. Refactored _sftp_open_exclusive to take a relative path
569
        :return: A lock object, which has an unlock() member function
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
570
        """
571
        # FIXME: there should be something clever i can do here...
572
        class BogusLock(object):
573
            def __init__(self, path):
574
                self.path = path
575
            def unlock(self):
576
                pass
577
        return BogusLock(relpath)
578
579
    def lock_write(self, relpath):
580
        """
581
        Lock the given file for exclusive (write) access.
582
        WARNING: many transports do not support this, so trying avoid using it
583
1185.49.3 by John Arbash Meinel
Added a form of locking to sftp branches. Refactored _sftp_open_exclusive to take a relative path
584
        :return: A lock object, which has an unlock() member function
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
585
        """
1185.49.2 by John Arbash Meinel
Adding a open_exclusive function since paramiko supports it, but doesn't expose it
586
        # This is a little bit bogus, but basically, we create a file
587
        # which should not already exist, and if it does, we assume
588
        # that there is a lock, and if it doesn't, the we assume
589
        # that we have taken the lock.
1185.49.3 by John Arbash Meinel
Added a form of locking to sftp branches. Refactored _sftp_open_exclusive to take a relative path
590
        return SFTPLock(relpath, self)
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
591
592
593
    def _unparse_url(self, path=None):
594
        if path is None:
1185.48.5 by James Henstridge
Change SFTP url parsing back to treat the path in sftp://host/path as
595
            path = self._path
596
        path = urllib.quote(path)
597
        if path.startswith('/'):
598
            path = '/%2F' + path[1:]
599
        else:
600
            path = '/' + path
1185.48.1 by James Henstridge
Use /usr/bin/ssh if we can.
601
        netloc = urllib.quote(self._host)
602
        if self._username is not None:
603
            netloc = '%s@%s' % (urllib.quote(self._username), netloc)
1185.49.23 by John Arbash Meinel
bugreport from Matthieu Moy: relpath was failing, but throwing an unhelpful exception.
604
        if self._port is not None:
1185.48.1 by James Henstridge
Use /usr/bin/ssh if we can.
605
            netloc = '%s:%d' % (netloc, self._port)
606
607
        return urlparse.urlunparse(('sftp', netloc, path, '', '', ''))
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
608
1185.48.8 by James Henstridge
More URL handling fixes
609
    def _split_url(self, url):
1185.48.3 by James Henstridge
More fixes
610
        if isinstance(url, unicode):
611
            url = url.encode('utf-8')
1185.48.1 by James Henstridge
Use /usr/bin/ssh if we can.
612
        (scheme, netloc, path, params,
613
         query, fragment) = urlparse.urlparse(url, allow_fragments=False)
614
        assert scheme == 'sftp'
1185.48.8 by James Henstridge
More URL handling fixes
615
        username = password = host = port = None
1185.48.1 by James Henstridge
Use /usr/bin/ssh if we can.
616
        if '@' in netloc:
1185.48.8 by James Henstridge
More URL handling fixes
617
            username, host = netloc.split('@', 1)
618
            if ':' in username:
619
                username, password = username.split(':', 1)
620
                password = urllib.unquote(password)
621
            username = urllib.unquote(username)
1185.48.1 by James Henstridge
Use /usr/bin/ssh if we can.
622
        else:
1185.48.8 by James Henstridge
More URL handling fixes
623
            host = netloc
624
625
        if ':' in host:
626
            host, port = host.rsplit(':', 1)
1185.33.67 by Martin Pool
[merge] use /usr/bin/ssh if we can (jamesh)
627
            try:
628
                port = int(port)
629
            except ValueError:
1185.50.13 by John Arbash Meinel
Expanded the Transport test suite. Including delete, copy, move, etc. Updated SftpTransport to conform.
630
                # TODO: Should this be ConnectionError?
1185.31.44 by John Arbash Meinel
Cleaned up Exceptions for all transports.
631
                raise TransportError('%s: invalid port number' % port)
1185.48.8 by James Henstridge
More URL handling fixes
632
        host = urllib.unquote(host)
633
634
        path = urllib.unquote(path)
1185.48.5 by James Henstridge
Change SFTP url parsing back to treat the path in sftp://host/path as
635
636
        # the initial slash should be removed from the path, and treated
637
        # as a homedir relative path (the path begins with a double slash
638
        # if it is absolute).
639
        # see draft-ietf-secsh-scp-sftp-ssh-uri-03.txt
1185.48.8 by James Henstridge
More URL handling fixes
640
        if path.startswith('/'):
641
            path = path[1:]
642
643
        return (username, password, host, port, path)
644
645
    def _parse_url(self, url):
646
        (self._username, self._password,
647
         self._host, self._port, self._path) = self._split_url(url)
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
648
649
    def _sftp_connect(self):
1185.49.14 by John Arbash Meinel
[merge] bzr.dev
650
        """Connect to the remote sftp server.
651
        After this, self._sftp should have a valid connection (or
1185.31.44 by John Arbash Meinel
Cleaned up Exceptions for all transports.
652
        we raise an TransportError 'could not connect').
1185.49.14 by John Arbash Meinel
[merge] bzr.dev
653
654
        TODO: Raise a more reasonable ConnectionFailed exception
655
        """
656
        global _connected_hosts
1185.49.10 by John Arbash Meinel
Use a weakref dictionary to enable re-use of a connection (for sftp).
657
658
        idx = (self._host, self._port, self._username)
659
        try:
660
            self._sftp = _connected_hosts[idx]
661
            return
662
        except KeyError:
663
            pass
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
664
        
1185.48.1 by James Henstridge
Use /usr/bin/ssh if we can.
665
        vendor = _get_ssh_vendor()
666
        if vendor != 'none':
667
            sock = SFTPSubprocess(self._host, self._port, self._username)
668
            self._sftp = SFTPClient(sock)
669
        else:
670
            self._paramiko_connect()
671
1185.49.14 by John Arbash Meinel
[merge] bzr.dev
672
        _connected_hosts[idx] = self._sftp
673
1185.48.1 by James Henstridge
Use /usr/bin/ssh if we can.
674
    def _paramiko_connect(self):
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
675
        global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
676
        
677
        load_host_keys()
1185.48.1 by James Henstridge
Use /usr/bin/ssh if we can.
678
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
679
        try:
1185.49.27 by John Arbash Meinel
James Henstridge confirmed that sftp.py needs self._port or 22
680
            t = paramiko.Transport((self._host, self._port or 22))
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
681
            t.start_client()
1185.50.13 by John Arbash Meinel
Expanded the Transport test suite. Including delete, copy, move, etc. Updated SftpTransport to conform.
682
        except paramiko.SSHException, e:
683
            raise ConnectionError('Unable to reach SSH host %s:%d' %
684
                                  (self._host, self._port), e)
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
685
            
686
        server_key = t.get_remote_server_key()
687
        server_key_hex = paramiko.util.hexify(server_key.get_fingerprint())
688
        keytype = server_key.get_name()
689
        if SYSTEM_HOSTKEYS.has_key(self._host) and SYSTEM_HOSTKEYS[self._host].has_key(keytype):
690
            our_server_key = SYSTEM_HOSTKEYS[self._host][keytype]
691
            our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
692
        elif BZR_HOSTKEYS.has_key(self._host) and BZR_HOSTKEYS[self._host].has_key(keytype):
693
            our_server_key = BZR_HOSTKEYS[self._host][keytype]
694
            our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
695
        else:
696
            warning('Adding %s host key for %s: %s' % (keytype, self._host, server_key_hex))
697
            if not BZR_HOSTKEYS.has_key(self._host):
698
                BZR_HOSTKEYS[self._host] = {}
699
            BZR_HOSTKEYS[self._host][keytype] = server_key
700
            our_server_key = server_key
701
            our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
702
            save_host_keys()
703
        if server_key != our_server_key:
704
            filename1 = os.path.expanduser('~/.ssh/known_hosts')
1185.31.33 by John Arbash Meinel
A couple more path.join statements needed changing.
705
            filename2 = pathjoin(config_dir(), 'ssh_host_keys')
1185.31.44 by John Arbash Meinel
Cleaned up Exceptions for all transports.
706
            raise TransportError('Host keys for %s do not match!  %s != %s' % \
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
707
                (self._host, our_server_key_hex, server_key_hex),
708
                ['Try editing %s or %s' % (filename1, filename2)])
709
1185.49.22 by John Arbash Meinel
Added get_password to the UIFactory, using it inside of sftp.py
710
        self._sftp_auth(t)
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
711
        
712
        try:
713
            self._sftp = t.open_sftp_client()
1185.50.13 by John Arbash Meinel
Expanded the Transport test suite. Including delete, copy, move, etc. Updated SftpTransport to conform.
714
        except paramiko.SSHException, e:
715
            raise ConnectionError('Unable to start sftp client %s:%d' %
716
                                  (self._host, self._port), e)
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
717
1185.49.22 by John Arbash Meinel
Added get_password to the UIFactory, using it inside of sftp.py
718
    def _sftp_auth(self, transport):
719
        # paramiko requires a username, but it might be none if nothing was supplied
720
        # use the local username, just in case.
721
        # We don't override self._username, because if we aren't using paramiko,
722
        # the username might be specified in ~/.ssh/config and we don't want to
723
        # force it to something else
724
        # Also, it would mess up the self.relpath() functionality
725
        username = self._username or getpass.getuser()
726
1185.31.44 by John Arbash Meinel
Cleaned up Exceptions for all transports.
727
        # Paramiko tries to open a socket.AF_UNIX in order to connect
728
        # to ssh-agent. That attribute doesn't exist on win32 (it does in cygwin)
729
        # so we get an AttributeError exception. For now, just don't try to
730
        # connect to an agent if we are on win32
731
        if sys.platform != 'win32':
732
            agent = paramiko.Agent()
733
            for key in agent.get_keys():
734
                mutter('Trying SSH agent key %s' % paramiko.util.hexify(key.get_fingerprint()))
735
                try:
736
                    transport.auth_publickey(username, key)
737
                    return
738
                except paramiko.SSHException, e:
739
                    pass
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
740
        
741
        # okay, try finding id_rsa or id_dss?  (posix only)
1185.49.22 by John Arbash Meinel
Added get_password to the UIFactory, using it inside of sftp.py
742
        if self._try_pkey_auth(transport, paramiko.RSAKey, username, 'id_rsa'):
743
            return
744
        if self._try_pkey_auth(transport, paramiko.DSSKey, username, 'id_dsa'):
745
            return
746
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
747
1185.27.1 by Jelmer Vernooij
Parse passwords in sftp URLs (sftp://foo:bar@localhost/).
748
        if self._password:
749
            try:
1185.49.22 by John Arbash Meinel
Added get_password to the UIFactory, using it inside of sftp.py
750
                transport.auth_password(username, self._password)
1185.27.1 by Jelmer Vernooij
Parse passwords in sftp URLs (sftp://foo:bar@localhost/).
751
                return
752
            except paramiko.SSHException, e:
753
                pass
754
1185.49.22 by John Arbash Meinel
Added get_password to the UIFactory, using it inside of sftp.py
755
            # FIXME: Don't keep a password held in memory if you can help it
756
            #self._password = None
757
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
758
        # give up and ask for a password
1185.50.10 by John Arbash Meinel
Don't import ui_factory directly, in case it gets changed later.
759
        password = bzrlib.ui.ui_factory.get_password(
760
                prompt='SSH %(user)s@%(host)s password',
761
                user=username, host=self._host)
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
762
        try:
1185.49.22 by John Arbash Meinel
Added get_password to the UIFactory, using it inside of sftp.py
763
            transport.auth_password(username, password)
1185.50.13 by John Arbash Meinel
Expanded the Transport test suite. Including delete, copy, move, etc. Updated SftpTransport to conform.
764
        except paramiko.SSHException, e:
765
            raise ConnectionError('Unable to authenticate to SSH host as %s@%s' %
766
                                  (username, self._host), e)
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
767
1185.49.22 by John Arbash Meinel
Added get_password to the UIFactory, using it inside of sftp.py
768
    def _try_pkey_auth(self, transport, pkey_class, username, filename):
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
769
        filename = os.path.expanduser('~/.ssh/' + filename)
770
        try:
771
            key = pkey_class.from_private_key_file(filename)
1185.49.22 by John Arbash Meinel
Added get_password to the UIFactory, using it inside of sftp.py
772
            transport.auth_publickey(username, key)
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
773
            return True
774
        except paramiko.PasswordRequiredException:
1185.50.10 by John Arbash Meinel
Don't import ui_factory directly, in case it gets changed later.
775
            password = bzrlib.ui.ui_factory.get_password(
776
                    prompt='SSH %(filename)s password',
777
                    filename=filename)
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
778
            try:
779
                key = pkey_class.from_private_key_file(filename, password)
1185.49.22 by John Arbash Meinel
Added get_password to the UIFactory, using it inside of sftp.py
780
                transport.auth_publickey(username, key)
1185.16.87 by mbp at sourcefrog
- fix line endings in sftp transport
781
                return True
782
            except paramiko.SSHException:
783
                mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))
784
        except paramiko.SSHException:
785
            mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))
786
        except IOError:
787
            pass
788
        return False
1185.49.2 by John Arbash Meinel
Adding a open_exclusive function since paramiko supports it, but doesn't expose it
789
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
790
    def _sftp_open_exclusive(self, abspath, mode=None):
1185.49.2 by John Arbash Meinel
Adding a open_exclusive function since paramiko supports it, but doesn't expose it
791
        """Open a remote path exclusively.
792
793
        SFTP supports O_EXCL (SFTP_FLAG_EXCL), which fails if
794
        the file already exists. However it does not expose this
795
        at the higher level of SFTPClient.open(), so we have to
796
        sneak away with it.
797
798
        WARNING: This breaks the SFTPClient abstraction, so it
799
        could easily break against an updated version of paramiko.
800
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
801
        :param abspath: The remote absolute path where the file should be opened
802
        :param mode: The mode permissions bits for the new file
1185.49.2 by John Arbash Meinel
Adding a open_exclusive function since paramiko supports it, but doesn't expose it
803
        """
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
804
        path = self._sftp._adjust_cwd(abspath)
1185.49.2 by John Arbash Meinel
Adding a open_exclusive function since paramiko supports it, but doesn't expose it
805
        attr = SFTPAttributes()
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
806
        if mode is not None:
807
            attr.st_mode = mode
808
        omode = (SFTP_FLAG_WRITE | SFTP_FLAG_CREATE 
1185.49.2 by John Arbash Meinel
Adding a open_exclusive function since paramiko supports it, but doesn't expose it
809
                | SFTP_FLAG_TRUNC | SFTP_FLAG_EXCL)
810
        try:
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
811
            t, msg = self._sftp._request(CMD_OPEN, path, omode, attr)
1185.49.2 by John Arbash Meinel
Adding a open_exclusive function since paramiko supports it, but doesn't expose it
812
            if t != CMD_HANDLE:
1185.31.44 by John Arbash Meinel
Cleaned up Exceptions for all transports.
813
                raise TransportError('Expected an SFTP handle')
1185.49.2 by John Arbash Meinel
Adding a open_exclusive function since paramiko supports it, but doesn't expose it
814
            handle = msg.get_string()
1185.31.51 by John Arbash Meinel
Setting binary flags for sftp.
815
            return SFTPFile(self._sftp, handle, 'wb', -1)
1185.31.44 by John Arbash Meinel
Cleaned up Exceptions for all transports.
816
        except (paramiko.SSHException, IOError), e:
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
817
            self._translate_io_exception(e, abspath, ': unable to open',
1185.50.13 by John Arbash Meinel
Expanded the Transport test suite. Including delete, copy, move, etc. Updated SftpTransport to conform.
818
                failure_exc=FileExists)
1185.49.2 by John Arbash Meinel
Adding a open_exclusive function since paramiko supports it, but doesn't expose it
819