1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 """
20 Client-mode SFTP support.
21 """
22
23 from binascii import hexlify
24 import errno
25 import os
26 import threading
27 import time
28 import weakref
29
30 from paramiko.sftp import *
31 from paramiko.sftp_attr import SFTPAttributes
32 from paramiko.ssh_exception import SSHException
33 from paramiko.sftp_file import SFTPFile
34
35
37 """
38 decode a string as ascii or utf8 if possible (as required by the sftp
39 protocol). if neither works, just return a byte string because the server
40 probably doesn't know the filename's encoding.
41 """
42 try:
43 return s.encode('ascii')
44 except UnicodeError:
45 try:
46 return s.decode('utf-8')
47 except UnicodeError:
48 return s
49
50
52 """
53 SFTP client object. C{SFTPClient} is used to open an sftp session across
54 an open ssh L{Transport} and do remote file operations.
55 """
56
58 """
59 Create an SFTP client from an existing L{Channel}. The channel
60 should already have requested the C{"sftp"} subsystem.
61
62 An alternate way to create an SFTP client context is by using
63 L{from_transport}.
64
65 @param sock: an open L{Channel} using the C{"sftp"} subsystem
66 @type sock: L{Channel}
67
68 @raise SSHException: if there's an exception while negotiating
69 sftp
70 """
71 BaseSFTP.__init__(self)
72 self.sock = sock
73 self.ultra_debug = False
74 self.request_number = 1
75
76 self._lock = threading.Lock()
77 self._cwd = None
78
79 self._expecting = weakref.WeakValueDictionary()
80 if type(sock) is Channel:
81
82 transport = self.sock.get_transport()
83 self.logger = util.get_logger(transport.get_log_channel() + '.sftp')
84 self.ultra_debug = transport.get_hexdump()
85 try:
86 server_version = self._send_version()
87 except EOFError, x:
88 raise SSHException('EOF during negotiation')
89 self._log(INFO, 'Opened sftp connection (server version %d)' % server_version)
90
92 """
93 Create an SFTP client channel from an open L{Transport}.
94
95 @param t: an open L{Transport} which is already authenticated
96 @type t: L{Transport}
97 @return: a new L{SFTPClient} object, referring to an sftp session
98 (channel) across the transport
99 @rtype: L{SFTPClient}
100 """
101 chan = t.open_session()
102 if chan is None:
103 return None
104 chan.invoke_subsystem('sftp')
105 return cls(chan)
106 from_transport = classmethod(from_transport)
107
108 - def _log(self, level, msg, *args):
109 super(SFTPClient, self)._log(level, "[chan %s] " + msg, *([ self.sock.get_name() ] + list(args)))
110
112 """
113 Close the SFTP session and its underlying channel.
114
115 @since: 1.4
116 """
117 self._log(INFO, 'sftp session closed.')
118 self.sock.close()
119
121 """
122 Return the underlying L{Channel} object for this SFTP session. This
123 might be useful for doing things like setting a timeout on the channel.
124
125 @return: the SSH channel
126 @rtype: L{Channel}
127
128 @since: 1.7.1
129 """
130 return self.sock
131
133 """
134 Return a list containing the names of the entries in the given C{path}.
135 The list is in arbitrary order. It does not include the special
136 entries C{'.'} and C{'..'} even if they are present in the folder.
137 This method is meant to mirror C{os.listdir} as closely as possible.
138 For a list of full L{SFTPAttributes} objects, see L{listdir_attr}.
139
140 @param path: path to list (defaults to C{'.'})
141 @type path: str
142 @return: list of filenames
143 @rtype: list of str
144 """
145 return [f.filename for f in self.listdir_attr(path)]
146
148 """
149 Return a list containing L{SFTPAttributes} objects corresponding to
150 files in the given C{path}. The list is in arbitrary order. It does
151 not include the special entries C{'.'} and C{'..'} even if they are
152 present in the folder.
153
154 The returned L{SFTPAttributes} objects will each have an additional
155 field: C{longname}, which may contain a formatted string of the file's
156 attributes, in unix format. The content of this string will probably
157 depend on the SFTP server implementation.
158
159 @param path: path to list (defaults to C{'.'})
160 @type path: str
161 @return: list of attributes
162 @rtype: list of L{SFTPAttributes}
163
164 @since: 1.2
165 """
166 path = self._adjust_cwd(path)
167 self._log(DEBUG, 'listdir(%r)' % path)
168 t, msg = self._request(CMD_OPENDIR, path)
169 if t != CMD_HANDLE:
170 raise SFTPError('Expected handle')
171 handle = msg.get_string()
172 filelist = []
173 while True:
174 try:
175 t, msg = self._request(CMD_READDIR, handle)
176 except EOFError, e:
177
178 break
179 if t != CMD_NAME:
180 raise SFTPError('Expected name response')
181 count = msg.get_int()
182 for i in range(count):
183 filename = _to_unicode(msg.get_string())
184 longname = _to_unicode(msg.get_string())
185 attr = SFTPAttributes._from_msg(msg, filename, longname)
186 if (filename != '.') and (filename != '..'):
187 filelist.append(attr)
188 self._request(CMD_CLOSE, handle)
189 return filelist
190
191 - def open(self, filename, mode='r', bufsize=-1):
192 """
193 Open a file on the remote server. The arguments are the same as for
194 python's built-in C{file} (aka C{open}). A file-like object is
195 returned, which closely mimics the behavior of a normal python file
196 object.
197
198 The mode indicates how the file is to be opened: C{'r'} for reading,
199 C{'w'} for writing (truncating an existing file), C{'a'} for appending,
200 C{'r+'} for reading/writing, C{'w+'} for reading/writing (truncating an
201 existing file), C{'a+'} for reading/appending. The python C{'b'} flag
202 is ignored, since SSH treats all files as binary. The C{'U'} flag is
203 supported in a compatible way.
204
205 Since 1.5.2, an C{'x'} flag indicates that the operation should only
206 succeed if the file was created and did not previously exist. This has
207 no direct mapping to python's file flags, but is commonly known as the
208 C{O_EXCL} flag in posix.
209
210 The file will be buffered in standard python style by default, but
211 can be altered with the C{bufsize} parameter. C{0} turns off
212 buffering, C{1} uses line buffering, and any number greater than 1
213 (C{>1}) uses that specific buffer size.
214
215 @param filename: name of the file to open
216 @type filename: str
217 @param mode: mode (python-style) to open in
218 @type mode: str
219 @param bufsize: desired buffering (-1 = default buffer size)
220 @type bufsize: int
221 @return: a file object representing the open file
222 @rtype: SFTPFile
223
224 @raise IOError: if the file could not be opened.
225 """
226 filename = self._adjust_cwd(filename)
227 self._log(DEBUG, 'open(%r, %r)' % (filename, mode))
228 imode = 0
229 if ('r' in mode) or ('+' in mode):
230 imode |= SFTP_FLAG_READ
231 if ('w' in mode) or ('+' in mode) or ('a' in mode):
232 imode |= SFTP_FLAG_WRITE
233 if ('w' in mode):
234 imode |= SFTP_FLAG_CREATE | SFTP_FLAG_TRUNC
235 if ('a' in mode):
236 imode |= SFTP_FLAG_CREATE | SFTP_FLAG_APPEND
237 if ('x' in mode):
238 imode |= SFTP_FLAG_CREATE | SFTP_FLAG_EXCL
239 attrblock = SFTPAttributes()
240 t, msg = self._request(CMD_OPEN, filename, imode, attrblock)
241 if t != CMD_HANDLE:
242 raise SFTPError('Expected handle')
243 handle = msg.get_string()
244 self._log(DEBUG, 'open(%r, %r) -> %s' % (filename, mode, hexlify(handle)))
245 return SFTPFile(self, handle, mode, bufsize)
246
247
248 file = open
249
251 """
252 Remove the file at the given path. This only works on files; for
253 removing folders (directories), use L{rmdir}.
254
255 @param path: path (absolute or relative) of the file to remove
256 @type path: str
257
258 @raise IOError: if the path refers to a folder (directory)
259 """
260 path = self._adjust_cwd(path)
261 self._log(DEBUG, 'remove(%r)' % path)
262 self._request(CMD_REMOVE, path)
263
264 unlink = remove
265
266 - def rename(self, oldpath, newpath):
267 """
268 Rename a file or folder from C{oldpath} to C{newpath}.
269
270 @param oldpath: existing name of the file or folder
271 @type oldpath: str
272 @param newpath: new name for the file or folder
273 @type newpath: str
274
275 @raise IOError: if C{newpath} is a folder, or something else goes
276 wrong
277 """
278 oldpath = self._adjust_cwd(oldpath)
279 newpath = self._adjust_cwd(newpath)
280 self._log(DEBUG, 'rename(%r, %r)' % (oldpath, newpath))
281 self._request(CMD_RENAME, oldpath, newpath)
282
283 - def mkdir(self, path, mode=0777):
284 """
285 Create a folder (directory) named C{path} with numeric mode C{mode}.
286 The default mode is 0777 (octal). On some systems, mode is ignored.
287 Where it is used, the current umask value is first masked out.
288
289 @param path: name of the folder to create
290 @type path: str
291 @param mode: permissions (posix-style) for the newly-created folder
292 @type mode: int
293 """
294 path = self._adjust_cwd(path)
295 self._log(DEBUG, 'mkdir(%r, %r)' % (path, mode))
296 attr = SFTPAttributes()
297 attr.st_mode = mode
298 self._request(CMD_MKDIR, path, attr)
299
301 """
302 Remove the folder named C{path}.
303
304 @param path: name of the folder to remove
305 @type path: str
306 """
307 path = self._adjust_cwd(path)
308 self._log(DEBUG, 'rmdir(%r)' % path)
309 self._request(CMD_RMDIR, path)
310
311 - def stat(self, path):
312 """
313 Retrieve information about a file on the remote system. The return
314 value is an object whose attributes correspond to the attributes of
315 python's C{stat} structure as returned by C{os.stat}, except that it
316 contains fewer fields. An SFTP server may return as much or as little
317 info as it wants, so the results may vary from server to server.
318
319 Unlike a python C{stat} object, the result may not be accessed as a
320 tuple. This is mostly due to the author's slack factor.
321
322 The fields supported are: C{st_mode}, C{st_size}, C{st_uid}, C{st_gid},
323 C{st_atime}, and C{st_mtime}.
324
325 @param path: the filename to stat
326 @type path: str
327 @return: an object containing attributes about the given file
328 @rtype: SFTPAttributes
329 """
330 path = self._adjust_cwd(path)
331 self._log(DEBUG, 'stat(%r)' % path)
332 t, msg = self._request(CMD_STAT, path)
333 if t != CMD_ATTRS:
334 raise SFTPError('Expected attributes')
335 return SFTPAttributes._from_msg(msg)
336
338 """
339 Retrieve information about a file on the remote system, without
340 following symbolic links (shortcuts). This otherwise behaves exactly
341 the same as L{stat}.
342
343 @param path: the filename to stat
344 @type path: str
345 @return: an object containing attributes about the given file
346 @rtype: SFTPAttributes
347 """
348 path = self._adjust_cwd(path)
349 self._log(DEBUG, 'lstat(%r)' % path)
350 t, msg = self._request(CMD_LSTAT, path)
351 if t != CMD_ATTRS:
352 raise SFTPError('Expected attributes')
353 return SFTPAttributes._from_msg(msg)
354
356 """
357 Create a symbolic link (shortcut) of the C{source} path at
358 C{destination}.
359
360 @param source: path of the original file
361 @type source: str
362 @param dest: path of the newly created symlink
363 @type dest: str
364 """
365 dest = self._adjust_cwd(dest)
366 self._log(DEBUG, 'symlink(%r, %r)' % (source, dest))
367 if type(source) is unicode:
368 source = source.encode('utf-8')
369 self._request(CMD_SYMLINK, source, dest)
370
371 - def chmod(self, path, mode):
372 """
373 Change the mode (permissions) of a file. The permissions are
374 unix-style and identical to those used by python's C{os.chmod}
375 function.
376
377 @param path: path of the file to change the permissions of
378 @type path: str
379 @param mode: new permissions
380 @type mode: int
381 """
382 path = self._adjust_cwd(path)
383 self._log(DEBUG, 'chmod(%r, %r)' % (path, mode))
384 attr = SFTPAttributes()
385 attr.st_mode = mode
386 self._request(CMD_SETSTAT, path, attr)
387
388 - def chown(self, path, uid, gid):
389 """
390 Change the owner (C{uid}) and group (C{gid}) of a file. As with
391 python's C{os.chown} function, you must pass both arguments, so if you
392 only want to change one, use L{stat} first to retrieve the current
393 owner and group.
394
395 @param path: path of the file to change the owner and group of
396 @type path: str
397 @param uid: new owner's uid
398 @type uid: int
399 @param gid: new group id
400 @type gid: int
401 """
402 path = self._adjust_cwd(path)
403 self._log(DEBUG, 'chown(%r, %r, %r)' % (path, uid, gid))
404 attr = SFTPAttributes()
405 attr.st_uid, attr.st_gid = uid, gid
406 self._request(CMD_SETSTAT, path, attr)
407
408 - def utime(self, path, times):
409 """
410 Set the access and modified times of the file specified by C{path}. If
411 C{times} is C{None}, then the file's access and modified times are set
412 to the current time. Otherwise, C{times} must be a 2-tuple of numbers,
413 of the form C{(atime, mtime)}, which is used to set the access and
414 modified times, respectively. This bizarre API is mimicked from python
415 for the sake of consistency -- I apologize.
416
417 @param path: path of the file to modify
418 @type path: str
419 @param times: C{None} or a tuple of (access time, modified time) in
420 standard internet epoch time (seconds since 01 January 1970 GMT)
421 @type times: tuple(int)
422 """
423 path = self._adjust_cwd(path)
424 if times is None:
425 times = (time.time(), time.time())
426 self._log(DEBUG, 'utime(%r, %r)' % (path, times))
427 attr = SFTPAttributes()
428 attr.st_atime, attr.st_mtime = times
429 self._request(CMD_SETSTAT, path, attr)
430
432 """
433 Change the size of the file specified by C{path}. This usually extends
434 or shrinks the size of the file, just like the C{truncate()} method on
435 python file objects.
436
437 @param path: path of the file to modify
438 @type path: str
439 @param size: the new size of the file
440 @type size: int or long
441 """
442 path = self._adjust_cwd(path)
443 self._log(DEBUG, 'truncate(%r, %r)' % (path, size))
444 attr = SFTPAttributes()
445 attr.st_size = size
446 self._request(CMD_SETSTAT, path, attr)
447
449 """
450 Return the target of a symbolic link (shortcut). You can use
451 L{symlink} to create these. The result may be either an absolute or
452 relative pathname.
453
454 @param path: path of the symbolic link file
455 @type path: str
456 @return: target path
457 @rtype: str
458 """
459 path = self._adjust_cwd(path)
460 self._log(DEBUG, 'readlink(%r)' % path)
461 t, msg = self._request(CMD_READLINK, path)
462 if t != CMD_NAME:
463 raise SFTPError('Expected name response')
464 count = msg.get_int()
465 if count == 0:
466 return None
467 if count != 1:
468 raise SFTPError('Readlink returned %d results' % count)
469 return _to_unicode(msg.get_string())
470
472 """
473 Return the normalized path (on the server) of a given path. This
474 can be used to quickly resolve symbolic links or determine what the
475 server is considering to be the "current folder" (by passing C{'.'}
476 as C{path}).
477
478 @param path: path to be normalized
479 @type path: str
480 @return: normalized form of the given path
481 @rtype: str
482
483 @raise IOError: if the path can't be resolved on the server
484 """
485 path = self._adjust_cwd(path)
486 self._log(DEBUG, 'normalize(%r)' % path)
487 t, msg = self._request(CMD_REALPATH, path)
488 if t != CMD_NAME:
489 raise SFTPError('Expected name response')
490 count = msg.get_int()
491 if count != 1:
492 raise SFTPError('Realpath returned %d results' % count)
493 return _to_unicode(msg.get_string())
494
496 """
497 Change the "current directory" of this SFTP session. Since SFTP
498 doesn't really have the concept of a current working directory, this
499 is emulated by paramiko. Once you use this method to set a working
500 directory, all operations on this SFTPClient object will be relative
501 to that path.
502
503 @param path: new current working directory
504 @type path: str
505
506 @raise IOError: if the requested path doesn't exist on the server
507
508 @since: 1.4
509 """
510 self._cwd = self.normalize(path)
511
513 """
514 Return the "current working directory" for this SFTP session, as
515 emulated by paramiko. If no directory has been set with L{chdir},
516 this method will return C{None}.
517
518 @return: the current working directory on the server, or C{None}
519 @rtype: str
520
521 @since: 1.4
522 """
523 return self._cwd
524
525 - def put(self, localpath, remotepath, callback=None):
526 """
527 Copy a local file (C{localpath}) to the SFTP server as C{remotepath}.
528 Any exception raised by operations will be passed through. This
529 method is primarily provided as a convenience.
530
531 The SFTP operations use pipelining for speed.
532
533 @param localpath: the local file to copy
534 @type localpath: str
535 @param remotepath: the destination path on the SFTP server
536 @type remotepath: str
537 @param callback: optional callback function that accepts the bytes
538 transferred so far and the total bytes to be transferred
539 (since 1.7.4)
540 @type callback: function(int, int)
541 @return: an object containing attributes about the given file
542 (since 1.7.4)
543 @rtype: SFTPAttributes
544
545 @since: 1.4
546 """
547 file_size = os.stat(localpath).st_size
548 fl = file(localpath, 'rb')
549 fr = self.file(remotepath, 'wb')
550 fr.set_pipelined(True)
551 size = 0
552 while True:
553 data = fl.read(32768)
554 if len(data) == 0:
555 break
556 fr.write(data)
557 size += len(data)
558 if callback is not None:
559 callback(size, file_size)
560 fl.close()
561 fr.close()
562 s = self.stat(remotepath)
563 if s.st_size != size:
564 raise IOError('size mismatch in put! %d != %d' % (s.st_size, size))
565 return s
566
567 - def get(self, remotepath, localpath, callback=None):
568 """
569 Copy a remote file (C{remotepath}) from the SFTP server to the local
570 host as C{localpath}. Any exception raised by operations will be
571 passed through. This method is primarily provided as a convenience.
572
573 @param remotepath: the remote file to copy
574 @type remotepath: str
575 @param localpath: the destination path on the local host
576 @type localpath: str
577 @param callback: optional callback function that accepts the bytes
578 transferred so far and the total bytes to be transferred
579 (since 1.7.4)
580 @type callback: function(int, int)
581
582 @since: 1.4
583 """
584 fr = self.file(remotepath, 'rb')
585 file_size = self.stat(remotepath).st_size
586 fr.prefetch()
587 fl = file(localpath, 'wb')
588 size = 0
589 while True:
590 data = fr.read(32768)
591 if len(data) == 0:
592 break
593 fl.write(data)
594 size += len(data)
595 if callback is not None:
596 callback(size, file_size)
597 fl.close()
598 fr.close()
599 s = os.stat(localpath)
600 if s.st_size != size:
601 raise IOError('size mismatch in get! %d != %d' % (s.st_size, size))
602
603
604
605
606
608 num = self._async_request(type(None), t, *arg)
609 return self._read_response(num)
610
612
613 self._lock.acquire()
614 try:
615 msg = Message()
616 msg.add_int(self.request_number)
617 for item in arg:
618 if type(item) is int:
619 msg.add_int(item)
620 elif type(item) is long:
621 msg.add_int64(item)
622 elif type(item) is str:
623 msg.add_string(item)
624 elif type(item) is SFTPAttributes:
625 item._pack(msg)
626 else:
627 raise Exception('unknown type for %r type %r' % (item, type(item)))
628 num = self.request_number
629 self._expecting[num] = fileobj
630 self._send_packet(t, str(msg))
631 self.request_number += 1
632 finally:
633 self._lock.release()
634 return num
635
637 while True:
638 try:
639 t, data = self._read_packet()
640 except EOFError, e:
641 raise SSHException('Server connection dropped: %s' % (str(e),))
642 msg = Message(data)
643 num = msg.get_int()
644 if num not in self._expecting:
645
646 self._log(DEBUG, 'Unexpected response #%d' % (num,))
647 if waitfor is None:
648
649 break
650 continue
651 fileobj = self._expecting[num]
652 del self._expecting[num]
653 if num == waitfor:
654
655 if t == CMD_STATUS:
656 self._convert_status(msg)
657 return t, msg
658 if fileobj is not type(None):
659 fileobj._async_response(t, msg)
660 if waitfor is None:
661
662 break
663 return (None, None)
664
666 while fileobj in self._expecting.values():
667 self._read_response()
668 fileobj._check_exception()
669
671 """
672 Raises EOFError or IOError on error status; otherwise does nothing.
673 """
674 code = msg.get_int()
675 text = msg.get_string()
676 if code == SFTP_OK:
677 return
678 elif code == SFTP_EOF:
679 raise EOFError(text)
680 elif code == SFTP_NO_SUCH_FILE:
681
682 raise IOError(errno.ENOENT, text)
683 elif code == SFTP_PERMISSION_DENIED:
684 raise IOError(errno.EACCES, text)
685 else:
686 raise IOError(text)
687
689 """
690 Return an adjusted path if we're emulating a "current working
691 directory" for the server.
692 """
693 if type(path) is unicode:
694 path = path.encode('utf-8')
695 if self._cwd is None:
696 return path
697 if (len(path) > 0) and (path[0] == '/'):
698
699 return path
700 if self._cwd == '/':
701 return self._cwd + path
702 return self._cwd + '/' + path
703
704
705 -class SFTP (SFTPClient):
706 "an alias for L{SFTPClient} for backwards compatability"
707 pass
708