diff --git a/tockloader-cli/src/cli.rs b/tockloader-cli/src/cli.rs index 529a4bf..611f0c3 100644 --- a/tockloader-cli/src/cli.rs +++ b/tockloader-cli/src/cli.rs @@ -58,6 +58,12 @@ fn get_subcommands() -> Vec { .args(get_app_args()) .args(get_channel_args()) .arg_required_else_help(false), + Command::new("uninstall") + .about("Uninstall apps") + .arg(arg!(--"name" ).required(false)) + .args(get_app_args()) + .args(get_channel_args()) + .arg_required_else_help(false), ] } diff --git a/tockloader-cli/src/main.rs b/tockloader-cli/src/main.rs index eaee432..5ea8e3e 100644 --- a/tockloader-cli/src/main.rs +++ b/tockloader-cli/src/main.rs @@ -10,6 +10,7 @@ use anyhow::{Context, Result}; use clap::ArgMatches; use cli::make_cli; use known_boards::KnownBoardNames; +use tockloader_lib::attributes::app_attributes::AppOption; use tockloader_lib::board_settings::BoardSettings; use tockloader_lib::connection::{ Connection, ProbeRSConnection, ProbeTargetInfo, SerialConnection, SerialTargetInfo, @@ -19,7 +20,7 @@ use tockloader_lib::known_boards::KnownBoard; use tockloader_lib::tabs::tab::Tab; use tockloader_lib::{ list_debug_probes, list_serial_ports, CommandEraseApps, CommandInfo, CommandInstall, - CommandList, + CommandList, CommandUninstall, }; fn get_serial_target_info(user_options: &ArgMatches) -> SerialTargetInfo { @@ -240,6 +241,63 @@ async fn main() -> Result<()> { conn.erase_apps().await.context("Failed to erase apps.")?; } + Some(("uninstall", sub_matches)) => { + cli::validate(&mut cmd, sub_matches); + let mut conn = open_connection(sub_matches).await?; + let installed_apps = conn.list().await.context("Failed to list apps.")?; + let app_name = sub_matches.get_one::("name").map(String::as_str); + + if installed_apps.is_empty() { + println!("No apps installed"); + return Ok(()); + } + match app_name { + Some(app_name) => { + installed_apps + .iter() + .find(|iter| iter.tbf_header.get_package_name().unwrap_or("") == app_name) + .expect("Specified app is not installed"); + conn.uninstall_app(Some(app_name.to_string()), None) + .await + .context("Failed to uninstall app.")?; + } + None => loop { + let mut options: Vec = installed_apps + .iter() + .enumerate() + .map(|(i, app)| AppOption { index: i + 1, app }) + .collect(); + + // Delete all option + options.insert( + 0, + AppOption { + index: 0, + app: &installed_apps[0], + }, + ); + let selected = + inquire::Select::new("Which app do you want to uninstall?", options) + .prompt() + .context("No apps installed") + .unwrap(); + + if inquire::Select::new( + format!("You chose {selected}",).as_str(), + ["Cancel", "Confirm"].to_vec(), + ) + .prompt() + .unwrap() + == "Confirm" + { + conn.uninstall_app(None, Some(selected.index)) + .await + .context("Failed to uninstall app")?; + break; + } + }, + } + } _ => { println!("Could not run the provided subcommand."); _ = make_cli().print_help(); diff --git a/tockloader-lib/src/attributes/app_attributes.rs b/tockloader-lib/src/attributes/app_attributes.rs index 31686b3..7abbe0f 100644 --- a/tockloader-lib/src/attributes/app_attributes.rs +++ b/tockloader-lib/src/attributes/app_attributes.rs @@ -21,6 +21,35 @@ pub struct AppAttributes { pub tbf_footers: Vec, } +/// This structure is used for displaying installed apps in the CLI +#[derive(Debug)] +pub struct AppOption<'a> { + pub index: usize, + pub app: &'a AppAttributes, +} + +impl<'a> std::fmt::Display for AppOption<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.index == 0 { + write!(f, "Delete all") + } else { + write!( + f, + "{}. {} - start: {:#x}, size: {}, type: {}", + self.index, + self.app.tbf_header.get_package_name().unwrap_or(""), + self.app.address, + self.app.tbf_header.total_size(), + if self.app.tbf_header.get_fixed_address_flash().is_none() { + "C (flexible)" + } else { + "Rust (fixed)" + } + ) + } + } +} + /// This structure represents a footer of a Tock application. Currently, footers /// only contain credentials, which are used to verify the integrity of the /// application. diff --git a/tockloader-lib/src/command_impl/install.rs b/tockloader-lib/src/command_impl/install.rs index d3af197..1be7dc6 100644 --- a/tockloader-lib/src/command_impl/install.rs +++ b/tockloader-lib/src/command_impl/install.rs @@ -21,14 +21,13 @@ impl CommandInstall for TockloaderConnection { // obtain the binaries in a vector let mut app_binaries: Vec> = Vec::new(); - let mut address = settings.start_address; for app in app_attributes_list.iter() { + let address = app.address; app_binaries.push( self.read(address, app.tbf_header.total_size() as usize) .await .unwrap(), ); - address += app.tbf_header.total_size() as u64; } let app = TockApp::from_tab(&tab, &settings).unwrap(); diff --git a/tockloader-lib/src/command_impl/mod.rs b/tockloader-lib/src/command_impl/mod.rs index 22d488e..5aaf7b8 100644 --- a/tockloader-lib/src/command_impl/mod.rs +++ b/tockloader-lib/src/command_impl/mod.rs @@ -6,3 +6,4 @@ pub mod list; pub mod probers; pub mod reshuffle_apps; pub mod serial; +pub mod uninstall; diff --git a/tockloader-lib/src/command_impl/uninstall.rs b/tockloader-lib/src/command_impl/uninstall.rs new file mode 100644 index 0000000..97e75e7 --- /dev/null +++ b/tockloader-lib/src/command_impl/uninstall.rs @@ -0,0 +1,70 @@ +use async_trait::async_trait; + +use crate::{ + attributes::app_attributes::AppAttributes, + command_impl::reshuffle_apps::{create_pkt, reshuffle_apps, TockApp}, + connection::{Connection, TockloaderConnection}, + errors::{InternalError, TockloaderError}, + CommandEraseApps, CommandList, CommandUninstall, IO, +}; + +#[async_trait] +impl CommandUninstall for TockloaderConnection { + async fn uninstall_app( + &mut self, + app_name: Option, + app_index: Option, + ) -> Result<(), TockloaderError> { + let settings = self.get_settings(); + + let mut app_attributes_list: Vec = self.list().await?; + + // Remove all apps with given name + if let Some(name) = app_name { + let _ = app_attributes_list + .retain(|app| app.tbf_header.get_package_name().unwrap_or("") != name); + } else if let Some(index) = app_index { + // Delete all apps, call erase + if index == 0 { + self.erase_apps().await?; + return Ok(()); + } + // Remove the selected index + app_attributes_list.remove(index - 1); + } else { + panic!("Called uninstall with wrong parameters!"); + } + + let tock_app_list = app_attributes_list + .iter() + .map(TockApp::from_app_attributes) + .collect::>(); + + // obtain the binaries in a vector + let mut app_binaries: Vec> = Vec::new(); + + for app in app_attributes_list.iter() { + let address = app.address; + app_binaries.push( + self.read(address, app.tbf_header.total_size() as usize) + .await + .unwrap(), + ); + } + + let configuration = + reshuffle_apps(&settings, tock_app_list).ok_or(TockloaderError::Internal( + InternalError::MisconfiguredBoardSettings("Can't fit new app".to_string()), + ))?; + + // create the pkt, this contains all the binaries in a vec + let mut pkt = create_pkt(configuration, app_binaries, None, &settings); + + pkt.append(&mut [0u8; 512].to_vec()); + + log::debug!("pkt len {}", pkt.len()); + // write the pkt + let _ = self.write(settings.start_address, &pkt).await?; + Ok(()) + } +} diff --git a/tockloader-lib/src/lib.rs b/tockloader-lib/src/lib.rs index fd023ee..d9f5c06 100644 --- a/tockloader-lib/src/lib.rs +++ b/tockloader-lib/src/lib.rs @@ -50,6 +50,20 @@ pub trait CommandInstall { async fn install_app(&mut self, tab_file: Tab) -> Result<(), TockloaderError>; } +#[async_trait] +pub trait CommandUninstall { + /// This function is used for uninstalling apps + /// - app_name is Some(value) if --name is used, otherwise it is None + /// - app_index is Some(value) if the app is chosen from the list, None otherwise + /// + /// There is no scenario in which both are Some(value) or None + async fn uninstall_app( + &mut self, + app_name: Option, + app_index: Option, + ) -> Result<(), TockloaderError>; +} + #[async_trait] pub trait CommandEraseApps { async fn erase_apps(&mut self) -> Result<(), TockloaderError>;