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::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,
@ -644,11 +642,12 @@ impl Options {
attributes_only: matches.is_present(options::ATTRIBUTES_ONLY), attributes_only: matches.is_present(options::ATTRIBUTES_ONLY),
copy_contents: matches.is_present(options::COPY_CONTENTS), copy_contents: matches.is_present(options::COPY_CONTENTS),
copy_mode: CopyMode::from_matches(matches), copy_mode: CopyMode::from_matches(matches),
dereference: matches.is_present(options::DEREFERENCE),
// No dereference is set with -p, -d and --archive // 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::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), one_file_system: matches.is_present(options::ONE_FILE_SYSTEM),
parents: matches.is_present(options::PARENTS), parents: matches.is_present(options::PARENTS),
update: matches.is_present(options::UPDATE), 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()); 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() { let root_parent = if target.exists() {
root_path.parent() root_path.parent()
@ -961,18 +968,13 @@ 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 = current_dir.join(&p.path());
// 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 local_to_root_parent = match root_parent { let local_to_root_parent = match root_parent {
Some(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); 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;
@ -1223,25 +1226,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
@ -1258,6 +1246,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<()> {
@ -1394,9 +1400,9 @@ pub fn paths_refer_to_same_file(p1: &Path, p2: &Path) -> io::Result<bool> {
fn test_cp_localize_to_target() { fn test_cp_localize_to_target() {
assert!( assert!(
localize_to_target( localize_to_target(
&Path::new("a/source/"), Path::new("a/source/"),
&Path::new("a/source/c.txt"), Path::new("a/source/c.txt"),
&Path::new("target/") Path::new("target/")
) )
.unwrap() .unwrap()
== Path::new("target/c.txt") == 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::GroupIdFlag),
"-G" => path(&f, PathCondition::GroupOwns), "-G" => path(&f, PathCondition::GroupOwns),
"-h" => path(&f, PathCondition::SymLink), "-h" => path(&f, PathCondition::SymLink),
"-k" => path(&f, PathCondition::Sticky),
"-L" => path(&f, PathCondition::SymLink), "-L" => path(&f, PathCondition::SymLink),
"-O" => path(&f, PathCondition::UserOwns), "-O" => path(&f, PathCondition::UserOwns),
"-p" => path(&f, PathCondition::Fifo), "-p" => path(&f, PathCondition::Fifo),
@ -170,6 +171,7 @@ enum PathCondition {
GroupIdFlag, GroupIdFlag,
GroupOwns, GroupOwns,
SymLink, SymLink,
Sticky,
UserOwns, UserOwns,
Fifo, Fifo,
Readable, Readable,
@ -187,6 +189,7 @@ fn path(path: &OsStr, condition: PathCondition) -> bool {
const S_ISUID: u32 = 0o4000; const S_ISUID: u32 = 0o4000;
const S_ISGID: u32 = 0o2000; const S_ISGID: u32 = 0o2000;
const S_ISVTX: u32 = 0o1000;
enum Permission { enum Permission {
Read = 0o4, Read = 0o4,
@ -246,6 +249,7 @@ fn path(path: &OsStr, condition: PathCondition) -> bool {
PathCondition::GroupIdFlag => metadata.mode() & S_ISGID != 0, PathCondition::GroupIdFlag => metadata.mode() & S_ISGID != 0,
PathCondition::GroupOwns => metadata.gid() == getegid(), PathCondition::GroupOwns => metadata.gid() == getegid(),
PathCondition::SymLink => metadata.file_type().is_symlink(), PathCondition::SymLink => metadata.file_type().is_symlink(),
PathCondition::Sticky => metadata.mode() & S_ISVTX != 0,
PathCondition::UserOwns => metadata.uid() == geteuid(), PathCondition::UserOwns => metadata.uid() == geteuid(),
PathCondition::Fifo => file_type.is_fifo(), PathCondition::Fifo => file_type.is_fifo(),
PathCondition::Readable => perm(metadata, Permission::Read), PathCondition::Readable => perm(metadata, Permission::Read),
@ -275,6 +279,7 @@ fn path(path: &OsStr, condition: PathCondition) -> bool {
PathCondition::GroupIdFlag => false, PathCondition::GroupIdFlag => false,
PathCondition::GroupOwns => unimplemented!(), PathCondition::GroupOwns => unimplemented!(),
PathCondition::SymLink => false, PathCondition::SymLink => false,
PathCondition::Sticky => false,
PathCondition::UserOwns => unimplemented!(), PathCondition::UserOwns => unimplemented!(),
PathCondition::Fifo => false, PathCondition::Fifo => false,
PathCondition::Readable => false, // TODO PathCondition::Readable => false, // TODO

View file

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

View file

@ -476,6 +476,27 @@ fn test_nonexistent_file_is_not_symlink() {
.succeeds(); .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] #[test]
#[cfg(not(windows))] #[cfg(not(windows))]
fn test_file_owned_by_euid() { fn test_file_owned_by_euid() {