mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-29 12:07:46 +00:00
cp: add support for preserving links on unix.
This commit is contained in:
parent
4cb727f80b
commit
f7072b7dfe
1 changed files with 92 additions and 17 deletions
109
src/cp/cp.rs
109
src/cp/cp.rs
|
@ -167,7 +167,7 @@ pub enum CopyMode {
|
||||||
AttrOnly
|
AttrOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
pub enum Attribute {
|
pub enum Attribute {
|
||||||
#[cfg(unix)] Mode,
|
#[cfg(unix)] Mode,
|
||||||
Ownership,
|
Ownership,
|
||||||
|
@ -665,27 +665,66 @@ fn copy(sources: &[Source], target: &Target, options: &Options) -> CopyResult<()
|
||||||
let target_type = TargetType::determine(sources, target);
|
let target_type = TargetType::determine(sources, target);
|
||||||
verify_target_type(target, &target_type)?;
|
verify_target_type(target, &target_type)?;
|
||||||
|
|
||||||
|
let mut preserve_hard_links = false;
|
||||||
|
for attribute in &options.preserve_attributes {
|
||||||
|
if *attribute == Attribute::Links {
|
||||||
|
preserve_hard_links = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
|
let mut hard_links: Vec<(String, u64)> = vec![];
|
||||||
|
|
||||||
let mut non_fatal_errors = false;
|
let mut non_fatal_errors = false;
|
||||||
let mut seen_sources = HashSet::with_capacity(sources.len());
|
let mut seen_sources = HashSet::with_capacity(sources.len());
|
||||||
|
|
||||||
for source in sources {
|
for source in sources {
|
||||||
if seen_sources.contains(source) {
|
if seen_sources.contains(source) {
|
||||||
show_warning!("source '{}' specified more than once", source.display());
|
show_warning!("source '{}' specified more than once", source.display());
|
||||||
|
|
||||||
} else if let Err(error) = copy_source(source, target, &target_type, options) {
|
} else {
|
||||||
show_error!("{}", error);
|
let mut found_hard_link = false;
|
||||||
match error {
|
if preserve_hard_links {
|
||||||
Error::Skipped(_) => (),
|
#[cfg(unix)]
|
||||||
_ => non_fatal_errors = true,
|
unsafe {
|
||||||
}
|
let dest = construct_dest_path(source, target, &target_type, options)?;
|
||||||
}
|
let mut stat = mem::zeroed();
|
||||||
seen_sources.insert(source);
|
let src_path = CString::new(Path::new(&source.clone()).as_os_str().to_str().unwrap()).unwrap();
|
||||||
}
|
|
||||||
|
|
||||||
|
if libc::lstat(src_path.as_ptr(), &mut stat) < 0 {
|
||||||
|
return Err(format!("cannot stat {:?}: {}", src_path, std::io::Error::last_os_error()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let inode = stat.st_ino;
|
||||||
|
for hard_link in &hard_links {
|
||||||
|
if hard_link.1 == inode {
|
||||||
|
std::fs::hard_link(hard_link.0.clone(), dest.clone());
|
||||||
|
found_hard_link = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if stat.st_nlink > 1 && !found_hard_link {
|
||||||
|
hard_links.push((dest.clone().to_str().unwrap().to_string(), inode));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found_hard_link {
|
||||||
|
if let Err(error) = copy_source(source, target, &target_type, options) {
|
||||||
|
show_error!("{}", error);
|
||||||
|
match error {
|
||||||
|
Error::Skipped(_) => (),
|
||||||
|
_ => non_fatal_errors = true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
seen_sources.insert(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
if non_fatal_errors {
|
if non_fatal_errors {
|
||||||
Err(Error::NotAllFilesCopied)
|
return Err(Error::NotAllFilesCopied)
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -710,7 +749,6 @@ fn copy_source(source: &Source, target: &Target, target_type: &TargetType, optio
|
||||||
-> CopyResult<()>
|
-> CopyResult<()>
|
||||||
{
|
{
|
||||||
let source_path = Path::new(&source);
|
let source_path = Path::new(&source);
|
||||||
|
|
||||||
if source_path.is_dir() {
|
if source_path.is_dir() {
|
||||||
// Copy as directory
|
// Copy as directory
|
||||||
copy_directory(source, target, options)
|
copy_directory(source, target, options)
|
||||||
|
@ -740,6 +778,14 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult
|
||||||
Some(root_path.as_path())
|
Some(root_path.as_path())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
let mut hard_links: Vec<(String, u64)> = vec![];
|
||||||
|
let mut preserve_hard_links = false;
|
||||||
|
for attribute in &options.preserve_attributes {
|
||||||
|
if *attribute == Attribute::Links {
|
||||||
|
preserve_hard_links = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
for path in WalkDir::new(root) {
|
for path in WalkDir::new(root) {
|
||||||
let path = or_continue!(or_continue!(path).path().canonicalize());
|
let path = or_continue!(or_continue!(path).path().canonicalize());
|
||||||
let local_to_root_parent = match root_parent {
|
let local_to_root_parent = match root_parent {
|
||||||
|
@ -752,14 +798,43 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult
|
||||||
if path.is_dir() && !local_to_target.exists() {
|
if path.is_dir() && !local_to_target.exists() {
|
||||||
or_continue!(fs::create_dir_all(local_to_target.clone()));
|
or_continue!(fs::create_dir_all(local_to_target.clone()));
|
||||||
} else if !path.is_dir() {
|
} else if !path.is_dir() {
|
||||||
copy_file(path.as_path(), local_to_target.as_path(), options)?;
|
if preserve_hard_links {
|
||||||
|
#[cfg(unix)]
|
||||||
|
unsafe {
|
||||||
|
let mut stat = mem::zeroed();
|
||||||
|
let src_path = CString::new(path.as_os_str().to_str().unwrap()).unwrap();
|
||||||
|
|
||||||
|
if libc::lstat(src_path.as_ptr(), &mut stat) < 0 {
|
||||||
|
return Err(format!("cannot stat {:?}: {}", src_path, std::io::Error::last_os_error()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let inode = stat.st_ino;
|
||||||
|
let mut found_hard_link = false;
|
||||||
|
for hard_link in &hard_links {
|
||||||
|
if hard_link.1 == inode {
|
||||||
|
std::fs::hard_link(hard_link.0.clone(), local_to_target.as_path());
|
||||||
|
found_hard_link = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if stat.st_nlink > 1 && !found_hard_link {
|
||||||
|
hard_links.push((local_to_target.as_path().to_str().unwrap().to_string(), inode));
|
||||||
|
copy_file(path.as_path(), local_to_target.as_path(), options)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("copy");
|
||||||
|
copy_file(path.as_path(), local_to_target.as_path(), options)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl OverwriteMode {
|
impl OverwriteMode {
|
||||||
fn verify(&self, path: &Path) -> CopyResult<()> {
|
fn verify(&self, path: &Path) -> CopyResult<()> {
|
||||||
match *self {
|
match *self {
|
||||||
|
@ -797,7 +872,7 @@ fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResu
|
||||||
filetime::set_file_times(Path::new(dest), FileTime::from_last_access_time(&metadata), FileTime::from_last_modification_time(&metadata))?;
|
filetime::set_file_times(Path::new(dest), FileTime::from_last_access_time(&metadata), FileTime::from_last_modification_time(&metadata))?;
|
||||||
},
|
},
|
||||||
Attribute::Context => return Err(Error::NotImplemented("preserving context not implemented".to_string())),
|
Attribute::Context => return Err(Error::NotImplemented("preserving context not implemented".to_string())),
|
||||||
Attribute::Links => return Err(Error::NotImplemented("preserving links not implemented".to_string())),
|
Attribute::Links => {},
|
||||||
Attribute::Xattr => {
|
Attribute::Xattr => {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue