1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-29 12:07:46 +00:00

cp: refactor attribute parsing

Clean up the parsing of the attributes to
preserve. There are several improvements here: Preserve now uses  `max`
from Ord, the `max` method is now called `union` and does not mutate,
the parse loop is extracted into a new function `parse_iter`, the `all`
and `none` methods are replaced with const values. Finally
all fields of Attributes are now public.
This commit is contained in:
Terts Diepraam 2023-08-16 19:04:18 +02:00
parent ffa08f4741
commit 597f51670c

View file

@ -13,6 +13,7 @@
use quick_error::quick_error; use quick_error::quick_error;
use std::borrow::Cow; use std::borrow::Cow;
use std::cmp::Ordering;
use std::collections::HashSet; use std::collections::HashSet;
use std::env; use std::env;
#[cfg(not(windows))] #[cfg(not(windows))]
@ -161,49 +162,53 @@ pub enum CopyMode {
} }
/// Preservation settings for various attributes /// 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)] #[derive(Debug)]
pub struct Attributes { pub struct Attributes {
#[cfg(unix)] #[cfg(unix)]
ownership: Preserve, pub ownership: Preserve,
mode: Preserve, pub mode: Preserve,
timestamps: Preserve, pub timestamps: Preserve,
context: Preserve, pub context: Preserve,
links: Preserve, pub links: Preserve,
xattr: Preserve, pub xattr: Preserve,
} }
impl Attributes { #[derive(Clone, Copy, Debug, PartialEq, Eq)]
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)]
pub enum Preserve { pub enum Preserve {
No, No,
Yes { required: bool }, Yes { required: bool },
} }
impl Preserve { impl PartialOrd for Preserve {
/// Preservation level should only increase, with no preservation being the lowest option, fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
/// preserve but don't require - middle, and preserve and require - top. Some(self.cmp(other))
pub(crate) fn max(&self, other: Self) -> Self { }
}
impl Ord for Preserve {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) { match (self, other) {
(Self::Yes { required: true }, _) | (_, Self::Yes { required: true }) => { (Self::No, Self::No) => Ordering::Equal,
Self::Yes { required: true } (Self::Yes { .. }, Self::No) => Ordering::Greater,
} (Self::No, Self::Yes { .. }) => Ordering::Less,
(Self::Yes { required: false }, _) | (_, Self::Yes { required: false }) => { (
Self::Yes { required: false } Self::Yes { required: req_self },
} Self::Yes {
_ => Self::No, required: req_other,
},
) => req_self.cmp(req_other),
} }
} }
} }
@ -786,67 +791,90 @@ impl CopyMode {
} }
impl Attributes { 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. // 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 { pub const LINKS: Self = Self {
links: Preserve::Yes { required: true },
..Self::NONE
};
pub fn union(self, other: &Self) -> Self {
Self { Self {
#[cfg(unix)] #[cfg(unix)]
ownership: Preserve::Yes { required: true }, ownership: self.ownership.max(other.ownership),
mode: Preserve::Yes { required: true }, context: self.context.max(other.context),
timestamps: Preserve::Yes { required: true }, timestamps: self.timestamps.max(other.timestamps),
context: { mode: self.mode.max(other.mode),
#[cfg(feature = "feat_selinux")] links: self.links.max(other.links),
{ xattr: self.xattr.max(other.xattr),
Preserve::Yes { required: false }
}
#[cfg(not(feature = "feat_selinux"))]
{
Preserve::No
}
},
links: Preserve::Yes { required: true },
xattr: Preserve::Yes { required: false },
} }
} }
fn default() -> Self { pub fn parse_iter<T>(values: impl Iterator<Item = T>) -> Result<Self, Error>
Self { where
#[cfg(unix)] T: AsRef<str>,
ownership: Preserve::Yes { required: true }, {
mode: Preserve::Yes { required: true }, let mut new = Self::NONE;
timestamps: Preserve::Yes { required: true }, for value in values {
context: Preserve::No, new = new.union(&Self::parse_single_string(value.as_ref())?);
links: Preserve::No,
xattr: Preserve::No,
}
}
fn none() -> Self {
Self {
#[cfg(unix)]
ownership: Preserve::No,
mode: Preserve::No,
timestamps: Preserve::No,
context: Preserve::No,
links: Preserve::No,
xattr: Preserve::No,
} }
Ok(new)
} }
/// 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 try_set_from_string(&mut self, value: &str) -> Result<(), Error> { fn parse_single_string(value: &str) -> Result<Self, Error> {
let preserve_yes_required = Preserve::Yes { required: true }; let value = value.to_lowercase();
match &*value.to_lowercase() { if value == "all" {
"mode" => self.mode = preserve_yes_required, return Ok(Self::ALL);
}
let mut new = Self::NONE;
let attribute = match value.as_ref() {
"mode" => &mut new.mode,
#[cfg(unix)] #[cfg(unix)]
"ownership" => self.ownership = preserve_yes_required, "ownership" => &mut new.ownership,
"timestamps" => self.timestamps = preserve_yes_required, "timestamps" => &mut new.timestamps,
"context" => self.context = preserve_yes_required, "context" => &mut new.context,
"link" | "links" => self.links = preserve_yes_required, "link" | "links" => &mut new.links,
"xattr" => self.xattr = preserve_yes_required, "xattr" => &mut new.xattr,
_ => { _ => {
return Err(Error::InvalidArgument(format!( return Err(Error::InvalidArgument(format!(
"invalid attribute {}", "invalid attribute {}",
@ -854,7 +882,10 @@ impl Attributes {
))); )));
} }
}; };
Ok(())
*attribute = Preserve::Yes { required: true };
Ok(new)
} }
} }
@ -903,39 +934,22 @@ impl Options {
}; };
// Parse attributes to preserve // Parse attributes to preserve
let attributes: Attributes = if matches.contains_id(options::PRESERVE) { let attributes = if let Some(attribute_strs) = matches.get_many::<String>(options::PRESERVE)
match matches.get_many::<String>(options::PRESERVE) { {
None => Attributes::default(), if attribute_strs.len() == 0 {
Some(attribute_strs) => { Attributes::DEFAULT
let mut attributes: Attributes = Attributes::none(); } else {
let mut attributes_empty = true; Attributes::parse_iter(attribute_strs)?
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
}
}
} }
} else if matches.get_flag(options::ARCHIVE) { } else if matches.get_flag(options::ARCHIVE) {
// --archive is used. Same as --preserve=all // --archive is used. Same as --preserve=all
Attributes::all() Attributes::ALL
} else if matches.get_flag(options::NO_DEREFERENCE_PRESERVE_LINKS) { } else if matches.get_flag(options::NO_DEREFERENCE_PRESERVE_LINKS) {
let mut attributes = Attributes::none(); Attributes::LINKS
attributes.links = Preserve::Yes { required: true };
attributes
} else if matches.get_flag(options::PRESERVE_DEFAULT_ATTRIBUTES) { } else if matches.get_flag(options::PRESERVE_DEFAULT_ATTRIBUTES) {
Attributes::default() Attributes::DEFAULT
} else { } else {
Attributes::none() Attributes::NONE
}; };
#[cfg(not(feature = "feat_selinux"))] #[cfg(not(feature = "feat_selinux"))]