mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
mv: implement selinux options (-Z/--context)
This commit is contained in:
@@ -41,6 +41,9 @@ windows-sys = { workspace = true, features = [
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = { workspace = true }
|
||||
|
||||
[features]
|
||||
selinux = ["uucore/selinux"]
|
||||
|
||||
[[bin]]
|
||||
name = "mv"
|
||||
path = "src/main.rs"
|
||||
|
||||
@@ -48,6 +48,8 @@ mv-help-verbose = explain what is being done
|
||||
mv-help-progress = Display a progress bar.
|
||||
Note: this feature is not supported by GNU coreutils.
|
||||
mv-help-debug = explain how a file is copied. Implies -v
|
||||
mv-help-selinux = set SELinux security context of destination file to default type
|
||||
mv-help-context = like -Z, or if CTX is specified then set the SELinux security context to CTX
|
||||
|
||||
# Verbose messages
|
||||
mv-verbose-renamed = renamed {$from} -> {$to}
|
||||
|
||||
@@ -44,6 +44,8 @@ use uucore::fs::{
|
||||
};
|
||||
#[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))]
|
||||
use uucore::fsxattr;
|
||||
#[cfg(feature = "selinux")]
|
||||
use uucore::selinux::set_selinux_security_context;
|
||||
use uucore::translate;
|
||||
use uucore::update_control;
|
||||
|
||||
@@ -99,6 +101,9 @@ pub struct Options {
|
||||
|
||||
/// `--debug`
|
||||
pub debug: bool,
|
||||
|
||||
/// `-Z, --context`
|
||||
pub context: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for Options {
|
||||
@@ -114,6 +119,7 @@ impl Default for Options {
|
||||
strip_slashes: false,
|
||||
progress_bar: false,
|
||||
debug: false,
|
||||
context: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,6 +146,8 @@ static OPT_VERBOSE: &str = "verbose";
|
||||
static OPT_PROGRESS: &str = "progress";
|
||||
static ARG_FILES: &str = "files";
|
||||
static OPT_DEBUG: &str = "debug";
|
||||
static OPT_CONTEXT: &str = "context";
|
||||
static OPT_SELINUX: &str = "selinux";
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
@@ -189,6 +197,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle -Z and --context options
|
||||
// If -Z is used, use the default context (empty string)
|
||||
// If --context=value is used, use that specific value
|
||||
let context = if matches.get_flag(OPT_SELINUX) {
|
||||
Some(String::new())
|
||||
} else {
|
||||
matches.get_one::<String>(OPT_CONTEXT).cloned()
|
||||
};
|
||||
|
||||
let opts = Options {
|
||||
overwrite: overwrite_mode,
|
||||
backup: backup_mode,
|
||||
@@ -200,6 +217,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
strip_slashes: matches.get_flag(OPT_STRIP_TRAILING_SLASHES),
|
||||
progress_bar: matches.get_flag(OPT_PROGRESS),
|
||||
debug: matches.get_flag(OPT_DEBUG),
|
||||
context,
|
||||
};
|
||||
|
||||
mv(&files[..], &opts)
|
||||
@@ -282,6 +300,22 @@ pub fn uu_app() -> Command {
|
||||
.help(translate!("mv-help-progress"))
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(OPT_SELINUX)
|
||||
.short('Z')
|
||||
.help(translate!("mv-help-selinux"))
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(OPT_CONTEXT)
|
||||
.long(OPT_CONTEXT)
|
||||
.value_name("CTX")
|
||||
.value_parser(clap::value_parser!(String))
|
||||
.help(translate!("mv-help-context"))
|
||||
.num_args(0..=1)
|
||||
.require_equals(true)
|
||||
.default_missing_value(""),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(ARG_FILES)
|
||||
.action(ArgAction::Append)
|
||||
@@ -733,6 +767,12 @@ fn rename(
|
||||
rename_with_fallback(from, to, multi_progress, None, None)?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "selinux")]
|
||||
if let Some(ref context) = opts.context {
|
||||
set_selinux_security_context(to, Some(context))
|
||||
.map_err(|e| io::Error::other(e.to_string()))?;
|
||||
}
|
||||
|
||||
if opts.verbose {
|
||||
let message = match backup_path {
|
||||
Some(path) => {
|
||||
|
||||
@@ -10,6 +10,11 @@ use rstest::rstest;
|
||||
use std::io::Write;
|
||||
#[cfg(not(windows))]
|
||||
use std::path::Path;
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
#[cfg(feature = "selinux")]
|
||||
use uucore::selinux::get_getfattr_output;
|
||||
>>>>>>> 947680a6d (fix build)
|
||||
use uutests::new_ucmd;
|
||||
use uutests::util::TestScenario;
|
||||
use uutests::{at_and_ucmd, util_name};
|
||||
@@ -2488,3 +2493,83 @@ fn test_mv_cross_device_permission_denied() {
|
||||
set_permissions(other_fs_tempdir.path(), PermissionsExt::from_mode(0o755))
|
||||
.expect("Unable to restore directory permissions");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "selinux")]
|
||||
fn test_mv_selinux_context() {
|
||||
use std::process::Command;
|
||||
|
||||
let test_cases = [
|
||||
("-Z", None),
|
||||
(
|
||||
"--context=unconfined_u:object_r:user_tmp_t:s0",
|
||||
Some("unconfined_u"),
|
||||
),
|
||||
];
|
||||
|
||||
for (arg, expected_context) in test_cases {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
let src = "source.txt";
|
||||
let dest = "dest.txt";
|
||||
|
||||
at.touch(&src);
|
||||
|
||||
for (arg, context_value, expected_context) in test_cases {
|
||||
|
||||
let mut cmd = scene.ucmd();
|
||||
cmd.arg(arg);
|
||||
|
||||
let result = cmd
|
||||
.arg(at.plus_as_string(&src))
|
||||
.arg(at.plus_as_string(&dest))
|
||||
.run();
|
||||
|
||||
// Skip test if SELinux is not enabled
|
||||
if result
|
||||
.stderr_str()
|
||||
.contains("SELinux is not enabled on this system")
|
||||
{
|
||||
println!("Skipping SELinux test: SELinux is not enabled");
|
||||
return;
|
||||
}
|
||||
|
||||
result.success();
|
||||
assert!(at.file_exists(&dest));
|
||||
assert!(!at.file_exists(&src));
|
||||
|
||||
// Verify SELinux context was set using getfattr
|
||||
let getfattr_output = Command::new("getfattr")
|
||||
.arg(at.plus_as_string(&dest))
|
||||
.arg("-n")
|
||||
.arg("security.selinux")
|
||||
.output();
|
||||
|
||||
if let Ok(output) = getfattr_output {
|
||||
let selinux_context = String::from_utf8_lossy(&output.stdout);
|
||||
if !selinux_context.is_empty() {
|
||||
match expected_context {
|
||||
Some(expected) => {
|
||||
let context_value =
|
||||
selinux_context.split('"').nth(1).unwrap_or("").to_string();
|
||||
assert!(
|
||||
context_value.contains(expected),
|
||||
"Expected context to contain '{}', got: {}",
|
||||
expected,
|
||||
context_value
|
||||
);
|
||||
}
|
||||
None => {
|
||||
if selinux_context.contains("security.selinux") {
|
||||
println!("SELinux context successfully set with {} flag", arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up files
|
||||
let _ = std::fs::remove_file(at.plus_as_string(&dest));
|
||||
let _ = std::fs::remove_file(at.plus_as_string(&src));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user