33
33
TransportNotPossible, NoSuchFile, PathNotChild,
36
from bzrlib.config import config_dir
36
from bzrlib.config import config_dir, ensure_config_dir_exists
37
37
from bzrlib.trace import mutter, warning, error
38
38
from bzrlib.transport import Transport, register_transport
39
from bzrlib.osutils import pathjoin, fancy_rename
166
174
Save "discovered" host keys in $(config)/ssh_host_keys/.
168
176
global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
169
bzr_hostkey_path = os.path.join(config_dir(), 'ssh_host_keys')
170
if not os.path.isdir(config_dir()):
171
os.mkdir(config_dir())
177
bzr_hostkey_path = pathjoin(config_dir(), 'ssh_host_keys')
178
ensure_config_dir_exists()
173
181
f = open(bzr_hostkey_path, 'w')
174
182
f.write('# SSH host keys collected by bzr\n')
345
def put(self, relpath, f):
354
def put(self, relpath, f, mode=None):
347
356
Copy the file-like or string object into the location.
349
358
:param relpath: Location to put the contents, relative to base.
350
359
:param f: File-like or string object.
360
:param mode: The final mode for the file
352
362
final_path = self._abspath(relpath)
353
tmp_relpath = '%s.tmp.%.9f.%d.%d' % (relpath, time.time(),
363
self._put(final_path, f, mode=mode)
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(),
354
368
os.getpid(), random.randint(0,0x7FFFFFFF))
355
tmp_abspath = self._abspath(tmp_relpath)
356
fout = self._sftp_open_exclusive(tmp_relpath)
369
fout = self._sftp_open_exclusive(tmp_abspath, mode=mode)
373
fout.set_pipelined(True)
360
374
self._pump(f, fout)
361
except (paramiko.SSHException, IOError), e:
362
self._translate_io_exception(e, relpath, ': unable to write')
375
except (IOError, paramiko.SSHException), e:
376
self._translate_io_exception(e, tmp_abspath)
378
self._sftp.chmod(tmp_abspath, mode)
381
self._rename(tmp_abspath, abspath)
363
382
except Exception, e:
364
383
# If we fail, try to clean up the temporary file
365
384
# before we throw the exception
366
385
# but don't let another exception mess things up
386
# Write out the traceback, because otherwise
387
# the catch and throw destroys it
389
mutter(traceback.format_exc())
369
393
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)
395
# raise the saved except
397
# raise the original with its traceback if we can.
397
400
def iter_files_recursive(self):
398
401
"""Walk the relative paths of all files in this transport."""
409
def mkdir(self, relpath):
412
def mkdir(self, relpath, mode=None):
410
413
"""Create a directory at the given path."""
412
415
path = self._abspath(relpath)
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
413
421
self._sftp.mkdir(path)
423
self._sftp.chmod(path, mode=mode)
414
424
except (paramiko.SSHException, IOError), e:
415
self._translate_io_exception(e, relpath, ': unable to mkdir',
425
self._translate_io_exception(e, path, ': unable to mkdir',
416
426
failure_exc=FileExists)
418
428
def _translate_io_exception(self, e, path, more_info='', failure_exc=NoSuchFile):
439
449
# strange but true, for the paramiko server.
440
450
if (e.args == ('Failure',)):
441
451
raise failure_exc(path, str(e) + more_info)
452
mutter('Raising exception with args %s', e.args)
453
if hasattr(e, 'errno'):
454
mutter('Raising exception with errno %s', e.errno)
444
457
def append(self, relpath, f):
459
472
path_to = self._abspath(rel_to)
460
473
self._copy_abspaths(path_from, path_to)
462
def _copy_abspaths(self, path_from, path_to):
475
def _copy_abspaths(self, path_from, path_to, mode=None):
463
476
"""Copy files given an absolute path
465
478
:param path_from: Path on remote server to read
475
488
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)
490
self._put(path_to, fin, mode=mode)
485
493
except (IOError, paramiko.SSHException), e:
486
494
self._translate_io_exception(e, path_from, ': unable copy to: %r' % path_to)
488
def copy_to(self, relpaths, other, pb=None):
496
def copy_to(self, relpaths, other, mode=None, pb=None):
489
497
"""Copy a set of entries from self into another Transport.
491
499
:param relpaths: A list/generator of entries to be copied.
500
508
path_from = self._abspath(relpath)
501
509
path_to = other._abspath(relpath)
502
510
self._update_pb(pb, 'copy-to', count, total)
503
self._copy_abspaths(path_from, path_to)
511
self._copy_abspaths(path_from, path_to, mode=mode)
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)
515
return super(SFTPTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
517
def _rename(self, abs_from, abs_to):
518
"""Do a fancy rename on the remote server.
520
Using the implementation provided by osutils.
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))
515
529
def move(self, rel_from, rel_to):
516
530
"""Move the item at rel_from to the location at rel_to"""
517
531
path_from = self._abspath(rel_from)
518
532
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)
533
self._rename(path_from, path_to)
524
535
def delete(self, relpath):
525
536
"""Delete the item at relpath"""
692
703
if server_key != our_server_key:
693
704
filename1 = os.path.expanduser('~/.ssh/known_hosts')
694
filename2 = os.path.join(config_dir(), 'ssh_host_keys')
705
filename2 = pathjoin(config_dir(), 'ssh_host_keys')
695
706
raise TransportError('Host keys for %s do not match! %s != %s' % \
696
707
(self._host, our_server_key_hex, server_key_hex),
697
708
['Try editing %s or %s' % (filename1, filename2)])
787
798
WARNING: This breaks the SFTPClient abstraction, so it
788
799
could easily break against an updated version of paramiko.
790
:param relpath: The relative path, where the file should be opened
801
:param abspath: The remote absolute path where the file should be opened
802
:param mode: The mode permissions bits for the new file
792
path = self._sftp._adjust_cwd(self._abspath(relpath))
804
path = self._sftp._adjust_cwd(abspath)
793
805
attr = SFTPAttributes()
794
mode = (SFTP_FLAG_WRITE | SFTP_FLAG_CREATE
808
omode = (SFTP_FLAG_WRITE | SFTP_FLAG_CREATE
795
809
| SFTP_FLAG_TRUNC | SFTP_FLAG_EXCL)
797
t, msg = self._sftp._request(CMD_OPEN, path, mode, attr)
811
t, msg = self._sftp._request(CMD_OPEN, path, omode, attr)
798
812
if t != CMD_HANDLE:
799
813
raise TransportError('Expected an SFTP handle')
800
814
handle = msg.get_string()
801
return SFTPFile(self._sftp, handle, 'w', -1)
815
return SFTPFile(self._sftp, handle, 'wb', -1)
802
816
except (paramiko.SSHException, IOError), e:
803
self._translate_io_exception(e, relpath, ': unable to open',
817
self._translate_io_exception(e, abspath, ': unable to open',
804
818
failure_exc=FileExists)