1use crate::flash::{read_blocks, read_primary_bank, write_config};
11use crate::util::{open, warn};
12use crate::{Ctx, Progress};
13use anyhow::{Context, Result};
14
15#[derive(Debug)]
17pub struct Snapshot {
18 pub firmware: Option<String>,
19 pub config: Option<String>,
20}
21
22pub fn load_snapshot(dir: &str) -> Result<Snapshot> {
28 let firmware_path = format!("{dir}/primary-region.bin");
29 let firmware = match std::fs::metadata(&firmware_path) {
30 Ok(_) => Some(firmware_path),
31 Err(e) if e.kind() == std::io::ErrorKind::NotFound => None,
32 Err(e) => return Err(e).context(firmware_path),
33 };
34
35 let config_path = format!("{dir}/config.rcvbp");
36 let config = std::fs::metadata(&config_path)
37 .is_ok()
38 .then_some(config_path);
39
40 anyhow::ensure!(
41 firmware.is_some() || config.is_some(),
42 "{dir} holds neither primary-region.bin nor config.rcvbp"
43 );
44 Ok(Snapshot { firmware, config })
45}
46
47pub fn all(
52 ctx: &Ctx,
53 dir: &str,
54 commit: bool,
55 index: u16,
56 wait: u64,
57 p: &mut dyn Progress,
58) -> Result<()> {
59 let snap = load_snapshot(dir)?;
60 if snap.firmware.is_some() {
61 warn(p, format!(
62 "{dir}/primary-region.bin is not restored by this command; host-writable blocks go back with: \
63 rxp firmware write {dir}/primary-region.bin --backup <fresh dump> --from-block 3 --to-block 7 --commit"
64 ));
65 }
66 let Some(config) = &snap.config else {
67 anyhow::bail!("{dir} holds no config.rcvbp; nothing this command can restore");
68 };
69 if !commit {
70 p.out(&format!(
71 "dry run: {config} -> parameter block (add --commit)"
72 ));
73 return Ok(());
74 }
75
76 let backup = format!("{dir}/block07-before-restore.bin");
78 write_config(ctx, config, true, &backup, None, index, wait, p)
79}
80
81pub fn snapshot(ctx: &Ctx, dir: &str, index: u16, wait: u64, p: &mut dyn Progress) -> Result<()> {
86 std::fs::create_dir_all(dir).with_context(|| format!("create {dir}"))?;
87 let m = ctx.model()?;
88 let mut dev = open(ctx)?;
89
90 let blocks = u16::from(m.memory.bank_blocks());
91 let primary = read_primary_bank(m, &mut dev, index, wait, p)?;
92 let path = format!("{dir}/primary-region.bin");
93 std::fs::write(&path, &primary).with_context(|| format!("write {path}"))?;
94 p.out(&path);
95
96 let golden = read_blocks(&mut dev, index, m.memory.golden_block(), blocks, wait, p)?;
97 let path = format!("{dir}/golden-bank.bin");
98 std::fs::write(&path, &golden).with_context(|| format!("write {path}"))?;
99 p.out(&path);
100 Ok(())
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 fn tmpdir(name: &str) -> String {
108 let d = std::env::temp_dir().join(format!("rxp-restore-{name}"));
109 std::fs::create_dir_all(&d).unwrap();
110 d.to_str().unwrap().to_owned()
111 }
112
113 #[test]
114 fn an_empty_directory_is_rejected() {
115 let d = tmpdir("empty");
116 let _ = std::fs::remove_file(format!("{d}/primary-region.bin"));
117 let _ = std::fs::remove_file(format!("{d}/config.rcvbp"));
118 assert!(load_snapshot(&d).is_err());
119 }
120
121 #[test]
122 fn a_bank_image_is_listed() {
123 let d = tmpdir("full");
124 std::fs::write(format!("{d}/primary-region.bin"), vec![0u8; 0x1000]).unwrap();
125 let snap = load_snapshot(&d).unwrap();
126 assert!(snap.firmware.is_some());
127 assert!(snap.config.is_none());
128 std::fs::remove_file(format!("{d}/primary-region.bin")).unwrap();
129 }
130}