diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index 36bdbfed02b..e715fae695a 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -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; @@ -125,46 +126,26 @@ fn resolve>(original: P) -> IOResult { 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>(original: P, can_mode: CanonicalizeMode) -> IOResult { - // 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 { + 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(); @@ -174,43 +155,107 @@ pub fn canonicalize>(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

(buf: &mut PathBuf, paths: &[P]) +where + P: AsRef, +{ + 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>(original: P, can_mode: CanonicalizeMode) -> IOResult { + // 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) }