mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 19:17:43 +00:00
Merge pull request #3982 from jfinkels/cp-sparse-copy
cp: make copy_on_write_linux() func more readable
This commit is contained in:
commit
45a7e52ed9
1 changed files with 93 additions and 91 deletions
|
@ -9,33 +9,22 @@
|
|||
// For the full copyright and license information, please view the LICENSE file
|
||||
// that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore (ToDO) ficlone ftruncate linkgs lstat nlink nlinks pathbuf pwrite reflink strs xattrs symlinked
|
||||
// spell-checker:ignore (ToDO) ficlone ftruncate linkgs lstat nlink nlinks pathbuf pwrite reflink strs xattrs symlinked fiemap
|
||||
|
||||
#[macro_use]
|
||||
extern crate quick_error;
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use uucore::display::Quotable;
|
||||
use uucore::format_usage;
|
||||
use uucore::fs::{paths_refer_to_same_file, FileInformation};
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use clap::{crate_version, Arg, ArgMatches, Command};
|
||||
use filetime::FileTime;
|
||||
#[cfg(unix)]
|
||||
use libc::mkfifo;
|
||||
use quick_error::ResultExt;
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
#[cfg(not(windows))]
|
||||
use std::ffi::CString;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io;
|
||||
use std::io::{stderr, stdin, Write};
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
use std::io::Read;
|
||||
use std::io::{self, stderr, stdin, Write};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
#[cfg(unix)]
|
||||
|
@ -45,9 +34,19 @@ use std::os::unix::io::AsRawFd;
|
|||
use std::path::{Path, PathBuf, StripPrefixError};
|
||||
use std::str::FromStr;
|
||||
use std::string::ToString;
|
||||
|
||||
use clap::{crate_version, Arg, ArgMatches, Command};
|
||||
use filetime::FileTime;
|
||||
#[cfg(unix)]
|
||||
use libc::mkfifo;
|
||||
use quick_error::ResultExt;
|
||||
use uucore::backup_control::{self, BackupMode};
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{set_exit_code, UClapError, UError, UResult, UUsageError};
|
||||
use uucore::fs::{canonicalize, MissingHandling, ResolveMode};
|
||||
use uucore::format_usage;
|
||||
use uucore::fs::{
|
||||
canonicalize, paths_refer_to_same_file, FileInformation, MissingHandling, ResolveMode,
|
||||
};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
quick_error! {
|
||||
|
@ -1688,6 +1687,73 @@ fn copy_no_cow_fallback(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Use the Linux `ioctl_ficlone` API to do a copy-on-write clone.
|
||||
///
|
||||
/// If `fallback` is true and there is a failure performing the clone,
|
||||
/// then this function performs a standard [`std::fs::copy`]. Otherwise,
|
||||
/// this function returns an error.
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
fn clone<P>(source: P, dest: P, fallback: bool) -> std::io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let src_file = File::open(&source)?;
|
||||
let dst_file = File::create(&dest)?;
|
||||
let src_fd = src_file.as_raw_fd();
|
||||
let dst_fd = dst_file.as_raw_fd();
|
||||
let result = unsafe { libc::ioctl(dst_fd, FICLONE!(), src_fd) };
|
||||
if result != 0 {
|
||||
if fallback {
|
||||
std::fs::copy(source, dest).map(|_| ())
|
||||
} else {
|
||||
Err(std::io::Error::last_os_error())
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform a sparse copy from one file to another.
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
fn sparse_copy<P>(source: P, dest: P) -> std::io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
use std::os::unix::prelude::MetadataExt;
|
||||
|
||||
let mut src_file = File::open(source)?;
|
||||
let dst_file = File::create(dest)?;
|
||||
let dst_fd = dst_file.as_raw_fd();
|
||||
|
||||
let size: usize = src_file.metadata()?.size().try_into().unwrap();
|
||||
if unsafe { libc::ftruncate(dst_fd, size.try_into().unwrap()) } < 0 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
|
||||
let blksize = dst_file.metadata()?.blksize();
|
||||
let mut buf: Vec<u8> = vec![0; blksize.try_into().unwrap()];
|
||||
let mut current_offset: usize = 0;
|
||||
|
||||
// TODO Perhaps we can employ the "fiemap ioctl" API to get the
|
||||
// file extent mappings:
|
||||
// https://www.kernel.org/doc/html/latest/filesystems/fiemap.html
|
||||
while current_offset < size {
|
||||
let this_read = src_file.read(&mut buf)?;
|
||||
if buf.iter().any(|&x| x != 0) {
|
||||
unsafe {
|
||||
libc::pwrite(
|
||||
dst_fd,
|
||||
buf.as_ptr() as *const libc::c_void,
|
||||
this_read,
|
||||
current_offset.try_into().unwrap(),
|
||||
)
|
||||
};
|
||||
}
|
||||
current_offset += this_read;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Copies `source` to `dest` using copy-on-write if possible.
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
fn copy_on_write_linux(
|
||||
|
@ -1697,81 +1763,17 @@ fn copy_on_write_linux(
|
|||
sparse_mode: SparseMode,
|
||||
context: &str,
|
||||
) -> CopyResult<()> {
|
||||
use std::os::unix::prelude::MetadataExt;
|
||||
|
||||
let mut src_file = File::open(source).context(context)?;
|
||||
let dst_file = OpenOptions::new()
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.create(true)
|
||||
.open(dest)
|
||||
.context(context)?;
|
||||
|
||||
match (reflink_mode, sparse_mode) {
|
||||
(ReflinkMode::Always, SparseMode::Auto) => unsafe {
|
||||
let result = libc::ioctl(dst_file.as_raw_fd(), FICLONE!(), src_file.as_raw_fd());
|
||||
|
||||
if result != 0 {
|
||||
Err(format!(
|
||||
"failed to clone {:?} from {:?}: {}",
|
||||
source,
|
||||
dest,
|
||||
std::io::Error::last_os_error()
|
||||
)
|
||||
.into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
(ReflinkMode::Always, SparseMode::Always) | (ReflinkMode::Always, SparseMode::Never) => {
|
||||
Err("`--reflink=always` can be used only with --sparse=auto".into())
|
||||
let result = match (reflink_mode, sparse_mode) {
|
||||
(ReflinkMode::Never, _) => std::fs::copy(source, dest).map(|_| ()),
|
||||
(ReflinkMode::Auto, SparseMode::Always) => sparse_copy(source, dest),
|
||||
(ReflinkMode::Auto, _) => clone(source, dest, true),
|
||||
(ReflinkMode::Always, SparseMode::Auto) => clone(source, dest, false),
|
||||
(ReflinkMode::Always, _) => {
|
||||
return Err("`--reflink=always` can be used only with --sparse=auto".into())
|
||||
}
|
||||
(_, SparseMode::Always) => unsafe {
|
||||
let size: usize = src_file.metadata()?.size().try_into().unwrap();
|
||||
if libc::ftruncate(dst_file.as_raw_fd(), size.try_into().unwrap()) < 0 {
|
||||
return Err(format!(
|
||||
"failed to ftruncate {:?} to size {}: {}",
|
||||
dest,
|
||||
size,
|
||||
std::io::Error::last_os_error()
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
let blksize = dst_file.metadata()?.blksize();
|
||||
let mut buf: Vec<u8> = vec![0; blksize.try_into().unwrap()];
|
||||
let mut current_offset: usize = 0;
|
||||
|
||||
while current_offset < size {
|
||||
use std::io::Read;
|
||||
|
||||
let this_read = src_file.read(&mut buf)?;
|
||||
|
||||
if buf.iter().any(|&x| x != 0) {
|
||||
libc::pwrite(
|
||||
dst_file.as_raw_fd(),
|
||||
buf.as_ptr() as *const libc::c_void,
|
||||
this_read,
|
||||
current_offset.try_into().unwrap(),
|
||||
);
|
||||
}
|
||||
current_offset += this_read;
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
(ReflinkMode::Auto, SparseMode::Auto) | (ReflinkMode::Auto, SparseMode::Never) => unsafe {
|
||||
let result = libc::ioctl(dst_file.as_raw_fd(), FICLONE!(), src_file.as_raw_fd());
|
||||
|
||||
if result != 0 {
|
||||
fs::copy(source, dest).context(context)?;
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
(ReflinkMode::Never, _) => {
|
||||
fs::copy(source, dest).context(context)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
};
|
||||
result.context(context)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Copies `source` to `dest` using copy-on-write if possible.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue