mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
cp: migrate from quick-error to thiserror (#7989)
* cp: migrate from quick-error to thiserror fixes: #7916 * Remove quick-error Now that we have migrated to thiserror, we can remove quick-error * cp: fix test failures * cp: fix fmt error
This commit is contained in:
parent
28612fe1f1
commit
bfbdd5275d
9 changed files with 171 additions and 121 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -1849,12 +1849,6 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quick-error"
|
|
||||||
version = "2.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.40"
|
version = "1.0.40"
|
||||||
|
@ -2697,8 +2691,8 @@ dependencies = [
|
||||||
"indicatif",
|
"indicatif",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"quick-error",
|
|
||||||
"selinux",
|
"selinux",
|
||||||
|
"thiserror 2.0.12",
|
||||||
"uucore",
|
"uucore",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"xattr",
|
"xattr",
|
||||||
|
|
|
@ -322,7 +322,6 @@ parse_datetime = "0.9.0"
|
||||||
phf = "0.11.2"
|
phf = "0.11.2"
|
||||||
phf_codegen = "0.11.2"
|
phf_codegen = "0.11.2"
|
||||||
platform-info = "2.0.3"
|
platform-info = "2.0.3"
|
||||||
quick-error = "2.0.1"
|
|
||||||
rand = { version = "0.9.0", features = ["small_rng"] }
|
rand = { version = "0.9.0", features = ["small_rng"] }
|
||||||
rand_core = "0.9.0"
|
rand_core = "0.9.0"
|
||||||
rayon = "1.10"
|
rayon = "1.10"
|
||||||
|
|
|
@ -22,7 +22,6 @@ clap = { workspace = true }
|
||||||
filetime = { workspace = true }
|
filetime = { workspace = true }
|
||||||
libc = { workspace = true }
|
libc = { workspace = true }
|
||||||
linux-raw-sys = { workspace = true }
|
linux-raw-sys = { workspace = true }
|
||||||
quick-error = { workspace = true }
|
|
||||||
selinux = { workspace = true, optional = true }
|
selinux = { workspace = true, optional = true }
|
||||||
uucore = { workspace = true, features = [
|
uucore = { workspace = true, features = [
|
||||||
"backup-control",
|
"backup-control",
|
||||||
|
@ -37,6 +36,7 @@ uucore = { workspace = true, features = [
|
||||||
] }
|
] }
|
||||||
walkdir = { workspace = true }
|
walkdir = { workspace = true }
|
||||||
indicatif = { workspace = true }
|
indicatif = { workspace = true }
|
||||||
|
thiserror = { workspace = true }
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
xattr = { workspace = true }
|
xattr = { workspace = true }
|
||||||
|
|
|
@ -26,7 +26,7 @@ use uucore::uio_error;
|
||||||
use walkdir::{DirEntry, WalkDir};
|
use walkdir::{DirEntry, WalkDir};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
CopyResult, Error, Options, aligned_ancestors, context_for, copy_attributes, copy_file,
|
CopyResult, CpError, Options, aligned_ancestors, context_for, copy_attributes, copy_file,
|
||||||
copy_link,
|
copy_link,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -266,7 +266,7 @@ fn copy_direntry(
|
||||||
// TODO What other kinds of errors, if any, should
|
// TODO What other kinds of errors, if any, should
|
||||||
// cause us to continue walking the directory?
|
// cause us to continue walking the directory?
|
||||||
match err {
|
match err {
|
||||||
Error::IoErrContext(e, _) if e.kind() == io::ErrorKind::PermissionDenied => {
|
CpError::IoErrContext(e, _) if e.kind() == io::ErrorKind::PermissionDenied => {
|
||||||
show!(uio_error!(
|
show!(uio_error!(
|
||||||
e,
|
e,
|
||||||
"cannot open {} for reading",
|
"cannot open {} for reading",
|
||||||
|
|
|
@ -4,22 +4,22 @@
|
||||||
// file that was distributed with this source code.
|
// file that was distributed with this source code.
|
||||||
// spell-checker:ignore (ToDO) copydir ficlone fiemap ftruncate linkgs lstat nlink nlinks pathbuf pwrite reflink strs xattrs symlinked deduplicated advcpmv nushell IRWXG IRWXO IRWXU IRWXUGO IRWXU IRWXG IRWXO IRWXUGO
|
// spell-checker:ignore (ToDO) copydir ficlone fiemap ftruncate linkgs lstat nlink nlinks pathbuf pwrite reflink strs xattrs symlinked deduplicated advcpmv nushell IRWXG IRWXO IRWXU IRWXUGO IRWXU IRWXG IRWXO IRWXUGO
|
||||||
|
|
||||||
use quick_error::quick_error;
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
|
use std::fmt::Display;
|
||||||
use std::fs::{self, Metadata, OpenOptions, Permissions};
|
use std::fs::{self, Metadata, OpenOptions, Permissions};
|
||||||
use std::io;
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::unix::fs::{FileTypeExt, PermissionsExt};
|
use std::os::unix::fs::{FileTypeExt, PermissionsExt};
|
||||||
use std::path::{Path, PathBuf, StripPrefixError};
|
use std::path::{Path, PathBuf, StripPrefixError};
|
||||||
|
use std::{fmt, io};
|
||||||
#[cfg(all(unix, not(target_os = "android")))]
|
#[cfg(all(unix, not(target_os = "android")))]
|
||||||
use uucore::fsxattr::copy_xattrs;
|
use uucore::fsxattr::copy_xattrs;
|
||||||
|
|
||||||
use clap::{Arg, ArgAction, ArgMatches, Command, builder::ValueParser, value_parser};
|
use clap::{Arg, ArgAction, ArgMatches, Command, builder::ValueParser, value_parser};
|
||||||
use filetime::FileTime;
|
use filetime::FileTime;
|
||||||
use indicatif::{ProgressBar, ProgressStyle};
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
use quick_error::ResultExt;
|
use thiserror::Error;
|
||||||
|
|
||||||
use platform::copy_on_write;
|
use platform::copy_on_write;
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
|
@ -45,65 +45,94 @@ use crate::copydir::copy_directory;
|
||||||
mod copydir;
|
mod copydir;
|
||||||
mod platform;
|
mod platform;
|
||||||
|
|
||||||
quick_error! {
|
#[derive(Debug, Error)]
|
||||||
#[derive(Debug)]
|
pub enum CpError {
|
||||||
pub enum Error {
|
|
||||||
/// Simple io::Error wrapper
|
/// Simple io::Error wrapper
|
||||||
IoErr(err: io::Error) { from() source(err) display("{err}")}
|
#[error("{0}")]
|
||||||
|
IoErr(#[from] io::Error),
|
||||||
|
|
||||||
/// Wrapper for io::Error with path context
|
/// Wrapper for io::Error with path context
|
||||||
IoErrContext(err: io::Error, path: String) {
|
#[error("{1}: {0}")]
|
||||||
display("{path}: {err}")
|
IoErrContext(io::Error, String),
|
||||||
context(path: &'a str, err: io::Error) -> (err, path.to_owned())
|
|
||||||
context(context: String, err: io::Error) -> (err, context)
|
|
||||||
source(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// General copy error
|
/// General copy error
|
||||||
Error(err: String) {
|
#[error("{0}")]
|
||||||
display("{err}")
|
Error(String),
|
||||||
from(err: String) -> (err)
|
|
||||||
from(err: &'static str) -> (err.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents the state when a non-fatal error has occurred
|
/// Represents the state when a non-fatal error has occurred
|
||||||
/// and not all files were copied.
|
/// and not all files were copied.
|
||||||
NotAllFilesCopied {}
|
#[error("Not all files were copied")]
|
||||||
|
NotAllFilesCopied,
|
||||||
|
|
||||||
/// Simple walkdir::Error wrapper
|
/// Simple walkdir::Error wrapper
|
||||||
WalkDirErr(err: walkdir::Error) { from() display("{err}") source(err) }
|
#[error("{0}")]
|
||||||
|
WalkDirErr(#[from] walkdir::Error),
|
||||||
|
|
||||||
/// Simple std::path::StripPrefixError wrapper
|
/// Simple std::path::StripPrefixError wrapper
|
||||||
StripPrefixError(err: StripPrefixError) { from() }
|
#[error(transparent)]
|
||||||
|
StripPrefixError(#[from] StripPrefixError),
|
||||||
|
|
||||||
/// Result of a skipped file
|
/// Result of a skipped file
|
||||||
/// Currently happens when "no" is selected in interactive mode or when
|
/// Currently happens when "no" is selected in interactive mode or when
|
||||||
/// `no-clobber` flag is set and destination is already present.
|
/// `no-clobber` flag is set and destination is already present.
|
||||||
/// `exit with error` is used to determine which exit code should be returned.
|
/// `exit with error` is used to determine which exit code should be returned.
|
||||||
Skipped(exit_with_error:bool) { }
|
#[error("Skipped copying file (exit with error = {0})")]
|
||||||
|
Skipped(bool),
|
||||||
|
|
||||||
/// Result of a skipped file
|
/// Result of a skipped file
|
||||||
InvalidArgument(description: String) { display("{description}") }
|
#[error("{0}")]
|
||||||
|
InvalidArgument(String),
|
||||||
|
|
||||||
/// All standard options are included as an an implementation
|
/// All standard options are included as an an implementation
|
||||||
/// path, but those that are not implemented yet should return
|
/// path, but those that are not implemented yet should return
|
||||||
/// a NotImplemented error.
|
/// a NotImplemented error.
|
||||||
NotImplemented(opt: String) { display("Option '{opt}' not yet implemented.") }
|
#[error("Option '{0}' not yet implemented.")]
|
||||||
|
NotImplemented(String),
|
||||||
|
|
||||||
/// Invalid arguments to backup
|
/// Invalid arguments to backup
|
||||||
Backup(description: String) { display("{description}\nTry '{} --help' for more information.", uucore::execution_phrase()) }
|
#[error(transparent)]
|
||||||
|
Backup(#[from] BackupError),
|
||||||
|
|
||||||
NotADirectory(path: PathBuf) { display("'{}' is not a directory", path.display()) }
|
#[error("'{}' is not a directory", .0.display())]
|
||||||
|
NotADirectory(PathBuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manual impl for &str
|
||||||
|
impl From<&'static str> for CpError {
|
||||||
|
fn from(s: &'static str) -> Self {
|
||||||
|
Self::Error(s.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UError for Error {
|
impl From<String> for CpError {
|
||||||
|
fn from(s: String) -> Self {
|
||||||
|
Self::Error(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BackupError(String);
|
||||||
|
|
||||||
|
impl Display for BackupError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"cp: {}\nTry '{} --help' for more information.",
|
||||||
|
self.0,
|
||||||
|
uucore::execution_phrase()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for BackupError {}
|
||||||
|
|
||||||
|
impl UError for CpError {
|
||||||
fn code(&self) -> i32 {
|
fn code(&self) -> i32 {
|
||||||
EXIT_ERR
|
EXIT_ERR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type CopyResult<T> = Result<T, Error>;
|
pub type CopyResult<T> = Result<T, CpError>;
|
||||||
|
|
||||||
/// Specifies how to overwrite files.
|
/// Specifies how to overwrite files.
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
|
||||||
|
@ -803,7 +832,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
match error {
|
match error {
|
||||||
// Error::NotAllFilesCopied is non-fatal, but the error
|
// Error::NotAllFilesCopied is non-fatal, but the error
|
||||||
// code should still be EXIT_ERR as does GNU cp
|
// code should still be EXIT_ERR as does GNU cp
|
||||||
Error::NotAllFilesCopied => {}
|
CpError::NotAllFilesCopied => {}
|
||||||
// Else we caught a fatal bubbled-up error, log it to stderr
|
// Else we caught a fatal bubbled-up error, log it to stderr
|
||||||
_ => show_error!("{error}"),
|
_ => show_error!("{error}"),
|
||||||
};
|
};
|
||||||
|
@ -940,7 +969,7 @@ impl Attributes {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_iter<T>(values: impl Iterator<Item = T>) -> Result<Self, Error>
|
pub fn parse_iter<T>(values: impl Iterator<Item = T>) -> CopyResult<Self>
|
||||||
where
|
where
|
||||||
T: AsRef<str>,
|
T: AsRef<str>,
|
||||||
{
|
{
|
||||||
|
@ -953,7 +982,7 @@ impl Attributes {
|
||||||
|
|
||||||
/// Tries to match string containing a parameter to preserve with the corresponding entry in the
|
/// Tries to match string containing a parameter to preserve with the corresponding entry in the
|
||||||
/// Attributes struct.
|
/// Attributes struct.
|
||||||
fn parse_single_string(value: &str) -> Result<Self, Error> {
|
fn parse_single_string(value: &str) -> CopyResult<Self> {
|
||||||
let value = value.to_lowercase();
|
let value = value.to_lowercase();
|
||||||
|
|
||||||
if value == "all" {
|
if value == "all" {
|
||||||
|
@ -970,7 +999,7 @@ impl Attributes {
|
||||||
"link" | "links" => &mut new.links,
|
"link" | "links" => &mut new.links,
|
||||||
"xattr" => &mut new.xattr,
|
"xattr" => &mut new.xattr,
|
||||||
_ => {
|
_ => {
|
||||||
return Err(Error::InvalidArgument(format!(
|
return Err(CpError::InvalidArgument(format!(
|
||||||
"invalid attribute {}",
|
"invalid attribute {}",
|
||||||
value.quote()
|
value.quote()
|
||||||
)));
|
)));
|
||||||
|
@ -998,14 +1027,14 @@ impl Options {
|
||||||
&& matches.value_source(not_implemented_opt)
|
&& matches.value_source(not_implemented_opt)
|
||||||
== Some(clap::parser::ValueSource::CommandLine)
|
== Some(clap::parser::ValueSource::CommandLine)
|
||||||
{
|
{
|
||||||
return Err(Error::NotImplemented(not_implemented_opt.to_string()));
|
return Err(CpError::NotImplemented(not_implemented_opt.to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let recursive = matches.get_flag(options::RECURSIVE) || matches.get_flag(options::ARCHIVE);
|
let recursive = matches.get_flag(options::RECURSIVE) || matches.get_flag(options::ARCHIVE);
|
||||||
|
|
||||||
let backup_mode = match backup_control::determine_backup_mode(matches) {
|
let backup_mode = match backup_control::determine_backup_mode(matches) {
|
||||||
Err(e) => return Err(Error::Backup(format!("{e}"))),
|
Err(e) => return Err(CpError::Backup(BackupError(format!("{e}")))),
|
||||||
Ok(mode) => mode,
|
Ok(mode) => mode,
|
||||||
};
|
};
|
||||||
let update_mode = update_control::determine_update_mode(matches);
|
let update_mode = update_control::determine_update_mode(matches);
|
||||||
|
@ -1015,7 +1044,7 @@ impl Options {
|
||||||
.get_one::<String>(update_control::arguments::OPT_UPDATE)
|
.get_one::<String>(update_control::arguments::OPT_UPDATE)
|
||||||
.is_some_and(|v| v == "none" || v == "none-fail")
|
.is_some_and(|v| v == "none" || v == "none-fail")
|
||||||
{
|
{
|
||||||
return Err(Error::InvalidArgument(
|
return Err(CpError::InvalidArgument(
|
||||||
"--backup is mutually exclusive with -n or --update=none-fail".to_string(),
|
"--backup is mutually exclusive with -n or --update=none-fail".to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -1032,7 +1061,7 @@ impl Options {
|
||||||
|
|
||||||
if let Some(dir) = &target_dir {
|
if let Some(dir) = &target_dir {
|
||||||
if !dir.is_dir() {
|
if !dir.is_dir() {
|
||||||
return Err(Error::NotADirectory(dir.clone()));
|
return Err(CpError::NotADirectory(dir.clone()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// cp follows POSIX conventions for overriding options such as "-a",
|
// cp follows POSIX conventions for overriding options such as "-a",
|
||||||
|
@ -1120,7 +1149,7 @@ impl Options {
|
||||||
#[cfg(not(feature = "selinux"))]
|
#[cfg(not(feature = "selinux"))]
|
||||||
if let Preserve::Yes { required } = attributes.context {
|
if let Preserve::Yes { required } = attributes.context {
|
||||||
let selinux_disabled_error =
|
let selinux_disabled_error =
|
||||||
Error::Error("SELinux was not enabled during the compile time!".to_string());
|
CpError::Error("SELinux was not enabled during the compile time!".to_owned());
|
||||||
if required {
|
if required {
|
||||||
return Err(selinux_disabled_error);
|
return Err(selinux_disabled_error);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1163,7 +1192,7 @@ impl Options {
|
||||||
"auto" => ReflinkMode::Auto,
|
"auto" => ReflinkMode::Auto,
|
||||||
"never" => ReflinkMode::Never,
|
"never" => ReflinkMode::Never,
|
||||||
value => {
|
value => {
|
||||||
return Err(Error::InvalidArgument(format!(
|
return Err(CpError::InvalidArgument(format!(
|
||||||
"invalid argument {} for \'reflink\'",
|
"invalid argument {} for \'reflink\'",
|
||||||
value.quote()
|
value.quote()
|
||||||
)));
|
)));
|
||||||
|
@ -1180,7 +1209,7 @@ impl Options {
|
||||||
"auto" => SparseMode::Auto,
|
"auto" => SparseMode::Auto,
|
||||||
"never" => SparseMode::Never,
|
"never" => SparseMode::Never,
|
||||||
_ => {
|
_ => {
|
||||||
return Err(Error::InvalidArgument(format!(
|
return Err(CpError::InvalidArgument(format!(
|
||||||
"invalid argument {val} for \'sparse\'"
|
"invalid argument {val} for \'sparse\'"
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
@ -1298,14 +1327,14 @@ fn parse_path_args(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// When handling errors, we don't always want to show them to the user. This function handles that.
|
/// When handling errors, we don't always want to show them to the user. This function handles that.
|
||||||
fn show_error_if_needed(error: &Error) {
|
fn show_error_if_needed(error: &CpError) {
|
||||||
match error {
|
match error {
|
||||||
// When using --no-clobber, we don't want to show
|
// When using --no-clobber, we don't want to show
|
||||||
// an error message
|
// an error message
|
||||||
Error::NotAllFilesCopied => {
|
CpError::NotAllFilesCopied => {
|
||||||
// Need to return an error code
|
// Need to return an error code
|
||||||
}
|
}
|
||||||
Error::Skipped(_) => {
|
CpError::Skipped(_) => {
|
||||||
// touch a b && echo "n"|cp -i a b && echo $?
|
// touch a b && echo "n"|cp -i a b && echo $?
|
||||||
// should return an error from GNU 9.2
|
// should return an error from GNU 9.2
|
||||||
}
|
}
|
||||||
|
@ -1382,7 +1411,7 @@ pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult
|
||||||
// There is already a file and it isn't a symlink (managed in a different place)
|
// There is already a file and it isn't a symlink (managed in a different place)
|
||||||
if copied_destinations.contains(&dest) && options.backup != BackupMode::Numbered {
|
if copied_destinations.contains(&dest) && options.backup != BackupMode::Numbered {
|
||||||
// If the target file was already created in this cp call, do not overwrite
|
// If the target file was already created in this cp call, do not overwrite
|
||||||
return Err(Error::Error(format!(
|
return Err(CpError::Error(format!(
|
||||||
"will not overwrite just-created '{}' with '{}'",
|
"will not overwrite just-created '{}' with '{}'",
|
||||||
dest.display(),
|
dest.display(),
|
||||||
source.display()
|
source.display()
|
||||||
|
@ -1401,7 +1430,7 @@ pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult
|
||||||
&mut copied_files,
|
&mut copied_files,
|
||||||
) {
|
) {
|
||||||
show_error_if_needed(&error);
|
show_error_if_needed(&error);
|
||||||
if !matches!(error, Error::Skipped(false)) {
|
if !matches!(error, CpError::Skipped(false)) {
|
||||||
non_fatal_errors = true;
|
non_fatal_errors = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1416,7 +1445,7 @@ pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult
|
||||||
}
|
}
|
||||||
|
|
||||||
if non_fatal_errors {
|
if non_fatal_errors {
|
||||||
Err(Error::NotAllFilesCopied)
|
Err(CpError::NotAllFilesCopied)
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1564,7 +1593,7 @@ impl OverwriteMode {
|
||||||
if debug {
|
if debug {
|
||||||
println!("skipped {}", path.quote());
|
println!("skipped {}", path.quote());
|
||||||
}
|
}
|
||||||
Err(Error::Skipped(false))
|
Err(CpError::Skipped(false))
|
||||||
}
|
}
|
||||||
Self::Interactive(_) => {
|
Self::Interactive(_) => {
|
||||||
let prompt_yes_result = if let Some((octal, human_readable)) =
|
let prompt_yes_result = if let Some((octal, human_readable)) =
|
||||||
|
@ -1581,7 +1610,7 @@ impl OverwriteMode {
|
||||||
if prompt_yes_result {
|
if prompt_yes_result {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(Error::Skipped(true))
|
Err(CpError::Skipped(true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Clobber(_) => Ok(()),
|
Self::Clobber(_) => Ok(()),
|
||||||
|
@ -1650,7 +1679,8 @@ pub(crate) fn copy_attributes(
|
||||||
attributes: &Attributes,
|
attributes: &Attributes,
|
||||||
) -> CopyResult<()> {
|
) -> CopyResult<()> {
|
||||||
let context = &*format!("{} -> {}", source.quote(), dest.quote());
|
let context = &*format!("{} -> {}", source.quote(), dest.quote());
|
||||||
let source_metadata = fs::symlink_metadata(source).context(context)?;
|
let source_metadata =
|
||||||
|
fs::symlink_metadata(source).map_err(|e| CpError::IoErrContext(e, context.to_owned()))?;
|
||||||
|
|
||||||
// Ownership must be changed first to avoid interfering with mode change.
|
// Ownership must be changed first to avoid interfering with mode change.
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
@ -1665,7 +1695,9 @@ pub(crate) fn copy_attributes(
|
||||||
// gnu compatibility: cp doesn't report an error if it fails to set the ownership.
|
// gnu compatibility: cp doesn't report an error if it fails to set the ownership.
|
||||||
let _ = wrap_chown(
|
let _ = wrap_chown(
|
||||||
dest,
|
dest,
|
||||||
&dest.symlink_metadata().context(context)?,
|
&dest
|
||||||
|
.symlink_metadata()
|
||||||
|
.map_err(|e| CpError::IoErrContext(e, context.to_owned()))?,
|
||||||
Some(dest_uid),
|
Some(dest_uid),
|
||||||
Some(dest_gid),
|
Some(dest_gid),
|
||||||
false,
|
false,
|
||||||
|
@ -1684,12 +1716,13 @@ pub(crate) fn copy_attributes(
|
||||||
// do nothing, since every symbolic link has the same
|
// do nothing, since every symbolic link has the same
|
||||||
// permissions.
|
// permissions.
|
||||||
if !dest.is_symlink() {
|
if !dest.is_symlink() {
|
||||||
fs::set_permissions(dest, source_metadata.permissions()).context(context)?;
|
fs::set_permissions(dest, source_metadata.permissions())
|
||||||
|
.map_err(|e| CpError::IoErrContext(e, context.to_owned()))?;
|
||||||
// FIXME: Implement this for windows as well
|
// FIXME: Implement this for windows as well
|
||||||
#[cfg(feature = "feat_acl")]
|
#[cfg(feature = "feat_acl")]
|
||||||
exacl::getfacl(source, None)
|
exacl::getfacl(source, None)
|
||||||
.and_then(|acl| exacl::setfacl(&[dest], &acl, None))
|
.and_then(|acl| exacl::setfacl(&[dest], &acl, None))
|
||||||
.map_err(|err| Error::Error(err.to_string()))?;
|
.map_err(|err| CpError::Error(err.to_string()))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1713,14 +1746,14 @@ pub(crate) fn copy_attributes(
|
||||||
if let Ok(context) = selinux::SecurityContext::of_path(source, false, false) {
|
if let Ok(context) = selinux::SecurityContext::of_path(source, false, false) {
|
||||||
if let Some(context) = context {
|
if let Some(context) = context {
|
||||||
if let Err(e) = context.set_for_path(dest, false, false) {
|
if let Err(e) = context.set_for_path(dest, false, false) {
|
||||||
return Err(Error::Error(format!(
|
return Err(CpError::Error(format!(
|
||||||
"failed to set the security context of {}: {e}",
|
"failed to set the security context of {}: {e}",
|
||||||
dest.display()
|
dest.display()
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::Error(format!(
|
return Err(CpError::Error(format!(
|
||||||
"failed to get security context of {}",
|
"failed to get security context of {}",
|
||||||
source.display()
|
source.display()
|
||||||
)));
|
)));
|
||||||
|
@ -1760,19 +1793,29 @@ fn symlink_file(
|
||||||
) -> CopyResult<()> {
|
) -> CopyResult<()> {
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
{
|
{
|
||||||
std::os::unix::fs::symlink(source, dest).context(format!(
|
std::os::unix::fs::symlink(source, dest).map_err(|e| {
|
||||||
|
CpError::IoErrContext(
|
||||||
|
e,
|
||||||
|
format!(
|
||||||
"cannot create symlink {} to {}",
|
"cannot create symlink {} to {}",
|
||||||
get_filename(dest).unwrap_or("invalid file name").quote(),
|
get_filename(dest).unwrap_or("invalid file name").quote(),
|
||||||
get_filename(source).unwrap_or("invalid file name").quote()
|
get_filename(source).unwrap_or("invalid file name").quote()
|
||||||
))?;
|
),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
std::os::windows::fs::symlink_file(source, dest).context(format!(
|
std::os::windows::fs::symlink_file(source, dest).map_err(|e| {
|
||||||
|
CpError::IoErrContext(
|
||||||
|
e,
|
||||||
|
format!(
|
||||||
"cannot create symlink {} to {}",
|
"cannot create symlink {} to {}",
|
||||||
get_filename(dest).unwrap_or("invalid file name").quote(),
|
get_filename(dest).unwrap_or("invalid file name").quote(),
|
||||||
get_filename(source).unwrap_or("invalid file name").quote()
|
get_filename(source).unwrap_or("invalid file name").quote()
|
||||||
))?;
|
),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
if let Ok(file_info) = FileInformation::from_path(dest, false) {
|
if let Ok(file_info) = FileInformation::from_path(dest, false) {
|
||||||
symlinked_files.insert(file_info);
|
symlinked_files.insert(file_info);
|
||||||
|
@ -1867,7 +1910,7 @@ fn handle_existing_dest(
|
||||||
if options.debug {
|
if options.debug {
|
||||||
println!("skipped {}", dest.quote());
|
println!("skipped {}", dest.quote());
|
||||||
}
|
}
|
||||||
return Err(Error::Skipped(false));
|
return Err(CpError::Skipped(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.update != UpdateMode::IfOlder {
|
if options.update != UpdateMode::IfOlder {
|
||||||
|
@ -1947,7 +1990,7 @@ fn delete_dest_if_needed_and_allowed(
|
||||||
&FileInformation::from_path(
|
&FileInformation::from_path(
|
||||||
source,
|
source,
|
||||||
options.dereference(source_in_command_line)
|
options.dereference(source_in_command_line)
|
||||||
).context(format!("cannot stat {}", source.quote()))?
|
).map_err(|e| CpError::IoErrContext(e, format!("cannot stat {}", source.quote())))?
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2088,11 +2131,16 @@ fn handle_copy_mode(
|
||||||
} else {
|
} else {
|
||||||
fs::hard_link(source, dest)
|
fs::hard_link(source, dest)
|
||||||
}
|
}
|
||||||
.context(format!(
|
.map_err(|e| {
|
||||||
|
CpError::IoErrContext(
|
||||||
|
e,
|
||||||
|
format!(
|
||||||
"cannot create hard link {} to {}",
|
"cannot create hard link {} to {}",
|
||||||
get_filename(dest).unwrap_or("invalid file name").quote(),
|
get_filename(dest).unwrap_or("invalid file name").quote(),
|
||||||
get_filename(source).unwrap_or("invalid file name").quote()
|
get_filename(source).unwrap_or("invalid file name").quote()
|
||||||
))?;
|
),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
CopyMode::Copy => {
|
CopyMode::Copy => {
|
||||||
copy_helper(
|
copy_helper(
|
||||||
|
@ -2137,7 +2185,10 @@ fn handle_copy_mode(
|
||||||
return Ok(PerformedAction::Skipped);
|
return Ok(PerformedAction::Skipped);
|
||||||
}
|
}
|
||||||
UpdateMode::NoneFail => {
|
UpdateMode::NoneFail => {
|
||||||
return Err(Error::Error(format!("not replacing '{}'", dest.display())));
|
return Err(CpError::Error(format!(
|
||||||
|
"not replacing '{}'",
|
||||||
|
dest.display()
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
UpdateMode::IfOlder => {
|
UpdateMode::IfOlder => {
|
||||||
let dest_metadata = fs::symlink_metadata(dest)?;
|
let dest_metadata = fs::symlink_metadata(dest)?;
|
||||||
|
@ -2209,7 +2260,10 @@ fn calculate_dest_permissions(
|
||||||
context: &str,
|
context: &str,
|
||||||
) -> CopyResult<Permissions> {
|
) -> CopyResult<Permissions> {
|
||||||
if dest.exists() {
|
if dest.exists() {
|
||||||
Ok(dest.symlink_metadata().context(context)?.permissions())
|
Ok(dest
|
||||||
|
.symlink_metadata()
|
||||||
|
.map_err(|e| CpError::IoErrContext(e, context.to_owned()))?
|
||||||
|
.permissions())
|
||||||
} else {
|
} else {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
|
@ -2259,7 +2313,7 @@ fn copy_file(
|
||||||
.map(|info| symlinked_files.contains(&info))
|
.map(|info| symlinked_files.contains(&info))
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
return Err(Error::Error(format!(
|
return Err(CpError::Error(format!(
|
||||||
"will not copy '{}' through just-created symlink '{}'",
|
"will not copy '{}' through just-created symlink '{}'",
|
||||||
source.display(),
|
source.display(),
|
||||||
dest.display()
|
dest.display()
|
||||||
|
@ -2269,7 +2323,7 @@ fn copy_file(
|
||||||
// Example: "cp file1 dir1/file1 tmp" where "tmp" is a directory containing a symlink "file1" pointing to a file named "foo".
|
// Example: "cp file1 dir1/file1 tmp" where "tmp" is a directory containing a symlink "file1" pointing to a file named "foo".
|
||||||
// foo will contain the contents of "file1" and "dir1/file1" will not be copied over to "tmp/file1"
|
// foo will contain the contents of "file1" and "dir1/file1" will not be copied over to "tmp/file1"
|
||||||
if copied_destinations.contains(dest) {
|
if copied_destinations.contains(dest) {
|
||||||
return Err(Error::Error(format!(
|
return Err(CpError::Error(format!(
|
||||||
"will not copy '{}' through just-created symlink '{}'",
|
"will not copy '{}' through just-created symlink '{}'",
|
||||||
source.display(),
|
source.display(),
|
||||||
dest.display()
|
dest.display()
|
||||||
|
@ -2286,7 +2340,7 @@ fn copy_file(
|
||||||
&& !is_symlink_loop(dest)
|
&& !is_symlink_loop(dest)
|
||||||
&& std::env::var_os("POSIXLY_CORRECT").is_none()
|
&& std::env::var_os("POSIXLY_CORRECT").is_none()
|
||||||
{
|
{
|
||||||
return Err(Error::Error(format!(
|
return Err(CpError::Error(format!(
|
||||||
"not writing through dangling symlink '{}'",
|
"not writing through dangling symlink '{}'",
|
||||||
dest.display()
|
dest.display()
|
||||||
)));
|
)));
|
||||||
|
@ -2368,7 +2422,7 @@ fn copy_file(
|
||||||
// in the destination tree.
|
// in the destination tree.
|
||||||
if let Some(new_source) = copied_files.get(
|
if let Some(new_source) = copied_files.get(
|
||||||
&FileInformation::from_path(source, options.dereference(source_in_command_line))
|
&FileInformation::from_path(source, options.dereference(source_in_command_line))
|
||||||
.context(format!("cannot stat {}", source.quote()))?,
|
.map_err(|e| CpError::IoErrContext(e, format!("cannot stat {}", source.quote())))?,
|
||||||
) {
|
) {
|
||||||
fs::hard_link(new_source, dest)?;
|
fs::hard_link(new_source, dest)?;
|
||||||
|
|
||||||
|
@ -2462,7 +2516,7 @@ fn copy_file(
|
||||||
if let Err(e) =
|
if let Err(e) =
|
||||||
uucore::selinux::set_selinux_security_context(dest, options.context.as_ref())
|
uucore::selinux::set_selinux_security_context(dest, options.context.as_ref())
|
||||||
{
|
{
|
||||||
return Err(Error::Error(format!("SELinux error: {}", e)));
|
return Err(CpError::Error(format!("SELinux error: {}", e)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2542,7 +2596,7 @@ fn copy_helper(
|
||||||
}
|
}
|
||||||
|
|
||||||
if path_ends_with_terminator(dest) && !dest.is_dir() {
|
if path_ends_with_terminator(dest) && !dest.is_dir() {
|
||||||
return Err(Error::NotADirectory(dest.to_path_buf()));
|
return Err(CpError::NotADirectory(dest.to_path_buf()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if source_is_fifo && options.recursive && !options.copy_contents {
|
if source_is_fifo && options.recursive && !options.copy_contents {
|
||||||
|
|
|
@ -14,11 +14,11 @@ use std::os::unix::io::AsRawFd;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use uucore::buf_copy;
|
use uucore::buf_copy;
|
||||||
|
|
||||||
use quick_error::ResultExt;
|
|
||||||
|
|
||||||
use uucore::mode::get_umask;
|
use uucore::mode::get_umask;
|
||||||
|
|
||||||
use crate::{CopyDebug, CopyResult, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode};
|
use crate::{
|
||||||
|
CopyDebug, CopyResult, CpError, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode,
|
||||||
|
};
|
||||||
|
|
||||||
/// The fallback behavior for [`clone`] on failed system call.
|
/// The fallback behavior for [`clone`] on failed system call.
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
@ -404,7 +404,7 @@ pub(crate) fn copy_on_write(
|
||||||
return Err("`--reflink=always` can be used only with --sparse=auto".into());
|
return Err("`--reflink=always` can be used only with --sparse=auto".into());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
result.context(context)?;
|
result.map_err(|e| CpError::IoErrContext(e, context.to_owned()))?;
|
||||||
Ok(copy_debug)
|
Ok(copy_debug)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,11 +9,12 @@ use std::os::unix::ffi::OsStrExt;
|
||||||
use std::os::unix::fs::OpenOptionsExt;
|
use std::os::unix::fs::OpenOptionsExt;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use quick_error::ResultExt;
|
|
||||||
use uucore::buf_copy;
|
use uucore::buf_copy;
|
||||||
use uucore::mode::get_umask;
|
use uucore::mode::get_umask;
|
||||||
|
|
||||||
use crate::{CopyDebug, CopyResult, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode};
|
use crate::{
|
||||||
|
CopyDebug, CopyResult, CpError, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode,
|
||||||
|
};
|
||||||
|
|
||||||
/// Copies `source` to `dest` using copy-on-write if possible.
|
/// Copies `source` to `dest` using copy-on-write if possible.
|
||||||
///
|
///
|
||||||
|
@ -104,14 +105,15 @@ pub(crate) fn copy_on_write(
|
||||||
|
|
||||||
let context = buf_copy::copy_stream(&mut src_file, &mut dst_file)
|
let context = buf_copy::copy_stream(&mut src_file, &mut dst_file)
|
||||||
.map_err(|_| std::io::Error::from(std::io::ErrorKind::Other))
|
.map_err(|_| std::io::Error::from(std::io::ErrorKind::Other))
|
||||||
.context(context)?;
|
.map_err(|e| CpError::IoErrContext(e, context.to_owned()))?;
|
||||||
|
|
||||||
if source_is_fifo {
|
if source_is_fifo {
|
||||||
dst_file.set_permissions(src_file.metadata()?.permissions())?;
|
dst_file.set_permissions(src_file.metadata()?.permissions())?;
|
||||||
}
|
}
|
||||||
context
|
context
|
||||||
} else {
|
} else {
|
||||||
fs::copy(source, dest).context(context)?
|
fs::copy(source, dest)
|
||||||
|
.map_err(|e| CpError::IoErrContext(e, context.to_owned()))?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use quick_error::ResultExt;
|
use crate::{
|
||||||
|
CopyDebug, CopyResult, CpError, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode,
|
||||||
use crate::{CopyDebug, CopyResult, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode};
|
};
|
||||||
|
|
||||||
/// Copies `source` to `dest` for systems without copy-on-write
|
/// Copies `source` to `dest` for systems without copy-on-write
|
||||||
pub(crate) fn copy_on_write(
|
pub(crate) fn copy_on_write(
|
||||||
|
@ -31,7 +31,7 @@ pub(crate) fn copy_on_write(
|
||||||
reflink: OffloadReflinkDebug::Unsupported,
|
reflink: OffloadReflinkDebug::Unsupported,
|
||||||
sparse_detection: SparseDebug::Unsupported,
|
sparse_detection: SparseDebug::Unsupported,
|
||||||
};
|
};
|
||||||
fs::copy(source, dest).context(context)?;
|
fs::copy(source, dest).map_err(|e| CpError::IoErrContext(e, context.to_owned()))?;
|
||||||
|
|
||||||
Ok(copy_debug)
|
Ok(copy_debug)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,12 @@ use std::fs::{self, File, OpenOptions};
|
||||||
use std::os::unix::fs::OpenOptionsExt;
|
use std::os::unix::fs::OpenOptionsExt;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use quick_error::ResultExt;
|
|
||||||
use uucore::buf_copy;
|
use uucore::buf_copy;
|
||||||
use uucore::mode::get_umask;
|
use uucore::mode::get_umask;
|
||||||
|
|
||||||
use crate::{CopyDebug, CopyResult, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode};
|
use crate::{
|
||||||
|
CopyDebug, CopyResult, CpError, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode,
|
||||||
|
};
|
||||||
|
|
||||||
/// Copies `source` to `dest` for systems without copy-on-write
|
/// Copies `source` to `dest` for systems without copy-on-write
|
||||||
pub(crate) fn copy_on_write(
|
pub(crate) fn copy_on_write(
|
||||||
|
@ -48,7 +49,7 @@ pub(crate) fn copy_on_write(
|
||||||
|
|
||||||
buf_copy::copy_stream(&mut src_file, &mut dst_file)
|
buf_copy::copy_stream(&mut src_file, &mut dst_file)
|
||||||
.map_err(|_| std::io::Error::from(std::io::ErrorKind::Other))
|
.map_err(|_| std::io::Error::from(std::io::ErrorKind::Other))
|
||||||
.context(context)?;
|
.map_err(|e| CpError::IoErrContext(e, context.to_owned()))?;
|
||||||
|
|
||||||
if source_is_fifo {
|
if source_is_fifo {
|
||||||
dst_file.set_permissions(src_file.metadata()?.permissions())?;
|
dst_file.set_permissions(src_file.metadata()?.permissions())?;
|
||||||
|
@ -56,7 +57,7 @@ pub(crate) fn copy_on_write(
|
||||||
return Ok(copy_debug);
|
return Ok(copy_debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
fs::copy(source, dest).context(context)?;
|
fs::copy(source, dest).map_err(|e| CpError::IoErrContext(e, context.to_owned()))?;
|
||||||
|
|
||||||
Ok(copy_debug)
|
Ok(copy_debug)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue