1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-27 19:17:43 +00:00

Merge branch 'master' into cp/update-options

This commit is contained in:
Terts Diepraam 2021-06-18 17:59:13 +02:00
commit fdfa44cb5c
4 changed files with 97 additions and 39 deletions

View file

@ -48,7 +48,6 @@ use std::path::{Path, PathBuf, StripPrefixError};
use std::str::FromStr;
use std::string::ToString;
use uucore::backup_control::{self, BackupMode};
use uucore::fs::resolve_relative_path;
use uucore::fs::{canonicalize, CanonicalizeMode};
use walkdir::WalkDir;
@ -198,7 +197,6 @@ pub struct Options {
copy_contents: bool,
copy_mode: CopyMode,
dereference: bool,
no_dereference: bool,
no_target_dir: bool,
one_file_system: bool,
overwrite: OverwriteMode,
@ -644,11 +642,12 @@ impl Options {
attributes_only: matches.is_present(options::ATTRIBUTES_ONLY),
copy_contents: matches.is_present(options::COPY_CONTENTS),
copy_mode: CopyMode::from_matches(matches),
dereference: matches.is_present(options::DEREFERENCE),
// No dereference is set with -p, -d and --archive
no_dereference: matches.is_present(options::NO_DEREFERENCE)
dereference: !(matches.is_present(options::NO_DEREFERENCE)
|| matches.is_present(options::NO_DEREFERENCE_PRESERVE_LINKS)
|| matches.is_present(options::ARCHIVE),
|| matches.is_present(options::ARCHIVE)
|| recursive)
|| matches.is_present(options::DEREFERENCE),
one_file_system: matches.is_present(options::ONE_FILE_SYSTEM),
parents: matches.is_present(options::PARENTS),
update: matches.is_present(options::UPDATE),
@ -940,7 +939,15 @@ fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyR
return Err(format!("omitting directory '{}'", root.display()).into());
}
let root_path = Path::new(&root).canonicalize()?;
// if no-dereference is enabled and this is a symlink, copy it as a file
if !options.dereference && fs::symlink_metadata(root).unwrap().file_type().is_symlink() {
return copy_file(root, target, options);
}
let current_dir =
env::current_dir().unwrap_or_else(|e| crash!(1, "failed to get current directory {}", e));
let root_path = current_dir.join(root);
let root_parent = if target.exists() {
root_path.parent()
@ -961,18 +968,13 @@ fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyR
#[cfg(any(windows, target_os = "redox"))]
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 is_symlink = fs::symlink_metadata(p.path())?.file_type().is_symlink();
let path = if (options.no_dereference || options.dereference) && is_symlink {
// we are dealing with a symlink. Don't follow it
match env::current_dir() {
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 path = current_dir.join(&p.path());
let local_to_root_parent = match root_parent {
Some(parent) => {
@ -995,9 +997,10 @@ fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyR
};
let local_to_target = target.join(&local_to_root_parent);
if path.is_dir() && !local_to_target.exists() {
or_continue!(fs::create_dir_all(local_to_target.clone()));
if is_symlink && !options.dereference {
copy_link(&path, &local_to_target)?;
} else if path.is_dir() && !local_to_target.exists() {
or_continue!(fs::create_dir_all(local_to_target));
} else if !path.is_dir() {
if preserve_hard_links {
let mut found_hard_link = false;
@ -1223,25 +1226,10 @@ fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()>
#[cfg(target_os = "macos")]
copy_on_write_macos(source, dest, options.reflink_mode)?;
#[cfg(target_os = "linux")]
copy_on_write_linux(source, dest, options.reflink_mode)?;
} else if options.no_dereference && fs::symlink_metadata(&source)?.file_type().is_symlink() {
// 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))?;
} else if !options.dereference && fs::symlink_metadata(&source)?.file_type().is_symlink() {
copy_link(source, dest)?;
} else if source.to_string_lossy() == "/dev/null" {
/* workaround a limitation of fs::copy
* https://github.com/rust-lang/rust/issues/79390
@ -1258,6 +1246,24 @@ fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()>
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.
#[cfg(target_os = "linux")]
fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyResult<()> {
@ -1394,9 +1400,9 @@ pub fn paths_refer_to_same_file(p1: &Path, p2: &Path) -> io::Result<bool> {
fn test_cp_localize_to_target() {
assert!(
localize_to_target(
&Path::new("a/source/"),
&Path::new("a/source/c.txt"),
&Path::new("target/")
Path::new("a/source/"),
Path::new("a/source/c.txt"),
Path::new("target/")
)
.unwrap()
== Path::new("target/c.txt")

View file

@ -98,6 +98,7 @@ fn eval(stack: &mut Vec<Symbol>) -> Result<bool, String> {
"-g" => path(&f, PathCondition::GroupIdFlag),
"-G" => path(&f, PathCondition::GroupOwns),
"-h" => path(&f, PathCondition::SymLink),
"-k" => path(&f, PathCondition::Sticky),
"-L" => path(&f, PathCondition::SymLink),
"-O" => path(&f, PathCondition::UserOwns),
"-p" => path(&f, PathCondition::Fifo),
@ -170,6 +171,7 @@ enum PathCondition {
GroupIdFlag,
GroupOwns,
SymLink,
Sticky,
UserOwns,
Fifo,
Readable,
@ -187,6 +189,7 @@ fn path(path: &OsStr, condition: PathCondition) -> bool {
const S_ISUID: u32 = 0o4000;
const S_ISGID: u32 = 0o2000;
const S_ISVTX: u32 = 0o1000;
enum Permission {
Read = 0o4,
@ -246,6 +249,7 @@ fn path(path: &OsStr, condition: PathCondition) -> bool {
PathCondition::GroupIdFlag => metadata.mode() & S_ISGID != 0,
PathCondition::GroupOwns => metadata.gid() == getegid(),
PathCondition::SymLink => metadata.file_type().is_symlink(),
PathCondition::Sticky => metadata.mode() & S_ISVTX != 0,
PathCondition::UserOwns => metadata.uid() == geteuid(),
PathCondition::Fifo => file_type.is_fifo(),
PathCondition::Readable => perm(metadata, Permission::Read),
@ -275,6 +279,7 @@ fn path(path: &OsStr, condition: PathCondition) -> bool {
PathCondition::GroupIdFlag => false,
PathCondition::GroupOwns => unimplemented!(),
PathCondition::SymLink => false,
PathCondition::Sticky => false,
PathCondition::UserOwns => unimplemented!(),
PathCondition::Fifo => false,
PathCondition::Readable => false, // TODO

View file

@ -1299,3 +1299,29 @@ fn test_closes_file_descriptors() {
.with_limit(Resource::NOFILE, 9, 9)
.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");
}

View file

@ -476,6 +476,27 @@ fn test_nonexistent_file_is_not_symlink() {
.succeeds();
}
#[test]
#[cfg(not(windows))] // Windows has no concept of sticky bit
fn test_file_is_sticky() {
let scenario = TestScenario::new(util_name!());
let mut ucmd = scenario.ucmd();
let mut chmod = scenario.cmd("chmod");
scenario.fixtures.touch("sticky_file");
chmod.args(&["+t", "sticky_file"]).succeeds();
ucmd.args(&["-k", "sticky_file"]).succeeds();
}
#[test]
fn test_file_is_not_sticky() {
new_ucmd!()
.args(&["-k", "regular_file"])
.run()
.status_code(1);
}
#[test]
#[cfg(not(windows))]
fn test_file_owned_by_euid() {