[project @ Arch-1:robey@lag.net--2003-public%secsh--dev--1.0--patch-73]
split sftp into sftp, sftp_client; renamed SFTP -> SFTPClient add sftp_client file, and split out the common code (sftp) from stuff specific to client mode (sftp_client). renamed SFTP class to SFTPClient, but left an alias so old code will still work. renamed a bunch of sftp constants now that they're better hidden from epydoc.
This commit is contained in:
parent
8dbab50233
commit
3973265264
2
README
2
README
|
@ -27,7 +27,7 @@ should have come with this archive.
|
||||||
*** REQUIREMENTS
|
*** REQUIREMENTS
|
||||||
|
|
||||||
python 2.3 <http://www.python.org/>
|
python 2.3 <http://www.python.org/>
|
||||||
(python 2.2 may work with some pain)
|
(python 2.2 is also supported, but not recommended)
|
||||||
pyCrypt <http://www.amk.ca/python/code/crypto.html>
|
pyCrypt <http://www.amk.ca/python/code/crypto.html>
|
||||||
|
|
||||||
PyCrypt compiled for Win32 can be downloaded from the HashTar homepage:
|
PyCrypt compiled for Win32 can be downloaded from the HashTar homepage:
|
||||||
|
|
|
@ -66,7 +66,8 @@ __version__ = "0.9-horsea"
|
||||||
__license__ = "GNU Lesser General Public License (LGPL)"
|
__license__ = "GNU Lesser General Public License (LGPL)"
|
||||||
|
|
||||||
|
|
||||||
import transport, auth_transport, channel, rsakey, dsskey, message, ssh_exception, sftp
|
import transport, auth_transport, channel, rsakey, dsskey, message, ssh_exception
|
||||||
|
import sftp, sftp_client
|
||||||
|
|
||||||
Transport = auth_transport.Transport
|
Transport = auth_transport.Transport
|
||||||
Channel = channel.Channel
|
Channel = channel.Channel
|
||||||
|
@ -75,9 +76,12 @@ DSSKey = dsskey.DSSKey
|
||||||
SSHException = ssh_exception.SSHException
|
SSHException = ssh_exception.SSHException
|
||||||
Message = message.Message
|
Message = message.Message
|
||||||
PasswordRequiredException = ssh_exception.PasswordRequiredException
|
PasswordRequiredException = ssh_exception.PasswordRequiredException
|
||||||
SFTP = sftp.SFTP
|
SFTP = sftp_client.SFTP
|
||||||
|
SFTPError = sftp_client.SFTPError
|
||||||
|
SFTPAttributes = sftp_client.SFTPAttributes
|
||||||
ServerInterface = server.ServerInterface
|
ServerInterface = server.ServerInterface
|
||||||
SecurityOptions = transport.SecurityOptions
|
SecurityOptions = transport.SecurityOptions
|
||||||
|
SubsystemHandler = transport.SubsystemHandler
|
||||||
|
|
||||||
from common import AUTH_SUCCESSFUL, AUTH_PARTIALLY_SUCCESSFUL, AUTH_FAILED, \
|
from common import AUTH_SUCCESSFUL, AUTH_PARTIALLY_SUCCESSFUL, AUTH_FAILED, \
|
||||||
OPEN_SUCCEEDED, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, OPEN_FAILED_CONNECT_FAILED, \
|
OPEN_SUCCEEDED, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, OPEN_FAILED_CONNECT_FAILED, \
|
||||||
|
@ -85,6 +89,7 @@ from common import AUTH_SUCCESSFUL, AUTH_PARTIALLY_SUCCESSFUL, AUTH_FAILED, \
|
||||||
|
|
||||||
__all__ = [ 'Transport',
|
__all__ = [ 'Transport',
|
||||||
'SecurityOptions',
|
'SecurityOptions',
|
||||||
|
'SubsystemHandler',
|
||||||
'Channel',
|
'Channel',
|
||||||
'RSAKey',
|
'RSAKey',
|
||||||
'DSSKey',
|
'DSSKey',
|
||||||
|
@ -92,6 +97,9 @@ __all__ = [ 'Transport',
|
||||||
'SSHException',
|
'SSHException',
|
||||||
'PasswordRequiredException',
|
'PasswordRequiredException',
|
||||||
'SFTP',
|
'SFTP',
|
||||||
|
'SFTPClient',
|
||||||
|
'SFTPError',
|
||||||
|
'SFTPAttributes',
|
||||||
'ServerInterface',
|
'ServerInterface',
|
||||||
'transport',
|
'transport',
|
||||||
'auth_transport',
|
'auth_transport',
|
||||||
|
@ -101,6 +109,6 @@ __all__ = [ 'Transport',
|
||||||
'pkey',
|
'pkey',
|
||||||
'message',
|
'message',
|
||||||
'ssh_exception',
|
'ssh_exception',
|
||||||
'sftp',
|
'sftp_client',
|
||||||
'server',
|
'server',
|
||||||
'util' ]
|
'util' ]
|
||||||
|
|
385
paramiko/sftp.py
385
paramiko/sftp.py
|
@ -25,16 +25,23 @@ from channel import Channel
|
||||||
from message import Message
|
from message import Message
|
||||||
from file import BufferedFile
|
from file import BufferedFile
|
||||||
|
|
||||||
_CMD_INIT, _CMD_VERSION, _CMD_OPEN, _CMD_CLOSE, _CMD_READ, _CMD_WRITE, _CMD_LSTAT, _CMD_FSTAT, \
|
CMD_INIT, CMD_VERSION, CMD_OPEN, CMD_CLOSE, CMD_READ, CMD_WRITE, CMD_LSTAT, CMD_FSTAT, \
|
||||||
_CMD_SETSTAT, _CMD_FSETSTAT, _CMD_OPENDIR, _CMD_READDIR, _CMD_REMOVE, _CMD_MKDIR, \
|
CMD_SETSTAT, CMD_FSETSTAT, CMD_OPENDIR, CMD_READDIR, CMD_REMOVE, CMD_MKDIR, \
|
||||||
_CMD_RMDIR, _CMD_REALPATH, _CMD_STAT, _CMD_RENAME, _CMD_READLINK, _CMD_SYMLINK \
|
CMD_RMDIR, CMD_REALPATH, CMD_STAT, CMD_RENAME, CMD_READLINK, CMD_SYMLINK \
|
||||||
= range(1, 21)
|
= range(1, 21)
|
||||||
_CMD_STATUS, _CMD_HANDLE, _CMD_DATA, _CMD_NAME, _CMD_ATTRS = range(101, 106)
|
CMD_STATUS, CMD_HANDLE, CMD_DATA, CMD_NAME, CMD_ATTRS = range(101, 106)
|
||||||
_CMD_EXTENDED, _CMD_EXTENDED_REPLY = range(200, 202)
|
CMD_EXTENDED, CMD_EXTENDED_REPLY = range(200, 202)
|
||||||
|
|
||||||
_FX_OK = 0
|
FX_OK = 0
|
||||||
_FX_EOF, _FX_NO_SUCH_FILE, _FX_PERMISSION_DENIED, _FX_FAILURE, _FX_BAD_MESSAGE, \
|
FX_EOF, FX_NO_SUCH_FILE, FX_PERMISSION_DENIED, FX_FAILURE, FX_BAD_MESSAGE, \
|
||||||
_FX_NO_CONNECTION, _FX_CONNECTION_LOST, _FX_OP_UNSUPPORTED = range(1, 9)
|
FX_NO_CONNECTION, FX_CONNECTION_LOST, FX_OP_UNSUPPORTED = range(1, 9)
|
||||||
|
|
||||||
|
FXF_READ = 0x1
|
||||||
|
FXF_WRITE = 0x2
|
||||||
|
FXF_APPEND = 0x4
|
||||||
|
FXF_CREATE = 0x8
|
||||||
|
FXF_TRUNC = 0x10
|
||||||
|
FXF_EXCL = 0x20
|
||||||
|
|
||||||
_VERSION = 3
|
_VERSION = 3
|
||||||
|
|
||||||
|
@ -136,8 +143,8 @@ class SFTPFile (BufferedFile):
|
||||||
BufferedFile._set_mode(self, mode, bufsize)
|
BufferedFile._set_mode(self, mode, bufsize)
|
||||||
|
|
||||||
def _get_size(self):
|
def _get_size(self):
|
||||||
t, msg = self.sftp._request(_CMD_FSTAT, self.handle)
|
t, msg = self.sftp._request(CMD_FSTAT, self.handle)
|
||||||
if t != _CMD_ATTRS:
|
if t != CMD_ATTRS:
|
||||||
raise SFTPError('Expected attrs')
|
raise SFTPError('Expected attrs')
|
||||||
attr = SFTPAttributes()
|
attr = SFTPAttributes()
|
||||||
attr.unpack(msg)
|
attr.unpack(msg)
|
||||||
|
@ -148,12 +155,12 @@ class SFTPFile (BufferedFile):
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
BufferedFile.close(self)
|
BufferedFile.close(self)
|
||||||
self.sftp._request(_CMD_CLOSE, self.handle)
|
self.sftp._request(CMD_CLOSE, self.handle)
|
||||||
|
|
||||||
def _read(self, size):
|
def _read(self, size):
|
||||||
size = min(size, self.MAX_REQUEST_SIZE)
|
size = min(size, self.MAX_REQUEST_SIZE)
|
||||||
t, msg = self.sftp._request(_CMD_READ, self.handle, long(self._realpos), int(size))
|
t, msg = self.sftp._request(CMD_READ, self.handle, long(self._realpos), int(size))
|
||||||
if t != _CMD_DATA:
|
if t != CMD_DATA:
|
||||||
raise SFTPError('Expected data')
|
raise SFTPError('Expected data')
|
||||||
return msg.get_string()
|
return msg.get_string()
|
||||||
|
|
||||||
|
@ -161,7 +168,7 @@ class SFTPFile (BufferedFile):
|
||||||
offset = 0
|
offset = 0
|
||||||
while offset < len(data):
|
while offset < len(data):
|
||||||
chunk = min(len(data) - offset, self.MAX_REQUEST_SIZE)
|
chunk = min(len(data) - offset, self.MAX_REQUEST_SIZE)
|
||||||
t, msg = self.sftp._request(_CMD_WRITE, self.handle, long(self._realpos + offset),
|
t, msg = self.sftp._request(CMD_WRITE, self.handle, long(self._realpos + offset),
|
||||||
str(data[offset : offset + chunk]))
|
str(data[offset : offset + chunk]))
|
||||||
offset += chunk
|
offset += chunk
|
||||||
return len(data)
|
return len(data)
|
||||||
|
@ -220,322 +227,29 @@ class SFTPFile (BufferedFile):
|
||||||
@return: an object containing attributes about this file.
|
@return: an object containing attributes about this file.
|
||||||
@rtype: SFTPAttributes
|
@rtype: SFTPAttributes
|
||||||
"""
|
"""
|
||||||
t, msg = self.sftp._request(_CMD_FSTAT, self.handle)
|
t, msg = self.sftp._request(CMD_FSTAT, self.handle)
|
||||||
if t != _CMD_ATTRS:
|
if t != CMD_ATTRS:
|
||||||
raise SFTPError('Expected attributes')
|
raise SFTPError('Expected attributes')
|
||||||
attr = SFTPAttributes(msg)
|
attr = SFTPAttributes(msg)
|
||||||
attr._pythonize()
|
attr._pythonize()
|
||||||
return attr
|
return attr
|
||||||
|
|
||||||
|
|
||||||
class SFTP (object):
|
class BaseSFTP (object):
|
||||||
def __init__(self, sock):
|
def _send_version(self):
|
||||||
self.sock = sock
|
self._send_packet(CMD_INIT, struct.pack('>I', _VERSION))
|
||||||
self.ultra_debug = False
|
|
||||||
self.request_number = 1
|
|
||||||
if type(sock) is Channel:
|
|
||||||
self.logger = logging.getLogger('paramiko.chan.' + sock.get_name() + '.sftp')
|
|
||||||
else:
|
|
||||||
self.logger = logging.getLogger('paramiko.sftp')
|
|
||||||
# protocol: (maybe should move to a different method)
|
|
||||||
self._send_packet(_CMD_INIT, struct.pack('>I', _VERSION))
|
|
||||||
t, data = self._read_packet()
|
t, data = self._read_packet()
|
||||||
if t != _CMD_VERSION:
|
if t != CMD_VERSION:
|
||||||
raise SFTPError('Incompatible sftp protocol')
|
raise SFTPError('Incompatible sftp protocol')
|
||||||
version = struct.unpack('>I', data[:4])[0]
|
version = struct.unpack('>I', data[:4])[0]
|
||||||
# if version != _VERSION:
|
# if version != _VERSION:
|
||||||
# raise SFTPError('Incompatible sftp protocol')
|
# raise SFTPError('Incompatible sftp protocol')
|
||||||
|
return version
|
||||||
def from_transport(selfclass, t):
|
|
||||||
"""
|
|
||||||
Create an SFTP client channel from an open L{Transport}.
|
|
||||||
|
|
||||||
@param t: an open L{Transport} which is already authenticated.
|
|
||||||
@type t: L{Transport}
|
|
||||||
@return: a new L{SFTP} object, referring to an sftp session (channel)
|
|
||||||
across the transport.
|
|
||||||
@rtype: L{SFTP}
|
|
||||||
"""
|
|
||||||
chan = t.open_session()
|
|
||||||
if chan is None:
|
|
||||||
return None
|
|
||||||
if not chan.invoke_subsystem('sftp'):
|
|
||||||
raise SFTPError('Failed to invoke sftp subsystem')
|
|
||||||
return selfclass(chan)
|
|
||||||
from_transport = classmethod(from_transport)
|
|
||||||
|
|
||||||
def listdir(self, path):
|
|
||||||
"""
|
|
||||||
Return a list containing the names of the entries in the given C{path}.
|
|
||||||
The list is in arbitrary order. It does not include the special
|
|
||||||
entries C{'.'} and C{'..'} even if they are present in the folder.
|
|
||||||
|
|
||||||
@param path: path to list.
|
|
||||||
@type path: string
|
|
||||||
@return: list of filenames.
|
|
||||||
@rtype: list of string
|
|
||||||
"""
|
|
||||||
t, msg = self._request(_CMD_OPENDIR, path)
|
|
||||||
if t != _CMD_HANDLE:
|
|
||||||
raise SFTPError('Expected handle')
|
|
||||||
handle = msg.get_string()
|
|
||||||
filelist = []
|
|
||||||
while 1:
|
|
||||||
try:
|
|
||||||
t, msg = self._request(_CMD_READDIR, handle)
|
|
||||||
except EOFError, e:
|
|
||||||
# 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_string()
|
|
||||||
longname = msg.get_string()
|
|
||||||
attr = SFTPAttributes(msg)
|
|
||||||
if (filename != '.') and (filename != '..'):
|
|
||||||
filelist.append(filename)
|
|
||||||
# currently we ignore the rest
|
|
||||||
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 C{open} (aka C{file}). A file-like object is
|
|
||||||
returned, which closely mimics the behavior of a normal python file
|
|
||||||
object.
|
|
||||||
|
|
||||||
The mode indicates how the file is to be opened: C{'r'} for reading,
|
|
||||||
C{'w'} for writing (truncating an existing file), C{'a'} for appending,
|
|
||||||
C{'r+'} for reading/writing, C{'w+'} for reading/writing (truncating an
|
|
||||||
existing file), C{'a+'} for reading/appending. The python C{'b'} flag
|
|
||||||
is ignored, since SSH treats all files as binary. The C{'U'} flag is
|
|
||||||
supported in a compatible way.
|
|
||||||
|
|
||||||
@param filename: name of the file to open.
|
|
||||||
@type filename: string
|
|
||||||
@param mode: mode (python-style) to open in.
|
|
||||||
@type mode: string
|
|
||||||
@param bufsize: desired buffering (-1 = default buffer size, 0 =
|
|
||||||
unbuffered, 1 = line buffered, >1 = requested buffer size).
|
|
||||||
@type bufsize: int
|
|
||||||
@return: a file object representing the open file.
|
|
||||||
@rtype: SFTPFile
|
|
||||||
|
|
||||||
@raise IOError: if the file could not be opened.
|
|
||||||
"""
|
|
||||||
imode = 0
|
|
||||||
if ('r' in mode) or ('+' in mode):
|
|
||||||
imode |= self._FXF_READ
|
|
||||||
if ('w' in mode) or ('+' in mode):
|
|
||||||
imode |= self._FXF_WRITE
|
|
||||||
if ('w' in mode):
|
|
||||||
imode |= self._FXF_CREATE | self._FXF_TRUNC
|
|
||||||
if ('a' in mode):
|
|
||||||
imode |= self._FXF_APPEND
|
|
||||||
attrblock = SFTPAttributes()
|
|
||||||
t, msg = self._request(_CMD_OPEN, filename, imode, attrblock)
|
|
||||||
if t != _CMD_HANDLE:
|
|
||||||
raise SFTPError('Expected handle')
|
|
||||||
handle = msg.get_string()
|
|
||||||
return SFTPFile(self, handle, mode, bufsize)
|
|
||||||
|
|
||||||
def remove(self, path):
|
|
||||||
"""
|
|
||||||
Remove the file at the given path.
|
|
||||||
|
|
||||||
@param path: path (absolute or relative) of the file to remove.
|
|
||||||
@type path: string
|
|
||||||
|
|
||||||
@raise IOError: if the path refers to a folder (directory). Use
|
|
||||||
L{rmdir} to remove a folder.
|
|
||||||
"""
|
|
||||||
self._request(_CMD_REMOVE, path)
|
|
||||||
|
|
||||||
unlink = remove
|
|
||||||
|
|
||||||
def rename(self, oldpath, newpath):
|
|
||||||
"""
|
|
||||||
Rename a file or folder from C{oldpath} to C{newpath}.
|
|
||||||
|
|
||||||
@param oldpath: existing name of the file or folder.
|
|
||||||
@type oldpath: string
|
|
||||||
@param newpath: new name for the file or folder.
|
|
||||||
@type newpath: string
|
|
||||||
|
|
||||||
@raise IOError: if C{newpath} is a folder, or something else goes
|
|
||||||
wrong.
|
|
||||||
"""
|
|
||||||
self._request(_CMD_RENAME, oldpath, newpath)
|
|
||||||
|
|
||||||
def mkdir(self, path, mode=0777):
|
|
||||||
"""
|
|
||||||
Create a folder (directory) named C{path} with numeric mode C{mode}.
|
|
||||||
The default mode is 0777 (octal). On some systems, mode is ignored.
|
|
||||||
Where it is used, the current umask value is first masked out.
|
|
||||||
|
|
||||||
@param path: name of the folder to create.
|
|
||||||
@type path: string
|
|
||||||
@param mode: permissions (posix-style) for the newly-created folder.
|
|
||||||
@type mode: int
|
|
||||||
"""
|
|
||||||
attr = SFTPAttributes()
|
|
||||||
attr.permissions = mode
|
|
||||||
self._request(_CMD_MKDIR, path, attr)
|
|
||||||
|
|
||||||
def rmdir(self, path):
|
|
||||||
"""
|
|
||||||
Remove the folder named C{path}.
|
|
||||||
|
|
||||||
@param path: name of the folder to remove.
|
|
||||||
@type path: string
|
|
||||||
"""
|
|
||||||
self._request(_CMD_RMDIR, path)
|
|
||||||
|
|
||||||
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 C{stat} structure as returned by C{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 C{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: C{st_mode}, C{st_size}, C{st_uid}, C{st_gid},
|
|
||||||
C{st_atime}, and C{st_mtime}.
|
|
||||||
|
|
||||||
@param path: the filename to stat.
|
|
||||||
@type path: string
|
|
||||||
@return: an object containing attributes about the given file.
|
|
||||||
@rtype: SFTPAttributes
|
|
||||||
"""
|
|
||||||
t, msg = self._request(_CMD_STAT, path)
|
|
||||||
if t != _CMD_ATTRS:
|
|
||||||
raise SFTPError('Expected attributes')
|
|
||||||
attr = SFTPAttributes(msg)
|
|
||||||
attr._pythonize()
|
|
||||||
return attr
|
|
||||||
|
|
||||||
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 L{stat}.
|
|
||||||
|
|
||||||
@param path: the filename to stat.
|
|
||||||
@type path: string
|
|
||||||
@return: an object containing attributes about the given file.
|
|
||||||
@rtype: SFTPAttributes
|
|
||||||
"""
|
|
||||||
t, msg = self._request(_CMD_LSTAT, path)
|
|
||||||
if t != _CMD_ATTRS:
|
|
||||||
raise SFTPError('Expected attributes')
|
|
||||||
attr = SFTPAttributes(msg)
|
|
||||||
attr._pythonize()
|
|
||||||
return attr
|
|
||||||
|
|
||||||
def symlink(self, source, dest):
|
|
||||||
"""
|
|
||||||
Create a symbolic link (shortcut) of the C{source} path at
|
|
||||||
C{destination}.
|
|
||||||
|
|
||||||
@param source: path of the original file.
|
|
||||||
@type source: string
|
|
||||||
@param dest: path of the newly created symlink.
|
|
||||||
@type dest: string
|
|
||||||
"""
|
|
||||||
self._request(_CMD_SYMLINK, source, dest)
|
|
||||||
|
|
||||||
def chmod(self, path, mode):
|
|
||||||
"""
|
|
||||||
Change the mode (permissions) of a file. The permissions are
|
|
||||||
unix-style and identical to those used by python's C{os.chmod}
|
|
||||||
function.
|
|
||||||
|
|
||||||
@param path: path of the file to change the permissions of.
|
|
||||||
@type path: string
|
|
||||||
@param mode: new permissions.
|
|
||||||
@type mode: int
|
|
||||||
"""
|
|
||||||
attr = SFTPAttributes()
|
|
||||||
attr.permissions = mode
|
|
||||||
self._request(_CMD_SETSTAT, path, attr)
|
|
||||||
|
|
||||||
def chown(self, path, uid, gid):
|
|
||||||
"""
|
|
||||||
Change the owner (C{uid}) and group (C{gid}) of a file. As with
|
|
||||||
python's C{os.chown} function, you must pass both arguments, so if you
|
|
||||||
only want to change one, use L{stat} first to retrieve the current
|
|
||||||
owner and group.
|
|
||||||
|
|
||||||
@param path: path of the file to change the owner and group of.
|
|
||||||
@type path: string
|
|
||||||
@param uid: new owner's uid
|
|
||||||
@type uid: int
|
|
||||||
@param gid: new group id
|
|
||||||
@type gid: int
|
|
||||||
"""
|
|
||||||
attr = SFTPAttributes()
|
|
||||||
attr.uid, attr.gid = uid, gid
|
|
||||||
self._request(_CMD_SETSTAT, path, attr)
|
|
||||||
|
|
||||||
def utime(self, path, times):
|
|
||||||
"""
|
|
||||||
Set the access and modified times of the file specified by C{path}. If
|
|
||||||
C{times} is C{None}, then the file's access and modified times are set
|
|
||||||
to the current time. Otherwise, C{times} must be a 2-tuple of numbers,
|
|
||||||
of the form C{(atime, mtime)}, which is used to set the access and
|
|
||||||
modified times, respectively. This bizarre API is mimicked from python
|
|
||||||
for the sake of consistency -- I apologize.
|
|
||||||
|
|
||||||
@param path: path of the file to modify.
|
|
||||||
@type path: string
|
|
||||||
@param times: C{None} or a tuple of (access time, modified time) in
|
|
||||||
standard internet epoch time (seconds since 01 January 1970 GMT).
|
|
||||||
@type times: tuple of int
|
|
||||||
"""
|
|
||||||
if times is None:
|
|
||||||
times = (time.time(), time.time())
|
|
||||||
attr = SFTPAttributes()
|
|
||||||
attr.atime, attr.mtime = times
|
|
||||||
self._request(_CMD_SETSTAT, path, attr)
|
|
||||||
|
|
||||||
def readlink(self, path):
|
|
||||||
"""
|
|
||||||
Return the target of a symbolic link (shortcut). You can use
|
|
||||||
L{symlink} to create these. The result may be either an absolute or
|
|
||||||
relative pathname.
|
|
||||||
|
|
||||||
@param path: path of the symbolic link file.
|
|
||||||
@type path: string
|
|
||||||
@return: target path.
|
|
||||||
@rtype: string
|
|
||||||
"""
|
|
||||||
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 msg.get_string()
|
|
||||||
|
|
||||||
|
|
||||||
### internals...
|
### internals...
|
||||||
|
|
||||||
|
|
||||||
_FXF_READ = 0x1
|
|
||||||
_FXF_WRITE = 0x2
|
|
||||||
_FXF_APPEND = 0x4
|
|
||||||
_FXF_CREATE = 0x8
|
|
||||||
_FXF_TRUNC = 0x10
|
|
||||||
_FXF_EXCL = 0x20
|
|
||||||
|
|
||||||
|
|
||||||
def _log(self, level, msg):
|
def _log(self, level, msg):
|
||||||
if type(msg) == type([]):
|
if type(msg) == type([]):
|
||||||
for m in msg:
|
for m in msg:
|
||||||
|
@ -577,42 +291,3 @@ class SFTP (object):
|
||||||
if size > 0:
|
if size > 0:
|
||||||
return ord(data[0]), data[1:]
|
return ord(data[0]), data[1:]
|
||||||
return 0, ''
|
return 0, ''
|
||||||
|
|
||||||
def _request(self, t, *arg):
|
|
||||||
msg = Message()
|
|
||||||
msg.add_int(self.request_number)
|
|
||||||
for item in arg:
|
|
||||||
if type(item) is int:
|
|
||||||
msg.add_int(item)
|
|
||||||
elif type(item) is long:
|
|
||||||
msg.add_int64(item)
|
|
||||||
elif type(item) is str:
|
|
||||||
msg.add_string(item)
|
|
||||||
elif type(item) is SFTPAttributes:
|
|
||||||
item.pack(msg)
|
|
||||||
else:
|
|
||||||
raise Exception('unknown type for ' + repr(item) + ' type ' + repr(type(item)))
|
|
||||||
self._send_packet(t, str(msg))
|
|
||||||
t, data = self._read_packet()
|
|
||||||
msg = Message(data)
|
|
||||||
num = msg.get_int()
|
|
||||||
if num != self.request_number:
|
|
||||||
raise SFTPError('Expected response #%d, got response #%d' % (self.request_number, num))
|
|
||||||
self.request_number += 1
|
|
||||||
if t == _CMD_STATUS:
|
|
||||||
self._convert_status(msg)
|
|
||||||
return t, msg
|
|
||||||
|
|
||||||
def _convert_status(self, msg):
|
|
||||||
"""
|
|
||||||
Raises EOFError or IOError on error status; otherwise does nothing.
|
|
||||||
"""
|
|
||||||
code = msg.get_int()
|
|
||||||
text = msg.get_string()
|
|
||||||
if code == _FX_OK:
|
|
||||||
return
|
|
||||||
elif code == _FX_EOF:
|
|
||||||
raise EOFError(text)
|
|
||||||
else:
|
|
||||||
raise IOError(text)
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue