Skip to content

Commit

Permalink
Add Physical mode to realpath
Browse files Browse the repository at this point in the history
This adds the 'Physical Mode' and 'Logical Mode' switches to realpath, which control when symlinks are resolved.
  • Loading branch information
jaggededgedjustice committed Jul 17, 2021
1 parent df0382c commit 9d287f6
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 62 deletions.
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 @@ -1421,8 +1421,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 @@ -22,7 +22,7 @@ use std::os::unix::fs::symlink;
#[cfg(windows)]
use std::os::windows::fs::{symlink_dir, symlink_file};
use std::path::{Path, PathBuf};
use uucore::fs::{canonicalize, CanonicalizeMode};
use uucore::fs::{canonicalize, MissingHandling, ResolveMode};

pub struct Settings {
overwrite: OverwriteMode,
Expand Down Expand Up @@ -375,8 +375,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
46 changes: 20 additions & 26 deletions src/uu/readlink/src/readlink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@
extern crate uucore;

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 +41,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,25 +77,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 {

for f in &files {
let p = PathBuf::from(f);
if can_mode == CanonicalizeMode::None {
match fs::read_link(&p) {
Ok(path) => show(&path, no_newline, use_zero),
Err(err) => {
if verbose {
eprintln!("{}: {}: errno {}", NAME, f, err.raw_os_error().unwrap());
}
return 1;
}
}
} else {
match canonicalize(&p, can_mode) {
Ok(path) => show(&path, no_newline, use_zero),
Err(err) => {
if verbose {
eprintln!("{}: {}: errno {:?}", NAME, f, err.raw_os_error().unwrap());
}
return 1;
match canonicalize(&p, can_mode, res_mode) {
Ok(path) => show(&path, no_newline, use_zero),
Err(err) => {
if verbose {
eprintln!("{}: {}: errno {:?}", NAME, f, err.raw_os_error().unwrap());
}
return 1;
}
}
}
Expand Down
54 changes: 45 additions & 9 deletions src/uu/realpath/src/realpath.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@
extern crate uucore;

use clap::{crate_version, App, Arg};
use std::path::{Path, PathBuf};
use uucore::fs::{canonicalize, CanonicalizeMode};
use std::path::{Component, Path, PathBuf};
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,34 @@ 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);

let mut final_path = PathBuf::new();
if cfg!(windows) && resolve == ResolveMode::Physical {
// strip the '\\?\' prefix to the sting indicating 'Extended Length Path syntax' (https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#namespaces)
for part in abs.components() {
match part {
Component::Prefix(_) => {
final_path.push(&part.as_os_str().to_str().unwrap()[4..]);
}
_ => {
final_path.push(part);
}
}
}
} else {
final_path = abs;
}

print!("{}{}", final_path.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
63 changes: 46 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,12 @@ 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 {
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 +222,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 +236,9 @@ pub fn canonicalize<P: AsRef<Path>>(original: P, can_mode: CanonicalizeMode) ->
}
Err(_) => (),
}
if res_mode == ResolveMode::Physical {
result = fs::canonicalize(result)?;
}
}
Ok(result)
}
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");
}

0 comments on commit 9d287f6

Please sign in to comment.