Skip to content

Commit

Permalink
uucore(fs): refactor canonicalize() into helpers
Browse files Browse the repository at this point in the history
Refactor the `uucore::fs::canonicalize()` function into three helper
functions. The first gets the absolute name of the path, the second
breaks the path into its components, and the third resolves all symbolic
links in the path.
  • Loading branch information
jfinkels committed Jul 28, 2021
1 parent 978033a commit 0d2bc99
Showing 1 changed file with 100 additions and 55 deletions.
155 changes: 100 additions & 55 deletions src/uucore/src/lib/features/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use libc::{
};
use std::borrow::Cow;
use std::env;
use std::ffi::OsStr;
use std::fs;
#[cfg(target_os = "redox")]
use std::io;
Expand Down Expand Up @@ -125,46 +126,26 @@ fn resolve<P: AsRef<Path>>(original: P) -> IOResult<PathBuf> {
Ok(result)
}

/// Return the canonical, absolute form of a path.
///
/// This function is a generalization of [`std::fs::canonicalize`] that
/// allows controlling how symbolic links are resolved and how to deal
/// with missing components. It returns the canonical, absolute form of
/// a path. The `can_mode` parameter controls how symbolic links are
/// resolved:
///
/// * [`CanonicalizeMode::Normal`] makes this function behave like
/// [`std::fs::canonicalize`], resolving symbolic links and returning
/// an error if the path does not exist.
/// * [`CanonicalizeMode::Missing`] makes this function ignore non-final
/// components of the path that could not be resolved.
/// * [`CanonicalizeMode::Existing`] makes this function return an error
/// if the final component of the path does not exist.
/// * [`CanonicalizeMode::None`] makes this function not try to resolve
/// any symbolic links.
///
pub fn canonicalize<P: AsRef<Path>>(original: P, can_mode: CanonicalizeMode) -> IOResult<PathBuf> {
// Create an absolute path
let original = original.as_ref();
let original = if original.is_absolute() {
original.to_path_buf()
fn get_absolute(path: &Path) -> IOResult<PathBuf> {
if path.is_absolute() {
Ok(path.to_path_buf())
} else {
dunce::canonicalize(env::current_dir().unwrap())
.unwrap()
.join(original)
};
let current_dir = env::current_dir()?;
let canonical_dir = dunce::canonicalize(current_dir)?;
Ok(canonical_dir.join(path))
}
}

let mut result = PathBuf::new();
fn get_parts(path: &Path) -> (Option<&OsStr>, Vec<&OsStr>) {
let mut parts = vec![];

// Split path by directory separator; add prefix (Windows-only) and root
// directory to final path buffer; add remaining parts to temporary
// vector for canonicalization.
for part in original.components() {
let mut prefix = None;
for part in path.components() {
match part {
Component::Prefix(_) | Component::RootDir => {
result.push(part.as_os_str());
}
Component::Prefix(_) | Component::RootDir => prefix = Some(part.as_os_str()),
Component::CurDir => (),
Component::ParentDir => {
parts.pop();
Expand All @@ -174,43 +155,107 @@ pub fn canonicalize<P: AsRef<Path>>(original: P, can_mode: CanonicalizeMode) ->
}
}
}
(prefix, parts)
}

// Resolve the symlinks where possible
if !parts.is_empty() {
for part in parts[..parts.len() - 1].iter() {
result.push(part);

if can_mode == CanonicalizeMode::None {
continue;
}
fn path_buf_append<P>(buf: &mut PathBuf, paths: &[P])
where
P: AsRef<Path>,
{
for path in paths {
buf.push(path)
}
}

match resolve(&result) {
Err(_) if can_mode == CanonicalizeMode::Missing => continue,
Err(e) => return Err(e),
Ok(path) => {
fn resolve_all_links(
result: &mut PathBuf,
parts: Vec<&OsStr>,
mode: CanonicalizeMode,
) -> IOResult<()> {
match mode {
// Resolve no links.
CanonicalizeMode::None => {
path_buf_append(result, &parts);
}
// Resolve all links, ignoring all errors.
CanonicalizeMode::Missing => {
for part in parts {
result.push(part);
if let Ok(path) = resolve(&result) {
result.pop();
result.push(path);
}
}
}

result.push(parts.last().unwrap());

if can_mode == CanonicalizeMode::None {
return Ok(result);
// Resolve all links, propagating all errors.
CanonicalizeMode::Existing => {
for part in parts {
result.push(part);
let path = resolve(&result)?;
result.pop();
result.push(path);
}
}

match resolve(&result) {
Err(e) if can_mode == CanonicalizeMode::Existing => {
return Err(e);
// Resolve all links, propagating errors on all but the last component.
CanonicalizeMode::Normal => {
let n = parts.len();
for part in &parts[..n - 1] {
result.push(part);
let path = resolve(&result)?;
result.pop();
result.push(path);
}
Ok(path) => {
let p = parts.last().unwrap();
result.push(p);
if let Ok(path) = resolve(&result) {
result.pop();
result.push(path);
}
Err(_) => (),
}
}
Ok(())
}

/// Return the canonical, absolute form of a path.
///
/// This function is a generalization of [`std::fs::canonicalize`] that
/// allows controlling how symbolic links are resolved and how to deal
/// with missing components. It returns the canonical, absolute form of
/// a path. The `can_mode` parameter controls how symbolic links are
/// resolved:
///
/// * [`CanonicalizeMode::Normal`] makes this function behave like
/// [`std::fs::canonicalize`], resolving symbolic links and returning
/// an error if the path does not exist.
/// * [`CanonicalizeMode::Missing`] makes this function ignore non-final
/// components of the path that could not be resolved.
/// * [`CanonicalizeMode::Existing`] makes this function return an error
/// if the final component of the path does not exist.
/// * [`CanonicalizeMode::None`] makes this function not try to resolve
/// any symbolic links.
///
pub fn canonicalize<P: AsRef<Path>>(original: P, can_mode: CanonicalizeMode) -> IOResult<PathBuf> {
// Get the absolute path. For example, convert "a/b" into "/a/b".
let absolute_path = get_absolute(original.as_ref())?;

// Convert the absolute path into its components, resolving ".." entries.
let (maybe_prefix, parts) = get_parts(&absolute_path);

// If there is a prefix, insert it into the `PathBuf` as the first element.
let mut result = PathBuf::new();
if let Some(prefix) = maybe_prefix {
result.push(prefix);
}

// If there were no other components in the path, then do nothing else.
if parts.is_empty() {
return Ok(result);
}

// Resolve all links in the path.
//
// This function modifies `result` in-place.
resolve_all_links(&mut result, parts, can_mode)?;
Ok(result)
}

Expand Down

0 comments on commit 0d2bc99

Please sign in to comment.