Skip to main content

vfs/directory/
simple.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! This is an implementation of "simple" pseudo directories.
6//! Use [`crate::directory::immutable::Simple::new()`]
7//! to construct actual instances.  See [`Simple`] for details.
8
9use 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
40/// An implementation of a "simple" pseudo directory.  This directory holds a set of entries,
41/// allowing the server to add or remove entries via the
42/// [`crate::directory::helper::DirectlyMutable::add_entry()`] and
43/// [`crate::directory::helper::DirectlyMutable::remove_entry`] methods.
44pub struct Simple {
45    inner: Mutex<Inner>,
46
47    // The inode for this directory. This should either be unique within this VFS, or INO_UNKNOWN.
48    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    /// Constructs a new pseudo directory with the provided entries.
73    ///
74    /// This function is marked `pub` only so the `pseudo_directory!` macro can use it. It should
75    /// not be used directly outside of this crate.
76    #[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    /// Creates a new directory with the provided function that will be called whenever this VFS
89    /// receives an open request for a path that is not present in the directory. The handler is
90    /// invoked with the full path of the missing entry, relative to the root.
91    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    /// Returns the entry identified by `name`.
100    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    /// Gets or inserts an entry (as supplied by the callback `f`).
111    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    /// Removes all entries from the directory.
128    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        // See if the path has a next segment, if so we want to traverse down the directory.
147        // Otherwise we've arrived at the right directory.
148        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                    // Creating an entry is not supported.
153                    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        // Don't hold the inner lock while opening the entry in case the directory contains itself.
163        let _guard;
164        let entry = match self.inner.lock().entries.get(name) {
165            Some(entry) => {
166                // Whilst we are holding the lock, see if an alternative scope should be used.
167                if let Some(s) = entry.scope() {
168                    // Make sure we can get an active guard.
169                    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                // Either:
183                //   - we're at an intermediate directory and the next entry doesn't exist, or
184                //   - we're at the last directory and the next entry doesn't exist and creating the
185                //     entry wasn't requested.
186                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                // We're at the last directory and the entry doesn't exist and creating the entry
200                // was requested which isn't supported.
201                Err(Status::NOT_SUPPORTED)
202            }
203            (
204                Some(_),
205                true,
206                CreationMode::UnnamedTemporary | CreationMode::UnlinkableUnnamedTemporary,
207            ) => {
208                // We're at the last directory and the entry exists and it was requested to create
209                // an unnamed temporary object in this entry (this is not supported for simple
210                // pseudo directory).
211                Err(Status::NOT_SUPPORTED)
212            }
213            (Some(_), true, CreationMode::Always) => {
214                // We're at the last directory and the entry exists but creating the entry is
215                // required.
216                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                // The only way to get a `TraversalPosition::Name` is if we returned it in the
303                // `AppendResult::Sealed` code path below. Therefore, the conversion from
304                // `next_name` to `Name` will never fail in practice.
305                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/// Builds a pseudo directory using a simple DSL. The directory entry names must be static strings
418/// (`&'static str`).
419///
420/// # Examples
421///
422/// This will construct a small tree of read-only files:
423/// ```
424/// let root = pseudo_directory! {
425///     "etc" => pseudo_directory! {
426///         "fstab" => read_only(b"/dev/fs /"),
427///         "passwd" => read_only(b"[redacted]"),
428///         "shells" => read_only(b"/bin/bash"),
429///         "ssh" => pseudo_directory! {
430///           "sshd_config" => read_only(b"# Empty"),
431///         },
432///     },
433///     "uname" => read_only(b"Fuchsia"),
434/// };
435/// ```
436///
437/// # Panics
438///
439/// This macro will panic if there are duplicate entries or any of the entry names are invalid. See
440/// [`name::validate_name`] for the restrictions.
441#[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        // Check for duplicate entries by comparing the length of the constructed map with the
453        // number of entries passed in.
454        ::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}