From 48ae9b64ba62c142c1edf22c38840a97aa65ecc9 Mon Sep 17 00:00:00 2001 From: Matt8898 Date: Fri, 4 Aug 2017 13:29:57 +0200 Subject: [PATCH 1/8] cp: add support for --attributes-only. --- src/cp/cp.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/cp/cp.rs b/src/cp/cp.rs index 98f47fe51..3ba76c935 100644 --- a/src/cp/cp.rs +++ b/src/cp/cp.rs @@ -157,6 +157,7 @@ pub enum CopyMode { Sparse, Copy, Update, + AttrOnly } #[derive(Clone)] @@ -343,6 +344,11 @@ pub fn uumain(args: Vec) -> i32 { .takes_value(true) .value_name("WHEN") .help("control clone/CoW copies. See below")) + .arg(Arg::with_name(OPT_ATTRIBUTES_ONLY) + .long(OPT_ATTRIBUTES_ONLY) + .conflicts_with(OPT_COPY_CONTENTS) + .overrides_with(OPT_REFLINK) + .help("Don't copy the file data, just the attributes")) // TODO: implement the following args .arg(Arg::with_name(OPT_ARCHIVE) @@ -350,11 +356,6 @@ pub fn uumain(args: Vec) -> i32 { .long(OPT_ARCHIVE) .conflicts_with_all(&[OPT_PRESERVE_DEFUALT_ATTRIBUTES, OPT_PRESERVE, OPT_NO_PRESERVE]) .help("NotImplemented: same as -dR --preserve=all")) - .arg(Arg::with_name(OPT_ATTRIBUTES_ONLY) - .long(OPT_ATTRIBUTES_ONLY) - .conflicts_with(OPT_COPY_CONTENTS) - .overrides_with(OPT_REFLINK) - .help("NotImplemented: don't copy the file data, just the attributes")) .arg(Arg::with_name(OPT_COPY_CONTENTS) .long(OPT_COPY_CONTENTS) .conflicts_with(OPT_ATTRIBUTES_ONLY) @@ -481,6 +482,8 @@ impl CopyMode { CopyMode::Sparse } else if matches.is_present(OPT_UPDATE) { CopyMode::Update + } else if matches.is_present(OPT_ATTRIBUTES_ONLY) { + CopyMode::AttrOnly } else { CopyMode::Copy } @@ -508,7 +511,6 @@ impl Options { fn from_matches(matches: &ArgMatches) -> CopyResult { let not_implemented_opts = vec![ OPT_ARCHIVE, - OPT_ATTRIBUTES_ONLY, OPT_COPY_CONTENTS, OPT_NO_DEREFERENCE_PRESERVE_LINKS, OPT_DEREFERENCE, @@ -888,6 +890,14 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { } else { copy_helper(source, dest, options)?; } + }, + CopyMode::AttrOnly => { + let dst_file = OpenOptions::new() + .write(true) + .truncate(false) + .create(true) + .open(dest) + .unwrap(); } }; From 03432db9516bae53fb95551bc64e685da81613af Mon Sep 17 00:00:00 2001 From: Matt8898 Date: Fri, 4 Aug 2017 14:08:16 +0200 Subject: [PATCH 2/8] Added support for preserving timestamps. --- src/cp/cp.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/cp/cp.rs b/src/cp/cp.rs index 3ba76c935..dfef452a2 100644 --- a/src/cp/cp.rs +++ b/src/cp/cp.rs @@ -10,6 +10,7 @@ * that was distributed with this source code. */ +extern crate libc; extern crate clap; extern crate walkdir; #[cfg(target_os = "linux")] @@ -17,6 +18,8 @@ extern crate walkdir; #[macro_use] extern crate uucore; #[macro_use] extern crate quick_error; +use std::mem; +use std::ffi::CString; use clap::{Arg, App, ArgMatches}; use quick_error::ResultExt; use std::collections::HashSet; @@ -516,7 +519,6 @@ impl Options { OPT_DEREFERENCE, OPT_NO_DEREFERENCE, OPT_PRESERVE_DEFUALT_ATTRIBUTES, - OPT_PRESERVE, OPT_NO_PRESERVE, OPT_PARENTS, OPT_SPARSE, @@ -787,7 +789,21 @@ fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResu let metadata = fs::metadata(source).context(context)?; fs::set_permissions(dest, metadata.permissions()).context(context)?; }, - Attribute::Timestamps => return Err(Error::NotImplemented("preserving timestamp not implemented".to_string())), + Attribute::Timestamps => { + let meta = fs::metadata(source)?; + let modified = meta.modified()?; + let accessed = meta.accessed()?; + let src_path = CString::new(source.as_os_str().to_str().unwrap()).unwrap(); + let dest_path = CString::new(dest.as_os_str().to_str().unwrap()).unwrap(); + unsafe { + let mut stat: libc::stat = mem::zeroed(); + libc::stat(src_path.as_ptr(), &mut stat); + libc::utime(dest_path.as_ptr(), &libc::utimbuf{ + actime: stat.st_atime, + modtime: stat.st_mtime + }); + } + }, 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::Xattr => return Err(Error::NotImplemented("preserving xattr not implemented".to_string())), @@ -892,7 +908,7 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { } }, CopyMode::AttrOnly => { - let dst_file = OpenOptions::new() + OpenOptions::new() .write(true) .truncate(false) .create(true) From 38dd8c58366ad2c68e7f8e09b377031fa72c5b9a Mon Sep 17 00:00:00 2001 From: Matt8898 Date: Tue, 15 Aug 2017 11:42:50 +0200 Subject: [PATCH 3/8] cp: use filetime to set timestamps. --- src/cp/Cargo.toml | 1 + src/cp/cp.rs | 17 ++++------------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/cp/Cargo.toml b/src/cp/Cargo.toml index e12413b66..38ca8b40c 100644 --- a/src/cp/Cargo.toml +++ b/src/cp/Cargo.toml @@ -17,6 +17,7 @@ walkdir = "1.0.7" clap = "2.20.0" quick-error = "1.1.0" uucore = { path="../uucore" } +filetime = "0.1" [target.'cfg(target_os = "linux")'.dependencies] ioctl-sys = "0.5.2" diff --git a/src/cp/cp.rs b/src/cp/cp.rs index dfef452a2..9b188750a 100644 --- a/src/cp/cp.rs +++ b/src/cp/cp.rs @@ -13,6 +13,8 @@ extern crate libc; extern crate clap; extern crate walkdir; +extern crate filetime; +use filetime::FileTime; #[cfg(target_os = "linux")] #[macro_use] extern crate ioctl_sys; #[macro_use] extern crate uucore; @@ -790,19 +792,8 @@ fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResu fs::set_permissions(dest, metadata.permissions()).context(context)?; }, Attribute::Timestamps => { - let meta = fs::metadata(source)?; - let modified = meta.modified()?; - let accessed = meta.accessed()?; - let src_path = CString::new(source.as_os_str().to_str().unwrap()).unwrap(); - let dest_path = CString::new(dest.as_os_str().to_str().unwrap()).unwrap(); - unsafe { - let mut stat: libc::stat = mem::zeroed(); - libc::stat(src_path.as_ptr(), &mut stat); - libc::utime(dest_path.as_ptr(), &libc::utimbuf{ - actime: stat.st_atime, - modtime: stat.st_mtime - }); - } + let metadata = fs::metadata(source)?; + 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::Links => return Err(Error::NotImplemented("preserving links not implemented".to_string())), From 6d3e9eabe414c72da13ddb040a6fe2c8dd97ca1d Mon Sep 17 00:00:00 2001 From: Matt8898 Date: Fri, 18 Aug 2017 10:05:41 +0200 Subject: [PATCH 4/8] cp: add support for --preserve. --- src/cp/cp.rs | 49 ++++++++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/src/cp/cp.rs b/src/cp/cp.rs index 9b188750a..b924b2604 100644 --- a/src/cp/cp.rs +++ b/src/cp/cp.rs @@ -237,7 +237,7 @@ static OPT_ONE_FILE_SYSTEM: &str = "one-file-system"; static OPT_PARENTS: &str = "parents"; static OPT_PATHS: &str = "paths"; static OPT_PRESERVE: &str = "preserve"; -static OPT_PRESERVE_DEFUALT_ATTRIBUTES: &str = "preserve-default-attributes"; +static OPT_PRESERVE_DEFAULT_ATTRIBUTES: &str = "preserve-default-attributes"; static OPT_RECURSIVE: &str = "recursive"; static OPT_RECURSIVE_ALIAS: &str = "recursive_alias"; static OPT_REFLINK: &str = "reflink"; @@ -354,12 +354,33 @@ pub fn uumain(args: Vec) -> i32 { .conflicts_with(OPT_COPY_CONTENTS) .overrides_with(OPT_REFLINK) .help("Don't copy the file data, just the attributes")) + .arg(Arg::with_name(OPT_PRESERVE) + .long(OPT_PRESERVE) + .takes_value(true) + .multiple(true) + .use_delimiter(true) + .possible_values(PRESERVABLE_ATTRIBUTES) + .value_name("ATTR_LIST") + .conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_NO_PRESERVE, OPT_ARCHIVE]) + .help("Preserve the specified attributes (default: mode(unix only),ownership,timestamps),\ + if possible additional attributes: context, links, xattr, all")) + .arg(Arg::with_name(OPT_PRESERVE_DEFAULT_ATTRIBUTES) + .short("-p") + .long(OPT_PRESERVE_DEFAULT_ATTRIBUTES) + .conflicts_with_all(&[OPT_PRESERVE, OPT_NO_PRESERVE, OPT_ARCHIVE]) + .help("same as --preserve=mode(unix only),ownership,timestamps")) + .arg(Arg::with_name(OPT_NO_PRESERVE) + .long(OPT_NO_PRESERVE) + .takes_value(true) + .value_name("ATTR_LIST") + .conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_PRESERVE, OPT_ARCHIVE]) + .help("don't preserve the specified attributes")) // TODO: implement the following args .arg(Arg::with_name(OPT_ARCHIVE) .short("a") .long(OPT_ARCHIVE) - .conflicts_with_all(&[OPT_PRESERVE_DEFUALT_ATTRIBUTES, OPT_PRESERVE, OPT_NO_PRESERVE]) + .conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_PRESERVE, OPT_NO_PRESERVE]) .help("NotImplemented: same as -dR --preserve=all")) .arg(Arg::with_name(OPT_COPY_CONTENTS) .long(OPT_COPY_CONTENTS) @@ -378,26 +399,6 @@ pub fn uumain(args: Vec) -> i32 { .long(OPT_NO_DEREFERENCE) .conflicts_with(OPT_DEREFERENCE) .help("NotImplemented: never follow symbolic links in SOURCE")) - .arg(Arg::with_name(OPT_PRESERVE_DEFUALT_ATTRIBUTES) - .short("-p") - .long(OPT_PRESERVE_DEFUALT_ATTRIBUTES) - .conflicts_with_all(&[OPT_PRESERVE, OPT_NO_PRESERVE, OPT_ARCHIVE]) - .help("NotImplemented: same as --preserve=mode(unix only),ownership,timestamps")) - .arg(Arg::with_name(OPT_PRESERVE) - .long(OPT_PRESERVE) - .takes_value(true) - .multiple(true) - .possible_values(PRESERVABLE_ATTRIBUTES) - .value_name("ATTR_LIST") - .conflicts_with_all(&[OPT_PRESERVE_DEFUALT_ATTRIBUTES, OPT_NO_PRESERVE, OPT_ARCHIVE]) - .help("NotImplemented: preserve the specified attributes (default: mode(unix only),ownership,timestamps),\ - if possible additional attributes: context, links, xattr, all")) - .arg(Arg::with_name(OPT_NO_PRESERVE) - .long(OPT_NO_PRESERVE) - .takes_value(true) - .value_name("ATTR_LIST") - .conflicts_with_all(&[OPT_PRESERVE_DEFUALT_ATTRIBUTES, OPT_PRESERVE, OPT_ARCHIVE]) - .help("NotImplemented: don't preserve the specified attributes")) .arg(Arg::with_name(OPT_PARENTS) .long(OPT_PARENTS) .help("NotImplemented: use full source file name under DIRECTORY")) @@ -520,8 +521,6 @@ impl Options { OPT_NO_DEREFERENCE_PRESERVE_LINKS, OPT_DEREFERENCE, OPT_NO_DEREFERENCE, - OPT_PRESERVE_DEFUALT_ATTRIBUTES, - OPT_NO_PRESERVE, OPT_PARENTS, OPT_SPARSE, OPT_STRIP_TRAILING_SLASHES, @@ -559,7 +558,7 @@ impl Options { attributes } } - } else if matches.is_present(OPT_PRESERVE_DEFUALT_ATTRIBUTES) { + } else if matches.is_present(OPT_PRESERVE_DEFAULT_ATTRIBUTES) { DEFAULT_ATTRIBUTES.to_vec() } else { vec![] From 4cb727f80b70936f52ad91a8158bad091578b630 Mon Sep 17 00:00:00 2001 From: Matt8898 Date: Fri, 18 Aug 2017 10:44:54 +0200 Subject: [PATCH 5/8] cp: Add support for preserving xattrs. --- src/cp/Cargo.toml | 3 +++ src/cp/cp.rs | 18 +++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/cp/Cargo.toml b/src/cp/Cargo.toml index 38ca8b40c..30ffda4b6 100644 --- a/src/cp/Cargo.toml +++ b/src/cp/Cargo.toml @@ -22,6 +22,9 @@ filetime = "0.1" [target.'cfg(target_os = "linux")'.dependencies] ioctl-sys = "0.5.2" +[target.'cfg(unix)'.dependencies] +xattr="0.2.1" + [[bin]] name = "cp" path = "main.rs" diff --git a/src/cp/cp.rs b/src/cp/cp.rs index b924b2604..6a8ce8b17 100644 --- a/src/cp/cp.rs +++ b/src/cp/cp.rs @@ -19,6 +19,8 @@ use filetime::FileTime; #[macro_use] extern crate ioctl_sys; #[macro_use] extern crate uucore; #[macro_use] extern crate quick_error; +#[cfg(unix)] +extern crate xattr; use std::mem; use std::ffi::CString; @@ -796,7 +798,21 @@ fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResu }, 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::Xattr => return Err(Error::NotImplemented("preserving xattr not implemented".to_string())), + Attribute::Xattr => { + #[cfg(unix)] + { + let xattrs = xattr::list(source)?; + for attr in xattrs { + if let Some(attr_value) = xattr::get(source, attr.clone())? { + xattr::set(dest, attr, &attr_value[..]); + } + } + } + #[cfg(not(unix))] + { + return Err(format!("XAttrs are only supported on unix.").into()); + } + }, Attribute::All => return Err(Error::NotImplemented("preserving a not implemented".to_string())), }) } From f7072b7dfe55d93e8d7396abd5d8cf0c982a50a2 Mon Sep 17 00:00:00 2001 From: Matt8898 Date: Sat, 19 Aug 2017 13:35:53 +0200 Subject: [PATCH 6/8] cp: add support for preserving links on unix. --- src/cp/cp.rs | 109 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 92 insertions(+), 17 deletions(-) diff --git a/src/cp/cp.rs b/src/cp/cp.rs index 6a8ce8b17..cef6812ff 100644 --- a/src/cp/cp.rs +++ b/src/cp/cp.rs @@ -167,7 +167,7 @@ pub enum CopyMode { AttrOnly } -#[derive(Clone)] +#[derive(Clone, Eq, PartialEq)] pub enum Attribute { #[cfg(unix)] Mode, Ownership, @@ -665,27 +665,66 @@ fn copy(sources: &[Source], target: &Target, options: &Options) -> CopyResult<() let target_type = TargetType::determine(sources, target); 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 seen_sources = HashSet::with_capacity(sources.len()); - for source in sources { if seen_sources.contains(source) { show_warning!("source '{}' specified more than once", source.display()); - } else 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); - } + } else { + let mut found_hard_link = false; + if preserve_hard_links { + #[cfg(unix)] + unsafe { + let dest = construct_dest_path(source, target, &target_type, options)?; + let mut stat = mem::zeroed(); + 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 { - Err(Error::NotAllFilesCopied) + return Err(Error::NotAllFilesCopied) } else { - Ok(()) + return Ok(()) } } @@ -710,7 +749,6 @@ fn copy_source(source: &Source, target: &Target, target_type: &TargetType, optio -> CopyResult<()> { let source_path = Path::new(&source); - if source_path.is_dir() { // Copy as directory copy_directory(source, target, options) @@ -740,6 +778,14 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult 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) { let path = or_continue!(or_continue!(path).path().canonicalize()); 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() { or_continue!(fs::create_dir_all(local_to_target.clone())); } 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(()) } - impl OverwriteMode { fn verify(&self, path: &Path) -> CopyResult<()> { 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))?; }, 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 => { #[cfg(unix)] { From 6476f6e616a3c206926ffc8c90a3b63e5c4ce23d Mon Sep 17 00:00:00 2001 From: Matteo Semenzato Date: Sun, 20 Aug 2017 16:55:36 +0200 Subject: [PATCH 7/8] cp: add support for preserving links on windows. --- src/cp/Cargo.toml | 4 +++ src/cp/cp.rs | 71 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/cp/Cargo.toml b/src/cp/Cargo.toml index 30ffda4b6..0057d9330 100644 --- a/src/cp/Cargo.toml +++ b/src/cp/Cargo.toml @@ -22,6 +22,10 @@ filetime = "0.1" [target.'cfg(target_os = "linux")'.dependencies] ioctl-sys = "0.5.2" +[target.'cfg(target_os = "windows")'.dependencies] +kernel32-sys = "*" +winapi = "*" + [target.'cfg(unix)'.dependencies] xattr="0.2.1" diff --git a/src/cp/cp.rs b/src/cp/cp.rs index cef6812ff..303b41019 100644 --- a/src/cp/cp.rs +++ b/src/cp/cp.rs @@ -22,6 +22,18 @@ use filetime::FileTime; #[cfg(unix)] extern crate xattr; +#[cfg(windows)] +use std::os::windows::io::AsRawHandle; + +#[cfg(windows)] +extern crate kernel32; +#[cfg(windows)] +use kernel32::GetFileInformationByHandle; +#[cfg(windows)] +use kernel32::CreateFile2; +#[cfg(windows)] +extern crate winapi; + use std::mem; use std::ffi::CString; use clap::{Arg, App, ArgMatches}; @@ -673,6 +685,9 @@ fn copy(sources: &[Source], target: &Target, options: &Options) -> CopyResult<() } #[cfg(unix)] let mut hard_links: Vec<(String, u64)> = vec![]; + + #[cfg(windows)] + let mut hard_links: Vec<(String, (u32, u32))> = vec![]; let mut non_fatal_errors = false; let mut seen_sources = HashSet::with_capacity(sources.len()); @@ -706,7 +721,32 @@ fn copy(sources: &[Source], target: &Target, options: &Options) -> CopyResult<() } #[cfg(windows)] { + unsafe { + if !source.is_dir() { + let dest = construct_dest_path(source, target, &target_type, options)?; + let handle = CreateFile2(CString::new(Path::new(&source.clone()).as_os_str().to_str().unwrap()).unwrap().as_ptr() as *const u16, + winapi::winnt::GENERIC_READ, + winapi::winnt::FILE_SHARE_READ, + 0, + std::ptr::null_mut()); + let file_info = std::mem::uninitialized(); + if GetFileInformationByHandle(handle, file_info) != 0 { + return Err(format!("cannot get file information {:?}: {}", source, std::io::Error::last_os_error()).into()); + } + let file_index = ((*file_info).nFileIndexHigh, (*file_info).nFileIndexLow); + for hard_link in &hard_links { + if (hard_link.1).0 == file_index.0 && (hard_link.1).1 == file_index.1 { + std::fs::hard_link(hard_link.0.clone(), dest.clone()).unwrap(); + found_hard_link = true; + } + } + if (((*file_info).nNumberOfLinks) > 1u32) && !found_hard_link { + println!("{}", (*file_info).nNumberOfLinks); + hard_links.push((dest.clone().to_str().unwrap().to_string(), file_index)); + } + } + } } } if !found_hard_link { @@ -786,6 +826,10 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult preserve_hard_links = true; } } + + #[cfg(windows)] + let mut hard_links: Vec<(String, (u32, u32))> = vec![]; + for path in WalkDir::new(root) { let path = or_continue!(or_continue!(path).path().canonicalize()); let local_to_root_parent = match root_parent { @@ -819,14 +863,39 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult 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)?; + } else { + copy_file(path.as_path(), local_to_target.as_path(), options)?; } } #[cfg(windows)] { + unsafe { + let mut found_hard_link = false; + let src_path = CString::new(path.as_os_str().to_str().unwrap()).unwrap(); + let handle = File::open(path.clone()).unwrap().as_raw_handle(); + let file_info = std::mem::uninitialized(); + let handle = File::open(path.clone()).unwrap().as_raw_handle(); + if GetFileInformationByHandle(handle, file_info) != 0 { + return Err(format!("cannot get file information {:?}: {}", src_path, std::io::Error::last_os_error()).into()); + } + + let file_index = ((*file_info).nFileIndexHigh, (*file_info).nFileIndexLow); + for hard_link in &hard_links { + if (hard_link.1).0 == file_index.0 && (hard_link.1).1 == file_index.1 { + std::fs::hard_link(hard_link.0.clone(), local_to_target.as_path()).unwrap(); + found_hard_link = true; + } + } + if (*file_info).nNumberOfLinks > 1u32 && !found_hard_link { + hard_links.push((local_to_target.as_path().to_str().unwrap().to_string(), file_index)); + copy_file(path.as_path(), local_to_target.as_path(), options)?; + } else { + copy_file(path.as_path(), local_to_target.as_path(), options)?; + } + } } } else { - println!("copy"); copy_file(path.as_path(), local_to_target.as_path(), options)?; } } From e33de238ca64a2320b6fd02e23ab377609fc64c4 Mon Sep 17 00:00:00 2001 From: Matt8898 Date: Wed, 23 Aug 2017 16:01:32 +0200 Subject: [PATCH 8/8] cp: add support for --preserve=all. --- src/cp/cp.rs | 193 +++++++++++++++++++++------------------------------ 1 file changed, 79 insertions(+), 114 deletions(-) diff --git a/src/cp/cp.rs b/src/cp/cp.rs index 303b41019..1730ad43b 100644 --- a/src/cp/cp.rs +++ b/src/cp/cp.rs @@ -14,7 +14,6 @@ extern crate libc; extern crate clap; extern crate walkdir; extern crate filetime; -use filetime::FileTime; #[cfg(target_os = "linux")] #[macro_use] extern crate ioctl_sys; #[macro_use] extern crate uucore; @@ -49,6 +48,10 @@ use walkdir::WalkDir; #[cfg(target_os = "linux")] use std::os::unix::io::IntoRawFd; use std::fs::File; use std::fs::OpenOptions; +use filetime::FileTime; + +#[cfg(target_os = "linux")] +use libc::{c_int, c_char}; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; @@ -187,7 +190,6 @@ pub enum Attribute { Context, Links, Xattr, - All, } /// Re-usable, extensible copy options @@ -521,7 +523,6 @@ impl FromStr for Attribute { "context" => Attribute::Context, "links" => Attribute::Links, "xattr" => Attribute::Xattr, - "all" => Attribute::All, _ => return Err(Error::InvalidArgument(format!("invalid attribute '{}'", value))) }) } @@ -567,7 +568,18 @@ impl Options { Some(attribute_strs) => { let mut attributes = Vec::new(); for attribute_str in attribute_strs { - attributes.push(Attribute::from_str(attribute_str)?); + if attribute_str == "all" { + #[cfg(unix)] + attributes.push(Attribute::Mode); + attributes.push(Attribute::Ownership); + attributes.push(Attribute::Timestamps); + attributes.push(Attribute::Context); + attributes.push(Attribute::Xattr); + attributes.push(Attribute::Links); + break; + } else { + attributes.push(Attribute::from_str(attribute_str)?); + } } attributes } @@ -577,6 +589,7 @@ impl Options { } else { vec![] }; + let options = Options { attributes_only: matches.is_present(OPT_ATTRIBUTES_ONLY), copy_contents: matches.is_present(OPT_COPY_CONTENTS), @@ -617,7 +630,6 @@ impl Options { } } - impl TargetType { /// Return TargetType required for `target`. /// @@ -665,6 +677,49 @@ fn parse_path_args(path_args: &[String], options: &Options) -> CopyResult<(Vec, source: &std::path::PathBuf, dest: std::path::PathBuf, found_hard_link: &mut bool) -> CopyResult<()> { + if !source.is_dir() { + unsafe { + let src_path = CString::new(source.as_os_str().to_str().unwrap()).unwrap(); + let mut inode: u64 = 0; + let mut nlinks = 0; + #[cfg(unix)] + { + let mut stat = mem::zeroed(); + if libc::lstat(src_path.as_ptr(), &mut stat) < 0 { + return Err(format!("cannot stat {:?}: {}", src_path, std::io::Error::last_os_error()).into()); + } + inode = stat.st_ino; + nlinks = stat.st_nlink; + } + #[cfg(windows)] + { + let mut stat = mem::uninitialized(); + let handle = CreateFile2(src_path.as_ptr() as *const u16, + winapi::winnt::GENERIC_READ, + winapi::winnt::FILE_SHARE_READ, + 0, + std::ptr::null_mut()); + if GetFileInformationByHandle(handle, stat) != 0 { + return Err(format!("cannot get file information {:?}: {}", source, std::io::Error::last_os_error()).into()); + } + inode = (((*stat).nFileIndexHigh as u64) << 32 | (*stat).nFileIndexLow as u64); + nlinks = (*stat).nNumberOfLinks; + } + + for hard_link in hard_links.iter() { + if hard_link.1 == inode { + std::fs::hard_link(hard_link.0.clone(), dest.clone()).unwrap(); + *found_hard_link = true; + } + } + if !(*found_hard_link) && nlinks > 1 { + hard_links.push((dest.clone().to_str().unwrap().to_string(), inode)); + } + } + } + Ok(()) +} /// Copy all `sources` to `target`. Returns an /// `Err(Error::NotAllFilesCopied)` if at least one non-fatal error was @@ -683,72 +738,21 @@ fn copy(sources: &[Source], target: &Target, options: &Options) -> CopyResult<() preserve_hard_links = true; } } - #[cfg(unix)] + let mut hard_links: Vec<(String, u64)> = vec![]; - - #[cfg(windows)] - let mut hard_links: Vec<(String, (u32, u32))> = vec![]; let mut non_fatal_errors = false; let mut seen_sources = HashSet::with_capacity(sources.len()); for source in sources { if seen_sources.contains(source) { show_warning!("source '{}' specified more than once", source.display()); - } else { let mut found_hard_link = false; if preserve_hard_links { - #[cfg(unix)] - unsafe { let dest = construct_dest_path(source, target, &target_type, options)?; - let mut stat = mem::zeroed(); 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)] - { - unsafe { - if !source.is_dir() { - let dest = construct_dest_path(source, target, &target_type, options)?; - let handle = CreateFile2(CString::new(Path::new(&source.clone()).as_os_str().to_str().unwrap()).unwrap().as_ptr() as *const u16, - winapi::winnt::GENERIC_READ, - winapi::winnt::FILE_SHARE_READ, - 0, - std::ptr::null_mut()); - let file_info = std::mem::uninitialized(); - if GetFileInformationByHandle(handle, file_info) != 0 { - return Err(format!("cannot get file information {:?}: {}", source, std::io::Error::last_os_error()).into()); - } - - let file_index = ((*file_info).nFileIndexHigh, (*file_info).nFileIndexLow); - for hard_link in &hard_links { - if (hard_link.1).0 == file_index.0 && (hard_link.1).1 == file_index.1 { - std::fs::hard_link(hard_link.0.clone(), dest.clone()).unwrap(); - found_hard_link = true; - } - } - if (((*file_info).nNumberOfLinks) > 1u32) && !found_hard_link { - println!("{}", (*file_info).nNumberOfLinks); - hard_links.push((dest.clone().to_str().unwrap().to_string(), file_index)); - } - } - } - } - } + preserve_hardlinks(&mut hard_links, source, dest, &mut found_hard_link).unwrap(); + } if !found_hard_link { if let Err(error) = copy_source(source, target, &target_type, options) { show_error!("{}", error); @@ -762,9 +766,9 @@ fn copy(sources: &[Source], target: &Target, options: &Options) -> CopyResult<() } } if non_fatal_errors { - return Err(Error::NotAllFilesCopied) + Err(Error::NotAllFilesCopied) } else { - return Ok(()) + Ok(()) } } @@ -828,7 +832,7 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult } #[cfg(windows)] - let mut hard_links: Vec<(String, (u32, u32))> = vec![]; + let mut hard_links: Vec<(String, u64)> = vec![]; for path in WalkDir::new(root) { let path = or_continue!(or_continue!(path).path().canonicalize()); @@ -843,57 +847,12 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult or_continue!(fs::create_dir_all(local_to_target.clone())); } else if !path.is_dir() { 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)); + let mut found_hard_link = false; + let source = path.to_path_buf(); + let dest = local_to_target.as_path().to_path_buf(); + preserve_hardlinks(&mut hard_links, &source, dest, &mut found_hard_link).unwrap(); + if !found_hard_link { copy_file(path.as_path(), local_to_target.as_path(), options)?; - } else { - copy_file(path.as_path(), local_to_target.as_path(), options)?; - } - } - #[cfg(windows)] - { - unsafe { - let mut found_hard_link = false; - let src_path = CString::new(path.as_os_str().to_str().unwrap()).unwrap(); - let handle = File::open(path.clone()).unwrap().as_raw_handle(); - let file_info = std::mem::uninitialized(); - - let handle = File::open(path.clone()).unwrap().as_raw_handle(); - if GetFileInformationByHandle(handle, file_info) != 0 { - return Err(format!("cannot get file information {:?}: {}", src_path, std::io::Error::last_os_error()).into()); - } - - let file_index = ((*file_info).nFileIndexHigh, (*file_info).nFileIndexLow); - for hard_link in &hard_links { - if (hard_link.1).0 == file_index.0 && (hard_link.1).1 == file_index.1 { - std::fs::hard_link(hard_link.0.clone(), local_to_target.as_path()).unwrap(); - found_hard_link = true; - } - } - if (*file_info).nNumberOfLinks > 1u32 && !found_hard_link { - hard_links.push((local_to_target.as_path().to_str().unwrap().to_string(), file_index)); - copy_file(path.as_path(), local_to_target.as_path(), options)?; - } else { - copy_file(path.as_path(), local_to_target.as_path(), options)?; - } - } } } else { copy_file(path.as_path(), local_to_target.as_path(), options)?; @@ -940,7 +899,7 @@ fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResu let metadata = fs::metadata(source)?; 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 => {}, Attribute::Links => {}, Attribute::Xattr => { #[cfg(unix)] @@ -957,7 +916,6 @@ fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResu return Err(format!("XAttrs are only supported on unix.").into()); } }, - Attribute::All => return Err(Error::NotImplemented("preserving a not implemented".to_string())), }) } @@ -1030,6 +988,13 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { println!("{}", context_for(source, dest)); } + let mut preserve_context = false; + for attribute in &options.preserve_attributes { + if *attribute == Attribute::Context { + preserve_context = true; + } + } + match options.copy_mode { CopyMode::Link => { fs::hard_link(source, dest).context(&*context_for(source, dest))?;