Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions tockloader-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ fn get_subcommands() -> Vec<Command> {
.args(get_app_args())
.args(get_channel_args())
.arg_required_else_help(false),
Command::new("uninstall")
.about("Uninstall apps")
.arg(arg!(--"name" <APPNAME>).required(false))
.args(get_app_args())
.args(get_channel_args())
.arg_required_else_help(false),
]
}

Expand Down
60 changes: 59 additions & 1 deletion tockloader-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -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::<String>("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<AppOption> = 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();
Expand Down
29 changes: 29 additions & 0 deletions tockloader-lib/src/attributes/app_attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,35 @@ pub struct AppAttributes {
pub tbf_footers: Vec<TbfFooter>,
}

/// 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)"
}
)
}
}
}

Comment on lines +25 to +52
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this structure only used in CLI? What is its purpose?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is used for displaying the list of installed apps.

/// This structure represents a footer of a Tock application. Currently, footers
/// only contain credentials, which are used to verify the integrity of the
/// application.
Expand Down
3 changes: 1 addition & 2 deletions tockloader-lib/src/command_impl/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,13 @@ impl CommandInstall for TockloaderConnection {
// obtain the binaries in a vector
let mut app_binaries: Vec<Vec<u8>> = Vec::new();

let mut address = settings.start_address;
for app in app_attributes_list.iter() {
let address = app.address;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reasoning behind this change?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is simpler and safer this way, the apps can be spaced out in memory and incrementing the address might not work every time. We already have every app's address and size.

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();
Expand Down
1 change: 1 addition & 0 deletions tockloader-lib/src/command_impl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ pub mod list;
pub mod probers;
pub mod reshuffle_apps;
pub mod serial;
pub mod uninstall;
70 changes: 70 additions & 0 deletions tockloader-lib/src/command_impl/uninstall.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
app_index: Option<usize>,
) -> Result<(), TockloaderError> {
let settings = self.get_settings();

let mut app_attributes_list: Vec<AppAttributes> = 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::<Vec<TockApp>>();

// obtain the binaries in a vector
let mut app_binaries: Vec<Vec<u8>> = 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(())
}
}
14 changes: 14 additions & 0 deletions tockloader-lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does uninstalling an with None name mean?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When calling the uninstall option we can specify the name of the app (or not), that's why the name is optional. If name is specified, index is None.

app_index: Option<usize>,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this index for?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This parameter is the selected index from the provided apps list. When --name is not used, app_name is None and index is Some(value). There is no scenario in which both have Some() or None values.

) -> Result<(), TockloaderError>;
}

#[async_trait]
pub trait CommandEraseApps {
async fn erase_apps(&mut self) -> Result<(), TockloaderError>;
Expand Down
Loading