bb_flasher_sd/
customization.rs

1use std::io::{Read, Seek, SeekFrom, Write};
2
3use crate::{Error, Result};
4
5#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]
6/// Post install customization options
7pub struct Customization {
8    pub hostname: Option<String>,
9    pub timezone: Option<String>,
10    pub keymap: Option<String>,
11    pub user: Option<(String, String)>,
12    pub wifi: Option<(String, String)>,
13}
14
15impl Customization {
16    pub(crate) fn customize(&self, mut dst: impl Write + Seek + Read) -> Result<()> {
17        if !self.has_customization() {
18            return Ok(());
19        }
20
21        let boot_partition = {
22            let mbr = mbrman::MBR::read_from(&mut dst, 512)
23                .map_err(|e| Error::Customization(format!("Failed to read mbr: {e}")))?;
24
25            let boot_part = mbr.get(1).ok_or(Error::Customization(
26                "Failed to get boot partition".to_string(),
27            ))?;
28            assert_eq!(boot_part.sys, 12);
29            let start_offset: u64 = (boot_part.starting_lba * mbr.sector_size).into();
30            let end_offset: u64 =
31                start_offset + u64::from(boot_part.sectors) * u64::from(mbr.sector_size);
32            let slice = fscommon::StreamSlice::new(dst, start_offset, end_offset)
33                .map_err(|_| Error::Customization("Failed to read partition".to_string()))?;
34            let boot_stream = fscommon::BufStream::new(slice);
35            fatfs::FileSystem::new(boot_stream, fatfs::FsOptions::new())
36                .map_err(|e| Error::Customization(format!("Failed to open boot partition: {e}")))?
37        };
38
39        let boot_root = boot_partition.root_dir();
40
41        let mut conf = boot_root
42            .create_file("sysconf.txt")
43            .map_err(|e| Error::Customization(format!("Failed to create sysconf.txt: {e}")))?;
44        conf.seek(SeekFrom::End(0)).map_err(|e| {
45            Error::Customization(format!("Failed to seek to end of sysconf.txt: {e}"))
46        })?;
47
48        if let Some(h) = &self.hostname {
49            sysconf_w(&mut conf, &format!("hostname={h}\n"), "hostname")?;
50        }
51
52        if let Some(tz) = &self.timezone {
53            sysconf_w(&mut conf, &format!("timezone={tz}\n"), "timezone")?;
54        }
55
56        if let Some(k) = &self.keymap {
57            sysconf_w(&mut conf, &format!("keymap={k}\n"), "keymap")?;
58        }
59
60        if let Some((u, p)) = &self.user {
61            sysconf_w(&mut conf, &format!("user_name={u}\n"), "user_name")?;
62            sysconf_w(&mut conf, &format!("user_password={p}\n"), "user_password")?;
63        }
64
65        if let Some((ssid, psk)) = &self.wifi {
66            sysconf_w(
67                &mut conf,
68                &format!("iwd_psk_file={ssid}.psk\n"),
69                "iwd_psk_file",
70            )?;
71
72            let mut wifi_file = boot_root
73                .create_file(format!("services/{ssid}.psk").as_str())
74                .map_err(|e| Error::Customization(format!("Failed to create iwd_psk_file: {e}")))?;
75
76            wifi_file
77                .write_all(
78                    format!("[Security]\nPassphrase={psk}\n\n[Settings]\nAutoConnect=true")
79                        .as_bytes(),
80                )
81                .map_err(|e| {
82                    Error::Customization(format!("Failed to write to iwd_psk_file: {e}"))
83                })?;
84        }
85
86        Ok(())
87    }
88
89    pub(crate) const fn has_customization(&self) -> bool {
90        self.hostname.is_some()
91            || self.timezone.is_some()
92            || self.keymap.is_some()
93            || self.user.is_some()
94            || self.wifi.is_some()
95    }
96}
97
98fn sysconf_w(mut sysconf: impl Write, data: &str, field: &str) -> Result<()> {
99    sysconf.write_all(data.as_bytes()).map_err(|e| {
100        Error::Customization(format!("Failed to write {field} to sysconf.txt: {e}"))
101    })?;
102    Ok(())
103}