apt-ostree/src/ostree_detection.rs

286 lines
No EOL
9.6 KiB
Rust

use std::path::Path;
use std::fs;
use std::io::Read;
use anyhow::{Result, Context};
use tracing::{debug, info, warn};
use ostree::gio;
/// OSTree environment detection module
///
/// This module provides functions to detect if apt-ostree is running
/// in an OSTree environment, following the same patterns as rpm-ostree.
pub struct OstreeDetection;
impl OstreeDetection {
/// Check if OSTree filesystem is present
///
/// This checks for the existence of `/ostree` directory, which indicates
/// that the OSTree filesystem layout is present.
///
/// Used by: Main daemon service (ConditionPathExists=/ostree)
pub fn is_ostree_filesystem() -> bool {
Path::new("/ostree").exists()
}
/// Check if system is booted from OSTree
///
/// This checks for the existence of `/run/ostree-booted` file, which indicates
/// that the system is currently booted from an OSTree deployment.
///
/// Used by: Boot status and monitoring services (ConditionPathExists=/run/ostree-booted)
pub fn is_ostree_booted() -> bool {
Path::new("/run/ostree-booted").exists()
}
/// Check if OSTree kernel parameter is present
///
/// This checks for the presence of "ostree" in the kernel command line,
/// which filters out non-traditional OSTree setups (e.g., live boots).
///
/// Used by: Security fix services (ConditionKernelCommandLine=ostree)
pub fn has_ostree_kernel_param() -> Result<bool> {
let mut cmdline = String::new();
fs::File::open("/proc/cmdline")
.context("Failed to open /proc/cmdline")?
.read_to_string(&mut cmdline)
.context("Failed to read kernel command line")?;
Ok(cmdline.contains("ostree"))
}
/// Check if OSTree sysroot can be loaded
///
/// This attempts to load the OSTree sysroot using the OSTree library,
/// which validates the OSTree repository structure.
///
/// Used by: Application-level detection
pub fn can_load_ostree_sysroot() -> Result<bool> {
// Use OSTree Rust bindings to check if sysroot can be loaded
let sysroot = ostree::Sysroot::new_default();
match sysroot.load(None::<&gio::Cancellable>) {
Ok(_) => {
debug!("OSTree sysroot loaded successfully");
Ok(true)
},
Err(e) => {
debug!("Failed to load OSTree sysroot: {}", e);
Ok(false)
}
}
}
/// Check if there's a booted deployment
///
/// This checks if there's a valid booted deployment in the OSTree sysroot.
///
/// Used by: Application-level detection
pub fn has_booted_deployment() -> Result<bool> {
let sysroot = ostree::Sysroot::new_default();
match sysroot.load(None::<&gio::Cancellable>) {
Ok(_) => {
match sysroot.booted_deployment() {
Some(_) => {
debug!("Booted deployment found");
Ok(true)
},
None => {
debug!("No booted deployment found");
Ok(false)
}
}
},
Err(e) => {
debug!("Failed to load OSTree sysroot: {}", e);
Ok(false)
}
}
}
/// Check if apt-ostree daemon is available
///
/// This checks for the availability of the apt-ostree daemon via D-Bus.
///
/// Used by: Daemon-level detection
pub async fn is_apt_ostree_daemon_available() -> Result<bool> {
match zbus::Connection::system().await {
Ok(conn) => {
match zbus::Proxy::new(
&conn,
"org.aptostree.dev",
"/org/aptostree/dev/Daemon",
"org.aptostree.dev.Daemon"
).await {
Ok(_) => {
debug!("apt-ostree daemon is available");
Ok(true)
},
Err(e) => {
debug!("apt-ostree daemon is not available: {}", e);
Ok(false)
}
}
},
Err(e) => {
debug!("Failed to connect to system D-Bus: {}", e);
Ok(false)
}
}
}
/// Comprehensive OSTree environment check
///
/// This performs all detection methods and returns a comprehensive
/// assessment of the OSTree environment.
pub async fn check_ostree_environment() -> Result<OstreeEnvironmentStatus> {
let filesystem = Self::is_ostree_filesystem();
let booted = Self::is_ostree_booted();
let kernel_param = Self::has_ostree_kernel_param()?;
let sysroot_loadable = Self::can_load_ostree_sysroot()?;
let has_deployment = Self::has_booted_deployment()?;
let daemon_available = Self::is_apt_ostree_daemon_available().await?;
let status = OstreeEnvironmentStatus {
filesystem,
booted,
kernel_param,
sysroot_loadable,
has_deployment,
daemon_available,
};
info!("OSTree environment status: {:?}", status);
Ok(status)
}
/// Check if apt-ostree can operate in the current environment
///
/// This determines if apt-ostree can function properly based on
/// the current environment detection.
pub async fn can_operate() -> Result<bool> {
let status = Self::check_ostree_environment().await?;
// Basic requirements: OSTree filesystem and booted deployment
let can_operate = status.filesystem && status.has_deployment;
if !can_operate {
warn!("apt-ostree cannot operate in this environment");
warn!("Filesystem: {}, Booted deployment: {}",
status.filesystem, status.has_deployment);
}
Ok(can_operate)
}
/// Validate environment and return user-friendly error if needed
///
/// This checks the environment and returns a helpful error message
/// if apt-ostree cannot operate.
pub async fn validate_environment() -> Result<()> {
if !Self::can_operate().await? {
return Err(anyhow::anyhow!(
"apt-ostree requires an OSTree environment to operate.\n\
\n\
This system does not appear to be running on an OSTree deployment.\n\
\n\
To use apt-ostree:\n\
1. Ensure you are running on an OSTree-based system\n\
2. Verify that /ostree directory exists\n\
3. Verify that /run/ostree-booted file exists\n\
4. Ensure you have a valid booted deployment\n\
\n\
For more information, see: https://github.com/your-org/apt-ostree"
));
}
Ok(())
}
}
/// Status of OSTree environment detection
#[derive(Debug, Clone)]
pub struct OstreeEnvironmentStatus {
/// OSTree filesystem is present (/ostree directory exists)
pub filesystem: bool,
/// System is booted from OSTree (/run/ostree-booted exists)
pub booted: bool,
/// OSTree kernel parameter is present
pub kernel_param: bool,
/// OSTree sysroot can be loaded
pub sysroot_loadable: bool,
/// There's a valid booted deployment
pub has_deployment: bool,
/// apt-ostree daemon is available
pub daemon_available: bool,
}
impl OstreeEnvironmentStatus {
/// Check if this is a fully functional OSTree environment
pub fn is_fully_functional(&self) -> bool {
self.filesystem &&
self.booted &&
self.kernel_param &&
self.sysroot_loadable &&
self.has_deployment
}
/// Check if this is a minimal OSTree environment (can operate)
pub fn is_minimal(&self) -> bool {
self.filesystem && self.has_deployment
}
/// Get a human-readable description of the environment
pub fn description(&self) -> String {
if self.is_fully_functional() {
"Fully functional OSTree environment".to_string()
} else if self.is_minimal() {
"Minimal OSTree environment (can operate)".to_string()
} else if self.filesystem {
"Partial OSTree environment (filesystem only)".to_string()
} else {
"Non-OSTree environment".to_string()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ostree_filesystem_detection() {
// This test will pass if /ostree exists, fail otherwise
// In a test environment, we can't guarantee the filesystem state
let _result = OstreeDetection::is_ostree_filesystem();
}
#[test]
fn test_ostree_booted_detection() {
// This test will pass if /run/ostree-booted exists, fail otherwise
let _result = OstreeDetection::is_ostree_booted();
}
#[test]
fn test_kernel_param_detection() {
// This test should always work since /proc/cmdline should exist
let result = OstreeDetection::has_ostree_kernel_param();
assert!(result.is_ok());
}
#[test]
fn test_environment_status() {
let status = OstreeEnvironmentStatus {
filesystem: true,
booted: true,
kernel_param: true,
sysroot_loadable: true,
has_deployment: true,
daemon_available: true,
};
assert!(status.is_fully_functional());
assert!(status.is_minimal());
assert_eq!(status.description(), "Fully functional OSTree environment");
}
}