mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 03:27:44 +00:00
Merge pull request #7752 from sylvestre/stat-C
stat: add support for selinux
This commit is contained in:
commit
219e87d57c
5 changed files with 169 additions and 1 deletions
|
@ -51,6 +51,7 @@ feat_selinux = [
|
||||||
"id/selinux",
|
"id/selinux",
|
||||||
"ls/selinux",
|
"ls/selinux",
|
||||||
"mkdir/selinux",
|
"mkdir/selinux",
|
||||||
|
"stat/selinux",
|
||||||
"selinux",
|
"selinux",
|
||||||
"feat_require_selinux",
|
"feat_require_selinux",
|
||||||
]
|
]
|
||||||
|
|
|
@ -22,6 +22,9 @@ clap = { workspace = true }
|
||||||
uucore = { workspace = true, features = ["entries", "libc", "fs", "fsext"] }
|
uucore = { workspace = true, features = ["entries", "libc", "fs", "fsext"] }
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
selinux = ["uucore/selinux"]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "stat"
|
name = "stat"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
|
@ -902,7 +902,27 @@ impl Stater {
|
||||||
// FIXME: blocksize differs on various platform
|
// FIXME: blocksize differs on various platform
|
||||||
// See coreutils/gnulib/lib/stat-size.h ST_NBLOCKSIZE // spell-checker:disable-line
|
// See coreutils/gnulib/lib/stat-size.h ST_NBLOCKSIZE // spell-checker:disable-line
|
||||||
'B' => OutputType::Unsigned(512),
|
'B' => OutputType::Unsigned(512),
|
||||||
|
// SELinux security context string
|
||||||
|
'C' => {
|
||||||
|
#[cfg(feature = "selinux")]
|
||||||
|
{
|
||||||
|
if uucore::selinux::check_selinux_enabled().is_ok() {
|
||||||
|
match uucore::selinux::get_selinux_security_context(Path::new(file))
|
||||||
|
{
|
||||||
|
Ok(ctx) => OutputType::Str(ctx),
|
||||||
|
Err(_) => OutputType::Str(
|
||||||
|
"failed to get security context".to_string(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
OutputType::Str("unsupported on this system".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "selinux"))]
|
||||||
|
{
|
||||||
|
OutputType::Str("unsupported for this operating system".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
// device number in decimal
|
// device number in decimal
|
||||||
'd' => OutputType::Unsigned(meta.dev()),
|
'd' => OutputType::Unsigned(meta.dev()),
|
||||||
// device number in hex
|
// device number in hex
|
||||||
|
|
|
@ -10,6 +10,20 @@ use selinux::SecurityContext;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
SELinuxNotEnabled,
|
SELinuxNotEnabled,
|
||||||
|
FileOpenFailure,
|
||||||
|
ContextRetrievalFailure,
|
||||||
|
ContextConversionFailure,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Error> for i32 {
|
||||||
|
fn from(error: Error) -> i32 {
|
||||||
|
match error {
|
||||||
|
Error::SELinuxNotEnabled => 1,
|
||||||
|
Error::FileOpenFailure => 2,
|
||||||
|
Error::ContextRetrievalFailure => 3,
|
||||||
|
Error::ContextConversionFailure => 4,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if SELinux is enabled on the system.
|
/// Checks if SELinux is enabled on the system.
|
||||||
|
@ -109,6 +123,73 @@ pub fn set_selinux_security_context(path: &Path, context: Option<&String>) -> Re
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the SELinux security context for the given filesystem path.
|
||||||
|
///
|
||||||
|
/// Retrieves the security context of the specified filesystem path if SELinux is enabled
|
||||||
|
/// on the system.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `path` - Filesystem path for which to retrieve the SELinux context.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Ok(String)` - The SELinux context string if successfully retrieved. Returns an empty
|
||||||
|
/// string if no context was found.
|
||||||
|
/// * `Err(Error)` - An error variant indicating the type of failure:
|
||||||
|
/// - `Error::SELinuxNotEnabled` - SELinux is not enabled on the system.
|
||||||
|
/// - `Error::FileOpenFailure` - Failed to open the specified file.
|
||||||
|
/// - `Error::ContextRetrievalFailure` - Failed to retrieve the security context.
|
||||||
|
/// - `Error::ContextConversionFailure` - Failed to convert the security context to a string.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use std::path::Path;
|
||||||
|
/// use uucore::selinux::{get_selinux_security_context, Error};
|
||||||
|
///
|
||||||
|
/// // Get the SELinux context for a file
|
||||||
|
/// match get_selinux_security_context(Path::new("/path/to/file")) {
|
||||||
|
/// Ok(context) => {
|
||||||
|
/// if context.is_empty() {
|
||||||
|
/// println!("No SELinux context found for the file");
|
||||||
|
/// } else {
|
||||||
|
/// println!("SELinux context: {}", context);
|
||||||
|
/// }
|
||||||
|
/// },
|
||||||
|
/// Err(Error::SELinuxNotEnabled) => println!("SELinux is not enabled on this system"),
|
||||||
|
/// Err(Error::FileOpenFailure) => println!("Failed to open the file"),
|
||||||
|
/// Err(Error::ContextRetrievalFailure) => println!("Failed to retrieve the security context"),
|
||||||
|
/// Err(Error::ContextConversionFailure) => println!("Failed to convert the security context to a string"),
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
pub fn get_selinux_security_context(path: &Path) -> Result<String, Error> {
|
||||||
|
if selinux::kernel_support() == selinux::KernelSupport::Unsupported {
|
||||||
|
return Err(Error::SELinuxNotEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
let f = std::fs::File::open(path).map_err(|_| Error::FileOpenFailure)?;
|
||||||
|
|
||||||
|
// Get the security context of the file
|
||||||
|
let context = match SecurityContext::of_file(&f, false) {
|
||||||
|
Ok(Some(ctx)) => ctx,
|
||||||
|
Ok(None) => return Ok(String::new()), // No context found, return empty string
|
||||||
|
Err(_) => return Err(Error::ContextRetrievalFailure),
|
||||||
|
};
|
||||||
|
|
||||||
|
let context_c_string = context
|
||||||
|
.to_c_string()
|
||||||
|
.map_err(|_| Error::ContextConversionFailure)?;
|
||||||
|
|
||||||
|
if let Some(c_str) = context_c_string {
|
||||||
|
// Convert the C string to a Rust String
|
||||||
|
Ok(c_str.to_string_lossy().to_string())
|
||||||
|
} else {
|
||||||
|
Ok(String::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -171,4 +252,43 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_selinux_security_context() {
|
||||||
|
let tmpfile = NamedTempFile::new().expect("Failed to create tempfile");
|
||||||
|
let path = tmpfile.path();
|
||||||
|
|
||||||
|
std::fs::write(path, b"test content").expect("Failed to write to tempfile");
|
||||||
|
|
||||||
|
let result = get_selinux_security_context(path);
|
||||||
|
|
||||||
|
if result.is_ok() {
|
||||||
|
println!("Retrieved SELinux context: {}", result.unwrap());
|
||||||
|
} else {
|
||||||
|
let err = result.unwrap_err();
|
||||||
|
|
||||||
|
// Valid error types
|
||||||
|
match err {
|
||||||
|
Error::SELinuxNotEnabled => assert!(true, "SELinux not supported"),
|
||||||
|
Error::ContextRetrievalFailure => assert!(true, "Context retrieval failure"),
|
||||||
|
Error::ContextConversionFailure => assert!(true, "Context conversion failure"),
|
||||||
|
Error::FileOpenFailure => {
|
||||||
|
panic!("File open failure occurred despite file being created")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_selinux_context_nonexistent_file() {
|
||||||
|
let path = Path::new("/nonexistent/file/that/does/not/exist");
|
||||||
|
|
||||||
|
let result = get_selinux_security_context(path);
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(
|
||||||
|
matches!(result.unwrap_err(), Error::FileOpenFailure),
|
||||||
|
"Expected file open error for nonexistent file"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -490,3 +490,27 @@ fn test_printf_invalid_directive() {
|
||||||
.fails_with_code(1)
|
.fails_with_code(1)
|
||||||
.stderr_contains("'%9%': invalid directive");
|
.stderr_contains("'%9%': invalid directive");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "feat_selinux")]
|
||||||
|
fn test_stat_selinux() {
|
||||||
|
let ts = TestScenario::new(util_name!());
|
||||||
|
let at = &ts.fixtures;
|
||||||
|
at.touch("f");
|
||||||
|
ts.ucmd()
|
||||||
|
.arg("--printf='%C'")
|
||||||
|
.arg("f")
|
||||||
|
.succeeds()
|
||||||
|
.no_stderr()
|
||||||
|
.stdout_contains("unconfined_u");
|
||||||
|
ts.ucmd()
|
||||||
|
.arg("--printf='%C'")
|
||||||
|
.arg("/bin/")
|
||||||
|
.succeeds()
|
||||||
|
.no_stderr()
|
||||||
|
.stdout_contains("system_u");
|
||||||
|
// Count that we have 4 fields
|
||||||
|
let result = ts.ucmd().arg("--printf='%C'").arg("/bin/").succeeds();
|
||||||
|
let s: Vec<_> = result.stdout_str().split(":").collect();
|
||||||
|
assert!(s.len() == 4);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue