use crate::error::{Error, Result};
use crate::service::Permit;
use std::path::{Path, PathBuf};
use std::sync::Arc;
pub(crate) trait Control: Send + Sync + std::fmt::Debug {
fn run(&self, permit: Permit, program: &str, args: &[&str]) -> Result<()>;
fn start(&self, permit: Permit, program: &str, args: &[&str]) -> Result<()> {
self.run(permit, program, args)
}
}
#[derive(Debug)]
pub(crate) struct System;
impl Control for System {
fn run(&self, _: Permit, program: &str, args: &[&str]) -> Result<()> {
if cfg!(any(test, feature = "test-support")) {
return Err(Error::ScheduleRefused {
detail: format!("a build for tests asked {program} itself"),
});
}
let out = std::process::Command::new(program)
.args(args)
.output()
.map_err(|source| Error::HomeUnwritable {
path: PathBuf::from(program),
source,
})?;
if out.status.success() {
return Ok(());
}
Err(Error::ScheduleRefused {
detail: format!(
"{program} exited {}: {}",
out.status
.code()
.map_or_else(|| "on a signal".into(), |c| c.to_string()),
String::from_utf8_lossy(&out.stderr).trim()
),
})
}
}
#[derive(Debug)]
#[cfg(any(test, feature = "test-support"))]
pub(crate) struct Pretend {
pub(crate) refuse_start: Arc<std::sync::atomic::AtomicBool>,
}
#[cfg(any(test, feature = "test-support"))]
impl Control for Pretend {
fn run(&self, _: Permit, _program: &str, _args: &[&str]) -> Result<()> {
Ok(())
}
fn start(&self, _: Permit, program: &str, _args: &[&str]) -> Result<()> {
if self
.refuse_start
.swap(false, std::sync::atomic::Ordering::SeqCst)
{
return Err(Error::ScheduleRefused {
detail: format!("{program} refused to start it in a test"),
});
}
Ok(())
}
}
pub(crate) fn write(permit: Permit, path: &Path, body: &str) -> Result<()> {
if let Some(parent) = path.parent() {
crate::host::fs::create_dir_all(permit, parent).map_err(|source| {
Error::HomeUnwritable {
path: parent.to_path_buf(),
source,
}
})?;
}
crate::atomic::write(permit, path, body.as_bytes(), crate::atomic::Perms::Secret).map_err(
|source| Error::HomeUnwritable {
path: path.to_path_buf(),
source,
},
)
}
pub(crate) fn restore(permit: Permit, path: &Path, before: Option<&str>) {
let _ = match before {
Some(body) => write(permit, path, body),
None => remove(permit, path),
};
}
pub(crate) fn remove(permit: Permit, path: &Path) -> Result<()> {
match crate::host::fs::remove_file(permit, path) {
Ok(()) => Ok(()),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(source) => Err(Error::HomeUnwritable {
path: path.to_path_buf(),
source,
}),
}
}
pub(crate) fn system() -> Arc<dyn Control> {
Arc::new(System)
}