diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 2c5222bc9..ddb3b2614 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -16,26 +16,13 @@ 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}; +use std::io::{self, stderr, stdin, Read, Write}; #[cfg(unix)] use std::os::unix::ffi::OsStrExt; #[cfg(unix)] @@ -45,9 +32,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! { @@ -1679,6 +1676,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(unix)] +fn clone

(source: P, dest: P, fallback: bool) -> std::io::Result<()> +where + P: AsRef, +{ + 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(unix)] +fn sparse_copy

(source: P, dest: P) -> std::io::Result<()> +where + P: AsRef, +{ + 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 = 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( @@ -1688,81 +1752,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) => { + 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 = 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(()) - } - } + (ReflinkMode::Always, _) => clone(source, dest, false), + }; + result.context(context)?; + Ok(()) } /// Copies `source` to `dest` using copy-on-write if possible.