mirror of
https://github.com/cargo-bins/cargo-binstall.git
synced 2025-04-22 13:38:43 +00:00
Merge pull request #195 from NobodyXu/feature/atomic-install
This commit is contained in:
commit
7518993212
2 changed files with 94 additions and 19 deletions
25
src/bins.rs
25
src/bins.rs
|
@ -4,7 +4,7 @@ use cargo_toml::Product;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::{BinstallError, PkgFmt, PkgMeta, Template};
|
use crate::{atomic_install, atomic_symlink_file, BinstallError, PkgFmt, PkgMeta, Template};
|
||||||
|
|
||||||
pub struct BinFile {
|
pub struct BinFile {
|
||||||
pub base_name: String,
|
pub base_name: String,
|
||||||
|
@ -80,18 +80,11 @@ impl BinFile {
|
||||||
pub fn install_bin(&self) -> Result<(), BinstallError> {
|
pub fn install_bin(&self) -> Result<(), BinstallError> {
|
||||||
// TODO: check if file already exists
|
// TODO: check if file already exists
|
||||||
debug!(
|
debug!(
|
||||||
"Copy file from '{}' to '{}'",
|
"Atomically install file from '{}' to '{}'",
|
||||||
self.source.display(),
|
self.source.display(),
|
||||||
self.dest.display()
|
self.dest.display()
|
||||||
);
|
);
|
||||||
std::fs::copy(&self.source, &self.dest)?;
|
atomic_install(&self.source, &self.dest)?;
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
{
|
|
||||||
use std::os::unix::fs::PermissionsExt;
|
|
||||||
debug!("Set permissions 755 on '{}'", self.dest.display());
|
|
||||||
std::fs::set_permissions(&self.dest, std::fs::Permissions::from_mode(0o755))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -110,21 +103,15 @@ impl BinFile {
|
||||||
self.link.display(),
|
self.link.display(),
|
||||||
dest.display()
|
dest.display()
|
||||||
);
|
);
|
||||||
#[cfg(target_family = "unix")]
|
atomic_symlink_file(dest, &self.link)?;
|
||||||
std::os::unix::fs::symlink(dest, &self.link)?;
|
|
||||||
#[cfg(target_family = "windows")]
|
|
||||||
std::os::windows::fs::symlink_file(dest, &self.link)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn link_dest(&self) -> &Path {
|
fn link_dest(&self) -> &Path {
|
||||||
#[cfg(target_family = "unix")]
|
if cfg!(target_family = "unix") {
|
||||||
{
|
|
||||||
Path::new(self.dest.file_name().unwrap())
|
Path::new(self.dest.file_name().unwrap())
|
||||||
}
|
} else {
|
||||||
#[cfg(target_family = "windows")]
|
|
||||||
{
|
|
||||||
&self.dest
|
&self.dest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
use std::fs;
|
||||||
|
use std::io;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
@ -8,6 +10,7 @@ use log::debug;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use reqwest::{tls, Client, ClientBuilder, Method, Response};
|
use reqwest::{tls, Client, ClientBuilder, Method, Response};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use tempfile::NamedTempFile;
|
||||||
use tinytemplate::TinyTemplate;
|
use tinytemplate::TinyTemplate;
|
||||||
use tokio::task::block_in_place;
|
use tokio::task::block_in_place;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -188,6 +191,91 @@ pub fn get_install_path<P: AsRef<Path>>(install_path: Option<P>) -> Option<PathB
|
||||||
dir
|
dir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Atomically install a file.
|
||||||
|
///
|
||||||
|
/// This is a blocking function, must be called in `block_in_place` mode.
|
||||||
|
pub fn atomic_install(src: &Path, dst: &Path) -> io::Result<()> {
|
||||||
|
debug!(
|
||||||
|
"Attempting to atomically rename from '{}' to '{}'",
|
||||||
|
src.display(),
|
||||||
|
dst.display()
|
||||||
|
);
|
||||||
|
|
||||||
|
if fs::rename(src, dst).is_err() {
|
||||||
|
debug!("Attempting at atomically failed, fallback to creating tempfile.");
|
||||||
|
// src and dst is not on the same filesystem/mountpoint.
|
||||||
|
// Fallback to creating NamedTempFile on the parent dir of
|
||||||
|
// dst.
|
||||||
|
|
||||||
|
let mut src_file = fs::File::open(src)?;
|
||||||
|
|
||||||
|
let parent = dst.parent().unwrap();
|
||||||
|
debug!("Creating named tempfile at '{}'", parent.display());
|
||||||
|
let mut tempfile = NamedTempFile::new_in(parent)?;
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"Copying from '{}' to '{}'",
|
||||||
|
src.display(),
|
||||||
|
tempfile.path().display()
|
||||||
|
);
|
||||||
|
io::copy(&mut src_file, tempfile.as_file_mut())?;
|
||||||
|
|
||||||
|
debug!("Retrieving permissions of '{}'", src.display());
|
||||||
|
let permissions = src_file.metadata()?.permissions();
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"Setting permissions of '{}' to '{permissions:#?}'",
|
||||||
|
tempfile.path().display()
|
||||||
|
);
|
||||||
|
tempfile.as_file().set_permissions(permissions)?;
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"Persisting '{}' to '{}'",
|
||||||
|
tempfile.path().display(),
|
||||||
|
dst.display()
|
||||||
|
);
|
||||||
|
tempfile.persist(dst).map_err(io::Error::from)?;
|
||||||
|
} else {
|
||||||
|
debug!("Attempting at atomically succeeded.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn symlink_file<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) -> io::Result<()> {
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
let f = std::os::unix::fs::symlink;
|
||||||
|
#[cfg(target_family = "windows")]
|
||||||
|
let f = std::os::windows::fs::symlink_file;
|
||||||
|
|
||||||
|
f(original, link)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Atomically install symlink "link" to a file "dst".
|
||||||
|
///
|
||||||
|
/// This is a blocking function, must be called in `block_in_place` mode.
|
||||||
|
pub fn atomic_symlink_file(dest: &Path, link: &Path) -> io::Result<()> {
|
||||||
|
let parent = link.parent().unwrap();
|
||||||
|
|
||||||
|
debug!("Creating tempPath at '{}'", parent.display());
|
||||||
|
let temp_path = NamedTempFile::new_in(parent)?.into_temp_path();
|
||||||
|
fs::remove_file(&temp_path)?;
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"Creating symlink '{}' to file '{}'",
|
||||||
|
temp_path.display(),
|
||||||
|
dest.display()
|
||||||
|
);
|
||||||
|
symlink_file(dest, &temp_path)?;
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"Persisting '{}' to '{}'",
|
||||||
|
temp_path.display(),
|
||||||
|
link.display()
|
||||||
|
);
|
||||||
|
temp_path.persist(link).map_err(io::Error::from)
|
||||||
|
}
|
||||||
|
|
||||||
pub trait Template: Serialize {
|
pub trait Template: Serialize {
|
||||||
fn render(&self, template: &str) -> Result<String, BinstallError>
|
fn render(&self, template: &str) -> Result<String, BinstallError>
|
||||||
where
|
where
|
||||||
|
|
Loading…
Add table
Reference in a new issue