1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-29 12:07:46 +00:00

Merge pull request #1056 from Matt8898/master

cp: add support for --reflink.
This commit is contained in:
Alex Lyon 2017-07-28 23:34:47 -07:00 committed by GitHub
commit c827795d17
2 changed files with 97 additions and 14 deletions

View file

@ -18,6 +18,9 @@ clap = "2.20.0"
quick-error = "1.1.0" quick-error = "1.1.0"
uucore = { path="../uucore" } uucore = { path="../uucore" }
[target.'cfg(target_os = "linux")'.dependencies]
ioctl-sys = "0.5.2"
[[bin]] [[bin]]
name = "cp" name = "cp"
path = "main.rs" path = "main.rs"

View file

@ -12,6 +12,8 @@
extern crate clap; extern crate clap;
extern crate walkdir; extern crate walkdir;
#[cfg(target_os = "linux")]
#[macro_use] extern crate ioctl_sys;
#[macro_use] extern crate uucore; #[macro_use] extern crate uucore;
#[macro_use] extern crate quick_error; #[macro_use] extern crate quick_error;
@ -25,9 +27,14 @@ use std::path::{Path, PathBuf, StripPrefixError};
use std::str::FromStr; use std::str::FromStr;
use uucore::fs::{canonicalize, CanonicalizeMode}; use uucore::fs::{canonicalize, CanonicalizeMode};
use walkdir::WalkDir; use walkdir::WalkDir;
#[cfg(target_os = "linux")] use std::os::unix::io::IntoRawFd;
use std::fs::File;
use std::fs::OpenOptions;
#[cfg(unix)] use std::os::unix::fs::PermissionsExt; #[cfg(unix)] use std::os::unix::fs::PermissionsExt;
#[cfg(target_os = "linux")] ioctl!(write ficlone with 0x94, 9; std::os::raw::c_int);
quick_error! { quick_error! {
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
@ -125,6 +132,11 @@ pub enum OverwriteMode {
NoClobber, NoClobber,
} }
#[derive (Clone, Eq, PartialEq)]
pub enum ReflinkMode {
Always, Auto, Never
}
/// Specifies the expected file type of copy target /// Specifies the expected file type of copy target
pub enum TargetType { pub enum TargetType {
Directory, Directory,
@ -170,6 +182,8 @@ pub struct Options {
one_file_system: bool, one_file_system: bool,
overwrite: OverwriteMode, overwrite: OverwriteMode,
parents: bool, parents: bool,
reflink: bool,
reflink_mode: ReflinkMode,
preserve_attributes: Vec<Attribute>, preserve_attributes: Vec<Attribute>,
recursive: bool, recursive: bool,
backup_suffix: String, backup_suffix: String,
@ -273,6 +287,7 @@ pub fn uumain(args: Vec<String>) -> i32 {
.arg(Arg::with_name(OPT_LINK) .arg(Arg::with_name(OPT_LINK)
.short("l") .short("l")
.long(OPT_LINK) .long(OPT_LINK)
.overrides_with(OPT_REFLINK)
.help("hard-link files instead of copying")) .help("hard-link files instead of copying"))
.arg(Arg::with_name(OPT_NO_CLOBBER) .arg(Arg::with_name(OPT_NO_CLOBBER)
.short("n") .short("n")
@ -294,6 +309,7 @@ pub fn uumain(args: Vec<String>) -> i32 {
.short("s") .short("s")
.long(OPT_SYMBOLIC_LINK) .long(OPT_SYMBOLIC_LINK)
.conflicts_with(OPT_LINK) .conflicts_with(OPT_LINK)
.overrides_with(OPT_REFLINK)
.help("make symbolic links instead of copying")) .help("make symbolic links instead of copying"))
.arg(Arg::with_name(OPT_FORCE) .arg(Arg::with_name(OPT_FORCE)
.short("f") .short("f")
@ -322,6 +338,12 @@ pub fn uumain(args: Vec<String>) -> i32 {
.long(OPT_UPDATE) .long(OPT_UPDATE)
.help("copy only when the SOURCE file is newer than the destination file\ .help("copy only when the SOURCE file is newer than the destination file\
or when the destination file is missing")) or when the destination file is missing"))
.arg(Arg::with_name(OPT_REFLINK)
.long(OPT_REFLINK)
.takes_value(true)
.value_name("WHEN")
.help("control clone/CoW copies. See below"))
// TODO: implement the following args // TODO: implement the following args
.arg(Arg::with_name(OPT_ARCHIVE) .arg(Arg::with_name(OPT_ARCHIVE)
.short("a") .short("a")
@ -331,6 +353,7 @@ pub fn uumain(args: Vec<String>) -> i32 {
.arg(Arg::with_name(OPT_ATTRIBUTES_ONLY) .arg(Arg::with_name(OPT_ATTRIBUTES_ONLY)
.long(OPT_ATTRIBUTES_ONLY) .long(OPT_ATTRIBUTES_ONLY)
.conflicts_with(OPT_COPY_CONTENTS) .conflicts_with(OPT_COPY_CONTENTS)
.overrides_with(OPT_REFLINK)
.help("NotImplemented: don't copy the file data, just the attributes")) .help("NotImplemented: don't copy the file data, just the attributes"))
.arg(Arg::with_name(OPT_COPY_CONTENTS) .arg(Arg::with_name(OPT_COPY_CONTENTS)
.long(OPT_COPY_CONTENTS) .long(OPT_COPY_CONTENTS)
@ -372,11 +395,6 @@ pub fn uumain(args: Vec<String>) -> i32 {
.arg(Arg::with_name(OPT_PARENTS) .arg(Arg::with_name(OPT_PARENTS)
.long(OPT_PARENTS) .long(OPT_PARENTS)
.help("NotImplemented: use full source file name under DIRECTORY")) .help("NotImplemented: use full source file name under DIRECTORY"))
.arg(Arg::with_name(OPT_REFLINK)
.long(OPT_REFLINK)
.takes_value(true)
.value_name("WHEN")
.help("NotImplemented: control clone/CoW copies. See below"))
.arg(Arg::with_name(OPT_SPARSE) .arg(Arg::with_name(OPT_SPARSE)
.long(OPT_SPARSE) .long(OPT_SPARSE)
.takes_value(true) .takes_value(true)
@ -499,7 +517,6 @@ impl Options {
OPT_PRESERVE, OPT_PRESERVE,
OPT_NO_PRESERVE, OPT_NO_PRESERVE,
OPT_PARENTS, OPT_PARENTS,
OPT_REFLINK,
OPT_SPARSE, OPT_SPARSE,
OPT_STRIP_TRAILING_SLASHES, OPT_STRIP_TRAILING_SLASHES,
OPT_ONE_FILE_SYSTEM, OPT_ONE_FILE_SYSTEM,
@ -541,7 +558,6 @@ impl Options {
} else { } else {
vec![] vec![]
}; };
let options = Options { let options = 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),
@ -553,6 +569,24 @@ impl Options {
backup_suffix: matches.value_of(OPT_SUFFIX).unwrap().to_string(), backup_suffix: matches.value_of(OPT_SUFFIX).unwrap().to_string(),
update: matches.is_present(OPT_UPDATE), update: matches.is_present(OPT_UPDATE),
verbose: matches.is_present(OPT_VERBOSE), verbose: matches.is_present(OPT_VERBOSE),
reflink: matches.is_present(OPT_REFLINK),
reflink_mode: {
if let Some(reflink) = matches.value_of(OPT_REFLINK) {
match reflink {
"always" => {
ReflinkMode::Always
},
"auto" => {
ReflinkMode::Auto
},
value => {
return Err(Error::InvalidArgument(format!("invalid argument '{}' for \'reflink\'", value)))
}
}
} else {
ReflinkMode::Never
}
},
backup, backup,
no_target_dir, no_target_dir,
preserve_attributes, preserve_attributes,
@ -824,10 +858,16 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
} }
match options.copy_mode { match options.copy_mode {
CopyMode::Link => { fs::hard_link(source, dest).context(&*context_for(source, dest))?; }, CopyMode::Link => {
CopyMode::Copy => { fs::copy(source, dest).context(&*context_for(source, dest))?; }, fs::hard_link(source, dest).context(&*context_for(source, dest))?;
CopyMode::SymLink => { symlink_file(source, dest, &*context_for(source, dest))?; }, }
CopyMode::Sparse => return Err(Error::NotImplemented(OPT_SPARSE.to_string())), CopyMode::Copy => {
copy_helper(source, dest, options)?;
}
CopyMode::SymLink => {
symlink_file(source, dest, &*context_for(source, dest))?;
}
CopyMode::Sparse => return Err(Error::NotImplemented(OPT_SPARSE.to_string())),
CopyMode::Update => { CopyMode::Update => {
if dest.exists() { if dest.exists() {
let src_metadata = fs::metadata(source.clone())?; let src_metadata = fs::metadata(source.clone())?;
@ -836,12 +876,12 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
let src_time = src_metadata.modified()?; let src_time = src_metadata.modified()?;
let dest_time = dest_metadata.modified()?; let dest_time = dest_metadata.modified()?;
if src_time <= dest_time { if src_time <= dest_time {
return Ok(()) return Ok(());
} else { } else {
fs::copy(source, dest).context(&*context_for(source, dest))?; copy_helper(source, dest, options)?;
} }
} else { } else {
fs::copy(source, dest).context(&*context_for(source, dest))?; copy_helper(source, dest, options)?;
} }
} }
}; };
@ -853,6 +893,46 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
Ok(()) Ok(())
} }
///Copy the file from `source` to `dest` either using the normal `fs::copy` or the
///`FICLONE` ioctl if --reflink is specified and the filesystem supports it.
fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
if options.reflink {
#[cfg(not(target_os = "linux"))]
return Err(format!("--reflink is only supported on linux").into());
#[cfg(target_os = "linux")]
{
let src_file = File::open(source).unwrap().into_raw_fd();
let dst_file = OpenOptions::new()
.write(true)
.truncate(false)
.create(true)
.open(dest)
.unwrap()
.into_raw_fd();
match options.reflink_mode {
ReflinkMode::Always => unsafe {
let result = ficlone(dst_file, src_file as *const i32);
if result != 0 {
return Err(format!("failed to clone {:?} from {:?}: {}", source, dest, std::io::Error::last_os_error()).into());
} else {
return Ok(())
}
},
ReflinkMode::Auto => unsafe {
let result = ficlone(dst_file, src_file as *const i32);
if result != 0 {
fs::copy(source, dest).context(&*context_for(source, dest))?;
}
},
ReflinkMode::Never => {}
}
}
} else {
fs::copy(source, dest).context(&*context_for(source, dest))?;
}
Ok(())
}
/// Generate an error message if `target` is not the correct `target_type` /// Generate an error message if `target` is not the correct `target_type`
pub fn verify_target_type(target: &Path, target_type: &TargetType) -> CopyResult<()> { pub fn verify_target_type(target: &Path, target_type: &TargetType) -> CopyResult<()> {