#[derive(Debug, Clone)]
pub struct Cmd {
pub timeout: std::time::Duration,
pub user: Option<String>,
pub group: Option<String>,
pub command: String,
pub args: Option<Vec<String>>,
}
impl std::fmt::Display for Cmd {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "cmd:{}", self.command)
}
}
impl Cmd {
pub fn run(&self) -> anyhow::Result<std::process::ExitStatus> {
self.run_inner(self.args.as_ref().unwrap_or(&vec![]))
}
pub fn run_with_args(&self, args: &Vec<String>) -> anyhow::Result<std::process::ExitStatus> {
self.run_inner(args)
}
pub fn run_inner(&self, args: &Vec<String>) -> anyhow::Result<std::process::ExitStatus> {
let mut child = std::process::Command::new(&self.command);
child.args(args);
if let Some(user_name) = &self.user {
if let Some(user) = users::get_user_by_name(&user_name) {
std::os::unix::prelude::CommandExt::uid(&mut child, user.uid());
} else {
anyhow::bail!("user not found: '{user_name}'")
}
}
if let Some(group_name) = &self.group {
if let Some(group) = users::get_group_by_name(group_name) {
std::os::unix::prelude::CommandExt::gid(&mut child, group.gid());
} else {
anyhow::bail!("group not found: '{group_name}'")
}
}
tracing::trace!(?child, "Running command.");
let mut child = match child.spawn() {
Ok(child) => child,
Err(err) => anyhow::bail!("cmd process failed to spawn: {err:?}"),
};
match wait_timeout::ChildExt::wait_timeout(&mut child, self.timeout) {
Ok(status) => Ok(status.unwrap_or_else(|| {
child.kill().expect("child has already exited");
child.wait().expect("command wasn't running")
})),
Err(err) => anyhow::bail!("cmd unexpected error: {err:?}"),
}
}
pub fn status_to_map(status: std::process::ExitStatus) -> rhai::Map {
let code = status.code().map(i64::from).map(rhai::Dynamic::from);
let signal = std::os::unix::prelude::ExitStatusExt::signal(&status)
.map(i64::from)
.map(rhai::Dynamic::from);
rhai::Map::from_iter([
("has_code".into(), rhai::Dynamic::from_bool(code.is_some())),
("code".into(), code.unwrap_or(rhai::Dynamic::UNIT)),
(
"has_signal".into(),
rhai::Dynamic::from_bool(signal.is_some()),
),
("signal".into(), signal.unwrap_or(rhai::Dynamic::UNIT)),
])
}
}