Add Physical mode to realpath

This adds the 'Physical Mode' and 'Logical Mode' switches to realpath, which control when symlinks are resolved.
This commit is contained in:
James Robson 2021-06-27 11:47:23 +01:00
parent c98e7f5de9
commit 0e04f959c2
8 changed files with 153 additions and 45 deletions

View file

@ -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)]
@ -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)
}

View file

@ -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,
@ -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()

View file

@ -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.";
@ -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
} else if matches.is_present(OPT_CANONICALIZE_MISSING) {
CanonicalizeMode::Missing
let res_mode = if matches.is_present(OPT_CANONICALIZE)
|| matches.is_present(OPT_CANONICALIZE_EXISTING)
|| matches.is_present(OPT_CANONICALIZE_MISSING)
{
ResolveMode::Logical
} else {
CanonicalizeMode::None
ResolveMode::None
};
let can_mode = if matches.is_present(OPT_CANONICALIZE_EXISTING) {
MissingHandling::Existing
} else if matches.is_present(OPT_CANONICALIZE_MISSING) {
MissingHandling::Missing
} else {
MissingHandling::Normal
};
let files: Vec<String> = matches
@ -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) => {
@ -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 {

View file

@ -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";
@ -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());
}
@ -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)
@ -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(())
}

View file

@ -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.
@ -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())
{

View file

@ -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.
@ -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
@ -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
/// any symbolic links.
///
pub fn canonicalize<P: AsRef<Path>>(original: P, can_mode: CanonicalizeMode) -> IOResult<PathBuf> {
/// 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,
miss_mode: MissingHandling,
res_mode: ResolveMode,
) -> IOResult<PathBuf> {
// Create an absolute path
let original = original.as_ref();
let original = if original.is_absolute() {
@ -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());
@ -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();
@ -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) => {
@ -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)
}

View file

@ -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!();

View file

@ -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");
}