1use std::{
4 collections::{BTreeMap, BTreeSet},
5 sync::{Arc, Mutex},
6};
7
8use sim_kernel::{
9 Cx, Error, Expr, Object, Result, Symbol, Value,
10 id::CORE_TABLE_CLASS_ID,
11 object::ClassRef,
12 table::{Dir, Table},
13};
14use sim_table_core::TablePath;
15
16use crate::{
17 routing::{
18 ResolvedNode, check_segment, format_path, has_mount_descendant, is_prefix, join_child,
19 longest_mount, owned_path, require_dir_value, require_table_value, resolve_mounted_target,
20 traverse_dir_path,
21 },
22 table_mount_capability,
23};
24
25pub(crate) type MountPath = Vec<String>;
26
27#[derive(Clone)]
28pub(crate) struct MountTarget {
29 pub(crate) kind: MountKind,
30 pub(crate) value: Value,
31}
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub enum MountKind {
36 Table,
38 Dir,
40}
41
42impl MountKind {
43 pub(crate) fn label(self) -> &'static str {
44 match self {
45 Self::Table => "table",
46 Self::Dir => "dir",
47 }
48 }
49}
50
51#[derive(Clone, Debug, PartialEq, Eq)]
53pub struct MountInspection {
54 pub path: String,
56 pub kind: MountKind,
58}
59
60#[derive(Clone)]
62pub struct MountedDir {
63 root: Value,
64 mounts: Arc<Mutex<BTreeMap<MountPath, MountTarget>>>,
65 path: MountPath,
66}
67
68impl MountedDir {
69 pub fn new(root: Value) -> Result<Self> {
73 require_dir_value(&root, "table/mount: root must be a Dir")?;
74 Ok(Self {
75 root,
76 mounts: Arc::new(Mutex::new(BTreeMap::new())),
77 path: Vec::new(),
78 })
79 }
80
81 pub fn mount_dir(&self, cx: &mut Cx, path: TablePath, target: Value) -> Result<()> {
83 cx.require(&table_mount_capability())?;
84 require_dir_value(&target, "table/mount: Dir mount target must be a Dir")?;
85 self.insert_mount(cx, path, MountKind::Dir, target)
86 }
87
88 pub fn mount_table(&self, cx: &mut Cx, path: TablePath, target: Value) -> Result<()> {
90 cx.require(&table_mount_capability())?;
91 require_table_value(&target, "table/mount: table mount target must be a Table")?;
92 self.insert_mount(cx, path, MountKind::Table, target)
93 }
94
95 pub fn unmount(&self, cx: &mut Cx, path: &TablePath) -> Result<Option<Value>> {
97 cx.require(&table_mount_capability())?;
98 let path = owned_path(path);
99 if path.is_empty() {
100 return Err(Error::Eval("table/mount: cannot unmount root".to_owned()));
101 }
102 Ok(self.lock_mounts()?.remove(&path).map(|target| target.value))
103 }
104
105 pub fn inspect(&self) -> Result<Vec<MountInspection>> {
107 Ok(self
108 .lock_mounts()?
109 .iter()
110 .map(|(path, target)| MountInspection {
111 path: format_path(path),
112 kind: target.kind,
113 })
114 .collect())
115 }
116
117 fn at_path(&self, path: MountPath) -> Self {
118 Self {
119 root: self.root.clone(),
120 mounts: self.mounts.clone(),
121 path,
122 }
123 }
124
125 fn insert_mount(
126 &self,
127 cx: &mut Cx,
128 path: TablePath,
129 kind: MountKind,
130 target: Value,
131 ) -> Result<()> {
132 let path = owned_path(&path);
133 if path.is_empty() {
134 return Err(Error::Eval("table/mount: cannot mount root".to_owned()));
135 }
136 if self.root_exact_kind(cx, &path)?.is_some() {
137 return Err(Error::Eval(format!(
138 "table/mount: mount point {} shadows an existing root node",
139 format_path(&path)
140 )));
141 }
142
143 let mut mounts = self.lock_mounts()?;
144 if mounts.contains_key(&path) {
145 return Err(Error::Eval(format!(
146 "table/mount: duplicate mount point {}",
147 format_path(&path)
148 )));
149 }
150 for (existing_path, existing) in mounts.iter() {
151 if is_prefix(existing_path, &path)
152 && existing.kind == MountKind::Table
153 && existing_path.len() < path.len()
154 {
155 return Err(Error::Eval(format!(
156 "table/mount: cannot mount below table leaf {}",
157 format_path(existing_path)
158 )));
159 }
160 if is_prefix(&path, existing_path) && kind == MountKind::Table {
161 return Err(Error::Eval(format!(
162 "table/mount: table mount {} would parent existing mount {}",
163 format_path(&path),
164 format_path(existing_path)
165 )));
166 }
167 }
168 mounts.insert(
169 path,
170 MountTarget {
171 kind,
172 value: target,
173 },
174 );
175 Ok(())
176 }
177
178 fn lock_mounts(&self) -> Result<std::sync::MutexGuard<'_, BTreeMap<MountPath, MountTarget>>> {
179 self.mounts
180 .lock()
181 .map_err(|_| Error::Eval("table/mount: mount registry lock poisoned".to_owned()))
182 }
183
184 fn mount_snapshot(&self) -> Result<BTreeMap<MountPath, MountTarget>> {
185 Ok(self.lock_mounts()?.clone())
186 }
187
188 fn resolve_current(&self, cx: &mut Cx) -> Result<ResolvedNode> {
189 let mounts = self.mount_snapshot()?;
190 if let Some((prefix_len, target)) = longest_mount(&mounts, &self.path) {
191 return resolve_mounted_target(cx, target, &self.path[prefix_len..]);
192 }
193 match traverse_dir_path(cx, self.root.clone(), &self.path)? {
194 Some(value) => Ok(ResolvedNode::Backed(value)),
195 None if has_mount_descendant(&mounts, &self.path) => Ok(ResolvedNode::Virtual),
196 None => Err(Error::Eval(format!(
197 "table/mount: {} has no backing directory",
198 format_path(&self.path)
199 ))),
200 }
201 }
202
203 fn root_exact_kind(&self, cx: &mut Cx, path: &[String]) -> Result<Option<MountKind>> {
204 if path.is_empty() {
205 return Ok(Some(MountKind::Dir));
206 }
207 let parent_path = &path[..path.len() - 1];
208 let Some(parent) = traverse_dir_path(cx, self.root.clone(), parent_path)? else {
209 return Ok(None);
210 };
211 let dir = require_dir_value(&parent, "table/mount: root path parent is not a Dir")?;
212 let table = require_table_value(&parent, "table/mount: root path parent is not a Table")?;
213 let name = Symbol::new(path.last().expect("non-empty path").as_str());
214 if dir.is_dir(cx, name.clone())? {
215 return Ok(Some(MountKind::Dir));
216 }
217 if table.has(cx, name)? {
218 return Ok(Some(MountKind::Table));
219 }
220 Ok(None)
221 }
222
223 fn direct_mount_children(&self) -> Result<BTreeMap<Symbol, MountKind>> {
224 let mut children = BTreeMap::new();
225 for (path, target) in self.lock_mounts()?.iter() {
226 if path.len() <= self.path.len() || !is_prefix(&self.path, path) {
227 continue;
228 }
229 let name = Symbol::new(path[self.path.len()].as_str());
230 let kind = if path.len() == self.path.len() + 1 {
231 target.kind
232 } else {
233 MountKind::Dir
234 };
235 children.entry(name).or_insert(kind);
236 }
237 Ok(children)
238 }
239
240 fn child_has_mount(&self, name: &Symbol) -> Result<bool> {
241 let mut child = self.path.clone();
242 child.push(name.name.to_string());
243 Ok(self
244 .lock_mounts()?
245 .keys()
246 .any(|path| is_prefix(&child, path)))
247 }
248
249 fn direct_mount_value(&self, name: &Symbol) -> Result<Option<MountTarget>> {
250 let mut child = self.path.clone();
251 child.push(name.name.to_string());
252 Ok(self.lock_mounts()?.get(&child).cloned())
253 }
254
255 fn check_no_mount_child(&self, name: &Symbol, action: &str) -> Result<()> {
256 if self.child_has_mount(name)? {
257 return Err(Error::Eval(format!(
258 "table/mount: cannot {action} {} while a mount exists there",
259 join_child(&self.path, name)
260 )));
261 }
262 Ok(())
263 }
264
265 fn backed_table(node: ResolvedNode) -> Result<Option<Value>> {
266 match node {
267 ResolvedNode::Backed(value) => {
268 require_table_value(&value, "table/mount: backing node is not a Table")?;
269 Ok(Some(value))
270 }
271 ResolvedNode::Virtual => Ok(None),
272 }
273 }
274}
275
276impl Object for MountedDir {
277 fn display(&self, _cx: &mut Cx) -> Result<String> {
278 Ok(format!(
279 "table/mount[{}; mounts={}]",
280 format_path(&self.path),
281 self.lock_mounts()?.len()
282 ))
283 }
284
285 fn as_any(&self) -> &dyn std::any::Any {
286 self
287 }
288}
289
290impl sim_kernel::ObjectCompat for MountedDir {
291 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
292 let symbol = Symbol::qualified("core", "Table");
293 if let Some(value) = cx.registry().class_by_symbol(&symbol) {
294 return Ok(value.clone());
295 }
296 cx.factory().class_stub(CORE_TABLE_CLASS_ID, symbol)
297 }
298
299 fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
300 self.as_table_expr(cx)
301 }
302
303 fn truth(&self, cx: &mut Cx) -> Result<bool> {
304 Ok(!self.is_empty(cx)?)
305 }
306
307 fn as_table_impl(&self) -> Option<&dyn Table> {
308 Some(self)
309 }
310
311 fn as_dir(&self) -> Option<&dyn Dir> {
312 Some(self)
313 }
314}
315
316impl Table for MountedDir {
317 fn backend_symbol(&self) -> Symbol {
318 Symbol::qualified("table", "mount")
319 }
320
321 fn get(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
322 if let Some(target) = self.direct_mount_value(&key)? {
323 return match target.kind {
324 MountKind::Table => Ok(target.value),
325 MountKind::Dir => cx.factory().nil(),
326 };
327 }
328 let node = self.resolve_current(cx)?;
329 let Some(value) = Self::backed_table(node)? else {
330 return cx.factory().nil();
331 };
332 require_table_value(&value, "table/mount: backing node is not a Table")?.get(cx, key)
333 }
334
335 fn set(&self, cx: &mut Cx, key: Symbol, value: Value) -> Result<()> {
336 self.check_no_mount_child(&key, "set")?;
337 let node = self.resolve_current(cx)?;
338 let Some(backing) = Self::backed_table(node)? else {
339 return Err(Error::Eval(format!(
340 "table/mount: {} has no writable backing table",
341 format_path(&self.path)
342 )));
343 };
344 require_table_value(&backing, "table/mount: backing node is not a Table")?
345 .set(cx, key, value)
346 }
347
348 fn has(&self, cx: &mut Cx, key: Symbol) -> Result<bool> {
349 if self.child_has_mount(&key)? {
350 return Ok(true);
351 }
352 let node = self.resolve_current(cx)?;
353 let Some(backing) = Self::backed_table(node)? else {
354 return Ok(false);
355 };
356 require_table_value(&backing, "table/mount: backing node is not a Table")?.has(cx, key)
357 }
358
359 fn del(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
360 self.check_no_mount_child(&key, "delete")?;
361 let node = self.resolve_current(cx)?;
362 let Some(backing) = Self::backed_table(node)? else {
363 return cx.factory().nil();
364 };
365 require_table_value(&backing, "table/mount: backing node is not a Table")?.del(cx, key)
366 }
367
368 fn keys(&self, cx: &mut Cx) -> Result<Vec<Symbol>> {
369 let mut keys = BTreeSet::new();
370 let node = self.resolve_current(cx)?;
371 if let Some(backing) = Self::backed_table(node)? {
372 keys.extend(
373 require_table_value(&backing, "table/mount: backing node is not a Table")?
374 .keys(cx)?,
375 );
376 }
377 keys.extend(self.direct_mount_children()?.into_keys());
378 Ok(keys.into_iter().collect())
379 }
380
381 fn entries(&self, cx: &mut Cx) -> Result<Vec<(Symbol, Value)>> {
382 let mount_children = self.direct_mount_children()?;
383 let mut out = Vec::new();
384 let mut seen = BTreeSet::new();
385 let node = self.resolve_current(cx)?;
386 if let Some(backing) = Self::backed_table(node)? {
387 for (key, value) in
388 require_table_value(&backing, "table/mount: backing node is not a Table")?
389 .entries(cx)?
390 {
391 if mount_children.contains_key(&key) {
392 return Err(Error::Eval(format!(
393 "table/mount: backing entry {key} conflicts with a mount"
394 )));
395 }
396 seen.insert(key.clone());
397 out.push((key, value));
398 }
399 }
400 for (key, kind) in mount_children {
401 if kind == MountKind::Table && seen.insert(key.clone()) {
402 let target = self
403 .direct_mount_value(&key)?
404 .expect("mount child collected from registry");
405 out.push((key, target.value));
406 }
407 }
408 Ok(out)
409 }
410
411 fn len(&self, cx: &mut Cx) -> Result<usize> {
412 Ok(self.entries(cx)?.len())
413 }
414
415 fn clear(&self, cx: &mut Cx) -> Result<()> {
416 if let Some(name) = self.direct_mount_children()?.keys().next().cloned() {
417 return Err(Error::Eval(format!(
418 "table/mount: cannot clear {} while mount child {name} exists",
419 format_path(&self.path)
420 )));
421 }
422 let node = self.resolve_current(cx)?;
423 let Some(backing) = Self::backed_table(node)? else {
424 return Ok(());
425 };
426 require_table_value(&backing, "table/mount: backing node is not a Table")?.clear(cx)
427 }
428}
429
430impl Dir for MountedDir {
431 fn mkdir(&self, cx: &mut Cx, name: Symbol) -> Result<Value> {
432 check_segment(&name)?;
433 self.check_no_mount_child(&name, "mkdir")?;
434 let ResolvedNode::Backed(value) = self.resolve_current(cx)? else {
435 return Err(Error::Eval(format!(
436 "table/mount: {} has no backing directory",
437 format_path(&self.path)
438 )));
439 };
440 require_dir_value(&value, "table/mount: backing node is not a Dir")?.mkdir(cx, name)
441 }
442
443 fn opendir(&self, cx: &mut Cx, name: Symbol) -> Result<Option<Value>> {
444 check_segment(&name)?;
445 let mut child_path = self.path.clone();
446 child_path.push(name.name.to_string());
447 if let Some(target) = self.direct_mount_value(&name)? {
448 return match target.kind {
449 MountKind::Dir => cx
450 .factory()
451 .opaque(Arc::new(self.at_path(child_path)))
452 .map(Some),
453 MountKind::Table => Err(Error::Eval(format!(
454 "table/mount: {} is a table mount, not a directory",
455 join_child(&self.path, &name)
456 ))),
457 };
458 }
459 if self.child_has_mount(&name)? {
460 return cx
461 .factory()
462 .opaque(Arc::new(self.at_path(child_path)))
463 .map(Some);
464 }
465 let ResolvedNode::Backed(value) = self.resolve_current(cx)? else {
466 return Ok(None);
467 };
468 let dir = require_dir_value(&value, "table/mount: backing node is not a Dir")?;
469 if dir.opendir(cx, name)?.is_some() {
470 return cx
471 .factory()
472 .opaque(Arc::new(self.at_path(child_path)))
473 .map(Some);
474 }
475 Ok(None)
476 }
477
478 fn rmdir(&self, cx: &mut Cx, name: Symbol) -> Result<Value> {
479 check_segment(&name)?;
480 self.check_no_mount_child(&name, "remove directory")?;
481 let ResolvedNode::Backed(value) = self.resolve_current(cx)? else {
482 return cx.factory().nil();
483 };
484 require_dir_value(&value, "table/mount: backing node is not a Dir")?.rmdir(cx, name)
485 }
486
487 fn is_dir(&self, cx: &mut Cx, name: Symbol) -> Result<bool> {
488 check_segment(&name)?;
489 if let Some(target) = self.direct_mount_value(&name)? {
490 return Ok(target.kind == MountKind::Dir);
491 }
492 if self.child_has_mount(&name)? {
493 return Ok(true);
494 }
495 let ResolvedNode::Backed(value) = self.resolve_current(cx)? else {
496 return Ok(false);
497 };
498 require_dir_value(&value, "table/mount: backing node is not a Dir")?.is_dir(cx, name)
499 }
500}