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"
|
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"
|
||||||
|
|
108
src/cp/cp.rs
108
src/cp/cp.rs
|
@ -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<()> {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue