use anyhow::{anyhow, Context, Result};
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use crate::downloader::events::{DownloaderError, FailureKind, UnsupportedReason};
pub(crate) static CRATES_FILES_LOCK: Mutex<()> = Mutex::new(());
pub fn install_binary(
src: &Path,
binary_name: &str,
new_version: &str,
) -> Result<PathBuf, DownloaderError> {
if cfg!(windows) {
return Err(DownloaderError::Unsupported(
UnsupportedReason::UnsupportedPlatform,
));
}
let cargo_home = cargo_home_path().map_err(failed_install)?;
let bin_dir = cargo_home.join("bin");
let dest = bin_dir.join(binary_name);
let uuid = format!("{:x}", std::process::id() as u128 * 1_000_000 + rand_u32() as u128);
let tmp = bin_dir.join(format!(".cargo-fresh-{binary_name}-{uuid}.tmp"));
std::fs::copy(src, &tmp).map_err(|e| failed_install(anyhow!(e).context("copy to tmp")))?;
#[cfg(unix)]
set_executable(&tmp).map_err(failed_install)?;
fsync_file(&tmp).map_err(failed_install)?;
if let Err(e) = std::fs::rename(&tmp, &dest) {
let _ = std::fs::remove_file(&tmp);
return Err(failed_install(anyhow!(e).context("rename tmp -> dest")));
}
{
let _guard = CRATES_FILES_LOCK
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
crate::package::crates2::write_install_record(&cargo_home, binary_name, new_version)
.map_err(|e| failed_install(e.context("update .crates2.json")))?;
crate::package::crates_toml::write_install_record(&cargo_home, binary_name, new_version)
.map_err(|e| failed_install(e.context("update .crates.toml")))?;
}
Ok(dest)
}
fn cargo_home_path() -> Result<PathBuf> {
if let Ok(p) = std::env::var("CARGO_HOME") {
return Ok(PathBuf::from(p));
}
let home = std::env::var("HOME").context("HOME unset")?;
Ok(PathBuf::from(home).join(".cargo"))
}
#[cfg(unix)]
fn set_executable(path: &Path) -> Result<()> {
use std::os::unix::fs::PermissionsExt;
let mut perms = std::fs::metadata(path).context("stat")?.permissions();
perms.set_mode(perms.mode() | 0o755);
std::fs::set_permissions(path, perms).context("set_permissions")?;
Ok(())
}
fn fsync_file(path: &Path) -> Result<()> {
let f = std::fs::File::open(path).context("open for fsync")?;
f.sync_all().context("fsync")?;
Ok(())
}
fn rand_u32() -> u32 {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.subsec_nanos())
.unwrap_or(0)
}
fn failed_install(e: anyhow::Error) -> DownloaderError {
DownloaderError::Failed {
kind: FailureKind::InstallFailed,
source: e,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn crates_files_lock_serializes_concurrent_threads() {
let h1 = std::thread::spawn(|| {
let _g = CRATES_FILES_LOCK.lock().expect("lock 1");
std::thread::sleep(std::time::Duration::from_millis(10));
});
let h2 = std::thread::spawn(|| {
let _g = CRATES_FILES_LOCK.lock().expect("lock 2");
std::thread::sleep(std::time::Duration::from_millis(10));
});
h1.join().unwrap();
h2.join().unwrap();
}
}