mirror of
https://github.com/uutils/coreutils
synced 2024-11-05 14:21:32 +00:00
cp: correctly copy mode, ownership, acl and context
Fix a mix-up between ownership and mode. The latter (mode / file permissions) can also be set on windows (which however only affects the read-only flag), while there doesn't seem to be a straight-forward way to change file ownership on windows. Copy the acl as well when copying the mode. This is a non-default feature and can be enabled with --features feat_acl, because it doesn't seem to work on CI. It is only available for unix so far. Copy the SELinux context if possible.
This commit is contained in:
parent
575fbd4cb7
commit
8b74562820
8 changed files with 303 additions and 51 deletions
|
@ -35,6 +35,7 @@ falsey
|
|||
fileio
|
||||
flamegraph
|
||||
fullblock
|
||||
getfacl
|
||||
gibi
|
||||
gibibytes
|
||||
glob
|
||||
|
@ -49,6 +50,7 @@ iflag
|
|||
iflags
|
||||
kibi
|
||||
kibibytes
|
||||
libacl
|
||||
lcase
|
||||
lossily
|
||||
mebi
|
||||
|
@ -91,6 +93,7 @@ seedable
|
|||
semver
|
||||
semiprime
|
||||
semiprimes
|
||||
setfacl
|
||||
shortcode
|
||||
shortcodes
|
||||
siginfo
|
||||
|
|
|
@ -16,6 +16,7 @@ chrono
|
|||
conv
|
||||
corasick
|
||||
crossterm
|
||||
exacl
|
||||
filetime
|
||||
formatteriteminfo
|
||||
fsext
|
||||
|
|
112
Cargo.lock
generated
112
Cargo.lock
generated
|
@ -650,6 +650,17 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derivative"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.9",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.12"
|
||||
|
@ -721,6 +732,21 @@ dependencies = [
|
|||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exacl"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "769bbd173781e84865b957cf83449f0d2869f4c9d2f191cbbffffb3d9751ba2b"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"log",
|
||||
"nix 0.21.0",
|
||||
"num_enum",
|
||||
"scopeguard",
|
||||
"serde",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fake-simd"
|
||||
version = "0.1.2"
|
||||
|
@ -964,9 +990,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.85"
|
||||
version = "0.2.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3"
|
||||
checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
|
@ -1115,6 +1141,19 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c3728fec49d363a50a8828a190b379a446cc5cf085c06259bbbeb34447e4ec7"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cc",
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nodrop"
|
||||
version = "0.1.14"
|
||||
|
@ -1182,6 +1221,28 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9bd055fb730c4f8f4f57d45d35cd6b3f0980535b056dc7ff119cee6a66ed6f"
|
||||
dependencies = [
|
||||
"derivative",
|
||||
"num_enum_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote 1.0.9",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "number_prefix"
|
||||
version = "0.4.0"
|
||||
|
@ -1349,6 +1410,16 @@ dependencies = [
|
|||
"output_vt100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fdbd1df62156fbc5945f4762632564d7d038153091c3fcf1067f6aef7cff92"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
|
@ -1706,6 +1777,26 @@ dependencies = [
|
|||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.130"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.130"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.9",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.6.0"
|
||||
|
@ -1981,6 +2072,15 @@ dependencies = [
|
|||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.13.0"
|
||||
|
@ -2196,10 +2296,12 @@ name = "uu_cp"
|
|||
version = "0.0.7"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"exacl",
|
||||
"filetime",
|
||||
"ioctl-sys",
|
||||
"libc",
|
||||
"quick-error 1.2.3",
|
||||
"selinux",
|
||||
"uucore",
|
||||
"uucore_procs",
|
||||
"walkdir",
|
||||
|
@ -3192,6 +3294,12 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.2"
|
||||
|
|
|
@ -147,7 +147,12 @@ feat_os_unix_musl = [
|
|||
# NOTE:
|
||||
# The selinux(-sys) crate requires `libselinux` headers and shared library to be accessible in the C toolchain at compile time.
|
||||
# Running a uutils compiled with `feat_selinux` requires an SELinux enabled Kernel at run time.
|
||||
feat_selinux = ["id/selinux", "selinux", "feat_require_selinux"]
|
||||
feat_selinux = ["cp/selinux", "id/selinux", "selinux", "feat_require_selinux"]
|
||||
# "feat_acl" == set of utilities providing support for acl (access control lists) if enabled with `--features feat_acl`.
|
||||
# NOTE:
|
||||
# On linux, the posix-acl/acl-sys crate requires `libacl` headers and shared library to be accessible in the C toolchain at compile time.
|
||||
# On FreeBSD and macOS this is not required.
|
||||
feat_acl = ["cp/feat_acl"]
|
||||
## feature sets with requirements (restricting cross-platform availability)
|
||||
#
|
||||
# ** NOTE: these `feat_require_...` sets should be minimized as much as possible to encourage cross-platform availability of utilities
|
||||
|
|
|
@ -23,7 +23,8 @@ clap = { version = "2.33", features = ["wrap_help"] }
|
|||
filetime = "0.2"
|
||||
libc = "0.2.85"
|
||||
quick-error = "1.2.3"
|
||||
uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["fs"] }
|
||||
selinux = { version="0.2.3", optional=true }
|
||||
uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["fs", "perms"] }
|
||||
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
|
||||
walkdir = "2.2"
|
||||
|
||||
|
@ -35,7 +36,12 @@ winapi = { version="0.3", features=["fileapi"] }
|
|||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
xattr="0.2.1"
|
||||
exacl= { version = "0.6.0", optional=true }
|
||||
|
||||
[[bin]]
|
||||
name = "cp"
|
||||
path = "src/main.rs"
|
||||
|
||||
[features]
|
||||
feat_selinux = ["selinux"]
|
||||
feat_acl = ["exacl"]
|
||||
|
|
|
@ -18,6 +18,7 @@ extern crate quick_error;
|
|||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use quick_error::Context;
|
||||
#[cfg(windows)]
|
||||
use winapi::um::fileapi::CreateFileW;
|
||||
#[cfg(windows)]
|
||||
|
@ -67,6 +68,7 @@ quick_error! {
|
|||
IoErrContext(err: io::Error, path: String) {
|
||||
display("{}: {}", path, err)
|
||||
context(path: &'a str, err: io::Error) -> (err, path.to_owned())
|
||||
context(context: String, err: io::Error) -> (err, context)
|
||||
cause(err)
|
||||
}
|
||||
|
||||
|
@ -180,12 +182,15 @@ pub enum CopyMode {
|
|||
AttrOnly,
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
// The ordering here determines the order in which attributes are (re-)applied.
|
||||
// In particular, Ownership must be changed first to avoid interfering with mode change.
|
||||
#[derive(Clone, Eq, PartialEq, Debug, PartialOrd, Ord)]
|
||||
pub enum Attribute {
|
||||
#[cfg(unix)]
|
||||
Mode,
|
||||
Ownership,
|
||||
Mode,
|
||||
Timestamps,
|
||||
#[cfg(feature = "feat_selinux")]
|
||||
Context,
|
||||
Links,
|
||||
Xattr,
|
||||
|
@ -240,7 +245,7 @@ mod options {
|
|||
pub const LINK: &str = "link";
|
||||
pub const NO_CLOBBER: &str = "no-clobber";
|
||||
pub const NO_DEREFERENCE: &str = "no-dereference";
|
||||
pub const NO_DEREFERENCE_PRESERVE_LINKS: &str = "no-dereference-preserve-linkgs";
|
||||
pub const NO_DEREFERENCE_PRESERVE_LINKS: &str = "no-dereference-preserve-links";
|
||||
pub const NO_PRESERVE: &str = "no-preserve";
|
||||
pub const NO_TARGET_DIRECTORY: &str = "no-target-directory";
|
||||
pub const ONE_FILE_SYSTEM: &str = "one-file-system";
|
||||
|
@ -266,6 +271,7 @@ static PRESERVABLE_ATTRIBUTES: &[&str] = &[
|
|||
"mode",
|
||||
"ownership",
|
||||
"timestamps",
|
||||
#[cfg(feature = "feat_selinux")]
|
||||
"context",
|
||||
"links",
|
||||
"xattr",
|
||||
|
@ -273,18 +279,12 @@ static PRESERVABLE_ATTRIBUTES: &[&str] = &[
|
|||
];
|
||||
|
||||
#[cfg(not(unix))]
|
||||
static PRESERVABLE_ATTRIBUTES: &[&str] = &[
|
||||
"ownership",
|
||||
"timestamps",
|
||||
"context",
|
||||
"links",
|
||||
"xattr",
|
||||
"all",
|
||||
];
|
||||
static PRESERVABLE_ATTRIBUTES: &[&str] =
|
||||
&["mode", "timestamps", "context", "links", "xattr", "all"];
|
||||
|
||||
static DEFAULT_ATTRIBUTES: &[Attribute] = &[
|
||||
#[cfg(unix)]
|
||||
Attribute::Mode,
|
||||
#[cfg(unix)]
|
||||
Attribute::Ownership,
|
||||
Attribute::Timestamps,
|
||||
];
|
||||
|
@ -381,13 +381,13 @@ pub fn uu_app() -> App<'static, 'static> {
|
|||
.conflicts_with_all(&[options::PRESERVE_DEFAULT_ATTRIBUTES, options::NO_PRESERVE])
|
||||
// -d sets this option
|
||||
// --archive sets this option
|
||||
.help("Preserve the specified attributes (default: mode (unix only), ownership, timestamps), \
|
||||
.help("Preserve the specified attributes (default: mode, ownership (unix only), timestamps), \
|
||||
if possible additional attributes: context, links, xattr, all"))
|
||||
.arg(Arg::with_name(options::PRESERVE_DEFAULT_ATTRIBUTES)
|
||||
.short("-p")
|
||||
.long(options::PRESERVE_DEFAULT_ATTRIBUTES)
|
||||
.conflicts_with_all(&[options::PRESERVE, options::NO_PRESERVE, options::ARCHIVE])
|
||||
.help("same as --preserve=mode(unix only),ownership,timestamps"))
|
||||
.help("same as --preserve=mode,ownership(unix only),timestamps"))
|
||||
.arg(Arg::with_name(options::NO_PRESERVE)
|
||||
.long(options::NO_PRESERVE)
|
||||
.takes_value(true)
|
||||
|
@ -532,10 +532,11 @@ impl FromStr for Attribute {
|
|||
|
||||
fn from_str(value: &str) -> CopyResult<Attribute> {
|
||||
Ok(match &*value.to_lowercase() {
|
||||
#[cfg(unix)]
|
||||
"mode" => Attribute::Mode,
|
||||
#[cfg(unix)]
|
||||
"ownership" => Attribute::Ownership,
|
||||
"timestamps" => Attribute::Timestamps,
|
||||
#[cfg(feature = "feat_selinux")]
|
||||
"context" => Attribute::Context,
|
||||
"links" => Attribute::Links,
|
||||
"xattr" => Attribute::Xattr,
|
||||
|
@ -552,14 +553,16 @@ impl FromStr for Attribute {
|
|||
fn add_all_attributes() -> Vec<Attribute> {
|
||||
use Attribute::*;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let attr = vec![Ownership, Timestamps, Context, Xattr, Links];
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let mut attr = vec![Ownership, Timestamps, Context, Xattr, Links];
|
||||
|
||||
#[cfg(unix)]
|
||||
attr.insert(0, Mode);
|
||||
let attr = vec![
|
||||
#[cfg(unix)]
|
||||
Ownership,
|
||||
Mode,
|
||||
Timestamps,
|
||||
#[cfg(feature = "feat_selinux")]
|
||||
Context,
|
||||
Links,
|
||||
Xattr,
|
||||
];
|
||||
|
||||
attr
|
||||
}
|
||||
|
@ -602,7 +605,7 @@ impl Options {
|
|||
.map(ToString::to_string);
|
||||
|
||||
// Parse attributes to preserve
|
||||
let preserve_attributes: Vec<Attribute> = if matches.is_present(options::PRESERVE) {
|
||||
let mut preserve_attributes: Vec<Attribute> = if matches.is_present(options::PRESERVE) {
|
||||
match matches.values_of(options::PRESERVE) {
|
||||
None => DEFAULT_ATTRIBUTES.to_vec(),
|
||||
Some(attribute_strs) => {
|
||||
|
@ -629,6 +632,11 @@ impl Options {
|
|||
vec![]
|
||||
};
|
||||
|
||||
// Make sure ownership is changed before other attributes,
|
||||
// as chown clears some of the permission and therefore could undo previous changes
|
||||
// if not executed first.
|
||||
preserve_attributes.sort_unstable();
|
||||
|
||||
let options = Options {
|
||||
attributes_only: matches.is_present(options::ATTRIBUTES_ONLY),
|
||||
copy_contents: matches.is_present(options::COPY_CONTENTS),
|
||||
|
@ -1048,28 +1056,79 @@ impl OverwriteMode {
|
|||
}
|
||||
}
|
||||
|
||||
fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResult<()> {
|
||||
fn copy_attribute(
|
||||
source: &Path,
|
||||
dest: &Path,
|
||||
attribute: &Attribute,
|
||||
options: &Options,
|
||||
) -> CopyResult<()> {
|
||||
let context = &*format!("'{}' -> '{}'", source.display().to_string(), dest.display());
|
||||
let source_metadata = if options.dereference {
|
||||
source.metadata()
|
||||
} else {
|
||||
source.symlink_metadata()
|
||||
}
|
||||
.context(context)?;
|
||||
match *attribute {
|
||||
#[cfg(unix)]
|
||||
Attribute::Mode => {
|
||||
let mode = fs::metadata(source).context(context)?.permissions().mode();
|
||||
let mut dest_metadata = fs::metadata(source).context(context)?.permissions();
|
||||
dest_metadata.set_mode(mode);
|
||||
fs::set_permissions(dest, source_metadata.permissions()).context(context)?;
|
||||
dest.metadata().unwrap().permissions();
|
||||
// FIXME: Implement this for windows as well
|
||||
#[cfg(feature = "feat_acl")]
|
||||
exacl::getfacl(source, None)
|
||||
.and_then(|acl| exacl::setfacl(&[dest], &acl, None))
|
||||
.map_err(|err| Error::Error(err.to_string()))?;
|
||||
}
|
||||
#[cfg(unix)]
|
||||
Attribute::Ownership => {
|
||||
let metadata = fs::metadata(source).context(context)?;
|
||||
fs::set_permissions(dest, metadata.permissions()).context(context)?;
|
||||
use std::os::unix::prelude::MetadataExt;
|
||||
use uucore::perms::wrap_chown;
|
||||
use uucore::perms::Verbosity;
|
||||
use uucore::perms::VerbosityLevel;
|
||||
|
||||
let dest_uid = source_metadata.uid();
|
||||
let dest_gid = source_metadata.gid();
|
||||
|
||||
wrap_chown(
|
||||
dest,
|
||||
&dest.metadata().context(context)?,
|
||||
Some(dest_uid),
|
||||
Some(dest_gid),
|
||||
false,
|
||||
Verbosity {
|
||||
groups_only: false,
|
||||
level: VerbosityLevel::Normal,
|
||||
},
|
||||
)
|
||||
.map_err(Error::Error)?;
|
||||
}
|
||||
Attribute::Timestamps => {
|
||||
let metadata = fs::metadata(source)?;
|
||||
filetime::set_file_times(
|
||||
Path::new(dest),
|
||||
FileTime::from_last_access_time(&metadata),
|
||||
FileTime::from_last_modification_time(&metadata),
|
||||
FileTime::from_last_access_time(&source_metadata),
|
||||
FileTime::from_last_modification_time(&source_metadata),
|
||||
)?;
|
||||
}
|
||||
Attribute::Context => {}
|
||||
#[cfg(feature = "feat_selinux")]
|
||||
Attribute::Context => {
|
||||
let context = selinux::SecurityContext::of_path(source, options.dereference, false)
|
||||
.map_err(|e| {
|
||||
format!(
|
||||
"failed to get security context of {}: {}",
|
||||
source.display(),
|
||||
e
|
||||
)
|
||||
})?;
|
||||
if let Some(context) = context {
|
||||
context.set_for_path(dest, false, false).map_err(|e| {
|
||||
format!(
|
||||
"failed to set security context for {}: {}",
|
||||
dest.display(),
|
||||
e
|
||||
)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
Attribute::Links => {}
|
||||
Attribute::Xattr => {
|
||||
#[cfg(unix)]
|
||||
|
@ -1087,6 +1146,7 @@ fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResu
|
|||
}
|
||||
}
|
||||
};
|
||||
dest.metadata().unwrap().permissions();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1160,17 +1220,37 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
|
|||
if options.verbose {
|
||||
println!("{}", context_for(source, dest));
|
||||
}
|
||||
// FIXME: `source` and `dest` should be dereferenced here if appropriate, so that the rest
|
||||
// of the code does not have to check `options.dereference` all the time and can work with the paths directly.
|
||||
|
||||
#[allow(unused)]
|
||||
{
|
||||
// TODO: implement --preserve flag
|
||||
let mut preserve_context = false;
|
||||
for attribute in &options.preserve_attributes {
|
||||
if *attribute == Attribute::Context {
|
||||
preserve_context = true;
|
||||
}
|
||||
let dest_permissions = if dest.exists() {
|
||||
dest.metadata()
|
||||
.map_err(|e| Context(context_for(dest, source), e))?
|
||||
.permissions()
|
||||
} else {
|
||||
#[allow(unused_mut)]
|
||||
let mut permissions = source
|
||||
.metadata()
|
||||
.map_err(|e| Context(context_for(dest, source), e))?
|
||||
.permissions();
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use uucore::mode::get_umask;
|
||||
|
||||
let mut mode = permissions.mode();
|
||||
|
||||
// remove sticky bit, suid and gid bit
|
||||
const SPECIAL_PERMS_MASK: u32 = 0o7000;
|
||||
mode &= !SPECIAL_PERMS_MASK;
|
||||
|
||||
// apply umask
|
||||
mode &= !get_umask();
|
||||
|
||||
permissions.set_mode(mode);
|
||||
}
|
||||
}
|
||||
permissions
|
||||
};
|
||||
|
||||
match options.copy_mode {
|
||||
CopyMode::Link => {
|
||||
fs::hard_link(source, dest).context(&*context_for(source, dest))?;
|
||||
|
@ -1207,8 +1287,9 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
|
|||
.unwrap();
|
||||
}
|
||||
};
|
||||
fs::set_permissions(dest, dest_permissions).unwrap();
|
||||
for attribute in &options.preserve_attributes {
|
||||
copy_attribute(source, dest, attribute)?;
|
||||
copy_attribute(source, dest, attribute, options)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ walkdir = { version="2.3.2", optional=true }
|
|||
data-encoding = { version="2.1", optional=true }
|
||||
data-encoding-macro = { version="0.1.12", optional=true }
|
||||
z85 = { version="3.0.3", optional=true }
|
||||
libc = { version="0.2.15, <= 0.2.85", optional=true } ## libc: initial utmp support added in v0.2.15; but v0.2.68 breaks the build for MinSRV v1.31.0
|
||||
libc = { version="0.2.15", optional=true }
|
||||
|
||||
[dev-dependencies]
|
||||
clap = "2.33.3"
|
||||
|
|
|
@ -7,7 +7,7 @@ use std::fs::set_permissions;
|
|||
#[cfg(not(windows))]
|
||||
use std::os::unix::fs;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::fs::symlink_file;
|
||||
|
@ -1305,3 +1305,51 @@ fn test_copy_symlink_force() {
|
|||
.succeeds();
|
||||
assert_eq!(at.resolve_link("copy"), "file");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_no_preserve_mode() {
|
||||
use std::os::unix::prelude::MetadataExt;
|
||||
|
||||
use uucore::mode::get_umask;
|
||||
|
||||
const PERMS_ALL: u32 = 0o7777;
|
||||
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
at.touch("file");
|
||||
set_permissions(at.plus("file"), PermissionsExt::from_mode(PERMS_ALL)).unwrap();
|
||||
ucmd.arg("file")
|
||||
.arg("dest")
|
||||
.succeeds()
|
||||
.no_stderr()
|
||||
.no_stdout();
|
||||
let umask = get_umask();
|
||||
// remove sticky bit, setuid and setgid bit; apply umask
|
||||
let expected_perms = PERMS_ALL & !0o7000 & !umask;
|
||||
assert_eq!(
|
||||
at.plus("dest").metadata().unwrap().mode() & 0o7777,
|
||||
expected_perms
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_preserve_mode() {
|
||||
use std::os::unix::prelude::MetadataExt;
|
||||
|
||||
const PERMS_ALL: u32 = 0o7777;
|
||||
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
at.touch("file");
|
||||
set_permissions(at.plus("file"), PermissionsExt::from_mode(PERMS_ALL)).unwrap();
|
||||
ucmd.arg("file")
|
||||
.arg("dest")
|
||||
.arg("-p")
|
||||
.succeeds()
|
||||
.no_stderr()
|
||||
.no_stdout();
|
||||
assert_eq!(
|
||||
at.plus("dest").metadata().unwrap().mode() & 0o7777,
|
||||
PERMS_ALL
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue