1use crate::ObjectRequestRef;
10#[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "32")))]
11use crate::ToObjectRequest;
12use crate::common::CreationMode;
13use crate::directory::dirents_sink;
14use crate::directory::entry::{DirectoryEntry, EntryInfo, OpenRequest, RequestFlags};
15use crate::directory::entry_container::{Directory, DirectoryWatcher};
16use crate::directory::helper::{AlreadyExists, DirectlyMutable, NotDirectory};
17use crate::directory::immutable::connection::ImmutableConnection;
18use crate::directory::traversal_position::TraversalPosition;
19use crate::directory::watchers::Watchers;
20use crate::directory::watchers::event_producers::{
21 SingleNameEventProducer, StaticVecEventProducer,
22};
23use crate::execution_scope::ExecutionScope;
24use crate::name::Name;
25use crate::node::Node;
26use crate::path::Path;
27use crate::protocols::ProtocolsExt;
28#[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "32")))]
29use flex_client::fidl::ServerEnd;
30use flex_fuchsia_io as fio;
31use fuchsia_sync::Mutex;
32use std::collections::BTreeMap;
33use std::collections::btree_map::Entry;
34use std::iter;
35use std::sync::Arc;
36use zx_status::Status;
37
38use super::entry::GetEntryInfo;
39
40pub struct Simple {
45 inner: Mutex<Inner>,
46
47 inode: u64,
49
50 not_found_handler: Option<Box<dyn Fn(&str) + Send + Sync + 'static>>,
51}
52
53struct Inner {
54 entries: BTreeMap<Name, Arc<dyn DirectoryEntry>>,
55
56 watchers: Watchers,
57}
58
59impl Simple {
60 pub fn new() -> Arc<Self> {
61 Self::new_with_inode(fio::INO_UNKNOWN)
62 }
63
64 pub(crate) fn new_with_inode(inode: u64) -> Arc<Self> {
65 Arc::new(Simple {
66 inner: Mutex::new(Inner { entries: BTreeMap::new(), watchers: Watchers::new() }),
67 inode,
68 not_found_handler: None,
69 })
70 }
71
72 #[doc(hidden)]
77 pub fn new_with_entries_and_inode(
78 entries: BTreeMap<Name, Arc<dyn DirectoryEntry>>,
79 inode: u64,
80 ) -> Arc<Self> {
81 Arc::new(Simple {
82 inner: Mutex::new(Inner { entries, watchers: Watchers::new() }),
83 inode,
84 not_found_handler: None,
85 })
86 }
87
88 pub fn new_with_not_found_handler(handler: impl Fn(&str) + Send + Sync + 'static) -> Arc<Self> {
92 Arc::new(Simple {
93 inner: Mutex::new(Inner { entries: BTreeMap::new(), watchers: Watchers::new() }),
94 inode: fio::INO_UNKNOWN,
95 not_found_handler: Some(Box::new(handler)),
96 })
97 }
98
99 pub fn get_entry(&self, name: &str) -> Result<Arc<dyn DirectoryEntry>, Status> {
101 crate::name::validate_name(name)?;
102
103 let this = self.inner.lock();
104 match this.entries.get(name) {
105 Some(entry) => Ok(entry.clone()),
106 None => Err(Status::NOT_FOUND),
107 }
108 }
109
110 pub fn get_or_insert<T: DirectoryEntry>(
112 &self,
113 name: Name,
114 f: impl FnOnce() -> Arc<T>,
115 ) -> Arc<dyn DirectoryEntry> {
116 let mut guard = self.inner.lock();
117 let inner = &mut *guard;
118 match inner.entries.entry(name) {
119 Entry::Vacant(slot) => {
120 inner.watchers.send_event(&mut SingleNameEventProducer::added(slot.key()));
121 slot.insert(f()).clone()
122 }
123 Entry::Occupied(entry) => entry.get().clone(),
124 }
125 }
126
127 pub fn remove_all_entries(&self) {
129 let mut inner = self.inner.lock();
130 if !inner.entries.is_empty() {
131 let names = std::mem::take(&mut inner.entries)
132 .into_keys()
133 .map(String::from)
134 .collect::<Vec<String>>();
135 inner.watchers.send_event(&mut StaticVecEventProducer::removed(names));
136 }
137 }
138
139 fn open_impl<'a, P: ProtocolsExt + ToRequestFlags>(
140 self: Arc<Self>,
141 mut scope: ExecutionScope,
142 mut path: Path,
143 protocols: P,
144 object_request: ObjectRequestRef<'_>,
145 ) -> Result<(), Status> {
146 let (name, path_ref) = match path.next_with_ref() {
149 (path_ref, Some(name)) => (name, path_ref),
150 (_, None) => {
151 if protocols.create_unnamed_temporary_in_directory_path() {
152 return Err(Status::NOT_SUPPORTED);
154 }
155 object_request
156 .take()
157 .create_connection_sync::<ImmutableConnection<_>, _>(scope, self, protocols);
158 return Ok(());
159 }
160 };
161
162 let _guard;
164 let entry = match self.inner.lock().entries.get(name) {
165 Some(entry) => {
166 if let Some(s) = entry.scope() {
168 let Some(g) = s.try_active_guard() else {
170 return Err(Status::PEER_CLOSED);
171 };
172 scope = s;
173 _guard = g;
174 }
175 Some(entry.clone())
176 }
177 None => None,
178 };
179
180 match (entry, path_ref.is_empty(), protocols.creation_mode()) {
181 (None, false, _) | (None, true, CreationMode::Never) => {
182 if let Some(not_found_handler) = &self.not_found_handler {
187 not_found_handler(path_ref.as_str());
188 }
189 Err(Status::NOT_FOUND)
190 }
191 (
192 None,
193 true,
194 CreationMode::Always
195 | CreationMode::AllowExisting
196 | CreationMode::UnnamedTemporary
197 | CreationMode::UnlinkableUnnamedTemporary,
198 ) => {
199 Err(Status::NOT_SUPPORTED)
202 }
203 (
204 Some(_),
205 true,
206 CreationMode::UnnamedTemporary | CreationMode::UnlinkableUnnamedTemporary,
207 ) => {
208 Err(Status::NOT_SUPPORTED)
212 }
213 (Some(_), true, CreationMode::Always) => {
214 Err(Status::ALREADY_EXISTS)
217 }
218 (Some(entry), _, _) => entry.open_entry(OpenRequest::new(
219 scope,
220 protocols.to_request_flags(),
221 path,
222 object_request,
223 )),
224 }
225 }
226}
227
228impl GetEntryInfo for Simple {
229 fn entry_info(&self) -> EntryInfo {
230 EntryInfo::new(self.inode, fio::DirentType::Directory)
231 }
232}
233
234impl DirectoryEntry for Simple {
235 fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status> {
236 request.open_dir(self)
237 }
238}
239
240impl Node for Simple {
241 async fn get_attributes(
242 &self,
243 requested_attributes: fio::NodeAttributesQuery,
244 ) -> Result<fio::NodeAttributes2, Status> {
245 Ok(immutable_attributes!(
246 requested_attributes,
247 Immutable {
248 protocols: fio::NodeProtocolKinds::DIRECTORY,
249 abilities: fio::Operations::GET_ATTRIBUTES
250 | fio::Operations::ENUMERATE
251 | fio::Operations::TRAVERSE,
252 id: self.inode,
253 }
254 ))
255 }
256}
257
258impl Directory for Simple {
259 #[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "32")))]
260 fn deprecated_open(
261 self: Arc<Self>,
262 scope: ExecutionScope,
263 flags: fio::OpenFlags,
264 path: Path,
265 server_end: ServerEnd<fio::NodeMarker>,
266 ) {
267 flags
268 .to_object_request(server_end)
269 .handle(|object_request| self.open_impl(scope, path, flags, object_request));
270 }
271
272 fn open(
273 self: Arc<Self>,
274 scope: ExecutionScope,
275 path: Path,
276 flags: fio::Flags,
277 object_request: ObjectRequestRef<'_>,
278 ) -> Result<(), Status> {
279 self.open_impl(scope, path, flags, object_request)
280 }
281
282 async fn read_dirents(
283 &self,
284 pos: &TraversalPosition,
285 sink: Box<dyn dirents_sink::Sink>,
286 ) -> Result<(TraversalPosition, Box<dyn dirents_sink::Sealed>), Status> {
287 use dirents_sink::AppendResult;
288
289 let this = self.inner.lock();
290
291 let (mut sink, entries_iter) = match pos {
292 TraversalPosition::Start => {
293 match sink.append(&EntryInfo::new(self.inode, fio::DirentType::Directory), ".") {
294 AppendResult::Ok(sink) => (sink, this.entries.range::<Name, _>(..)),
295 AppendResult::Sealed(sealed) => {
296 return Ok((TraversalPosition::Start, sealed));
297 }
298 }
299 }
300
301 TraversalPosition::Name(next_name) => {
302 let next: Name = next_name.to_owned().try_into().unwrap();
306 (sink, this.entries.range::<Name, _>(next..))
307 }
308
309 TraversalPosition::Bytes(_) | TraversalPosition::Index(_) => unreachable!(),
310
311 TraversalPosition::End => return Ok((TraversalPosition::End, sink.seal())),
312 };
313
314 for (name, entry) in entries_iter {
315 match sink.append(&entry.entry_info(), &name) {
316 AppendResult::Ok(new_sink) => sink = new_sink,
317 AppendResult::Sealed(sealed) => {
318 return Ok((TraversalPosition::Name(name.clone().into()), sealed));
319 }
320 }
321 }
322
323 Ok((TraversalPosition::End, sink.seal()))
324 }
325
326 fn register_watcher(
327 self: Arc<Self>,
328 scope: ExecutionScope,
329 mask: fio::WatchMask,
330 watcher: DirectoryWatcher,
331 ) -> Result<(), Status> {
332 let mut this = self.inner.lock();
333
334 let mut names = StaticVecEventProducer::existing({
335 let entry_names = this.entries.keys();
336 iter::once(".".to_string()).chain(entry_names.map(|x| x.to_owned().into())).collect()
337 });
338
339 let controller = this.watchers.add(scope, self.clone(), mask, watcher);
340 controller.send_event(&mut names);
341 controller.send_event(&mut SingleNameEventProducer::idle());
342
343 Ok(())
344 }
345
346 fn unregister_watcher(self: Arc<Self>, key: usize) {
347 let mut this = self.inner.lock();
348 this.watchers.remove(key);
349 }
350}
351
352impl DirectlyMutable for Simple {
353 fn add_entry_impl(
354 &self,
355 name: Name,
356 entry: Arc<dyn DirectoryEntry>,
357 overwrite: bool,
358 ) -> Result<(), AlreadyExists> {
359 let mut this = self.inner.lock();
360
361 if !overwrite && this.entries.contains_key(&name) {
362 return Err(AlreadyExists);
363 }
364
365 this.watchers.send_event(&mut SingleNameEventProducer::added(&name));
366
367 let _ = this.entries.insert(name, entry);
368 Ok(())
369 }
370
371 fn remove_entry_impl(
372 &self,
373 name: Name,
374 must_be_directory: bool,
375 ) -> Result<Option<Arc<dyn DirectoryEntry>>, NotDirectory> {
376 let mut this = self.inner.lock();
377
378 match this.entries.entry(name) {
379 Entry::Vacant(_) => Ok(None),
380 Entry::Occupied(occupied) => {
381 if must_be_directory
382 && occupied.get().entry_info().type_() != fio::DirentType::Directory
383 {
384 Err(NotDirectory)
385 } else {
386 let (key, value) = occupied.remove_entry();
387 this.watchers.send_event(&mut SingleNameEventProducer::removed(&key));
388 Ok(Some(value))
389 }
390 }
391 }
392 }
393}
394
395trait ToRequestFlags {
396 fn to_request_flags(&self) -> RequestFlags;
397}
398
399#[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "32")))]
400impl ToRequestFlags for fio::OpenFlags {
401 fn to_request_flags(&self) -> RequestFlags {
402 RequestFlags::Open1(*self)
403 }
404}
405
406impl ToRequestFlags for fio::Flags {
407 fn to_request_flags(&self) -> RequestFlags {
408 RequestFlags::Open3(*self)
409 }
410}
411
412#[doc(hidden)]
413pub mod __private {
414 pub const INO_UNKNOWN: u64 = flex_fuchsia_io::INO_UNKNOWN;
415}
416
417#[macro_export]
442macro_rules! pseudo_directory {
443 ( $( $name:expr => $entry:expr ),* $(,)? ) => {{
444 let entries = ::std::collections::BTreeMap::from([
445 $(
446 (
447 $crate::name::Name::from_static($name),
448 $entry as ::std::sync::Arc<dyn $crate::directory::entry::DirectoryEntry>,
449 ),
450 )*
451 ]);
452 ::std::assert_eq!(
455 entries.len(),
456 <[()]>::len(&[ $( $crate::__replace_with_unit_type!($name) ),* ]),
457 "Duplicate entries in pseudo_directory!"
458 );
459 $crate::directory::immutable::Simple::new_with_entries_and_inode(
460 entries,
461 $crate::directory::simple::__private::INO_UNKNOWN,
462 )
463 }};
464}
465
466#[doc(hidden)]
467#[macro_export]
468macro_rules! __replace_with_unit_type {
469 ($_t:tt) => {
470 ()
471 };
472}
473
474#[cfg(test)]
475mod tests {
476 use super::*;
477 use crate::directory::immutable::Simple;
478 use crate::file;
479 use crate::object_request::ObjectRequest;
480
481 #[test]
482 fn add_entry_success() {
483 let dir = Simple::new();
484 assert_eq!(
485 dir.add_entry("path_without_separators", file::read_only(b"test")),
486 Ok(()),
487 "add entry with valid filename should succeed"
488 );
489 }
490
491 #[test]
492 fn add_entry_error_name_with_path_separator() {
493 let dir = Simple::new();
494 let status = dir
495 .add_entry("path/with/separators", file::read_only(b"test"))
496 .expect_err("add entry with path separator should fail");
497 assert_eq!(status, Status::INVALID_ARGS);
498 }
499
500 #[test]
501 fn add_entry_error_name_too_long() {
502 let dir = Simple::new();
503 let status = dir
504 .add_entry("a".repeat(10000), file::read_only(b"test"))
505 .expect_err("add entry whose name is too long should fail");
506 assert_eq!(status, Status::BAD_PATH);
507 }
508
509 #[fuchsia::test]
510 async fn not_found_handler() {
511 let path_mutex = Arc::new(Mutex::new(None));
512 let path_mutex_clone = path_mutex.clone();
513 let dir = Simple::new_with_not_found_handler(move |path| {
514 *path_mutex_clone.lock() = Some(path.to_string());
515 });
516
517 let path_mutex_clone = path_mutex.clone();
518 let sub_dir = Simple::new_with_not_found_handler(move |path| {
519 *path_mutex_clone.lock() = Some(path.to_string());
520 });
521 dir.add_entry("dir", sub_dir).expect("add entry with valid filename should succeed");
522
523 dir.add_entry("file", file::read_only(b"test"))
524 .expect("add entry with valid filename should succeed");
525
526 #[cfg(feature = "fdomain")]
527 let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
528 #[cfg(not(feature = "fdomain"))]
529 let scope = crate::execution_scope::ExecutionScope::new();
530
531 for (path, expectation) in vec![
532 (".", None),
533 ("does-not-exist", Some("does-not-exist".to_string())),
534 ("file", None),
535 ("dir", None),
536 ("dir/does-not-exist", Some("dir/does-not-exist".to_string())),
537 ] {
538 log::info!("{path}");
539 #[cfg(feature = "fdomain")]
540 let (_proxy, server_end) = {
541 let client = scope.domain();
542 client.create_proxy::<fio::NodeMarker>()
543 };
544 #[cfg(not(feature = "fdomain"))]
545 let (_proxy, server_end) = fidl::endpoints::create_proxy::<fio::NodeMarker>();
546 let flags = fio::Flags::PROTOCOL_NODE | fio::Flags::FLAG_SEND_REPRESENTATION;
547 let path = Path::validate_and_split(path).unwrap();
548 ObjectRequest::new(flags, &fio::Options::default(), server_end.into_channel().into())
549 .handle(|request| dir.clone().open(scope.clone(), path, flags, request));
550
551 assert_eq!(expectation, path_mutex.lock().take());
552 }
553 }
554
555 #[test]
556 fn remove_all_entries() {
557 let dir = Simple::new();
558
559 dir.add_entry("file", file::read_only(""))
560 .expect("add entry with valid filename should succeed");
561
562 dir.remove_all_entries();
563 assert_eq!(
564 dir.get_entry("file").err().expect("file should no longer exist"),
565 Status::NOT_FOUND
566 );
567 }
568
569 #[fuchsia::test]
570 async fn test_alternate_scope() {
571 struct MockEntry(ExecutionScope);
572
573 impl DirectoryEntry for MockEntry {
574 fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status> {
575 assert_eq!(request.scope(), &self.0);
576 Ok(())
577 }
578
579 fn scope(&self) -> Option<ExecutionScope> {
580 Some(self.0.clone())
581 }
582 }
583
584 impl GetEntryInfo for MockEntry {
585 fn entry_info(&self) -> EntryInfo {
586 EntryInfo::new(1, fio::DirentType::Directory)
587 }
588 }
589
590 let dir = Simple::new();
591
592 #[cfg(feature = "fdomain")]
593 let dummy_scope =
594 crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
595 #[cfg(not(feature = "fdomain"))]
596 let dummy_scope = crate::execution_scope::ExecutionScope::new();
597
598 dir.add_entry("foo", Arc::new(MockEntry(dummy_scope))).expect("add_entry failed");
599
600 #[cfg(feature = "fdomain")]
601 let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
602 #[cfg(not(feature = "fdomain"))]
603 let scope = crate::execution_scope::ExecutionScope::new();
604 #[cfg(feature = "fdomain")]
605 let (_proxy, server) = scope.domain().create_proxy::<fio::DirectoryMarker>();
606 #[cfg(not(feature = "fdomain"))]
607 let (_client, server) = fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
608 let mut request = ObjectRequest::new(
609 fio::Flags::empty(),
610 &fio::Options::default(),
611 server.into_channel().into(),
612 );
613 dir.open(scope, Path::dot(), fio::Flags::empty(), &mut request).expect("open succeeded");
614 }
615}