mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 03:27:44 +00:00
Merge pull request #7635 from sylvestre/temp-selinux-impl
mkdir: add the selinux support
This commit is contained in:
commit
80b6a2155c
10 changed files with 315 additions and 23 deletions
2
.github/workflows/CICD.yml
vendored
2
.github/workflows/CICD.yml
vendored
|
@ -1188,7 +1188,7 @@ jobs:
|
||||||
- run: rsync -v -a -e ssh . lima-default:~/work/
|
- run: rsync -v -a -e ssh . lima-default:~/work/
|
||||||
- name: Setup Rust and other build deps in VM
|
- name: Setup Rust and other build deps in VM
|
||||||
run: |
|
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
|
lima rustup-init -y --default-toolchain stable
|
||||||
- name: Verify SELinux Status
|
- name: Verify SELinux Status
|
||||||
run: |
|
run: |
|
||||||
|
|
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -3585,6 +3585,7 @@ dependencies = [
|
||||||
"number_prefix",
|
"number_prefix",
|
||||||
"os_display",
|
"os_display",
|
||||||
"regex",
|
"regex",
|
||||||
|
"selinux",
|
||||||
"sha1",
|
"sha1",
|
||||||
"sha2",
|
"sha2",
|
||||||
"sha3",
|
"sha3",
|
||||||
|
|
|
@ -50,6 +50,7 @@ feat_selinux = [
|
||||||
"cp/selinux",
|
"cp/selinux",
|
||||||
"id/selinux",
|
"id/selinux",
|
||||||
"ls/selinux",
|
"ls/selinux",
|
||||||
|
"mkdir/selinux",
|
||||||
"selinux",
|
"selinux",
|
||||||
"feat_require_selinux",
|
"feat_require_selinux",
|
||||||
]
|
]
|
||||||
|
|
|
@ -21,6 +21,8 @@ path = "src/mkdir.rs"
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
uucore = { workspace = true, features = ["fs", "mode", "fsxattr"] }
|
uucore = { workspace = true, features = ["fs", "mode", "fsxattr"] }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
selinux = ["uucore/selinux"]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "mkdir"
|
name = "mkdir"
|
||||||
|
|
|
@ -29,6 +29,26 @@ mod options {
|
||||||
pub const PARENTS: &str = "parents";
|
pub const PARENTS: &str = "parents";
|
||||||
pub const VERBOSE: &str = "verbose";
|
pub const VERBOSE: &str = "verbose";
|
||||||
pub const DIRS: &str = "dirs";
|
pub const DIRS: &str = "dirs";
|
||||||
|
pub const SELINUX: &str = "z";
|
||||||
|
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)]
|
#[cfg(windows)]
|
||||||
|
@ -91,8 +111,21 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let verbose = matches.get_flag(options::VERBOSE);
|
let verbose = matches.get_flag(options::VERBOSE);
|
||||||
let recursive = matches.get_flag(options::PARENTS);
|
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::<String>(options::CONTEXT);
|
||||||
|
|
||||||
match get_mode(&matches, mode_had_minus_prefix) {
|
match get_mode(&matches, mode_had_minus_prefix) {
|
||||||
Ok(mode) => exec(dirs, recursive, mode, verbose),
|
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)),
|
Err(f) => Err(USimpleError::new(1, f)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,6 +157,15 @@ pub fn uu_app() -> Command {
|
||||||
.help("print a message for each printed directory")
|
.help("print a message for each printed directory")
|
||||||
.action(ArgAction::SetTrue),
|
.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(
|
||||||
Arg::new(options::DIRS)
|
Arg::new(options::DIRS)
|
||||||
.action(ArgAction::Append)
|
.action(ArgAction::Append)
|
||||||
|
@ -137,12 +179,12 @@ pub fn uu_app() -> Command {
|
||||||
/**
|
/**
|
||||||
* Create the list of new directories
|
* Create the list of new directories
|
||||||
*/
|
*/
|
||||||
fn exec(dirs: ValuesRef<OsString>, recursive: bool, mode: u32, verbose: bool) -> UResult<()> {
|
fn exec(dirs: ValuesRef<OsString>, config: &Config) -> UResult<()> {
|
||||||
for dir in dirs {
|
for dir in dirs {
|
||||||
let path_buf = PathBuf::from(dir);
|
let path_buf = PathBuf::from(dir);
|
||||||
let path = path_buf.as_path();
|
let path = path_buf.as_path();
|
||||||
|
|
||||||
show_if_err!(mkdir(path, recursive, mode, verbose));
|
show_if_err!(mkdir(path, config));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -160,7 +202,7 @@ fn exec(dirs: ValuesRef<OsString>, recursive: bool, mode: u32, verbose: bool) ->
|
||||||
///
|
///
|
||||||
/// To match the GNU behavior, a path with the last directory being a single dot
|
/// 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).
|
/// (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, config: &Config) -> UResult<()> {
|
||||||
if path.as_os_str().is_empty() {
|
if path.as_os_str().is_empty() {
|
||||||
return Err(USimpleError::new(
|
return Err(USimpleError::new(
|
||||||
1,
|
1,
|
||||||
|
@ -173,7 +215,7 @@ pub fn mkdir(path: &Path, recursive: bool, mode: u32, verbose: bool) -> UResult<
|
||||||
// std::fs::create_dir("foo/."); fails in pure Rust
|
// std::fs::create_dir("foo/."); fails in pure Rust
|
||||||
let path_buf = dir_strip_dot_for_creation(path);
|
let path_buf = dir_strip_dot_for_creation(path);
|
||||||
let path = path_buf.as_path();
|
let path = path_buf.as_path();
|
||||||
create_dir(path, recursive, verbose, false, mode)
|
create_dir(path, false, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(unix, target_os = "redox"))]
|
#[cfg(any(unix, target_os = "redox"))]
|
||||||
|
@ -194,15 +236,9 @@ fn chmod(_path: &Path, _mode: u32) -> UResult<()> {
|
||||||
// Return true if the directory at `path` has been created by this call.
|
// Return true if the directory at `path` has been created by this call.
|
||||||
// `is_parent` argument is not used on windows
|
// `is_parent` argument is not used on windows
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn create_dir(
|
fn create_dir(path: &Path, is_parent: bool, config: &Config) -> UResult<()> {
|
||||||
path: &Path,
|
|
||||||
recursive: bool,
|
|
||||||
verbose: bool,
|
|
||||||
is_parent: bool,
|
|
||||||
mode: u32,
|
|
||||||
) -> UResult<()> {
|
|
||||||
let path_exists = path.exists();
|
let path_exists = path.exists();
|
||||||
if path_exists && !recursive {
|
if path_exists && !config.recursive {
|
||||||
return Err(USimpleError::new(
|
return Err(USimpleError::new(
|
||||||
1,
|
1,
|
||||||
format!("{}: File exists", path.display()),
|
format!("{}: File exists", path.display()),
|
||||||
|
@ -212,9 +248,9 @@ fn create_dir(
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if recursive {
|
if config.recursive {
|
||||||
match path.parent() {
|
match path.parent() {
|
||||||
Some(p) => create_dir(p, recursive, verbose, true, mode)?,
|
Some(p) => create_dir(p, true, config)?,
|
||||||
None => {
|
None => {
|
||||||
USimpleError::new(1, "failed to create whole tree");
|
USimpleError::new(1, "failed to create whole tree");
|
||||||
}
|
}
|
||||||
|
@ -223,7 +259,7 @@ fn create_dir(
|
||||||
|
|
||||||
match std::fs::create_dir(path) {
|
match std::fs::create_dir(path) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
if verbose {
|
if config.verbose {
|
||||||
println!(
|
println!(
|
||||||
"{}: created directory {}",
|
"{}: created directory {}",
|
||||||
uucore::util_name(),
|
uucore::util_name(),
|
||||||
|
@ -233,7 +269,7 @@ fn create_dir(
|
||||||
|
|
||||||
#[cfg(all(unix, target_os = "linux"))]
|
#[cfg(all(unix, target_os = "linux"))]
|
||||||
let new_mode = if path_exists {
|
let new_mode = if path_exists {
|
||||||
mode
|
config.mode
|
||||||
} else {
|
} else {
|
||||||
// TODO: Make this macos and freebsd compatible by creating a function to get permission bits from
|
// TODO: Make this macos and freebsd compatible by creating a function to get permission bits from
|
||||||
// acl in extended attributes
|
// acl in extended attributes
|
||||||
|
@ -242,19 +278,33 @@ fn create_dir(
|
||||||
if is_parent {
|
if is_parent {
|
||||||
(!mode::get_umask() & 0o777) | 0o300 | acl_perm_bits
|
(!mode::get_umask() & 0o777) | 0o300 | acl_perm_bits
|
||||||
} else {
|
} else {
|
||||||
mode | acl_perm_bits
|
config.mode | acl_perm_bits
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
#[cfg(all(unix, not(target_os = "linux")))]
|
#[cfg(all(unix, not(target_os = "linux")))]
|
||||||
let new_mode = if is_parent {
|
let new_mode = if is_parent {
|
||||||
(!mode::get_umask() & 0o777) | 0o300
|
(!mode::get_umask() & 0o777) | 0o300
|
||||||
} else {
|
} else {
|
||||||
mode
|
config.mode
|
||||||
};
|
};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let new_mode = mode;
|
let new_mode = config.mode;
|
||||||
|
|
||||||
chmod(path, new_mode)?;
|
chmod(path, new_mode)?;
|
||||||
|
|
||||||
|
// Apply SELinux context if requested
|
||||||
|
#[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(
|
||||||
|
1,
|
||||||
|
format!("failed to set SELinux security context: {}", e),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,7 @@ crc32fast = { workspace = true, optional = true }
|
||||||
regex = { workspace = true, optional = true }
|
regex = { workspace = true, optional = true }
|
||||||
bigdecimal = { workspace = true, optional = true }
|
bigdecimal = { workspace = true, optional = true }
|
||||||
num-traits = { workspace = true, optional = true }
|
num-traits = { workspace = true, optional = true }
|
||||||
|
selinux = { workspace = true, optional = true }
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
walkdir = { workspace = true, optional = true }
|
walkdir = { workspace = true, optional = true }
|
||||||
|
@ -105,13 +106,14 @@ format = [
|
||||||
mode = ["libc"]
|
mode = ["libc"]
|
||||||
perms = ["entries", "libc", "walkdir"]
|
perms = ["entries", "libc", "walkdir"]
|
||||||
buf-copy = []
|
buf-copy = []
|
||||||
|
parser = ["extendedbigdecimal", "glob", "num-traits"]
|
||||||
pipes = []
|
pipes = []
|
||||||
process = ["libc"]
|
process = ["libc"]
|
||||||
proc-info = ["tty", "walkdir"]
|
proc-info = ["tty", "walkdir"]
|
||||||
quoting-style = []
|
quoting-style = []
|
||||||
ranges = []
|
ranges = []
|
||||||
ringbuffer = []
|
ringbuffer = []
|
||||||
parser = ["extendedbigdecimal", "glob", "num-traits"]
|
selinux = ["dep:selinux"]
|
||||||
signals = []
|
signals = []
|
||||||
sum = [
|
sum = [
|
||||||
"digest",
|
"digest",
|
||||||
|
|
|
@ -66,6 +66,8 @@ pub mod tty;
|
||||||
|
|
||||||
#[cfg(all(unix, feature = "fsxattr"))]
|
#[cfg(all(unix, feature = "fsxattr"))]
|
||||||
pub mod fsxattr;
|
pub mod fsxattr;
|
||||||
|
#[cfg(all(target_os = "linux", feature = "selinux"))]
|
||||||
|
pub mod selinux;
|
||||||
#[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))]
|
#[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))]
|
||||||
pub mod signals;
|
pub mod signals;
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
|
|
174
src/uucore/src/lib/features/selinux.rs
Normal file
174
src/uucore/src/lib/features/selinux.rs
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
#[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.
|
||||||
|
/// 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
|
||||||
|
check_selinux_enabled().map_err(|e| format!("{:?}", e))?;
|
||||||
|
|
||||||
|
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)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -103,6 +103,9 @@ pub use crate::features::fsext;
|
||||||
#[cfg(all(unix, feature = "fsxattr"))]
|
#[cfg(all(unix, feature = "fsxattr"))]
|
||||||
pub use crate::features::fsxattr;
|
pub use crate::features::fsxattr;
|
||||||
|
|
||||||
|
#[cfg(all(target_os = "linux", feature = "selinux"))]
|
||||||
|
pub use crate::features::selinux;
|
||||||
|
|
||||||
//## core functions
|
//## core functions
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
// For the full copyright and license information, please view the LICENSE
|
// For the full copyright and license information, please view the LICENSE
|
||||||
// file that was distributed with this source code.
|
// 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)]
|
#![allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
|
||||||
|
|
||||||
|
@ -358,3 +358,60 @@ fn test_empty_argument() {
|
||||||
.fails()
|
.fails()
|
||||||
.stderr_only("mkdir: cannot create directory '': No such file or directory\n");
|
.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));
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue