Skip to content

Commit c981767

Browse files
authored
Merge pull request #7878 from sylvestre/selinux-cp
cp: improve the selinux support
2 parents 5541172 + 5148ba1 commit c981767

File tree

3 files changed

+491
-38
lines changed

3 files changed

+491
-38
lines changed

src/uu/cp/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,5 @@ name = "cp"
4747
path = "src/main.rs"
4848

4949
[features]
50-
feat_selinux = ["selinux"]
50+
feat_selinux = ["selinux", "uucore/selinux"]
5151
feat_acl = ["exacl"]

src/uu/cp/src/cp.rs

Lines changed: 66 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use std::path::{Path, PathBuf, StripPrefixError};
1616
#[cfg(all(unix, not(target_os = "android")))]
1717
use uucore::fsxattr::copy_xattrs;
1818

19-
use clap::{Arg, ArgAction, ArgMatches, Command, builder::ValueParser};
19+
use clap::{Arg, ArgAction, ArgMatches, Command, builder::ValueParser, value_parser};
2020
use filetime::FileTime;
2121
use indicatif::{ProgressBar, ProgressStyle};
2222
use quick_error::ResultExt;
@@ -311,6 +311,10 @@ pub struct Options {
311311
pub verbose: bool,
312312
/// `-g`, `--progress`
313313
pub progress_bar: bool,
314+
/// -Z
315+
pub set_selinux_context: bool,
316+
// --context
317+
pub context: Option<String>,
314318
}
315319

316320
impl Default for Options {
@@ -337,6 +341,8 @@ impl Default for Options {
337341
debug: false,
338342
verbose: false,
339343
progress_bar: false,
344+
set_selinux_context: false,
345+
context: None,
340346
}
341347
}
342348
}
@@ -448,6 +454,7 @@ mod options {
448454
pub const RECURSIVE: &str = "recursive";
449455
pub const REFLINK: &str = "reflink";
450456
pub const REMOVE_DESTINATION: &str = "remove-destination";
457+
pub const SELINUX: &str = "Z";
451458
pub const SPARSE: &str = "sparse";
452459
pub const STRIP_TRAILING_SLASHES: &str = "strip-trailing-slashes";
453460
pub const SYMBOLIC_LINK: &str = "symbolic-link";
@@ -476,6 +483,7 @@ const PRESERVE_DEFAULT_VALUES: &str = if cfg!(unix) {
476483
} else {
477484
"mode,timestamp"
478485
};
486+
479487
pub fn uu_app() -> Command {
480488
const MODE_ARGS: &[&str] = &[
481489
options::LINK,
@@ -709,24 +717,25 @@ pub fn uu_app() -> Command {
709717
.value_parser(ShortcutValueParser::new(["never", "auto", "always"]))
710718
.help("control creation of sparse files. See below"),
711719
)
712-
// TODO: implement the following args
713720
.arg(
714-
Arg::new(options::COPY_CONTENTS)
715-
.long(options::COPY_CONTENTS)
716-
.overrides_with(options::ATTRIBUTES_ONLY)
717-
.help("NotImplemented: copy contents of special files when recursive")
721+
Arg::new(options::SELINUX)
722+
.short('Z')
723+
.help("set SELinux security context of destination file to default type")
718724
.action(ArgAction::SetTrue),
719725
)
720726
.arg(
721727
Arg::new(options::CONTEXT)
722728
.long(options::CONTEXT)
723729
.value_name("CTX")
730+
.value_parser(value_parser!(String))
724731
.help(
725-
"NotImplemented: set SELinux security context of destination file to \
726-
default type",
727-
),
732+
"like -Z, or if CTX is specified then set the SELinux or SMACK security \
733+
context to CTX",
734+
)
735+
.num_args(0..=1)
736+
.require_equals(true)
737+
.default_missing_value(""),
728738
)
729-
// END TODO
730739
.arg(
731740
// The 'g' short flag is modeled after advcpmv
732741
// See this repo: https://github.com/jarun/advcpmv
@@ -739,6 +748,15 @@ pub fn uu_app() -> Command {
739748
Note: this feature is not supported by GNU coreutils.",
740749
),
741750
)
751+
// TODO: implement the following args
752+
.arg(
753+
Arg::new(options::COPY_CONTENTS)
754+
.long(options::COPY_CONTENTS)
755+
.overrides_with(options::ATTRIBUTES_ONLY)
756+
.help("NotImplemented: copy contents of special files when recursive")
757+
.action(ArgAction::SetTrue),
758+
)
759+
// END TODO
742760
.arg(
743761
Arg::new(options::PATHS)
744762
.action(ArgAction::Append)
@@ -971,7 +989,6 @@ impl Options {
971989
let not_implemented_opts = vec![
972990
#[cfg(not(any(windows, unix)))]
973991
options::ONE_FILE_SYSTEM,
974-
options::CONTEXT,
975992
#[cfg(windows)]
976993
options::FORCE,
977994
];
@@ -1018,7 +1035,6 @@ impl Options {
10181035
return Err(Error::NotADirectory(dir.clone()));
10191036
}
10201037
};
1021-
10221038
// cp follows POSIX conventions for overriding options such as "-a",
10231039
// "-d", "--preserve", and "--no-preserve". We can use clap's
10241040
// override-all behavior to achieve this, but there's a challenge: when
@@ -1101,7 +1117,7 @@ impl Options {
11011117
}
11021118
}
11031119

1104-
#[cfg(not(feature = "feat_selinux"))]
1120+
#[cfg(not(feature = "selinux"))]
11051121
if let Preserve::Yes { required } = attributes.context {
11061122
let selinux_disabled_error =
11071123
Error::Error("SELinux was not enabled during the compile time!".to_string());
@@ -1112,6 +1128,15 @@ impl Options {
11121128
}
11131129
}
11141130

1131+
// Extract the SELinux related flags and options
1132+
let set_selinux_context = matches.get_flag(options::SELINUX);
1133+
1134+
let context = if matches.contains_id(options::CONTEXT) {
1135+
matches.get_one::<String>(options::CONTEXT).cloned()
1136+
} else {
1137+
None
1138+
};
1139+
11151140
let options = Self {
11161141
attributes_only: matches.get_flag(options::ATTRIBUTES_ONLY),
11171142
copy_contents: matches.get_flag(options::COPY_CONTENTS),
@@ -1172,6 +1197,8 @@ impl Options {
11721197
recursive,
11731198
target_dir,
11741199
progress_bar: matches.get_flag(options::PROGRESS_BAR),
1200+
set_selinux_context: set_selinux_context || context.is_some(),
1201+
context,
11751202
};
11761203

11771204
Ok(options)
@@ -1676,20 +1703,24 @@ pub(crate) fn copy_attributes(
16761703
Ok(())
16771704
})?;
16781705

1679-
#[cfg(feature = "feat_selinux")]
1706+
#[cfg(feature = "selinux")]
16801707
handle_preserve(&attributes.context, || -> CopyResult<()> {
1681-
let context = selinux::SecurityContext::of_path(source, false, false).map_err(|e| {
1682-
format!(
1683-
"failed to get security context of {}: {e}",
1684-
source.display(),
1685-
)
1686-
})?;
1687-
if let Some(context) = context {
1688-
context.set_for_path(dest, false, false).map_err(|e| {
1689-
format!("failed to set security context for {}: {e}", dest.display(),)
1690-
})?;
1708+
// Get the source context and apply it to the destination
1709+
if let Ok(context) = selinux::SecurityContext::of_path(source, false, false) {
1710+
if let Some(context) = context {
1711+
if let Err(e) = context.set_for_path(dest, false, false) {
1712+
return Err(Error::Error(format!(
1713+
"failed to set security context for {}: {e}",
1714+
dest.display()
1715+
)));
1716+
}
1717+
}
1718+
} else {
1719+
return Err(Error::Error(format!(
1720+
"failed to get security context of {}",
1721+
source.display()
1722+
)));
16911723
}
1692-
16931724
Ok(())
16941725
})?;
16951726

@@ -2417,11 +2448,20 @@ fn copy_file(
24172448
// like anonymous pipes. Thus, we can't really copy its
24182449
// attributes. However, this is already handled in the stream
24192450
// copy function (see `copy_stream` under platform/linux.rs).
2420-
copy_attributes(source, dest, &options.attributes)?;
24212451
} else {
24222452
copy_attributes(source, dest, &options.attributes)?;
24232453
}
24242454

2455+
#[cfg(feature = "selinux")]
2456+
if options.set_selinux_context && uucore::selinux::is_selinux_enabled() {
2457+
// Set the given selinux permissions on the copied file.
2458+
if let Err(e) =
2459+
uucore::selinux::set_selinux_security_context(dest, options.context.as_ref())
2460+
{
2461+
return Err(Error::Error(format!("SELinux error: {}", e)));
2462+
}
2463+
}
2464+
24252465
copied_files.insert(
24262466
FileInformation::from_path(source, options.dereference(source_in_command_line))?,
24272467
dest.to_path_buf(),

0 commit comments

Comments
 (0)