use tokio::io::AsyncWriteExt as _;
use russh::client::Msg;
use russh::{Channel, ChannelMsg, Sig};
use crate::error::GitwayError;
pub async fn relay_channel(channel: Channel<Msg>) -> Result<u32, GitwayError> {
let (mut read_half, write_half) = channel.split();
let mut channel_writer = write_half.make_writer();
let stdin_task = tokio::spawn(async move {
let mut stdin = tokio::io::stdin();
tokio::io::copy(&mut stdin, &mut channel_writer).await?;
channel_writer.shutdown().await?;
Ok::<_, std::io::Error>(())
});
let mut stdout = tokio::io::stdout();
let mut stderr = tokio::io::stderr();
let mut exit_code: Option<u32> = None;
loop {
let Some(msg) = read_half.wait().await else {
break;
};
match msg {
ChannelMsg::Data { ref data } => {
stdout.write_all(data).await?;
stdout.flush().await?;
}
ChannelMsg::ExtendedData { ref data, ext: 1 } => {
stderr.write_all(data).await?;
stderr.flush().await?;
}
ChannelMsg::ExitStatus { exit_status } => {
log::debug!("relay: remote process exited with code {exit_status}");
exit_code = Some(exit_status);
}
ChannelMsg::ExitSignal {
signal_name,
core_dumped,
..
} => {
let sig_num = signal_number(&signal_name);
let code = 128_u32.saturating_add(sig_num);
log::debug!(
"relay: remote process killed by signal {signal_name:?} \
(core_dumped={core_dumped}), exit code {code}"
);
exit_code = Some(code);
}
ChannelMsg::Close => break,
_ => {}
}
}
stdin_task.abort();
Ok(exit_code.unwrap_or(0))
}
fn signal_number(sig: &Sig) -> u32 {
match sig {
Sig::HUP => 1,
Sig::INT => 2,
Sig::QUIT => 3,
Sig::ILL => 4,
Sig::ABRT => 6,
Sig::FPE => 8,
Sig::KILL => 9,
Sig::SEGV => 11,
Sig::PIPE => 13,
Sig::ALRM => 14,
Sig::TERM => 15,
Sig::USR1 => 10,
Sig::Custom(_) => 0,
}
}