use std::fs;
use std::io::{self, Read, Write};
use std::path::Path;
use std::sync::OnceLock;
use thiserror::Error;
use crate::error::{UError, USimpleError, strip_errno};
use crate::translate;
#[derive(Debug, Error)]
pub enum SmackError {
#[error("{}", translate!("smack-error-not-enabled"))]
SmackNotEnabled,
#[error("{}", translate!("smack-error-label-retrieval-failure", "error" => strip_errno(.0)))]
LabelRetrievalFailure(io::Error),
#[error("{}", translate!("smack-error-label-set-failure", "context" => .0.clone(), "error" => strip_errno(.1)))]
LabelSetFailure(String, io::Error),
}
impl UError for SmackError {
fn code(&self) -> i32 {
match self {
Self::SmackNotEnabled => 1,
Self::LabelRetrievalFailure(_) => 2,
Self::LabelSetFailure(_, _) => 3,
}
}
}
impl From<SmackError> for i32 {
fn from(error: SmackError) -> Self {
error.code()
}
}
pub fn is_smack_enabled() -> bool {
static SMACK_ENABLED: OnceLock<bool> = OnceLock::new();
*SMACK_ENABLED.get_or_init(|| Path::new("/sys/fs/smackfs").exists())
}
pub fn get_smack_label_for_self() -> Result<String, SmackError> {
if !is_smack_enabled() {
return Err(SmackError::SmackNotEnabled);
}
let mut label = String::new();
fs::File::open("/proc/self/attr/current")
.map_err(SmackError::LabelRetrievalFailure)?
.read_to_string(&mut label)
.map_err(SmackError::LabelRetrievalFailure)?;
Ok(label.trim().to_string())
}
pub fn set_smack_label_for_self(label: &str) -> Result<(), SmackError> {
if !is_smack_enabled() {
return Err(SmackError::SmackNotEnabled);
}
fs::File::create("/proc/self/attr/current")
.and_then(|mut f| f.write_all(label.as_bytes()))
.map_err(|e| SmackError::LabelSetFailure(label.to_string(), e))
}
pub fn get_smack_label_for_path(path: &Path) -> Result<String, SmackError> {
if !is_smack_enabled() {
return Err(SmackError::SmackNotEnabled);
}
match xattr::get(path, "security.SMACK64") {
Ok(Some(value)) => Ok(String::from_utf8_lossy(&value).trim().to_string()),
Ok(None) => Err(SmackError::LabelRetrievalFailure(io::Error::new(
io::ErrorKind::NotFound,
translate!("smack-error-no-label-set"),
))),
Err(e) => Err(SmackError::LabelRetrievalFailure(e)),
}
}
pub fn set_smack_label_for_path(path: &Path, label: &str) -> Result<(), SmackError> {
if !is_smack_enabled() {
return Err(SmackError::SmackNotEnabled);
}
xattr::set(path, "security.SMACK64", label.as_bytes())
.map_err(|e| SmackError::LabelSetFailure(label.to_string(), e))
}
pub fn set_smack_label_and_cleanup(
path: impl AsRef<Path>,
context: Option<&String>,
cleanup: impl FnOnce(&Path) -> io::Result<()>,
) -> Result<(), Box<dyn UError>> {
let Some(ctx) = context else { return Ok(()) };
if !is_smack_enabled() {
return Ok(());
}
let path = path.as_ref();
set_smack_label_for_path(path, ctx).map_err(|e| {
let _ = cleanup(path);
USimpleError::new(1, e.to_string())
})
}