#![deny(
missing_docs,
trivial_casts,
trivial_numeric_casts,
unused_extern_crates,
unused_import_braces,
unused_qualifications,
unused_results
)]
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::{
fs::Permissions,
io::Write,
ops::Deref,
path::{Path, PathBuf},
};
#[cfg(doc)]
use tempfile::NamedTempFile;
use tempfile::{Builder, TempDir, TempPath};
enum ReplicatePath {
TempPath(TempPath),
PathBuf(PathBuf),
}
pub struct Replicate {
parent: TempDir,
path: ReplicatePath,
}
impl Replicate {
pub fn new() -> Result<Self, std::io::Error> {
let parent = tempfile::tempdir()?;
let mut copy = Builder::new()
.prefix("replicate_")
.rand_bytes(5)
.tempfile_in(parent.path())?;
let _ = Self::copy_self_into_writer(&mut copy)?;
let path = copy.into_temp_path();
Self::make_executable(&path)?;
Ok(Self {
parent,
path: ReplicatePath::TempPath(path),
})
}
fn copy_self_into_writer<W: ?Sized + Write>(writer: &mut W) -> std::io::Result<u64> {
let mut self_exe = exe()?;
std::io::copy(&mut self_exe, writer)
}
fn make_executable<P: AsRef<Path>>(path: P) -> std::io::Result<()> {
#[cfg(unix)]
{
let permissions = Permissions::from_mode(0o755);
std::fs::set_permissions(path.as_ref(), permissions)
}
#[cfg(not(unix))]
{
Ok(())
}
}
pub fn same_name() -> std::io::Result<Self> {
let current_exe_path = std::env::current_exe()?;
let filename = current_exe_path
.file_name()
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidInput, "No file name"))?;
let parent = tempfile::tempdir()?;
let copy_path = parent.path().join(filename);
{
let mut copy = std::fs::File::create(©_path)?;
let _ = Self::copy_self_into_writer(&mut copy)?;
}
Self::make_executable(©_path)?;
Ok(Self {
parent,
path: ReplicatePath::PathBuf(copy_path),
})
}
pub fn parent(&self) -> &Path {
self.parent.path()
}
pub fn path(&self) -> &Path {
match &self.path {
ReplicatePath::TempPath(temp_path) => temp_path.as_ref(),
ReplicatePath::PathBuf(path_buf) => path_buf.as_ref(),
}
}
}
impl Deref for Replicate {
type Target = Path;
fn deref(&self) -> &Path {
self.path()
}
}
impl AsRef<Path> for Replicate {
fn as_ref(&self) -> &Path {
self.path()
}
}
pub fn exe() -> std::io::Result<std::fs::File> {
exe_path().and_then(std::fs::File::open)
}
pub fn exe_path() -> std::io::Result<PathBuf> {
#[cfg(any(target_os = "android", target_os = "linux"))]
{
Ok(PathBuf::from("/proc/self/exe"))
}
#[cfg(target_os = "dragonfly")]
{
Ok(PathBuf::from("/proc/curproc/file"))
}
#[cfg(target_os = "netbsd")]
{
Ok(PathBuf::from("/proc/curproc/exe"))
}
#[cfg(target_os = "solaris")]
{
Ok(PathBuf::from(format!(
"/proc/{}/path/a.out",
nix::unistd::getpid()
))) }
#[cfg(not(any(
target_os = "android",
target_os = "dragonfly",
target_os = "linux",
target_os = "netbsd",
target_os = "solaris"
)))]
{
std::env::current_exe()
}
}
#[cfg(test)]
mod tests {
use std::ffi::OsStr;
use super::*;
#[test]
fn create_replicate() -> anyhow::Result<()> {
let copy = Replicate::new()?;
println!("Created new copy: {}", copy.display());
let name = copy
.file_name()
.and_then(OsStr::to_str)
.expect("Failed to copy program");
assert!(name.starts_with("replicate"));
Ok(())
}
#[test]
fn create_replicate_with_same_name() -> anyhow::Result<()> {
let copy = Replicate::same_name()?;
println!("Created new copy: {}", copy.display());
let name = copy
.file_name()
.and_then(OsStr::to_str)
.expect("Failed to copy program");
assert!(name.starts_with("replicate"));
Ok(())
}
#[test]
fn test_that_files_are_cleared_up() -> anyhow::Result<()> {
let path_str = {
let copy = Replicate::new()?;
println!("My copy's path is {}", copy.display());
copy.parent().to_path_buf()
};
assert!(!path_str.exists(), "Temporary directory still exists");
Ok(())
}
}