Skip to content

Commit 265cae1

Browse files
committed
sftp: Increase SFTP chunk size to specification-recommended 32kB
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
1 parent 83c22c2 commit 265cae1

File tree

3 files changed

+75
-21
lines changed

3 files changed

+75
-21
lines changed

src/pylibsshext/includes/libssh.pxd

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ cdef extern from "libssh/libssh.h" nogil:
2727
pass
2828
ctypedef ssh_session_struct* ssh_session
2929

30+
cdef struct ssh_string_struct:
31+
pass
32+
ctypedef ssh_string_struct* ssh_string
33+
3034
cdef struct ssh_key_struct:
3135
pass
3236
ctypedef ssh_key_struct* ssh_key

src/pylibsshext/includes/sftp.pxd

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
#
1818
from posix.types cimport mode_t
1919

20-
from pylibsshext.includes.libssh cimport ssh_channel, ssh_session
20+
from libc cimport stdint
21+
22+
from pylibsshext.includes.libssh cimport ssh_channel, ssh_session, ssh_string
2123

2224

2325
cdef extern from "libssh/sftp.h" nogil:
@@ -30,6 +32,31 @@ cdef extern from "libssh/sftp.h" nogil:
3032
pass
3133
ctypedef sftp_file_struct * sftp_file
3234

35+
struct sftp_attributes_struct:
36+
char *name
37+
char *longname
38+
stdint.uint32_t flags
39+
stdint.uint8_t type
40+
stdint.uint64_t size
41+
stdint.uint32_t uid
42+
stdint.uint32_t gid
43+
char *owner
44+
char *group
45+
stdint.uint32_t permissions
46+
stdint.uint64_t atime64
47+
stdint.uint32_t atime
48+
stdint.uint32_t atime_nseconds
49+
stdint.uint64_t createtime
50+
stdint.uint32_t createtime_nseconds
51+
stdint.uint64_t mtime64
52+
stdint.uint32_t mtime
53+
stdint.uint32_t mtime_nseconds
54+
ssh_string acl
55+
stdint.uint32_t extended_count
56+
ssh_string extended_type
57+
ssh_string extended_data
58+
ctypedef sftp_attributes_struct * sftp_attributes
59+
3360
cdef int SSH_FX_OK
3461
cdef int SSH_FX_EOF
3562
cdef int SSH_FX_NO_SUCH_FILE
@@ -55,5 +82,8 @@ cdef extern from "libssh/sftp.h" nogil:
5582
ssize_t sftp_read(sftp_file file, const void *buf, size_t count)
5683
int sftp_get_error(sftp_session sftp)
5784

85+
sftp_attributes sftp_stat(sftp_session session, const char *path)
86+
87+
5888
cdef extern from "sys/stat.h" nogil:
5989
cdef int S_IRWXU

src/pylibsshext/sftp.pyx

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,15 @@
1818
from posix.fcntl cimport O_CREAT, O_RDONLY, O_TRUNC, O_WRONLY
1919

2020
from cpython.bytes cimport PyBytes_AS_STRING
21+
from cpython.mem cimport PyMem_Free, PyMem_Malloc
2122

2223
from pylibsshext.errors cimport LibsshSFTPException
2324
from pylibsshext.session cimport get_libssh_session
2425

2526

27+
SFTP_MAX_CHUNK = 32_768 # 32kB
28+
29+
2630
MSG_MAP = {
2731
sftp.SSH_FX_OK: "No error",
2832
sftp.SSH_FX_EOF: "End-of-file encountered",
@@ -63,7 +67,7 @@ cdef class SFTP:
6367
rf = sftp.sftp_open(self._libssh_sftp_session, remote_file_b, O_WRONLY | O_CREAT | O_TRUNC, sftp.S_IRWXU)
6468
if rf is NULL:
6569
raise LibsshSFTPException("Opening remote file [%s] for write failed with error [%s]" % (remote_file, self._get_sftp_error_str()))
66-
buffer = f.read(1024)
70+
buffer = f.read(SFTP_MAX_CHUNK)
6771

6872
while buffer != b"":
6973
length = len(buffer)
@@ -76,38 +80,54 @@ cdef class SFTP:
7680
self._get_sftp_error_str(),
7781
)
7882
)
79-
buffer = f.read(1024)
83+
buffer = f.read(SFTP_MAX_CHUNK)
8084
sftp.sftp_close(rf)
8185

8286
def get(self, remote_file, local_file):
8387
cdef sftp.sftp_file rf
84-
cdef char read_buffer[1024]
88+
cdef char *read_buffer = NULL
89+
cdef sftp.sftp_attributes attrs
8590

8691
remote_file_b = remote_file
8792
if isinstance(remote_file_b, unicode):
8893
remote_file_b = remote_file.encode("utf-8")
8994

95+
attrs = sftp.sftp_stat(self._libssh_sftp_session, remote_file_b)
96+
if attrs is NULL:
97+
raise LibsshSFTPException("Failed to stat the remote file [%s]. Error: [%s]"
98+
% (remote_file, self._get_sftp_error_str()))
99+
file_size = attrs.size
100+
90101
rf = sftp.sftp_open(self._libssh_sftp_session, remote_file_b, O_RDONLY, sftp.S_IRWXU)
91102
if rf is NULL:
92103
raise LibsshSFTPException("Opening remote file [%s] for read failed with error [%s]" % (remote_file, self._get_sftp_error_str()))
93104

94-
with open(local_file, 'wb') as f:
95-
while True:
96-
file_data = sftp.sftp_read(rf, <void *>read_buffer, sizeof(char) * 1024)
97-
if file_data == 0:
98-
break
99-
elif file_data < 0:
100-
sftp.sftp_close(rf)
101-
raise LibsshSFTPException("Reading data from remote file [%s] failed with error [%s]"
102-
% (remote_file, self._get_sftp_error_str()))
103-
104-
bytes_written = f.write(read_buffer[:file_data])
105-
if bytes_written and file_data != bytes_written:
106-
sftp.sftp_close(rf)
107-
raise LibsshSFTPException("Number of bytes [%s] read from remote file [%s]"
108-
" does not match number of bytes [%s] written to local file [%s]"
109-
" due to error [%s]"
110-
% (file_data, remote_file, bytes_written, local_file, self._get_sftp_error_str()))
105+
try:
106+
with open(local_file, 'wb') as f:
107+
buffer_size = min(SFTP_MAX_CHUNK, file_size)
108+
read_buffer = <char *>PyMem_Malloc(buffer_size)
109+
if read_buffer is NULL:
110+
raise LibsshSFTPException("Memory allocation error")
111+
112+
while True:
113+
file_data = sftp.sftp_read(rf, <void *>read_buffer, sizeof(char) * buffer_size)
114+
if file_data == 0:
115+
break
116+
elif file_data < 0:
117+
sftp.sftp_close(rf)
118+
raise LibsshSFTPException("Reading data from remote file [%s] failed with error [%s]"
119+
% (remote_file, self._get_sftp_error_str()))
120+
121+
bytes_written = f.write(read_buffer[:file_data])
122+
if bytes_written and file_data != bytes_written:
123+
sftp.sftp_close(rf)
124+
raise LibsshSFTPException("Number of bytes [%s] read from remote file [%s]"
125+
" does not match number of bytes [%s] written to local file [%s]"
126+
" due to error [%s]"
127+
% (file_data, remote_file, bytes_written, local_file, self._get_sftp_error_str()))
128+
finally:
129+
if read_buffer is not NULL:
130+
PyMem_Free(read_buffer)
111131
sftp.sftp_close(rf)
112132

113133
def close(self):

0 commit comments

Comments
 (0)