monty_fs/
overlay_state.rs1use std::{collections::BTreeMap, mem, ops::Bound};
8
9use cap_std::fs::Dir;
10
11use super::{
12 MountError,
13 common::{as_u64, mtime_secs},
14};
15
16pub(super) const ENTRY_MEMORY_USAGE: u64 = 256;
21
22#[derive(Debug, Default)]
27pub struct OverlayState {
28 entries: BTreeMap<String, OverlayEntry>,
34 memory_usage: u64,
36}
37
38impl OverlayState {
39 #[must_use]
41 pub fn new() -> Self {
42 Self::default()
43 }
44
45 #[must_use]
47 pub(super) fn get(&self, relative_path: &str) -> Option<&OverlayEntry> {
48 self.entries.get(relative_path)
49 }
50
51 #[must_use]
53 pub(super) fn memory_usage(&self) -> u64 {
54 self.memory_usage
55 }
56
57 pub(super) fn remove(&mut self, relative_path: &str) -> Option<OverlayEntry> {
59 let entry = self.entries.remove(relative_path)?;
60 self.memory_usage = self
61 .memory_usage
62 .saturating_sub(entry_memory_usage(relative_path, &entry));
63 Some(entry)
64 }
65
66 pub(super) fn insert(&mut self, relative_path: String, entry: OverlayEntry, limit: u64) -> Result<(), MountError> {
68 let projected = self.projected_usage(&relative_path, &entry);
69 if projected > limit {
70 Err(MountError::MemoryUsageLimitExceeded(limit))
71 } else {
72 self.entries.insert(relative_path, entry);
73 self.memory_usage = projected;
74 Ok(())
75 }
76 }
77
78 pub(super) fn insert_unchecked(&mut self, relative_path: String, entry: OverlayEntry) {
80 self.memory_usage = self.projected_usage(&relative_path, &entry);
81 self.entries.insert(relative_path, entry);
82 }
83
84 fn projected_usage(&self, relative_path: &str, entry: &OverlayEntry) -> u64 {
86 let old_usage = self
87 .entries
88 .get(relative_path)
89 .map_or(0, |old| entry_memory_usage(relative_path, old));
90 let new_usage = entry_memory_usage(relative_path, entry);
91 self.memory_usage.saturating_sub(old_usage).saturating_add(new_usage)
92 }
93
94 pub(super) fn append_file(
96 &mut self,
97 relative_path: &str,
98 data: &[u8],
99 mtime: f64,
100 limit: u64,
101 ) -> Result<bool, MountError> {
102 let Some(OverlayEntry::File(file)) = self.entries.get_mut(relative_path) else {
103 return Ok(false);
104 };
105 let projected = self.memory_usage.saturating_add(as_u64(data.len()));
106 if projected > limit {
107 Err(MountError::MemoryUsageLimitExceeded(limit))
108 } else {
109 file.content.extend_from_slice(data);
110 file.mtime = mtime;
111 self.memory_usage = projected;
112 Ok(true)
113 }
114 }
115
116 pub(super) fn check_file_replacement(
118 &self,
119 relative_path: &str,
120 content_len: usize,
121 limit: u64,
122 ) -> Result<(), MountError> {
123 let old_usage = self
124 .entries
125 .get(relative_path)
126 .map_or(0, |old| entry_memory_usage(relative_path, old));
127 let new_usage = base_entry_memory_usage(relative_path).saturating_add(as_u64(content_len));
128 let projected = self.memory_usage.saturating_sub(old_usage).saturating_add(new_usage);
129 if projected > limit {
130 Err(MountError::MemoryUsageLimitExceeded(limit))
131 } else {
132 Ok(())
133 }
134 }
135
136 pub(super) fn check_replacements<'a>(
138 &self,
139 replacements: impl IntoIterator<Item = (&'a str, &'a OverlayEntry)>,
140 limit: u64,
141 ) -> Result<(), MountError> {
142 let mut projected = self.memory_usage;
143 let mut replaced = BTreeMap::new();
144 for (path, entry) in replacements {
145 let old_usage = replaced
146 .get(path)
147 .copied()
148 .unwrap_or_else(|| self.entries.get(path).map_or(0, |old| entry_memory_usage(path, old)));
149 let new_usage = entry_memory_usage(path, entry);
150 projected = projected.saturating_sub(old_usage).saturating_add(new_usage);
151 replaced.insert(path, new_usage);
152 }
153 if projected > limit {
154 Err(MountError::MemoryUsageLimitExceeded(limit))
155 } else {
156 Ok(())
157 }
158 }
159
160 pub(super) fn prefix_iter(&self, prefix: &str) -> impl Iterator<Item = (&str, &OverlayEntry)> {
166 debug_assert!(prefix.is_empty() || prefix.ends_with('/'));
167
168 let upper_storage;
169 let bounds: (Bound<&str>, Bound<&str>) = if prefix.is_empty() {
170 (Bound::Unbounded, Bound::Unbounded)
171 } else {
172 upper_storage = {
173 let mut upper = prefix.to_owned();
174 upper.pop();
175 upper.push('0');
176 upper
177 };
178 (Bound::Included(prefix), Bound::Excluded(upper_storage.as_str()))
179 };
180
181 self.entries
182 .range::<str, _>(bounds)
183 .map(|(key, value)| (key.as_str(), value))
184 }
185}
186
187fn entry_memory_usage(relative_path: &str, entry: &OverlayEntry) -> u64 {
189 let variable = match entry {
190 OverlayEntry::File(file) => file.content.len(),
191 OverlayEntry::RealFileRef(file_ref) => file_ref.relative.len(),
192 OverlayEntry::Directory { .. } | OverlayEntry::Deleted => 0,
193 };
194 base_entry_memory_usage(relative_path).saturating_add(as_u64(variable))
195}
196
197fn base_entry_memory_usage(relative_path: &str) -> u64 {
199 ENTRY_MEMORY_USAGE
200 .saturating_add(as_u64(relative_path.len()))
201 .saturating_add(as_u64(mem::size_of::<OverlayEntry>()))
202}
203
204#[derive(Debug)]
206pub(super) enum OverlayEntry {
207 File(OverlayFile),
209
210 RealFileRef(OverlayFileRef),
213
214 Directory {
216 mtime: f64,
218 },
219
220 Deleted,
222}
223
224#[derive(Debug)]
226pub(super) struct OverlayFile {
227 pub content: Vec<u8>,
229 pub mtime: f64,
231}
232
233#[derive(Debug)]
239pub(super) struct OverlayFileRef {
240 pub relative: String,
242 pub mtime: f64,
244 pub size: i64,
246}
247
248impl OverlayFileRef {
249 #[must_use]
254 pub fn from_relative(dir: &Dir, relative: &str) -> Option<Self> {
255 let metadata = dir.symlink_metadata(relative).ok()?;
256 metadata.is_file().then(|| Self {
257 relative: relative.to_owned(),
258 mtime: mtime_secs(&metadata),
259 size: i64::try_from(metadata.len()).unwrap_or(i64::MAX),
260 })
261 }
262}