mirror of
https://gitlab.steamos.cloud/holo/steamos-manager.git
synced 2025-07-05 06:00:30 -04:00
platform: Improve validation of platform config items
This commit is contained in:
parent
f32e354b64
commit
d5d2d2c9a3
3 changed files with 218 additions and 12 deletions
|
@ -855,6 +855,7 @@ impl WifiPowerManagement1 {
|
|||
async fn create_config_interfaces(
|
||||
proxy: &Proxy<'static>,
|
||||
object_server: &ObjectServer,
|
||||
connection: &Connection,
|
||||
job_manager: &UnboundedSender<JobManagerCommand>,
|
||||
tdp_manager: Option<UnboundedSender<TdpManagerCommand>>,
|
||||
) -> Result<()> {
|
||||
|
@ -885,12 +886,24 @@ async fn create_config_interfaces(
|
|||
job_manager: job_manager.clone(),
|
||||
};
|
||||
|
||||
if config.factory_reset.is_some() {
|
||||
object_server.at(MANAGER_PATH, factory_reset).await?;
|
||||
if let Some(config) = config.factory_reset.as_ref() {
|
||||
match config.is_valid(true).await {
|
||||
Ok(true) => {
|
||||
object_server.at(MANAGER_PATH, factory_reset).await?;
|
||||
}
|
||||
Ok(false) => (),
|
||||
Err(e) => error!("Failed to verify if factory reset config is valid: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
if config.fan_control.is_some() {
|
||||
object_server.at(MANAGER_PATH, fan_control).await?;
|
||||
if let Some(config) = config.fan_control.as_ref() {
|
||||
match config.is_valid(connection, true).await {
|
||||
Ok(true) => {
|
||||
object_server.at(MANAGER_PATH, fan_control).await?;
|
||||
}
|
||||
Ok(false) => (),
|
||||
Err(e) => error!("Failed to verify if fan control config is valid: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(manager) = tdp_manager {
|
||||
|
@ -918,7 +931,7 @@ async fn create_config_interfaces(
|
|||
});
|
||||
}
|
||||
|
||||
if let Some(ref config) = config.performance_profile {
|
||||
if let Some(config) = config.performance_profile.as_ref() {
|
||||
if !get_available_platform_profiles(&config.platform_profile_name)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
|
@ -928,8 +941,14 @@ async fn create_config_interfaces(
|
|||
}
|
||||
}
|
||||
|
||||
if config.storage.is_some() {
|
||||
object_server.at(MANAGER_PATH, storage).await?;
|
||||
if let Some(config) = config.storage.as_ref() {
|
||||
match config.is_valid(true).await {
|
||||
Ok(true) => {
|
||||
object_server.at(MANAGER_PATH, storage).await?;
|
||||
}
|
||||
Ok(false) => (),
|
||||
Err(e) => error!("Failed to verify if storage config is valid: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(config) = config.update_bios.as_ref() {
|
||||
|
@ -1007,7 +1026,7 @@ pub(crate) async fn create_interfaces(
|
|||
let object_server = session.object_server();
|
||||
object_server.at(MANAGER_PATH, manager).await?;
|
||||
|
||||
create_config_interfaces(&proxy, object_server, &job_manager, tdp_manager).await?;
|
||||
create_config_interfaces(&proxy, object_server, &system, &job_manager, tdp_manager).await?;
|
||||
|
||||
if device_type().await.unwrap_or_default() == DeviceType::SteamDeck {
|
||||
object_server.at(MANAGER_PATH, als).await?;
|
||||
|
@ -1069,8 +1088,8 @@ mod test {
|
|||
use crate::hardware::test::fake_model;
|
||||
use crate::hardware::SteamDeckVariant;
|
||||
use crate::platform::{
|
||||
BatteryChargeLimitConfig, PerformanceProfileConfig, PlatformConfig, RangeConfig,
|
||||
ResetConfig, ScriptConfig, ServiceConfig, StorageConfig, TdpLimitConfig,
|
||||
BatteryChargeLimitConfig, FormatDeviceConfig, PerformanceProfileConfig, PlatformConfig,
|
||||
RangeConfig, ResetConfig, ScriptConfig, ServiceConfig, StorageConfig, TdpLimitConfig,
|
||||
};
|
||||
use crate::power::TdpLimitingMethod;
|
||||
use crate::systemd::test::{MockManager, MockUnit};
|
||||
|
@ -1078,6 +1097,7 @@ mod test {
|
|||
|
||||
use std::num::NonZeroU32;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use tokio::fs::{set_permissions, write};
|
||||
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver};
|
||||
|
@ -1273,6 +1293,42 @@ mod test {
|
|||
assert!(test_interface_missing::<FactoryReset1>(&test.connection).await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn interface_missing_invalid_all_factory_reset1() {
|
||||
let mut config = all_config().unwrap();
|
||||
config.factory_reset.as_mut().unwrap().all = ScriptConfig {
|
||||
script: PathBuf::from("oxo"),
|
||||
script_args: Vec::new(),
|
||||
};
|
||||
let test = start(Some(config)).await.expect("start");
|
||||
|
||||
assert!(test_interface_missing::<FactoryReset1>(&test.connection).await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn interface_missing_invalid_os_factory_reset1() {
|
||||
let mut config = all_config().unwrap();
|
||||
config.factory_reset.as_mut().unwrap().os = ScriptConfig {
|
||||
script: PathBuf::from("oxo"),
|
||||
script_args: Vec::new(),
|
||||
};
|
||||
let test = start(Some(config)).await.expect("start");
|
||||
|
||||
assert!(test_interface_missing::<FactoryReset1>(&test.connection).await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn interface_missing_invalid_user_factory_reset1() {
|
||||
let mut config = all_config().unwrap();
|
||||
config.factory_reset.as_mut().unwrap().user = ScriptConfig {
|
||||
script: PathBuf::from("oxo"),
|
||||
script_args: Vec::new(),
|
||||
};
|
||||
let test = start(Some(config)).await.expect("start");
|
||||
|
||||
assert!(test_interface_missing::<FactoryReset1>(&test.connection).await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn interface_matches_fan_control1() {
|
||||
let test = start(all_config()).await.expect("start");
|
||||
|
@ -1416,6 +1472,29 @@ mod test {
|
|||
assert!(test_interface_missing::<Storage1>(&test.connection).await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn interface_missing_invalid_trim_storage1() {
|
||||
let mut config = all_config().unwrap();
|
||||
config.storage.as_mut().unwrap().trim_devices = ScriptConfig {
|
||||
script: PathBuf::from("oxo"),
|
||||
script_args: Vec::new(),
|
||||
};
|
||||
let test = start(Some(config)).await.expect("start");
|
||||
|
||||
assert!(test_interface_missing::<Storage1>(&test.connection).await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn interface_missing_invalid_format_storage1() {
|
||||
let mut config = all_config().unwrap();
|
||||
let mut format_config = FormatDeviceConfig::default();
|
||||
format_config.script = PathBuf::from("oxo");
|
||||
config.storage.as_mut().unwrap().format_device = format_config;
|
||||
let test = start(Some(config)).await.expect("start");
|
||||
|
||||
assert!(test_interface_missing::<Storage1>(&test.connection).await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn interface_matches_update_bios1() {
|
||||
let test = start(all_config()).await.expect("start");
|
||||
|
@ -1432,6 +1511,18 @@ mod test {
|
|||
assert!(test_interface_missing::<UpdateBios1>(&test.connection).await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn interface_missing_invalid_update_bios1() {
|
||||
let mut config = all_config().unwrap();
|
||||
config.update_bios = Some(ScriptConfig {
|
||||
script: PathBuf::from("oxo"),
|
||||
script_args: Vec::new(),
|
||||
});
|
||||
let test = start(Some(config)).await.expect("start");
|
||||
|
||||
assert!(test_interface_missing::<UpdateBios1>(&test.connection).await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn interface_matches_update_dock1() {
|
||||
let test = start(all_config()).await.expect("start");
|
||||
|
@ -1448,6 +1539,18 @@ mod test {
|
|||
assert!(test_interface_missing::<UpdateDock1>(&test.connection).await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn interface_missing_invalid_update_dock1() {
|
||||
let mut config = all_config().unwrap();
|
||||
config.update_dock = Some(ScriptConfig {
|
||||
script: PathBuf::from("oxo"),
|
||||
script_args: Vec::new(),
|
||||
});
|
||||
let test = start(Some(config)).await.expect("start");
|
||||
|
||||
assert!(test_interface_missing::<UpdateDock1>(&test.connection).await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn interface_matches_wifi_power_management1() {
|
||||
let test = start(all_config()).await.expect("start");
|
||||
|
|
|
@ -19,10 +19,12 @@ use tokio::fs::{metadata, read_to_string};
|
|||
#[cfg(not(test))]
|
||||
use tokio::sync::OnceCell;
|
||||
use tokio::task::spawn_blocking;
|
||||
use zbus::Connection;
|
||||
|
||||
#[cfg(not(test))]
|
||||
use crate::hardware::{device_type, DeviceType};
|
||||
use crate::power::TdpLimitingMethod;
|
||||
use crate::systemd::SystemdUnit;
|
||||
|
||||
#[cfg(not(test))]
|
||||
static CONFIG: OnceCell<Option<PlatformConfig>> = OnceCell::const_new();
|
||||
|
@ -93,6 +95,14 @@ pub(crate) struct ResetConfig {
|
|||
pub user: ScriptConfig,
|
||||
}
|
||||
|
||||
impl ResetConfig {
|
||||
pub(crate) async fn is_valid(&self, root: bool) -> Result<bool> {
|
||||
Ok(self.all.is_valid(root).await?
|
||||
&& self.os.is_valid(root).await?
|
||||
&& self.user.is_valid(root).await?)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Debug)]
|
||||
pub(crate) enum ServiceConfig {
|
||||
#[serde(rename = "systemd")]
|
||||
|
@ -105,12 +115,33 @@ pub(crate) enum ServiceConfig {
|
|||
},
|
||||
}
|
||||
|
||||
impl ServiceConfig {
|
||||
pub(crate) async fn is_valid(&self, connection: &Connection, root: bool) -> Result<bool> {
|
||||
match self {
|
||||
ServiceConfig::Systemd(unit) => SystemdUnit::exists(connection, unit).await,
|
||||
ServiceConfig::Script {
|
||||
start,
|
||||
stop,
|
||||
status,
|
||||
} => Ok(start.is_valid(root).await?
|
||||
&& stop.is_valid(root).await?
|
||||
&& status.is_valid(root).await?),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Deserialize, Debug)]
|
||||
pub(crate) struct StorageConfig {
|
||||
pub trim_devices: ScriptConfig,
|
||||
pub format_device: FormatDeviceConfig,
|
||||
}
|
||||
|
||||
impl StorageConfig {
|
||||
pub(crate) async fn is_valid(&self, root: bool) -> Result<bool> {
|
||||
Ok(self.trim_devices.is_valid(root).await? && self.format_device.is_valid(root).await?)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Debug)]
|
||||
pub(crate) struct BatteryChargeLimitConfig {
|
||||
pub suggested_minimum_limit: Option<i32>,
|
||||
|
@ -153,6 +184,36 @@ pub(crate) struct FormatDeviceConfig {
|
|||
pub no_validate_flag: Option<String>,
|
||||
}
|
||||
|
||||
impl FormatDeviceConfig {
|
||||
pub(crate) async fn is_valid(&self, root: bool) -> Result<bool> {
|
||||
let meta = match metadata(&self.script).await {
|
||||
Ok(meta) => meta,
|
||||
Err(e) if [ErrorKind::NotFound, ErrorKind::PermissionDenied].contains(&e.kind()) => {
|
||||
return Ok(false)
|
||||
}
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
if !meta.is_file() {
|
||||
return Ok(false);
|
||||
}
|
||||
if root {
|
||||
let script = self.script.clone();
|
||||
if !spawn_blocking(move || match access(&script, AccessFlags::X_OK) {
|
||||
Ok(()) => Ok(true),
|
||||
Err(Errno::ENOENT | Errno::EACCES) => Ok(false),
|
||||
Err(e) => Err(e),
|
||||
})
|
||||
.await??
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
} else if (meta.mode() & 0o111) == 0 {
|
||||
return Ok(false);
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> RangeConfig<T> {
|
||||
#[allow(unused)]
|
||||
pub(crate) fn new(min: T, max: T) -> RangeConfig<T> {
|
||||
|
@ -181,14 +242,35 @@ impl PlatformConfig {
|
|||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn set_test_paths(&mut self) {
|
||||
use crate::path;
|
||||
|
||||
if let Some(ref mut factory_reset) = self.factory_reset {
|
||||
if factory_reset.all.script.as_os_str().is_empty() {
|
||||
factory_reset.all.script = path("exe");
|
||||
}
|
||||
if factory_reset.os.script.as_os_str().is_empty() {
|
||||
factory_reset.os.script = path("exe");
|
||||
}
|
||||
if factory_reset.user.script.as_os_str().is_empty() {
|
||||
factory_reset.user.script = path("exe");
|
||||
}
|
||||
}
|
||||
if let Some(ref mut storage) = self.storage {
|
||||
if storage.trim_devices.script.as_os_str().is_empty() {
|
||||
storage.trim_devices.script = path("exe");
|
||||
}
|
||||
if storage.format_device.script.as_os_str().is_empty() {
|
||||
storage.format_device.script = path("exe");
|
||||
}
|
||||
}
|
||||
if let Some(ref mut update_bios) = self.update_bios {
|
||||
if update_bios.script.as_os_str().is_empty() {
|
||||
update_bios.script = crate::path("exe");
|
||||
update_bios.script = path("exe");
|
||||
}
|
||||
}
|
||||
if let Some(ref mut update_dock) = self.update_dock {
|
||||
if update_dock.script.as_os_str().is_empty() {
|
||||
update_dock.script = crate::path("exe");
|
||||
update_dock.script = path("exe");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,6 +62,8 @@ trait SystemdManager {
|
|||
) -> zbus::Result<Vec<(String, String, String)>>;
|
||||
|
||||
async fn reload(&self) -> zbus::Result<()>;
|
||||
|
||||
async fn get_unit(&self, name: &str) -> zbus::Result<OwnedObjectPath>;
|
||||
}
|
||||
|
||||
#[derive(Display, EnumString, PartialEq, Debug, Copy, Clone)]
|
||||
|
@ -86,6 +88,17 @@ pub async fn daemon_reload(connection: &Connection) -> Result<()> {
|
|||
}
|
||||
|
||||
impl<'dbus> SystemdUnit<'dbus> {
|
||||
pub async fn exists(connection: &Connection, name: &str) -> Result<bool> {
|
||||
let manager = SystemdManagerProxy::new(connection).await?;
|
||||
// This is kinda hacky, but zbus makes it pretty hard to get the proper error name
|
||||
let expected_error = format!("Unit {name} not loaded.");
|
||||
match manager.get_unit(name).await {
|
||||
Ok(_) => Ok(true),
|
||||
Err(zbus::Error::Failure(message)) if message == expected_error => Ok(false),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn new(connection: Connection, name: &str) -> Result<SystemdUnit<'dbus>> {
|
||||
let path = PathBuf::from("/org/freedesktop/systemd1/unit").join(escape(name));
|
||||
let path = String::from(path.to_str().ok_or(anyhow!("Unit name {name} invalid"))?);
|
||||
|
@ -358,6 +371,14 @@ pub mod test {
|
|||
async fn reload(&self) -> fdo::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_unit(&mut self, unit: &str) -> fdo::Result<OwnedObjectPath> {
|
||||
Ok(
|
||||
ObjectPath::try_from(format!("/org/freedesktop/systemd1/unit/{}", escape(unit)))
|
||||
.unwrap()
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue