microvms_edges/
run_store.rs1use std::io::{self, Write};
5use std::path::{Path, PathBuf};
6
7use microvms_app::run_store::{RunStore, StoredLedger};
8
9#[derive(Clone, Debug)]
13pub struct FileRunStore {
14 root: PathBuf,
15}
16
17impl FileRunStore {
18 pub fn new(root: impl Into<PathBuf>) -> Self {
19 Self { root: root.into() }
20 }
21
22 fn ledger_path(&self, key: &str) -> io::Result<PathBuf> {
23 Self::check_component(key)?;
24 Ok(self.root.join(format!("{key}.json")))
25 }
26
27 fn history_path(&self, vm_id: &str) -> io::Result<PathBuf> {
28 Self::check_component(vm_id)?;
29 Ok(self.root.join("history").join(format!("{vm_id}.jsonl")))
30 }
31
32 fn check_component(value: &str) -> io::Result<()> {
33 if value.is_empty() || matches!(value, "." | "..") || value.contains(['/', '\\', ':']) {
36 return Err(io::Error::new(
37 io::ErrorKind::InvalidInput,
38 "record key must be one file name component",
39 ));
40 }
41 Ok(())
42 }
43
44 fn prepare(path: &Path) -> io::Result<()> {
45 if let Some(parent) = path.parent() {
46 std::fs::create_dir_all(parent)?;
47 }
48 Ok(())
49 }
50}
51
52impl RunStore for FileRunStore {
53 fn ledgers(&self) -> Vec<StoredLedger> {
54 let Ok(entries) = std::fs::read_dir(&self.root) else {
55 return Vec::new();
56 };
57 entries
58 .filter_map(Result::ok)
59 .map(|entry| entry.path())
60 .filter(|path| path.extension().is_some_and(|ext| ext == "json"))
61 .map(|path| {
62 let Some(key) = path.file_stem().and_then(|stem| stem.to_str()) else {
63 return StoredLedger {
67 key: String::new(),
68 location: format!("{path:?}"),
69 text: None,
70 };
71 };
72 StoredLedger {
73 key: key.to_owned(),
74 location: path.to_string_lossy().into_owned(),
75 text: std::fs::read_to_string(&path).ok(),
76 }
77 })
78 .collect()
79 }
80
81 fn write_ledger(&self, key: &str, text: &str) -> io::Result<()> {
82 let path = self.ledger_path(key)?;
83 Self::prepare(&path)?;
84 std::fs::write(path, text)
85 }
86
87 fn remove_ledger(&self, key: &str) -> io::Result<()> {
88 std::fs::remove_file(self.ledger_path(key)?)
89 }
90
91 fn read_history(&self, vm_id: &str) -> io::Result<String> {
92 std::fs::read_to_string(self.history_path(vm_id)?)
93 }
94
95 fn append_history(&self, vm_id: &str, line: &str) -> io::Result<()> {
96 let path = self.history_path(vm_id)?;
97 Self::prepare(&path)?;
98 let mut file = std::fs::OpenOptions::new()
99 .append(true)
100 .create(true)
101 .open(path)?;
102 writeln!(file, "{line}")
103 }
104}