use libc::{c_char, c_int, c_uchar, c_uint, c_ulong, c_void, size_t};
use parking_lot::{Mutex, MutexGuard};
use std::cmp;
use std::io;
use std::io::prelude::*;
use std::slice;
use std::sync::Arc;
use {raw, Error, ExtendedData, PtyModes, SessionInner};
struct ChannelInner {
unsafe_raw: *mut raw::LIBSSH2_CHANNEL,
sess: Arc<Mutex<SessionInner>>,
read_limit: Mutex<Option<u64>>,
}
unsafe impl Send for ChannelInner {}
unsafe impl Sync for ChannelInner {}
struct LockedChannel<'a> {
raw: *mut raw::LIBSSH2_CHANNEL,
sess: MutexGuard<'a, SessionInner>,
}
pub struct Channel {
channel_inner: Arc<ChannelInner>,
}
impl Channel {
pub(crate) fn from_raw_opt(
raw: *mut raw::LIBSSH2_CHANNEL,
err: Option<Error>,
sess: &Arc<Mutex<SessionInner>>,
) -> Result<Self, Error> {
if raw.is_null() {
Err(err.unwrap_or_else(Error::unknown))
} else {
Ok(Self {
channel_inner: Arc::new(ChannelInner {
unsafe_raw: raw,
sess: Arc::clone(sess),
read_limit: Mutex::new(None),
}),
})
}
}
fn lock(&self) -> LockedChannel {
let sess = self.channel_inner.sess.lock();
LockedChannel {
sess,
raw: self.channel_inner.unsafe_raw,
}
}
}
pub struct Stream {
channel_inner: Arc<ChannelInner>,
id: i32,
}
struct LockedStream<'a> {
raw: *mut raw::LIBSSH2_CHANNEL,
sess: MutexGuard<'a, SessionInner>,
id: i32,
read_limit: MutexGuard<'a, Option<u64>>,
}
impl<'a> LockedStream<'a> {
pub fn eof(&self) -> bool {
*self.read_limit == Some(0) || unsafe { raw::libssh2_channel_eof(self.raw) != 0 }
}
}
pub struct ExitSignal {
pub exit_signal: Option<String>,
pub error_message: Option<String>,
pub lang_tag: Option<String>,
}
#[derive(Copy, Clone)]
pub struct ReadWindow {
pub remaining: u32,
pub available: u32,
pub window_size_initial: u32,
}
#[derive(Copy, Clone)]
pub struct WriteWindow {
pub remaining: u32,
pub window_size_initial: u32,
}
impl Channel {
pub fn setenv(&mut self, var: &str, val: &str) -> Result<(), Error> {
let locked = self.lock();
unsafe {
locked.sess.rc(raw::libssh2_channel_setenv_ex(
locked.raw,
var.as_ptr() as *const _,
var.len() as c_uint,
val.as_ptr() as *const _,
val.len() as c_uint,
))
}
}
pub fn request_pty(
&mut self,
term: &str,
mode: Option<PtyModes>,
dim: Option<(u32, u32, u32, u32)>,
) -> Result<(), Error> {
let locked = self.lock();
let mode = mode.map(PtyModes::finish);
let mode = mode.as_ref().map(Vec::as_slice).unwrap_or(&[]);
locked.sess.rc(unsafe {
let (width, height, width_px, height_px) = dim.unwrap_or((80, 24, 0, 0));
raw::libssh2_channel_request_pty_ex(
locked.raw,
term.as_ptr() as *const _,
term.len() as c_uint,
mode.as_ptr() as *const _,
mode.len() as c_uint,
width as c_int,
height as c_int,
width_px as c_int,
height_px as c_int,
)
})
}
pub fn request_pty_size(
&mut self,
width: u32,
height: u32,
width_px: Option<u32>,
height_px: Option<u32>,
) -> Result<(), Error> {
let locked = self.lock();
let width_px = width_px.unwrap_or(0);
let height_px = height_px.unwrap_or(0);
locked.sess.rc(unsafe {
raw::libssh2_channel_request_pty_size_ex(
locked.raw,
width as c_int,
height as c_int,
width_px as c_int,
height_px as c_int,
)
})
}
pub fn exec(&mut self, command: &str) -> Result<(), Error> {
self.process_startup("exec", Some(command))
}
pub fn shell(&mut self) -> Result<(), Error> {
self.process_startup("shell", None)
}
pub fn subsystem(&mut self, system: &str) -> Result<(), Error> {
self.process_startup("subsystem", Some(system))
}
pub fn process_startup(&mut self, request: &str, message: Option<&str>) -> Result<(), Error> {
let message_len = message.map(|s| s.len()).unwrap_or(0);
let message = message.map(|s| s.as_ptr()).unwrap_or(0 as *const _);
let locked = self.lock();
unsafe {
let rc = raw::libssh2_channel_process_startup(
locked.raw,
request.as_ptr() as *const _,
request.len() as c_uint,
message as *const _,
message_len as c_uint,
);
locked.sess.rc(rc)
}
}
pub fn stderr(&self) -> Stream {
self.stream(::EXTENDED_DATA_STDERR)
}
pub fn stream(&self, stream_id: i32) -> Stream {
Stream {
channel_inner: Arc::clone(&self.channel_inner),
id: stream_id,
}
}
pub fn handle_extended_data(&mut self, mode: ExtendedData) -> Result<(), Error> {
let locked = self.lock();
unsafe {
let rc = raw::libssh2_channel_handle_extended_data2(locked.raw, mode as c_int);
locked.sess.rc(rc)
}
}
pub fn exit_status(&self) -> Result<i32, Error> {
let locked = self.lock();
Ok(unsafe { raw::libssh2_channel_get_exit_status(locked.raw) })
}
pub fn exit_signal(&self) -> Result<ExitSignal, Error> {
let locked = self.lock();
unsafe {
let mut sig = 0 as *mut _;
let mut siglen = 0;
let mut msg = 0 as *mut _;
let mut msglen = 0;
let mut lang = 0 as *mut _;
let mut langlen = 0;
let rc = raw::libssh2_channel_get_exit_signal(
locked.raw,
&mut sig,
&mut siglen,
&mut msg,
&mut msglen,
&mut lang,
&mut langlen,
);
locked.sess.rc(rc)?;
return Ok(ExitSignal {
exit_signal: convert(&locked, sig, siglen),
error_message: convert(&locked, msg, msglen),
lang_tag: convert(&locked, lang, langlen),
});
}
unsafe fn convert(locked: &LockedChannel, ptr: *mut c_char, len: size_t) -> Option<String> {
if ptr.is_null() {
return None;
}
let slice = slice::from_raw_parts(ptr as *const u8, len as usize);
let ret = slice.to_vec();
raw::libssh2_free(locked.sess.raw, ptr as *mut c_void);
String::from_utf8(ret).ok()
}
}
pub fn read_window(&self) -> ReadWindow {
let locked = self.lock();
unsafe {
let mut avail = 0;
let mut init = 0;
let remaining = raw::libssh2_channel_window_read_ex(locked.raw, &mut avail, &mut init);
ReadWindow {
remaining: remaining as u32,
available: avail as u32,
window_size_initial: init as u32,
}
}
}
pub fn write_window(&self) -> WriteWindow {
let locked = self.lock();
unsafe {
let mut init = 0;
let remaining = raw::libssh2_channel_window_write_ex(locked.raw, &mut init);
WriteWindow {
remaining: remaining as u32,
window_size_initial: init as u32,
}
}
}
pub fn adjust_receive_window(&mut self, adjust: u64, force: bool) -> Result<u64, Error> {
let locked = self.lock();
let mut ret = 0;
let rc = unsafe {
raw::libssh2_channel_receive_window_adjust2(
locked.raw,
adjust as c_ulong,
force as c_uchar,
&mut ret,
)
};
locked.sess.rc(rc)?;
Ok(ret as u64)
}
#[doc(hidden)]
pub(crate) fn limit_read(&mut self, limit: u64) {
*self.channel_inner.read_limit.lock() = Some(limit);
}
pub fn eof(&self) -> bool {
let locked = self.lock();
*self.channel_inner.read_limit.lock() == Some(0)
|| unsafe { raw::libssh2_channel_eof(locked.raw) != 0 }
}
pub fn send_eof(&mut self) -> Result<(), Error> {
let locked = self.lock();
unsafe { locked.sess.rc(raw::libssh2_channel_send_eof(locked.raw)) }
}
pub fn wait_eof(&mut self) -> Result<(), Error> {
let locked = self.lock();
unsafe { locked.sess.rc(raw::libssh2_channel_wait_eof(locked.raw)) }
}
pub fn close(&mut self) -> Result<(), Error> {
let locked = self.lock();
unsafe { locked.sess.rc(raw::libssh2_channel_close(locked.raw)) }
}
pub fn wait_close(&mut self) -> Result<(), Error> {
let locked = self.lock();
unsafe { locked.sess.rc(raw::libssh2_channel_wait_closed(locked.raw)) }
}
}
impl Write for Channel {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.stream(0).write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.stream(0).flush()
}
}
impl Read for Channel {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.stream(0).read(buf)
}
}
impl Drop for ChannelInner {
fn drop(&mut self) {
unsafe {
let _ = raw::libssh2_channel_free(self.unsafe_raw);
}
}
}
impl Stream {
fn lock(&self) -> LockedStream {
let sess = self.channel_inner.sess.lock();
LockedStream {
sess,
raw: self.channel_inner.unsafe_raw,
id: self.id,
read_limit: self.channel_inner.read_limit.lock(),
}
}
}
impl Read for Stream {
fn read(&mut self, data: &mut [u8]) -> io::Result<usize> {
let mut locked = self.lock();
if locked.eof() {
return Ok(0);
}
let data = match locked.read_limit.as_mut() {
Some(amt) => {
let len = data.len();
&mut data[..cmp::min(*amt as usize, len)]
}
None => data,
};
let ret = unsafe {
let rc = raw::libssh2_channel_read_ex(
locked.raw,
locked.id as c_int,
data.as_mut_ptr() as *mut _,
data.len() as size_t,
);
locked.sess.rc(rc as c_int).map(|()| rc as usize)
};
match ret {
Ok(n) => {
if let Some(ref mut amt) = locked.read_limit.as_mut() {
**amt -= n as u64;
}
Ok(n)
}
Err(e) => Err(e.into()),
}
}
}
impl Write for Stream {
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
let locked = self.lock();
unsafe {
let rc = raw::libssh2_channel_write_ex(
locked.raw,
locked.id as c_int,
data.as_ptr() as *mut _,
data.len() as size_t,
);
locked.sess.rc(rc as c_int).map(|()| rc as usize)
}
.map_err(Into::into)
}
fn flush(&mut self) -> io::Result<()> {
let locked = self.lock();
unsafe {
let rc = raw::libssh2_channel_flush_ex(locked.raw, locked.id as c_int);
locked.sess.rc(rc)
}
.map_err(Into::into)
}
}