Skip to content

Commit

Permalink
channels: Use dynamically allocated memory for callbacks
Browse files Browse the repository at this point in the history
Fixes ansible#57

Signed-off-by: Jakub Jelen <[email protected]>
  • Loading branch information
Jakuje committed Nov 20, 2024
1 parent 6ce8f9a commit cdeba7e
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 7 deletions.
3 changes: 3 additions & 0 deletions src/pylibsshext/channel.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ cdef class Channel:
cdef _session
cdef libssh.ssh_channel _libssh_channel
cdef libssh.ssh_session _libssh_session

cdef class ChannelCallback:
cdef callbacks.ssh_channel_callbacks callback
28 changes: 21 additions & 7 deletions src/pylibsshext/channel.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import time
from io import BytesIO

from cpython.bytes cimport PyBytes_AS_STRING
from libc.string cimport memset
from cpython.mem cimport PyMem_Calloc, PyMem_Free

from pylibsshext.errors cimport LibsshChannelException
from pylibsshext.errors import LibsshChannelReadFailure
Expand All @@ -45,6 +45,20 @@ cdef int _process_outputs(libssh.ssh_session session,
result.stdout += data_b
return len

cdef class ChannelCallback:
def __cinit__(self):
cb_size = sizeof(callbacks.ssh_channel_callbacks_struct)
self.callback = <callbacks.ssh_channel_callbacks>PyMem_Calloc(1, cb_size)
if self.callback is NULL:
raise LibsshChannelException("Memory allocation error")
self.callback.channel_data_function = <callbacks.ssh_channel_data_callback>&_process_outputs

def __dealloc__(self):
PyMem_Free(self.callback)

def set_user_data(self, userdata):
self.callback.userdata = <void *>userdata

cdef class Channel:
def __cinit__(self, session):
self._session = session
Expand Down Expand Up @@ -166,12 +180,12 @@ cdef class Channel:
raise LibsshChannelException("Failed to execute command [{0}]: [{1}]".format(command, rc))
result = CompletedProcess(args=command, returncode=-1, stdout=b'', stderr=b'')

cdef callbacks.ssh_channel_callbacks_struct cb
memset(&cb, 0, sizeof(cb))
cb.channel_data_function = <callbacks.ssh_channel_data_callback>&_process_outputs
cb.userdata = <void *>result
callbacks.ssh_callbacks_init(&cb)
callbacks.ssh_set_channel_callbacks(channel, &cb)
cb = ChannelCallback()
cb.set_user_data(result)
callbacks.ssh_callbacks_init(cb.callback)
callbacks.ssh_set_channel_callbacks(channel, cb.callback)
# keep it around in the session object
self._session.push_callback(cb)

libssh.ssh_channel_send_eof(channel)
result.returncode = libssh.ssh_channel_get_exit_status(channel)
Expand Down
1 change: 1 addition & 0 deletions src/pylibsshext/session.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ cdef class Session:
cdef _hash_py
cdef _fingerprint_py
cdef _keytype_py
cdef _channel_callbacks

cdef libssh.ssh_session get_libssh_session(Session session)
7 changes: 7 additions & 0 deletions src/pylibsshext/session.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ cdef class Session(object):
self._hash_py = None
self._fingerprint_py = None
self._keytype_py = None
# Due to delayed freeing of channels, some older libssh versions might expect
# the callbacks to be around even after we free the underlying channels so
# we should free them only when we terminate the session.
self._channel_callbacks = []

def __cinit__(self, host=None, **kwargs):
self._libssh_session = libssh.ssh_new()
Expand All @@ -123,6 +127,9 @@ cdef class Session(object):
libssh.ssh_free(self._libssh_session)
self._libssh_session = NULL

def push_callback(self, callback):
self._channel_callbacks.push(callback)

@property
def port(self):
cdef unsigned int port_i
Expand Down

0 comments on commit cdeba7e

Please sign in to comment.