use std::io::{self, Read, Write};
use std::process::Command;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::thread;
use crate::broker::protocol_v2::{SessionExit, SessionFrame};
use crate::broker::session_codec::{encode_session_frame, try_decode_session_frame};
use crate::broker::session_pump::{run_child_session, SessionChild};
use crate::containment::ContainedProcessGroup;
use crate::spawn::{SpawnStdio, SpawnedChild, StdioSource};
impl SessionChild for SpawnedChild {
type Stdin = std::process::ChildStdin;
type Stdout = std::process::ChildStdout;
type Stderr = std::process::ChildStderr;
fn take_stdin(&mut self) -> Option<Self::Stdin> {
self.stdin.take()
}
fn take_stdout(&mut self) -> Option<Self::Stdout> {
self.stdout.take()
}
fn take_stderr(&mut self) -> Option<Self::Stderr> {
self.stderr.take()
}
fn wait_session(&mut self) -> io::Result<SessionExit> {
Ok(SessionExit {
code: self.wait()?,
signal: 0,
metadata: Default::default(),
})
}
}
pub fn spawn_contained_session(
group: &ContainedProcessGroup,
command: &mut Command,
) -> io::Result<SpawnedChild> {
let stdio = SpawnStdio {
stdin: StdioSource::Pipe,
stdout: StdioSource::Pipe,
stderr: StdioSource::Pipe,
..SpawnStdio::default()
};
group.spawn(command, stdio)
}
pub fn spawn_contained_session_with_environment(
group: &ContainedProcessGroup,
command: &mut Command,
environment_policy: crate::EnvironmentPolicy,
) -> io::Result<SpawnedChild> {
let stdio = SpawnStdio {
stdin: StdioSource::Pipe,
stdout: StdioSource::Pipe,
stderr: StdioSource::Pipe,
..SpawnStdio::default()
};
group.spawn_with_environment_policy(command, stdio, environment_policy)
}
pub fn serve_session<C, R, W>(child: C, inbound: R, outbound: W) -> io::Result<SessionExit>
where
C: SessionChild,
R: Read + Send + 'static,
W: Write + Send + 'static,
{
let (stdin_tx, stdin_rx) = channel::<SessionFrame>();
let (out_tx, out_rx) = channel::<SessionFrame>();
let inbound_thread = thread::spawn(move || decode_inbound(inbound, stdin_tx));
let outbound_thread = thread::spawn(move || encode_outbound(out_rx, outbound));
let exit = run_child_session(child, out_tx, stdin_rx)?;
let _ = inbound_thread.join();
let outbound_result = outbound_thread.join();
match outbound_result {
Ok(result) => result?,
Err(_) => return Err(io::Error::other("session outbound thread panicked")),
}
Ok(exit)
}
fn decode_inbound<R: Read>(mut inbound: R, stdin_tx: Sender<SessionFrame>) -> io::Result<()> {
let mut buf: Vec<u8> = Vec::new();
let mut chunk = [0u8; 8192];
loop {
loop {
match try_decode_session_frame(&buf) {
Ok(Some(decoded)) => {
buf.drain(..decoded.consumed);
if stdin_tx.send(decoded.frame).is_err() {
return Ok(()); }
}
Ok(None) => break,
Err(err) => return Err(io::Error::new(io::ErrorKind::InvalidData, err)),
}
}
match inbound.read(&mut chunk) {
Ok(0) => return Ok(()),
Ok(n) => buf.extend_from_slice(&chunk[..n]),
Err(ref err) if err.kind() == io::ErrorKind::Interrupted => continue,
Err(err) => return Err(err),
}
}
}
fn encode_outbound<W: Write>(out_rx: Receiver<SessionFrame>, mut outbound: W) -> io::Result<()> {
for (seq, frame) in out_rx.into_iter().enumerate() {
let wire = encode_session_frame(&frame, seq as u64)
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
outbound.write_all(&wire)?;
outbound.flush()?;
}
Ok(())
}
#[cfg(test)]
mod tests;