#[macro_use]
extern crate log;
use std::collections;
use std::error;
use std::ffi;
use std::fmt;
use std::io;
use std::path;
#[derive(Debug)]
pub enum Error {
IoError { err: io::Error, at: path::PathBuf },
SymlinkLoops(path::PathBuf),
PathIsAbsolute(path::PathBuf),
RelativePathEscapesPrefix(path::PathBuf),
#[doc(hidden)]
__NonExhaustive,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
Error::IoError { err, at } => write!(f, "{}: {:?}", err, at),
Error::SymlinkLoops(p) => {
write!(f, "Found an infinite symlink loop: {:?}", p)
}
Error::PathIsAbsolute(p) => write!(
f,
"Tried to make a relative path from absolute path {:?}",
p
),
Error::RelativePathEscapesPrefix(p) => write!(
f,
"Tried to make a relative path with leading '..': {:?}",
p
),
_ => write!(f, "Unknown error"),
}
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
Error::IoError { ref err, .. } => Some(err),
_ => None,
}
}
}
fn split_head_and_tail<P: AsRef<path::Path>>(
path: P,
) -> Result<(path::PathBuf, collections::VecDeque<ffi::OsString>), Error> {
let path = path.as_ref();
debug!("Splitting head and tail of {:?}", path);
let mut head =
path::Path::new(".")
.canonicalize()
.map_err(|err| Error::IoError {
err: err,
at: path.into(),
})?;
let mut tail = collections::VecDeque::new();
debug!("Initial head: {:?} tail: {:?}", head, tail);
for each in path.components() {
debug!("Component: {:?}", each);
match each {
path::Component::Prefix(prefix) => {
match prefix.kind() {
path::Prefix::Verbatim(..)
| path::Prefix::VerbatimUNC(..)
| path::Prefix::VerbatimDisk(..)
| path::Prefix::DeviceNS(..) => head.push(each),
_ => {
let path_prefix: &path::Path = each.as_ref();
let path_prefix =
path_prefix.canonicalize().map_err(|err| {
Error::IoError {
err,
at: path_prefix.into(),
}
})?;
head.push(path_prefix)
}
}
}
path::Component::RootDir => head.push(each),
_ => tail.push_back(each.as_os_str().to_os_string()),
}
debug!("Current head: {:?} tail: {:?}", head, tail);
}
debug!("Head: {:?}, tail: {:?}", head, tail);
Ok((head, tail))
}
#[cfg(unix)]
fn read_link_if_exists<P: AsRef<path::Path>>(
path: P,
) -> Result<Option<path::PathBuf>, Error> {
let path = path.as_ref();
path.symlink_metadata()
.map(|metadata| metadata.file_type().is_symlink())
.or_else(|err| {
if err.kind() == io::ErrorKind::NotFound {
debug!("{:?} does not exist, it's not a symlink", path,);
Ok(false)
} else {
error!("Could not check {:?}", path);
Err(err)
}
})
.and_then(|is_symlink| {
if is_symlink {
debug!("{:?} exists and is a symlink", path);
Ok(Some(path.read_link()?))
} else {
debug!("{:?} exists and isn't a symlink", path);
Ok(None)
}
})
.map_err(|err| Error::IoError {
err: err,
at: path.into(),
})
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Absolute(path::PathBuf);
impl Absolute {
#[cfg(unix)]
fn from_path<P: AsRef<path::Path>>(path: P) -> Result<Absolute, Error> {
let (mut res, mut tail) = split_head_and_tail(path)?;
let mut seen_paths = collections::BTreeSet::new();
while !tail.is_empty() {
debug!("Monotonic path: {:?}, to check: {:?}", res, tail);
let current =
tail.pop_front().expect("len() > 0 but vec is empty?");
if ¤t == "." {
} else if ¤t == ".." {
if seen_paths.contains(&res) {
return Err(Error::SymlinkLoops(res));
}
seen_paths.insert(res.clone());
match read_link_if_exists(&res)? {
None => (),
Some(target) => {
debug!("Symlink target: {:?}", target);
tail.push_front("..".into());
let target = if target.is_absolute() {
debug!("Target is absolute");
let (new_head, new_target) =
split_head_and_tail(target)?;
res.push(new_head);
new_target
} else {
debug!("Target is relative");
target
.components()
.map(|each| each.as_os_str().to_os_string())
.collect::<collections::VecDeque<_>>()
};
for each in target.into_iter().rev() {
tail.push_front(each);
}
}
}
res.pop();
} else {
res.push(current);
}
}
Ok(Absolute(res))
}
#[cfg(windows)]
fn from_path<P: AsRef<path::Path>>(path: P) -> Result<Absolute, Error> {
let (mut res, mut tail) = split_head_and_tail(path)?;
while !tail.is_empty() {
debug!("Monotonic path: {:?}, to check: {:?}", res, tail);
let current =
tail.pop_front().expect("len() > 0 but vec is empty?");
if ¤t == "." {
} else if ¤t == ".." {
res.pop();
} else {
res.push(current);
}
}
Ok(Absolute(res))
}
pub fn new<P: AsRef<path::Path>>(path: P) -> Result<Absolute, Error> {
Absolute::from_path(path)
}
pub fn join<P: AsRef<path::Path>>(
&self,
path: P,
) -> Result<Absolute, Error> {
Ok(self.join_relative(&Relative::new(path)?))
}
pub fn join_relative(&self, tail: &Relative) -> Absolute {
Absolute(self.0.join(tail))
}
pub fn as_path(&self) -> &path::Path {
<Self as AsRef<path::Path>>::as_ref(self)
}
pub fn as_os_str(&self) -> &ffi::OsStr {
<Self as AsRef<ffi::OsStr>>::as_ref(self)
}
}
impl AsRef<path::Path> for Absolute {
fn as_ref(&self) -> &path::Path {
self.0.as_path()
}
}
impl AsRef<ffi::OsStr> for Absolute {
fn as_ref(&self) -> &ffi::OsStr {
self.0.as_os_str()
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Relative(path::PathBuf);
impl Relative {
pub fn new<P: AsRef<path::Path>>(path: P) -> Result<Relative, Error> {
let components = path.as_ref().components();
let mut res = path::PathBuf::new();
for each in components {
match each {
path::Component::Prefix(_) | path::Component::RootDir => {
return Err(Error::PathIsAbsolute(path.as_ref().into()));
}
path::Component::Normal(name) => res.push(name),
path::Component::ParentDir => {
if res.as_os_str() == "" {
return Err(Error::RelativePathEscapesPrefix(
path.as_ref().into(),
));
}
res.pop();
}
path::Component::CurDir => (),
}
}
Ok(Relative(res))
}
pub fn join<P: AsRef<path::Path>>(
&self,
path: P,
) -> Result<Relative, Error> {
Ok(self.join_relative(&Relative::new(path)?))
}
pub fn join_relative(&self, tail: &Relative) -> Relative {
Relative(self.0.join(tail))
}
pub fn as_path(&self) -> &path::Path {
<Self as AsRef<path::Path>>::as_ref(self)
}
pub fn as_os_str(&self) -> &ffi::OsStr {
<Self as AsRef<ffi::OsStr>>::as_ref(self)
}
}
impl AsRef<path::Path> for Relative {
fn as_ref(&self) -> &path::Path {
self.0.as_path()
}
}
impl AsRef<ffi::OsStr> for Relative {
fn as_ref(&self) -> &ffi::OsStr {
self.0.as_os_str()
}
}
#[cfg(test)]
mod tests;