mirror of
https://github.com/uutils/coreutils
synced 2024-11-16 01:38:04 +00:00
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:
parent
c98e7f5de9
commit
0e04f959c2
8 changed files with 153 additions and 45 deletions
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
{
|
||||
|
|
|
@ -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 => {
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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!();
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue