summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--paramiko/sftp_client.py109
-rw-r--r--paramiko/sftp_file.py65
-rw-r--r--paramiko/sftp_server.py2
-rw-r--r--tests/stub_sftp.py1
-rwxr-xr-xtests/test_sftp.py84
5 files changed, 193 insertions, 68 deletions
diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py
index c7ab771f..f02827e6 100644
--- a/paramiko/sftp_client.py
+++ b/paramiko/sftp_client.py
@@ -59,7 +59,7 @@ class SFTPClient (BaseSFTP):
An alternate way to create an SFTP client context is by using
L{from_transport}.
- @param sock: an open L{Channel} using the C{"sftp"} subsystem.
+ @param sock: an open L{Channel} using the C{"sftp"} subsystem
@type sock: L{Channel}
"""
BaseSFTP.__init__(self)
@@ -84,10 +84,10 @@ class SFTPClient (BaseSFTP):
"""
Create an SFTP client channel from an open L{Transport}.
- @param t: an open L{Transport} which is already authenticated.
+ @param t: an open L{Transport} which is already authenticated
@type t: L{Transport}
@return: a new L{SFTPClient} object, referring to an sftp session
- (channel) across the transport.
+ (channel) across the transport
@rtype: L{SFTPClient}
"""
chan = t.open_session()
@@ -185,13 +185,13 @@ class SFTPClient (BaseSFTP):
buffering, C{1} uses line buffering, and any number greater than 1
(C{>1}) uses that specific buffer size.
- @param filename: name of the file to open.
- @type filename: string
- @param mode: mode (python-style) to open in.
- @type mode: string
+ @param filename: name of the file to open
+ @type filename: str
+ @param mode: mode (python-style) to open in
+ @type mode: str
@param bufsize: desired buffering (-1 = default buffer size)
@type bufsize: int
- @return: a file object representing the open file.
+ @return: a file object representing the open file
@rtype: SFTPFile
@raise IOError: if the file could not be opened.
@@ -223,13 +223,13 @@ class SFTPClient (BaseSFTP):
def remove(self, path):
"""
- Remove the file at the given path.
+ Remove the file at the given path. This only works on files; for
+ removing folders (directories), use L{rmdir}.
- @param path: path (absolute or relative) of the file to remove.
- @type path: string
+ @param path: path (absolute or relative) of the file to remove
+ @type path: str
- @raise IOError: if the path refers to a folder (directory). Use
- L{rmdir} to remove a folder.
+ @raise IOError: if the path refers to a folder (directory)
"""
path = self._adjust_cwd(path)
self._log(DEBUG, 'remove(%r)' % path)
@@ -241,13 +241,13 @@ class SFTPClient (BaseSFTP):
"""
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
+ @param oldpath: existing name of the file or folder
+ @type oldpath: str
+ @param newpath: new name for the file or folder
+ @type newpath: str
@raise IOError: if C{newpath} is a folder, or something else goes
- wrong.
+ wrong
"""
oldpath = self._adjust_cwd(oldpath)
newpath = self._adjust_cwd(newpath)
@@ -260,9 +260,9 @@ class SFTPClient (BaseSFTP):
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.
+ @param path: name of the folder to create
+ @type path: str
+ @param mode: permissions (posix-style) for the newly-created folder
@type mode: int
"""
path = self._adjust_cwd(path)
@@ -275,8 +275,8 @@ class SFTPClient (BaseSFTP):
"""
Remove the folder named C{path}.
- @param path: name of the folder to remove.
- @type path: string
+ @param path: name of the folder to remove
+ @type path: str
"""
path = self._adjust_cwd(path)
self._log(DEBUG, 'rmdir(%r)' % path)
@@ -296,9 +296,9 @@ class SFTPClient (BaseSFTP):
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.
+ @param path: the filename to stat
+ @type path: str
+ @return: an object containing attributes about the given file
@rtype: SFTPAttributes
"""
path = self._adjust_cwd(path)
@@ -314,9 +314,9 @@ class SFTPClient (BaseSFTP):
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.
+ @param path: the filename to stat
+ @type path: str
+ @return: an object containing attributes about the given file
@rtype: SFTPAttributes
"""
path = self._adjust_cwd(path)
@@ -331,10 +331,10 @@ class SFTPClient (BaseSFTP):
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
+ @param source: path of the original file
+ @type source: str
+ @param dest: path of the newly created symlink
+ @type dest: str
"""
dest = self._adjust_cwd(dest)
self._log(DEBUG, 'symlink(%r, %r)' % (source, dest))
@@ -348,9 +348,9 @@ class SFTPClient (BaseSFTP):
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.
+ @param path: path of the file to change the permissions of
+ @type path: str
+ @param mode: new permissions
@type mode: int
"""
path = self._adjust_cwd(path)
@@ -366,8 +366,8 @@ class SFTPClient (BaseSFTP):
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 path: path of the file to change the owner and group of
+ @type path: str
@param uid: new owner's uid
@type uid: int
@param gid: new group id
@@ -388,11 +388,11 @@ class SFTPClient (BaseSFTP):
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 path: path of the file to modify
+ @type path: str
@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
+ standard internet epoch time (seconds since 01 January 1970 GMT)
+ @type times: tuple(int)
"""
path = self._adjust_cwd(path)
if times is None:
@@ -402,15 +402,32 @@ class SFTPClient (BaseSFTP):
attr.st_atime, attr.st_mtime = times
self._request(CMD_SETSTAT, path, attr)
+ def truncate(self, path, size):
+ """
+ Change the size of the file specified by C{path}. This usually extends
+ or shrinks the size of the file, just like the C{truncate()} method on
+ python file objects.
+
+ @param path: path of the file to modify
+ @type path: str
+ @param size: the new size of the file
+ @type size: int or long
+ """
+ path = self._adjust_cwd(path)
+ self._log(DEBUG, 'truncate(%r, %r)' % (path, size))
+ attr = SFTPAttributes()
+ attr.st_size = size
+ 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.
+ @param path: path of the symbolic link file
@type path: str
- @return: target path.
+ @return: target path
@rtype: str
"""
path = self._adjust_cwd(path)
@@ -432,9 +449,9 @@ class SFTPClient (BaseSFTP):
server is considering to be the "current folder" (by passing C{'.'}
as C{path}).
- @param path: path to be normalized.
+ @param path: path to be normalized
@type path: str
- @return: normalized form of the given path.
+ @return: normalized form of the given path
@rtype: str
@raise IOError: if the path can't be resolved on the server
diff --git a/paramiko/sftp_file.py b/paramiko/sftp_file.py
index 5bc08187..02c6e760 100644
--- a/paramiko/sftp_file.py
+++ b/paramiko/sftp_file.py
@@ -181,6 +181,71 @@ class SFTPFile (BufferedFile):
if t != CMD_ATTRS:
raise SFTPError('Expected attributes')
return SFTPAttributes._from_msg(msg)
+
+ def chmod(self, mode):
+ """
+ Change the mode (permissions) of this file. The permissions are
+ unix-style and identical to those used by python's C{os.chmod}
+ function.
+
+ @param mode: new permissions
+ @type mode: int
+ """
+ self.sftp._log(DEBUG, 'chmod(%s, %r)' % (util.hexify(self.handle), mode))
+ attr = SFTPAttributes()
+ attr.st_mode = mode
+ self.sftp._request(CMD_FSETSTAT, self.handle, attr)
+
+ def chown(self, uid, gid):
+ """
+ Change the owner (C{uid}) and group (C{gid}) of this 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 uid: new owner's uid
+ @type uid: int
+ @param gid: new group id
+ @type gid: int
+ """
+ self.sftp._log(DEBUG, 'chown(%s, %r, %r)' % (util.hexify(self.handle), uid, gid))
+ attr = SFTPAttributes()
+ attr.st_uid, attr.st_gid = uid, gid
+ self.sftp._request(CMD_FSETSTAT, self.handle, attr)
+
+ def utime(self, times):
+ """
+ Set the access and modified times of this file. 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 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(int)
+ """
+ if times is None:
+ times = (time.time(), time.time())
+ self.sftp._log(DEBUG, 'utime(%s, %r)' % (util.hexify(self.handle), times))
+ attr = SFTPAttributes()
+ attr.st_atime, attr.st_mtime = times
+ self.sftp._request(CMD_FSETSTAT, self.handle, attr)
+
+ def truncate(self, size):
+ """
+ Change the size of this file. This usually extends
+ or shrinks the size of the file, just like the C{truncate()} method on
+ python file objects.
+
+ @param size: the new size of the file
+ @type size: int or long
+ """
+ self.sftp._log(DEBUG, 'truncate(%s, %r)' % (util.hexify(self.handle), size))
+ attr = SFTPAttributes()
+ attr.st_size = size
+ self.sftp._request(CMD_FSETSTAT, self.handle, attr)
def check(self, hash_algorithm, offset=0, length=0, block_size=0):
"""
diff --git a/paramiko/sftp_server.py b/paramiko/sftp_server.py
index 34f91ade..0c122f78 100644
--- a/paramiko/sftp_server.py
+++ b/paramiko/sftp_server.py
@@ -147,6 +147,8 @@ class SFTPServer (BaseSFTP, SubsystemHandler):
os.chown(filename, attr.st_uid, attr.st_gid)
if attr._flags & attr.FLAG_AMTIME:
os.utime(filename, (attr.st_atime, attr.st_mtime))
+ if attr._flags & attr.FLAG_SIZE:
+ open(filename, 'w+').truncate(attr.st_size)
set_file_attr = staticmethod(set_file_attr)
diff --git a/tests/stub_sftp.py b/tests/stub_sftp.py
index 4b8b9c3c..16e91b46 100644
--- a/tests/stub_sftp.py
+++ b/tests/stub_sftp.py
@@ -48,6 +48,7 @@ class StubSFTPHandle (SFTPHandle):
# use the stored filename
try:
SFTPServer.set_file_attr(self.filename, attr)
+ return SFTP_OK
except OSError, e:
return SFTPServer.convert_errno(e.errno)
diff --git a/tests/test_sftp.py b/tests/test_sftp.py
index 993899ad..77ab2ed6 100755
--- a/tests/test_sftp.py
+++ b/tests/test_sftp.py
@@ -273,28 +273,68 @@ class SFTPTest (unittest.TestCase):
def test_8_setstat(self):
"""
- verify that the setstat functions (chown, chmod, utime) work.
+ verify that the setstat functions (chown, chmod, utime, truncate) work.
"""
f = sftp.open(FOLDER + '/special', 'w')
try:
+ f.write('x' * 1024)
f.close()
stat = sftp.stat(FOLDER + '/special')
sftp.chmod(FOLDER + '/special', (stat.st_mode & ~0777) | 0600)
- self.assertEqual(sftp.stat(FOLDER + '/special').st_mode & 0777, 0600)
+ stat = sftp.stat(FOLDER + '/special')
+ self.assertEqual(stat.st_mode & 0777, 0600)
+ self.assertEqual(stat.st_size, 1024)
mtime = stat.st_mtime - 3600
atime = stat.st_atime - 1800
sftp.utime(FOLDER + '/special', (atime, mtime))
- nstat = sftp.stat(FOLDER + '/special')
- self.assertEqual(nstat.st_mtime, mtime)
- self.assertEqual(nstat.st_atime, atime)
+ stat = sftp.stat(FOLDER + '/special')
+ self.assertEqual(stat.st_mtime, mtime)
+ self.assertEqual(stat.st_atime, atime)
# can't really test chown, since we'd have to know a valid uid.
+
+ sftp.truncate(FOLDER + '/special', 512)
+ stat = sftp.stat(FOLDER + '/special')
+ self.assertEqual(stat.st_size, 512)
finally:
sftp.remove(FOLDER + '/special')
- def test_9_readline_seek(self):
+ def test_9_fsetstat(self):
+ """
+ verify that the fsetstat functions (chown, chmod, utime, truncate)
+ work on open files.
+ """
+ f = sftp.open(FOLDER + '/special', 'w')
+ try:
+ f.write('x' * 1024)
+ f.close()
+
+ f = sftp.open(FOLDER + '/special', 'r+')
+ stat = f.stat()
+ f.chmod((stat.st_mode & ~0777) | 0600)
+ stat = f.stat()
+ self.assertEqual(stat.st_mode & 0777, 0600)
+ self.assertEqual(stat.st_size, 1024)
+
+ mtime = stat.st_mtime - 3600
+ atime = stat.st_atime - 1800
+ f.utime((atime, mtime))
+ stat = f.stat()
+ self.assertEqual(stat.st_mtime, mtime)
+ self.assertEqual(stat.st_atime, atime)
+
+ # can't really test chown, since we'd have to know a valid uid.
+
+ f.truncate(512)
+ stat = f.stat()
+ self.assertEqual(stat.st_size, 512)
+ f.close()
+ finally:
+ sftp.remove(FOLDER + '/special')
+
+ def test_A_readline_seek(self):
"""
create a text file and write a bunch of text into it. then count the lines
in the file, and seek around to retreive particular lines. this should
@@ -324,7 +364,7 @@ class SFTPTest (unittest.TestCase):
finally:
sftp.remove(FOLDER + '/duck.txt')
- def test_A_write_seek(self):
+ def test_B_write_seek(self):
"""
create a text file, seek back and change part of it, and verify that the
changes worked.
@@ -344,7 +384,7 @@ class SFTPTest (unittest.TestCase):
finally:
sftp.remove(FOLDER + '/testing.txt')
- def test_B_symlink(self):
+ def test_C_symlink(self):
"""
create a symlink and then check that lstat doesn't follow it.
"""
@@ -387,7 +427,7 @@ class SFTPTest (unittest.TestCase):
except:
pass
- def test_C_flush_seek(self):
+ def test_D_flush_seek(self):
"""
verify that buffered writes are automatically flushed on seek.
"""
@@ -409,7 +449,7 @@ class SFTPTest (unittest.TestCase):
except:
pass
- def test_D_lots_of_files(self):
+ def test_E_lots_of_files(self):
"""
create a bunch of files over the same session.
"""
@@ -440,7 +480,7 @@ class SFTPTest (unittest.TestCase):
except:
pass
- def test_E_big_file(self):
+ def test_F_big_file(self):
"""
write a 1MB file with no buffering.
"""
@@ -474,7 +514,7 @@ class SFTPTest (unittest.TestCase):
finally:
sftp.remove('%s/hongry.txt' % FOLDER)
- def test_F_big_file_pipelined(self):
+ def test_G_big_file_pipelined(self):
"""
write a 1MB file, with no linefeeds, using pipelining.
"""
@@ -510,7 +550,7 @@ class SFTPTest (unittest.TestCase):
finally:
sftp.remove('%s/hongry.txt' % FOLDER)
- def test_G_lots_of_prefetching(self):
+ def test_H_lots_of_prefetching(self):
"""
prefetch a 1MB file a bunch of times, discarding the file object
without using it, to verify that paramiko doesn't get confused.
@@ -546,7 +586,7 @@ class SFTPTest (unittest.TestCase):
finally:
sftp.remove('%s/hongry.txt' % FOLDER)
- def test_H_big_file_big_buffer(self):
+ def test_I_big_file_big_buffer(self):
"""
write a 1MB file, with no linefeeds, and a big buffer.
"""
@@ -563,7 +603,7 @@ class SFTPTest (unittest.TestCase):
finally:
sftp.remove('%s/hongry.txt' % FOLDER)
- def test_I_big_file_renegotiate(self):
+ def test_J_big_file_renegotiate(self):
"""
write a 1MB file, forcing key renegotiation in the middle.
"""
@@ -585,7 +625,7 @@ class SFTPTest (unittest.TestCase):
sftp.remove('%s/hongry.txt' % FOLDER)
t.packetizer.REKEY_BYTES = pow(2, 30)
- def test_J_realpath(self):
+ def test_K_realpath(self):
"""
test that realpath is returning something non-empty and not an
error.
@@ -596,7 +636,7 @@ class SFTPTest (unittest.TestCase):
self.assert_(len(f) > 0)
self.assertEquals(os.path.join(pwd, FOLDER), f)
- def test_K_mkdir(self):
+ def test_L_mkdir(self):
"""
verify that mkdir/rmdir work.
"""
@@ -619,7 +659,7 @@ class SFTPTest (unittest.TestCase):
except IOError:
pass
- def test_L_chdir(self):
+ def test_M_chdir(self):
"""
verify that chdir/getcwd work.
"""
@@ -656,7 +696,7 @@ class SFTPTest (unittest.TestCase):
except:
pass
- def test_M_get_put(self):
+ def test_N_get_put(self):
"""
verify that get/put work.
"""
@@ -685,7 +725,7 @@ class SFTPTest (unittest.TestCase):
os.unlink(localname)
sftp.unlink(FOLDER + '/bunny.txt')
- def test_N_check(self):
+ def test_O_check(self):
"""
verify that file.check() works against our own server.
(it's an sftp extension that we support, and may be the only ones who
@@ -707,7 +747,7 @@ class SFTPTest (unittest.TestCase):
finally:
sftp.unlink(FOLDER + '/kitty.txt')
- def test_O_x_flag(self):
+ def test_P_x_flag(self):
"""
verify that the 'x' flag works when opening a file.
"""
@@ -723,7 +763,7 @@ class SFTPTest (unittest.TestCase):
finally:
sftp.unlink(FOLDER + '/unusual.txt')
- def test_P_utf8(self):
+ def test_Q_utf8(self):
"""
verify that unicode strings are encoded into utf8 correctly.
"""