use std::fs;
use std::path::{Path, PathBuf};
use crate::error::{Error, Result};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CurrentExecutable {
target: PathBuf,
installation_root: PathBuf,
destination_name: PathBuf,
digest: [u8; 32],
#[cfg(unix)]
device: u64,
#[cfg(unix)]
inode: u64,
}
impl CurrentExecutable {
pub fn resolve() -> Result<Self> {
let reported = std::env::current_exe()
.map_err(|source| Error::io("resolving current executable", source))?;
Self::bind(reported)
}
pub fn bind(executable: impl Into<PathBuf>) -> Result<Self> {
let reported = executable.into();
if !reported.is_absolute() {
return Err(Error::invalid("current executable path must be absolute"));
}
let target = fs::canonicalize(&reported)
.map_err(|source| Error::io("canonicalizing current executable", source))?;
if target.as_os_str().is_empty() {
return Err(Error::invalid(
"current executable resolved to an empty path",
));
}
let metadata = fs::symlink_metadata(&target)
.map_err(|source| Error::io("reading current executable metadata", source))?;
if metadata.file_type().is_symlink() || !metadata.is_file() {
return Err(Error::DestinationConflict {
destination: target,
});
}
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
if metadata.nlink() != 1 {
return Err(Error::DestinationConflict {
destination: target,
});
}
}
let installation_root = target
.parent()
.ok_or_else(|| Error::invalid("current executable has no parent directory"))?
.to_path_buf();
let root_metadata = fs::symlink_metadata(&installation_root)
.map_err(|source| Error::io("reading executable directory metadata", source))?;
if root_metadata.file_type().is_symlink() || !root_metadata.is_dir() {
return Err(Error::DestinationConflict {
destination: target,
});
}
let destination_name = PathBuf::from(
target
.file_name()
.ok_or_else(|| Error::invalid("current executable has no file name"))?,
);
#[cfg(unix)]
let (device, inode) = {
use std::os::unix::fs::MetadataExt;
(metadata.dev(), metadata.ino())
};
let digest = crate::integrity::hash_file(&target)?;
Ok(Self {
target,
installation_root,
destination_name,
digest,
#[cfg(unix)]
device,
#[cfg(unix)]
inode,
})
}
pub fn target(&self) -> &Path {
&self.target
}
pub fn installation_root(&self) -> &Path {
&self.installation_root
}
pub fn destination_name(&self) -> &Path {
&self.destination_name
}
pub fn destination(&self) -> PathBuf {
self.installation_root.join(&self.destination_name)
}
pub(crate) fn revalidate(&self) -> Result<()> {
let metadata = match fs::symlink_metadata(&self.target) {
Ok(metadata) => metadata,
Err(source) if source.kind() == std::io::ErrorKind::NotFound => {
return Err(Error::DestinationConflict {
destination: self.target.clone(),
})
}
Err(source) => {
return Err(Error::io(
"reading current executable before mutation",
source,
))
}
};
if metadata.file_type().is_symlink() || !metadata.is_file() {
return Err(Error::DestinationConflict {
destination: self.target.clone(),
});
}
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
if metadata.nlink() != 1
|| metadata.dev() != self.device
|| metadata.ino() != self.inode
{
return Err(Error::DestinationConflict {
destination: self.target.clone(),
});
}
}
match crate::integrity::hash_file(&self.target) {
Ok(digest) if digest == self.digest => Ok(()),
Ok(_) => Err(Error::DestinationConflict {
destination: self.target.clone(),
}),
Err(error) => Err(error),
}
}
}