83
59
CMD_HANDLE, CMD_OPEN)
84
60
from paramiko.sftp_attr import SFTPAttributes
85
61
from paramiko.sftp_file import SFTPFile
88
# GZ 2017-05-25: Some dark hackery to monkeypatch out issues with paramiko's
89
# Python 3 compatibility code. Replace broken b() and asbytes() code.
91
from paramiko.py3compat import b as _bad
92
from paramiko.common import asbytes as _bad_asbytes
96
def _b_for_broken_paramiko(s, encoding='utf8'):
97
"""Hacked b() that does not raise TypeError."""
98
# https://github.com/paramiko/paramiko/issues/967
99
if not isinstance(s, bytes):
100
encode = getattr(s, 'encode', None)
101
if encode is not None:
102
return encode(encoding)
103
# Would like to pass buffer objects along, but have to realise.
104
tobytes = getattr(s, 'tobytes', None)
105
if tobytes is not None:
109
def _asbytes_for_broken_paramiko(s):
110
"""Hacked asbytes() that does not raise Exception."""
111
# https://github.com/paramiko/paramiko/issues/968
112
if not isinstance(s, bytes):
113
encode = getattr(s, 'encode', None)
114
if encode is not None:
115
return encode('utf8')
116
asbytes = getattr(s, 'asbytes', None)
117
if asbytes is not None:
121
_bad.__code__ = _b_for_broken_paramiko.__code__
122
_bad_asbytes.__code__ = _asbytes_for_broken_paramiko.__code__
62
from paramiko.sftp_client import SFTPClient
65
register_urlparse_netloc_protocol('sftp')
68
def os_specific_subprocess_params():
69
"""Get O/S specific subprocess parameters."""
70
if sys.platform == 'win32':
71
# setting the process group and closing fds is not supported on
75
# we close fds as the child process does not need them to be open.
76
# we set the process group so that signals from the keyboard like
77
# 'SIGINT' - KeyboardInterrupt - are not recieved in the child procecss
78
# if we do not do this, then the sftp/ssh subprocesses will terminate
79
# when a user hits CTRL-C, and we are unable to use them to unlock the
80
# remote branch/repository etc.
81
return {'preexec_fn': os.setpgrp,
86
# don't use prefetch unless paramiko version >= 1.5.2 (there were bugs earlier)
87
_default_do_prefetch = False
88
if getattr(paramiko, '__version_info__', (0, 0, 0)) >= (1, 5, 5):
89
_default_do_prefetch = True
93
def _get_ssh_vendor():
94
"""Find out what version of SSH is on the system."""
96
if _ssh_vendor is not None:
101
if 'BZR_SSH' in os.environ:
102
_ssh_vendor = os.environ['BZR_SSH']
103
if _ssh_vendor == 'paramiko':
108
p = subprocess.Popen(['ssh', '-V'],
109
stdin=subprocess.PIPE,
110
stdout=subprocess.PIPE,
111
stderr=subprocess.PIPE,
112
**os_specific_subprocess_params())
113
returncode = p.returncode
114
stdout, stderr = p.communicate()
118
if 'OpenSSH' in stderr:
119
mutter('ssh implementation is OpenSSH')
120
_ssh_vendor = 'openssh'
121
elif 'SSH Secure Shell' in stderr:
122
mutter('ssh implementation is SSH Corp.')
125
if _ssh_vendor != 'none':
128
# XXX: 20051123 jamesh
129
# A check for putty's plink or lsh would go here.
131
mutter('falling back to paramiko implementation')
135
class SFTPSubprocess:
136
"""A socket-like object that talks to an ssh subprocess via pipes."""
137
def __init__(self, hostname, vendor, port=None, user=None):
138
assert vendor in ['openssh', 'ssh']
139
if vendor == 'openssh':
141
'-oForwardX11=no', '-oForwardAgent=no',
142
'-oClearAllForwardings=yes', '-oProtocol=2',
143
'-oNoHostAuthenticationForLocalhost=yes']
145
args.extend(['-p', str(port)])
147
args.extend(['-l', user])
148
args.extend(['-s', hostname, 'sftp'])
149
elif vendor == 'ssh':
152
args.extend(['-p', str(port)])
154
args.extend(['-l', user])
155
args.extend(['-s', 'sftp', hostname])
157
self.proc = subprocess.Popen(args,
158
stdin=subprocess.PIPE,
159
stdout=subprocess.PIPE,
160
**os_specific_subprocess_params())
162
def send(self, data):
163
return os.write(self.proc.stdin.fileno(), data)
165
def recv_ready(self):
166
# TODO: jam 20051215 this function is necessary to support the
167
# pipelined() function. In reality, it probably should use
168
# poll() or select() to actually return if there is data
169
# available, otherwise we probably don't get any benefit
172
def recv(self, count):
173
return os.read(self.proc.stdout.fileno(), count)
176
self.proc.stdin.close()
177
self.proc.stdout.close()
181
class LoopbackSFTP(object):
182
"""Simple wrapper for a socket that pretends to be a paramiko Channel."""
184
def __init__(self, sock):
187
def send(self, data):
188
return self.__socket.send(data)
191
return self.__socket.recv(n)
193
def recv_ready(self):
197
self.__socket.close()
203
# This is a weakref dictionary, so that we can reuse connections
204
# that are still active. Long term, it might be nice to have some
205
# sort of expiration policy, such as disconnect if inactive for
206
# X seconds. But that requires a lot more fanciness.
207
_connected_hosts = weakref.WeakValueDictionary()
209
def clear_connection_cache():
210
"""Remove all hosts from the SFTP connection cache.
212
Primarily useful for test cases wanting to force garbage collection.
214
_connected_hosts.clear()
217
def load_host_keys():
219
Load system host keys (probably doesn't work on windows) and any
220
"discovered" keys from previous sessions.
222
global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
224
SYSTEM_HOSTKEYS = paramiko.util.load_host_keys(os.path.expanduser('~/.ssh/known_hosts'))
226
mutter('failed to load system host keys: ' + str(e))
227
bzr_hostkey_path = pathjoin(config_dir(), 'ssh_host_keys')
229
BZR_HOSTKEYS = paramiko.util.load_host_keys(bzr_hostkey_path)
231
mutter('failed to load bzr host keys: ' + str(e))
235
def save_host_keys():
237
Save "discovered" host keys in $(config)/ssh_host_keys/.
239
global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
240
bzr_hostkey_path = pathjoin(config_dir(), 'ssh_host_keys')
241
ensure_config_dir_exists()
244
f = open(bzr_hostkey_path, 'w')
245
f.write('# SSH host keys collected by bzr\n')
246
for hostname, keys in BZR_HOSTKEYS.iteritems():
247
for keytype, key in keys.iteritems():
248
f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
251
mutter('failed to save bzr host keys: ' + str(e))
125
254
class SFTPLock(object):
126
"""This fakes a lock in a remote location.
128
A present lock is indicated just by the existence of a file. This
129
doesn't work well on all transports and they are only used in
130
deprecated storage formats.
255
"""This fakes a lock in a remote location."""
133
256
__slots__ = ['path', 'lock_path', 'lock_file', 'transport']
135
257
def __init__(self, path, transport):
258
assert isinstance(transport, SFTPTransport)
136
260
self.lock_file = None
138
262
self.lock_path = path + '.write-lock'
155
285
# What specific errors should we catch here?
159
class _SFTPReadvHelper(object):
160
"""A class to help with managing the state of a readv request."""
162
# See _get_requests for an explanation.
163
_max_request_size = 32768
165
def __init__(self, original_offsets, relpath, _report_activity):
166
"""Create a new readv helper.
168
:param original_offsets: The original requests given by the caller of
170
:param relpath: The name of the file (if known)
171
:param _report_activity: A Transport._report_activity bound method,
172
to be called as data arrives.
174
self.original_offsets = list(original_offsets)
175
self.relpath = relpath
176
self._report_activity = _report_activity
178
def _get_requests(self):
179
"""Break up the offsets into individual requests over sftp.
181
The SFTP spec only requires implementers to support 32kB requests. We
182
could try something larger (openssh supports 64kB), but then we have to
183
handle requests that fail.
184
So instead, we just break up our maximum chunks into 32kB chunks, and
185
asyncronously requests them.
186
Newer versions of paramiko would do the chunking for us, but we want to
187
start processing results right away, so we do it ourselves.
189
# TODO: Because we issue async requests, we don't 'fudge' any extra
190
# data. I'm not 100% sure that is the best choice.
192
# The first thing we do, is to collapse the individual requests as much
193
# as possible, so we don't issues requests <32kB
194
sorted_offsets = sorted(self.original_offsets)
195
coalesced = list(ConnectedTransport._coalesce_offsets(sorted_offsets,
196
limit=0, fudge_factor=0))
198
for c_offset in coalesced:
199
start = c_offset.start
200
size = c_offset.length
202
# Break this up into 32kB requests
204
next_size = min(size, self._max_request_size)
205
requests.append((start, next_size))
208
if 'sftp' in debug.debug_flags:
209
mutter('SFTP.readv(%s) %s offsets => %s coalesced => %s requests',
210
self.relpath, len(sorted_offsets), len(coalesced),
214
def request_and_yield_offsets(self, fp):
215
"""Request the data from the remote machine, yielding the results.
217
:param fp: A Paramiko SFTPFile object that supports readv.
218
:return: Yield the data requested by the original readv caller, one by
221
requests = self._get_requests()
222
offset_iter = iter(self.original_offsets)
223
cur_offset, cur_size = next(offset_iter)
224
# paramiko .readv() yields strings that are in the order of the requests
225
# So we track the current request to know where the next data is
226
# being returned from.
232
# This is used to buffer chunks which we couldn't process yet
233
# It is (start, end, data) tuples.
235
# Create an 'unlimited' data stream, so we stop based on requests,
236
# rather than just because the data stream ended. This lets us detect
238
data_stream = itertools.chain(fp.readv(requests),
239
itertools.repeat(None))
240
for (start, length), data in zip(requests, data_stream):
242
if cur_coalesced is not None:
243
raise errors.ShortReadvError(self.relpath,
244
start, length, len(data))
245
if len(data) != length:
246
raise errors.ShortReadvError(self.relpath,
247
start, length, len(data))
248
self._report_activity(length, 'read')
250
# This is the first request, just buffer it
251
buffered_data = [data]
252
buffered_len = length
254
elif start == last_end:
255
# The data we are reading fits neatly on the previous
256
# buffer, so this is all part of a larger coalesced range.
257
buffered_data.append(data)
258
buffered_len += length
260
# We have an 'interrupt' in the data stream. So we know we are
261
# at a request boundary.
263
# We haven't consumed the buffer so far, so put it into
264
# data_chunks, and continue.
265
buffered = b''.join(buffered_data)
266
data_chunks.append((input_start, buffered))
268
buffered_data = [data]
269
buffered_len = length
270
last_end = start + length
271
if input_start == cur_offset and cur_size <= buffered_len:
272
# Simplify the next steps a bit by transforming buffered_data
273
# into a single string. We also have the nice property that
274
# when there is only one string ''.join([x]) == x, so there is
276
buffered = b''.join(buffered_data)
277
# Clean out buffered data so that we keep memory
281
# TODO: We *could* also consider the case where cur_offset is in
282
# in the buffered range, even though it doesn't *start*
283
# the buffered range. But for packs we pretty much always
284
# read in order, so you won't get any extra data in the
286
while (input_start == cur_offset
287
and (buffered_offset + cur_size) <= buffered_len):
288
# We've buffered enough data to process this request, spit it
290
cur_data = buffered[buffered_offset:buffered_offset + cur_size]
291
# move the direct pointer into our buffered data
292
buffered_offset += cur_size
293
# Move the start-of-buffer pointer
294
input_start += cur_size
295
# Yield the requested data
296
yield cur_offset, cur_data
298
cur_offset, cur_size = next(offset_iter)
299
except StopIteration:
301
# at this point, we've consumed as much of buffered as we can,
302
# so break off the portion that we consumed
303
if buffered_offset == len(buffered_data):
304
# No tail to leave behind
308
buffered = buffered[buffered_offset:]
309
buffered_data = [buffered]
310
buffered_len = len(buffered)
311
# now that the data stream is done, close the handle
314
buffered = b''.join(buffered_data)
316
data_chunks.append((input_start, buffered))
318
if 'sftp' in debug.debug_flags:
319
mutter('SFTP readv left with %d out-of-order bytes',
320
sum(len(x[1]) for x in data_chunks))
321
# We've processed all the readv data, at this point, anything we
322
# couldn't process is in data_chunks. This doesn't happen often, so
323
# this code path isn't optimized
324
# We use an interesting process for data_chunks
325
# Specifically if we have "bisect_left([(start, len, entries)],
327
# If start == qstart, then we get the specific node. Otherwise we
328
# get the previous node
330
idx = bisect.bisect_left(data_chunks, (cur_offset,))
331
if idx < len(data_chunks) and data_chunks[idx][0] == cur_offset:
332
# The data starts here
333
data = data_chunks[idx][1][:cur_size]
335
# The data is in a portion of a previous page
337
sub_offset = cur_offset - data_chunks[idx][0]
338
data = data_chunks[idx][1]
339
data = data[sub_offset:sub_offset + cur_size]
341
# We are missing the page where the data should be found,
344
if len(data) != cur_size:
345
raise AssertionError('We must have miscalulated.'
346
' We expected %d bytes, but only found %d'
347
% (cur_size, len(data)))
348
yield cur_offset, data
350
cur_offset, cur_size = next(offset_iter)
351
except StopIteration:
355
class SFTPTransport(ConnectedTransport):
356
"""Transport implementation for SFTP access."""
358
# TODO: jam 20060717 Conceivably these could be configurable, either
359
# by auto-tuning at run-time, or by a configuration (per host??)
360
# but the performance curve is pretty flat, so just going with
361
# reasonable defaults.
362
_max_readv_combine = 200
363
# Having to round trip to the server means waiting for a response,
364
# so it is better to download extra bytes.
365
# 8KiB had good performance for both local and remote network operations
366
_bytes_to_read_before_seek = 8192
368
# The sftp spec says that implementations SHOULD allow reads
369
# to be at least 32K. paramiko.readv() does an async request
370
# for the chunks. So we need to keep it within a single request
371
# size for paramiko <= 1.6.1. paramiko 1.6.2 will probably chop
372
# up the request itself, rather than us having to worry about it
373
_max_request_size = 32768
288
class SFTPTransport (Transport):
290
Transport implementation for SFTP access.
292
_do_prefetch = _default_do_prefetch
294
def __init__(self, base, clone_from=None):
295
assert base.startswith('sftp://')
296
self._parse_url(base)
297
base = self._unparse_url()
300
super(SFTPTransport, self).__init__(base)
301
if clone_from is None:
304
# use the same ssh connection, etc
305
self._sftp = clone_from._sftp
306
# super saves 'self.base'
308
def should_cache(self):
310
Return True if the data pulled across should be cached locally.
314
def clone(self, offset=None):
316
Return a new SFTPTransport with root at self.base + offset.
317
We share the same SFTP session between such transports, because it's
318
fairly expensive to set them up.
321
return SFTPTransport(self.base, self)
323
return SFTPTransport(self.abspath(offset), self)
325
def abspath(self, relpath):
327
Return the full url to the given relative path.
329
@param relpath: the relative path or path components
330
@type relpath: str or list
332
return self._unparse_url(self._remote_path(relpath))
375
334
def _remote_path(self, relpath):
376
335
"""Return the path to be passed along the sftp protocol for relpath.
378
:param relpath: is a urlencoded string.
380
remote_path = self._parsed_url.clone(relpath).path
381
# the initial slash should be removed from the path, and treated as a
382
# homedir relative path (the path begins with a double slash if it is
383
# absolute). see draft-ietf-secsh-scp-sftp-ssh-uri-03.txt
384
# RBC 20060118 we are not using this as its too user hostile. instead
385
# we are following lftp and using /~/foo to mean '~/foo'
386
# vila--20070602 and leave absolute paths begin with a single slash.
387
if remote_path.startswith('/~/'):
388
remote_path = remote_path[3:]
389
elif remote_path == '/~':
393
def _create_connection(self, credentials=None):
394
"""Create a new connection with the provided credentials.
396
:param credentials: The credentials needed to establish the connection.
398
:return: The created connection and its associated credentials.
400
The credentials are only the password as it may have been entered
401
interactively by the user and may be different from the one provided
402
in base url at transport creation time.
404
if credentials is None:
405
password = self._parsed_url.password
407
password = credentials
409
vendor = ssh._get_ssh_vendor()
410
user = self._parsed_url.user
412
auth = config.AuthenticationConfig()
413
user = auth.get_user('ssh', self._parsed_url.host,
414
self._parsed_url.port)
415
connection = vendor.connect_sftp(self._parsed_url.user, password,
416
self._parsed_url.host, self._parsed_url.port)
417
return connection, (user, password)
419
def disconnect(self):
420
connection = self._get_connection()
421
if connection is not None:
425
"""Ensures that a connection is established"""
426
connection = self._get_connection()
427
if connection is None:
428
# First connection ever
429
connection, credentials = self._create_connection()
430
self._set_connection(connection, credentials)
337
relpath is a urlencoded string.
339
# FIXME: share the common code across transports
340
assert isinstance(relpath, basestring)
341
relpath = urllib.unquote(relpath).split('/')
342
basepath = self._path.split('/')
343
if len(basepath) > 0 and basepath[-1] == '':
344
basepath = basepath[:-1]
348
if len(basepath) == 0:
349
# In most filesystems, a request for the parent
350
# of root, just returns root.
358
path = '/'.join(basepath)
361
def relpath(self, abspath):
362
username, password, host, port, path = self._split_url(abspath)
364
if (username != self._username):
365
error.append('username mismatch')
366
if (host != self._host):
367
error.append('host mismatch')
368
if (port != self._port):
369
error.append('port mismatch')
370
if (not path.startswith(self._path)):
371
error.append('path mismatch')
373
extra = ': ' + ', '.join(error)
374
raise PathNotChild(abspath, self.base, extra=extra)
376
return path[pl:].strip('/')
433
378
def has(self, relpath):
435
380
Does the target location exist?
438
self._get_sftp().stat(self._remote_path(relpath))
439
# stat result is about 20 bytes, let's say
440
self._report_activity(20, 'read')
383
self._sftp.stat(self._remote_path(relpath))
445
388
def get(self, relpath):
446
"""Get the file at the given relative path.
390
Get the file at the given relative path.
448
392
:param relpath: The relative path to the file
451
395
path = self._remote_path(relpath)
452
f = self._get_sftp().file(path, mode='rb')
453
size = f.stat().st_size
454
if getattr(f, 'prefetch', None) is not None:
396
f = self._sftp.file(path, mode='rb')
397
if self._do_prefetch and (getattr(f, 'prefetch', None) is not None):
457
except (IOError, paramiko.SSHException) as e:
458
self._translate_io_exception(e, path, ': error retrieving',
459
failure_exc=errors.ReadError)
461
def get_bytes(self, relpath):
462
# reimplement this here so that we can report how many bytes came back
463
with self.get(relpath) as f:
465
self._report_activity(len(bytes), 'read')
468
def _readv(self, relpath, offsets):
469
"""See Transport.readv()"""
470
# We overload the default readv() because we want to use a file
471
# that does not have prefetch enabled.
472
# Also, if we have a new paramiko, it implements an async readv()
477
path = self._remote_path(relpath)
478
fp = self._get_sftp().file(path, mode='rb')
479
readv = getattr(fp, 'readv', None)
481
return self._sftp_readv(fp, offsets, relpath)
482
if 'sftp' in debug.debug_flags:
483
mutter('seek and read %s offsets', len(offsets))
484
return self._seek_and_read(fp, offsets, relpath)
485
except (IOError, paramiko.SSHException) as e:
400
except (IOError, paramiko.SSHException), e:
486
401
self._translate_io_exception(e, path, ': error retrieving')
488
def recommended_page_size(self):
489
"""See Transport.recommended_page_size().
491
For SFTP we suggest a large page size to reduce the overhead
492
introduced by latency.
496
def _sftp_readv(self, fp, offsets, relpath):
497
"""Use the readv() member of fp to do async readv.
499
Then read them using paramiko.readv(). paramiko.readv()
500
does not support ranges > 64K, so it caps the request size, and
501
just reads until it gets all the stuff it wants.
503
helper = _SFTPReadvHelper(offsets, relpath, self._report_activity)
504
return helper.request_and_yield_offsets(fp)
506
def put_file(self, relpath, f, mode=None):
508
Copy the file-like object into the location.
403
def get_partial(self, relpath, start, length=None):
405
Get just part of a file.
407
:param relpath: Path to the file, relative to base
408
:param start: The starting position to read from
409
:param length: The length to read. A length of None indicates
410
read to the end of the file.
411
:return: A file-like object containing at least the specified bytes.
412
Some implementations may return objects which can be read
413
past this length, but this is not guaranteed.
415
# TODO: implement get_partial_multi to help with knit support
416
f = self.get(relpath)
418
if self._do_prefetch and hasattr(f, 'prefetch'):
422
def put(self, relpath, f, mode=None):
424
Copy the file-like or string object into the location.
510
426
:param relpath: Location to put the contents, relative to base.
511
:param f: File-like object.
427
:param f: File-like or string object.
512
428
:param mode: The final mode for the file
514
430
final_path = self._remote_path(relpath)
515
return self._put(final_path, f, mode=mode)
431
self._put(final_path, f, mode=mode)
517
433
def _put(self, abspath, f, mode=None):
518
434
"""Helper function so both put() and copy_abspaths can reuse the code"""
519
435
tmp_abspath = '%s.tmp.%.9f.%d.%d' % (abspath, time.time(),
520
os.getpid(), random.randint(0, 0x7FFFFFFF))
436
os.getpid(), random.randint(0,0x7FFFFFFF))
521
437
fout = self._sftp_open_exclusive(tmp_abspath, mode=mode)
525
441
fout.set_pipelined(True)
526
length = self._pump(f, fout)
527
except (IOError, paramiko.SSHException) as e:
443
except (IOError, paramiko.SSHException), e:
528
444
self._translate_io_exception(e, tmp_abspath)
529
# XXX: This doesn't truly help like we would like it to.
530
# The problem is that openssh strips sticky bits. So while we
531
# can properly set group write permission, we lose the group
532
# sticky bit. So it is probably best to stop chmodding, and
533
# just tell users that they need to set the umask correctly.
534
# The attr.st_mode = mode, in _sftp_open_exclusive
535
# will handle when the user wants the final mode to be more
536
# restrictive. And then we avoid a round trip. Unless
537
# paramiko decides to expose an async chmod()
539
# This is designed to chmod() right before we close.
540
# Because we set_pipelined() earlier, theoretically we might
541
# avoid the round trip for fout.close()
542
445
if mode is not None:
543
self._get_sftp().chmod(tmp_abspath, mode)
446
self._sftp.chmod(tmp_abspath, mode)
546
449
self._rename_and_overwrite(tmp_abspath, abspath)
548
except Exception as e:
549
451
# If we fail, try to clean up the temporary file
550
452
# before we throw the exception
551
453
# but don't let another exception mess things up
559
self._get_sftp().remove(tmp_abspath)
461
self._sftp.remove(tmp_abspath)
561
463
# raise the saved except
563
465
# raise the original with its traceback if we can.
566
def _put_non_atomic_helper(self, relpath, writer, mode=None,
567
create_parent_dir=False,
569
abspath = self._remote_path(relpath)
571
# TODO: jam 20060816 paramiko doesn't publicly expose a way to
572
# set the file mode at create time. If it does, use it.
573
# But for now, we just chmod later anyway.
575
def _open_and_write_file():
576
"""Try to open the target file, raise error on failure"""
580
fout = self._get_sftp().file(abspath, mode='wb')
581
fout.set_pipelined(True)
583
except (paramiko.SSHException, IOError) as e:
584
self._translate_io_exception(e, abspath,
587
# This is designed to chmod() right before we close.
588
# Because we set_pipelined() earlier, theoretically we might
589
# avoid the round trip for fout.close()
591
self._get_sftp().chmod(abspath, mode)
596
if not create_parent_dir:
597
_open_and_write_file()
600
# Try error handling to create the parent directory if we need to
602
_open_and_write_file()
604
# Try to create the parent directory, and then go back to
606
parent_dir = os.path.dirname(abspath)
607
self._mkdir(parent_dir, dir_mode)
608
_open_and_write_file()
610
def put_file_non_atomic(self, relpath, f, mode=None,
611
create_parent_dir=False,
613
"""Copy the file-like object into the target location.
615
This function is not strictly safe to use. It is only meant to
616
be used when you already know that the target does not exist.
617
It is not safe, because it will open and truncate the remote
618
file. So there may be a time when the file has invalid contents.
620
:param relpath: The remote location to put the contents.
621
:param f: File-like object.
622
:param mode: Possible access permissions for new file.
623
None means do not set remote permissions.
624
:param create_parent_dir: If we cannot create the target file because
625
the parent directory does not exist, go ahead and
626
create it, and then try again.
630
self._put_non_atomic_helper(relpath, writer, mode=mode,
631
create_parent_dir=create_parent_dir,
634
def put_bytes_non_atomic(self, relpath, raw_bytes, mode=None,
635
create_parent_dir=False,
637
if not isinstance(raw_bytes, bytes):
639
'raw_bytes must be a plain string, not %s' % type(raw_bytes))
642
fout.write(raw_bytes)
643
self._put_non_atomic_helper(relpath, writer, mode=mode,
644
create_parent_dir=create_parent_dir,
647
468
def iter_files_recursive(self):
648
469
"""Walk the relative paths of all files in this transport."""
649
# progress is handled by list_dir
650
470
queue = list(self.list_dir('.'))
652
relpath = queue.pop(0)
472
relpath = urllib.quote(queue.pop(0))
653
473
st = self.stat(relpath)
654
474
if stat.S_ISDIR(st.st_mode):
655
475
for i, basename in enumerate(self.list_dir(relpath)):
656
queue.insert(i, relpath + '/' + basename)
476
queue.insert(i, relpath+'/'+basename)
660
def _mkdir(self, abspath, mode=None):
666
self._report_activity(len(abspath), 'write')
667
self._get_sftp().mkdir(abspath, local_mode)
668
self._report_activity(1, 'read')
670
# chmod a dir through sftp will erase any sgid bit set
671
# on the server side. So, if the bit mode are already
672
# set, avoid the chmod. If the mode is not fine but
673
# the sgid bit is set, report a warning to the user
674
# with the umask fix.
675
stat = self._get_sftp().lstat(abspath)
676
mode = mode & 0o777 # can't set special bits anyway
677
if mode != stat.st_mode & 0o777:
678
if stat.st_mode & 0o6000:
679
warning('About to chmod %s over sftp, which will result'
680
' in its suid or sgid bits being cleared. If'
681
' you want to preserve those bits, change your '
682
' environment on the server to use umask 0%03o.'
683
% (abspath, 0o777 - mode))
684
self._get_sftp().chmod(abspath, mode=mode)
685
except (paramiko.SSHException, IOError) as e:
686
self._translate_io_exception(e, abspath, ': unable to mkdir',
687
failure_exc=FileExists)
689
480
def mkdir(self, relpath, mode=None):
690
481
"""Create a directory at the given path."""
691
self._mkdir(self._remote_path(relpath), mode=mode)
693
def open_write_stream(self, relpath, mode=None):
694
"""See Transport.open_write_stream."""
695
# initialise the file to zero-length
696
# this is three round trips, but we don't use this
697
# api more than once per write_group at the moment so
698
# it is a tolerable overhead. Better would be to truncate
699
# the file after opening. RBC 20070805
700
self.put_bytes_non_atomic(relpath, b"", mode)
701
abspath = self._remote_path(relpath)
702
# TODO: jam 20060816 paramiko doesn't publicly expose a way to
703
# set the file mode at create time. If it does, use it.
704
# But for now, we just chmod later anyway.
707
handle = self._get_sftp().file(abspath, mode='wb')
708
handle.set_pipelined(True)
709
except (paramiko.SSHException, IOError) as e:
710
self._translate_io_exception(e, abspath,
712
_file_streams[self.abspath(relpath)] = handle
713
return FileFileStream(self, relpath, handle)
483
path = self._remote_path(relpath)
484
# In the paramiko documentation, it says that passing a mode flag
485
# will filtered against the server umask.
486
# StubSFTPServer does not do this, which would be nice, because it is
487
# what we really want :)
488
# However, real servers do use umask, so we really should do it that way
489
self._sftp.mkdir(path)
491
self._sftp.chmod(path, mode=mode)
492
except (paramiko.SSHException, IOError), e:
493
self._translate_io_exception(e, path, ': unable to mkdir',
494
failure_exc=FileExists)
715
def _translate_io_exception(self, e, path, more_info='',
496
def _translate_io_exception(self, e, path, more_info='',
716
497
failure_exc=PathError):
717
498
"""Translate a paramiko or IOError into a friendlier exception.
723
504
:param failure_exc: Paramiko has the super fun ability to raise completely
724
505
opaque errors that just set "e.args = ('Failure',)" with
725
506
no more information.
726
If this parameter is set, it defines the exception
507
If this parameter is set, it defines the exception
727
508
to raise in these cases.
729
510
# paramiko seems to generate detailless errors.
730
511
self._translate_error(e, path, raise_generic=False)
731
if getattr(e, 'args', None) is not None:
512
if hasattr(e, 'args'):
732
513
if (e.args == ('No such file or directory',) or
733
e.args == ('No such file',)):
514
e.args == ('No such file',)):
734
515
raise NoSuchFile(path, str(e) + more_info)
735
if (e.args == ('mkdir failed',) or
736
e.args[0].startswith('syserr: File exists')):
516
if (e.args == ('mkdir failed',)):
737
517
raise FileExists(path, str(e) + more_info)
738
518
# strange but true, for the paramiko server.
739
519
if (e.args == ('Failure',)):
740
520
raise failure_exc(path, str(e) + more_info)
741
# Can be something like args = ('Directory not empty:
742
# '/srv/bazaar.launchpad.net/blah...: '
743
# [Errno 39] Directory not empty',)
744
if (e.args[0].startswith('Directory not empty: ')
745
or getattr(e, 'errno', None) == errno.ENOTEMPTY):
746
raise errors.DirectoryNotEmpty(path, str(e))
747
if e.args == ('Operation unsupported',):
748
raise errors.TransportNotPossible()
749
521
mutter('Raising exception with args %s', e.args)
750
if getattr(e, 'errno', None) is not None:
522
if hasattr(e, 'errno'):
751
523
mutter('Raising exception with errno %s', e.errno)
754
def append_file(self, relpath, f, mode=None):
526
def append(self, relpath, f, mode=None):
756
528
Append the text in the file-like object into the final
760
532
path = self._remote_path(relpath)
761
fout = self._get_sftp().file(path, 'ab')
533
fout = self._sftp.file(path, 'ab')
762
534
if mode is not None:
763
self._get_sftp().chmod(path, mode)
535
self._sftp.chmod(path, mode)
764
536
result = fout.tell()
765
537
self._pump(f, fout)
767
except (IOError, paramiko.SSHException) as e:
539
except (IOError, paramiko.SSHException), e:
768
540
self._translate_io_exception(e, relpath, ': unable to append')
770
542
def rename(self, rel_from, rel_to):
771
543
"""Rename without special overwriting"""
773
self._get_sftp().rename(self._remote_path(rel_from),
774
self._remote_path(rel_to))
775
except (IOError, paramiko.SSHException) as e:
545
self._sftp.rename(self._remote_path(rel_from),
546
self._remote_path(rel_to))
547
except (IOError, paramiko.SSHException), e:
776
548
self._translate_io_exception(e, rel_from,
777
': unable to rename to %r' % (rel_to))
549
': unable to rename to %r' % (rel_to))
779
551
def _rename_and_overwrite(self, abs_from, abs_to):
780
552
"""Do a fancy rename on the remote server.
782
554
Using the implementation provided by osutils.
785
sftp = self._get_sftp()
786
557
fancy_rename(abs_from, abs_to,
787
rename_func=sftp.rename,
788
unlink_func=sftp.remove)
789
except (IOError, paramiko.SSHException) as e:
790
self._translate_io_exception(e, abs_from,
791
': unable to rename to %r' % (abs_to))
558
rename_func=self._sftp.rename,
559
unlink_func=self._sftp.remove)
560
except (IOError, paramiko.SSHException), e:
561
self._translate_io_exception(e, abs_from, ': unable to rename to %r' % (abs_to))
793
563
def move(self, rel_from, rel_to):
794
564
"""Move the item at rel_from to the location at rel_to"""
895
631
# that we have taken the lock.
896
632
return SFTPLock(relpath, self)
634
def _unparse_url(self, path=None):
637
path = urllib.quote(path)
638
# handle homedir paths
639
if not path.startswith('/'):
641
netloc = urllib.quote(self._host)
642
if self._username is not None:
643
netloc = '%s@%s' % (urllib.quote(self._username), netloc)
644
if self._port is not None:
645
netloc = '%s:%d' % (netloc, self._port)
646
return urlparse.urlunparse(('sftp', netloc, path, '', '', ''))
648
def _split_url(self, url):
649
if isinstance(url, unicode):
650
url = url.encode('utf-8')
651
(scheme, netloc, path, params,
652
query, fragment) = urlparse.urlparse(url, allow_fragments=False)
653
assert scheme == 'sftp'
654
username = password = host = port = None
656
username, host = netloc.split('@', 1)
658
username, password = username.split(':', 1)
659
password = urllib.unquote(password)
660
username = urllib.unquote(username)
665
host, port = host.rsplit(':', 1)
669
# TODO: Should this be ConnectionError?
670
raise TransportError('%s: invalid port number' % port)
671
host = urllib.unquote(host)
673
path = urllib.unquote(path)
675
# the initial slash should be removed from the path, and treated
676
# as a homedir relative path (the path begins with a double slash
677
# if it is absolute).
678
# see draft-ietf-secsh-scp-sftp-ssh-uri-03.txt
679
# RBC 20060118 we are not using this as its too user hostile. instead
680
# we are following lftp and using /~/foo to mean '~/foo'.
681
# handle homedir paths
682
if path.startswith('/~/'):
686
return (username, password, host, port, path)
688
def _parse_url(self, url):
689
(self._username, self._password,
690
self._host, self._port, self._path) = self._split_url(url)
692
def _sftp_connect(self):
693
"""Connect to the remote sftp server.
694
After this, self._sftp should have a valid connection (or
695
we raise an TransportError 'could not connect').
697
TODO: Raise a more reasonable ConnectionFailed exception
699
global _connected_hosts
701
idx = (self._host, self._port, self._username)
703
self._sftp = _connected_hosts[idx]
708
vendor = _get_ssh_vendor()
709
if vendor == 'loopback':
710
sock = socket.socket()
711
sock.connect((self._host, self._port))
712
self._sftp = SFTPClient(LoopbackSFTP(sock))
713
elif vendor != 'none':
714
sock = SFTPSubprocess(self._host, vendor, self._port,
716
self._sftp = SFTPClient(sock)
718
self._paramiko_connect()
720
_connected_hosts[idx] = self._sftp
722
def _paramiko_connect(self):
723
global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
728
t = paramiko.Transport((self._host, self._port or 22))
729
t.set_log_channel('bzr.paramiko')
731
except paramiko.SSHException, e:
732
raise ConnectionError('Unable to reach SSH host %s:%d' %
733
(self._host, self._port), e)
735
server_key = t.get_remote_server_key()
736
server_key_hex = paramiko.util.hexify(server_key.get_fingerprint())
737
keytype = server_key.get_name()
738
if SYSTEM_HOSTKEYS.has_key(self._host) and SYSTEM_HOSTKEYS[self._host].has_key(keytype):
739
our_server_key = SYSTEM_HOSTKEYS[self._host][keytype]
740
our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
741
elif BZR_HOSTKEYS.has_key(self._host) and BZR_HOSTKEYS[self._host].has_key(keytype):
742
our_server_key = BZR_HOSTKEYS[self._host][keytype]
743
our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
745
warning('Adding %s host key for %s: %s' % (keytype, self._host, server_key_hex))
746
if not BZR_HOSTKEYS.has_key(self._host):
747
BZR_HOSTKEYS[self._host] = {}
748
BZR_HOSTKEYS[self._host][keytype] = server_key
749
our_server_key = server_key
750
our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
752
if server_key != our_server_key:
753
filename1 = os.path.expanduser('~/.ssh/known_hosts')
754
filename2 = pathjoin(config_dir(), 'ssh_host_keys')
755
raise TransportError('Host keys for %s do not match! %s != %s' % \
756
(self._host, our_server_key_hex, server_key_hex),
757
['Try editing %s or %s' % (filename1, filename2)])
762
self._sftp = t.open_sftp_client()
763
except paramiko.SSHException, e:
764
raise ConnectionError('Unable to start sftp client %s:%d' %
765
(self._host, self._port), e)
767
def _sftp_auth(self, transport):
768
# paramiko requires a username, but it might be none if nothing was supplied
769
# use the local username, just in case.
770
# We don't override self._username, because if we aren't using paramiko,
771
# the username might be specified in ~/.ssh/config and we don't want to
772
# force it to something else
773
# Also, it would mess up the self.relpath() functionality
774
username = self._username or getpass.getuser()
776
# Paramiko tries to open a socket.AF_UNIX in order to connect
777
# to ssh-agent. That attribute doesn't exist on win32 (it does in cygwin)
778
# so we get an AttributeError exception. For now, just don't try to
779
# connect to an agent if we are on win32
780
if sys.platform != 'win32':
781
agent = paramiko.Agent()
782
for key in agent.get_keys():
783
mutter('Trying SSH agent key %s' % paramiko.util.hexify(key.get_fingerprint()))
785
transport.auth_publickey(username, key)
787
except paramiko.SSHException, e:
790
# okay, try finding id_rsa or id_dss? (posix only)
791
if self._try_pkey_auth(transport, paramiko.RSAKey, username, 'id_rsa'):
793
if self._try_pkey_auth(transport, paramiko.DSSKey, username, 'id_dsa'):
798
transport.auth_password(username, self._password)
800
except paramiko.SSHException, e:
803
# FIXME: Don't keep a password held in memory if you can help it
804
#self._password = None
806
# give up and ask for a password
807
password = bzrlib.ui.ui_factory.get_password(
808
prompt='SSH %(user)s@%(host)s password',
809
user=username, host=self._host)
811
transport.auth_password(username, password)
812
except paramiko.SSHException, e:
813
raise ConnectionError('Unable to authenticate to SSH host as %s@%s' %
814
(username, self._host), e)
816
def _try_pkey_auth(self, transport, pkey_class, username, filename):
817
filename = os.path.expanduser('~/.ssh/' + filename)
819
key = pkey_class.from_private_key_file(filename)
820
transport.auth_publickey(username, key)
822
except paramiko.PasswordRequiredException:
823
password = bzrlib.ui.ui_factory.get_password(
824
prompt='SSH %(filename)s password',
827
key = pkey_class.from_private_key_file(filename, password)
828
transport.auth_publickey(username, key)
830
except paramiko.SSHException:
831
mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))
832
except paramiko.SSHException:
833
mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))
898
838
def _sftp_open_exclusive(self, abspath, mode=None):
899
839
"""Open a remote path exclusively.
909
849
:param abspath: The remote absolute path where the file should be opened
910
850
:param mode: The mode permissions bits for the new file
912
# TODO: jam 20060816 Paramiko >= 1.6.2 (probably earlier) supports
913
# using the 'x' flag to indicate SFTP_FLAG_EXCL.
914
# However, there is no way to set the permission mode at open
915
# time using the sftp_client.file() functionality.
916
path = self._get_sftp()._adjust_cwd(abspath)
917
# mutter('sftp abspath %s => %s', abspath, path)
852
path = self._sftp._adjust_cwd(abspath)
918
853
attr = SFTPAttributes()
919
854
if mode is not None:
920
855
attr.st_mode = mode
921
omode = (SFTP_FLAG_WRITE | SFTP_FLAG_CREATE
922
| SFTP_FLAG_TRUNC | SFTP_FLAG_EXCL)
856
omode = (SFTP_FLAG_WRITE | SFTP_FLAG_CREATE
857
| SFTP_FLAG_TRUNC | SFTP_FLAG_EXCL)
924
t, msg = self._get_sftp()._request(CMD_OPEN, path, omode, attr)
859
t, msg = self._sftp._request(CMD_OPEN, path, omode, attr)
925
860
if t != CMD_HANDLE:
926
861
raise TransportError('Expected an SFTP handle')
927
862
handle = msg.get_string()
928
return SFTPFile(self._get_sftp(), handle, 'wb', -1)
929
except (paramiko.SSHException, IOError) as e:
863
return SFTPFile(self._sftp, handle, 'wb', -1)
864
except (paramiko.SSHException, IOError), e:
930
865
self._translate_io_exception(e, abspath, ': unable to open',
931
failure_exc=FileExists)
933
def _can_roundtrip_unix_modebits(self):
934
if sys.platform == 'win32':
866
failure_exc=FileExists)
869
# ------------- server test implementation --------------
873
from bzrlib.tests.stub_sftp import StubServer, StubSFTPServer
875
STUB_SERVER_KEY = """
876
-----BEGIN RSA PRIVATE KEY-----
877
MIICWgIBAAKBgQDTj1bqB4WmayWNPB+8jVSYpZYk80Ujvj680pOTh2bORBjbIAyz
878
oWGW+GUjzKxTiiPvVmxFgx5wdsFvF03v34lEVVhMpouqPAYQ15N37K/ir5XY+9m/
879
d8ufMCkjeXsQkKqFbAlQcnWMCRnOoPHS3I4vi6hmnDDeeYTSRvfLbW0fhwIBIwKB
880
gBIiOqZYaoqbeD9OS9z2K9KR2atlTxGxOJPXiP4ESqP3NVScWNwyZ3NXHpyrJLa0
881
EbVtzsQhLn6rF+TzXnOlcipFvjsem3iYzCpuChfGQ6SovTcOjHV9z+hnpXvQ/fon
882
soVRZY65wKnF7IAoUwTmJS9opqgrN6kRgCd3DASAMd1bAkEA96SBVWFt/fJBNJ9H
883
tYnBKZGw0VeHOYmVYbvMSstssn8un+pQpUm9vlG/bp7Oxd/m+b9KWEh2xPfv6zqU
884
avNwHwJBANqzGZa/EpzF4J8pGti7oIAPUIDGMtfIcmqNXVMckrmzQ2vTfqtkEZsA
885
4rE1IERRyiJQx6EJsz21wJmGV9WJQ5kCQQDwkS0uXqVdFzgHO6S++tjmjYcxwr3g
886
H0CoFYSgbddOT6miqRskOQF3DZVkJT3kyuBgU2zKygz52ukQZMqxCb1fAkASvuTv
887
qfpH87Qq5kQhNKdbbwbmd2NxlNabazPijWuphGTdW0VfJdWfklyS2Kr+iqrs/5wV
888
HhathJt636Eg7oIjAkA8ht3MQ+XSl9yIJIS8gVpbPxSw5OMfw0PjVE7tBdQruiSc
889
nvuQES5C9BMHjF39LZiGH1iLQy7FgdHyoP+eodI7
890
-----END RSA PRIVATE KEY-----
894
class SingleListener(threading.Thread):
896
def __init__(self, callback):
897
threading.Thread.__init__(self)
898
self._callback = callback
899
self._socket = socket.socket()
900
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
901
self._socket.bind(('localhost', 0))
902
self._socket.listen(1)
903
self.port = self._socket.getsockname()[1]
904
self.stop_event = threading.Event()
907
s, _ = self._socket.accept()
908
# now close the listen socket
911
self._callback(s, self.stop_event)
913
pass #Ignore socket errors
915
# probably a failed test
916
warning('Exception from within unit test server thread: %r' % x)
919
self.stop_event.set()
920
# use a timeout here, because if the test fails, the server thread may
921
# never notice the stop_event.
925
class SFTPServer(Server):
926
"""Common code for SFTP server facilities."""
929
self._original_vendor = None
931
self._server_homedir = None
932
self._listener = None
934
self._vendor = 'none'
938
def _get_sftp_url(self, path):
939
"""Calculate an sftp url to this server for path."""
940
return 'sftp://foo:bar@localhost:%d/%s' % (self._listener.port, path)
942
def log(self, message):
943
"""StubServer uses this to log when a new server is created."""
944
self.logs.append(message)
946
def _run_server(self, s, stop_event):
947
ssh_server = paramiko.Transport(s)
948
key_file = os.path.join(self._homedir, 'test_rsa.key')
949
file(key_file, 'w').write(STUB_SERVER_KEY)
950
host_key = paramiko.RSAKey.from_private_key_file(key_file)
951
ssh_server.add_server_key(host_key)
952
server = StubServer(self)
953
ssh_server.set_subsystem_handler('sftp', paramiko.SFTPServer,
954
StubSFTPServer, root=self._root,
955
home=self._server_homedir)
956
event = threading.Event()
957
ssh_server.start_server(event, server)
959
stop_event.wait(30.0)
963
self._original_vendor = _ssh_vendor
964
_ssh_vendor = self._vendor
965
self._homedir = os.getcwdu()
966
if self._server_homedir is None:
967
self._server_homedir = self._homedir
969
# FIXME WINDOWS: _root should be _server_homedir[0]:/
970
self._listener = SingleListener(self._run_server)
971
self._listener.setDaemon(True)
972
self._listener.start()
975
"""See bzrlib.transport.Server.tearDown."""
977
self._listener.stop()
978
_ssh_vendor = self._original_vendor
981
class SFTPFullAbsoluteServer(SFTPServer):
982
"""A test server for sftp transports, using absolute urls and ssh."""
985
"""See bzrlib.transport.Server.get_url."""
986
return self._get_sftp_url(urlescape(self._homedir[1:]))
989
class SFTPServerWithoutSSH(SFTPServer):
990
"""An SFTP server that uses a simple TCP socket pair rather than SSH."""
993
super(SFTPServerWithoutSSH, self).__init__()
994
self._vendor = 'loopback'
996
def _run_server(self, sock, stop_event):
997
class FakeChannel(object):
998
def get_transport(self):
1000
def get_log_channel(self):
1004
def get_hexdump(self):
1007
server = paramiko.SFTPServer(FakeChannel(), 'sftp', StubServer(self), StubSFTPServer,
1008
root=self._root, home=self._server_homedir)
1009
server.start_subsystem('sftp', None, sock)
1010
server.finish_subsystem()
1013
class SFTPAbsoluteServer(SFTPServerWithoutSSH):
1014
"""A test server for sftp transports, using absolute urls."""
1017
"""See bzrlib.transport.Server.get_url."""
1018
return self._get_sftp_url(urlescape(self._homedir[1:]))
1021
class SFTPHomeDirServer(SFTPServerWithoutSSH):
1022
"""A test server for sftp transports, using homedir relative urls."""
1025
"""See bzrlib.transport.Server.get_url."""
1026
return self._get_sftp_url("~/")
1029
class SFTPSiblingAbsoluteServer(SFTPAbsoluteServer):
1030
"""A test servere for sftp transports, using absolute urls to non-home."""
1033
self._server_homedir = '/dev/noone/runs/tests/here'
1034
super(SFTPSiblingAbsoluteServer, self).setUp()
941
1037
def get_test_permutations():
942
1038
"""Return the permutations to be used in testing."""
943
from ..tests import stub_sftp
944
return [(SFTPTransport, stub_sftp.SFTPAbsoluteServer),
945
(SFTPTransport, stub_sftp.SFTPHomeDirServer),
946
(SFTPTransport, stub_sftp.SFTPSiblingAbsoluteServer),
1039
return [(SFTPTransport, SFTPAbsoluteServer),
1040
(SFTPTransport, SFTPHomeDirServer),
1041
(SFTPTransport, SFTPSiblingAbsoluteServer),