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:
commit
c827795d17
2 changed files with 97 additions and 14 deletions
|
@ -18,6 +18,9 @@ clap = "2.20.0"
|
|||
quick-error = "1.1.0"
|
||||
uucore = { path="../uucore" }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
ioctl-sys = "0.5.2"
|
||||
|
||||
[[bin]]
|
||||
name = "cp"
|
||||
path = "main.rs"
|
||||
|
|
108
src/cp/cp.rs
108
src/cp/cp.rs
|
@ -12,6 +12,8 @@
|
|||
|
||||
extern crate clap;
|
||||
extern crate walkdir;
|
||||
#[cfg(target_os = "linux")]
|
||||
#[macro_use] extern crate ioctl_sys;
|
||||
#[macro_use] extern crate uucore;
|
||||
#[macro_use] extern crate quick_error;
|
||||
|
||||
|
@ -25,9 +27,14 @@ use std::path::{Path, PathBuf, StripPrefixError};
|
|||
use std::str::FromStr;
|
||||
use uucore::fs::{canonicalize, CanonicalizeMode};
|
||||
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(target_os = "linux")] ioctl!(write ficlone with 0x94, 9; std::os::raw::c_int);
|
||||
|
||||
quick_error! {
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
|
@ -125,6 +132,11 @@ pub enum OverwriteMode {
|
|||
NoClobber,
|
||||
}
|
||||
|
||||
#[derive (Clone, Eq, PartialEq)]
|
||||
pub enum ReflinkMode {
|
||||
Always, Auto, Never
|
||||
}
|
||||
|
||||
/// Specifies the expected file type of copy target
|
||||
pub enum TargetType {
|
||||
Directory,
|
||||
|
@ -170,6 +182,8 @@ pub struct Options {
|
|||
one_file_system: bool,
|
||||
overwrite: OverwriteMode,
|
||||
parents: bool,
|
||||
reflink: bool,
|
||||
reflink_mode: ReflinkMode,
|
||||
preserve_attributes: Vec<Attribute>,
|
||||
recursive: bool,
|
||||
backup_suffix: String,
|
||||
|
@ -273,6 +287,7 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
|||
.arg(Arg::with_name(OPT_LINK)
|
||||
.short("l")
|
||||
.long(OPT_LINK)
|
||||
.overrides_with(OPT_REFLINK)
|
||||
.help("hard-link files instead of copying"))
|
||||
.arg(Arg::with_name(OPT_NO_CLOBBER)
|
||||
.short("n")
|
||||
|
@ -294,6 +309,7 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
|||
.short("s")
|
||||
.long(OPT_SYMBOLIC_LINK)
|
||||
.conflicts_with(OPT_LINK)
|
||||
.overrides_with(OPT_REFLINK)
|
||||
.help("make symbolic links instead of copying"))
|
||||
.arg(Arg::with_name(OPT_FORCE)
|
||||
.short("f")
|
||||
|
@ -322,6 +338,12 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
|||
.long(OPT_UPDATE)
|
||||
.help("copy only when the SOURCE file is newer than the destination file\
|
||||
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
|
||||
.arg(Arg::with_name(OPT_ARCHIVE)
|
||||
.short("a")
|
||||
|
@ -331,6 +353,7 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
|||
.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)
|
||||
|
@ -372,11 +395,6 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
|||
.arg(Arg::with_name(OPT_PARENTS)
|
||||
.long(OPT_PARENTS)
|
||||
.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)
|
||||
.long(OPT_SPARSE)
|
||||
.takes_value(true)
|
||||
|
@ -499,7 +517,6 @@ impl Options {
|
|||
OPT_PRESERVE,
|
||||
OPT_NO_PRESERVE,
|
||||
OPT_PARENTS,
|
||||
OPT_REFLINK,
|
||||
OPT_SPARSE,
|
||||
OPT_STRIP_TRAILING_SLASHES,
|
||||
OPT_ONE_FILE_SYSTEM,
|
||||
|
@ -541,7 +558,6 @@ impl Options {
|
|||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let options = Options {
|
||||
attributes_only: matches.is_present(OPT_ATTRIBUTES_ONLY),
|
||||
copy_contents: matches.is_present(OPT_COPY_CONTENTS),
|
||||
|
@ -553,6 +569,24 @@ impl Options {
|
|||
backup_suffix: matches.value_of(OPT_SUFFIX).unwrap().to_string(),
|
||||
update: matches.is_present(OPT_UPDATE),
|
||||
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,
|
||||
no_target_dir,
|
||||
preserve_attributes,
|
||||
|
@ -824,10 +858,16 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
|
|||
}
|
||||
|
||||
match options.copy_mode {
|
||||
CopyMode::Link => { fs::hard_link(source, dest).context(&*context_for(source, dest))?; },
|
||||
CopyMode::Copy => { fs::copy(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::Link => {
|
||||
fs::hard_link(source, dest).context(&*context_for(source, dest))?;
|
||||
}
|
||||
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 => {
|
||||
if dest.exists() {
|
||||
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 dest_time = dest_metadata.modified()?;
|
||||
if src_time <= dest_time {
|
||||
return Ok(())
|
||||
return Ok(());
|
||||
} else {
|
||||
fs::copy(source, dest).context(&*context_for(source, dest))?;
|
||||
copy_helper(source, dest, options)?;
|
||||
}
|
||||
} 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(())
|
||||
}
|
||||
|
||||
///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`
|
||||
pub fn verify_target_type(target: &Path, target_type: &TargetType) -> CopyResult<()> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue