Skip to content

Commit

Permalink
introduce mion dump-eeprom & dump-memory (#15)
Browse files Browse the repository at this point in the history
* introduce mion dump-eeprom & dump-memory

introduce two new subcommands we found while poking around in telnet
memory dumps (telnet  has the same username/password as the http
interface). these cgi pages allow us to figure out the memory, and
eeprom of the MION itself, which should be helpful for certain tasks.

* retry dumping memory

* fix clap breakage

* allow retry on body read too

* write as needed + resumption

fixes #16

this writes the memory dump file which takes forever (4Gbs at 512
bytes per request, which can sometimes fail and need to be retried)
as the requests come in rather than actually waiting for it to all be
buffered in memory first. this also lowers the total amount of memory
required to do so.

also allow resuming a stopped/failed memory dump.

* mention how long a mion memory dump takes

* concurrently fetch pages
  • Loading branch information
Mythra authored Jun 30, 2024
1 parent 2b80416 commit 4d338be
Show file tree
Hide file tree
Showing 11 changed files with 924 additions and 3 deletions.
76 changes: 76 additions & 0 deletions cmd/bridgectl/src/commands/mion/dump_eeprom.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//! Dump the EEPROM for a running CAT-DEV.
use crate::{
commands::argv_helpers::get_targeted_bridge_ip,
exit_codes::{DUMP_EEPROM_FAILURE, FAILED_TO_WRITE_TO_DISK},
SHOULD_LOG_JSON,
};
use cat_dev::mion::cgis::dump_eeprom;
use miette::miette;
use std::path::PathBuf;
use tokio::fs::write;
use tracing::{error, info};

/// Actual command handler for the `mion dump-eeprom` command.
pub async fn handle_dump_eeprom(output_path: Option<PathBuf>) {
let bridge_ip = get_targeted_bridge_ip().await;
if SHOULD_LOG_JSON() {
info!(
id = "bridgectl::mion::dump_eeprom::start",
%bridge_ip,
"Dumping MION EEPROM...",
);
} else {
info!(
%bridge_ip,
"Dumping MION EEPROM...",
);
}

match dump_eeprom(bridge_ip).await {
Ok(memory) => {
let err = if let Some(path) = output_path.as_ref() {
write(path, memory).await.err()
} else {
write("eeprom-memory.bin", memory).await.err()
};

if let Some(cause) = err {
if SHOULD_LOG_JSON() {
error!(
id = "bridgectl::mion::dump_eeprom::write_failure",
%bridge_ip,
?cause,
path = output_path.map_or("eeprom-memory.bin".to_owned(), |p| p.to_string_lossy().to_string()),
"Failed to write eeprom memory to disk!",
);
} else {
error!(
"\n{:?}",
miette!("Could not write successfully dumped MION's EEPROM Memory")
.wrap_err(cause),
);
}

std::process::exit(FAILED_TO_WRITE_TO_DISK);
}
}
Err(cause) => {
if SHOULD_LOG_JSON() {
error!(
id = "bridgectl::mion::dump_eeprom::failure",
%bridge_ip,
?cause,
"Failure to dump MION's EEPROM memory",
);
} else {
error!(
"\n{:?}",
miette!("Could not dump MION's EEPROM Memory.").wrap_err(cause),
);
}

std::process::exit(DUMP_EEPROM_FAILURE);
}
}
}
102 changes: 102 additions & 0 deletions cmd/bridgectl/src/commands/mion/dump_memory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//! Dump the memory for a running CAT-DEV.
use crate::{
commands::argv_helpers::get_targeted_bridge_ip,
exit_codes::{DUMP_MEMORY_FAILURE, FAILED_TO_WRITE_TO_DISK},
SHOULD_LOG_JSON,
};
use cat_dev::mion::cgis::dump_memory_with_writer;
use miette::miette;
use std::{
fs::OpenOptions,
io::{BufWriter, Write},
path::PathBuf,
};
use tracing::{error, info};

/// Actual command handler for the `mion dump-memory` command.
pub async fn handle_dump_memory(output_path: Option<PathBuf>, resume_at: Option<usize>) {
let bridge_ip = get_targeted_bridge_ip().await;
if SHOULD_LOG_JSON() {
info!(
id = "bridgectl::mion::dump_memory::start",
%bridge_ip,
"Dumping MION Memory, this will take a LONG time...",
);
} else {
info!(
%bridge_ip,
"Dumping MION Memory, this will take a LONG time...",
);
}

let path = output_path.unwrap_or(PathBuf::from("88F6281-memory.bin"));
let file_writer = match OpenOptions::new()
.write(true)
.append(resume_at.is_some())
.create(true)
.open(&path)
{
Ok(val) => val,
Err(cause) => {
if SHOULD_LOG_JSON() {
error!(
id = "bridgectl::mion::dump_memory::write_failure",
%bridge_ip,
?cause,
path = %path.to_string_lossy(),
"Failed to write 88F6281 memory to disk!",
);
} else {
error!(
"\n{:?}",
miette!("Could not write successfully dumped MION's 88F6281 Memory")
.wrap_err(cause),
);
}

std::process::exit(FAILED_TO_WRITE_TO_DISK);
}
};
let mut buff_writer = BufWriter::new(file_writer);

if let Err(cause) = dump_memory_with_writer(bridge_ip, resume_at, |bytes: Vec<u8>| {
if let Err(cause) = buff_writer.write(&bytes) {
if SHOULD_LOG_JSON() {
error!(
id = "bridgectl::mion::dump_memory::write_failure",
%bridge_ip,
?cause,
path = %path.to_string_lossy(),
"Failed to write 88F6281 memory to disk!",
);
} else {
error!(
"\n{:?}",
miette!("Could not write successfully dumped MION's 88F6281 Memory")
.wrap_err(cause),
);
}

std::process::exit(FAILED_TO_WRITE_TO_DISK);
}
})
.await
{
if SHOULD_LOG_JSON() {
error!(
id = "bridgectl::mion::dump_memory::failure",
%bridge_ip,
?cause,
"Failure to dump MION's memory",
);
} else {
error!(
"\n{:?}",
miette!("Could not dump MION's Memory.").wrap_err(cause),
);
}

std::process::exit(DUMP_MEMORY_FAILURE);
}
}
7 changes: 7 additions & 0 deletions cmd/bridgectl/src/commands/mion/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//! Subdirectory containing MION subcommands.
mod dump_eeprom;
mod dump_memory;

pub use dump_eeprom::*;
pub use dump_memory::*;
2 changes: 2 additions & 0 deletions cmd/bridgectl/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ pub use remove::*;
pub use set_default::*;
pub use set_parameters::*;
pub use tail::*;

pub mod mion;
5 changes: 5 additions & 0 deletions cmd/bridgectl/src/exit_codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pub const NOT_YET_IMPLEMENTED: i32 = 1;
pub const LOGGING_HANDLER_INSTALL_FAILURE: i32 = 2;
pub const SHOULD_NEVER_HAPPEN_FAILURE: i32 = 3;
pub const FAILED_TO_WRITE_TO_DISK: i32 = 4;

pub const ARGV_PARSE_FAILURE: i32 = 10;
pub const ARGV_NO_COMMAND_SPECIFIED: i32 = 11;
Expand Down Expand Up @@ -49,3 +50,7 @@ pub const SET_PARAMS_FAILED_TO_SET_PARAMS: i32 = 78;

pub const TAIL_NEEDS_SERIAL_PORT: i32 = 80;
pub const TAIL_COULD_NOT_SPAWN: i32 = 81;

pub const DUMP_EEPROM_FAILURE: i32 = 90;

pub const DUMP_MEMORY_FAILURE: i32 = 100;
103 changes: 101 additions & 2 deletions cmd/bridgectl/src/knobs/cli.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Defines the command line interface a.k.a. all the arguments & flags.
use clap::{Args, Parser};
use clap::{Args, Parser, Subcommand};
use mac_address::MacAddress;
use std::{
fmt::{Display, Formatter, Result as FmtResult},
Expand Down Expand Up @@ -58,7 +58,7 @@ pub enum Subcommands {
// Add only command arguments.
// ///////////////////////////////////////////////////
#[arg(
long = "default",
long = "set-default",
help = "Makes this bridge the default.",
long_help = "Sets the bridge as the default bridge to use when opening new shells, with this you don't need to separately call `set-default`."
)]
Expand Down Expand Up @@ -261,6 +261,8 @@ pub enum Subcommands {
visible_aliases = ["ls-serial-ports", "lssp", "list_serial_ports", "ls_serial_ports"],
)]
ListSerialPorts {},
#[clap(subcommand)]
Mion(MionSubcommands),
/// Remove a bridge from your local configuration file.
#[command(name = "remove", visible_alias = "rm")]
Remove {
Expand Down Expand Up @@ -424,6 +426,7 @@ impl Subcommands {
|| name == "ls_serial_ports"
|| name == "lssp"
}
Self::Mion(_) => name == "mion",
Self::Remove {
bridge_config_flags,
scan_flags,
Expand Down Expand Up @@ -452,6 +455,102 @@ impl Subcommands {
}
}

#[derive(Subcommand, Debug)]
pub enum MionSubcommands {
/// Dump the EEPROM on a mion.
#[command(name = "dump-eeprom", alias = "dump_eeprom")]
DumpEeprom {
// ///////////////////////////////////////////////////
// Shared Flags for targeting a single bridge.
// ///////////////////////////////////////////////////
#[command(flatten)]
bridge_config_flags: BridgeConfigurationFlags,
#[command(flatten)]
scan_flags: BridgeScanFlags,
#[command(flatten)]
target_flags: TargetBridgeFlags,
#[arg(
index = 1,
help = "Search for a bridge with a particular name/ip/mac address.",
long_help = "If you don't want to specify what type you're searching for with `--ip`, `--mac-address`, or `--name` you can just pass in a positional argument where we can guess"
)]
bridge_name_positional: Option<String>,
// ///////////////////////////////////////////////////
// Get only command flags.
// ///////////////////////////////////////////////////
#[arg(
short = 'p',
long = "output-path",
alias = "output_path",
help = "The path to output the dumped EEPROM.",
long_help = "The path to the file to write the EEPMROM dump."
)]
output_path: Option<PathBuf>,
},
/// Dump the Memory on a mion.
#[command(name = "dump-memory", alias = "dump_memory")]
DumpMemory {
// ///////////////////////////////////////////////////
// Shared Flags for targeting a single bridge.
// ///////////////////////////////////////////////////
#[command(flatten)]
bridge_config_flags: BridgeConfigurationFlags,
#[command(flatten)]
scan_flags: BridgeScanFlags,
#[command(flatten)]
target_flags: TargetBridgeFlags,
#[arg(
index = 1,
help = "Search for a bridge with a particular name/ip/mac address.",
long_help = "If you don't want to specify what type you're searching for with `--ip`, `--mac-address`, or `--name` you can just pass in a positional argument where we can guess"
)]
bridge_name_positional: Option<String>,
// ///////////////////////////////////////////////////
// Get only command flags.
// ///////////////////////////////////////////////////
#[arg(
short = 'p',
long = "output-path",
alias = "output_path",
help = "The path to output the dumped EEPROM.",
long_help = "The path to the file to write the EEPMROM dump."
)]
output_path: Option<PathBuf>,
#[arg(
short = 'r',
long = "resume-at",
alias = "resume_at",
help = "The byte offset to resume reading at.",
long_help = "The byte offset on the page to resume reading at, use debug logs to see where you are if you intend to resume."
)]
resume_at: Option<usize>,
},
}
impl MionSubcommands {
/// If this subcommand matches a particular name.
#[allow(unused)]
#[must_use]
pub fn name_matches(&self, name: &str) -> bool {
match self {
Self::DumpEeprom {
bridge_config_flags,
scan_flags,
target_flags,
bridge_name_positional,
output_path,
} => name == "dump-eeprom" || name == "dump_eeprom",
Self::DumpMemory {
bridge_config_flags,
scan_flags,
target_flags,
bridge_name_positional,
output_path,
resume_at,
} => name == "dump-memory" || name == "dump_memory",
}
}
}

/// Arguments specific to targeting just a single bridge to lookup.
#[derive(Args, Debug)]
pub struct TargetBridgeFlags {
Expand Down
33 changes: 32 additions & 1 deletion cmd/bridgectl/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ use crate::{
handle_add_or_update, handle_boot, handle_dump_parameters, handle_get,
handle_get_parameters, handle_help, handle_list, handle_list_serial_ports,
handle_remove_bridge, handle_set_default_bridge, handle_set_parameters, handle_tail,
mion::handle_dump_eeprom as mion_handle_dump_eeprom,
mion::handle_dump_memory as mion_handle_dump_memory,
},
exit_codes::{
ARGV_NO_COMMAND_SPECIFIED, ARGV_PARSE_FAILURE, LOGGING_HANDLER_INSTALL_FAILURE,
SHOULD_NEVER_HAPPEN_FAILURE,
},
knobs::{
cli::{CliArguments, Subcommands},
cli::{CliArguments, MionSubcommands, Subcommands},
env::USE_JSON_OUTPUT,
},
};
Expand Down Expand Up @@ -185,6 +187,35 @@ async fn main() {
Subcommands::ListSerialPorts {} => {
handle_list_serial_ports();
}
Subcommands::Mion(mion_subcommands) => match mion_subcommands {
MionSubcommands::DumpEeprom {
bridge_config_flags,
scan_flags,
target_flags,
bridge_name_positional,
output_path,
} => {
initialize_host_bridge(bridge_config_flags).await;
initialize_scan_flags(scan_flags).await;
_ = target_bridge(target_flags, bridge_name_positional.as_deref(), false).await;

mion_handle_dump_eeprom(output_path).await;
}
MionSubcommands::DumpMemory {
bridge_config_flags,
scan_flags,
target_flags,
bridge_name_positional,
output_path,
resume_at,
} => {
initialize_host_bridge(bridge_config_flags).await;
initialize_scan_flags(scan_flags).await;
_ = target_bridge(target_flags, bridge_name_positional.as_deref(), false).await;

mion_handle_dump_memory(output_path, resume_at).await;
}
},
Subcommands::Remove {
bridge_config_flags,
scan_flags,
Expand Down
Loading

0 comments on commit 4d338be

Please sign in to comment.