1use anyhow::Result;
9use fuchsia_runtime::UtcTimeline;
10use log::{debug, error};
11use serde::{Deserialize, Serialize};
12use std::fs;
13use std::path::Path;
14
15const PERSISTENT_STATE_PATH: &'static str = "/data/persistent_state.json";
17
18const REBOOT_COUNT: u8 = 1;
20
21#[derive(Serialize, Deserialize, Debug, Default, Clone)]
26pub struct State {
27 num_reboots_until_rtc_write_allowed: u8,
30
31 #[serde(default)]
38 reference_boot_instant_ns: i64,
39
40 #[serde(default)]
43 reference_utc_instant_ns: i64,
44}
45
46impl State {
47 pub fn new(update_rtc: bool) -> Self {
48 let mut value = Self { ..Default::default() };
49 value.set_may_update_rtc(update_rtc);
50 value
51 }
52
53 pub fn may_update_rtc(&self) -> bool {
55 self.num_reboots_until_rtc_write_allowed == 0
56 }
57
58 pub fn set_may_update_rtc(&mut self, update_rtc: bool) {
59 self.num_reboots_until_rtc_write_allowed = match update_rtc {
60 true => 0,
61 false => REBOOT_COUNT,
64 };
65 }
66
67 pub fn get_rtc_reference(&self) -> (zx::BootInstant, zx::Instant<UtcTimeline>) {
69 let reference_boot = zx::BootInstant::from_nanos(self.reference_boot_instant_ns);
70 let reference_utc = zx::Instant::from_nanos(self.reference_utc_instant_ns);
71 (reference_boot, reference_utc)
72 }
73
74 pub fn set_rtc_reference(
84 &mut self,
85 boot_reference: zx::BootInstant,
86 utc_reference: zx::Instant<UtcTimeline>,
87 ) {
88 self.reference_boot_instant_ns = boot_reference.into_nanos();
89 self.reference_utc_instant_ns = utc_reference.into_nanos();
90 }
91
92 fn spend_one_reboot_count(&mut self) {
94 self.num_reboots_until_rtc_write_allowed =
95 self.num_reboots_until_rtc_write_allowed.saturating_sub(1);
96 }
97
98 pub fn read_and_update() -> Result<Self> {
100 Self::read_and_update_internal(PERSISTENT_STATE_PATH)
101 }
102
103 pub fn read_and_update_internal<P: AsRef<Path>>(path: P) -> Result<Self> {
104 let path = path.as_ref();
105 let maybe_state_str: String = fs::read_to_string(path).map_err(|e| {
106 debug!("while reading: {:?}", e);
107 e
108 })?;
109 let state: State = serde_json::from_str(&maybe_state_str).map_err(|e| {
110 debug!("while deserializing: {:?}", e);
111 e
112 })?;
113 log::info!("read persistent RTC state: {state:?}");
115 let mut next = state.clone();
116 next.spend_one_reboot_count();
117 Self::write_internal(path, &next)?;
118 Ok(state)
119 }
120
121 pub fn write(state: &Self) -> Result<()> {
123 Self::write_internal(PERSISTENT_STATE_PATH, state)
124 }
125
126 pub fn write_internal<P: AsRef<Path>>(path: P, state: &Self) -> Result<()> {
127 let path = path.as_ref();
128 log::info!("writing persistent RTC state: {:?} to {:?}", state, path);
130 let state_str = serde_json::to_string(state).map_err(|e| {
131 error!("while serializing state: {:?}", e);
132 e
133 })?;
134 fs::write(path, state_str).map_err(|e| {
135 error!("while writing persistent state: {:?}: {:?}", path, e);
136 e.into()
137 })
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn test_roundtrip() {
147 let d = tempfile::TempDir::new().expect("tempdir created");
148 let p = d.path().join("file.json");
149 let s = State::new(false);
150 State::write_internal(&p, &s).unwrap();
151
152 assert!(!State::read_and_update_internal(&p).unwrap().may_update_rtc());
154 assert!(State::read_and_update_internal(&p).unwrap().may_update_rtc());
156 }
157}