persistence/
file_handler.rs1use anyhow::{Context, Error, bail};
6use log::info;
7use persistence_config::Config;
8use serde::de::{self, Visitor};
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10use std::fs::{self, File};
11use std::io::ErrorKind;
12
13use crate::fetcher::PersistenceData;
14
15const CURRENT_DATA: &str = "/cache/current.json";
16const PREVIOUS_DATA: &str = "/cache/previous.json";
17const TEMP_DATA: &str = "/cache/current.json.tmp";
18
19#[derive(Clone, Debug, Serialize, Deserialize)]
20pub(crate) struct Timestamps {
21 #[serde(serialize_with = "serialize_boot_time", deserialize_with = "deserialize_boot_time")]
25 pub last_sample_boot: zx::BootInstant,
26 #[serde(serialize_with = "serialize_utc_time", deserialize_with = "deserialize_utc_time")]
27 pub last_sample_utc: fuchsia_runtime::UtcInstant,
28}
29
30impl Timestamps {
31 pub fn merge(&mut self, other: Self) {
32 if self.last_sample_boot < other.last_sample_boot {
33 self.last_sample_boot = other.last_sample_boot;
34 }
35 if self.last_sample_utc < other.last_sample_utc {
36 self.last_sample_utc = other.last_sample_utc;
37 }
38 }
39}
40
41fn serialize_boot_time<S: Serializer>(
42 time: &zx::BootInstant,
43 serializer: S,
44) -> Result<S::Ok, S::Error> {
45 serializer.serialize_i64(time.into_nanos())
46}
47
48fn deserialize_boot_time<'de, D: Deserializer<'de>>(
49 deserializer: D,
50) -> Result<zx::BootInstant, D::Error> {
51 deserializer.deserialize_i64(TimeNanos).map(zx::BootInstant::from_nanos)
52}
53
54fn serialize_utc_time<S: Serializer>(
55 time: &fuchsia_runtime::UtcInstant,
56 serializer: S,
57) -> Result<S::Ok, S::Error> {
58 serializer.serialize_i64(time.into_nanos())
59}
60
61fn deserialize_utc_time<'de, D: Deserializer<'de>>(
62 deserializer: D,
63) -> Result<fuchsia_runtime::UtcInstant, D::Error> {
64 deserializer.deserialize_i64(TimeNanos).map(fuchsia_runtime::UtcInstant::from_nanos)
65}
66
67struct TimeNanos;
69
70impl<'de> Visitor<'de> for TimeNanos {
71 type Value = i64;
72
73 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 formatter
75 .write_str("a 64-bit integer representing time in nanoseconds on an arbitrary timeline")
76 }
77
78 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
79 where
80 E: de::Error,
81 {
82 i64::try_from(v).map_err(de::Error::custom)
83 }
84}
85
86pub async fn forget_old_data(config: &Config) -> Result<(), Error> {
94 info!(
95 "Forgetting persisted inspect data from two boots ago, except for tags with persist_across_boot enabled"
96 );
97
98 match fs::remove_file(PREVIOUS_DATA) {
99 Err(e) if e.kind() == ErrorKind::NotFound => {}
101 Err(e) => {
103 bail!("Failed to wipe previous data: {e}");
104 }
105 _ => {}
106 }
107
108 if let Err(e) = fs::rename(CURRENT_DATA, PREVIOUS_DATA) {
109 if e.kind() == ErrorKind::NotFound {
110 return Ok(());
111 }
112 bail!("Failed to swap current data with previous: {e}");
113 }
114
115 let mut data = match previous_data().await {
116 Ok(Some(data)) => data,
117 Ok(None) => bail!("Data not found; filesystem inconsistency"),
118 Err(e) => {
119 log::error!("Previous data corrupted, starting fresh: {e:?}");
120 PersistenceData::default()
121 }
122 };
123
124 remove_tags_without_persist_across_boot(&mut data, config)
125 .context("Failed to remove tags without persist_across_boot")?;
126
127 let file = File::create(TEMP_DATA).context("Failed to open temp data")?;
128 serde_json::to_writer(file, &data).context("Failed to write temp data")?;
129 std::fs::rename(TEMP_DATA, CURRENT_DATA).context("Failed to rename temp data to current")
130}
131
132fn remove_tags_without_persist_across_boot(
133 data: &mut PersistenceData,
134 config: &Config,
135) -> Result<(), Error> {
136 let mut copied_count = 0;
137
138 for (service, service_data) in data.iter_mut() {
139 let tags_to_remove = config
140 .get(&service.clone())
141 .with_context(|| format!("Failed to find service \"{service}\" in config"))?
142 .iter()
143 .filter(|(_, config)| !config.persist_across_boot)
144 .map(|(tag, _)| tag);
145
146 for tag in tags_to_remove {
147 service_data.remove(tag);
148 }
149
150 copied_count += service_data.len();
151 }
152
153 info!("Persisted {copied_count} tags across boot");
154 Ok(())
155}
156
157async fn read_data(path: &str) -> Result<Option<PersistenceData>, Error> {
158 match fuchsia_fs::file::read_in_namespace(path).await {
159 Ok(bytes) => Ok(serde_json::from_slice(&bytes).with_context(|| {
160 let s = String::from_utf8_lossy(&bytes);
161 format!("Failed to deserialize Persistence data from {path}: \"{s}\"")
162 })?),
163 Err(e) if e.is_not_found_error() => Ok(None),
164 Err(e) => {
165 bail!("Failed to read Persistence data from \"{path}\": {e:?}")
166 }
167 }
168}
169
170pub(crate) async fn current_data() -> Result<Option<PersistenceData>, Error> {
171 read_data(CURRENT_DATA).await
172}
173
174pub(crate) async fn previous_data() -> Result<Option<PersistenceData>, Error> {
175 read_data(PREVIOUS_DATA).await
176}
177
178pub(crate) async fn write_current_data(data: &PersistenceData) -> Result<(), Error> {
179 let file = fuchsia_fs::file::open_in_namespace(
180 TEMP_DATA,
181 fuchsia_fs::Flags::FLAG_MAYBE_CREATE
182 | fuchsia_fs::Flags::FILE_TRUNCATE
183 | fuchsia_fs::Flags::PERM_WRITE_BYTES,
184 )
185 .context("Failed to open temp Persistence data for writing")?;
186 let buf = serde_json::to_vec(data).context("Failed to serialize Persistence data")?;
187 fuchsia_fs::file::write(&file, &buf).await.context("Failed to write temp Persistence data")?;
188 std::fs::rename(TEMP_DATA, CURRENT_DATA).context("Failed to rename temp data to current")
189}
190
191#[cfg(test)]
192mod test {
193 use super::*;
194
195 fn make_timestamps(nanos: i64) -> Timestamps {
196 Timestamps {
197 last_sample_boot: zx::BootInstant::from_nanos(nanos),
198 last_sample_utc: fuchsia_runtime::UtcInstant::from_nanos(nanos),
199 }
200 }
201
202 #[fuchsia::test]
203 fn test_timestamps_merge() {
204 let mut timestamps_1 = make_timestamps(100);
205 let timestamps_2 = make_timestamps(200);
206
207 timestamps_1.merge(timestamps_2);
208
209 assert_eq!(timestamps_1.last_sample_boot.into_nanos(), 200);
211 assert_eq!(timestamps_1.last_sample_utc.into_nanos(), 200);
212
213 let timestamps_3 = make_timestamps(50);
214 let mut timestamps_4 = make_timestamps(300);
215
216 timestamps_4.merge(timestamps_3);
217
218 assert_eq!(timestamps_4.last_sample_boot.into_nanos(), 300);
220 assert_eq!(timestamps_4.last_sample_utc.into_nanos(), 300);
221 }
222}