2022-06-22 14:09:59 -04:00
|
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
2022-06-23 10:42:50 -04:00
|
|
|
use std::process::{ExitStatus, Stdio};
|
2022-06-22 14:09:59 -04:00
|
|
|
use tokio::{
|
|
|
|
|
io::{AsyncBufReadExt, BufReader},
|
|
|
|
|
process::Command,
|
2022-06-23 10:42:50 -04:00
|
|
|
sync::mpsc::UnboundedSender,
|
2022-06-22 14:09:59 -04:00
|
|
|
};
|
|
|
|
|
use tokio_util::sync::CancellationToken;
|
|
|
|
|
|
|
|
|
|
pub enum ProcessEvent {
|
|
|
|
|
Started,
|
|
|
|
|
Stdout(String),
|
|
|
|
|
Stderr(String),
|
2022-06-23 10:42:50 -04:00
|
|
|
Ended(Option<ExitStatus>),
|
2022-06-22 14:09:59 -04:00
|
|
|
}
|
|
|
|
|
|
2022-06-23 10:42:50 -04:00
|
|
|
pub struct ProcessHandler {
|
|
|
|
|
tx: UnboundedSender<ProcessEvent>,
|
2022-06-22 14:09:59 -04:00
|
|
|
cancellation_token: CancellationToken,
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-23 10:42:50 -04:00
|
|
|
impl ProcessHandler {
|
|
|
|
|
pub fn new(tx: UnboundedSender<ProcessEvent>, cancellation_token: &CancellationToken) -> Self {
|
2022-06-22 14:09:59 -04:00
|
|
|
Self {
|
2022-06-23 10:42:50 -04:00
|
|
|
tx,
|
2022-06-22 14:09:59 -04:00
|
|
|
cancellation_token: cancellation_token.child_token(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 14:46:14 -04:00
|
|
|
pub fn run(self, executable: impl ToString, args: Vec<String>, vars: Vec<(String, String)>) {
|
2022-06-22 14:09:59 -04:00
|
|
|
let executable = executable.to_string();
|
|
|
|
|
tokio::spawn(async move {
|
|
|
|
|
let mut child = match Command::new(&executable)
|
|
|
|
|
.args(&args)
|
|
|
|
|
.stdin(Stdio::null())
|
|
|
|
|
.stdout(Stdio::piped())
|
|
|
|
|
.stderr(Stdio::piped())
|
2022-06-24 14:46:14 -04:00
|
|
|
.envs(vars)
|
2022-06-22 14:09:59 -04:00
|
|
|
.kill_on_drop(true)
|
|
|
|
|
.spawn()
|
|
|
|
|
{
|
|
|
|
|
Ok(child) => child,
|
|
|
|
|
Err(error) => {
|
|
|
|
|
error!(
|
|
|
|
|
"failed to launch '{} {}': {}",
|
|
|
|
|
executable,
|
|
|
|
|
args.join(" "),
|
|
|
|
|
error
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
let mut stdout = BufReader::new(child.stdout.take().unwrap()).lines();
|
|
|
|
|
let mut stderr = BufReader::new(child.stderr.take().unwrap()).lines();
|
2022-06-23 10:42:50 -04:00
|
|
|
std::mem::drop(self.tx.send(ProcessEvent::Started));
|
2022-06-22 14:09:59 -04:00
|
|
|
loop {
|
|
|
|
|
tokio::select! {
|
|
|
|
|
status = child.wait() => match status {
|
|
|
|
|
Ok(status) => {
|
|
|
|
|
info!("'{}' exited with status {}", executable, status);
|
2022-06-23 10:42:50 -04:00
|
|
|
std::mem::drop(self.tx.send(ProcessEvent::Ended(Some(status))));
|
2022-06-22 14:09:59 -04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
Err(error) => {
|
|
|
|
|
error!(
|
|
|
|
|
"failed to wait for '{}' to end: {}",
|
|
|
|
|
executable,
|
|
|
|
|
error
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
line = stdout.next_line() => match line {
|
|
|
|
|
Ok(Some(line)) => {
|
2022-06-23 10:42:50 -04:00
|
|
|
std::mem::drop(self.tx.send(ProcessEvent::Stdout(line)));
|
2022-06-22 14:09:59 -04:00
|
|
|
},
|
|
|
|
|
Ok(None) => (),
|
|
|
|
|
Err(error) => {
|
|
|
|
|
warn!(
|
|
|
|
|
"failed to get stdout line from '{}': {}",
|
|
|
|
|
executable,
|
|
|
|
|
error
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
line = stderr.next_line() => match line {
|
|
|
|
|
Ok(Some(line)) => {
|
2022-06-23 10:42:50 -04:00
|
|
|
std::mem::drop(self.tx.send(ProcessEvent::Stderr(line)));
|
2022-06-22 14:09:59 -04:00
|
|
|
},
|
|
|
|
|
Ok(None) => (),
|
|
|
|
|
Err(error) => {
|
|
|
|
|
warn!(
|
|
|
|
|
"failed to get stderr line from '{}': {}",
|
|
|
|
|
executable,
|
|
|
|
|
error
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
_ = self.cancellation_token.cancelled() => {
|
|
|
|
|
warn!("exiting '{}': cancelled", executable);
|
2022-06-23 10:42:50 -04:00
|
|
|
std::mem::drop(self.tx.send(ProcessEvent::Ended(None)));
|
2022-06-22 14:09:59 -04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|