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