pub mod carve;
#[cfg(target_os = "linux")]
pub mod linux;
use std::fmt;
use std::path::{Path, PathBuf};
use anyhow::Result;
use clap::ValueEnum;
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum Backend {
Auto,
Mount,
Landlock,
}
impl fmt::Display for Backend {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
Backend::Auto => "auto",
Backend::Mount => "mount",
Backend::Landlock => "landlock",
};
f.pad(name)
}
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
#[derive(Debug, Clone)]
pub enum Availability {
Available { detail: String },
Unavailable { reason: String },
}
impl Availability {
pub fn is_available(&self) -> bool {
matches!(self, Availability::Available { .. })
}
}
impl fmt::Display for Availability {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Availability::Available { detail } if detail.is_empty() => f.write_str("available"),
Availability::Available { detail } => write!(f, "available ({detail})"),
Availability::Unavailable { reason } => write!(f, "unavailable — {reason}"),
}
}
}
pub struct Plan {
pub backend: Backend,
pub protected: Vec<PathBuf>,
pub pinned: Vec<PathBuf>,
pub carve: Option<carve::Carve>,
}
impl Plan {
pub fn build(backend: Backend, root: &Path, protected: Vec<PathBuf>) -> Plan {
let carve =
(backend == Backend::Landlock).then(|| carve::plan(&protected, &carve::read_dir));
let pinned = match backend {
Backend::Mount => pinned_directories(root, &protected),
_ => Vec::new(),
};
Plan {
backend,
protected,
pinned,
carve,
}
}
}
pub fn pinned_directories(root: &Path, protected: &[PathBuf]) -> Vec<PathBuf> {
let mut pinned = std::collections::BTreeSet::new();
for path in protected {
for ancestor in path.ancestors().skip(1) {
if !ancestor.starts_with(root) {
break;
}
pinned.insert(ancestor.to_path_buf());
if ancestor == root {
break;
}
}
}
pinned.into_iter().collect()
}
#[cfg(target_os = "linux")]
pub fn availability() -> Vec<(Backend, Availability)> {
vec![
(Backend::Mount, linux::mount_availability()),
(Backend::Landlock, linux::landlock_availability()),
]
}
#[cfg(not(target_os = "linux"))]
pub fn availability() -> Vec<(Backend, Availability)> {
let reason = format!(
"kernel enforcement is Linux-only, this is {}",
std::env::consts::OS
);
vec![
(
Backend::Mount,
Availability::Unavailable {
reason: reason.clone(),
},
),
(Backend::Landlock, Availability::Unavailable { reason }),
]
}
pub fn resolve(requested: Backend) -> Result<Backend> {
let availability = availability();
let find = |backend: Backend| {
availability
.iter()
.find(|(candidate, _)| *candidate == backend)
.map(|(_, status)| status.clone())
.expect("every backend is reported")
};
match requested {
Backend::Auto => {
for backend in [Backend::Mount, Backend::Landlock] {
if find(backend).is_available() {
return Ok(backend);
}
}
let reasons = availability
.iter()
.map(|(backend, status)| format!("\n {backend:<9} {status}"))
.collect::<String>();
anyhow::bail!("no enforcement backend is available:{reasons}")
}
backend => match find(backend) {
Availability::Available { .. } => Ok(backend),
Availability::Unavailable { reason } => {
anyhow::bail!("the {backend} backend is unavailable — {reason}")
}
},
}
}
#[cfg(target_os = "linux")]
pub fn enforce_and_exec(plan: &Plan, command: &[std::ffi::OsString]) -> anyhow::Error {
linux::enforce_and_exec(plan, command)
}
#[cfg(not(target_os = "linux"))]
pub fn enforce_and_exec(_plan: &Plan, _command: &[std::ffi::OsString]) -> anyhow::Error {
anyhow::anyhow!(
"kernel enforcement is Linux-only, this is {} (use --dry-run to inspect the plan)",
std::env::consts::OS
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pins_every_directory_down_to_a_protected_file() {
let root = Path::new("/p");
let protected = vec![
PathBuf::from("/p/src/deep/index.tsx"),
PathBuf::from("/p/.env"),
];
assert_eq!(
pinned_directories(root, &protected),
[
PathBuf::from("/p"),
PathBuf::from("/p/src"),
PathBuf::from("/p/src/deep"),
]
);
}
#[test]
fn pinning_stops_at_the_project_root() {
let root = Path::new("/p");
let pinned = pinned_directories(root, &[PathBuf::from("/p/a")]);
assert_eq!(pinned, [PathBuf::from("/p")]);
}
#[test]
fn nothing_protected_means_nothing_pinned() {
assert!(pinned_directories(Path::new("/p"), &[]).is_empty());
}
}