Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Physical mode to realpath #2459

Merged
merged 1 commit into from
Aug 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/uu/cp/src/cp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ use std::path::{Path, PathBuf, StripPrefixError};
use std::str::FromStr;
use std::string::ToString;
use uucore::backup_control::{self, BackupMode};
use uucore::fs::{canonicalize, CanonicalizeMode};
use uucore::fs::{canonicalize, MissingHandling, ResolveMode};
use walkdir::WalkDir;

#[cfg(unix)]
Expand Down Expand Up @@ -1431,8 +1431,8 @@ pub fn localize_to_target(root: &Path, source: &Path, target: &Path) -> CopyResu

pub fn paths_refer_to_same_file(p1: &Path, p2: &Path) -> io::Result<bool> {
// We have to take symlinks and relative paths into account.
let pathbuf1 = canonicalize(p1, CanonicalizeMode::Normal)?;
let pathbuf2 = canonicalize(p2, CanonicalizeMode::Normal)?;
let pathbuf1 = canonicalize(p1, MissingHandling::Normal, ResolveMode::Logical)?;
let pathbuf2 = canonicalize(p2, MissingHandling::Normal, ResolveMode::Logical)?;

Ok(pathbuf1 == pathbuf2)
}
Expand Down
10 changes: 7 additions & 3 deletions src/uu/ln/src/ln.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use std::os::unix::fs::symlink;
use std::os::windows::fs::{symlink_dir, symlink_file};
use std::path::{Path, PathBuf};
use uucore::backup_control::{self, BackupMode};
use uucore::fs::{canonicalize, CanonicalizeMode};
use uucore::fs::{canonicalize, MissingHandling, ResolveMode};

pub struct Settings {
overwrite: OverwriteMode,
Expand Down Expand Up @@ -361,8 +361,12 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings)
}

fn relative_path<'a>(src: &Path, dst: &Path) -> Result<Cow<'a, Path>> {
let src_abs = canonicalize(src, CanonicalizeMode::Normal)?;
let mut dst_abs = canonicalize(dst.parent().unwrap(), CanonicalizeMode::Normal)?;
let src_abs = canonicalize(src, MissingHandling::Normal, ResolveMode::Logical)?;
let mut dst_abs = canonicalize(
dst.parent().unwrap(),
MissingHandling::Normal,
ResolveMode::Logical,
)?;
dst_abs.push(dst.components().last().unwrap());
let suffix_pos = src_abs
.components()
Expand Down
25 changes: 16 additions & 9 deletions src/uu/readlink/src/readlink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use clap::{crate_version, App, Arg};
use std::fs;
use std::io::{stdout, Write};
use std::path::{Path, PathBuf};
use uucore::fs::{canonicalize, CanonicalizeMode};
use uucore::fs::{canonicalize, MissingHandling, ResolveMode};

const NAME: &str = "readlink";
const ABOUT: &str = "Print value of a symbolic link or canonical file name.";
Expand Down Expand Up @@ -42,14 +42,21 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let silent = matches.is_present(OPT_SILENT) || matches.is_present(OPT_QUIET);
let verbose = matches.is_present(OPT_VERBOSE);

let can_mode = if matches.is_present(OPT_CANONICALIZE) {
CanonicalizeMode::Normal
} else if matches.is_present(OPT_CANONICALIZE_EXISTING) {
CanonicalizeMode::Existing
let res_mode = if matches.is_present(OPT_CANONICALIZE)
|| matches.is_present(OPT_CANONICALIZE_EXISTING)
|| matches.is_present(OPT_CANONICALIZE_MISSING)
{
ResolveMode::Logical
} else {
ResolveMode::None
};

let can_mode = if matches.is_present(OPT_CANONICALIZE_EXISTING) {
MissingHandling::Existing
} else if matches.is_present(OPT_CANONICALIZE_MISSING) {
CanonicalizeMode::Missing
MissingHandling::Missing
} else {
CanonicalizeMode::None
MissingHandling::Normal
};

let files: Vec<String> = matches
Expand All @@ -71,7 +78,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {

for f in &files {
let p = PathBuf::from(f);
if can_mode == CanonicalizeMode::None {
if res_mode == ResolveMode::None {
match fs::read_link(&p) {
Ok(path) => show(&path, no_newline, use_zero),
Err(err) => {
Expand All @@ -82,7 +89,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
}
}
} else {
match canonicalize(&p, can_mode) {
match canonicalize(&p, can_mode, res_mode) {
Ok(path) => show(&path, no_newline, use_zero),
Err(err) => {
if verbose {
Expand Down
33 changes: 26 additions & 7 deletions src/uu/realpath/src/realpath.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ extern crate uucore;

use clap::{crate_version, App, Arg};
use std::path::{Path, PathBuf};
use uucore::fs::{canonicalize, CanonicalizeMode};
use uucore::fs::{canonicalize, MissingHandling, ResolveMode};

static ABOUT: &str = "print the resolved path";

static OPT_QUIET: &str = "quiet";
static OPT_STRIP: &str = "strip";
static OPT_ZERO: &str = "zero";
static OPT_PHYSICAL: &str = "physical";
static OPT_LOGICAL: &str = "logical";

static ARG_FILES: &str = "files";

Expand All @@ -42,9 +44,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let strip = matches.is_present(OPT_STRIP);
let zero = matches.is_present(OPT_ZERO);
let quiet = matches.is_present(OPT_QUIET);
let logical = matches.is_present(OPT_LOGICAL);
let mut retcode = 0;
for path in &paths {
if let Err(e) = resolve_path(path, strip, zero) {
if let Err(e) = resolve_path(path, strip, zero, logical) {
if !quiet {
show_error!("{}: {}", e, path.display());
}
Expand Down Expand Up @@ -76,6 +79,19 @@ pub fn uu_app() -> App<'static, 'static> {
.long(OPT_ZERO)
.help("Separate output filenames with \\0 rather than newline"),
)
.arg(
Arg::with_name(OPT_LOGICAL)
.short("L")
.long(OPT_LOGICAL)
.help("resolve '..' components before symlinks"),
)
.arg(
Arg::with_name(OPT_PHYSICAL)
.short("P")
.long(OPT_PHYSICAL)
.overrides_with_all(&[OPT_STRIP, OPT_LOGICAL])
.help("resolve symlinks as encountered (default)"),
)
.arg(
Arg::with_name(ARG_FILES)
.multiple(true)
Expand All @@ -96,14 +112,17 @@ pub fn uu_app() -> App<'static, 'static> {
///
/// This function returns an error if there is a problem resolving
/// symbolic links.
fn resolve_path(p: &Path, strip: bool, zero: bool) -> std::io::Result<()> {
let mode = if strip {
CanonicalizeMode::None
fn resolve_path(p: &Path, strip: bool, zero: bool, logical: bool) -> std::io::Result<()> {
let resolve = if strip {
ResolveMode::None
} else if logical {
ResolveMode::Logical
} else {
CanonicalizeMode::Normal
ResolveMode::Physical
};
let abs = canonicalize(p, mode)?;
let abs = canonicalize(p, MissingHandling::Normal, resolve)?;
let line_ending = if zero { '\0' } else { '\n' };

print!("{}{}", abs.display(), line_ending);
Ok(())
}
8 changes: 4 additions & 4 deletions src/uu/relpath/src/relpath.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ extern crate uucore;
use clap::{crate_version, App, Arg};
use std::env;
use std::path::{Path, PathBuf};
use uucore::fs::{canonicalize, CanonicalizeMode};
use uucore::fs::{canonicalize, MissingHandling, ResolveMode};
use uucore::InvalidEncodingHandling;

static ABOUT: &str = "Convert TO destination to the relative path from the FROM dir.
Expand Down Expand Up @@ -42,12 +42,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
Some(p) => Path::new(p).to_path_buf(),
None => env::current_dir().unwrap(),
};
let absto = canonicalize(to, CanonicalizeMode::Normal).unwrap();
let absfrom = canonicalize(from, CanonicalizeMode::Normal).unwrap();
let absto = canonicalize(to, MissingHandling::Normal, ResolveMode::Logical).unwrap();
let absfrom = canonicalize(from, MissingHandling::Normal, ResolveMode::Logical).unwrap();

if matches.is_present(options::DIR) {
let base = Path::new(&matches.value_of(options::DIR).unwrap()).to_path_buf();
let absbase = canonicalize(base, CanonicalizeMode::Normal).unwrap();
let absbase = canonicalize(base, MissingHandling::Normal, ResolveMode::Logical).unwrap();
if !absto.as_path().starts_with(absbase.as_path())
|| !absfrom.as_path().starts_with(absbase.as_path())
{
Expand Down
68 changes: 51 additions & 17 deletions src/uucore/src/lib/features/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,8 @@ pub fn resolve_relative_path(path: &Path) -> Cow<Path> {

/// Controls how symbolic links should be handled when canonicalizing a path.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CanonicalizeMode {
/// Do not resolve any symbolic links.
None,

/// Resolve all symbolic links.
pub enum MissingHandling {
/// Return an error if any part of the path is missing.
Normal,

/// Resolve symbolic links, ignoring errors on the final component.
Expand All @@ -70,6 +67,19 @@ pub enum CanonicalizeMode {
Missing,
}

/// Controls when symbolic links are resolved
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ResolveMode {
/// Do not resolve any symbolic links.
None,

/// Resolve symlinks as encountered when processing the path
Physical,

/// Resolve '..' elements before symlinks
Logical,
}

// copied from https://github.com/rust-lang/cargo/blob/2e4cfc2b7d43328b207879228a2ca7d427d188bb/src/cargo/util/paths.rs#L65-L90
// both projects are MIT https://github.com/rust-lang/cargo/blob/master/LICENSE-MIT
// for std impl progress see rfc https://github.com/rust-lang/rfcs/issues/2208
Expand Down Expand Up @@ -130,20 +140,32 @@ fn resolve<P: AsRef<Path>>(original: P) -> IOResult<PathBuf> {
/// 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:
/// a path.
/// The `miss_mode` parameter controls how missing path elements are handled
///
/// * [`CanonicalizeMode::Normal`] makes this function behave like
/// * [`MissingHandling::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
/// * [`MissingHandling::Missing`] makes this function ignore non-final
/// components of the path that could not be resolved.
/// * [`CanonicalizeMode::Existing`] makes this function return an error
/// * [`MissingHandling::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
///
/// The `res_mode` parameter controls how symbolic links are
/// resolved:
///
/// * [`ResolveMode::None`] makes this function not try to resolve
/// any symbolic links.
/// * [`ResolveMode::Physical`] makes this function resolve symlinks as they
/// are encountered
/// * [`ResolveMode::Logical`] makes this function resolve '..' components
/// before symlinks
///
pub fn canonicalize<P: AsRef<Path>>(original: P, can_mode: CanonicalizeMode) -> IOResult<PathBuf> {
pub fn canonicalize<P: AsRef<Path>>(
original: P,
miss_mode: MissingHandling,
res_mode: ResolveMode,
) -> IOResult<PathBuf> {
// Create an absolute path
let original = original.as_ref();
let original = if original.is_absolute() {
Expand All @@ -167,7 +189,11 @@ pub fn canonicalize<P: AsRef<Path>>(original: P, can_mode: CanonicalizeMode) ->
}
Component::CurDir => (),
Component::ParentDir => {
parts.pop();
if res_mode == ResolveMode::Logical {
parts.pop();
} else {
parts.push(part.as_os_str());
}
}
Component::Normal(_) => {
parts.push(part.as_os_str());
Expand All @@ -180,12 +206,17 @@ pub fn canonicalize<P: AsRef<Path>>(original: P, can_mode: CanonicalizeMode) ->
for part in parts[..parts.len() - 1].iter() {
result.push(part);

if can_mode == CanonicalizeMode::None {
//resolve as we go to handle long relative paths on windows
if res_mode == ResolveMode::Physical {
result = normalize_path(&result);
}

if res_mode == ResolveMode::None {
continue;
}

match resolve(&result) {
Err(_) if can_mode == CanonicalizeMode::Missing => continue,
Err(_) if miss_mode == MissingHandling::Missing => continue,
Err(e) => return Err(e),
Ok(path) => {
result.pop();
Expand All @@ -196,12 +227,12 @@ pub fn canonicalize<P: AsRef<Path>>(original: P, can_mode: CanonicalizeMode) ->

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

if can_mode == CanonicalizeMode::None {
if res_mode == ResolveMode::None {
return Ok(result);
}

match resolve(&result) {
Err(e) if can_mode == CanonicalizeMode::Existing => {
Err(e) if miss_mode == MissingHandling::Existing => {
return Err(e);
}
Ok(path) => {
Expand All @@ -210,6 +241,9 @@ pub fn canonicalize<P: AsRef<Path>>(original: P, can_mode: CanonicalizeMode) ->
}
Err(_) => (),
}
if res_mode == ResolveMode::Physical {
result = normalize_path(&result);
}
}
Ok(result)
}
Expand Down
11 changes: 11 additions & 0 deletions tests/by-util/test_readlink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@ use crate::common::util::*;

static GIBBERISH: &str = "supercalifragilisticexpialidocious";

#[test]
fn test_resolve() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;

at.touch("foo");
at.symlink_file("foo", "bar");

scene.ucmd().arg("bar").succeeds().stdout_contains("foo\n");
}

#[test]
fn test_canonicalize() {
let (at, mut ucmd) = at_and_ucmd!();
Expand Down
33 changes: 33 additions & 0 deletions tests/by-util/test_realpath.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,36 @@ fn test_realpath_file_and_links_strip_zero() {
.succeeds()
.stdout_contains("bar\u{0}");
}

#[test]
fn test_realpath_physical_mode() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;

at.mkdir("dir1");
at.mkdir_all("dir2/bar");
at.symlink_dir("dir2/bar", "dir1/foo");

scene
.ucmd()
.arg("dir1/foo/..")
.succeeds()
.stdout_contains("dir2\n");
}

#[test]
fn test_realpath_logical_mode() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;

at.mkdir("dir1");
at.mkdir("dir2");
at.symlink_dir("dir2", "dir1/foo");

scene
.ucmd()
.arg("-L")
.arg("dir1/foo/..")
.succeeds()
.stdout_contains("dir1\n");
}