Skip to main content

ops/
restore.rs

1//! Snapshots of the card's flash, and restoring the configuration from one.
2//!
3//! `flash snapshot` saves the primary bank and the golden bank; `flash restore`
4//! puts the `.rcvbp` configuration (and with it the screen-size record) back.
5//! Firmware is not restored here: the firmware guards blocks from the host
6//! path (`config/cards/*.toml`, `memory.guarded`), so a firmware image goes
7//! in through `firmware install` plus `firmware write` (`provision
8//! --firmware` does both).
9
10use crate::flash::{read_blocks, read_primary_bank, write_config};
11use crate::util::{open, warn};
12use crate::{Ctx, Progress};
13use anyhow::{Context, Result};
14
15/// A saved copy of everything we know how to put back.
16#[derive(Debug)]
17pub struct Snapshot {
18    pub firmware: Option<String>,
19    pub config: Option<String>,
20}
21
22/// Load whatever a snapshot directory holds. The bank image's size is
23/// checked by `flash_firmware` when it is used as the recovery copy.
24///
25/// # Errors
26/// Fails if a file is present but unreadable, or neither is there.
27pub 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
47/// Put the configuration and screen record back from a snapshot.
48///
49/// # Errors
50/// Fails if the snapshot holds no `config.rcvbp` or the write does not verify.
51pub 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    // write_config reads the block off the card and restores the screen record.
77    let backup = format!("{dir}/block07-before-restore.bin");
78    write_config(ctx, config, true, &backup, None, index, wait, p)
79}
80
81/// Capture everything we know how to restore into a directory.
82///
83/// # Errors
84/// Fails if the card does not answer or the files cannot be written.
85pub 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}