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):
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 @return: an object containing attributes about the given file
553 (since 1.7.4)
554 @rtype: SFTPAttributes
555
556 @since: 1.4
557 """
558 file_size = os.stat(localpath).st_size
559 fl = file(localpath, 'rb')
560 try:
561 fr = self.file(remotepath, 'wb')
562 fr.set_pipelined(True)
563 size = 0
564 try:
565 while True:
566 data = fl.read(32768)
567 if len(data) == 0:
568 break
569 fr.write(data)
570 size += len(data)
571 if callback is not None:
572 callback(size, file_size)
573 finally:
574 fr.close()
575 finally:
576 fl.close()
577 s = self.stat(remotepath)
578 if s.st_size != size:
579 raise IOError('size mismatch in put! %d != %d' % (s.st_size, size))
580 return s
581
582 - def get(self, remotepath, localpath, callback=None):
583 """
584 Copy a remote file (C{remotepath}) from the SFTP server to the local
585 host as C{localpath}. Any exception raised by operations will be
586 passed through. This method is primarily provided as a convenience.
587
588 @param remotepath: the remote file to copy
589 @type remotepath: str
590 @param localpath: the destination path on the local host
591 @type localpath: str
592 @param callback: optional callback function that accepts the bytes
593 transferred so far and the total bytes to be transferred
594 (since 1.7.4)
595 @type callback: function(int, int)
596
597 @since: 1.4
598 """
599 fr = self.file(remotepath, 'rb')
600 file_size = self.stat(remotepath).st_size
601 fr.prefetch()
602 try:
603 fl = file(localpath, 'wb')
604 try:
605 size = 0
606 while True:
607 data = fr.read(32768)
608 if len(data) == 0:
609 break
610 fl.write(data)
611 size += len(data)
612 if callback is not None:
613 callback(size, file_size)
614 finally:
615 fl.close()
616 finally:
617 fr.close()
618 s = os.stat(localpath)
619 if s.st_size != size:
620 raise IOError('size mismatch in get! %d != %d' % (s.st_size, size))
621
622
623
624
625
627 num = self._async_request(type(None), t, *arg)
628 return self._read_response(num)
629
631
632 self._lock.acquire()
633 try:
634 msg = Message()
635 msg.add_int(self.request_number)
636 for item in arg:
637 if type(item) is int:
638 msg.add_int(item)
639 elif type(item) is long:
640 msg.add_int64(item)
641 elif type(item) is str:
642 msg.add_string(item)
643 elif type(item) is SFTPAttributes:
644 item._pack(msg)
645 else:
646 raise Exception('unknown type for %r type %r' % (item, type(item)))
647 num = self.request_number
648 self._expecting[num] = fileobj
649 self._send_packet(t, str(msg))
650 self.request_number += 1
651 finally:
652 self._lock.release()
653 return num
654
656 while True:
657 try:
658 t, data = self._read_packet()
659 except EOFError, e:
660 raise SSHException('Server connection dropped: %s' % (str(e),))
661 msg = Message(data)
662 num = msg.get_int()
663 if num not in self._expecting:
664
665 self._log(DEBUG, 'Unexpected response #%d' % (num,))
666 if waitfor is None:
667
668 break
669 continue
670 fileobj = self._expecting[num]
671 del self._expecting[num]
672 if num == waitfor:
673
674 if t == CMD_STATUS:
675 self._convert_status(msg)
676 return t, msg
677 if fileobj is not type(None):
678 fileobj._async_response(t, msg)
679 if waitfor is None:
680
681 break
682 return (None, None)
683
685 while fileobj in self._expecting.values():
686 self._read_response()
687 fileobj._check_exception()
688
690 """
691 Raises EOFError or IOError on error status; otherwise does nothing.
692 """
693 code = msg.get_int()
694 text = msg.get_string()
695 if code == SFTP_OK:
696 return
697 elif code == SFTP_EOF:
698 raise EOFError(text)
699 elif code == SFTP_NO_SUCH_FILE:
700
701 raise IOError(errno.ENOENT, text)
702 elif code == SFTP_PERMISSION_DENIED:
703 raise IOError(errno.EACCES, text)
704 else:
705 raise IOError(text)
706
708 """
709 Return an adjusted path if we're emulating a "current working
710 directory" for the server.
711 """
712 if type(path) is unicode:
713 path = path.encode('utf-8')
714 if self._cwd is None:
715 return path
716 if (len(path) > 0) and (path[0] == '/'):
717
718 return path
719 if self._cwd == '/':
720 return self._cwd + path
721 return self._cwd + '/' + path
722
723
724 -class SFTP (SFTPClient):
725 "an alias for L{SFTPClient} for backwards compatability"
726 pass
727