mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
cp: improve symlink handling
This commit is contained in:
parent
439b7e0ca5
commit
12a1c87cb8
2 changed files with 70 additions and 36 deletions
|
@ -48,7 +48,6 @@ use std::path::{Path, PathBuf, StripPrefixError};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::string::ToString;
|
use std::string::ToString;
|
||||||
use uucore::backup_control::{self, BackupMode};
|
use uucore::backup_control::{self, BackupMode};
|
||||||
use uucore::fs::resolve_relative_path;
|
|
||||||
use uucore::fs::{canonicalize, CanonicalizeMode};
|
use uucore::fs::{canonicalize, CanonicalizeMode};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
|
@ -198,7 +197,6 @@ pub struct Options {
|
||||||
copy_contents: bool,
|
copy_contents: bool,
|
||||||
copy_mode: CopyMode,
|
copy_mode: CopyMode,
|
||||||
dereference: bool,
|
dereference: bool,
|
||||||
no_dereference: bool,
|
|
||||||
no_target_dir: bool,
|
no_target_dir: bool,
|
||||||
one_file_system: bool,
|
one_file_system: bool,
|
||||||
overwrite: OverwriteMode,
|
overwrite: OverwriteMode,
|
||||||
|
@ -641,11 +639,12 @@ impl Options {
|
||||||
attributes_only: matches.is_present(OPT_ATTRIBUTES_ONLY),
|
attributes_only: matches.is_present(OPT_ATTRIBUTES_ONLY),
|
||||||
copy_contents: matches.is_present(OPT_COPY_CONTENTS),
|
copy_contents: matches.is_present(OPT_COPY_CONTENTS),
|
||||||
copy_mode: CopyMode::from_matches(matches),
|
copy_mode: CopyMode::from_matches(matches),
|
||||||
dereference: matches.is_present(OPT_DEREFERENCE),
|
|
||||||
// No dereference is set with -p, -d and --archive
|
// No dereference is set with -p, -d and --archive
|
||||||
no_dereference: matches.is_present(OPT_NO_DEREFERENCE)
|
dereference: !(matches.is_present(OPT_NO_DEREFERENCE)
|
||||||
|| matches.is_present(OPT_NO_DEREFERENCE_PRESERVE_LINKS)
|
|| matches.is_present(OPT_NO_DEREFERENCE_PRESERVE_LINKS)
|
||||||
|| matches.is_present(OPT_ARCHIVE),
|
|| matches.is_present(OPT_ARCHIVE)
|
||||||
|
|| recursive)
|
||||||
|
|| matches.is_present(OPT_DEREFERENCE),
|
||||||
one_file_system: matches.is_present(OPT_ONE_FILE_SYSTEM),
|
one_file_system: matches.is_present(OPT_ONE_FILE_SYSTEM),
|
||||||
parents: matches.is_present(OPT_PARENTS),
|
parents: matches.is_present(OPT_PARENTS),
|
||||||
update: matches.is_present(OPT_UPDATE),
|
update: matches.is_present(OPT_UPDATE),
|
||||||
|
@ -896,7 +895,14 @@ fn copy_source(
|
||||||
options: &Options,
|
options: &Options,
|
||||||
) -> CopyResult<()> {
|
) -> CopyResult<()> {
|
||||||
let source_path = Path::new(&source);
|
let source_path = Path::new(&source);
|
||||||
if source_path.is_dir() {
|
// if no-dereference is enabled and this is a symlink, don't treat it as a directory
|
||||||
|
if source_path.is_dir()
|
||||||
|
&& !(!options.dereference
|
||||||
|
&& fs::symlink_metadata(source_path)
|
||||||
|
.unwrap()
|
||||||
|
.file_type()
|
||||||
|
.is_symlink())
|
||||||
|
{
|
||||||
// Copy as directory
|
// Copy as directory
|
||||||
copy_directory(source, target, options)
|
copy_directory(source, target, options)
|
||||||
} else {
|
} else {
|
||||||
|
@ -937,7 +943,7 @@ fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyR
|
||||||
return Err(format!("omitting directory '{}'", root.display()).into());
|
return Err(format!("omitting directory '{}'", root.display()).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let root_path = Path::new(&root).canonicalize()?;
|
let root_path = env::current_dir().unwrap().join(root);
|
||||||
|
|
||||||
let root_parent = if target.exists() {
|
let root_parent = if target.exists() {
|
||||||
root_path.parent()
|
root_path.parent()
|
||||||
|
@ -958,17 +964,15 @@ fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyR
|
||||||
#[cfg(any(windows, target_os = "redox"))]
|
#[cfg(any(windows, target_os = "redox"))]
|
||||||
let mut hard_links: Vec<(String, u64)> = vec![];
|
let mut hard_links: Vec<(String, u64)> = vec![];
|
||||||
|
|
||||||
for path in WalkDir::new(root).same_file_system(options.one_file_system) {
|
for path in WalkDir::new(root)
|
||||||
|
.same_file_system(options.one_file_system)
|
||||||
|
.follow_links(options.dereference)
|
||||||
|
{
|
||||||
let p = or_continue!(path);
|
let p = or_continue!(path);
|
||||||
let is_symlink = fs::symlink_metadata(p.path())?.file_type().is_symlink();
|
let is_symlink = fs::symlink_metadata(p.path())?.file_type().is_symlink();
|
||||||
let path = if (options.no_dereference || options.dereference) && is_symlink {
|
let path = match env::current_dir() {
|
||||||
// we are dealing with a symlink. Don't follow it
|
Ok(cwd) => cwd.join(&p.path()),
|
||||||
match env::current_dir() {
|
Err(e) => crash!(1, "failed to get current directory {}", e),
|
||||||
Ok(cwd) => cwd.join(resolve_relative_path(p.path())),
|
|
||||||
Err(e) => crash!(1, "failed to get current directory {}", e),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
or_continue!(p.path().canonicalize())
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let local_to_root_parent = match root_parent {
|
let local_to_root_parent = match root_parent {
|
||||||
|
@ -992,9 +996,10 @@ fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyR
|
||||||
};
|
};
|
||||||
|
|
||||||
let local_to_target = target.join(&local_to_root_parent);
|
let local_to_target = target.join(&local_to_root_parent);
|
||||||
|
if is_symlink && !options.dereference {
|
||||||
if path.is_dir() && !local_to_target.exists() {
|
copy_link(&path, &local_to_target)?;
|
||||||
or_continue!(fs::create_dir_all(local_to_target.clone()));
|
} else if path.is_dir() && !local_to_target.exists() {
|
||||||
|
or_continue!(fs::create_dir_all(local_to_target));
|
||||||
} else if !path.is_dir() {
|
} else if !path.is_dir() {
|
||||||
if preserve_hard_links {
|
if preserve_hard_links {
|
||||||
let mut found_hard_link = false;
|
let mut found_hard_link = false;
|
||||||
|
@ -1220,25 +1225,10 @@ fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()>
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
copy_on_write_macos(source, dest, options.reflink_mode)?;
|
copy_on_write_macos(source, dest, options.reflink_mode)?;
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
copy_on_write_linux(source, dest, options.reflink_mode)?;
|
copy_on_write_linux(source, dest, options.reflink_mode)?;
|
||||||
} else if options.no_dereference && fs::symlink_metadata(&source)?.file_type().is_symlink() {
|
} else if !options.dereference && fs::symlink_metadata(&source)?.file_type().is_symlink() {
|
||||||
// Here, we will copy the symlink itself (actually, just recreate it)
|
copy_link(source, dest)?;
|
||||||
let link = fs::read_link(&source)?;
|
|
||||||
let dest: Cow<'_, Path> = if dest.is_dir() {
|
|
||||||
match source.file_name() {
|
|
||||||
Some(name) => dest.join(name).into(),
|
|
||||||
None => crash!(
|
|
||||||
EXIT_ERR,
|
|
||||||
"cannot stat ‘{}’: No such file or directory",
|
|
||||||
source.display()
|
|
||||||
),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dest.into()
|
|
||||||
};
|
|
||||||
symlink_file(&link, &dest, &*context_for(&link, &dest))?;
|
|
||||||
} else if source.to_string_lossy() == "/dev/null" {
|
} else if source.to_string_lossy() == "/dev/null" {
|
||||||
/* workaround a limitation of fs::copy
|
/* workaround a limitation of fs::copy
|
||||||
* https://github.com/rust-lang/rust/issues/79390
|
* https://github.com/rust-lang/rust/issues/79390
|
||||||
|
@ -1255,6 +1245,24 @@ fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()>
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn copy_link(source: &Path, dest: &Path) -> CopyResult<()> {
|
||||||
|
// Here, we will copy the symlink itself (actually, just recreate it)
|
||||||
|
let link = fs::read_link(&source)?;
|
||||||
|
let dest: Cow<'_, Path> = if dest.is_dir() {
|
||||||
|
match source.file_name() {
|
||||||
|
Some(name) => dest.join(name).into(),
|
||||||
|
None => crash!(
|
||||||
|
EXIT_ERR,
|
||||||
|
"cannot stat ‘{}’: No such file or directory",
|
||||||
|
source.display()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dest.into()
|
||||||
|
};
|
||||||
|
symlink_file(&link, &dest, &*context_for(&link, &dest))
|
||||||
|
}
|
||||||
|
|
||||||
/// Copies `source` to `dest` using copy-on-write if possible.
|
/// Copies `source` to `dest` using copy-on-write if possible.
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyResult<()> {
|
fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyResult<()> {
|
||||||
|
|
|
@ -1299,3 +1299,29 @@ fn test_closes_file_descriptors() {
|
||||||
.with_limit(Resource::NOFILE, 9, 9)
|
.with_limit(Resource::NOFILE, 9, 9)
|
||||||
.succeeds();
|
.succeeds();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_copy_dir_symlink() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
at.mkdir("dir");
|
||||||
|
at.symlink_dir("dir", "dir-link");
|
||||||
|
ucmd.args(&["-r", "dir-link", "copy"]).succeeds();
|
||||||
|
assert_eq!(at.resolve_link("copy"), "dir");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_copy_dir_with_symlinks() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
at.mkdir("dir");
|
||||||
|
at.make_file("dir/file");
|
||||||
|
|
||||||
|
TestScenario::new("ln")
|
||||||
|
.ucmd()
|
||||||
|
.arg("-sr")
|
||||||
|
.arg(at.subdir.join("dir/file"))
|
||||||
|
.arg(at.subdir.join("dir/file-link"))
|
||||||
|
.succeeds();
|
||||||
|
|
||||||
|
ucmd.args(&["-r", "dir", "copy"]).succeeds();
|
||||||
|
assert_eq!(at.resolve_link("copy/file-link"), "file");
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue