use std::{
fs,
os::unix::fs::PermissionsExt,
path::{Path, PathBuf},
process::Command,
};
use anyhow::{anyhow, Context, Result};
use log::info;
pub enum Outcome {
Installed,
AlreadyCurrent,
}
pub fn install_to(src: &Path, dest: &Path) -> Result<Outcome> {
let src = canonical(src)?;
if dest_matches_running(&src, dest) {
return Ok(Outcome::AlreadyCurrent);
}
if let Some(parent) = dest.parent() {
fs::create_dir_all(parent).with_context(|| format!("creating `{}`", parent.display()))?;
}
let tmp = dest.with_extension("linprov-new");
fs::copy(&src, &tmp)
.with_context(|| format!("copying `{}` -> `{}`", src.display(), tmp.display()))?;
fs::set_permissions(&tmp, fs::Permissions::from_mode(0o755))
.with_context(|| format!("chmod 0755 `{}`", tmp.display()))?;
fs::rename(&tmp, dest)
.with_context(|| format!("renaming `{}` -> `{}`", tmp.display(), dest.display()))?;
info!("installed {} -> {}", src.display(), dest.display());
Ok(Outcome::Installed)
}
fn canonical(p: &Path) -> Result<PathBuf> {
p.canonicalize()
.with_context(|| format!("resolving `{}`", p.display()))
}
fn dest_matches_running(src: &Path, dest: &Path) -> bool {
let Ok(src_meta) = fs::metadata(src) else {
return false;
};
let Ok(dest_meta) = fs::metadata(dest) else {
return false;
};
src_meta.len() == dest_meta.len() && fs::read(src).ok() == fs::read(dest).ok()
}
pub fn current_exe() -> Result<PathBuf> {
std::env::current_exe().context("locating the running linprov binary")
}
pub fn refuse_distro_owned(dest: &Path) -> Result<()> {
if !dest.exists() {
return Ok(());
}
if let Ok(out) = Command::new("/usr/bin/dpkg").arg("-S").arg(dest).output() {
if out.status.success() {
let pkg = String::from_utf8_lossy(&out.stdout).trim().to_string();
return Err(anyhow!(
"{} is owned by a dpkg package ({pkg}); refusing to overwrite. \
Uninstall that package first.",
dest.display()
));
}
}
Ok(())
}