def listdir_ex(self, path): path = self._adjust_cwd(path) basename = os.path.basename(path) dir = os.path.dirname(path) t, msg = self._request(CMD_OPENDIR, dir) if t != CMD_HANDLE: raise SFTPError('Expected handle') handle = msg.get_string() filelist = [] while True: try: t, msg = self._request(CMD_READDIR, handle) except EOFError: # done with handle break if t != CMD_NAME: raise SFTPError('Expected name response') count = msg.get_int() for i in range(count): filename = _to_unicode(msg.get_string()) longname = _to_unicode(msg.get_string()) attr = SFTPAttributes._from_msg(msg, filename, longname) if (filename != '.') and ( filename != '..') and filename.startswith(basename): filelist.append(filename) self._request(CMD_CLOSE, handle) return filelist
def stat(self, path): """ Retrieve information about a file on the remote system. The return value is an object whose attributes correspond to the attributes of Python's ``stat`` structure as returned by ``os.stat``, except that it contains fewer fields. An SFTP server may return as much or as little info as it wants, so the results may vary from server to server. Unlike a Python `python:stat` object, the result may not be accessed as a tuple. This is mostly due to the author's slack factor. The fields supported are: ``st_mode``, ``st_size``, ``st_uid``, ``st_gid``, ``st_atime``, and ``st_mtime``. :param str path: the filename to stat :return: an `.SFTPAttributes` object containing attributes about the given file """ path = self._adjust_cwd(path) self._log(DEBUG, 'stat(%r)' % path) t, msg = self._request(CMD_STAT, path) if t != CMD_ATTRS: raise SFTPError('Expected attributes') return SFTPAttributes._from_msg(msg)
def patch_chdir(self, path=None): """ STANDARD IMPLEMENTATION DOESN'T WORK WITH RELATIVE PATHS Change the "current directory" of this SFTP session. Since SFTP doesn't really have the concept of a current working directory, this is emulated by Paramiko. Once you use this method to set a working directory, all operations on this `.SFTPClient` object will be relative to that path. You can pass in ``None`` to stop using a current working directory. :param str path: new current working directory :raises IOError: if the requested path doesn't exist on the server .. versionadded:: 1.4 """ if path is None: self._cwd = None return if not stat.S_ISDIR(self.stat(path).st_mode): raise SFTPError(errno.ENOTDIR, "%s: %s" % (os.strerror(errno.ENOTDIR), path)) # self._cwd = b(self.normalize(path)) if self._cwd is None or os.path.isabs(path): self._cwd = b(self.normalize(os.path.abspath(path))) else: cwd = self._cwd.decode() self._cwd = b(self.normalize(os.path.join(cwd, path)))
def chdir(self, path=None, path_encoding='utf-8'): """ Change the "current directory" of this SFTP session. Since SFTP doesn't really have the concept of a current working directory, this is emulated by Paramiko. Once you use this method to set a working directory, all operations on this `.SFTPClient` object will be relative to that path. You can pass in ``None`` to stop using a current working directory. :param str path: new current working directory :param str path_encoding: the param ```'path'``` encode format, eg: chdir(path='001中文目录'.encode('GB18030'),path_encoding='GB18030') :param str path_encoding: 当需要跳转的目录路径中包含有中文,而服务器未使用utf-8的编码格式时需要设置此参数,参数值为服务器使用的编码格式,如当服务器使用GB18030的编码格式时,使用实例:chdir(path="/test/中文目录".encode('GB18030'), path_encoding='GB18030') :raises: ``IOError`` -- if the requested path doesn't exist on the server .. versionadded:: 1.4 """ if path is None: self._cwd = None return if not stat.S_ISDIR(self.stat(path).st_mode): code = errno.ENOTDIR raise SFTPError(code, "{}: {}".format(os.strerror(code), path)) self._cwd = b(self.normalize(path_encoding=path_encoding, path=path))
def chdir(self, path=None): """ Change the "current directory" of this SFTP session. Since SFTP doesn't really have the concept of a current working directory, this is emulated by Paramiko. Once you use this method to set a working directory, all operations on this `.SFTPClient` object will be relative to that path. You can pass in ``None`` to stop using a current working directory. :param str path: new current working directory :raises: ``IOError`` -- if the requested path doesn't exist on the server .. versionadded:: 1.4 """ if path is None: self._cwd = None return if not stat.S_ISDIR(self.stat(path).st_mode): code = errno.ENOTDIR raise SFTPError( code, "{}: {}".format(os.strerror(code), path) ) self._cwd = b(self.normalize(path))
def listdir_attr(self, path=".", encoding="utf-8", path_encoding='utf-8'): """ Return a list containing `.SFTPAttributes` objects corresponding to files in the given ``path``. The list is in arbitrary order. It does not include the special entries ``'.'`` and ``'..'`` even if they are present in the folder. The returned `.SFTPAttributes` objects will each have an additional field: ``longname``, which may contain a formatted string of the file's attributes, in unix format. The content of this string will probably depend on the SFTP server implementation. :param str encoding: the byte decode format (defauls to ```'utf-8'```),used to decode filename(under the path) byte ,eg: ```'中文目录'``` :param str encoding: 设置服务器中返回byte的解码格式,当服务器返回内容中有使用其他格式编码的内容时(如目录下包含有中文)需要设置此参数,参数值建议为服务器使用的编码格式,如: 'GB180303'编码的中文,此时建议使用方式: listdir_attr(path='/test', encoding='GB18030') :param str path: path to list (defaults to ``'.'``) :param str path_encoding: the path parameter encode format (defauls to ```'utf-8'```),eg: listdir_attr(path='中文目录'.encode('GB18030'), path_encoding='GB18030') :param str path_encoding: 当需要查看的路径path在服务器上的编码格式不是utf-8时需要设置此参数,参数值为服务器所使用的编码格式,如需要查看的路径是'/test/中文路径',而服务器使用的编码格式是GB18030, 此时就建议如下使用方式: listdir_attr(path="/test/中文路径".encode("GB18030"), path_encoding='GB18030') :return: list of `.SFTPAttributes` objects .. versionadded:: 1.2 """ if path == '.' and self.getcwd() is not None: path = self.getcwd().encode(path_encoding) path = self._adjust_cwd(path) self._log(DEBUG, "listdir({!r})".format(path)) t, msg = self._request(CMD_OPENDIR, path) if t != CMD_HANDLE: raise SFTPError("Expected handle") handle = msg.get_binary() filelist = [] while True: try: t, msg = self._request(CMD_READDIR, handle) except EOFError: # done with handle break if t != CMD_NAME: raise SFTPError("Expected name response") count = msg.get_int() for i in range(count): filename = msg.get_text(encoding) longname = msg.get_text(encoding) attr = SFTPAttributes._from_msg(msg, filename, longname) if (filename != ".") and (filename != ".."): filelist.append(attr) self._request(CMD_CLOSE, handle) return filelist
def open(self, filename, mode="r", bufsize=-1): """ Open a file on the remote server. The arguments are the same as for Python's built-in `python:file` (aka `python:open`). A file-like object is returned, which closely mimics the behavior of a normal Python file object, including the ability to be used as a context manager. The mode indicates how the file is to be opened: ``'r'`` for reading, ``'w'`` for writing (truncating an existing file), ``'a'`` for appending, ``'r+'`` for reading/writing, ``'w+'`` for reading/writing (truncating an existing file), ``'a+'`` for reading/appending. The Python ``'b'`` flag is ignored, since SSH treats all files as binary. The ``'U'`` flag is supported in a compatible way. Since 1.5.2, an ``'x'`` flag indicates that the operation should only succeed if the file was created and did not previously exist. This has no direct mapping to Python's file flags, but is commonly known as the ``O_EXCL`` flag in posix. The file will be buffered in standard Python style by default, but can be altered with the ``bufsize`` parameter. ``0`` turns off buffering, ``1`` uses line buffering, and any number greater than 1 (``>1``) uses that specific buffer size. :param str filename: name of the file to open :param str mode: mode (Python-style) to open in :param int bufsize: desired buffering (-1 = default buffer size) :return: an `.SFTPFile` object representing the open file :raises: ``IOError`` -- if the file could not be opened. """ filename = self._adjust_cwd(filename) self._log(DEBUG, "open({!r}, {!r})".format(filename, mode)) imode = 0 if ("r" in mode) or ("+" in mode): imode |= SFTP_FLAG_READ if ("w" in mode) or ("+" in mode) or ("a" in mode): imode |= SFTP_FLAG_WRITE if "w" in mode: imode |= SFTP_FLAG_CREATE | SFTP_FLAG_TRUNC if "a" in mode: imode |= SFTP_FLAG_CREATE | SFTP_FLAG_APPEND if "x" in mode: imode |= SFTP_FLAG_CREATE | SFTP_FLAG_EXCL attrblock = SFTPAttributes() t, msg = self._request(CMD_OPEN, filename, imode, attrblock) if t != CMD_HANDLE: raise SFTPError("Expected handle") handle = msg.get_binary() self._log( DEBUG, "open({!r}, {!r}) -> {}".format( filename, mode, u(hexlify(handle)) ), ) return SFTPFile(self, handle, mode, bufsize)
def _read(self, size): size = min(size, self.MAX_REQUEST_SIZE) if self._prefetching: data = self._read_prefetch(size) if data is not None: return data t, msg = self.sftp._request(CMD_READ, self.handle, long(self._realpos), int(size)) if t != CMD_DATA: raise SFTPError('Expected data') return msg.get_string()
def fstat(self, handle): """ Get stats about a file on the remote server via it's handle """ log.debug(f"stat request: [{handle}]") resp_type, msg = self._blocking_request(CMD_FSTAT, handle) if resp_type != CMD_ATTRS: raise SFTPError("Expected back attributes") return SFTPAttributes._from_msg(msg)
def readlink(self, path): """ Return the target of a symbolic link (shortcut). You can use `symlink` to create these. The result may be either an absolute or relative pathname. :param str path: path of the symbolic link file :return: target path, as a `str` """ path = self._adjust_cwd(path) self._log(DEBUG, 'readlink(%r)' % path) t, msg = self._request(CMD_READLINK, path) if t != CMD_NAME: raise SFTPError('Expected name response') count = msg.get_int() if count == 0: return None if count != 1: raise SFTPError('Readlink returned %d results' % count) return _to_unicode(msg.get_string())
def listdir_iter(self, path='.', read_aheads=10): """ Generator version of `.listdir_attr`. See the API docs for `.listdir_attr` for overall details. This function adds one more kwarg on top of `.listdir_attr`: ``read_aheads``, an integer controlling how many ``SSH_FXP_READDIR`` requests are made to the server. The default should suffice for most file listings as each request/response cycle may contain multiple files (dependent on server implementation.) .. versionadded:: 1.15 """ path = self._adjust_cwd(path) self._log(DEBUG, "listdir(%r)", path) t, msg = self._request(CMD_OPENDIR, path) if t != CMD_HANDLE: raise SFTPError('Expected handle') handle = msg.get_string() while True: nums = list() results = list() try: # Send out a bunch of readdir requests so that we can read the responses later # Section 6.7 of the SSH file transfer RFC explains this # http://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt for i in range(read_aheads): num = self._async_request(type(None), CMD_READDIR, handle) nums.append(num) # need to read whole batch before yielding any for num in nums: t, msg = self._read_response(num) count = msg.get_int() for i in range(count): filename = msg.get_text() longname = msg.get_text() attr = SFTPAttributes._from_msg( msg, filename, longname) if (filename != '.') and (filename != '..'): results.append(attr) for a in results: yield a except EOFError: self._request(CMD_CLOSE, handle) for a in results: yield a return
def listdir_attr(self, path='.'): """ Return a list containing `.SFTPAttributes` objects corresponding to files in the given ``path``. The list is in arbitrary order. It does not include the special entries ``'.'`` and ``'..'`` even if they are present in the folder. The returned `.SFTPAttributes` objects will each have an additional field: ``longname``, which may contain a formatted string of the file's attributes, in unix format. The content of this string will probably depend on the SFTP server implementation. :param str path: path to list (defaults to ``'.'``) :return: list of `.SFTPAttributes` objects .. versionadded:: 1.2 """ path = self._adjust_cwd(path) self._log(DEBUG, 'listdir(%r)' % path) t, msg = self._request(CMD_OPENDIR, path) if t != CMD_HANDLE: raise SFTPError('Expected handle') handle = msg.get_binary() filelist = [] while True: try: t, msg = self._request(CMD_READDIR, handle) except EOFError: # done with handle break if t != CMD_NAME: raise SFTPError('Expected name response') count = msg.get_int() for i in range(count): filename = msg.get_text() longname = msg.get_text() attr = SFTPAttributes._from_msg(msg, filename, longname) if (filename != '.') and (filename != '..'): filelist.append(attr) self._request(CMD_CLOSE, handle) return filelist
def normalize(self, path): """ Return the normalized path (on the server) of a given path. This can be used to quickly resolve symbolic links or determine what the server is considering to be the "current folder" (by passing ``'.'`` as ``path``). :param str path: path to be normalized :return: normalized form of the given path (as a `str`) :raises IOError: if the path can't be resolved on the server """ path = self._adjust_cwd(path) self._log(DEBUG, 'normalize(%r)' % path) t, msg = self._request(CMD_REALPATH, path) if t != CMD_NAME: raise SFTPError('Expected name response') count = msg.get_int() if count != 1: raise SFTPError('Realpath returned %d results' % count) return msg.get_text()
def stat(self): """ Retrieve information about this file from the remote system. This is exactly like `.SFTPClient.stat`, except that it operates on an already-open file. :return: an `.SFTPAttributes` object containing attributes about this file. """ t, msg = self.sftp._request(CMD_FSTAT, self.handle) if t != CMD_ATTRS: raise SFTPError('Expected attributes') return SFTPAttributes._from_msg(msg)
def close(self, handle): """ Close the remote file :param str handle: remote file handle to close """ resp_type, msg = self._blocking_request(CMD_CLOSE, handle) if resp_type != CMD_STATUS: raise SFTPError("Error closing file") status = msg.get_int() log.debug(f'closed [{handle}] on server: {status}') return status
def _write(self, data): # may write less than requested if it would exceed max packet size chunk = min(len(data), self.MAX_REQUEST_SIZE) self._reqs.append(self.sftp._async_request(type(None), CMD_WRITE, self.handle, long(self._realpos), data[:chunk])) if not self.pipelined or (len(self._reqs) > 100 and self.sftp.sock.recv_ready()): while len(self._reqs): req = self._reqs.popleft() t, msg = self.sftp._read_response(req) if t != CMD_STATUS: raise SFTPError('Expected status') # convert_status already called return chunk
def normalize(self, path, path_encoding='utf-8'): """ Return the normalized path (on the server) of a given path. This can be used to quickly resolve symbolic links or determine what the server is considering to be the "current folder" (by passing ``'.'`` as ``path``). :param str path: path to be normalized :param str path_encoding: 编码/解码格式,默认: utf-8 :return: normalized form of the given path (as a `str`) :raises: ``IOError`` -- if the path can't be resolved on the server """ path = self._adjust_cwd(path) self._log(DEBUG, "normalize({!r})".format(path)) t, msg = self._request(CMD_REALPATH, path) if t != CMD_NAME: raise SFTPError("Expected name response") count = msg.get_int() if count != 1: raise SFTPError("Realpath returned {} results".format(count)) return msg.get_text(path_encoding)
def lstat(self, path): """ Retrieve information about a file on the remote system, without following symbolic links (shortcuts). This otherwise behaves exactly the same as `stat`. :param str path: the filename to stat :return: an `.SFTPAttributes` object containing attributes about the given file """ path = self._adjust_cwd(path) self._log(DEBUG, 'lstat(%r)' % path) t, msg = self._request(CMD_LSTAT, path) if t != CMD_ATTRS: raise SFTPError('Expected attributes') return SFTPAttributes._from_msg(msg)
def _async_response(self, t, msg, num): if t == CMD_STATUS: # save exception and re-raise it on next file operation try: self.sftp._convert_status(msg) except Exception as e: self._saved_exception = e return if t != CMD_DATA: raise SFTPError('Expected data') data = msg.get_string() with self._prefetch_lock: offset, length = self._prefetch_extents[num] self._prefetch_data[offset] = data del self._prefetch_extents[num] if len(self._prefetch_extents) == 0: self._prefetch_done = True
def open(self, filename, mode="r"): """ Open a remote file, ``filename``, on the server for reading or writing. Args: filename (str): name of remote file to open mode (str): mode to open file in """ filename = self.encode_path(filename) pflags = 0 if "r" in mode: pflags |= SFTP_FLAG_READ if "w" in mode: pflags |= SFTP_FLAG_WRITE | SFTP_FLAG_CREATE | SFTP_FLAG_TRUNC attrs = SFTPAttributes() resp_type, msg = self._blocking_request(CMD_OPEN, filename, pflags, attrs) if resp_type != CMD_HANDLE: raise SFTPError("Expected remote file handle") return msg.get_binary()
def _threaded_reader(self, handle, writer, size): futures = [] with self._lock: lo["expected_responses"] = math.ceil(size / MAX_PAYLOAD_SIZE) with ThreadPoolExecutor() as executor: n = 0 while n < size: chunk = min(MAX_PAYLOAD_SIZE, size - n) futures.append(executor.submit(self.read, handle, chunk, n, thread=True)) n += chunk requests = [f.result() for f in futures] for r in requests: resp_type, data = self._sftp._read_packet() if resp_type != CMD_DATA: raise SFTPError("Expected data") msg = Message(data) resp_num = msg.get_int() if resp_num in _request_stack: writer.seek(_request_stack[resp_num][0]) log.debug(f'write local at byte {_request_stack[resp_num][0]}') writer.write(msg.get_string())
def listdir_iter(self, path='.', read_aheads=50): """ Generator version of `.listdir_attr`. See the API docs for `.listdir_attr` for overall details. This function adds one more kwarg on top of `.listdir_attr`: ``read_aheads``, an integer controlling how many ``SSH_FXP_READDIR`` requests are made to the server. The default of 50 should suffice for most file listings as each request/response cycle may contain multiple files (dependent on server implementation.) .. versionadded:: 1.15 """ path = self._adjust_cwd(path) self._log(DEBUG, 'listdir(%r)' % path) t, msg = self._request(CMD_OPENDIR, path) if t != CMD_HANDLE: raise SFTPError('Expected handle') handle = msg.get_string() nums = list() while True: try: # Send out a bunch of readdir requests so that we can read the # responses later on Section 6.7 of the SSH file transfer RFC # explains this # http://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt for i in range(read_aheads): num = self._async_request(type(None), CMD_READDIR, handle) nums.append(num) # For each of our sent requests # Read and parse the corresponding packets # If we're at the end of our queued requests, then fire off # some more requests # Exit the loop when we've reached the end of the directory # handle for num in nums: t, pkt_data = self._read_packet() msg = Message(pkt_data) new_num = msg.get_int() if num == new_num: if t == CMD_STATUS: self._convert_status(msg) count = msg.get_int() for i in range(count): filename = msg.get_text() longname = msg.get_text() attr = SFTPAttributes._from_msg( msg, filename, longname) if (filename != '.') and (filename != '..'): yield attr # If we've hit the end of our queued requests, reset nums. nums = list() except EOFError: self._request(CMD_CLOSE, handle) return