Skip to content

Commit afb490d

Browse files
author
Pan
committed
Added sftp write implementation and tests.
Added sftp attribute setting and tests. Added sftp exceptions.
1 parent 1cce0d0 commit afb490d

File tree

6 files changed

+252
-27
lines changed

6 files changed

+252
-27
lines changed

ssh/exceptions.pyx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,5 +155,9 @@ class ResourceShortage(BaseSSHError):
155155
"""Raised on resource shortage errors"""
156156

157157

158-
class SFTPHandleError(BaseSSHError):
159-
"""Raised on errors SFTP handle errors"""
158+
class SFTPError(BaseSSHError):
159+
"""Raised on SFTP errors"""
160+
161+
162+
class SFTPHandleError(SFTPError):
163+
"""Raised on SFTP handle errors"""

ssh/sftp_attributes.pxd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ cimport c_sftp
2222
cdef class SFTPAttributes:
2323
cdef c_sftp.sftp_attributes _attrs
2424
cdef SFTP sftp
25+
cdef bint self_made
2526

2627
@staticmethod
2728
cdef SFTPAttributes from_ptr(c_sftp.sftp_attributes attrs, SFTP sftp)

ssh/sftp_attributes.pyx

Lines changed: 92 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,22 @@ from sftp cimport SFTP
2020
from utils cimport ssh_string_to_bytes
2121

2222
cimport c_sftp
23-
from c_ssh cimport ssh_string
23+
from c_ssh cimport ssh_string, uint8_t, uint32_t, uint64_t
2424

2525

2626
cdef class SFTPAttributes:
2727

2828
def __cinit__(self, SFTP sftp):
2929
self.sftp = sftp
30+
self.self_made = False
3031

3132
def __dealloc__(self):
32-
if self._attrs is not NULL:
33+
if self._attrs is not NULL and not self.self_made:
3334
c_sftp.sftp_attributes_free(self._attrs)
3435
self._attrs = NULL
36+
elif self._attrs is not NULL and self.self_made:
37+
free(self._attrs)
38+
self._attrs = NULL
3539

3640
@staticmethod
3741
cdef SFTPAttributes from_ptr(c_sftp.sftp_attributes attrs, SFTP sftp):
@@ -42,13 +46,38 @@ cdef class SFTPAttributes:
4246

4347
@staticmethod
4448
def new_attrs(SFTP sftp):
49+
cdef SFTPAttributes attrs
4550
cdef c_sftp.sftp_attributes _attrs
4651
with nogil:
4752
_attrs = <c_sftp.sftp_attributes>malloc(
4853
sizeof(c_sftp.sftp_attributes_struct))
4954
if _attrs is NULL:
5055
raise MemoryError
51-
return SFTPAttributes.from_ptr(_attrs, sftp)
56+
_attrs.name = b''
57+
_attrs.longname = b''
58+
_attrs.flags = 0
59+
_attrs.type = 0
60+
_attrs.size = 0
61+
_attrs.uid = 0
62+
_attrs.gid = 0
63+
_attrs.owner = b''
64+
_attrs.group = b''
65+
_attrs.permissions = 0
66+
_attrs.atime64 = 0
67+
_attrs.atime = 0
68+
_attrs.atime_nseconds = 0
69+
_attrs.createtime = 0
70+
_attrs.createtime_nseconds = 0
71+
_attrs.mtime64 = 0
72+
_attrs.mtime = 0
73+
_attrs.mtime_nseconds = 0
74+
_attrs.acl = NULL
75+
_attrs.extended_count = 0
76+
_attrs.extended_type = NULL
77+
_attrs.extended_data = NULL
78+
attrs = SFTPAttributes.from_ptr(_attrs, sftp)
79+
attrs.self_made = True
80+
return attrs
5281

5382
@property
5483
def name(self):
@@ -68,22 +97,42 @@ cdef class SFTPAttributes:
6897
def flags(self):
6998
return self._attrs.flags if self._attrs is not NULL else None
7099

100+
@flags.setter
101+
def flags(self, uint32_t flags):
102+
self._attrs.flags = flags
103+
71104
@property
72105
def type(self):
73106
return self._attrs.type if self._attrs is not NULL else None
74107

108+
@type.setter
109+
def type(self, uint8_t _type):
110+
self._attrs.type = _type
111+
75112
@property
76113
def size(self):
77114
return self._attrs.size if self._attrs is not NULL else None
78115

116+
@size.setter
117+
def size(self, uint64_t size):
118+
self._attrs.size = size
119+
79120
@property
80121
def uid(self):
81122
return self._attrs.uid if self._attrs is not NULL else None
82123

124+
@uid.setter
125+
def uid(self, uint32_t uid):
126+
self._attrs.uid = uid
127+
83128
@property
84129
def gid(self):
85130
return self._attrs.gid if self._attrs is not NULL else None
86131

132+
@gid.setter
133+
def gid(self, uint32_t gid):
134+
self._attrs.gid = gid
135+
87136
@property
88137
def owner(self):
89138
if self._attrs is NULL:
@@ -104,38 +153,74 @@ cdef class SFTPAttributes:
104153
def permissions(self):
105154
return self._attrs.permissions if self._attrs is not NULL else None
106155

156+
@permissions.setter
157+
def permissions(self, uint32_t permissions):
158+
self._attrs.permissions = permissions
159+
107160
@property
108161
def atime64(self):
109162
return self._attrs.atime64 if self._attrs is not NULL else None
110163

164+
@atime64.setter
165+
def atime64(self, uint64_t atime):
166+
self._attrs.atime64 = atime
167+
111168
@property
112169
def atime(self):
113170
return self._attrs.atime if self._attrs is not NULL else None
114171

172+
@atime.setter
173+
def atime(self, uint32_t atime):
174+
self._attrs.atime = atime
175+
115176
@property
116177
def atime_nseconds(self):
117178
return self._attrs.atime_nseconds if self._attrs is not NULL else None
118179

180+
@atime_nseconds.setter
181+
def atime_nseconds(self, uint32_t nseconds):
182+
self._attrs.atime_nseconds = nseconds
183+
119184
@property
120185
def createtime(self):
121186
return self._attrs.createtime if self._attrs is not NULL else None
122187

188+
@createtime.setter
189+
def createtime(self, uint64_t createtime):
190+
self._attrs.createtime = createtime
191+
123192
@property
124193
def createtime_nseconds(self):
125194
return self._attrs.createtime_nseconds if self._attrs is not NULL else None
126195

196+
@createtime_nseconds.setter
197+
def createtime_nseconds(self, uint32_t nseconds):
198+
self._attrs.createtime_nseconds = nseconds
199+
127200
@property
128201
def mtime64(self):
129202
return self._attrs.mtime64 if self._attrs is not NULL else None
130203

204+
@mtime64.setter
205+
def mtime64(self, uint64_t mtime):
206+
self._attrs.mtime64 = mtime
207+
131208
@property
132209
def mtime(self):
133210
return self._attrs.mtime if self._attrs is not NULL else None
134211

212+
@mtime.setter
213+
def mtime(self, uint32_t mtime):
214+
self._attrs.mtime = mtime
215+
135216
@property
136217
def mtime_nseconds(self):
137218
return self._attrs.mtime_nseconds if self._attrs is not NULL else None
138219

220+
@mtime_nseconds.setter
221+
def mtime_nseconds(self, uint32_t nseconds):
222+
self._attrs.mtime_nseconds = nseconds
223+
139224
@property
140225
def acl(self):
141226
if self._attrs is NULL:
@@ -146,6 +231,10 @@ cdef class SFTPAttributes:
146231
def extended_count(self):
147232
return self._attrs.extended_count if self._attrs is not NULL else None
148233

234+
@extended_count.setter
235+
def extended_count(self, uint32_t count):
236+
self._attrs.extended_count = count
237+
149238
@property
150239
def extended_type(self):
151240
if self._attrs is NULL:

ssh/sftp_handles.pxd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ cimport c_sftp
2323
cdef class SFTPFile:
2424
cdef c_sftp.sftp_file _file
2525
cdef SFTP sftp
26-
cdef bint closed
26+
cdef readonly bint closed
2727

2828
@staticmethod
2929
cdef SFTPFile from_ptr(c_sftp.sftp_file _file, SFTP sftp)

ssh/sftp_handles.pyx

Lines changed: 86 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@
1717
from libc.stdlib cimport malloc, free
1818

1919
from sftp_attributes cimport SFTPAttributes
20+
from sftp_statvfs cimport SFTPStatVFS
2021

21-
from .exceptions import SFTPHandleError
22+
from .exceptions import SFTPError, SFTPHandleError
2223

23-
from c_ssh cimport uint32_t, uint64_t, ssh_get_error
24+
from c_ssh cimport uint32_t, uint64_t, ssh_get_error, SSH_ERROR, SSH_AGAIN
2425
cimport c_sftp
2526

2627

@@ -87,52 +88,116 @@ cdef class SFTPFile:
8788
with nogil:
8889
c_sftp.sftp_file_set_blocking(self._file)
8990

90-
def read(self, size_t count=1024000):
91-
cdef ssize_t size
91+
def read(self, size_t size=1048576):
92+
cdef ssize_t _size
9293
cdef bytes buf = b''
9394
cdef char *c_buf
9495
with nogil:
95-
c_buf = <char *>malloc(sizeof(char) * count)
96+
c_buf = <char *>malloc(sizeof(char) * size)
9697
if c_buf is NULL:
9798
with gil:
9899
raise MemoryError
99-
size = c_sftp.sftp_read(self._file, c_buf, count)
100+
_size = c_sftp.sftp_read(self._file, c_buf, size)
101+
try:
102+
if _size > 0:
103+
buf = c_buf[:_size]
104+
finally:
105+
free(c_buf)
106+
return _size, buf
107+
108+
def async_read_begin(self, uint32_t length=1048576):
109+
cdef int rc
110+
with nogil:
111+
rc = c_sftp.sftp_async_read_begin(self._file, length)
112+
if rc < 0:
113+
raise SFTPHandleError(ssh_get_error(self.sftp.session._session))
114+
return rc
115+
116+
def async_read(self, uint32_t _id, uint32_t length=1048576):
117+
cdef int size
118+
cdef bytes buf = b''
119+
cdef char *c_buf
120+
with nogil:
121+
c_buf = <char *>malloc(sizeof(char) * length)
122+
if c_buf is NULL:
123+
with gil:
124+
raise MemoryError
125+
size = c_sftp.sftp_async_read(self._file, c_buf, length, _id)
100126
try:
101127
if size > 0:
102128
buf = c_buf[:size]
129+
elif size < 0:
130+
if size == SSH_ERROR:
131+
raise SFTPError(ssh_get_error(self.sftp.session._session))
132+
elif size == SSH_AGAIN:
133+
return SSH_AGAIN
103134
finally:
104135
free(c_buf)
105136
return size, buf
106137

107-
def async_read_begin(self, uint32_t length=1024000):
108-
pass
109-
110-
def async_read(self, uint32_t length, uint32_t, _id):
111-
pass
112-
113-
def write(self, size_t count=1024000):
114-
pass
138+
def write(self, bytes data):
139+
cdef ssize_t rc
140+
cdef const char *c_data = data
141+
cdef size_t data_len = len(data)
142+
with nogil:
143+
rc = c_sftp.sftp_write(self._file, c_data, data_len)
144+
if rc < 0:
145+
raise SFTPError(ssh_get_error(self.sftp.session._session))
146+
return rc
115147

116148
def seek(self, uint32_t offset):
117-
pass
149+
cdef int rc
150+
with nogil:
151+
rc = c_sftp.sftp_seek(self._file, offset)
152+
if rc < 0:
153+
raise SFTPHandleError(ssh_get_error(self.sftp.session._session))
154+
return rc
118155

119156
def seek64(self, uint64_t offset):
120-
pass
157+
cdef int rc
158+
with nogil:
159+
rc = c_sftp.sftp_seek64(self._file, offset)
160+
if rc < 0:
161+
raise SFTPHandleError(ssh_get_error(self.sftp.session._session))
162+
return rc
121163

122164
def tell(self):
123-
pass
165+
cdef unsigned long rc
166+
with nogil:
167+
rc = c_sftp.sftp_tell(self._file)
168+
if rc < 0:
169+
raise SFTPHandleError(ssh_get_error(self.sftp.session._session))
170+
return rc
124171

125172
def tell64(self):
126-
pass
173+
cdef uint64_t rc
174+
with nogil:
175+
rc = c_sftp.sftp_tell64(self._file)
176+
if rc < 0:
177+
raise SFTPHandleError(ssh_get_error(self.sftp.session._session))
178+
return rc
127179

128180
def rewind(self):
129-
pass
181+
with nogil:
182+
c_sftp.sftp_rewind(self._file)
130183

131184
def fstatvfs(self):
132-
pass
185+
cdef SFTPStatVFS vfs
186+
cdef c_sftp.sftp_statvfs_t c_vfs
187+
with nogil:
188+
c_vfs = c_sftp.sftp_fstatvfs(self._file)
189+
if c_vfs is NULL:
190+
raise SFTPHandleError(ssh_get_error(self.sftp.session._session))
191+
vfs = SFTPStatVFS.from_ptr(c_vfs, self.sftp)
192+
return vfs
133193

134194
def fsync(self):
135-
pass
195+
cdef int rc
196+
with nogil:
197+
rc = c_sftp.sftp_fsync(self._file)
198+
if rc < 0:
199+
raise SFTPHandleError(ssh_get_error(self.sftp.session._session))
200+
return rc
136201

137202

138203
cdef class SFTPDir:

0 commit comments

Comments
 (0)