diff --git a/src/uu/cp/src/copydir.rs b/src/uu/cp/src/copydir.rs index aaeb73f5acf..818b0d2229a 100644 --- a/src/uu/cp/src/copydir.rs +++ b/src/uu/cp/src/copydir.rs @@ -25,7 +25,7 @@ use walkdir::{DirEntry, WalkDir}; use crate::{ aligned_ancestors, context_for, copy_attributes, copy_file, copy_link, preserve_hardlinks, - CopyResult, Error, Options, TargetSlice, + CopyResult, Error, Options, }; /// Ensure a Windows path starts with a `\\?`. @@ -307,7 +307,7 @@ fn copy_direntry( pub(crate) fn copy_directory( progress_bar: &Option, root: &Path, - target: &TargetSlice, + target: &Path, options: &Options, symlinked_files: &mut HashSet, source_in_command_line: bool, diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index de9dd1c919f..e0a40d56e87 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -9,10 +9,11 @@ // For the full copyright and license information, please view the LICENSE 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 +// spell-checker:ignore (ToDO) copydir ficlone fiemap ftruncate linkgs lstat nlink nlinks pathbuf pwrite reflink strs xattrs symlinked deduplicated advcpmv nushell use quick_error::quick_error; use std::borrow::Cow; +use std::cmp::Ordering; use std::collections::HashSet; use std::env; #[cfg(not(windows))] @@ -51,6 +52,7 @@ use crate::copydir::copy_directory; mod copydir; mod platform; + quick_error! { #[derive(Debug)] pub enum Error { @@ -108,12 +110,8 @@ impl UError for Error { } pub type CopyResult = Result; -pub type Source = PathBuf; -pub type SourceSlice = Path; -pub type Target = PathBuf; -pub type TargetSlice = Path; -/// Specifies whether when overwrite files +/// Specifies how to overwrite files. #[derive(Clone, Copy, Eq, PartialEq)] pub enum ClobberMode { Force, @@ -121,7 +119,7 @@ pub enum ClobberMode { Standard, } -/// Specifies whether when overwrite files +/// Specifies whether files should be overwritten. #[derive(Clone, Copy, Eq, PartialEq)] pub enum OverwriteMode { /// [Default] Always overwrite existing files @@ -148,12 +146,13 @@ pub enum SparseMode { Never, } -/// Specifies the expected file type of copy target +/// The expected file type of copy target pub enum TargetType { Directory, File, } +/// Copy action to perform pub enum CopyMode { Link, SymLink, @@ -162,77 +161,128 @@ pub enum CopyMode { AttrOnly, } +/// Preservation settings for various attributes +/// +/// It should be derived from options as follows: +/// +/// - if there is a list of attributes to preserve (i.e. `--preserve=ATTR_LIST`) parse that list with [`Attributes::parse_iter`], +/// - if `-p` or `--preserve` is given without arguments, use [`Attributes::DEFAULT`], +/// - if `-a`/`--archive` is passed, use [`Attributes::ALL`], +/// - if `-d` is passed use [`Attributes::LINKS`], +/// - otherwise, use [`Attributes::NONE`]. +/// +/// For full compatibility with GNU, these options should also combine. We +/// currently only do a best effort imitation of that behavior, because it is +/// difficult to achieve in clap, especially with `--no-preserve`. #[derive(Debug)] pub struct Attributes { #[cfg(unix)] - ownership: Preserve, - mode: Preserve, - timestamps: Preserve, - context: Preserve, - links: Preserve, - xattr: Preserve, + pub ownership: Preserve, + pub mode: Preserve, + pub timestamps: Preserve, + pub context: Preserve, + pub links: Preserve, + pub xattr: Preserve, } -impl Attributes { - pub(crate) fn max(&mut self, other: Self) { - #[cfg(unix)] - { - self.ownership = self.ownership.max(other.ownership); - } - self.mode = self.mode.max(other.mode); - self.timestamps = self.timestamps.max(other.timestamps); - self.context = self.context.max(other.context); - self.links = self.links.max(other.links); - self.xattr = self.xattr.max(other.xattr); - } -} - -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Preserve { No, Yes { required: bool }, } -impl Preserve { - /// Preservation level should only increase, with no preservation being the lowest option, - /// preserve but don't require - middle, and preserve and require - top. - pub(crate) fn max(&self, other: Self) -> Self { +impl PartialOrd for Preserve { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Preserve { + fn cmp(&self, other: &Self) -> Ordering { match (self, other) { - (Self::Yes { required: true }, _) | (_, Self::Yes { required: true }) => { - Self::Yes { required: true } - } - (Self::Yes { required: false }, _) | (_, Self::Yes { required: false }) => { - Self::Yes { required: false } - } - _ => Self::No, + (Self::No, Self::No) => Ordering::Equal, + (Self::Yes { .. }, Self::No) => Ordering::Greater, + (Self::No, Self::Yes { .. }) => Ordering::Less, + ( + Self::Yes { required: req_self }, + Self::Yes { + required: req_other, + }, + ) => req_self.cmp(req_other), } } } -/// Re-usable, extensible copy options +/// Options for the `cp` command +/// +/// All options are public so that the options can be programmatically +/// constructed by other crates, such as nushell. That means that this struct +/// is part of our public API. It should therefore not be changed without good +/// reason. +/// +/// The fields are documented with the arguments that determine their value. #[allow(dead_code)] pub struct Options { - attributes_only: bool, - backup: BackupMode, - copy_contents: bool, - cli_dereference: bool, - copy_mode: CopyMode, - dereference: bool, - no_target_dir: bool, - one_file_system: bool, - overwrite: OverwriteMode, - parents: bool, - sparse_mode: SparseMode, - strip_trailing_slashes: bool, - reflink_mode: ReflinkMode, - attributes: Attributes, - recursive: bool, - backup_suffix: String, - target_dir: Option, - update: UpdateMode, - debug: bool, - verbose: bool, - progress_bar: bool, + /// `--attributes-only` + pub attributes_only: bool, + /// `--backup[=CONTROL]`, `-b` + pub backup: BackupMode, + /// `--copy-contents` + pub copy_contents: bool, + /// `-H` + pub cli_dereference: bool, + /// Determines the type of copying that should be done + /// + /// Set by the following arguments: + /// - `-l`, `--link`: [`CopyMode::Link`] + /// - `-s`, `--symbolic-link`: [`CopyMode::SymLink`] + /// - `-u`, `--update[=WHEN]`: [`CopyMode::Update`] + /// - `--attributes-only`: [`CopyMode::AttrOnly`] + /// - otherwise: [`CopyMode::Copy`] + pub copy_mode: CopyMode, + /// `-L`, `--dereference` + pub dereference: bool, + /// `-T`, `--no-target-dir` + pub no_target_dir: bool, + /// `-x`, `--one-file-system` + pub one_file_system: bool, + /// Specifies what to do with an existing destination + /// + /// Set by the following arguments: + /// - `-i`, `--interactive`: [`OverwriteMode::Interactive`] + /// - `-n`, `--no-clobber`: [`OverwriteMode::NoClobber`] + /// - otherwise: [`OverwriteMode::Clobber`] + /// + /// The `Interactive` and `Clobber` variants have a [`ClobberMode`] argument, + /// set by the following arguments: + /// - `-f`, `--force`: [`ClobberMode::Force`] + /// - `--remove-destination`: [`ClobberMode::RemoveDestination`] + /// - otherwise: [`ClobberMode::Standard`] + pub overwrite: OverwriteMode, + /// `--parents` + pub parents: bool, + /// `--sparse[=WHEN]` + pub sparse_mode: SparseMode, + /// `--strip-trailing-slashes` + pub strip_trailing_slashes: bool, + /// `--reflink[=WHEN]` + pub reflink_mode: ReflinkMode, + /// `--preserve=[=ATTRIBUTE_LIST]` and `--no-preserve=ATTRIBUTE_LIST` + pub attributes: Attributes, + /// `-R`, `-r`, `--recursive` + pub recursive: bool, + /// `-S`, `--suffix` + pub backup_suffix: String, + /// `-t`, `--target-directory` + pub target_dir: Option, + /// `--update[=UPDATE]` + pub update: UpdateMode, + /// `--debug` + pub debug: bool, + /// `-v`, `--verbose` + pub verbose: bool, + /// `-g`, `--progress` + pub progress_bar: bool, } /// Enum representing various debug states of the offload and reflink actions. @@ -741,67 +791,90 @@ impl CopyMode { } impl Attributes { + pub const ALL: Self = Self { + #[cfg(unix)] + ownership: Preserve::Yes { required: true }, + mode: Preserve::Yes { required: true }, + timestamps: Preserve::Yes { required: true }, + context: { + #[cfg(feature = "feat_selinux")] + { + Preserve::Yes { required: false } + } + #[cfg(not(feature = "feat_selinux"))] + { + Preserve::No + } + }, + links: Preserve::Yes { required: true }, + xattr: Preserve::Yes { required: false }, + }; + + pub const NONE: Self = Self { + #[cfg(unix)] + ownership: Preserve::No, + mode: Preserve::No, + timestamps: Preserve::No, + context: Preserve::No, + links: Preserve::No, + xattr: Preserve::No, + }; + // TODO: ownership is required if the user is root, for non-root users it's not required. - // See: https://github.com/coreutils/coreutils/blob/master/src/copy.c#L3181 + pub const DEFAULT: Self = Self { + #[cfg(unix)] + ownership: Preserve::Yes { required: true }, + mode: Preserve::Yes { required: true }, + timestamps: Preserve::Yes { required: true }, + ..Self::NONE + }; - fn all() -> Self { - Self { - #[cfg(unix)] - ownership: Preserve::Yes { required: true }, - mode: Preserve::Yes { required: true }, - timestamps: Preserve::Yes { required: true }, - context: { - #[cfg(feature = "feat_selinux")] - { - Preserve::Yes { required: false } - } - #[cfg(not(feature = "feat_selinux"))] - { - Preserve::No - } - }, - links: Preserve::Yes { required: true }, - xattr: Preserve::Yes { required: false }, - } - } + pub const LINKS: Self = Self { + links: Preserve::Yes { required: true }, + ..Self::NONE + }; - fn default() -> Self { + pub fn union(self, other: &Self) -> Self { Self { #[cfg(unix)] - ownership: Preserve::Yes { required: true }, - mode: Preserve::Yes { required: true }, - timestamps: Preserve::Yes { required: true }, - context: Preserve::No, - links: Preserve::No, - xattr: Preserve::No, + ownership: self.ownership.max(other.ownership), + context: self.context.max(other.context), + timestamps: self.timestamps.max(other.timestamps), + mode: self.mode.max(other.mode), + links: self.links.max(other.links), + xattr: self.xattr.max(other.xattr), } } - fn none() -> Self { - Self { - #[cfg(unix)] - ownership: Preserve::No, - mode: Preserve::No, - timestamps: Preserve::No, - context: Preserve::No, - links: Preserve::No, - xattr: Preserve::No, + pub fn parse_iter(values: impl Iterator) -> Result + where + T: AsRef, + { + let mut new = Self::NONE; + for value in values { + new = new.union(&Self::parse_single_string(value.as_ref())?); } + Ok(new) } /// Tries to match string containing a parameter to preserve with the corresponding entry in the /// Attributes struct. - fn try_set_from_string(&mut self, value: &str) -> Result<(), Error> { - let preserve_yes_required = Preserve::Yes { required: true }; + fn parse_single_string(value: &str) -> Result { + let value = value.to_lowercase(); - match &*value.to_lowercase() { - "mode" => self.mode = preserve_yes_required, + if value == "all" { + return Ok(Self::ALL); + } + + let mut new = Self::NONE; + let attribute = match value.as_ref() { + "mode" => &mut new.mode, #[cfg(unix)] - "ownership" => self.ownership = preserve_yes_required, - "timestamps" => self.timestamps = preserve_yes_required, - "context" => self.context = preserve_yes_required, - "link" | "links" => self.links = preserve_yes_required, - "xattr" => self.xattr = preserve_yes_required, + "ownership" => &mut new.ownership, + "timestamps" => &mut new.timestamps, + "context" => &mut new.context, + "link" | "links" => &mut new.links, + "xattr" => &mut new.xattr, _ => { return Err(Error::InvalidArgument(format!( "invalid attribute {}", @@ -809,7 +882,10 @@ impl Attributes { ))); } }; - Ok(()) + + *attribute = Preserve::Yes { required: true }; + + Ok(new) } } @@ -858,39 +934,22 @@ impl Options { }; // Parse attributes to preserve - let attributes: Attributes = if matches.contains_id(options::PRESERVE) { - match matches.get_many::(options::PRESERVE) { - None => Attributes::default(), - Some(attribute_strs) => { - let mut attributes: Attributes = Attributes::none(); - let mut attributes_empty = true; - for attribute_str in attribute_strs { - attributes_empty = false; - if attribute_str == "all" { - attributes.max(Attributes::all()); - } else { - attributes.try_set_from_string(attribute_str)?; - } - } - // `--preserve` case, use the defaults - if attributes_empty { - Attributes::default() - } else { - attributes - } - } + let attributes = if let Some(attribute_strs) = matches.get_many::(options::PRESERVE) + { + if attribute_strs.len() == 0 { + Attributes::DEFAULT + } else { + Attributes::parse_iter(attribute_strs)? } } else if matches.get_flag(options::ARCHIVE) { // --archive is used. Same as --preserve=all - Attributes::all() + Attributes::ALL } else if matches.get_flag(options::NO_DEREFERENCE_PRESERVE_LINKS) { - let mut attributes = Attributes::none(); - attributes.links = Preserve::Yes { required: true }; - attributes + Attributes::LINKS } else if matches.get_flag(options::PRESERVE_DEFAULT_ATTRIBUTES) { - Attributes::default() + Attributes::DEFAULT } else { - Attributes::none() + Attributes::NONE }; #[cfg(not(feature = "feat_selinux"))] @@ -1000,7 +1059,7 @@ impl TargetType { /// /// Treat target as a dir if we have multiple sources or the target /// exists and already is a directory - fn determine(sources: &[Source], target: &TargetSlice) -> Self { + fn determine(sources: &[PathBuf], target: &Path) -> Self { if sources.len() > 1 || target.is_dir() { Self::Directory } else { @@ -1010,7 +1069,10 @@ impl TargetType { } /// Returns tuple of (Source paths, Target) -fn parse_path_args(mut paths: Vec, options: &Options) -> CopyResult<(Vec, Target)> { +fn parse_path_args( + mut paths: Vec, + options: &Options, +) -> CopyResult<(Vec, PathBuf)> { if paths.is_empty() { // No files specified return Err("missing file operand".into()); @@ -1122,14 +1184,13 @@ fn show_error_if_needed(error: &Error) { } } -/// Copy all `sources` to `target`. Returns an -/// `Err(Error::NotAllFilesCopied)` if at least one non-fatal error was -/// encountered. +/// Copy all `sources` to `target`. /// -/// Behavior depends on path`options`, see [`Options`] for details. +/// Returns an `Err(Error::NotAllFilesCopied)` if at least one non-fatal error +/// was encountered. /// -/// [`Options`]: ./struct.Options.html -fn copy(sources: &[Source], target: &TargetSlice, options: &Options) -> CopyResult<()> { +/// Behavior is determined by the `options` parameter, see [`Options`] for details. +pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult<()> { let target_type = TargetType::determine(sources, target); verify_target_type(target, &target_type)?; @@ -1192,7 +1253,7 @@ fn copy(sources: &[Source], target: &TargetSlice, options: &Options) -> CopyResu fn construct_dest_path( source_path: &Path, - target: &TargetSlice, + target: &Path, target_type: &TargetType, options: &Options, ) -> CopyResult { @@ -1223,8 +1284,8 @@ fn construct_dest_path( fn copy_source( progress_bar: &Option, - source: &SourceSlice, - target: &TargetSlice, + source: &Path, + target: &Path, target_type: &TargetType, options: &Options, symlinked_files: &mut HashSet,