From 81c02b7408b6e1367039e3bfe672611f083a0bb4 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 30 Mar 2025 11:10:14 +0200 Subject: [PATCH 1/5] mkdir: add support of -Z Should fix: gnu/tests/mkdir/selinux.sh tests/mkdir/restorecon.sh --- src/uu/mkdir/Cargo.toml | 1 + src/uu/mkdir/src/mkdir.rs | 138 ++++++++++++++++++++++++++++++++++-- tests/by-util/test_mkdir.rs | 59 ++++++++++++++- 3 files changed, 191 insertions(+), 7 deletions(-) diff --git a/src/uu/mkdir/Cargo.toml b/src/uu/mkdir/Cargo.toml index 8fade0583..7dc27cd16 100644 --- a/src/uu/mkdir/Cargo.toml +++ b/src/uu/mkdir/Cargo.toml @@ -19,6 +19,7 @@ path = "src/mkdir.rs" [dependencies] clap = { workspace = true } +selinux = { workspace = true } uucore = { workspace = true, features = ["fs", "mode", "fsxattr"] } diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index 5886127be..0bc703d85 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -8,6 +8,7 @@ use clap::builder::ValueParser; use clap::parser::ValuesRef; use clap::{Arg, ArgAction, ArgMatches, Command}; +use selinux::SecurityContext; use std::ffi::OsString; use std::path::{Path, PathBuf}; #[cfg(not(windows))] @@ -29,6 +30,8 @@ mod options { pub const PARENTS: &str = "parents"; pub const VERBOSE: &str = "verbose"; pub const DIRS: &str = "dirs"; + pub const SELINUX: &str = "z"; + pub const CONTEXT: &str = "context"; } #[cfg(windows)] @@ -72,6 +75,58 @@ fn strip_minus_from_mode(args: &mut Vec) -> bool { mode::strip_minus_from_mode(args) } +// Add a new function to handle setting the SELinux security context +#[cfg(target_os = "linux")] +fn set_selinux_security_context(path: &Path, context: Option<&String>) -> Result<(), String> { + // Get SELinux kernel support + let support = selinux::kernel_support(); + + // If SELinux is not enabled, return early + if support == selinux::KernelSupport::Unsupported { + return Err("SELinux is not enabled on this system".to_string()); + } + + // If a specific context was provided, use it + if let Some(ctx_str) = context { + // Use the provided context + match SecurityContext::of_path(path, false, false) { + Ok(_) => { + // Create a CString from the context string + let c_context = std::ffi::CString::new(ctx_str.as_str()) + .map_err(|_| "Invalid context string (contains null bytes)".to_string())?; + + // Create a security context from the string + let security_context = match selinux::OpaqueSecurityContext::from_c_str(&c_context) + { + Ok(ctx) => ctx, + Err(e) => return Err(format!("Failed to create security context: {}", e)), + }; + + // Convert back to string for the API + let context_str = match security_context.to_c_string() { + Ok(ctx) => ctx, + Err(e) => return Err(format!("Failed to convert context to string: {}", e)), + }; + + // Set the context on the file + let sc = SecurityContext::from_c_str(&context_str, false); + + match sc.set_for_path(path, false, false) { + Ok(_) => Ok(()), + Err(e) => Err(format!("Failed to set context: {}", e)), + } + } + Err(e) => Err(format!("Failed to get current context: {}", e)), + } + } else { + // If no context was specified, use the default context for the path + match SecurityContext::set_default_for_path(path) { + Ok(_) => Ok(()), + Err(e) => Err(format!("Failed to set default context: {}", e)), + } + } +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let mut args = args.collect_lossy(); @@ -91,8 +146,19 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let verbose = matches.get_flag(options::VERBOSE); let recursive = matches.get_flag(options::PARENTS); + // Extract the SELinux related flags and options + let set_selinux_context = matches.get_flag(options::SELINUX); + let context = matches.get_one::(options::CONTEXT); + match get_mode(&matches, mode_had_minus_prefix) { - Ok(mode) => exec(dirs, recursive, mode, verbose), + Ok(mode) => exec( + dirs, + recursive, + mode, + verbose, + set_selinux_context || context.is_some(), + context, + ), Err(f) => Err(USimpleError::new(1, f)), } } @@ -124,6 +190,15 @@ pub fn uu_app() -> Command { .help("print a message for each printed directory") .action(ArgAction::SetTrue), ) + .arg( + Arg::new(options::SELINUX) + .short('Z') + .help("set SELinux security context of each created directory to the default type") + .action(ArgAction::SetTrue), + ) + .arg(Arg::new(options::CONTEXT).long(options::CONTEXT).value_name("CTX").help( + "like -Z, or if CTX is specified then set the SELinux or SMACK security context to CTX", + )) .arg( Arg::new(options::DIRS) .action(ArgAction::Append) @@ -137,12 +212,26 @@ pub fn uu_app() -> Command { /** * Create the list of new directories */ -fn exec(dirs: ValuesRef, recursive: bool, mode: u32, verbose: bool) -> UResult<()> { +fn exec( + dirs: ValuesRef, + recursive: bool, + mode: u32, + verbose: bool, + set_selinux_context: bool, + context: Option<&String>, +) -> UResult<()> { for dir in dirs { let path_buf = PathBuf::from(dir); let path = path_buf.as_path(); - show_if_err!(mkdir(path, recursive, mode, verbose)); + show_if_err!(mkdir( + path, + recursive, + mode, + verbose, + set_selinux_context, + context + )); } Ok(()) } @@ -160,7 +249,14 @@ fn exec(dirs: ValuesRef, recursive: bool, mode: u32, verbose: bool) -> /// /// To match the GNU behavior, a path with the last directory being a single dot /// (like `some/path/to/.`) is created (with the dot stripped). -pub fn mkdir(path: &Path, recursive: bool, mode: u32, verbose: bool) -> UResult<()> { +pub fn mkdir( + path: &Path, + recursive: bool, + mode: u32, + verbose: bool, + set_selinux_context: bool, + context: Option<&String>, +) -> UResult<()> { if path.as_os_str().is_empty() { return Err(USimpleError::new( 1, @@ -173,7 +269,15 @@ pub fn mkdir(path: &Path, recursive: bool, mode: u32, verbose: bool) -> UResult< // std::fs::create_dir("foo/."); fails in pure Rust let path_buf = dir_strip_dot_for_creation(path); let path = path_buf.as_path(); - create_dir(path, recursive, verbose, false, mode) + create_dir( + path, + recursive, + verbose, + false, + mode, + set_selinux_context, + context, + ) } #[cfg(any(unix, target_os = "redox"))] @@ -200,6 +304,8 @@ fn create_dir( verbose: bool, is_parent: bool, mode: u32, + set_selinux_context: bool, + context: Option<&String>, ) -> UResult<()> { let path_exists = path.exists(); if path_exists && !recursive { @@ -214,7 +320,15 @@ fn create_dir( if recursive { match path.parent() { - Some(p) => create_dir(p, recursive, verbose, true, mode)?, + Some(p) => create_dir( + p, + recursive, + verbose, + true, + mode, + set_selinux_context, + context, + )?, None => { USimpleError::new(1, "failed to create whole tree"); } @@ -255,6 +369,18 @@ fn create_dir( let new_mode = mode; chmod(path, new_mode)?; + + // Apply SELinux context if requested + #[cfg(target_os = "linux")] + if set_selinux_context { + if let Err(e) = set_selinux_security_context(path, context) { + return Err(USimpleError::new( + 1, + format!("failed to set SELinux security context: {}", e), + )); + } + } + Ok(()) } diff --git a/tests/by-util/test_mkdir.rs b/tests/by-util/test_mkdir.rs index e544e3423..3eaec9774 100644 --- a/tests/by-util/test_mkdir.rs +++ b/tests/by-util/test_mkdir.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore bindgen +// spell-checker:ignore bindgen getfattr testtest #![allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] @@ -358,3 +358,60 @@ fn test_empty_argument() { .fails() .stderr_only("mkdir: cannot create directory '': No such file or directory\n"); } + +#[test] +#[cfg(feature = "feat_selinux")] +fn test_selinux() { + use std::process::Command; + + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let dest = "test_dir_a"; + let args = ["-Z", "--context=unconfined_u:object_r:user_tmp_t:s0"]; + for arg in args { + new_ucmd!() + .arg(arg) + .arg("-v") + .arg(at.plus_as_string(dest)) + .succeeds() + .stdout_contains("created directory"); + + let getfattr_output = Command::new("getfattr") + .arg(at.plus_as_string(dest)) + .arg("-n") + .arg("security.selinux") + .output() + .expect("Failed to run `getfattr` on the destination file"); + + assert!( + getfattr_output.status.success(), + "getfattr did not run successfully: {}", + String::from_utf8_lossy(&getfattr_output.stderr) + ); + + let stdout = String::from_utf8_lossy(&getfattr_output.stdout); + assert!( + stdout.contains("unconfined_u"), + "Expected '{}' not found in getfattr output:\n{}", + "unconfined_u", + stdout + ); + at.rmdir(dest); + } +} + +#[test] +#[cfg(feature = "feat_selinux")] +fn test_selinux_invalid() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let dest = "test_dir_a"; + new_ucmd!() + .arg("--context=testtest") + .arg(at.plus_as_string(dest)) + .fails() + .no_stdout() + .stderr_contains("failed to set SELinux security context:"); + // invalid context, so, no directory + assert!(!at.dir_exists(dest)); +} From 842f47b3723e38a9987e7df27aac785efd240913 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 8 Apr 2025 17:10:27 -0400 Subject: [PATCH 2/5] uucore: move the selinux function --- Cargo.lock | 1 + Cargo.toml | 1 + src/uu/mkdir/Cargo.toml | 3 +- src/uu/mkdir/src/mkdir.rs | 57 +---------- src/uucore/Cargo.toml | 4 +- src/uucore/src/lib/features.rs | 2 + src/uucore/src/lib/features/selinux.rs | 130 +++++++++++++++++++++++++ src/uucore/src/lib/lib.rs | 3 + 8 files changed, 145 insertions(+), 56 deletions(-) create mode 100644 src/uucore/src/lib/features/selinux.rs diff --git a/Cargo.lock b/Cargo.lock index 1004312ab..0000001cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3585,6 +3585,7 @@ dependencies = [ "number_prefix", "os_display", "regex", + "selinux", "sha1", "sha2", "sha3", diff --git a/Cargo.toml b/Cargo.toml index 5ae51301f..224e127a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ feat_selinux = [ "cp/selinux", "id/selinux", "ls/selinux", + "mkdir/selinux", "selinux", "feat_require_selinux", ] diff --git a/src/uu/mkdir/Cargo.toml b/src/uu/mkdir/Cargo.toml index 7dc27cd16..77b664738 100644 --- a/src/uu/mkdir/Cargo.toml +++ b/src/uu/mkdir/Cargo.toml @@ -19,9 +19,10 @@ path = "src/mkdir.rs" [dependencies] clap = { workspace = true } -selinux = { workspace = true } uucore = { workspace = true, features = ["fs", "mode", "fsxattr"] } +[features] +selinux = ["uucore/selinux"] [[bin]] name = "mkdir" diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index 0bc703d85..5147ec5e0 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -8,7 +8,6 @@ use clap::builder::ValueParser; use clap::parser::ValuesRef; use clap::{Arg, ArgAction, ArgMatches, Command}; -use selinux::SecurityContext; use std::ffi::OsString; use std::path::{Path, PathBuf}; #[cfg(not(windows))] @@ -75,58 +74,6 @@ fn strip_minus_from_mode(args: &mut Vec) -> bool { mode::strip_minus_from_mode(args) } -// Add a new function to handle setting the SELinux security context -#[cfg(target_os = "linux")] -fn set_selinux_security_context(path: &Path, context: Option<&String>) -> Result<(), String> { - // Get SELinux kernel support - let support = selinux::kernel_support(); - - // If SELinux is not enabled, return early - if support == selinux::KernelSupport::Unsupported { - return Err("SELinux is not enabled on this system".to_string()); - } - - // If a specific context was provided, use it - if let Some(ctx_str) = context { - // Use the provided context - match SecurityContext::of_path(path, false, false) { - Ok(_) => { - // Create a CString from the context string - let c_context = std::ffi::CString::new(ctx_str.as_str()) - .map_err(|_| "Invalid context string (contains null bytes)".to_string())?; - - // Create a security context from the string - let security_context = match selinux::OpaqueSecurityContext::from_c_str(&c_context) - { - Ok(ctx) => ctx, - Err(e) => return Err(format!("Failed to create security context: {}", e)), - }; - - // Convert back to string for the API - let context_str = match security_context.to_c_string() { - Ok(ctx) => ctx, - Err(e) => return Err(format!("Failed to convert context to string: {}", e)), - }; - - // Set the context on the file - let sc = SecurityContext::from_c_str(&context_str, false); - - match sc.set_for_path(path, false, false) { - Ok(_) => Ok(()), - Err(e) => Err(format!("Failed to set context: {}", e)), - } - } - Err(e) => Err(format!("Failed to get current context: {}", e)), - } - } else { - // If no context was specified, use the default context for the path - match SecurityContext::set_default_for_path(path) { - Ok(_) => Ok(()), - Err(e) => Err(format!("Failed to set default context: {}", e)), - } - } -} - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let mut args = args.collect_lossy(); @@ -373,7 +320,9 @@ fn create_dir( // Apply SELinux context if requested #[cfg(target_os = "linux")] if set_selinux_context { - if let Err(e) = set_selinux_security_context(path, context) { + if let Err(e) = uucore::selinux_support::set_selinux_security_context(path, context) + { + let _ = std::fs::remove_dir(path); return Err(USimpleError::new( 1, format!("failed to set SELinux security context: {}", e), diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 2da81bc1c..6f70843db 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -58,6 +58,7 @@ crc32fast = { workspace = true, optional = true } regex = { workspace = true, optional = true } bigdecimal = { workspace = true, optional = true } num-traits = { workspace = true, optional = true } +selinux = { workspace = true, optional = true } [target.'cfg(unix)'.dependencies] walkdir = { workspace = true, optional = true } @@ -105,13 +106,14 @@ format = [ mode = ["libc"] perms = ["entries", "libc", "walkdir"] buf-copy = [] +parser = ["extendedbigdecimal", "glob", "num-traits"] pipes = [] process = ["libc"] proc-info = ["tty", "walkdir"] quoting-style = [] ranges = [] ringbuffer = [] -parser = ["extendedbigdecimal", "glob", "num-traits"] +selinux = ["dep:selinux"] signals = [] sum = [ "digest", diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index d3a8ebb44..b19387c69 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -66,6 +66,8 @@ pub mod tty; #[cfg(all(unix, feature = "fsxattr"))] pub mod fsxattr; +#[cfg(all(target_os = "linux", feature = "selinux"))] +pub mod selinux; #[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))] pub mod signals; #[cfg(all( diff --git a/src/uucore/src/lib/features/selinux.rs b/src/uucore/src/lib/features/selinux.rs new file mode 100644 index 000000000..09c71078b --- /dev/null +++ b/src/uucore/src/lib/features/selinux.rs @@ -0,0 +1,130 @@ +// This file is part of the uutils uucore package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use std::path::Path; + +use selinux::SecurityContext; + +/// Sets the SELinux security context for the given filesystem path. +/// +/// If a specific context is provided, it attempts to set this context explicitly. +/// Otherwise, it applies the default SELinux context for the provided path. +/// +/// # Arguments +/// +/// * `path` - Filesystem path on which to set the SELinux context. +/// * `context` - Optional SELinux context string to explicitly set. +/// +/// # Errors +/// +/// Returns an error if: +/// - SELinux is not enabled on the system. +/// - The provided context is invalid or cannot be applied. +/// - The default SELinux context cannot be set. +/// +/// # Examples +/// +/// Setting default context: +/// ``` +/// use std::path::Path; +/// use uucore::selinux::set_selinux_security_context; +/// +/// // Set the default SELinux context for a file +/// let result = set_selinux_security_context(Path::new("/path/to/file"), None); +/// if let Err(err) = result { +/// eprintln!("Failed to set default context: {}", err); +/// } +/// ``` +/// +/// Setting specific context: +/// ``` +/// use std::path::Path; +/// use uucore::selinux::set_selinux_security_context; +/// +/// // Set a specific SELinux context for a file +/// let context = String::from("unconfined_u:object_r:user_home_t:s0"); +/// let result = set_selinux_security_context(Path::new("/path/to/file"), Some(&context)); +/// if let Err(err) = result { +/// eprintln!("Failed to set context: {}", err); +/// } +/// ``` +pub fn set_selinux_security_context(path: &Path, context: Option<&String>) -> Result<(), String> { + // Check if SELinux is enabled on the system + if selinux::kernel_support() == selinux::KernelSupport::Unsupported { + return Err("SELinux is not enabled on this system".into()); + } + + if let Some(ctx_str) = context { + // Create a CString from the provided context string + let c_context = std::ffi::CString::new(ctx_str.as_str()) + .map_err(|_| "Invalid context string (contains null bytes)".to_string())?; + + // Convert the CString into an SELinux security context + let security_context = selinux::OpaqueSecurityContext::from_c_str(&c_context) + .map_err(|e| format!("Failed to create security context: {}", e))?; + + // Set the provided security context on the specified path + SecurityContext::from_c_str( + &security_context.to_c_string().map_err(|e| e.to_string())?, + false, + ) + .set_for_path(path, false, false) + .map_err(|e| format!("Failed to set context: {}", e)) + } else { + // If no context provided, set the default SELinux context for the path + SecurityContext::set_default_for_path(path) + .map_err(|e| format!("Failed to set default context: {}", e)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::NamedTempFile; + + #[test] + fn test_selinux_context_setting() { + let tmpfile = NamedTempFile::new().expect("Failed to create tempfile"); + let path = tmpfile.path(); + + let result = set_selinux_security_context(path, None); + + if result.is_ok() { + // SELinux enabled and successfully set default context + assert!(true, "Successfully set SELinux context"); + } else { + let err = result.unwrap_err(); + let valid_errors = [ + "SELinux is not enabled on this system", + &format!( + "Failed to set default context: selinux_lsetfilecon_default() failed on path '{}'", + path.display() + ), + ]; + + assert!( + valid_errors.contains(&err.as_str()), + "Unexpected error message: {}", + err + ); + } + } + + #[test] + fn test_invalid_context_string_error() { + let tmpfile = NamedTempFile::new().expect("Failed to create tempfile"); + let path = tmpfile.path(); + + // Pass a context string containing a null byte to trigger CString::new error + let invalid_context = String::from("invalid\0context"); + let result = set_selinux_security_context(path, Some(&invalid_context)); + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err(), + "Invalid context string (contains null bytes)" + ); + } +} diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index c8257823a..bc4281979 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -103,6 +103,9 @@ pub use crate::features::fsext; #[cfg(all(unix, feature = "fsxattr"))] pub use crate::features::fsxattr; +#[cfg(all(target_os = "linux", feature = "selinux"))] +pub use crate::features::selinux; + //## core functions #[cfg(unix)] From d41c0ceb538a8a4058774c51a5f7db5015c908e1 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 12 Apr 2025 18:36:58 +0200 Subject: [PATCH 3/5] uucore/selinux: add a function to verify if selinux is enabled --- src/uucore/src/lib/features/selinux.rs | 50 ++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/src/uucore/src/lib/features/selinux.rs b/src/uucore/src/lib/features/selinux.rs index 09c71078b..d708fe715 100644 --- a/src/uucore/src/lib/features/selinux.rs +++ b/src/uucore/src/lib/features/selinux.rs @@ -7,6 +7,38 @@ use std::path::Path; use selinux::SecurityContext; +#[derive(Debug)] +pub enum Error { + SELinuxNotEnabled, +} + +/// Checks if SELinux is enabled on the system. +/// +/// This function verifies whether the kernel has SELinux support enabled. +/// +/// # Returns +/// +/// * `Ok(())` - If SELinux is enabled on the system. +/// * `Err(Error::SELinuxNotEnabled)` - If SELinux is not enabled. +/// +/// # Examples +/// +/// ``` +/// use uucore::selinux::check_selinux_enabled; +/// +/// match check_selinux_enabled() { +/// Ok(_) => println!("SELinux is enabled"), +/// Err(_) => println!("SELinux is not enabled"), +/// } +/// ``` +pub fn check_selinux_enabled() -> Result<(), Error> { + if selinux::kernel_support() == selinux::KernelSupport::Unsupported { + Err(Error::SELinuxNotEnabled) + } else { + Ok(()) + } +} + /// Sets the SELinux security context for the given filesystem path. /// /// If a specific context is provided, it attempts to set this context explicitly. @@ -52,9 +84,7 @@ use selinux::SecurityContext; /// ``` pub fn set_selinux_security_context(path: &Path, context: Option<&String>) -> Result<(), String> { // Check if SELinux is enabled on the system - if selinux::kernel_support() == selinux::KernelSupport::Unsupported { - return Err("SELinux is not enabled on this system".into()); - } + check_selinux_enabled().map_err(|e| format!("{:?}", e))?; if let Some(ctx_str) = context { // Create a CString from the provided context string @@ -127,4 +157,18 @@ mod tests { "Invalid context string (contains null bytes)" ); } + + #[test] + fn test_check_selinux_enabled_runtime_behavior() { + let result = check_selinux_enabled(); + + match selinux::kernel_support() { + selinux::KernelSupport::Unsupported => { + assert!(matches!(result, Err(Error::SELinuxNotEnabled))); + } + _ => { + assert!(result.is_ok(), "Expected Ok(()) when SELinux is supported"); + } + } + } } From b874d90345ee63ab0c123ac2377c5ca8e326e4af Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 30 Mar 2025 11:23:52 +0200 Subject: [PATCH 4/5] mkdir: move to a config approach for the functions --- src/uu/mkdir/src/mkdir.rs | 113 +++++++++++++++----------------------- 1 file changed, 44 insertions(+), 69 deletions(-) diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index 5147ec5e0..14c7701db 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -33,6 +33,24 @@ mod options { pub const CONTEXT: &str = "context"; } +/// Configuration for directory creation. +pub struct Config<'a> { + /// Create parent directories as needed. + pub recursive: bool, + + /// File permissions (octal). + pub mode: u32, + + /// Print message for each created directory. + pub verbose: bool, + + /// Set SELinux security context. + pub set_selinux_context: bool, + + /// Specific SELinux context. + pub context: Option<&'a String>, +} + #[cfg(windows)] fn get_mode(_matches: &ArgMatches, _mode_had_minus_prefix: bool) -> Result { Ok(DEFAULT_PERM) @@ -98,14 +116,16 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let context = matches.get_one::(options::CONTEXT); match get_mode(&matches, mode_had_minus_prefix) { - Ok(mode) => exec( - dirs, - recursive, - mode, - verbose, - set_selinux_context || context.is_some(), - context, - ), + Ok(mode) => { + let config = Config { + recursive, + mode, + verbose, + set_selinux_context: set_selinux_context || context.is_some(), + context, + }; + exec(dirs, &config) + } Err(f) => Err(USimpleError::new(1, f)), } } @@ -159,26 +179,12 @@ pub fn uu_app() -> Command { /** * Create the list of new directories */ -fn exec( - dirs: ValuesRef, - recursive: bool, - mode: u32, - verbose: bool, - set_selinux_context: bool, - context: Option<&String>, -) -> UResult<()> { +fn exec(dirs: ValuesRef, config: &Config) -> UResult<()> { for dir in dirs { let path_buf = PathBuf::from(dir); let path = path_buf.as_path(); - show_if_err!(mkdir( - path, - recursive, - mode, - verbose, - set_selinux_context, - context - )); + show_if_err!(mkdir(path, config)); } Ok(()) } @@ -196,14 +202,7 @@ fn exec( /// /// To match the GNU behavior, a path with the last directory being a single dot /// (like `some/path/to/.`) is created (with the dot stripped). -pub fn mkdir( - path: &Path, - recursive: bool, - mode: u32, - verbose: bool, - set_selinux_context: bool, - context: Option<&String>, -) -> UResult<()> { +pub fn mkdir(path: &Path, config: &Config) -> UResult<()> { if path.as_os_str().is_empty() { return Err(USimpleError::new( 1, @@ -216,15 +215,7 @@ pub fn mkdir( // std::fs::create_dir("foo/."); fails in pure Rust let path_buf = dir_strip_dot_for_creation(path); let path = path_buf.as_path(); - create_dir( - path, - recursive, - verbose, - false, - mode, - set_selinux_context, - context, - ) + create_dir(path, false, config) } #[cfg(any(unix, target_os = "redox"))] @@ -245,17 +236,9 @@ fn chmod(_path: &Path, _mode: u32) -> UResult<()> { // Return true if the directory at `path` has been created by this call. // `is_parent` argument is not used on windows #[allow(unused_variables)] -fn create_dir( - path: &Path, - recursive: bool, - verbose: bool, - is_parent: bool, - mode: u32, - set_selinux_context: bool, - context: Option<&String>, -) -> UResult<()> { +fn create_dir(path: &Path, is_parent: bool, config: &Config) -> UResult<()> { let path_exists = path.exists(); - if path_exists && !recursive { + if path_exists && !config.recursive { return Err(USimpleError::new( 1, format!("{}: File exists", path.display()), @@ -265,17 +248,9 @@ fn create_dir( return Ok(()); } - if recursive { + if config.recursive { match path.parent() { - Some(p) => create_dir( - p, - recursive, - verbose, - true, - mode, - set_selinux_context, - context, - )?, + Some(p) => create_dir(p, true, config)?, None => { USimpleError::new(1, "failed to create whole tree"); } @@ -284,7 +259,7 @@ fn create_dir( match std::fs::create_dir(path) { Ok(()) => { - if verbose { + if config.verbose { println!( "{}: created directory {}", uucore::util_name(), @@ -294,7 +269,7 @@ fn create_dir( #[cfg(all(unix, target_os = "linux"))] let new_mode = if path_exists { - mode + config.mode } else { // TODO: Make this macos and freebsd compatible by creating a function to get permission bits from // acl in extended attributes @@ -303,24 +278,24 @@ fn create_dir( if is_parent { (!mode::get_umask() & 0o777) | 0o300 | acl_perm_bits } else { - mode | acl_perm_bits + config.mode | acl_perm_bits } }; #[cfg(all(unix, not(target_os = "linux")))] let new_mode = if is_parent { (!mode::get_umask() & 0o777) | 0o300 } else { - mode + config.mode }; #[cfg(windows)] - let new_mode = mode; + let new_mode = config.mode; chmod(path, new_mode)?; // Apply SELinux context if requested - #[cfg(target_os = "linux")] - if set_selinux_context { - if let Err(e) = uucore::selinux_support::set_selinux_security_context(path, context) + #[cfg(feature = "selinux")] + if config.set_selinux_context && uucore::selinux::check_selinux_enabled().is_ok() { + if let Err(e) = uucore::selinux::set_selinux_security_context(path, config.context) { let _ = std::fs::remove_dir(path); return Err(USimpleError::new( From fe77174904466b02bb631841c79e688ad7415046 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 31 Mar 2025 23:55:52 +0200 Subject: [PATCH 5/5] CI/Selinux: also install attr for getfattr - test --- .github/workflows/CICD.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 2992baa77..8ab528f39 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -1188,7 +1188,7 @@ jobs: - run: rsync -v -a -e ssh . lima-default:~/work/ - name: Setup Rust and other build deps in VM run: | - lima sudo dnf install gcc g++ git rustup libselinux-devel clang-devel -y + lima sudo dnf install gcc g++ git rustup libselinux-devel clang-devel attr -y lima rustup-init -y --default-toolchain stable - name: Verify SELinux Status run: |