From a504c0342cc723e33f345714780754af5a5c77bc Mon Sep 17 00:00:00 2001 From: Devdutt Shenoi Date: Mon, 29 Jan 2024 20:25:10 +0530 Subject: [PATCH] feat: preconditions check of disk space --- uplink/src/base/mod.rs | 7 +++ uplink/src/collector/mod.rs | 1 + uplink/src/collector/preconditions.rs | 84 +++++++++++++++++++++++++++ uplink/src/lib.rs | 10 +++- 4 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 uplink/src/collector/preconditions.rs diff --git a/uplink/src/base/mod.rs b/uplink/src/base/mod.rs index 73b9422f..8d18d3e3 100644 --- a/uplink/src/base/mod.rs +++ b/uplink/src/base/mod.rs @@ -248,6 +248,12 @@ impl Default for DeviceShadowConfig { } } +#[derive(Debug, Clone, Deserialize)] +pub struct PreconditionCheckerConfig { + pub path: PathBuf, + pub actions: Vec, +} + #[derive(Debug, Clone, Deserialize, Default)] pub struct Config { pub project_id: String, @@ -289,6 +295,7 @@ pub struct Config { pub logging: Option, #[cfg(target_os = "android")] pub logging: Option, + pub precondition_checks: Option, } /// Send control messages to the various components in uplink. Currently this is diff --git a/uplink/src/collector/mod.rs b/uplink/src/collector/mod.rs index 3e2742bd..903663c3 100644 --- a/uplink/src/collector/mod.rs +++ b/uplink/src/collector/mod.rs @@ -5,6 +5,7 @@ pub mod installer; pub mod journalctl; #[cfg(target_os = "android")] pub mod logcat; +pub mod preconditions; pub mod process; pub mod script_runner; pub mod simulator; diff --git a/uplink/src/collector/preconditions.rs b/uplink/src/collector/preconditions.rs new file mode 100644 index 00000000..8f21e85c --- /dev/null +++ b/uplink/src/collector/preconditions.rs @@ -0,0 +1,84 @@ +use std::{fs::metadata, os::unix::fs::MetadataExt, sync::Arc}; + +use flume::Receiver; +use human_bytes::human_bytes; +use log::debug; +use serde::Deserialize; + +use crate::{base::bridge::BridgeTx, Action, ActionResponse, Config}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("File io Error: {0}")] + Io(#[from] std::io::Error), + #[error("Disk space is insufficient: {0}")] + InsufficientDisk(String), +} + +#[derive(Deserialize, Clone, PartialEq, Eq, Debug)] +pub struct Preconditions { + #[serde(alias = "content-length")] + content_length: usize, + #[serde(alias = "uncompressed-length")] + uncompressed_length: usize, +} + +pub struct PreconditionChecker { + config: Arc, + actions_rx: Receiver, + bridge_tx: BridgeTx, +} + +impl PreconditionChecker { + pub fn new(config: Arc, actions_rx: Receiver, bridge_tx: BridgeTx) -> Self { + Self { config, actions_rx, bridge_tx } + } + + #[tokio::main] + pub async fn start(self) { + while let Ok(action) = self.actions_rx.recv() { + let preconditions: Preconditions = match serde_json::from_str(&action.payload) { + Ok(c) => c, + Err(e) => { + let response = ActionResponse::failure(&action.action_id, e.to_string()); + self.bridge_tx.send_action_response(response).await; + continue; + } + }; + + if let Err(e) = self.check_disk_size(preconditions) { + let response = ActionResponse::failure(&action.action_id, e.to_string()); + self.bridge_tx.send_action_response(response).await; + continue; + } + + let response = ActionResponse::progress(&action.action_id, "Checked OK", 100); + self.bridge_tx.send_action_response(response).await; + } + } + + // Fails if there isn't enough space to download and/or install update + // NOTE: both download and installation could happen in the same partition + fn check_disk_size(&self, preconditions: Preconditions) -> Result<(), Error> { + let disk_free_space = + fs2::free_space(&self.config.precondition_checks.as_ref().unwrap().path)? as usize; + let mut required_free_space = preconditions.uncompressed_length; + + // Check if download and installation paths are on the same partition, if yes add download file size to required + if metadata(&self.config.downloader.path)?.dev() + == metadata(&self.config.precondition_checks.as_ref().unwrap().path)?.dev() + { + required_free_space += preconditions.content_length; + } + + let req_size = human_bytes(required_free_space as f64); + let free_size = human_bytes(disk_free_space as f64); + debug!("The installation requires {req_size}; Disk free space is {free_size}"); + + if required_free_space > disk_free_space { + return Err(Error::InsufficientDisk(free_size)); + } + + Ok(()) + } +} diff --git a/uplink/src/lib.rs b/uplink/src/lib.rs index a72727a8..e6f62a03 100644 --- a/uplink/src/lib.rs +++ b/uplink/src/lib.rs @@ -57,6 +57,7 @@ use collector::installer::OTAInstaller; use collector::journalctl::JournalCtl; #[cfg(target_os = "android")] use collector::logcat::Logcat; +use collector::preconditions::PreconditionChecker; use collector::process::ProcessHandler; use collector::script_runner::ScriptRunner; use collector::systemstats::StatCollector; @@ -502,7 +503,7 @@ impl Uplink { if !self.config.script_runner.is_empty() { let (actions_tx, actions_rx) = bounded(1); bridge.register_action_routes(&self.config.script_runner, actions_tx)?; - let script_runner = ScriptRunner::new(actions_rx, bridge_tx); + let script_runner = ScriptRunner::new(actions_rx, bridge_tx.clone()); spawn_named_thread("Script Runner", || { if let Err(e) = script_runner.start() { error!("Script runner stopped!! Error = {:?}", e); @@ -510,6 +511,13 @@ impl Uplink { }); } + if let Some(checker_config) = &self.config.precondition_checks { + let (actions_tx, actions_rx) = bounded(1); + let checker = PreconditionChecker::new(self.config.clone(), actions_rx, bridge_tx); + bridge.register_action_routes(&checker_config.actions, actions_tx)?; + spawn_named_thread("Logger", || checker.start()); + } + Ok(()) }