32
from bzrlib.config import config_dir, ensure_config_dir_exists
32
33
from bzrlib.errors import (ConnectionError,
34
35
TransportNotPossible, NoSuchFile, PathNotChild,
38
from bzrlib.config import config_dir
39
from bzrlib.osutils import pathjoin, fancy_rename
39
40
from bzrlib.trace import mutter, warning, error
40
41
from bzrlib.transport import Transport, Server, urlescape
167
175
Save "discovered" host keys in $(config)/ssh_host_keys/.
169
177
global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
170
bzr_hostkey_path = os.path.join(config_dir(), 'ssh_host_keys')
171
if not os.path.isdir(config_dir()):
172
os.mkdir(config_dir())
178
bzr_hostkey_path = pathjoin(config_dir(), 'ssh_host_keys')
179
ensure_config_dir_exists()
174
182
f = open(bzr_hostkey_path, 'w')
175
183
f.write('# SSH host keys collected by bzr\n')
352
def put(self, relpath, f):
362
def put(self, relpath, f, mode=None):
354
364
Copy the file-like or string object into the location.
356
366
:param relpath: Location to put the contents, relative to base.
357
367
:param f: File-like or string object.
368
:param mode: The final mode for the file
359
370
final_path = self._remote_path(relpath)
360
tmp_relpath = '%s.tmp.%.9f.%d.%d' % (relpath, time.time(),
371
self._put(final_path, f, mode=mode)
373
def _put(self, abspath, f, mode=None):
374
"""Helper function so both put() and copy_abspaths can reuse the code"""
375
tmp_abspath = '%s.tmp.%.9f.%d.%d' % (abspath, time.time(),
361
376
os.getpid(), random.randint(0,0x7FFFFFFF))
362
tmp_abspath = self._remote_path(tmp_relpath)
363
fout = self._sftp_open_exclusive(tmp_relpath)
377
fout = self._sftp_open_exclusive(tmp_abspath, mode=mode)
381
fout.set_pipelined(True)
367
382
self._pump(f, fout)
368
except (paramiko.SSHException, IOError), e:
369
self._translate_io_exception(e, relpath, ': unable to write')
383
except (IOError, paramiko.SSHException), e:
384
self._translate_io_exception(e, tmp_abspath)
386
self._sftp.chmod(tmp_abspath, mode)
389
self._rename(tmp_abspath, abspath)
370
390
except Exception, e:
371
391
# If we fail, try to clean up the temporary file
372
392
# before we throw the exception
373
393
# but don't let another exception mess things up
394
# Write out the traceback, because otherwise
395
# the catch and throw destroys it
397
mutter(traceback.format_exc())
376
401
self._sftp.remove(tmp_abspath)
381
# sftp rename doesn't allow overwriting, so play tricks:
382
tmp_safety = 'bzr.tmp.%.9f.%d.%d' % (time.time(), os.getpid(), random.randint(0, 0x7FFFFFFF))
383
tmp_safety = self._remote_path(tmp_safety)
385
self._sftp.rename(final_path, tmp_safety)
392
self._sftp.rename(tmp_abspath, final_path)
393
except (paramiko.SSHException, IOError), e:
394
self._translate_io_exception(e, relpath, ': unable to rename')
400
self._sftp.unlink(tmp_safety)
402
self._sftp.rename(tmp_safety, final_path)
403
# raise the saved except
405
# raise the original with its traceback if we can.
404
408
def iter_files_recursive(self):
405
409
"""Walk the relative paths of all files in this transport."""
416
def mkdir(self, relpath):
420
def mkdir(self, relpath, mode=None):
417
421
"""Create a directory at the given path."""
419
423
path = self._remote_path(relpath)
424
# In the paramiko documentation, it says that passing a mode flag
425
# will filtered against the server umask.
426
# StubSFTPServer does not do this, which would be nice, because it is
427
# what we really want :)
428
# However, real servers do use umask, so we really should do it that way
420
429
self._sftp.mkdir(path)
431
self._sftp.chmod(path, mode=mode)
421
432
except (paramiko.SSHException, IOError), e:
422
self._translate_io_exception(e, relpath, ': unable to mkdir',
433
self._translate_io_exception(e, path, ': unable to mkdir',
423
434
failure_exc=FileExists)
425
436
def _translate_io_exception(self, e, path, more_info='', failure_exc=NoSuchFile):
446
457
# strange but true, for the paramiko server.
447
458
if (e.args == ('Failure',)):
448
459
raise failure_exc(path, str(e) + more_info)
460
mutter('Raising exception with args %s', e.args)
461
if hasattr(e, 'errno'):
462
mutter('Raising exception with errno %s', e.errno)
451
465
def append(self, relpath, f):
466
480
path_to = self._remote_path(rel_to)
467
481
self._copy_abspaths(path_from, path_to)
469
def _copy_abspaths(self, path_from, path_to):
483
def _copy_abspaths(self, path_from, path_to, mode=None):
470
484
"""Copy files given an absolute path
472
486
:param path_from: Path on remote server to read
482
496
fin = self._sftp.file(path_from, 'rb')
484
fout = self._sftp.file(path_to, 'wb')
486
fout.set_pipelined(True)
487
self._pump(fin, fout)
498
self._put(path_to, fin, mode=mode)
492
501
except (IOError, paramiko.SSHException), e:
493
502
self._translate_io_exception(e, path_from, ': unable copy to: %r' % path_to)
495
def copy_to(self, relpaths, other, pb=None):
504
def copy_to(self, relpaths, other, mode=None, pb=None):
496
505
"""Copy a set of entries from self into another Transport.
498
507
:param relpaths: A list/generator of entries to be copied.
507
516
path_from = self._remote_path(relpath)
508
517
path_to = other._remote_path(relpath)
509
518
self._update_pb(pb, 'copy-to', count, total)
510
self._copy_abspaths(path_from, path_to)
519
self._copy_abspaths(path_from, path_to, mode=mode)
514
return super(SFTPTransport, self).copy_to(relpaths, other, pb=pb)
516
# The dummy implementation just does a simple get + put
517
def copy_entry(path):
518
other.put(path, self.get(path))
520
return self._iterate_over(relpaths, copy_entry, pb, 'copy_to', expand=False)
523
return super(SFTPTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
525
def _rename(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))
522
537
def move(self, rel_from, rel_to):
523
538
"""Move the item at rel_from to the location at rel_to"""
524
539
path_from = self._remote_path(rel_from)
525
540
path_to = self._remote_path(rel_to)
527
self._sftp.rename(path_from, path_to)
528
except (IOError, paramiko.SSHException), e:
529
self._translate_io_exception(e, path_from, ': unable to move to: %r' % path_to)
541
self._rename(path_from, path_to)
531
543
def delete(self, relpath):
532
544
"""Delete the item at relpath"""
700
712
if server_key != our_server_key:
701
713
filename1 = os.path.expanduser('~/.ssh/known_hosts')
702
filename2 = os.path.join(config_dir(), 'ssh_host_keys')
714
filename2 = pathjoin(config_dir(), 'ssh_host_keys')
703
715
raise TransportError('Host keys for %s do not match! %s != %s' % \
704
716
(self._host, our_server_key_hex, server_key_hex),
705
717
['Try editing %s or %s' % (filename1, filename2)])
794
806
WARNING: This breaks the SFTPClient abstraction, so it
795
807
could easily break against an updated version of paramiko.
797
:param relpath: The relative path, where the file should be opened
809
:param abspath: The remote absolute path where the file should be opened
810
:param mode: The mode permissions bits for the new file
799
path = self._sftp._adjust_cwd(self._remote_path(relpath))
812
path = self._sftp._adjust_cwd(abspath)
800
813
attr = SFTPAttributes()
801
mode = (SFTP_FLAG_WRITE | SFTP_FLAG_CREATE
816
omode = (SFTP_FLAG_WRITE | SFTP_FLAG_CREATE
802
817
| SFTP_FLAG_TRUNC | SFTP_FLAG_EXCL)
804
t, msg = self._sftp._request(CMD_OPEN, path, mode, attr)
819
t, msg = self._sftp._request(CMD_OPEN, path, omode, attr)
805
820
if t != CMD_HANDLE:
806
821
raise TransportError('Expected an SFTP handle')
807
822
handle = msg.get_string()
808
return SFTPFile(self._sftp, handle, 'w', -1)
823
return SFTPFile(self._sftp, handle, 'wb', -1)
809
824
except (paramiko.SSHException, IOError), e:
810
self._translate_io_exception(e, relpath, ': unable to open',
825
self._translate_io_exception(e, abspath, ': unable to open',
811
826
failure_exc=FileExists)